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:
Christopher Chedeau 2015-02-10 16:27:57 -08:00
parent 63e329e388
commit f7bd1e329a
13 changed files with 171 additions and 134 deletions

View File

@ -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' +

0
react-packager/, Normal file
View File

View File

@ -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; }

View File

@ -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.
*/

View File

@ -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;

View File

@ -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();
});

View File

@ -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');
});
};

View File

@ -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('-')
);
}

View File

@ -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');
});

View File

@ -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: []},

View File

@ -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;

View File

@ -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);

View File

@ -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) {