mirror of
https://github.com/status-im/metro.git
synced 2025-02-27 18:20:53 +00:00
[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() {
|
pit('runs changes through ignore filter', function() {
|
||||||
var root = '/root';
|
var root = '/root';
|
||||||
var filesystem = fs.__setMockFilesystem({
|
var filesystem = fs.__setMockFilesystem({
|
||||||
|
@ -482,7 +482,12 @@ DependecyGraph.prototype._lookupPackage = function(modulePath) {
|
|||||||
/**
|
/**
|
||||||
* Process a filewatcher change event.
|
* 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);
|
var absPath = path.join(root, filePath);
|
||||||
if (this._ignoreFilePath(absPath)) {
|
if (this._ignoreFilePath(absPath)) {
|
||||||
return;
|
return;
|
||||||
@ -490,6 +495,11 @@ DependecyGraph.prototype._processFileChange = function(eventType, filePath, root
|
|||||||
|
|
||||||
this._debugUpdateEvents.push({event: eventType, path: filePath});
|
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';
|
var isPackage = path.basename(filePath) === 'package.json';
|
||||||
if (eventType === 'delete') {
|
if (eventType === 'delete') {
|
||||||
if (isPackage) {
|
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) {
|
DependecyGraph.prototype._getAbsolutePath = function(filePath) {
|
||||||
if (isAbsolutePath(filePath)) {
|
if (isAbsolutePath(filePath)) {
|
||||||
@ -547,12 +558,43 @@ DependecyGraph.prototype._buildAssetMap = function() {
|
|||||||
return q();
|
return q();
|
||||||
}
|
}
|
||||||
|
|
||||||
var self = this;
|
this._assetMap = Object.create(null);
|
||||||
return buildAssetMap(this._assetRoots, this._assetExts)
|
return buildAssetMap(
|
||||||
.then(function(map) {
|
this._assetRoots,
|
||||||
self._assetMap = map;
|
this._processAsset.bind(this)
|
||||||
return map;
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
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
|
* 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.
|
* 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 queue = roots.slice(0);
|
||||||
var map = Object.create(null);
|
|
||||||
|
|
||||||
function search() {
|
function search() {
|
||||||
var root = queue.shift();
|
var root = queue.shift();
|
||||||
|
|
||||||
if (root == null) {
|
if (root == null) {
|
||||||
return q(map);
|
return q();
|
||||||
}
|
}
|
||||||
|
|
||||||
return readAndStatDir(root).spread(function(files, stats) {
|
return readAndStatDir(root).spread(function(files, stats) {
|
||||||
@ -643,21 +684,7 @@ function buildAssetMap(roots, exts) {
|
|||||||
if (stats[i].isDirectory()) {
|
if (stats[i].isDirectory()) {
|
||||||
queue.push(file);
|
queue.push(file);
|
||||||
} else {
|
} else {
|
||||||
var ext = path.extname(file).replace(/^\./, '');
|
processAsset(file);
|
||||||
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: [],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -668,6 +695,15 @@ function buildAssetMap(roots, exts) {
|
|||||||
return search();
|
return search();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function assetName(file, ext) {
|
||||||
|
return path.basename(file, '.' + ext).replace(/@[\d\.]+x/, '');
|
||||||
|
}
|
||||||
|
|
||||||
|
function extname(name) {
|
||||||
|
return path.extname(name).replace(/^\./, '');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
function NotFoundError() {
|
function NotFoundError() {
|
||||||
Error.call(this);
|
Error.call(this);
|
||||||
Error.captureStackTrace(this, this.constructor);
|
Error.captureStackTrace(this, this.constructor);
|
||||||
|
@ -51,15 +51,15 @@ var validateOpts = declareOpts({
|
|||||||
type: 'array',
|
type: 'array',
|
||||||
default: [],
|
default: [],
|
||||||
},
|
},
|
||||||
|
fileWatcher: {
|
||||||
|
type: 'object',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
function HasteDependencyResolver(options) {
|
function HasteDependencyResolver(options) {
|
||||||
var opts = validateOpts(options);
|
var opts = validateOpts(options);
|
||||||
|
|
||||||
this._fileWatcher = opts.nonPersistent
|
|
||||||
? FileWatcher.createDummyWatcher()
|
|
||||||
: new FileWatcher(opts.projectRoots);
|
|
||||||
|
|
||||||
this._depGraph = new DependencyGraph({
|
this._depGraph = new DependencyGraph({
|
||||||
roots: opts.projectRoots,
|
roots: opts.projectRoots,
|
||||||
assetRoots: opts.assetRoots,
|
assetRoots: opts.assetRoots,
|
||||||
@ -67,7 +67,7 @@ function HasteDependencyResolver(options) {
|
|||||||
return filepath.indexOf('__tests__') !== -1 ||
|
return filepath.indexOf('__tests__') !== -1 ||
|
||||||
(opts.blacklistRE && opts.blacklistRE.test(filepath));
|
(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() {
|
HasteDependencyResolver.prototype.getDebugInfo = function() {
|
||||||
return this._depGraph.getDebugInfo();
|
return this._depGraph.getDebugInfo();
|
||||||
};
|
};
|
||||||
|
@ -21,6 +21,7 @@ describe('FileWatcher', function() {
|
|||||||
var Watcher;
|
var Watcher;
|
||||||
|
|
||||||
beforeEach(function() {
|
beforeEach(function() {
|
||||||
|
require('mock-modules').dumpCache();
|
||||||
FileWatcher = require('../');
|
FileWatcher = require('../');
|
||||||
Watcher = require('sane').WatchmanWatcher;
|
Watcher = require('sane').WatchmanWatcher;
|
||||||
Watcher.prototype.once.mockImplementation(function(type, callback) {
|
Watcher.prototype.once.mockImplementation(function(type, callback) {
|
||||||
|
32
react-packager/src/FileWatcher/index.js
vendored
32
react-packager/src/FileWatcher/index.js
vendored
@ -16,7 +16,7 @@ var exec = require('child_process').exec;
|
|||||||
|
|
||||||
var Promise = q.Promise;
|
var Promise = q.Promise;
|
||||||
|
|
||||||
var detectingWatcherClass = new Promise(function(resolve, reject) {
|
var detectingWatcherClass = new Promise(function(resolve) {
|
||||||
exec('which watchman', function(err, out) {
|
exec('which watchman', function(err, out) {
|
||||||
if (err || out.length === 0) {
|
if (err || out.length === 0) {
|
||||||
resolve(sane.NodeWatcher);
|
resolve(sane.NodeWatcher);
|
||||||
@ -30,14 +30,23 @@ module.exports = FileWatcher;
|
|||||||
|
|
||||||
var MAX_WAIT_TIME = 3000;
|
var MAX_WAIT_TIME = 3000;
|
||||||
|
|
||||||
function FileWatcher(projectRoots) {
|
// Singleton
|
||||||
var self = this;
|
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(
|
this._loading = q.all(
|
||||||
projectRoots.map(createWatcher)
|
rootConfigs.map(createWatcher)
|
||||||
).then(function(watchers) {
|
).then(function(watchers) {
|
||||||
watchers.forEach(function(watcher) {
|
watchers.forEach(function(watcher) {
|
||||||
watcher.on('all', function(type, filepath, root) {
|
watcher.on('all', function(type, filepath, root) {
|
||||||
self.emit('all', type, filepath, root);
|
fileWatcher.emit('all', type, filepath, root);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
return watchers;
|
return watchers;
|
||||||
@ -50,21 +59,14 @@ util.inherits(FileWatcher, EventEmitter);
|
|||||||
FileWatcher.prototype.end = function() {
|
FileWatcher.prototype.end = function() {
|
||||||
return this._loading.then(function(watchers) {
|
return this._loading.then(function(watchers) {
|
||||||
watchers.forEach(function(watcher) {
|
watchers.forEach(function(watcher) {
|
||||||
delete watchersByRoot[watcher._root];
|
|
||||||
return q.ninvoke(watcher, 'close');
|
return q.ninvoke(watcher, 'close');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
var watchersByRoot = Object.create(null);
|
function createWatcher(rootConfig) {
|
||||||
|
|
||||||
function createWatcher(root) {
|
|
||||||
if (watchersByRoot[root] != null) {
|
|
||||||
return Promise.resolve(watchersByRoot[root]);
|
|
||||||
}
|
|
||||||
|
|
||||||
return detectingWatcherClass.then(function(Watcher) {
|
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) {
|
return new Promise(function(resolve, reject) {
|
||||||
var rejectTimeout = setTimeout(function() {
|
var rejectTimeout = setTimeout(function() {
|
||||||
@ -77,8 +79,6 @@ function createWatcher(root) {
|
|||||||
|
|
||||||
watcher.once('ready', function() {
|
watcher.once('ready', function() {
|
||||||
clearTimeout(rejectTimeout);
|
clearTimeout(rejectTimeout);
|
||||||
watchersByRoot[root] = watcher;
|
|
||||||
watcher._root = root;
|
|
||||||
resolve(watcher);
|
resolve(watcher);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
14
react-packager/src/Packager/index.js
vendored
14
react-packager/src/Packager/index.js
vendored
@ -56,6 +56,14 @@ var validateOpts = declareOpts({
|
|||||||
type: 'array',
|
type: 'array',
|
||||||
required: false,
|
required: false,
|
||||||
},
|
},
|
||||||
|
assetExts: {
|
||||||
|
type: 'array',
|
||||||
|
default: ['png'],
|
||||||
|
},
|
||||||
|
fileWatcher: {
|
||||||
|
type: 'object',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
function Packager(options) {
|
function Packager(options) {
|
||||||
@ -70,6 +78,7 @@ function Packager(options) {
|
|||||||
nonPersistent: opts.nonPersistent,
|
nonPersistent: opts.nonPersistent,
|
||||||
moduleFormat: opts.moduleFormat,
|
moduleFormat: opts.moduleFormat,
|
||||||
assetRoots: opts.assetRoots,
|
assetRoots: opts.assetRoots,
|
||||||
|
fileWatcher: opts.fileWatcher,
|
||||||
});
|
});
|
||||||
|
|
||||||
this._transformer = new Transformer({
|
this._transformer = new Transformer({
|
||||||
@ -83,10 +92,7 @@ function Packager(options) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Packager.prototype.kill = function() {
|
Packager.prototype.kill = function() {
|
||||||
return q.all([
|
return this._transformer.kill();
|
||||||
this._transformer.kill(),
|
|
||||||
this._resolver.end(),
|
|
||||||
]);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
Packager.prototype.package = function(main, runModule, sourceMapUrl, isDev) {
|
Packager.prototype.package = function(main, runModule, sourceMapUrl, isDev) {
|
||||||
|
35
react-packager/src/Server/index.js
vendored
35
react-packager/src/Server/index.js
vendored
@ -55,18 +55,49 @@ var validateOpts = declareOpts({
|
|||||||
type: 'array',
|
type: 'array',
|
||||||
required: false,
|
required: false,
|
||||||
},
|
},
|
||||||
|
assetExts: {
|
||||||
|
type: 'array',
|
||||||
|
default: ['png'],
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
function Server(options) {
|
function Server(options) {
|
||||||
var opts = validateOpts(options);
|
var opts = validateOpts(options);
|
||||||
|
|
||||||
this._projectRoots = opts.projectRoots;
|
this._projectRoots = opts.projectRoots;
|
||||||
this._packages = Object.create(null);
|
this._packages = Object.create(null);
|
||||||
this._packager = new Packager(opts);
|
|
||||||
this._changeWatchers = [];
|
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
|
this._fileWatcher = options.nonPersistent
|
||||||
? FileWatcher.createDummyWatcher()
|
? 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);
|
var onFileChange = this._onFileChange.bind(this);
|
||||||
this._fileWatcher.on('all', onFileChange);
|
this._fileWatcher.on('all', onFileChange);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user