2015-02-09 updates
- [react-server] Support multiple roots | Amjad Masad - [react-packager] Add a helpful error message when watcher fails to start | Amjad Masad - [madman] Fix review screen button and animation | Eric Vicenti
This commit is contained in:
parent
d29d07ee06
commit
ff2f1ce945
|
@ -88,7 +88,7 @@ class Subscribable {
|
|||
this._internalEmitter = new EventEmitter();
|
||||
this._eventMapping = eventMapping || (data => data);
|
||||
|
||||
eventEmitter.addListener(
|
||||
this._upstreamSubscription = eventEmitter.addListener(
|
||||
eventName,
|
||||
this._handleEmit,
|
||||
this
|
||||
|
@ -105,6 +105,13 @@ class Subscribable {
|
|||
return this._lastData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unsubscribe from the upstream EventEmitter
|
||||
*/
|
||||
cleanup() {
|
||||
this._upstreamSubscription && this._upstreamSubscription.remove();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new listener to the subscribable. This should almost never be used
|
||||
* directly, and instead through Subscribable.Mixin.subscribeTo
|
||||
|
@ -229,6 +236,55 @@ Subscribable.Mixin = {
|
|||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets a Subscribable store, scoped to the component, that can be passed to
|
||||
* children. The component will automatically clean up the subscribable's
|
||||
* subscription to the eventEmitter when unmounting.
|
||||
*
|
||||
* `provideSubscribable` will always return the same Subscribable for any
|
||||
* particular emitter/eventName combo, so it can be called directly from
|
||||
* render, and it will never create duplicate Subscribables.
|
||||
*
|
||||
* @param {EventEmitter} eventEmitter Emitter to trigger subscription events.
|
||||
* @param {string} eventName Name of emitted event that triggers subscription
|
||||
* events.
|
||||
* @param {function} eventMapping (optional) Function to convert the output
|
||||
* of the eventEmitter to the subscription output.
|
||||
* @param {function} getInitData (optional) Async function to grab the initial
|
||||
* data to publish. Signature `function(successCallback, errorCallback)`.
|
||||
* The resolved data will be transformed with the eventMapping before it
|
||||
* gets emitted.
|
||||
*/
|
||||
provideSubscribable: function(eventEmitter, eventName, eventMapping, getInitData) {
|
||||
this._localSubscribables = this._localSubscribables || {};
|
||||
this._localSubscribables[eventEmitter] =
|
||||
this._localSubscribables[eventEmitter] || {};
|
||||
if (!this._localSubscribables[eventEmitter][eventName]) {
|
||||
this._localSubscribables[eventEmitter][eventName] =
|
||||
new Subscribable(eventEmitter, eventName, eventMapping, getInitData);
|
||||
}
|
||||
return this._localSubscribables[eventEmitter][eventName];
|
||||
},
|
||||
|
||||
/**
|
||||
* Removes any local Subscribables created with `provideSubscribable`, so the
|
||||
* component can unmount without leaving any dangling listeners on
|
||||
* eventEmitters
|
||||
*/
|
||||
_cleanupLocalSubscribables: function() {
|
||||
if (!this._localSubscribables) {
|
||||
return;
|
||||
}
|
||||
var emitterSubscribables;
|
||||
Object.keys(this._localSubscribables).forEach((eventEmitter) => {
|
||||
emitterSubscribables = this._localSubscribables[eventEmitter];
|
||||
Object.keys(emitterSubscribables).forEach((eventName) => {
|
||||
emitterSubscribables[eventName].cleanup();
|
||||
});
|
||||
});
|
||||
this._localSubscribables = null;
|
||||
},
|
||||
|
||||
componentWillMount: function() {
|
||||
this._endSubscribableLifespanCallbacks = [];
|
||||
|
||||
|
@ -241,6 +297,8 @@ Subscribable.Mixin = {
|
|||
// remaining subscriptions
|
||||
this._endSubscribableLifespan && this._endSubscribableLifespan();
|
||||
|
||||
this._cleanupLocalSubscribables();
|
||||
|
||||
// DEPRECATED addListenerOn* usage uses _subscribableSubscriptions array
|
||||
// instead of lifespan
|
||||
this._subscribableSubscriptions.forEach(
|
||||
|
|
24
README.md
24
README.md
|
@ -34,9 +34,9 @@ products with no compromises in quality or capability.
|
|||
Get up and running with our Movies sample app:
|
||||
|
||||
1. Once you have the repo cloned and met all the requirements above, start the
|
||||
packager that will transform your JS code on-the-fly:
|
||||
`npm install`
|
||||
`npm start`
|
||||
packager that will transform your JS code on-the-fly:
|
||||
`npm install`
|
||||
`npm start`
|
||||
2. Open the `Examples/Movies/Movies.xcodeproj` project in Xcode.
|
||||
3. Make sure the target is set to `Movies` and that you have an iOS simulator
|
||||
selected to run the app.
|
||||
|
@ -93,27 +93,27 @@ the responder system.
|
|||
|
||||
# FAQ
|
||||
|
||||
Q. How does debugging work? Can I set breakpoints in my JS?
|
||||
Q. How does debugging work? Can I set breakpoints in my JS?
|
||||
A. We are going to add the ability to use the Chrome developer tools soon. We
|
||||
are very passionate about building the best possible developer experience.
|
||||
|
||||
Q. When is this coming to Android/Windows/OS X/etc?
|
||||
Q. When is this coming to Android/Windows/OS X/etc?
|
||||
A. We're working on Android, and we are excited to release it as soon as we can.
|
||||
We are looking forward to the community helping us target other platforms as
|
||||
well :)
|
||||
|
||||
Q. How do I create my own app?
|
||||
Q. How do I create my own app?
|
||||
A. Copy the entire `Examples/TicTacToe` folder, rename stuff in Xcode, and
|
||||
replace the `TicTacToeApp.js` with your own. Then, in `AppDelegate.m`, update
|
||||
`moduleName` to match your call to
|
||||
`Bundler.registerComponent(<moduleName>, <componentName>)` at the bottom of your
|
||||
JS file, and update `jsCodeLocation` to match your JS file name and location.
|
||||
|
||||
Q. Can I submit my own React Native app to the App Store?
|
||||
Q. Can I submit my own React Native app to the App Store?
|
||||
A. Not yet, but you will be able to soon. If you build something you want to
|
||||
submit to the App Store, come talk to us ASAP.
|
||||
|
||||
Q. How do I deploy to my device?
|
||||
Q. How do I deploy to my device?
|
||||
A. You can change `localhost` in `AppDelegate.m` to your laptop's IP address and
|
||||
grab the bundle over the same Wi-Fi network. You can also download the bundle
|
||||
that the React packager generates, save it to the file `main.jsbundle`, and add it
|
||||
|
@ -121,20 +121,20 @@ as a static resource in your Xcode project. Then set the `jsCodeLocation` in
|
|||
`AppDelegate.m` to point to that file and deploy to your device like you would
|
||||
any other app.
|
||||
|
||||
Q. What's up with this private repo? Why aren't you just open sourcing it now?
|
||||
Q. What's up with this private repo? Why aren't you just open sourcing it now?
|
||||
A. We want input from the React community before we open the floodgates so we
|
||||
can incorporate your feedback, and we also have a bunch more features we want to
|
||||
add to make a more complete offering before we open source.
|
||||
|
||||
Q. Do you have to ship a JS runtime with your apps?
|
||||
Q. Do you have to ship a JS runtime with your apps?
|
||||
A. No, we just use the JavaScriptCore public API that is part of iOS 7 and
|
||||
later.
|
||||
|
||||
Q. How do I add more native capabilities?
|
||||
Q. How do I add more native capabilities?
|
||||
A. React Native is designed to be extensible - come talk to us, we would love to
|
||||
work with you.
|
||||
|
||||
Q. Can I reuse existing iOS code?
|
||||
Q. Can I reuse existing iOS code?
|
||||
A. Yes, React Native is designed to be extensible and allow integration of all
|
||||
sorts of native components, such as `UINavigationController` (available as
|
||||
`<NavigatorIOS>`), `MKMapView` (not available yet), or your own custom
|
||||
|
|
|
@ -28,8 +28,8 @@ var options = parseCommandLine([{
|
|||
default: 8081,
|
||||
}]);
|
||||
|
||||
if (!options.projectRoot) {
|
||||
options.projectRoot = path.resolve(__dirname, '..');
|
||||
if (!options.projectRoots) {
|
||||
options.projectRoots = [path.resolve(__dirname, '..')];
|
||||
}
|
||||
|
||||
console.log('\n' +
|
||||
|
|
|
@ -21,12 +21,8 @@ describe('DependencyGraph', function() {
|
|||
DependencyGraph = require('../index');
|
||||
|
||||
fileWatcher = {
|
||||
getWatcher: function() {
|
||||
return q({
|
||||
on: function() {
|
||||
return this;
|
||||
}
|
||||
});
|
||||
on: function() {
|
||||
return this;
|
||||
}
|
||||
};
|
||||
});
|
||||
|
@ -50,7 +46,7 @@ describe('DependencyGraph', function() {
|
|||
}
|
||||
});
|
||||
|
||||
var dgraph = new DependencyGraph({root: root, fileWatcher: fileWatcher});
|
||||
var dgraph = new DependencyGraph({roots: [root], fileWatcher: fileWatcher});
|
||||
return dgraph.load().then(function() {
|
||||
expect(dgraph.getOrderedDependencies('/root/index.js'))
|
||||
.toEqual([
|
||||
|
@ -79,7 +75,7 @@ describe('DependencyGraph', function() {
|
|||
}
|
||||
});
|
||||
|
||||
var dgraph = new DependencyGraph({root: root, fileWatcher: fileWatcher});
|
||||
var dgraph = new DependencyGraph({roots: [root], fileWatcher: fileWatcher});
|
||||
return dgraph.load().then(function() {
|
||||
expect(dgraph.getOrderedDependencies('/root/index.js'))
|
||||
.toEqual([
|
||||
|
@ -109,7 +105,7 @@ describe('DependencyGraph', function() {
|
|||
}
|
||||
});
|
||||
|
||||
var dgraph = new DependencyGraph({root: root, fileWatcher: fileWatcher});
|
||||
var dgraph = new DependencyGraph({roots: [root], fileWatcher: fileWatcher});
|
||||
return dgraph.load().then(function() {
|
||||
expect(dgraph.getOrderedDependencies('/root/index.js'))
|
||||
.toEqual([
|
||||
|
@ -179,7 +175,7 @@ describe('DependencyGraph', function() {
|
|||
}
|
||||
});
|
||||
|
||||
var dgraph = new DependencyGraph({root: root, fileWatcher: fileWatcher});
|
||||
var dgraph = new DependencyGraph({roots: [root], fileWatcher: fileWatcher});
|
||||
return dgraph.load().then(function() {
|
||||
expect(dgraph.getOrderedDependencies('/root/somedir/somefile.js'))
|
||||
.toEqual([
|
||||
|
@ -220,7 +216,7 @@ describe('DependencyGraph', function() {
|
|||
}
|
||||
});
|
||||
|
||||
var dgraph = new DependencyGraph({root: root, fileWatcher: fileWatcher});
|
||||
var dgraph = new DependencyGraph({roots: [root], fileWatcher: fileWatcher});
|
||||
return dgraph.load().then(function() {
|
||||
expect(dgraph.getOrderedDependencies('/root/index.js'))
|
||||
.toEqual([
|
||||
|
@ -249,7 +245,7 @@ describe('DependencyGraph', function() {
|
|||
}
|
||||
});
|
||||
|
||||
var dgraph = new DependencyGraph({root: root, fileWatcher: fileWatcher});
|
||||
var dgraph = new DependencyGraph({roots: [root], fileWatcher: fileWatcher});
|
||||
return dgraph.load().then(function() {
|
||||
expect(dgraph.getOrderedDependencies('/root/index.js'))
|
||||
.toEqual([
|
||||
|
@ -284,7 +280,7 @@ describe('DependencyGraph', function() {
|
|||
}
|
||||
});
|
||||
|
||||
var dgraph = new DependencyGraph({root: root, fileWatcher: fileWatcher});
|
||||
var dgraph = new DependencyGraph({roots: [root], fileWatcher: fileWatcher});
|
||||
return dgraph.load().then(function() {
|
||||
expect(dgraph.getOrderedDependencies('/root/index.js'))
|
||||
.toEqual([
|
||||
|
@ -324,7 +320,7 @@ describe('DependencyGraph', function() {
|
|||
}
|
||||
});
|
||||
|
||||
var dgraph = new DependencyGraph({root: root, fileWatcher: fileWatcher});
|
||||
var dgraph = new DependencyGraph({roots: [root], fileWatcher: fileWatcher});
|
||||
return dgraph.load().then(function() {
|
||||
expect(dgraph.getOrderedDependencies('/root/index.js'))
|
||||
.toEqual([
|
||||
|
@ -364,7 +360,7 @@ describe('DependencyGraph', function() {
|
|||
}
|
||||
});
|
||||
|
||||
var dgraph = new DependencyGraph({root: root, fileWatcher: fileWatcher});
|
||||
var dgraph = new DependencyGraph({roots: [root], fileWatcher: fileWatcher});
|
||||
return dgraph.load().then(function() {
|
||||
expect(dgraph.getOrderedDependencies('/root/index.js'))
|
||||
.toEqual([
|
||||
|
@ -395,16 +391,12 @@ describe('DependencyGraph', function() {
|
|||
|
||||
beforeEach(function() {
|
||||
fileWatcher = {
|
||||
getWatcher: function() {
|
||||
return q({
|
||||
on: function(eventType, callback) {
|
||||
if (eventType !== 'all') {
|
||||
throw new Error('Can only handle "all" event in watcher.');
|
||||
}
|
||||
triggerFileChange = callback;
|
||||
return this;
|
||||
}
|
||||
});
|
||||
on: function(eventType, callback) {
|
||||
if (eventType !== 'all') {
|
||||
throw new Error('Can only handle "all" event in watcher.');
|
||||
}
|
||||
triggerFileChange = callback;
|
||||
return this;
|
||||
}
|
||||
};
|
||||
});
|
||||
|
@ -436,11 +428,11 @@ describe('DependencyGraph', function() {
|
|||
}
|
||||
});
|
||||
|
||||
var dgraph = new DependencyGraph({root: root, fileWatcher: fileWatcher});
|
||||
var dgraph = new DependencyGraph({roots: [root], fileWatcher: fileWatcher});
|
||||
return dgraph.load().then(function() {
|
||||
filesystem.root['index.js'] =
|
||||
filesystem.root['index.js'].replace('require("foo")', '');
|
||||
triggerFileChange('change', 'index.js');
|
||||
triggerFileChange('change', 'index.js', root);
|
||||
return dgraph.load().then(function() {
|
||||
expect(dgraph.getOrderedDependencies('/root/index.js'))
|
||||
.toEqual([
|
||||
|
@ -484,11 +476,11 @@ describe('DependencyGraph', function() {
|
|||
}
|
||||
});
|
||||
|
||||
var dgraph = new DependencyGraph({root: root, fileWatcher: fileWatcher});
|
||||
var dgraph = new DependencyGraph({roots: [root], fileWatcher: fileWatcher});
|
||||
return dgraph.load().then(function() {
|
||||
filesystem.root['index.js'] =
|
||||
filesystem.root['index.js'].replace('require("foo")', '');
|
||||
triggerFileChange('change', 'index.js');
|
||||
triggerFileChange('change', 'index.js', root);
|
||||
return dgraph.load().then(function() {
|
||||
expect(dgraph.getOrderedDependencies('/root/index.js'))
|
||||
.toEqual([
|
||||
|
@ -532,10 +524,10 @@ describe('DependencyGraph', function() {
|
|||
}
|
||||
});
|
||||
|
||||
var dgraph = new DependencyGraph({root: root, fileWatcher: fileWatcher});
|
||||
var dgraph = new DependencyGraph({roots: [root], fileWatcher: fileWatcher});
|
||||
return dgraph.load().then(function() {
|
||||
delete filesystem.root.foo;
|
||||
triggerFileChange('delete', 'foo.js');
|
||||
triggerFileChange('delete', 'foo.js', root);
|
||||
return dgraph.load().then(function() {
|
||||
expect(dgraph.getOrderedDependencies('/root/index.js'))
|
||||
.toEqual([
|
||||
|
@ -579,7 +571,7 @@ describe('DependencyGraph', function() {
|
|||
}
|
||||
});
|
||||
|
||||
var dgraph = new DependencyGraph({root: root, fileWatcher: fileWatcher});
|
||||
var dgraph = new DependencyGraph({roots: [root], fileWatcher: fileWatcher});
|
||||
return dgraph.load().then(function() {
|
||||
filesystem.root['bar.js'] = [
|
||||
'/**',
|
||||
|
@ -587,10 +579,10 @@ describe('DependencyGraph', function() {
|
|||
' */',
|
||||
'require("foo")'
|
||||
].join('\n');
|
||||
triggerFileChange('add', 'bar.js');
|
||||
triggerFileChange('add', 'bar.js', root);
|
||||
|
||||
filesystem.root.aPackage['main.js'] = 'require("bar")';
|
||||
triggerFileChange('change', 'aPackage/main.js');
|
||||
triggerFileChange('change', 'aPackage/main.js', root);
|
||||
|
||||
return dgraph.load().then(function() {
|
||||
expect(dgraph.getOrderedDependencies('/root/index.js'))
|
||||
|
@ -644,7 +636,7 @@ describe('DependencyGraph', function() {
|
|||
});
|
||||
|
||||
var dgraph = new DependencyGraph({
|
||||
root: root,
|
||||
roots: [root],
|
||||
fileWatcher: fileWatcher,
|
||||
ignoreFilePath: function(filePath) {
|
||||
if (filePath === '/root/bar.js') {
|
||||
|
@ -660,10 +652,10 @@ describe('DependencyGraph', function() {
|
|||
' */',
|
||||
'require("foo")'
|
||||
].join('\n');
|
||||
triggerFileChange('add', 'bar.js');
|
||||
triggerFileChange('add', 'bar.js', root);
|
||||
|
||||
filesystem.root.aPackage['main.js'] = 'require("bar")';
|
||||
triggerFileChange('change', 'aPackage/main.js');
|
||||
triggerFileChange('change', 'aPackage/main.js', root);
|
||||
|
||||
return dgraph.load().then(function() {
|
||||
expect(dgraph.getOrderedDependencies('/root/index.js'))
|
||||
|
@ -711,7 +703,7 @@ describe('DependencyGraph', function() {
|
|||
}
|
||||
}
|
||||
});
|
||||
var dgraph = new DependencyGraph({root: root, fileWatcher: fileWatcher});
|
||||
var dgraph = new DependencyGraph({roots: [root], fileWatcher: fileWatcher});
|
||||
return dgraph.load().then(function() {
|
||||
triggerFileChange('change', 'aPackage', '/root', {
|
||||
isDirectory: function(){ return true; }
|
||||
|
|
|
@ -14,10 +14,10 @@ var lstat = q.nfbind(fs.lstat);
|
|||
var realpath = q.nfbind(fs.realpath);
|
||||
|
||||
function DependecyGraph(options) {
|
||||
this._root = options.root;
|
||||
this._roots = options.roots;
|
||||
this._ignoreFilePath = options.ignoreFilePath || function(){};
|
||||
this._loaded = false;
|
||||
this._queue = [this._root];
|
||||
this._queue = this._roots.slice();
|
||||
this._graph = Object.create(null);
|
||||
this._packageByRoot = Object.create(null);
|
||||
this._packagesById = Object.create(null);
|
||||
|
@ -36,16 +36,14 @@ DependecyGraph.prototype.load = function() {
|
|||
* Given an entry file return an array of all the dependent module descriptors.
|
||||
*/
|
||||
DependecyGraph.prototype.getOrderedDependencies = function(entryPath) {
|
||||
var absolutePath;
|
||||
if (!isAbsolutePath(entryPath)) {
|
||||
absolutePath = path.join(this._root, entryPath);
|
||||
} else {
|
||||
absolutePath = entryPath;
|
||||
var absolutePath = this._getAbsolutePath(entryPath);
|
||||
if (absolutePath == null) {
|
||||
throw new Error('Cannot find entry file in any of the roots: ' + entryPath);
|
||||
}
|
||||
|
||||
var module = this._graph[absolutePath];
|
||||
if (module == null) {
|
||||
throw new Error('Module with path "' + absolutePath + '" is not in graph');
|
||||
throw new Error('Module with path "' + entryPath + '" is not in graph');
|
||||
}
|
||||
|
||||
var self = this;
|
||||
|
@ -172,15 +170,11 @@ DependecyGraph.prototype.resolveDependency = function(
|
|||
*/
|
||||
DependecyGraph.prototype._init = function() {
|
||||
var processChange = this._processFileChange.bind(this);
|
||||
var loadingWatcher = this._fileWatcher.getWatcher();
|
||||
var watcher = this._fileWatcher;
|
||||
|
||||
this._loading = this.load()
|
||||
.then(function() {
|
||||
return loadingWatcher;
|
||||
})
|
||||
.then(function(watcher) {
|
||||
watcher.on('all', processChange);
|
||||
});
|
||||
this._loading = this.load().then(function() {
|
||||
watcher.on('all', processChange);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -370,7 +364,6 @@ DependecyGraph.prototype._updateGraphWithModule = function(module) {
|
|||
* Find the nearest package to a module.
|
||||
*/
|
||||
DependecyGraph.prototype._lookupPackage = function(modulePath) {
|
||||
var root = this._root;
|
||||
var packageByRoot = this._packageByRoot;
|
||||
|
||||
/**
|
||||
|
@ -380,8 +373,7 @@ DependecyGraph.prototype._lookupPackage = function(modulePath) {
|
|||
// ideally we stop once we're outside root and this can be a simple child
|
||||
// dir check. However, we have to support modules that was symlinked inside
|
||||
// our project root.
|
||||
if (!path.relative(root, currDir) === '' || currDir === '.'
|
||||
|| currDir === '/') {
|
||||
if (currDir === '/') {
|
||||
return null;
|
||||
} else {
|
||||
var packageJson = packageByRoot[currDir];
|
||||
|
@ -400,7 +392,7 @@ DependecyGraph.prototype._lookupPackage = function(modulePath) {
|
|||
* Process a filewatcher change event.
|
||||
*/
|
||||
DependecyGraph.prototype._processFileChange = function(eventType, filePath, root, stat) {
|
||||
var absPath = path.join(this._root, filePath);
|
||||
var absPath = path.join(root, filePath);
|
||||
if (this._ignoreFilePath(absPath)) {
|
||||
return;
|
||||
}
|
||||
|
@ -420,6 +412,24 @@ DependecyGraph.prototype._processFileChange = function(eventType, filePath, root
|
|||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Searches all roots for the file and returns the first one that has file of the same path.
|
||||
*/
|
||||
DependecyGraph.prototype._getAbsolutePath = function(filePath) {
|
||||
if (isAbsolutePath(filePath)) {
|
||||
return filePath;
|
||||
}
|
||||
|
||||
for (var i = 0, root; root = this._roots[i]; i++) {
|
||||
var absPath = path.join(root, filePath);
|
||||
if (this._graph[absPath]) {
|
||||
return absPath;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Extract all required modules from a `code` string.
|
||||
*/
|
||||
|
|
|
@ -23,13 +23,14 @@ var DEFINE_MODULE_REPLACE_RE = /_moduleName_|_code_|_deps_/g;
|
|||
var REL_REQUIRE_STMT = /require\(['"]([\.\/0-9A-Z_$\-]*)['"]\)/gi;
|
||||
|
||||
function HasteDependencyResolver(config) {
|
||||
this._fileWatcher = new FileWatcher(config.projectRoots);
|
||||
this._depGraph = new DependencyGraph({
|
||||
root: config.projectRoot,
|
||||
roots: config.projectRoots,
|
||||
ignoreFilePath: function(filepath) {
|
||||
return filepath.indexOf('__tests__') !== -1 ||
|
||||
(config.blacklistRE && config.blacklistRE.test(filepath));
|
||||
},
|
||||
fileWatcher: new FileWatcher(config.projectRoot)
|
||||
fileWatcher: this._fileWatcher
|
||||
});
|
||||
|
||||
this._polyfillModuleNames = [
|
||||
|
@ -118,4 +119,8 @@ HasteDependencyResolver.prototype.wrapModule = function(module, code) {
|
|||
});
|
||||
};
|
||||
|
||||
HasteDependencyResolver.prototype.end = function() {
|
||||
return this._fileWatcher.end();
|
||||
};
|
||||
|
||||
module.exports = HasteDependencyResolver;
|
||||
|
|
|
@ -16,15 +16,17 @@ describe('FileWatcher', function() {
|
|||
});
|
||||
});
|
||||
|
||||
pit('it should get the watcher instance when ready', function() {
|
||||
var fileWatcher = new FileWatcher('rootDir');
|
||||
return fileWatcher.getWatcher().then(function(watcher) {
|
||||
expect(watcher instanceof Watcher).toBe(true);
|
||||
it('it should get the watcher instance when ready', function() {
|
||||
var fileWatcher = new FileWatcher(['rootDir']);
|
||||
return fileWatcher._loading.then(function(watchers) {
|
||||
watchers.forEach(function(watcher) {
|
||||
expect(watcher instanceof Watcher).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
pit('it should end the watcher', function() {
|
||||
var fileWatcher = new FileWatcher('rootDir');
|
||||
var fileWatcher = new FileWatcher(['rootDir']);
|
||||
Watcher.prototype.close.mockImplementation(function(callback) {
|
||||
callback();
|
||||
});
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
'use strict';
|
||||
|
||||
var EventEmitter = require('events').EventEmitter;
|
||||
var sane = require('sane');
|
||||
var q = require('q');
|
||||
var util = require('util');
|
||||
var exec = require('child_process').exec;
|
||||
|
||||
var Promise = q.Promise;
|
||||
|
@ -20,37 +22,57 @@ module.exports = FileWatcher;
|
|||
|
||||
var MAX_WAIT_TIME = 3000;
|
||||
|
||||
var memoizedInstances = Object.create(null);
|
||||
function FileWatcher(projectRoots) {
|
||||
var self = this;
|
||||
this._loading = q.all(
|
||||
projectRoots.map(createWatcher)
|
||||
).then(function(watchers) {
|
||||
watchers.forEach(function(watcher) {
|
||||
watcher.on('all', function(type, filepath, root) {
|
||||
self.emit('all', type, filepath, root);
|
||||
});
|
||||
});
|
||||
return watchers;
|
||||
});
|
||||
this._loading.done();
|
||||
}
|
||||
|
||||
function FileWatcher(projectRoot) {
|
||||
if (memoizedInstances[projectRoot]) {
|
||||
return memoizedInstances[projectRoot];
|
||||
} else {
|
||||
memoizedInstances[projectRoot] = this;
|
||||
util.inherits(FileWatcher, EventEmitter);
|
||||
|
||||
FileWatcher.prototype.end = function() {
|
||||
return this._loading.then(function(watchers) {
|
||||
watchers.forEach(function(watcher) {
|
||||
delete watchersByRoot[watcher._root];
|
||||
return q.ninvoke(watcher, 'close');
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
var watchersByRoot = Object.create(null);
|
||||
|
||||
function createWatcher(root) {
|
||||
if (watchersByRoot[root] != null) {
|
||||
return Promise.resolve(watchersByRoot[root]);
|
||||
}
|
||||
|
||||
this._loadingWatcher = detectingWatcherClass.then(function(Watcher) {
|
||||
var watcher = new Watcher(projectRoot, {glob: '**/*.js'});
|
||||
return detectingWatcherClass.then(function(Watcher) {
|
||||
var watcher = new Watcher(root, {glob: '**/*.js'});
|
||||
|
||||
return new Promise(function(resolve, reject) {
|
||||
var rejectTimeout = setTimeout(function() {
|
||||
reject(new Error('Watcher took too long to load.'));
|
||||
reject(new Error([
|
||||
'Watcher took too long to load',
|
||||
'Try running `watchman` from your terminal',
|
||||
'https://facebook.github.io/watchman/docs/troubleshooting.html',
|
||||
].join('\n')));
|
||||
}, MAX_WAIT_TIME);
|
||||
|
||||
watcher.once('ready', function() {
|
||||
clearTimeout(rejectTimeout);
|
||||
watchersByRoot[root] = watcher;
|
||||
watcher._root = root;
|
||||
resolve(watcher);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
FileWatcher.prototype.getWatcher = function() {
|
||||
return this._loadingWatcher;
|
||||
};
|
||||
|
||||
FileWatcher.prototype.end = function() {
|
||||
return this._loadingWatcher.then(function(watcher) {
|
||||
return q.ninvoke(watcher, 'close');
|
||||
});
|
||||
};
|
||||
|
|
|
@ -13,12 +13,7 @@ var Promise = q.Promise;
|
|||
module.exports = Cache;
|
||||
|
||||
function Cache(projectConfig) {
|
||||
this._cacheFilePath = path.join(
|
||||
tmpdir,
|
||||
'React-Packager-JSTransformer-' + version + '-' +
|
||||
projectConfig.projectRoot.split(path.sep).join('-') +
|
||||
'-' + (projectConfig.cacheVersion || '0')
|
||||
);
|
||||
this._cacheFilePath = cacheFilePath(projectConfig);
|
||||
|
||||
var data;
|
||||
if (!projectConfig.resetCache) {
|
||||
|
@ -118,3 +113,17 @@ function loadCacheSync(cacheFilepath) {
|
|||
|
||||
return ret;
|
||||
}
|
||||
|
||||
function cacheFilePath(projectConfig) {
|
||||
var roots = projectConfig.projectRoots.join(',').split(path.sep).join('-');
|
||||
var cacheVersion = projectConfig.cacheVersion || '0';
|
||||
return path.join(
|
||||
tmpdir,
|
||||
[
|
||||
'react-packager-cache',
|
||||
version,
|
||||
cacheVersion,
|
||||
roots,
|
||||
].join('-')
|
||||
);
|
||||
}
|
||||
|
|
|
@ -23,7 +23,7 @@ describe('JSTransformer Cache', function() {
|
|||
|
||||
describe('getting/settig', function() {
|
||||
it('calls loader callback for uncached file', function() {
|
||||
var cache = new Cache({projectRoot: '/rootDir'});
|
||||
var cache = new Cache({projectRoots: ['/rootDir']});
|
||||
var loaderCb = jest.genMockFn().mockImpl(function() {
|
||||
return q();
|
||||
});
|
||||
|
@ -39,7 +39,7 @@ describe('JSTransformer Cache', function() {
|
|||
}
|
||||
});
|
||||
});
|
||||
var cache = new Cache({projectRoot: '/rootDir'});
|
||||
var cache = new Cache({projectRoots: ['/rootDir']});
|
||||
var loaderCb = jest.genMockFn().mockImpl(function() {
|
||||
return q('lol');
|
||||
});
|
||||
|
@ -56,7 +56,7 @@ describe('JSTransformer Cache', function() {
|
|||
}
|
||||
});
|
||||
});
|
||||
var cache = new Cache({projectRoot: '/rootDir'});
|
||||
var cache = new Cache({projectRoots: ['/rootDir']});
|
||||
var loaderCb = jest.genMockFn().mockImpl(function() {
|
||||
return q('lol');
|
||||
});
|
||||
|
@ -117,7 +117,7 @@ describe('JSTransformer Cache', function() {
|
|||
});
|
||||
|
||||
pit('should load cache from disk', function() {
|
||||
var cache = new Cache({projectRoot: '/rootDir'});
|
||||
var cache = new Cache({projectRoots: ['/rootDir']});
|
||||
var loaderCb = jest.genMockFn();
|
||||
return cache.get('/rootDir/someFile', loaderCb).then(function(value) {
|
||||
expect(loaderCb).not.toBeCalled();
|
||||
|
@ -143,7 +143,7 @@ describe('JSTransformer Cache', function() {
|
|||
return 123;
|
||||
};
|
||||
|
||||
var cache = new Cache({projectRoot: '/rootDir'});
|
||||
var cache = new Cache({projectRoots: ['/rootDir']});
|
||||
var loaderCb = jest.genMockFn().mockImpl(function() {
|
||||
return q('new value');
|
||||
});
|
||||
|
@ -184,7 +184,7 @@ describe('JSTransformer Cache', function() {
|
|||
});
|
||||
});
|
||||
|
||||
var cache = new Cache({projectRoot: '/rootDir'});
|
||||
var cache = new Cache({projectRoots: ['/rootDir']});
|
||||
cache.get('/rootDir/bar', function() {
|
||||
return q('bar value');
|
||||
});
|
||||
|
|
|
@ -35,7 +35,7 @@ describe('Packager', function() {
|
|||
};
|
||||
});
|
||||
|
||||
var packager = new Packager({});
|
||||
var packager = new Packager({projectRoots: []});
|
||||
var modules = [
|
||||
{id: 'foo', path: '/root/foo.js', dependencies: []},
|
||||
{id: 'bar', path: '/root/bar.js', dependencies: []},
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
var assert = require('assert');
|
||||
var fs = require('fs');
|
||||
var path = require('path');
|
||||
var q = require('q');
|
||||
var Promise = require('q').Promise;
|
||||
var Transformer = require('../JSTransformer');
|
||||
var DependencyResolver = require('../DependencyResolver');
|
||||
|
@ -45,10 +46,7 @@ var DEFAULT_CONFIG = {
|
|||
};
|
||||
|
||||
function Packager(projectConfig) {
|
||||
// Verify that the root exists.
|
||||
var root = projectConfig.projectRoot;
|
||||
assert(fs.statSync(root).isDirectory(), 'Root has to be a valid directory');
|
||||
this._rootPath = root;
|
||||
projectConfig.projectRoots.forEach(verifyRootExists);
|
||||
|
||||
this._config = Object.create(DEFAULT_CONFIG);
|
||||
for (var key in projectConfig) {
|
||||
|
@ -61,7 +59,10 @@ function Packager(projectConfig) {
|
|||
}
|
||||
|
||||
Packager.prototype.kill = function() {
|
||||
return this._transformer.kill();
|
||||
return q.all([
|
||||
this._transformer.kill(),
|
||||
this._resolver.end(),
|
||||
]);
|
||||
};
|
||||
|
||||
Packager.prototype.package = function(main, runModule, sourceMapUrl) {
|
||||
|
@ -116,4 +117,9 @@ Packager.prototype._transformModule = function(module) {
|
|||
});
|
||||
};
|
||||
|
||||
function verifyRootExists(root) {
|
||||
// Verify that the root exists.
|
||||
assert(fs.statSync(root).isDirectory(), 'Root has to be a valid directory');
|
||||
}
|
||||
|
||||
module.exports = Packager;
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
'use strict';
|
||||
|
||||
jest.dontMock('worker-farm')
|
||||
jest.setMock('worker-farm', function(){ return function(){}; })
|
||||
.dontMock('q')
|
||||
.dontMock('os')
|
||||
.dontMock('errno/custom')
|
||||
|
@ -19,7 +17,7 @@ describe('processRequest', function(){
|
|||
var FileWatcher;
|
||||
|
||||
var options = {
|
||||
projectRoot: 'root',
|
||||
projectRoots: ['root'],
|
||||
blacklistRE: null,
|
||||
cacheVersion: null,
|
||||
polyfillModuleNames: null
|
||||
|
@ -59,11 +57,8 @@ describe('processRequest', function(){
|
|||
}
|
||||
})
|
||||
};
|
||||
FileWatcher.prototype.getWatcher = function() {
|
||||
return q({
|
||||
on: watcherFunc
|
||||
});
|
||||
};
|
||||
|
||||
FileWatcher.prototype.on = watcherFunc;
|
||||
|
||||
Packager.prototype.invalidateFile = invalidatorFunc;
|
||||
|
||||
|
@ -98,16 +93,12 @@ describe('processRequest', function(){
|
|||
describe('file changes', function() {
|
||||
var triggerFileChange;
|
||||
beforeEach(function() {
|
||||
FileWatcher.prototype.getWatcher = function() {
|
||||
return q({
|
||||
on: function(eventType, callback) {
|
||||
if (eventType !== 'all') {
|
||||
throw new Error('Can only handle "all" event in watcher.');
|
||||
}
|
||||
triggerFileChange = callback;
|
||||
return this;
|
||||
}
|
||||
});
|
||||
FileWatcher.prototype.on = function(eventType, callback) {
|
||||
if (eventType !== 'all') {
|
||||
throw new Error('Can only handle "all" event in watcher.');
|
||||
}
|
||||
triggerFileChange = callback;
|
||||
return this;
|
||||
};
|
||||
});
|
||||
|
||||
|
@ -115,7 +106,7 @@ describe('processRequest', function(){
|
|||
result = makeRequest(requestHandler,'mybundle.includeRequire.runModule.bundle');
|
||||
return result.then(function(response){
|
||||
var onFileChange = watcherFunc.mock.calls[0][1];
|
||||
onFileChange('all','path/file.js');
|
||||
onFileChange('all','path/file.js', options.projectRoots[0]);
|
||||
expect(invalidatorFunc.mock.calls[0][0]).toEqual('root/path/file.js');
|
||||
});
|
||||
});
|
||||
|
@ -152,7 +143,7 @@ describe('processRequest', function(){
|
|||
.then(function(response){
|
||||
expect(response).toEqual("this is the first source");
|
||||
expect(packageFunc.mock.calls.length).toBe(1);
|
||||
triggerFileChange('all','path/file.js');
|
||||
triggerFileChange('all','path/file.js', options.projectRoots[0]);
|
||||
})
|
||||
.then(function(){
|
||||
expect(packageFunc.mock.calls.length).toBe(2);
|
||||
|
|
|
@ -8,10 +8,10 @@ var q = require('q');
|
|||
module.exports = Server;
|
||||
|
||||
function Server(options) {
|
||||
this._projectRoot = options.projectRoot;
|
||||
this._projectRoots = options.projectRoots;
|
||||
this._packages = Object.create(null);
|
||||
this._packager = new Packager({
|
||||
projectRoot: options.projectRoot,
|
||||
projectRoots: options.projectRoots,
|
||||
blacklistRE: options.blacklistRE,
|
||||
polyfillModuleNames: options.polyfillModuleNames || [],
|
||||
runtimeCode: options.runtimeCode,
|
||||
|
@ -20,18 +20,18 @@ function Server(options) {
|
|||
dev: options.dev,
|
||||
});
|
||||
|
||||
this._fileWatcher = new FileWatcher(options.projectRoot);
|
||||
this._fileWatcher = new FileWatcher(options.projectRoots);
|
||||
|
||||
var onFileChange = this._onFileChange.bind(this);
|
||||
this._fileWatcher.getWatcher().done(function(watcher) {
|
||||
watcher.on('all', onFileChange);
|
||||
});
|
||||
this._fileWatcher.on('all', onFileChange);
|
||||
}
|
||||
|
||||
Server.prototype._onFileChange = function(type, filepath) {
|
||||
var absPath = path.join(this._projectRoot, filepath);
|
||||
Server.prototype._onFileChange = function(type, filepath, root) {
|
||||
var absPath = path.join(root, filepath);
|
||||
this._packager.invalidateFile(absPath);
|
||||
this._rebuildPackages(absPath);
|
||||
// Make sure the file watcher event runs through the system before
|
||||
// we rebuild the packages.
|
||||
setImmediate(this._rebuildPackages.bind(this, absPath))
|
||||
};
|
||||
|
||||
Server.prototype._rebuildPackages = function(filepath) {
|
||||
|
|
Loading…
Reference in New Issue