New file watching implementation

Reviewed By: davidaurelio

Differential Revision: D4161662

fbshipit-source-id: 9a2a399304c83b411a8b0b74ea015c18b599fbaf
This commit is contained in:
Christoph Pojer 2016-11-16 01:10:32 -08:00 committed by Facebook Github Bot
parent c22b00e58f
commit e8a623a2ab
20 changed files with 192 additions and 742 deletions

View File

@ -362,8 +362,6 @@ If everything is set up correctly, you should see your new app running in your A
<block class="windows android" /> <block class="windows android" />
> If you hit a `ERROR Watcher took too long to load`, try increasing the timeout in [this file](https://github.com/facebook/react-native/blob/5fa33f3d07f8595a188f6fe04d6168a6ede1e721/packager/react-packager/src/DependencyResolver/FileWatcher/index.js#L16) (under your `node_modules/react-native/`).
> If you're targeting API level 23, the app might crash on first launch with an error smilar to `Unable to add window android.view.ViewRootImpl$W@c51fa6 -- permission denied for this window type`. To fix this, you need to go to `System settings > Apps > Configure apps > Draw over other apps` and grant the permission for the app. > If you're targeting API level 23, the app might crash on first launch with an error smilar to `Unable to add window android.view.ViewRootImpl$W@c51fa6 -- permission denied for this window type`. To fix this, you need to go to `System settings > Apps > Configure apps > Draw over other apps` and grant the permission for the app.
NOTE: Many React Native modules haven't been tested on Marshmallow and might break. Please throughly test the app if you target API level 23 and file a bug report if you find that something is broken. NOTE: Many React Native modules haven't been tested on Marshmallow and might break. Please throughly test the app if you target API level 23 and file a bug report if you find that something is broken.
@ -467,7 +465,7 @@ if (window.location.hash !== '' && window.location.hash !== 'content') { // cont
} }
// We would have broken out if both targetPlatform and devOS hadn't been filled. // We would have broken out if both targetPlatform and devOS hadn't been filled.
display('os', devOS); display('os', devOS);
display('platform', targetPlatform); display('platform', targetPlatform);
foundHash = true; foundHash = true;
break; break;
} }

View File

@ -55,8 +55,8 @@ function buildBundle(args, config, output = outputBundle, packagerInstance) {
getTransformOptionsModulePath: config.getTransformOptionsModulePath, getTransformOptionsModulePath: config.getTransformOptionsModulePath,
transformModulePath: transformModulePath, transformModulePath: transformModulePath,
extraNodeModules: config.extraNodeModules, extraNodeModules: config.extraNodeModules,
nonPersistent: true,
resetCache: args.resetCache, resetCache: args.resetCache,
watch: false,
}; };
packagerInstance = new Server(options); packagerInstance = new Server(options);

View File

@ -8,12 +8,14 @@
*/ */
'use strict'; 'use strict';
const InspectorProxy = require('./util/inspectorProxy.js');
const ReactPackager = require('../../packager/react-packager'); const ReactPackager = require('../../packager/react-packager');
const attachHMRServer = require('./util/attachHMRServer'); const attachHMRServer = require('./util/attachHMRServer');
const connect = require('connect'); const connect = require('connect');
const copyToClipBoardMiddleware = require('./middleware/copyToClipBoardMiddleware'); const copyToClipBoardMiddleware = require('./middleware/copyToClipBoardMiddleware');
const cpuProfilerMiddleware = require('./middleware/cpuProfilerMiddleware'); const cpuProfilerMiddleware = require('./middleware/cpuProfilerMiddleware');
const defaultAssetExts = require('../../packager/defaults').assetExts;
const getDevToolsMiddleware = require('./middleware/getDevToolsMiddleware'); const getDevToolsMiddleware = require('./middleware/getDevToolsMiddleware');
const heapCaptureMiddleware = require('./middleware/heapCaptureMiddleware.js'); const heapCaptureMiddleware = require('./middleware/heapCaptureMiddleware.js');
const http = require('http'); const http = require('http');
@ -25,10 +27,8 @@ const openStackFrameInEditorMiddleware = require('./middleware/openStackFrameInE
const path = require('path'); const path = require('path');
const statusPageMiddleware = require('./middleware/statusPageMiddleware.js'); const statusPageMiddleware = require('./middleware/statusPageMiddleware.js');
const systraceProfileMiddleware = require('./middleware/systraceProfileMiddleware.js'); const systraceProfileMiddleware = require('./middleware/systraceProfileMiddleware.js');
const webSocketProxy = require('./util/webSocketProxy.js');
const InspectorProxy = require('./util/inspectorProxy.js');
const defaultAssetExts = require('../../packager/defaults').assetExts;
const unless = require('./middleware/unless'); const unless = require('./middleware/unless');
const webSocketProxy = require('./util/webSocketProxy.js');
function runServer(args, config, readyCallback) { function runServer(args, config, readyCallback) {
var wsProxy = null; var wsProxy = null;
@ -86,17 +86,17 @@ function getPackagerServer(args, config) {
undefined; undefined;
return ReactPackager.createServer({ return ReactPackager.createServer({
nonPersistent: args.nonPersistent, assetExts: defaultAssetExts.concat(args.assetExts),
projectRoots: args.projectRoots, assetRoots: args.assetRoots,
blacklistRE: config.getBlacklistRE(), blacklistRE: config.getBlacklistRE(),
cacheVersion: '3', cacheVersion: '3',
getTransformOptionsModulePath: config.getTransformOptionsModulePath,
transformModulePath: transformModulePath,
extraNodeModules: config.extraNodeModules, extraNodeModules: config.extraNodeModules,
assetRoots: args.assetRoots, getTransformOptionsModulePath: config.getTransformOptionsModulePath,
assetExts: defaultAssetExts.concat(args.assetExts), projectRoots: args.projectRoots,
resetCache: args.resetCache, resetCache: args.resetCache,
transformModulePath: transformModulePath,
verbose: args.verbose, verbose: args.verbose,
watch: args.nonPersistent != null,
}); });
} }

View File

@ -5,32 +5,5 @@
"repository": { "repository": {
"type": "git", "type": "git",
"url": "git@github.com:facebook/react-native.git" "url": "git@github.com:facebook/react-native.git"
},
"engines": {
"node": ">=4"
},
"jest": {
"setupEnvScriptFile": "jest/setup.js",
"testPathIgnorePatterns": [
"/node_modules/"
],
"testFileExtensions": [
"js"
],
"unmockedModulePathPatterns": [
"source-map"
]
},
"scripts": {
"test": "jest",
"lint": "node linter.js Examples/",
"start": "./packager.sh"
},
"dependencies": {
"wordwrap": "^1.0.0"
},
"devDependencies": {
"jest-cli": "git://github.com/facebook/jest#0.5.x",
"eslint": "0.9.2"
} }
} }

View File

@ -45,26 +45,14 @@ function createServer(options) {
enableDebug(); enableDebug();
} }
options = Object.assign({}, options);
delete options.verbose;
var Server = require('./src/Server'); var Server = require('./src/Server');
return new Server(omit(options, ['verbose'])); return new Server(options);
} }
function createNonPersistentServer(options) { function createNonPersistentServer(options) {
Logger.disablePrinting(); Logger.disablePrinting();
// Don't start the filewatcher or the cache. options.watch = options.nonPersistent != null;
if (options.nonPersistent == null) {
options.nonPersistent = true;
}
return createServer(options); return createServer(options);
} }
function omit(obj, blacklistedKeys) {
return Object.keys(obj).reduce((clone, key) => {
if (blacklistedKeys.indexOf(key) === -1) {
clone[key] = obj[key];
}
return clone;
}, {});
}

View File

@ -15,17 +15,15 @@ jest.mock('fs');
const AssetServer = require('../'); const AssetServer = require('../');
const crypto = require('crypto'); const crypto = require('crypto');
const {EventEmitter} = require('events');
const fs = require('fs'); const fs = require('fs');
const {objectContaining} = jasmine; const {objectContaining} = jasmine;
describe('AssetServer', () => { describe('AssetServer', () => {
let fileWatcher;
beforeEach(() => { beforeEach(() => {
const NodeHaste = require('../../node-haste'); const NodeHaste = require('../../node-haste');
NodeHaste.getAssetDataFromName = require.requireActual('../../node-haste/lib/getAssetDataFromName'); NodeHaste.getAssetDataFromName =
fileWatcher = new EventEmitter(); require.requireActual('../../node-haste/lib/getAssetDataFromName');
}); });
describe('assetServer.get', () => { describe('assetServer.get', () => {
@ -33,7 +31,6 @@ describe('AssetServer', () => {
const server = new AssetServer({ const server = new AssetServer({
projectRoots: ['/root'], projectRoots: ['/root'],
assetExts: ['png'], assetExts: ['png'],
fileWatcher,
}); });
fs.__setMockFilesystem({ fs.__setMockFilesystem({
@ -59,7 +56,6 @@ describe('AssetServer', () => {
const server = new AssetServer({ const server = new AssetServer({
projectRoots: ['/root'], projectRoots: ['/root'],
assetExts: ['png'], assetExts: ['png'],
fileWatcher,
}); });
fs.__setMockFilesystem({ fs.__setMockFilesystem({
@ -97,7 +93,6 @@ describe('AssetServer', () => {
const server = new AssetServer({ const server = new AssetServer({
projectRoots: ['/root'], projectRoots: ['/root'],
assetExts: ['png', 'jpg'], assetExts: ['png', 'jpg'],
fileWatcher,
}); });
fs.__setMockFilesystem({ fs.__setMockFilesystem({
@ -124,7 +119,6 @@ describe('AssetServer', () => {
const server = new AssetServer({ const server = new AssetServer({
projectRoots: ['/root'], projectRoots: ['/root'],
assetExts: ['png'], assetExts: ['png'],
fileWatcher,
}); });
fs.__setMockFilesystem({ fs.__setMockFilesystem({
@ -147,7 +141,6 @@ describe('AssetServer', () => {
const server = new AssetServer({ const server = new AssetServer({
projectRoots: ['/root'], projectRoots: ['/root'],
assetExts: ['png'], assetExts: ['png'],
fileWatcher,
}); });
fs.__setMockFilesystem({ fs.__setMockFilesystem({
@ -179,7 +172,6 @@ describe('AssetServer', () => {
const server = new AssetServer({ const server = new AssetServer({
projectRoots: ['/root', '/root2'], projectRoots: ['/root', '/root2'],
assetExts: ['png'], assetExts: ['png'],
fileWatcher,
}); });
fs.__setMockFilesystem({ fs.__setMockFilesystem({
@ -208,7 +200,6 @@ describe('AssetServer', () => {
const server = new AssetServer({ const server = new AssetServer({
projectRoots: ['/root'], projectRoots: ['/root'],
assetExts: ['png'], assetExts: ['png'],
fileWatcher,
}); });
fs.__setMockFilesystem({ fs.__setMockFilesystem({
@ -241,7 +232,6 @@ describe('AssetServer', () => {
const server = new AssetServer({ const server = new AssetServer({
projectRoots: ['/root'], projectRoots: ['/root'],
assetExts: ['png', 'jpeg'], assetExts: ['png', 'jpeg'],
fileWatcher,
}); });
fs.__setMockFilesystem({ fs.__setMockFilesystem({
@ -271,15 +261,14 @@ describe('AssetServer', () => {
}); });
describe('hash:', () => { describe('hash:', () => {
let server, fileSystem; let server, mockFS;
beforeEach(() => { beforeEach(() => {
server = new AssetServer({ server = new AssetServer({
projectRoots: ['/root'], projectRoots: ['/root'],
assetExts: ['jpg'], assetExts: ['jpg'],
fileWatcher,
}); });
fileSystem = { mockFS = {
'root': { 'root': {
imgs: { imgs: {
'b@1x.jpg': 'b1 image', 'b@1x.jpg': 'b1 image',
@ -290,13 +279,13 @@ describe('AssetServer', () => {
} }
}; };
fs.__setMockFilesystem(fileSystem); fs.__setMockFilesystem(mockFS);
}); });
it('uses the file contents to build the hash', () => { it('uses the file contents to build the hash', () => {
const hash = crypto.createHash('md5'); const hash = crypto.createHash('md5');
for (const name in fileSystem.root.imgs) { for (const name in mockFS.root.imgs) {
hash.update(fileSystem.root.imgs[name]); hash.update(mockFS.root.imgs[name]);
} }
return server.getAssetData('imgs/b.jpg').then(data => return server.getAssetData('imgs/b.jpg').then(data =>
@ -306,8 +295,8 @@ describe('AssetServer', () => {
it('changes the hash when the passed-in file watcher emits an `all` event', () => { it('changes the hash when the passed-in file watcher emits an `all` event', () => {
return server.getAssetData('imgs/b.jpg').then(initialData => { return server.getAssetData('imgs/b.jpg').then(initialData => {
fileSystem.root.imgs['b@4x.jpg'] = 'updated data'; mockFS.root.imgs['b@4x.jpg'] = 'updated data';
fileWatcher.emit('all', 'arbitrary', '/root', 'imgs/b@4x.jpg'); server.onFileChange('all', '/root/imgs/b@4x.jpg');
return server.getAssetData('imgs/b.jpg').then(data => return server.getAssetData('imgs/b.jpg').then(data =>
expect(data.hash).not.toEqual(initialData.hash) expect(data.hash).not.toEqual(initialData.hash)
); );

View File

@ -42,10 +42,6 @@ const validateOpts = declareOpts({
type: 'array', type: 'array',
required: true, required: true,
}, },
fileWatcher: {
type: 'object',
required: true,
}
}); });
class AssetServer { class AssetServer {
@ -55,9 +51,6 @@ class AssetServer {
this._assetExts = opts.assetExts; this._assetExts = opts.assetExts;
this._hashes = new Map(); this._hashes = new Map();
this._files = new Map(); this._files = new Map();
opts.fileWatcher
.on('all', (type, root, file) => this._onFileChange(type, root, file));
} }
get(assetPath, platform = null) { get(assetPath, platform = null) {
@ -84,7 +77,6 @@ class AssetServer {
data.scales = record.scales; data.scales = record.scales;
data.files = record.files; data.files = record.files;
if (this._hashes.has(assetPath)) { if (this._hashes.has(assetPath)) {
data.hash = this._hashes.get(assetPath); data.hash = this._hashes.get(assetPath);
return data; return data;
@ -106,9 +98,8 @@ class AssetServer {
}); });
} }
_onFileChange(type, root, file) { onFileChange(type, filePath) {
const asset = this._files.get(path.join(root, file)); this._hashes.delete(this._files.get(filePath));
this._hashes.delete(asset);
} }
/** /**
@ -187,7 +178,10 @@ class AssetServer {
} }
const rootsString = roots.map(s => `'${s}'`).join(', '); const rootsString = roots.map(s => `'${s}'`).join(', ');
throw new Error(`'${debugInfoFile}' could not be found, because '${dir}' is not a subdirectory of any of the roots (${rootsString})`); throw new Error(
`'${debugInfoFile}' could not be found, because '${dir}' is not a ` +
`subdirectory of any of the roots (${rootsString})`,
);
}); });
} }

View File

@ -79,10 +79,6 @@ const validateOpts = declareOpts({
type: 'object', type: 'object',
required: false, required: false,
}, },
nonPersistent: {
type: 'boolean',
default: false,
},
assetRoots: { assetRoots: {
type: 'array', type: 'array',
required: false, required: false,
@ -91,9 +87,9 @@ const validateOpts = declareOpts({
type: 'array', type: 'array',
default: ['png'], default: ['png'],
}, },
fileWatcher: { watch: {
type: 'object', type: 'boolean',
required: true, default: false,
}, },
assetServer: { assetServer: {
type: 'object', type: 'object',
@ -124,10 +120,9 @@ type Options = {
resetCache: boolean, resetCache: boolean,
transformModulePath: string, transformModulePath: string,
extraNodeModules: {}, extraNodeModules: {},
nonPersistent: boolean,
assetRoots: Array<string>, assetRoots: Array<string>,
assetExts: Array<string>, assetExts: Array<string>,
fileWatcher: {}, watch: boolean,
assetServer: AssetServer, assetServer: AssetServer,
transformTimeoutInterval: ?number, transformTimeoutInterval: ?number,
allowBundleUpdates: boolean, allowBundleUpdates: boolean,
@ -191,7 +186,7 @@ class Bundler {
blacklistRE: opts.blacklistRE, blacklistRE: opts.blacklistRE,
cache: this._cache, cache: this._cache,
extraNodeModules: opts.extraNodeModules, extraNodeModules: opts.extraNodeModules,
fileWatcher: opts.fileWatcher, watch: opts.watch,
minifyCode: this._transformer.minify, minifyCode: this._transformer.minify,
moduleFormat: opts.moduleFormat, moduleFormat: opts.moduleFormat,
polyfillModuleNames: opts.polyfillModuleNames, polyfillModuleNames: opts.polyfillModuleNames,
@ -217,9 +212,12 @@ class Bundler {
} }
} }
kill() { end() {
this._transformer.kill(); this._transformer.kill();
return this._cache.end(); return Promise.all([
this._cache.end(),
this.getResolver().getDependencyGraph().getWatcher().end(),
]);
} }
bundle(options: { bundle(options: {

View File

@ -35,9 +35,9 @@ const validateOpts = declareOpts({
type: 'array', type: 'array',
default: [], default: [],
}, },
fileWatcher: { watch: {
type: 'object', type: 'boolean',
required: true, default: false,
}, },
assetExts: { assetExts: {
type: 'array', type: 'array',
@ -101,14 +101,13 @@ class Resolver {
providesModuleNodeModules: defaults.providesModuleNodeModules, providesModuleNodeModules: defaults.providesModuleNodeModules,
platforms: defaults.platforms, platforms: defaults.platforms,
preferNativePlatform: true, preferNativePlatform: true,
fileWatcher: opts.fileWatcher, watch: opts.watch,
cache: opts.cache, cache: opts.cache,
shouldThrowOnUnresolvedErrors: (_, platform) => platform !== 'android', shouldThrowOnUnresolvedErrors: (_, platform) => platform !== 'android',
transformCode: opts.transformCode, transformCode: opts.transformCode,
transformCacheKey: opts.transformCacheKey, transformCacheKey: opts.transformCacheKey,
extraNodeModules: opts.extraNodeModules, extraNodeModules: opts.extraNodeModules,
assetDependencies: ['react-native/Libraries/Image/AssetRegistry'], assetDependencies: ['react-native/Libraries/Image/AssetRegistry'],
// for jest-haste-map
resetCache: options.resetCache, resetCache: options.resetCache,
moduleOptions: { moduleOptions: {
cacheTransformResults: true, cacheTransformResults: true,
@ -260,7 +259,7 @@ class Resolver {
return this._minifyCode(path, code, map); return this._minifyCode(path, code, map);
} }
getDependecyGraph() { getDependencyGraph() {
return this._depGraph; return this._depGraph;
} }
} }

View File

@ -21,8 +21,6 @@ jest.setMock('worker-farm', function() { return () => {}; })
.mock('../../node-haste') .mock('../../node-haste')
.mock('../../Logger'); .mock('../../Logger');
let FileWatcher;
describe('processRequest', () => { describe('processRequest', () => {
let SourceMapConsumer, Bundler, Server, AssetServer, Promise; let SourceMapConsumer, Bundler, Server, AssetServer, Promise;
beforeEach(() => { beforeEach(() => {
@ -62,12 +60,9 @@ describe('processRequest', () => {
); );
const invalidatorFunc = jest.fn(); const invalidatorFunc = jest.fn();
const watcherFunc = jest.fn();
let requestHandler; let requestHandler;
let triggerFileChange;
beforeEach(() => { beforeEach(() => {
FileWatcher = require('../../node-haste').FileWatcher;
Bundler.prototype.bundle = jest.fn(() => Bundler.prototype.bundle = jest.fn(() =>
Promise.resolve({ Promise.resolve({
getSource: () => 'this is the source', getSource: () => 'this is the source',
@ -75,19 +70,10 @@ describe('processRequest', () => {
getEtag: () => 'this is an etag', getEtag: () => 'this is an etag',
})); }));
FileWatcher.prototype.on = function(eventType, callback) {
if (eventType !== 'all') {
throw new Error('Can only handle "all" event in watcher.');
}
watcherFunc.apply(this, arguments);
triggerFileChange = callback;
return this;
};
Bundler.prototype.invalidateFile = invalidatorFunc; Bundler.prototype.invalidateFile = invalidatorFunc;
Bundler.prototype.getResolver = Bundler.prototype.getResolver =
jest.fn().mockReturnValue({ jest.fn().mockReturnValue({
getDependecyGraph: jest.fn().mockReturnValue({ getDependencyGraph: jest.fn().mockReturnValue({
getHasteMap: jest.fn().mockReturnValue({on: jest.fn()}), getHasteMap: jest.fn().mockReturnValue({on: jest.fn()}),
load: jest.fn(() => Promise.resolve()), load: jest.fn(() => Promise.resolve()),
}), }),
@ -97,7 +83,7 @@ describe('processRequest', () => {
requestHandler = server.processRequest.bind(server); requestHandler = server.processRequest.bind(server);
}); });
pit('returns JS bundle source on request of *.bundle', () => { it('returns JS bundle source on request of *.bundle', () => {
return makeRequest( return makeRequest(
requestHandler, requestHandler,
'mybundle.bundle?runModule=true', 'mybundle.bundle?runModule=true',
@ -107,7 +93,7 @@ describe('processRequest', () => {
); );
}); });
pit('returns JS bundle source on request of *.bundle (compat)', () => { it('returns JS bundle source on request of *.bundle (compat)', () => {
return makeRequest( return makeRequest(
requestHandler, requestHandler,
'mybundle.runModule.bundle' 'mybundle.runModule.bundle'
@ -116,7 +102,7 @@ describe('processRequest', () => {
); );
}); });
pit('returns ETag header on request of *.bundle', () => { it('returns ETag header on request of *.bundle', () => {
return makeRequest( return makeRequest(
requestHandler, requestHandler,
'mybundle.bundle?runModule=true' 'mybundle.bundle?runModule=true'
@ -125,7 +111,7 @@ describe('processRequest', () => {
}); });
}); });
pit('returns 304 on request of *.bundle when if-none-match equals the ETag', () => { it('returns 304 on request of *.bundle when if-none-match equals the ETag', () => {
return makeRequest( return makeRequest(
requestHandler, requestHandler,
'mybundle.bundle?runModule=true', 'mybundle.bundle?runModule=true',
@ -135,7 +121,7 @@ describe('processRequest', () => {
}); });
}); });
pit('returns sourcemap on request of *.map', () => { it('returns sourcemap on request of *.map', () => {
return makeRequest( return makeRequest(
requestHandler, requestHandler,
'mybundle.map?runModule=true' 'mybundle.map?runModule=true'
@ -144,7 +130,7 @@ describe('processRequest', () => {
); );
}); });
pit('works with .ios.js extension', () => { it('works with .ios.js extension', () => {
return makeRequest( return makeRequest(
requestHandler, requestHandler,
'index.ios.includeRequire.bundle' 'index.ios.includeRequire.bundle'
@ -169,7 +155,7 @@ describe('processRequest', () => {
}); });
}); });
pit('passes in the platform param', function() { it('passes in the platform param', function() {
return makeRequest( return makeRequest(
requestHandler, requestHandler,
'index.bundle?platform=ios' 'index.bundle?platform=ios'
@ -194,7 +180,7 @@ describe('processRequest', () => {
}); });
}); });
pit('passes in the assetPlugin param', function() { it('passes in the assetPlugin param', function() {
return makeRequest( return makeRequest(
requestHandler, requestHandler,
'index.bundle?assetPlugin=assetPlugin1&assetPlugin=assetPlugin2' 'index.bundle?assetPlugin=assetPlugin1&assetPlugin=assetPlugin2'
@ -219,24 +205,13 @@ describe('processRequest', () => {
}); });
}); });
pit('watches all files in projectRoot', () => {
return makeRequest(
requestHandler,
'mybundle.bundle?runModule=true'
).then(() => {
expect(watcherFunc.mock.calls[0][0]).toEqual('all');
expect(watcherFunc.mock.calls[0][1]).not.toBe(null);
});
});
describe('file changes', () => { describe('file changes', () => {
pit('invalides files in bundle when file is updated', () => { it('invalides files in bundle when file is updated', () => {
return makeRequest( return makeRequest(
requestHandler, requestHandler,
'mybundle.bundle?runModule=true' 'mybundle.bundle?runModule=true'
).then(() => { ).then(() => {
const onFileChange = watcherFunc.mock.calls[0][1]; server.onFileChange('all', options.projectRoots[0] + '/path/file.js');
onFileChange('all','path/file.js', options.projectRoots[0]);
expect(invalidatorFunc.mock.calls[0][0]).toEqual('root/path/file.js'); expect(invalidatorFunc.mock.calls[0][0]).toEqual('root/path/file.js');
}); });
}); });
@ -273,7 +248,7 @@ describe('processRequest', () => {
jest.runAllTicks(); jest.runAllTicks();
triggerFileChange('all','path/file.js', options.projectRoots[0]); server.onFileChange('all', options.projectRoots[0] + 'path/file.js');
jest.runAllTimers(); jest.runAllTimers();
jest.runAllTicks(); jest.runAllTicks();
@ -322,7 +297,7 @@ describe('processRequest', () => {
jest.runAllTicks(); jest.runAllTicks();
triggerFileChange('all','path/file.js', options.projectRoots[0]); server.onFileChange('all', options.projectRoots[0] + 'path/file.js');
jest.runAllTimers(); jest.runAllTimers();
jest.runAllTicks(); jest.runAllTicks();
@ -355,7 +330,7 @@ describe('processRequest', () => {
it('should hold on to request and inform on change', () => { it('should hold on to request and inform on change', () => {
server.processRequest(req, res); server.processRequest(req, res);
triggerFileChange('all', 'path/file.js', options.projectRoots[0]); server.onFileChange('all', options.projectRoots[0] + 'path/file.js');
jest.runAllTimers(); jest.runAllTimers();
expect(res.end).toBeCalledWith(JSON.stringify({changed: true})); expect(res.end).toBeCalledWith(JSON.stringify({changed: true}));
}); });
@ -364,7 +339,7 @@ describe('processRequest', () => {
server.processRequest(req, res); server.processRequest(req, res);
req.emit('close'); req.emit('close');
jest.runAllTimers(); jest.runAllTimers();
triggerFileChange('all', 'path/file.js', options.projectRoots[0]); server.onFileChange('all', options.projectRoots[0] + 'path/file.js');
jest.runAllTimers(); jest.runAllTimers();
expect(res.end).not.toBeCalled(); expect(res.end).not.toBeCalled();
}); });
@ -428,7 +403,7 @@ describe('processRequest', () => {
}); });
describe('buildbundle(options)', () => { describe('buildbundle(options)', () => {
pit('Calls the bundler with the correct args', () => { it('Calls the bundler with the correct args', () => {
return server.buildBundle({ return server.buildBundle({
entryFile: 'foo file' entryFile: 'foo file'
}).then(() => }).then(() =>
@ -451,7 +426,7 @@ describe('processRequest', () => {
}); });
describe('buildBundleFromUrl(options)', () => { describe('buildBundleFromUrl(options)', () => {
pit('Calls the bundler with the correct args', () => { it('Calls the bundler with the correct args', () => {
return server.buildBundleFromUrl('/path/to/foo.bundle?dev=false&runModule=false') return server.buildBundleFromUrl('/path/to/foo.bundle?dev=false&runModule=false')
.then(() => .then(() =>
expect(Bundler.prototype.bundle).toBeCalledWith({ expect(Bundler.prototype.bundle).toBeCalledWith({
@ -474,7 +449,7 @@ describe('processRequest', () => {
}); });
describe('/symbolicate endpoint', () => { describe('/symbolicate endpoint', () => {
pit('should symbolicate given stack trace', () => { it('should symbolicate given stack trace', () => {
const body = JSON.stringify({stack: [{ const body = JSON.stringify({stack: [{
file: 'http://foo.bundle?platform=ios', file: 'http://foo.bundle?platform=ios',
lineNumber: 2100, lineNumber: 2100,
@ -508,7 +483,7 @@ describe('processRequest', () => {
}); });
}); });
pit('ignores `/debuggerWorker.js` stack frames', () => { it('ignores `/debuggerWorker.js` stack frames', () => {
const body = JSON.stringify({stack: [{ const body = JSON.stringify({stack: [{
file: 'http://localhost:8081/debuggerWorker.js', file: 'http://localhost:8081/debuggerWorker.js',
lineNumber: 123, lineNumber: 123,
@ -532,7 +507,7 @@ describe('processRequest', () => {
}); });
describe('/symbolicate handles errors', () => { describe('/symbolicate handles errors', () => {
pit('should symbolicate given stack trace', () => { it('should symbolicate given stack trace', () => {
const body = 'clearly-not-json'; const body = 'clearly-not-json';
console.error = jest.fn(); console.error = jest.fn();

View File

@ -9,7 +9,6 @@
'use strict'; 'use strict';
const AssetServer = require('../AssetServer'); const AssetServer = require('../AssetServer');
const FileWatcher = require('../node-haste').FileWatcher;
const getPlatformExtension = require('../node-haste').getPlatformExtension; const getPlatformExtension = require('../node-haste').getPlatformExtension;
const Bundler = require('../Bundler'); const Bundler = require('../Bundler');
const MultipartResponse = require('./MultipartResponse'); const MultipartResponse = require('./MultipartResponse');
@ -76,7 +75,7 @@ const validateOpts = declareOpts({
type: 'object', type: 'object',
required: false, required: false,
}, },
nonPersistent: { watch: {
type: 'boolean', type: 'boolean',
default: false, default: false,
}, },
@ -200,57 +199,32 @@ const NODE_MODULES = `${path.sep}node_modules${path.sep}`;
class Server { class Server {
constructor(options) { constructor(options) {
const opts = this._opts = validateOpts(options); const opts = this._opts = validateOpts(options);
const processFileChange =
({type, filePath, stat}) => this.onFileChange(type, filePath, stat);
this._projectRoots = opts.projectRoots; this._projectRoots = opts.projectRoots;
this._bundles = Object.create(null); this._bundles = Object.create(null);
this._changeWatchers = []; this._changeWatchers = [];
this._fileChangeListeners = []; this._fileChangeListeners = [];
const assetGlobs = opts.assetExts.map(ext => '**/*.' + ext);
let watchRootConfigs = opts.projectRoots.map(dir => {
return {
dir: dir,
globs: [
'**/*.js',
'**/*.json',
].concat(assetGlobs),
};
});
if (opts.assetRoots != null) {
watchRootConfigs = watchRootConfigs.concat(
opts.assetRoots.map(dir => {
return {
dir: dir,
globs: assetGlobs,
};
})
);
}
this._fileWatcher = options.nonPersistent
? FileWatcher.createDummyWatcher()
: new FileWatcher(watchRootConfigs, {useWatchman: true});
this._assetServer = new AssetServer({ this._assetServer = new AssetServer({
assetExts: opts.assetExts, assetExts: opts.assetExts,
fileWatcher: this._fileWatcher,
projectRoots: opts.projectRoots, projectRoots: opts.projectRoots,
}); });
const bundlerOpts = Object.create(opts); const bundlerOpts = Object.create(opts);
bundlerOpts.fileWatcher = this._fileWatcher;
bundlerOpts.assetServer = this._assetServer; bundlerOpts.assetServer = this._assetServer;
bundlerOpts.allowBundleUpdates = !options.nonPersistent; bundlerOpts.allowBundleUpdates = options.watch;
bundlerOpts.watch = options.watch;
this._bundler = new Bundler(bundlerOpts); this._bundler = new Bundler(bundlerOpts);
this._fileWatcher.on('all', this._onFileChange.bind(this));
// changes to the haste map can affect resolution of files in the bundle // changes to the haste map can affect resolution of files in the bundle
const dependencyGraph = this._bundler.getResolver().getDependecyGraph(); const dependencyGraph = this._bundler.getResolver().getDependencyGraph();
dependencyGraph.load().then(() => { dependencyGraph.load().then(() => {
dependencyGraph.getWatcher().on(
'change',
({eventsQueue}) => eventsQueue.forEach(processFileChange),
);
dependencyGraph.getHasteMap().on('change', () => { dependencyGraph.getHasteMap().on('change', () => {
debug('Clearing bundle cache due to haste map change'); debug('Clearing bundle cache due to haste map change');
this._clearBundles(); this._clearBundles();
@ -282,10 +256,7 @@ class Server {
} }
end() { end() {
Promise.all([ return this._bundler.end();
this._fileWatcher.end(),
this._bundler.kill(),
]);
} }
setHMRFileChangeListener(listener) { setHMRFileChangeListener(listener) {
@ -299,7 +270,7 @@ class Server {
} }
buildBundle(options) { buildBundle(options) {
return this._bundler.getResolver().getDependecyGraph().load().then(() => { return this._bundler.getResolver().getDependencyGraph().load().then(() => {
if (!options.platform) { if (!options.platform) {
options.platform = getPlatformExtension(options.entryFile); options.platform = getPlatformExtension(options.entryFile);
} }
@ -370,9 +341,9 @@ class Server {
}); });
} }
_onFileChange(type, filepath, root) { onFileChange(type, filePath, stat) {
const absPath = path.join(root, filepath); this._assetServer.onFileChange(type, filePath, stat);
this._bundler.invalidateFile(absPath); this._bundler.invalidateFile(filePath);
// If Hot Loading is enabled avoid rebuilding bundles and sending live // If Hot Loading is enabled avoid rebuilding bundles and sending live
// updates. Instead, send the HMR updates right away and clear the bundles // updates. Instead, send the HMR updates right away and clear the bundles
@ -380,26 +351,26 @@ class Server {
if (this._hmrFileChangeListener) { if (this._hmrFileChangeListener) {
// Clear cached bundles in case user reloads // Clear cached bundles in case user reloads
this._clearBundles(); this._clearBundles();
this._hmrFileChangeListener(absPath, this._bundler.stat(absPath)); this._hmrFileChangeListener(filePath, this._bundler.stat(filePath));
return; return;
} else if (type !== 'change' && absPath.indexOf(NODE_MODULES) !== -1) { } else if (type !== 'change' && filePath.indexOf(NODE_MODULES) !== -1) {
// node module resolution can be affected by added or removed files // node module resolution can be affected by added or removed files
debug('Clearing bundles due to potential node_modules resolution change'); debug('Clearing bundles due to potential node_modules resolution change');
this._clearBundles(); this._clearBundles();
} }
Promise.all( Promise.all(
this._fileChangeListeners.map(listener => listener(absPath)) this._fileChangeListeners.map(listener => listener(filePath))
).then( ).then(
() => this._onFileChangeComplete(absPath), () => this._onFileChangeComplete(filePath),
() => this._onFileChangeComplete(absPath) () => this._onFileChangeComplete(filePath)
); );
} }
_onFileChangeComplete(absPath) { _onFileChangeComplete(filePath) {
// Make sure the file watcher event runs through the system before // Make sure the file watcher event runs through the system before
// we rebuild the bundles. // we rebuild the bundles.
this._debouncedFileChangeHandler(absPath); this._debouncedFileChangeHandler(filePath);
} }
_clearBundles() { _clearBundles() {

View File

@ -54,14 +54,14 @@ class DeprecatedAssetMap {
} }
} }
processFileChange(type, filePath, root, fstat) { processFileChange(type, filePath, fstat) {
const name = assetName(filePath); const name = assetName(filePath);
if (type === 'change' || type === 'delete') { if (type === 'change' || type === 'delete') {
delete this._map[name]; delete this._map[name];
} }
if (type === 'change' || type === 'add') { if (type === 'change' || type === 'add') {
this._processAsset(path.join(root, filePath)); this._processAsset(filePath);
} }
} }
} }

View File

@ -1,14 +0,0 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
'use strict';
module.exports = {
WatchmanWatcher: jest.genMockFromModule('sane/src/watchman_watcher'),
NodeWatcher: jest.genMockFromModule('sane/src/node_watcher'),
};

View File

@ -1,75 +0,0 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
'use strict';
jest
.dontMock('util')
.dontMock('events')
.dontMock('../')
.setMock('child_process', {
execSync: () => '/usr/bin/watchman',
});
describe('FileWatcher', () => {
let WatchmanWatcher;
let FileWatcher;
let config;
beforeEach(() => {
jest.resetModules();
const sane = require('sane');
WatchmanWatcher = sane.WatchmanWatcher;
WatchmanWatcher.prototype.once.mockImplementation(
(type, callback) => callback()
);
FileWatcher = require('../');
config = [{
dir: 'rootDir',
globs: [
'**/*.js',
'**/*.json',
],
}];
});
pit('gets the watcher instance when ready', () => {
const fileWatcher = new FileWatcher(config);
return fileWatcher.getWatchers().then(watchers => {
watchers.forEach(watcher => {
expect(watcher instanceof WatchmanWatcher).toBe(true);
});
});
});
pit('emits events', () => {
let cb;
WatchmanWatcher.prototype.on.mockImplementation((type, callback) => {
cb = callback;
});
const fileWatcher = new FileWatcher(config);
const handler = jest.genMockFn();
fileWatcher.on('all', handler);
return fileWatcher.getWatchers().then(watchers => {
cb(1, 2, 3, 4);
jest.runAllTimers();
expect(handler.mock.calls[0]).toEqual([1, 2, 3, 4]);
});
});
pit('ends the watcher', () => {
const fileWatcher = new FileWatcher(config);
WatchmanWatcher.prototype.close.mockImplementation(callback => callback());
return fileWatcher.end().then(() => {
expect(WatchmanWatcher.prototype.close).toBeCalled();
});
});
});

View File

@ -1,123 +0,0 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
'use strict';
const EventEmitter = require('events').EventEmitter;
const denodeify = require('denodeify');
const sane = require('sane');
const execSync = require('child_process').execSync;
const MAX_WAIT_TIME = 360000;
const detectWatcherClass = () => {
try {
execSync('watchman version', {stdio: 'ignore'});
return sane.WatchmanWatcher;
} catch (e) {}
return sane.NodeWatcher;
};
const WatcherClass = detectWatcherClass();
let inited = false;
class FileWatcher extends EventEmitter {
constructor(rootConfigs) {
if (inited) {
throw new Error('FileWatcher can only be instantiated once');
}
inited = true;
super();
this._watcherByRoot = Object.create(null);
const watcherPromises = rootConfigs.map((rootConfig) => {
return this._createWatcher(rootConfig);
});
this._loading = Promise.all(watcherPromises).then(watchers => {
watchers.forEach((watcher, i) => {
this._watcherByRoot[rootConfigs[i].dir] = watcher;
watcher.on(
'all',
// args = (type, filePath, root, stat)
(...args) => this.emit('all', ...args)
);
});
return watchers;
});
}
getWatchers() {
return this._loading;
}
getWatcherForRoot(root) {
return this._loading.then(() => this._watcherByRoot[root]);
}
isWatchman() {
return Promise.resolve(FileWatcher.canUseWatchman());
}
end() {
inited = false;
return this._loading.then(
(watchers) => watchers.map(
watcher => denodeify(watcher.close).call(watcher)
)
);
}
_createWatcher(rootConfig) {
const watcher = new WatcherClass(rootConfig.dir, {
glob: rootConfig.globs,
dot: false,
});
return new Promise((resolve, reject) => {
const rejectTimeout = setTimeout(
() => reject(new Error(timeoutMessage(WatcherClass))),
MAX_WAIT_TIME
);
watcher.once('ready', () => {
clearTimeout(rejectTimeout);
resolve(watcher);
});
});
}
static createDummyWatcher() {
return Object.assign(new EventEmitter(), {
isWatchman: () => Promise.resolve(false),
end: () => Promise.resolve(),
});
}
static canUseWatchman() {
return WatcherClass == sane.WatchmanWatcher;
}
}
function timeoutMessage(Watcher) {
const lines = [
'Watcher took too long to load (' + Watcher.name + ')',
];
if (Watcher === sane.WatchmanWatcher) {
lines.push(
'Try running `watchman version` from your terminal',
'https://facebook.github.io/watchman/docs/troubleshooting.html',
);
}
return lines.join('\n');
}
module.exports = FileWatcher;

View File

@ -16,8 +16,6 @@ const Module = require('./Module');
const Package = require('./Package'); const Package = require('./Package');
const Polyfill = require('./Polyfill'); const Polyfill = require('./Polyfill');
const path = require('path');
import type Cache from './Cache'; import type Cache from './Cache';
import type { import type {
DepGraphHelpers, DepGraphHelpers,
@ -69,8 +67,6 @@ class ModuleCache {
this._assetDependencies = assetDependencies; this._assetDependencies = assetDependencies;
this._moduleOptions = moduleOptions; this._moduleOptions = moduleOptions;
this._packageModuleMap = new WeakMap(); this._packageModuleMap = new WeakMap();
fastfs.on('change', this._processFileChange.bind(this));
} }
getModule(filePath: string) { getModule(filePath: string) {
@ -149,16 +145,14 @@ class ModuleCache {
}); });
} }
_processFileChange(type, filePath, root) { processFileChange(type: string, filePath: string) {
const absPath = path.join(root, filePath); if (this._moduleCache[filePath]) {
this._moduleCache[filePath].invalidate();
if (this._moduleCache[absPath]) { delete this._moduleCache[filePath];
this._moduleCache[absPath].invalidate();
delete this._moduleCache[absPath];
} }
if (this._packageCache[absPath]) { if (this._packageCache[filePath]) {
this._packageCache[absPath].invalidate(); this._packageCache[filePath].invalidate();
delete this._packageCache[absPath]; delete this._packageCache[filePath];
} }
} }
} }

View File

@ -61,12 +61,6 @@ describe('DependencyGraph', function() {
jest.resetModules(); jest.resetModules();
Module = require('../Module'); Module = require('../Module');
const fileWatcher = {
on: function() {
return this;
},
isWatchman: () => Promise.resolve(false),
};
const Cache = jest.genMockFn().mockImplementation(function() { const Cache = jest.genMockFn().mockImplementation(function() {
this._maps = Object.create(null); this._maps = Object.create(null);
@ -109,7 +103,6 @@ describe('DependencyGraph', function() {
defaults = { defaults = {
assetExts: ['png', 'jpg'], assetExts: ['png', 'jpg'],
cache: new Cache(), cache: new Cache(),
fileWatcher,
forceNodeFilesystemAPI: true, forceNodeFilesystemAPI: true,
providesModuleNodeModules: [ providesModuleNodeModules: [
'haste-fbjs', 'haste-fbjs',
@ -4825,7 +4818,6 @@ describe('DependencyGraph', function() {
}); });
describe('file watch updating', function() { describe('file watch updating', function() {
var triggerFileChange;
var mockStat = { var mockStat = {
isDirectory: () => false, isDirectory: () => false,
}; };
@ -4836,21 +4828,6 @@ describe('DependencyGraph', function() {
beforeEach(function() { beforeEach(function() {
process.platform = 'linux'; process.platform = 'linux';
DependencyGraph = require('../index'); DependencyGraph = require('../index');
var callbacks = [];
triggerFileChange = (...args) =>
callbacks.map(callback => callback(...args));
defaults.fileWatcher = {
on: function(eventType, callback) {
if (eventType !== 'all') {
throw new Error('Can only handle "all" event in watcher.');
}
callbacks.push(callback);
return this;
},
isWatchman: () => Promise.resolve(false),
};
}); });
afterEach(function() { afterEach(function() {
@ -4891,7 +4868,7 @@ describe('DependencyGraph', function() {
return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function() { return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function() {
filesystem.root['index.js'] = filesystem.root['index.js'] =
filesystem.root['index.js'].replace('require("foo")', ''); filesystem.root['index.js'].replace('require("foo")', '');
triggerFileChange('change', 'index.js', root, mockStat); dgraph.processFileChange('change', root + '/index.js', mockStat);
return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) {
expect(deps) expect(deps)
.toEqual([ .toEqual([
@ -4954,7 +4931,7 @@ describe('DependencyGraph', function() {
return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function() { return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function() {
filesystem.root['index.js'] = filesystem.root['index.js'] =
filesystem.root['index.js'].replace('require("foo")', ''); filesystem.root['index.js'].replace('require("foo")', '');
triggerFileChange('change', 'index.js', root, mockStat); dgraph.processFileChange('change', root + '/index.js', mockStat);
return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) {
expect(deps) expect(deps)
.toEqual([ .toEqual([
@ -5016,7 +4993,7 @@ describe('DependencyGraph', function() {
}); });
return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function() { return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function() {
delete filesystem.root.foo; delete filesystem.root.foo;
triggerFileChange('delete', 'foo.js', root); dgraph.processFileChange('delete', root + '/foo.js');
return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) {
expect(deps) expect(deps)
.toEqual([ .toEqual([
@ -5083,10 +5060,10 @@ describe('DependencyGraph', function() {
' */', ' */',
'require("foo")', 'require("foo")',
].join('\n'); ].join('\n');
triggerFileChange('add', 'bar.js', root, mockStat); dgraph.processFileChange('add', root + '/bar.js', mockStat);
filesystem.root.aPackage['main.js'] = 'require("bar")'; filesystem.root.aPackage['main.js'] = 'require("bar")';
triggerFileChange('change', 'aPackage/main.js', root, mockStat); dgraph.processFileChange('change', root + '/aPackage/main.js', mockStat);
return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) {
expect(deps) expect(deps)
@ -5175,7 +5152,7 @@ describe('DependencyGraph', function() {
]); ]);
filesystem.root['foo.png'] = ''; filesystem.root['foo.png'] = '';
triggerFileChange('add', 'foo.png', root, mockStat); dgraph.processFileChange('add', root + '/foo.png', mockStat);
return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps2) { return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps2) {
expect(deps2) expect(deps2)
@ -5245,7 +5222,7 @@ describe('DependencyGraph', function() {
]); ]);
filesystem.root['foo.png'] = ''; filesystem.root['foo.png'] = '';
triggerFileChange('add', 'foo.png', root, mockStat); dgraph.processFileChange('add', root + '/foo.png', mockStat);
return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps2) { return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps2) {
expect(deps2) expect(deps2)
@ -5277,171 +5254,6 @@ describe('DependencyGraph', function() {
}); });
}); });
it('runs changes through ignore filter', function() {
var root = '/root';
var filesystem = setMockFileSystem({
'root': {
'index.js': [
'/**',
' * @providesModule index',
' */',
'require("aPackage")',
'require("foo")',
].join('\n'),
'foo.js': [
'/**',
' * @providesModule foo',
' */',
'require("aPackage")',
].join('\n'),
'aPackage': {
'package.json': JSON.stringify({
name: 'aPackage',
main: 'main.js',
}),
'main.js': 'main',
},
},
});
var dgraph = new DependencyGraph({
...defaults,
roots: [root],
ignoreFilePath: function(filePath) {
if (filePath === '/root/bar.js') {
return true;
}
return false;
},
});
return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function() {
filesystem.root['bar.js'] = [
'/**',
' * @providesModule bar',
' */',
'require("foo")',
].join('\n');
triggerFileChange('add', 'bar.js', root, mockStat);
filesystem.root.aPackage['main.js'] = 'require("bar")';
triggerFileChange('change', 'aPackage/main.js', root, mockStat);
return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) {
expect(deps)
.toEqual([
{
id: 'index',
path: '/root/index.js',
dependencies: ['aPackage', 'foo'],
isAsset: false,
isAsset_DEPRECATED: false,
isJSON: false,
isPolyfill: false,
resolution: undefined,
resolveDependency: undefined,
},
{
id: 'aPackage/main.js',
path: '/root/aPackage/main.js',
dependencies: ['bar'],
isAsset: false,
isAsset_DEPRECATED: false,
isJSON: false,
isPolyfill: false,
resolution: undefined,
resolveDependency: undefined,
},
{
id: 'foo',
path: '/root/foo.js',
dependencies: ['aPackage'],
isAsset: false,
isAsset_DEPRECATED: false,
isJSON: false,
isPolyfill: false,
resolution: undefined,
resolveDependency: undefined,
},
]);
});
});
});
it('should ignore directory updates', function() {
var root = '/root';
setMockFileSystem({
'root': {
'index.js': [
'/**',
' * @providesModule index',
' */',
'require("aPackage")',
'require("foo")',
].join('\n'),
'foo.js': [
'/**',
' * @providesModule foo',
' */',
'require("aPackage")',
].join('\n'),
'aPackage': {
'package.json': JSON.stringify({
name: 'aPackage',
main: 'main.js',
}),
'main.js': 'main',
},
},
});
var dgraph = new DependencyGraph({
...defaults,
roots: [root],
});
return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function() {
triggerFileChange('change', 'aPackage', '/root', {
isDirectory: () => true,
});
return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) {
expect(deps)
.toEqual([
{
id: 'index',
path: '/root/index.js',
dependencies: ['aPackage', 'foo'],
isAsset: false,
isAsset_DEPRECATED: false,
isJSON: false,
isPolyfill: false,
resolution: undefined,
resolveDependency: undefined,
},
{
id: 'aPackage/main.js',
path: '/root/aPackage/main.js',
dependencies: [],
isAsset: false,
isAsset_DEPRECATED: false,
isJSON: false,
isPolyfill: false,
resolution: undefined,
resolveDependency: undefined,
},
{
id: 'foo',
path: '/root/foo.js',
dependencies: ['aPackage'],
isAsset: false,
isAsset_DEPRECATED: false,
isJSON: false,
isPolyfill: false,
resolution: undefined,
resolveDependency: undefined,
},
]);
});
});
});
it('changes to browser field', function() { it('changes to browser field', function() {
var root = '/root'; var root = '/root';
var filesystem = setMockFileSystem({ var filesystem = setMockFileSystem({
@ -5473,7 +5285,7 @@ describe('DependencyGraph', function() {
main: 'main.js', main: 'main.js',
browser: 'browser.js', browser: 'browser.js',
}); });
triggerFileChange('change', 'package.json', '/root/aPackage', mockStat); dgraph.processFileChange('change', root + '/aPackage/package.json', mockStat);
return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) {
expect(deps) expect(deps)
@ -5535,7 +5347,7 @@ describe('DependencyGraph', function() {
name: 'bPackage', name: 'bPackage',
main: 'main.js', main: 'main.js',
}); });
triggerFileChange('change', 'package.json', '/root/aPackage', mockStat); dgraph.processFileChange('change', root + '/aPackage/package.json', mockStat);
return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) {
expect(deps) expect(deps)
@ -5630,7 +5442,7 @@ describe('DependencyGraph', function() {
]); ]);
filesystem.root.node_modules.foo['main.js'] = 'lol'; filesystem.root.node_modules.foo['main.js'] = 'lol';
triggerFileChange('change', 'main.js', '/root/node_modules/foo', mockStat); dgraph.processFileChange('change', root + '/node_modules/foo/main.js', mockStat);
return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps2) { return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps2) {
expect(deps2) expect(deps2)
@ -5695,7 +5507,7 @@ describe('DependencyGraph', function() {
main: 'main.js', main: 'main.js',
browser: 'browser.js', browser: 'browser.js',
}); });
triggerFileChange('change', 'package.json', '/root/node_modules/foo', mockStat); dgraph.processFileChange('change', root + '/node_modules/foo/package.json', mockStat);
return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps2) { return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps2) {
expect(deps2) expect(deps2)
@ -5752,7 +5564,7 @@ describe('DependencyGraph', function() {
}); });
return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function() { return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function() {
triggerFileChange('add', 'index.js', root, mockStat); dgraph.processFileChange('add', root + '/index.js', mockStat);
return getOrderedDependenciesAsJSON(dgraph, '/root/index.js'); return getOrderedDependenciesAsJSON(dgraph, '/root/index.js');
}); });
}); });

View File

@ -47,10 +47,6 @@ function mockIndexFile(indexJs) {
} }
describe('Module', () => { describe('Module', () => {
const fileWatcher = {
on: () => this,
isWatchman: () => Promise.resolve(false),
};
const fileName = '/root/index.js'; const fileName = '/root/index.js';
let cache, fastfs; let cache, fastfs;
@ -85,7 +81,6 @@ describe('Module', () => {
new Fastfs( new Fastfs(
'test', 'test',
['/root'], ['/root'],
fileWatcher,
['/root/index.js', '/root/package.json'], ['/root/index.js', '/root/package.json'],
{ignore: []}, {ignore: []},
); );
@ -119,22 +114,22 @@ describe('Module', () => {
mockIndexFile(source); mockIndexFile(source);
}); });
pit('extracts the module name from the header', () => it('extracts the module name from the header', () =>
module.getName().then(name => expect(name).toEqual(moduleId)) module.getName().then(name => expect(name).toEqual(moduleId))
); );
pit('identifies the module as haste module', () => it('identifies the module as haste module', () =>
module.isHaste().then(isHaste => expect(isHaste).toBe(true)) module.isHaste().then(isHaste => expect(isHaste).toBe(true))
); );
pit('does not transform the file in order to access the name', () => { it('does not transform the file in order to access the name', () => {
const transformCode = const transformCode =
jest.genMockFn().mockReturnValue(Promise.resolve()); jest.genMockFn().mockReturnValue(Promise.resolve());
return createModule({transformCode}).getName() return createModule({transformCode}).getName()
.then(() => expect(transformCode).not.toBeCalled()); .then(() => expect(transformCode).not.toBeCalled());
}); });
pit('does not transform the file in order to access the haste status', () => { it('does not transform the file in order to access the haste status', () => {
const transformCode = const transformCode =
jest.genMockFn().mockReturnValue(Promise.resolve()); jest.genMockFn().mockReturnValue(Promise.resolve());
return createModule({transformCode}).isHaste() return createModule({transformCode}).isHaste()
@ -147,22 +142,22 @@ describe('Module', () => {
mockIndexFile(source.replace(/@providesModule/, '@provides')); mockIndexFile(source.replace(/@providesModule/, '@provides'));
}); });
pit('extracts the module name from the header if it has a @provides annotation', () => it('extracts the module name from the header if it has a @provides annotation', () =>
module.getName().then(name => expect(name).toEqual(moduleId)) module.getName().then(name => expect(name).toEqual(moduleId))
); );
pit('identifies the module as haste module', () => it('identifies the module as haste module', () =>
module.isHaste().then(isHaste => expect(isHaste).toBe(true)) module.isHaste().then(isHaste => expect(isHaste).toBe(true))
); );
pit('does not transform the file in order to access the name', () => { it('does not transform the file in order to access the name', () => {
const transformCode = const transformCode =
jest.genMockFn().mockReturnValue(Promise.resolve()); jest.genMockFn().mockReturnValue(Promise.resolve());
return createModule({transformCode}).getName() return createModule({transformCode}).getName()
.then(() => expect(transformCode).not.toBeCalled()); .then(() => expect(transformCode).not.toBeCalled());
}); });
pit('does not transform the file in order to access the haste status', () => { it('does not transform the file in order to access the haste status', () => {
const transformCode = const transformCode =
jest.genMockFn().mockReturnValue(Promise.resolve()); jest.genMockFn().mockReturnValue(Promise.resolve());
return createModule({transformCode}).isHaste() return createModule({transformCode}).isHaste()
@ -175,22 +170,22 @@ describe('Module', () => {
mockIndexFile('arbitrary(code);'); mockIndexFile('arbitrary(code);');
}); });
pit('uses the file name as module name', () => it('uses the file name as module name', () =>
module.getName().then(name => expect(name).toEqual(fileName)) module.getName().then(name => expect(name).toEqual(fileName))
); );
pit('does not identify the module as haste module', () => it('does not identify the module as haste module', () =>
module.isHaste().then(isHaste => expect(isHaste).toBe(false)) module.isHaste().then(isHaste => expect(isHaste).toBe(false))
); );
pit('does not transform the file in order to access the name', () => { it('does not transform the file in order to access the name', () => {
const transformCode = const transformCode =
jest.genMockFn().mockReturnValue(Promise.resolve()); jest.genMockFn().mockReturnValue(Promise.resolve());
return createModule({transformCode}).getName() return createModule({transformCode}).getName()
.then(() => expect(transformCode).not.toBeCalled()); .then(() => expect(transformCode).not.toBeCalled());
}); });
pit('does not transform the file in order to access the haste status', () => { it('does not transform the file in order to access the haste status', () => {
const transformCode = const transformCode =
jest.genMockFn().mockReturnValue(Promise.resolve()); jest.genMockFn().mockReturnValue(Promise.resolve());
return createModule({transformCode}).isHaste() return createModule({transformCode}).isHaste()
@ -205,12 +200,12 @@ describe('Module', () => {
mockIndexFile(fileContents); mockIndexFile(fileContents);
}); });
pit('exposes file contents as `code` property on the data exposed by `read()`', () => it('exposes file contents as `code` property on the data exposed by `read()`', () =>
createModule().read().then(({code}) => createModule().read().then(({code}) =>
expect(code).toBe(fileContents)) expect(code).toBe(fileContents))
); );
pit('exposes file contents via the `getCode()` method', () => it('exposes file contents via the `getCode()` method', () =>
createModule().getCode().then(code => createModule().getCode().then(code =>
expect(code).toBe(fileContents)) expect(code).toBe(fileContents))
); );
@ -241,7 +236,7 @@ describe('Module', () => {
mockIndexFile(fileContents); mockIndexFile(fileContents);
}); });
pit('passes the module and file contents to the transform function when reading', () => { it('passes the module and file contents to the transform function when reading', () => {
const module = createModule({transformCode}); const module = createModule({transformCode});
return module.read() return module.read()
.then(() => { .then(() => {
@ -249,7 +244,7 @@ describe('Module', () => {
}); });
}); });
pit('passes any additional options to the transform function when reading', () => { it('passes any additional options to the transform function when reading', () => {
const module = createModule({transformCode}); const module = createModule({transformCode});
const transformOptions = {arbitrary: Object()}; const transformOptions = {arbitrary: Object()};
return module.read(transformOptions) return module.read(transformOptions)
@ -258,7 +253,7 @@ describe('Module', () => {
); );
}); });
pit('passes the module and file contents to the transform if the file is annotated with @extern', () => { it('passes module and file contents if the file is annotated with @extern', () => {
const module = createModule({transformCode}); const module = createModule({transformCode});
const customFileContents = ` const customFileContents = `
/** /**
@ -271,7 +266,7 @@ describe('Module', () => {
}); });
}); });
pit('passes the module and file contents to the transform for JSON files', () => { it('passes the module and file contents to the transform for JSON files', () => {
mockPackageFile(); mockPackageFile();
const module = createJSONModule({transformCode}); const module = createJSONModule({transformCode});
return module.read().then(() => { return module.read().then(() => {
@ -279,7 +274,7 @@ describe('Module', () => {
}); });
}); });
pit('does not extend the passed options object if the file is annotated with @extern', () => { it('does not extend the passed options object if the file is annotated with @extern', () => {
const module = createModule({transformCode}); const module = createModule({transformCode});
const customFileContents = ` const customFileContents = `
/** /**
@ -295,7 +290,7 @@ describe('Module', () => {
}); });
}); });
pit('does not extend the passed options object for JSON files', () => { it('does not extend the passed options object for JSON files', () => {
mockPackageFile(); mockPackageFile();
const module = createJSONModule({transformCode}); const module = createJSONModule({transformCode});
const options = {arbitrary: 'foo'}; const options = {arbitrary: 'foo'};
@ -306,7 +301,7 @@ describe('Module', () => {
}); });
}); });
pit('uses dependencies that `transformCode` resolves to, instead of extracting them', () => { it('uses dependencies that `transformCode` resolves to, instead of extracting them', () => {
const mockedDependencies = ['foo', 'bar']; const mockedDependencies = ['foo', 'bar'];
transformResult = { transformResult = {
code: exampleCode, code: exampleCode,
@ -319,7 +314,7 @@ describe('Module', () => {
}); });
}); });
pit('forwards all additional properties of the result provided by `transformCode`', () => { it('forwards all additional properties of the result provided by `transformCode`', () => {
transformResult = { transformResult = {
code: exampleCode, code: exampleCode,
arbitrary: 'arbitrary', arbitrary: 'arbitrary',
@ -334,7 +329,7 @@ describe('Module', () => {
}); });
}); });
pit('does not store anything but dependencies if the `cacheTransformResults` option is disabled', () => { it('only stores dependencies if `cacheTransformResults` option is disabled', () => {
transformResult = { transformResult = {
code: exampleCode, code: exampleCode,
arbitrary: 'arbitrary', arbitrary: 'arbitrary',
@ -354,7 +349,7 @@ describe('Module', () => {
}); });
}); });
pit('stores all things if options is undefined', () => { it('stores all things if options is undefined', () => {
transformResult = { transformResult = {
code: exampleCode, code: exampleCode,
arbitrary: 'arbitrary', arbitrary: 'arbitrary',
@ -370,7 +365,7 @@ describe('Module', () => {
}); });
}); });
pit('exposes the transformed code rather than the raw file contents', () => { it('exposes the transformed code rather than the raw file contents', () => {
transformResult = {code: exampleCode}; transformResult = {code: exampleCode};
const module = createModule({transformCode}); const module = createModule({transformCode});
return Promise.all([module.read(), module.getCode()]) return Promise.all([module.read(), module.getCode()])
@ -380,13 +375,13 @@ describe('Module', () => {
}); });
}); });
pit('exposes the raw file contents as `source` property', () => { it('exposes the raw file contents as `source` property', () => {
const module = createModule({transformCode}); const module = createModule({transformCode});
return module.read() return module.read()
.then(data => expect(data.source).toBe(fileContents)); .then(data => expect(data.source).toBe(fileContents));
}); });
pit('exposes a source map returned by the transform', () => { it('exposes a source map returned by the transform', () => {
const map = {version: 3}; const map = {version: 3};
transformResult = {map, code: exampleCode}; transformResult = {map, code: exampleCode};
const module = createModule({transformCode}); const module = createModule({transformCode});
@ -397,7 +392,7 @@ describe('Module', () => {
}); });
}); });
pit('caches the transform result for the same transform options', () => { it('caches the transform result for the same transform options', () => {
let module = createModule({transformCode}); let module = createModule({transformCode});
return module.read() return module.read()
.then(() => { .then(() => {
@ -412,7 +407,7 @@ describe('Module', () => {
}); });
}); });
pit('triggers a new transform for different transform options', () => { it('triggers a new transform for different transform options', () => {
const module = createModule({transformCode}); const module = createModule({transformCode});
return module.read({foo: 1}) return module.read({foo: 1})
.then(() => { .then(() => {
@ -424,7 +419,7 @@ describe('Module', () => {
}); });
}); });
pit('triggers a new transform for different source code', () => { it('triggers a new transform for different source code', () => {
let module = createModule({transformCode}); let module = createModule({transformCode});
return module.read() return module.read()
.then(() => { .then(() => {
@ -440,7 +435,7 @@ describe('Module', () => {
}); });
}); });
pit('triggers a new transform for different transform cache key', () => { it('triggers a new transform for different transform cache key', () => {
let module = createModule({transformCode}); let module = createModule({transformCode});
return module.read() return module.read()
.then(() => { .then(() => {

View File

@ -18,10 +18,6 @@ const {EventEmitter} = require('events');
const NOT_FOUND_IN_ROOTS = 'NotFoundInRootsError'; const NOT_FOUND_IN_ROOTS = 'NotFoundInRootsError';
interface FileWatcher {
on(event: 'all', handler: (type: string, filePath: string, rootPath: string, fstat: fs.Stats) => void): void,
}
const { const {
createActionStartEntry, createActionStartEntry,
createActionEndEntry, createActionEndEntry,
@ -32,7 +28,6 @@ const {
class Fastfs extends EventEmitter { class Fastfs extends EventEmitter {
_name: string; _name: string;
_fileWatcher: FileWatcher;
_ignore: (filePath: string) => boolean; _ignore: (filePath: string) => boolean;
_roots: Array<File>; _roots: Array<File>;
_fastPaths: {[filePath: string]: File}; _fastPaths: {[filePath: string]: File};
@ -40,7 +35,6 @@ class Fastfs extends EventEmitter {
constructor( constructor(
name: string, name: string,
roots: Array<string>, roots: Array<string>,
fileWatcher: FileWatcher,
files: Array<string>, files: Array<string>,
{ignore}: { {ignore}: {
ignore: (filePath: string) => boolean, ignore: (filePath: string) => boolean,
@ -48,7 +42,6 @@ class Fastfs extends EventEmitter {
) { ) {
super(); super();
this._name = name; this._name = name;
this._fileWatcher = fileWatcher;
this._ignore = ignore; this._ignore = ignore;
this._roots = roots.map(root => { this._roots = roots.map(root => {
// If the path ends in a separator ("/"), remove it to make string // If the path ends in a separator ("/"), remove it to make string
@ -82,10 +75,6 @@ class Fastfs extends EventEmitter {
}); });
print(log(createActionEndEntry(buildingInMemoryFSLogEntry))); print(log(createActionEndEntry(buildingInMemoryFSLogEntry)));
if (this._fileWatcher) {
this._fileWatcher.on('all', this._processFileChange.bind(this));
}
} }
stat(filePath: string) { stat(filePath: string) {
@ -205,33 +194,23 @@ class Fastfs extends EventEmitter {
return this._fastPaths[filePath]; return this._fastPaths[filePath];
} }
_processFileChange(type, filePath, rootPath, fstat) { processFileChange(type: string, filePath: string) {
const absPath = path.join(rootPath, filePath);
if (this._ignore(absPath) || (fstat && fstat.isDirectory())) {
return;
}
// Make sure this event belongs to one of our roots.
const root = this._getRoot(absPath);
if (!root) {
return;
}
if (type === 'delete' || type === 'change') { if (type === 'delete' || type === 'change') {
const file = this._getFile(absPath); const file = this._getFile(filePath);
if (file) { if (file) {
file.remove(); file.remove();
} }
} }
delete this._fastPaths[path.resolve(absPath)]; delete this._fastPaths[path.resolve(filePath)];
if (type !== 'delete') { if (type !== 'delete') {
const file = new File(absPath, false); const file = new File(filePath, false);
root.addChild(file, this._fastPaths); const root = this._getRoot(filePath);
if (root) {
root.addChild(file, this._fastPaths);
}
} }
this.emit('change', type, filePath, rootPath, fstat);
} }
} }

View File

@ -15,7 +15,6 @@ const Cache = require('./Cache');
const DependencyGraphHelpers = require('./DependencyGraph/DependencyGraphHelpers'); const DependencyGraphHelpers = require('./DependencyGraph/DependencyGraphHelpers');
const DeprecatedAssetMap = require('./DependencyGraph/DeprecatedAssetMap'); const DeprecatedAssetMap = require('./DependencyGraph/DeprecatedAssetMap');
const Fastfs = require('./fastfs'); const Fastfs = require('./fastfs');
const FileWatcher = require('./FileWatcher');
const HasteMap = require('./DependencyGraph/HasteMap'); const HasteMap = require('./DependencyGraph/HasteMap');
const JestHasteMap = require('jest-haste-map'); const JestHasteMap = require('jest-haste-map');
const Module = require('./Module'); const Module = require('./Module');
@ -47,12 +46,16 @@ const {
print, print,
} = require('../Logger'); } = require('../Logger');
const escapePath = (p: string) => {
return (path.sep === '\\') ? p.replace(/(\/|\\(?!\.))/g, '\\\\') : p;
};
class DependencyGraph { class DependencyGraph {
_opts: { _opts: {
roots: Array<string>, roots: Array<string>,
ignoreFilePath: (filePath: string) => boolean, ignoreFilePath: (filePath: string) => boolean,
fileWatcher: FileWatcher, watch: boolean,
forceNodeFilesystemAPI: boolean, forceNodeFilesystemAPI: boolean,
assetRoots_DEPRECATED: Array<string>, assetRoots_DEPRECATED: Array<string>,
assetExts: Array<string>, assetExts: Array<string>,
@ -71,21 +74,23 @@ class DependencyGraph {
maxWorkers: number, maxWorkers: number,
resetCache: boolean, resetCache: boolean,
}; };
_cache: Cache;
_assetDependencies: mixed; _assetDependencies: mixed;
_helpers: DependencyGraphHelpers; _assetPattern: RegExp;
_fastfs: Fastfs; _cache: Cache;
_moduleCache: ModuleCache;
_hasteMap: HasteMap;
_deprecatedAssetMap: DeprecatedAssetMap; _deprecatedAssetMap: DeprecatedAssetMap;
_fastfs: Fastfs;
_haste: JestHasteMap;
_hasteMap: HasteMap;
_hasteMapError: ?Error; _hasteMapError: ?Error;
_helpers: DependencyGraphHelpers;
_moduleCache: ModuleCache;
_loading: ?Promise<mixed>; _loading: Promise<mixed>;
constructor({ constructor({
roots, roots,
ignoreFilePath, ignoreFilePath,
fileWatcher, watch,
forceNodeFilesystemAPI, forceNodeFilesystemAPI,
assetRoots_DEPRECATED, assetRoots_DEPRECATED,
assetExts, assetExts,
@ -109,7 +114,7 @@ class DependencyGraph {
}: { }: {
roots: Array<string>, roots: Array<string>,
ignoreFilePath: (filePath: string) => boolean, ignoreFilePath: (filePath: string) => boolean,
fileWatcher: FileWatcher, watch: boolean,
forceNodeFilesystemAPI?: boolean, forceNodeFilesystemAPI?: boolean,
assetRoots_DEPRECATED: Array<string>, assetRoots_DEPRECATED: Array<string>,
assetExts: Array<string>, assetExts: Array<string>,
@ -133,7 +138,7 @@ class DependencyGraph {
this._opts = { this._opts = {
roots, roots,
ignoreFilePath: ignoreFilePath || (() => {}), ignoreFilePath: ignoreFilePath || (() => {}),
fileWatcher, watch: !!watch,
forceNodeFilesystemAPI: !!forceNodeFilesystemAPI, forceNodeFilesystemAPI: !!forceNodeFilesystemAPI,
assetRoots_DEPRECATED: assetRoots_DEPRECATED || [], assetRoots_DEPRECATED: assetRoots_DEPRECATED || [],
assetExts: assetExts || [], assetExts: assetExts || [],
@ -155,6 +160,9 @@ class DependencyGraph {
maxWorkers, maxWorkers,
resetCache, resetCache,
}; };
this._assetPattern =
new RegExp('^' + this._opts.assetRoots_DEPRECATED.map(escapePath).join('|'));
this._cache = cache; this._cache = cache;
this._assetDependencies = assetDependencies; this._assetDependencies = assetDependencies;
this._helpers = new DependencyGraphHelpers(this._opts); this._helpers = new DependencyGraphHelpers(this._opts);
@ -167,7 +175,7 @@ class DependencyGraph {
} }
const mw = this._opts.maxWorkers; const mw = this._opts.maxWorkers;
const haste = new JestHasteMap({ this._haste = new JestHasteMap({
extensions: this._opts.extensions.concat(this._opts.assetExts), extensions: this._opts.extensions.concat(this._opts.assetExts),
forceNodeFilesystemAPI: this._opts.forceNodeFilesystemAPI, forceNodeFilesystemAPI: this._opts.forceNodeFilesystemAPI,
ignorePattern: {test: this._opts.ignoreFilePath}, ignorePattern: {test: this._opts.ignoreFilePath},
@ -180,9 +188,10 @@ class DependencyGraph {
retainAllFiles: true, retainAllFiles: true,
roots: this._opts.roots.concat(this._opts.assetRoots_DEPRECATED), roots: this._opts.roots.concat(this._opts.assetRoots_DEPRECATED),
useWatchman: this._opts.useWatchman, useWatchman: this._opts.useWatchman,
watch: this._opts.watch,
}); });
this._loading = haste.build().then(hasteMap => { this._loading = this._haste.build().then(hasteMap => {
const initializingPackagerLogEntry = const initializingPackagerLogEntry =
print(log(createActionStartEntry('Initializing Packager'))); print(log(createActionStartEntry('Initializing Packager')));
@ -191,15 +200,12 @@ class DependencyGraph {
this._fastfs = new Fastfs( this._fastfs = new Fastfs(
'JavaScript', 'JavaScript',
this._opts.roots, this._opts.roots,
this._opts.fileWatcher,
hasteFSFiles, hasteFSFiles,
{ {
ignore: this._opts.ignoreFilePath, ignore: this._opts.ignoreFilePath,
} }
); );
this._fastfs.on('change', this._processFileChange.bind(this));
this._moduleCache = new ModuleCache({ this._moduleCache = new ModuleCache({
fastfs: this._fastfs, fastfs: this._fastfs,
cache: this._cache, cache: this._cache,
@ -219,14 +225,7 @@ class DependencyGraph {
platforms: this._opts.platforms, platforms: this._opts.platforms,
}); });
const escapePath = (p: string) => { const assetFiles = hasteMap.hasteFS.matchFiles(this._assetPattern);
return (path.sep === '\\') ? p.replace(/(\/|\\(?!\.))/g, '\\\\') : p;
};
const assetPattern =
new RegExp('^' + this._opts.assetRoots_DEPRECATED.map(escapePath).join('|'));
const assetFiles = hasteMap.hasteFS.matchFiles(assetPattern);
this._deprecatedAssetMap = new DeprecatedAssetMap({ this._deprecatedAssetMap = new DeprecatedAssetMap({
helpers: this._helpers, helpers: this._helpers,
@ -235,11 +234,11 @@ class DependencyGraph {
files: assetFiles, files: assetFiles,
}); });
this._fastfs.on('change', (type, filePath, root, fstat) => { this._haste.on('change', ({eventsQueue}) =>
if (assetPattern.test(path.join(root, filePath))) { eventsQueue.forEach(({type, filePath, stat}) =>
this._deprecatedAssetMap.processFileChange(type, filePath, root, fstat); this.processFileChange(type, filePath, stat)
} )
}); );
const buildingHasteMapLogEntry = const buildingHasteMapLogEntry =
print(log(createActionStartEntry('Building Haste Map'))); print(log(createActionStartEntry('Building Haste Map')));
@ -279,6 +278,10 @@ class DependencyGraph {
return this._fastfs; return this._fastfs;
} }
getWatcher() {
return this._haste;
}
/** /**
* Returns the module object for the given path. * Returns the module object for the given path.
*/ */
@ -365,21 +368,16 @@ class DependencyGraph {
); );
} }
_processFileChange(type, filePath, root, fstat) { processFileChange(type: string, filePath: string, stat: Object) {
const absPath = path.join(root, filePath); this._fastfs.processFileChange(type, filePath, stat);
if (fstat && fstat.isDirectory() || this._moduleCache.processFileChange(type, filePath, stat);
this._opts.ignoreFilePath(absPath) || if (this._assetPattern.test(filePath)) {
this._helpers.isNodeModulesDir(absPath)) { this._deprecatedAssetMap.processFileChange(type, filePath, stat);
return;
} }
// Ok, this is some tricky promise code. Our requirements are: // This code reports failures but doesn't block recovery in the dev server
// * we need to report back failures // mode. When the hasteMap is left in an incorrect state, we'll rebuild when
// * failures shouldn't block recovery // the next file changes.
// * Errors can leave `hasteMap` in an incorrect state, and we need to rebuild
// After we process a file change we record any errors which will also be
// reported via the next request. On the next file change, we'll see that
// we are in an error state and we should decide to do a full rebuild.
const resolve = () => { const resolve = () => {
if (this._hasteMapError) { if (this._hasteMapError) {
console.warn( console.warn(
@ -391,13 +389,14 @@ class DependencyGraph {
// Rebuild the entire map if last change resulted in an error. // Rebuild the entire map if last change resulted in an error.
this._loading = this._hasteMap.build(); this._loading = this._hasteMap.build();
} else { } else {
this._loading = this._hasteMap.processFileChange(type, absPath); this._loading = this._hasteMap.processFileChange(type, filePath);
this._loading.catch((e) => {this._hasteMapError = e;}); this._loading.catch(error => {
this._hasteMapError = error;
});
} }
return this._loading; return this._loading;
}; };
/* $FlowFixMe: there is a risk this happen before we assign that
* variable in the load() function. */
this._loading = this._loading.then(resolve, resolve); this._loading = this._loading.then(resolve, resolve);
} }
@ -411,7 +410,6 @@ class DependencyGraph {
static Cache; static Cache;
static Fastfs; static Fastfs;
static FileWatcher;
static Module; static Module;
static Polyfill; static Polyfill;
static getAssetDataFromName; static getAssetDataFromName;
@ -424,7 +422,6 @@ class DependencyGraph {
Object.assign(DependencyGraph, { Object.assign(DependencyGraph, {
Cache, Cache,
Fastfs, Fastfs,
FileWatcher,
Module, Module,
Polyfill, Polyfill,
getAssetDataFromName, getAssetDataFromName,