mirror of https://github.com/status-im/metro.git
[react-packager] Watch asset roots for changes to update dependency graph
This commit is contained in:
parent
c74da17c7c
commit
4a67c84426
|
@ -821,6 +821,55 @@ describe('DependencyGraph', function() {
|
|||
});
|
||||
});
|
||||
|
||||
pit('updates module dependencies on asset add', function() {
|
||||
var root = '/root';
|
||||
var filesystem = fs.__setMockFilesystem({
|
||||
'root': {
|
||||
'index.js': [
|
||||
'/**',
|
||||
' * @providesModule index',
|
||||
' */',
|
||||
'require("image!foo")'
|
||||
].join('\n'),
|
||||
},
|
||||
});
|
||||
|
||||
var dgraph = new DependencyGraph({
|
||||
roots: [root],
|
||||
assetRoots: [root],
|
||||
assetExts: ['png'],
|
||||
fileWatcher: fileWatcher
|
||||
});
|
||||
|
||||
return dgraph.load().then(function() {
|
||||
expect(dgraph.getOrderedDependencies('/root/index.js'))
|
||||
.toEqual([
|
||||
{ id: 'index', altId: '/root/index.js',
|
||||
path: '/root/index.js',
|
||||
dependencies: ['image!foo']
|
||||
}
|
||||
]);
|
||||
|
||||
filesystem.root['foo.png'] = '';
|
||||
triggerFileChange('add', 'foo.png', root);
|
||||
|
||||
return dgraph.load().then(function() {
|
||||
expect(dgraph.getOrderedDependencies('/root/index.js'))
|
||||
.toEqual([
|
||||
{ id: 'index', altId: '/root/index.js',
|
||||
path: '/root/index.js',
|
||||
dependencies: ['image!foo']
|
||||
},
|
||||
{ id: 'image!foo',
|
||||
path: '/root/foo.png',
|
||||
dependencies: [],
|
||||
isAsset: true,
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
pit('runs changes through ignore filter', function() {
|
||||
var root = '/root';
|
||||
var filesystem = fs.__setMockFilesystem({
|
||||
|
|
|
@ -482,7 +482,12 @@ DependecyGraph.prototype._lookupPackage = function(modulePath) {
|
|||
/**
|
||||
* Process a filewatcher change event.
|
||||
*/
|
||||
DependecyGraph.prototype._processFileChange = function(eventType, filePath, root, stat) {
|
||||
DependecyGraph.prototype._processFileChange = function(
|
||||
eventType,
|
||||
filePath,
|
||||
root,
|
||||
stat
|
||||
) {
|
||||
var absPath = path.join(root, filePath);
|
||||
if (this._ignoreFilePath(absPath)) {
|
||||
return;
|
||||
|
@ -490,6 +495,11 @@ DependecyGraph.prototype._processFileChange = function(eventType, filePath, root
|
|||
|
||||
this._debugUpdateEvents.push({event: eventType, path: filePath});
|
||||
|
||||
if (this._assetExts.indexOf(extname(filePath)) > -1) {
|
||||
this._processAssetChange(eventType, absPath);
|
||||
return;
|
||||
}
|
||||
|
||||
var isPackage = path.basename(filePath) === 'package.json';
|
||||
if (eventType === 'delete') {
|
||||
if (isPackage) {
|
||||
|
@ -524,7 +534,8 @@ DependecyGraph.prototype.getDebugInfo = function() {
|
|||
};
|
||||
|
||||
/**
|
||||
* Searches all roots for the file and returns the first one that has file of the same path.
|
||||
* 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)) {
|
||||
|
@ -547,12 +558,43 @@ DependecyGraph.prototype._buildAssetMap = function() {
|
|||
return q();
|
||||
}
|
||||
|
||||
var self = this;
|
||||
return buildAssetMap(this._assetRoots, this._assetExts)
|
||||
.then(function(map) {
|
||||
self._assetMap = map;
|
||||
return map;
|
||||
this._assetMap = Object.create(null);
|
||||
return buildAssetMap(
|
||||
this._assetRoots,
|
||||
this._processAsset.bind(this)
|
||||
);
|
||||
};
|
||||
|
||||
DependecyGraph.prototype._processAsset = function(file) {
|
||||
var ext = extname(file);
|
||||
if (this._assetExts.indexOf(ext) !== -1) {
|
||||
var name = assetName(file, ext);
|
||||
if (this._assetMap[name] != null) {
|
||||
debug('Conflcting assets', name);
|
||||
}
|
||||
|
||||
this._assetMap[name] = new ModuleDescriptor({
|
||||
id: 'image!' + name,
|
||||
path: path.resolve(file),
|
||||
isAsset: true,
|
||||
dependencies: [],
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
DependecyGraph.prototype._processAssetChange = function(eventType, file) {
|
||||
if (this._assetMap == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
var name = assetName(file, extname(file));
|
||||
if (eventType === 'change' || eventType === 'delete') {
|
||||
delete this._assetMap[name];
|
||||
}
|
||||
|
||||
if (eventType === 'change' || eventType === 'add') {
|
||||
this._processAsset(file);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -627,15 +669,14 @@ function readAndStatDir(dir) {
|
|||
* Given a list of roots and list of extensions find all the files in
|
||||
* the directory with that extension and build a map of those assets.
|
||||
*/
|
||||
function buildAssetMap(roots, exts) {
|
||||
function buildAssetMap(roots, processAsset) {
|
||||
var queue = roots.slice(0);
|
||||
var map = Object.create(null);
|
||||
|
||||
function search() {
|
||||
var root = queue.shift();
|
||||
|
||||
if (root == null) {
|
||||
return q(map);
|
||||
return q();
|
||||
}
|
||||
|
||||
return readAndStatDir(root).spread(function(files, stats) {
|
||||
|
@ -643,21 +684,7 @@ function buildAssetMap(roots, exts) {
|
|||
if (stats[i].isDirectory()) {
|
||||
queue.push(file);
|
||||
} else {
|
||||
var ext = path.extname(file).replace(/^\./, '');
|
||||
if (exts.indexOf(ext) !== -1) {
|
||||
var assetName = path.basename(file, '.' + ext)
|
||||
.replace(/@[\d\.]+x/, '');
|
||||
if (map[assetName] != null) {
|
||||
debug('Conflcting assets', assetName);
|
||||
}
|
||||
|
||||
map[assetName] = new ModuleDescriptor({
|
||||
id: 'image!' + assetName,
|
||||
path: path.resolve(file),
|
||||
isAsset: true,
|
||||
dependencies: [],
|
||||
});
|
||||
}
|
||||
processAsset(file);
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -668,6 +695,15 @@ function buildAssetMap(roots, exts) {
|
|||
return search();
|
||||
}
|
||||
|
||||
function assetName(file, ext) {
|
||||
return path.basename(file, '.' + ext).replace(/@[\d\.]+x/, '');
|
||||
}
|
||||
|
||||
function extname(name) {
|
||||
return path.extname(name).replace(/^\./, '');
|
||||
}
|
||||
|
||||
|
||||
function NotFoundError() {
|
||||
Error.call(this);
|
||||
Error.captureStackTrace(this, this.constructor);
|
||||
|
|
|
@ -51,15 +51,15 @@ var validateOpts = declareOpts({
|
|||
type: 'array',
|
||||
default: [],
|
||||
},
|
||||
fileWatcher: {
|
||||
type: 'object',
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
|
||||
function HasteDependencyResolver(options) {
|
||||
var opts = validateOpts(options);
|
||||
|
||||
this._fileWatcher = opts.nonPersistent
|
||||
? FileWatcher.createDummyWatcher()
|
||||
: new FileWatcher(opts.projectRoots);
|
||||
|
||||
this._depGraph = new DependencyGraph({
|
||||
roots: opts.projectRoots,
|
||||
assetRoots: opts.assetRoots,
|
||||
|
@ -67,7 +67,7 @@ function HasteDependencyResolver(options) {
|
|||
return filepath.indexOf('__tests__') !== -1 ||
|
||||
(opts.blacklistRE && opts.blacklistRE.test(filepath));
|
||||
},
|
||||
fileWatcher: this._fileWatcher,
|
||||
fileWatcher: opts.fileWatcher,
|
||||
});
|
||||
|
||||
|
||||
|
@ -164,10 +164,6 @@ HasteDependencyResolver.prototype.wrapModule = function(module, code) {
|
|||
});
|
||||
};
|
||||
|
||||
HasteDependencyResolver.prototype.end = function() {
|
||||
return this._fileWatcher.end();
|
||||
};
|
||||
|
||||
HasteDependencyResolver.prototype.getDebugInfo = function() {
|
||||
return this._depGraph.getDebugInfo();
|
||||
};
|
||||
|
|
|
@ -21,6 +21,7 @@ describe('FileWatcher', function() {
|
|||
var Watcher;
|
||||
|
||||
beforeEach(function() {
|
||||
require('mock-modules').dumpCache();
|
||||
FileWatcher = require('../');
|
||||
Watcher = require('sane').WatchmanWatcher;
|
||||
Watcher.prototype.once.mockImplementation(function(type, callback) {
|
||||
|
|
|
@ -16,7 +16,7 @@ var exec = require('child_process').exec;
|
|||
|
||||
var Promise = q.Promise;
|
||||
|
||||
var detectingWatcherClass = new Promise(function(resolve, reject) {
|
||||
var detectingWatcherClass = new Promise(function(resolve) {
|
||||
exec('which watchman', function(err, out) {
|
||||
if (err || out.length === 0) {
|
||||
resolve(sane.NodeWatcher);
|
||||
|
@ -30,14 +30,23 @@ module.exports = FileWatcher;
|
|||
|
||||
var MAX_WAIT_TIME = 3000;
|
||||
|
||||
function FileWatcher(projectRoots) {
|
||||
var self = this;
|
||||
// Singleton
|
||||
var fileWatcher = null;
|
||||
|
||||
function FileWatcher(rootConfigs) {
|
||||
if (fileWatcher) {
|
||||
// This allows us to optimize watching in the future by merging roots etc.
|
||||
throw new Error('FileWatcher can only be instantiated once');
|
||||
}
|
||||
|
||||
fileWatcher = this;
|
||||
|
||||
this._loading = q.all(
|
||||
projectRoots.map(createWatcher)
|
||||
rootConfigs.map(createWatcher)
|
||||
).then(function(watchers) {
|
||||
watchers.forEach(function(watcher) {
|
||||
watcher.on('all', function(type, filepath, root) {
|
||||
self.emit('all', type, filepath, root);
|
||||
fileWatcher.emit('all', type, filepath, root);
|
||||
});
|
||||
});
|
||||
return watchers;
|
||||
|
@ -50,21 +59,14 @@ 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]);
|
||||
}
|
||||
|
||||
function createWatcher(rootConfig) {
|
||||
return detectingWatcherClass.then(function(Watcher) {
|
||||
var watcher = new Watcher(root, {glob: ['**/*.js', '**/package.json']});
|
||||
var watcher = new Watcher(rootConfig.dir, rootConfig.globs);
|
||||
|
||||
return new Promise(function(resolve, reject) {
|
||||
var rejectTimeout = setTimeout(function() {
|
||||
|
@ -77,8 +79,6 @@ function createWatcher(root) {
|
|||
|
||||
watcher.once('ready', function() {
|
||||
clearTimeout(rejectTimeout);
|
||||
watchersByRoot[root] = watcher;
|
||||
watcher._root = root;
|
||||
resolve(watcher);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -56,6 +56,14 @@ var validateOpts = declareOpts({
|
|||
type: 'array',
|
||||
required: false,
|
||||
},
|
||||
assetExts: {
|
||||
type: 'array',
|
||||
default: ['png'],
|
||||
},
|
||||
fileWatcher: {
|
||||
type: 'object',
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
|
||||
function Packager(options) {
|
||||
|
@ -70,6 +78,7 @@ function Packager(options) {
|
|||
nonPersistent: opts.nonPersistent,
|
||||
moduleFormat: opts.moduleFormat,
|
||||
assetRoots: opts.assetRoots,
|
||||
fileWatcher: opts.fileWatcher,
|
||||
});
|
||||
|
||||
this._transformer = new Transformer({
|
||||
|
@ -83,10 +92,7 @@ function Packager(options) {
|
|||
}
|
||||
|
||||
Packager.prototype.kill = function() {
|
||||
return q.all([
|
||||
this._transformer.kill(),
|
||||
this._resolver.end(),
|
||||
]);
|
||||
return this._transformer.kill();
|
||||
};
|
||||
|
||||
Packager.prototype.package = function(main, runModule, sourceMapUrl, isDev) {
|
||||
|
|
|
@ -55,18 +55,49 @@ var validateOpts = declareOpts({
|
|||
type: 'array',
|
||||
required: false,
|
||||
},
|
||||
assetExts: {
|
||||
type: 'array',
|
||||
default: ['png'],
|
||||
},
|
||||
});
|
||||
|
||||
function Server(options) {
|
||||
var opts = validateOpts(options);
|
||||
|
||||
this._projectRoots = opts.projectRoots;
|
||||
this._packages = Object.create(null);
|
||||
this._packager = new Packager(opts);
|
||||
this._changeWatchers = [];
|
||||
|
||||
var watchRootConfigs = opts.projectRoots.map(function(dir) {
|
||||
return {
|
||||
dir: dir,
|
||||
globs: [
|
||||
'**/*.js',
|
||||
'**/package.json',
|
||||
]
|
||||
};
|
||||
});
|
||||
|
||||
if (opts.assetRoots != null) {
|
||||
watchRootConfigs = watchRootConfigs.concat(
|
||||
opts.assetRoots.map(function(dir) {
|
||||
return {
|
||||
dir: dir,
|
||||
globs: opts.assetExts.map(function(ext) {
|
||||
return '**/*.' + ext;
|
||||
}),
|
||||
};
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
this._fileWatcher = options.nonPersistent
|
||||
? FileWatcher.createDummyWatcher()
|
||||
: new FileWatcher(options.projectRoots);
|
||||
: new FileWatcher(watchRootConfigs);
|
||||
|
||||
var packagerOpts = Object.create(opts);
|
||||
packagerOpts.fileWatcher = this._fileWatcher;
|
||||
this._packager = new Packager(packagerOpts);
|
||||
|
||||
var onFileChange = this._onFileChange.bind(this);
|
||||
this._fileWatcher.on('all', onFileChange);
|
||||
|
|
Loading…
Reference in New Issue