diff --git a/docs/GettingStarted.md b/docs/GettingStarted.md
index 38ba3b785..04cbaf406 100644
--- a/docs/GettingStarted.md
+++ b/docs/GettingStarted.md
@@ -362,8 +362,6 @@ If everything is set up correctly, you should see your new app running in your A
-> 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.
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.
display('os', devOS);
- display('platform', targetPlatform);
+ display('platform', targetPlatform);
foundHash = true;
break;
}
diff --git a/local-cli/bundle/buildBundle.js b/local-cli/bundle/buildBundle.js
index 047ec2ac5..d950ff802 100644
--- a/local-cli/bundle/buildBundle.js
+++ b/local-cli/bundle/buildBundle.js
@@ -55,8 +55,8 @@ function buildBundle(args, config, output = outputBundle, packagerInstance) {
getTransformOptionsModulePath: config.getTransformOptionsModulePath,
transformModulePath: transformModulePath,
extraNodeModules: config.extraNodeModules,
- nonPersistent: true,
resetCache: args.resetCache,
+ watch: false,
};
packagerInstance = new Server(options);
diff --git a/local-cli/server/runServer.js b/local-cli/server/runServer.js
index 5d2ae1730..68e51d7f1 100644
--- a/local-cli/server/runServer.js
+++ b/local-cli/server/runServer.js
@@ -8,12 +8,14 @@
*/
'use strict';
+const InspectorProxy = require('./util/inspectorProxy.js');
const ReactPackager = require('../../packager/react-packager');
const attachHMRServer = require('./util/attachHMRServer');
const connect = require('connect');
const copyToClipBoardMiddleware = require('./middleware/copyToClipBoardMiddleware');
const cpuProfilerMiddleware = require('./middleware/cpuProfilerMiddleware');
+const defaultAssetExts = require('../../packager/defaults').assetExts;
const getDevToolsMiddleware = require('./middleware/getDevToolsMiddleware');
const heapCaptureMiddleware = require('./middleware/heapCaptureMiddleware.js');
const http = require('http');
@@ -25,10 +27,8 @@ const openStackFrameInEditorMiddleware = require('./middleware/openStackFrameInE
const path = require('path');
const statusPageMiddleware = require('./middleware/statusPageMiddleware.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 webSocketProxy = require('./util/webSocketProxy.js');
function runServer(args, config, readyCallback) {
var wsProxy = null;
@@ -86,17 +86,17 @@ function getPackagerServer(args, config) {
undefined;
return ReactPackager.createServer({
- nonPersistent: args.nonPersistent,
- projectRoots: args.projectRoots,
+ assetExts: defaultAssetExts.concat(args.assetExts),
+ assetRoots: args.assetRoots,
blacklistRE: config.getBlacklistRE(),
cacheVersion: '3',
- getTransformOptionsModulePath: config.getTransformOptionsModulePath,
- transformModulePath: transformModulePath,
extraNodeModules: config.extraNodeModules,
- assetRoots: args.assetRoots,
- assetExts: defaultAssetExts.concat(args.assetExts),
+ getTransformOptionsModulePath: config.getTransformOptionsModulePath,
+ projectRoots: args.projectRoots,
resetCache: args.resetCache,
+ transformModulePath: transformModulePath,
verbose: args.verbose,
+ watch: args.nonPersistent != null,
});
}
diff --git a/packager/package.json b/packager/package.json
index 700d9715c..126696df9 100644
--- a/packager/package.json
+++ b/packager/package.json
@@ -5,32 +5,5 @@
"repository": {
"type": "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"
}
}
diff --git a/packager/react-packager/index.js b/packager/react-packager/index.js
index e8f31d07a..a0f7c501c 100644
--- a/packager/react-packager/index.js
+++ b/packager/react-packager/index.js
@@ -45,26 +45,14 @@ function createServer(options) {
enableDebug();
}
+ options = Object.assign({}, options);
+ delete options.verbose;
var Server = require('./src/Server');
- return new Server(omit(options, ['verbose']));
+ return new Server(options);
}
function createNonPersistentServer(options) {
Logger.disablePrinting();
- // Don't start the filewatcher or the cache.
- if (options.nonPersistent == null) {
- options.nonPersistent = true;
- }
-
+ options.watch = options.nonPersistent != null;
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;
- }, {});
-}
diff --git a/packager/react-packager/src/AssetServer/__tests__/AssetServer-test.js b/packager/react-packager/src/AssetServer/__tests__/AssetServer-test.js
index 7e70d6bc4..69b442bf7 100644
--- a/packager/react-packager/src/AssetServer/__tests__/AssetServer-test.js
+++ b/packager/react-packager/src/AssetServer/__tests__/AssetServer-test.js
@@ -15,17 +15,15 @@ jest.mock('fs');
const AssetServer = require('../');
const crypto = require('crypto');
-const {EventEmitter} = require('events');
const fs = require('fs');
const {objectContaining} = jasmine;
describe('AssetServer', () => {
- let fileWatcher;
beforeEach(() => {
const NodeHaste = require('../../node-haste');
- NodeHaste.getAssetDataFromName = require.requireActual('../../node-haste/lib/getAssetDataFromName');
- fileWatcher = new EventEmitter();
+ NodeHaste.getAssetDataFromName =
+ require.requireActual('../../node-haste/lib/getAssetDataFromName');
});
describe('assetServer.get', () => {
@@ -33,7 +31,6 @@ describe('AssetServer', () => {
const server = new AssetServer({
projectRoots: ['/root'],
assetExts: ['png'],
- fileWatcher,
});
fs.__setMockFilesystem({
@@ -59,7 +56,6 @@ describe('AssetServer', () => {
const server = new AssetServer({
projectRoots: ['/root'],
assetExts: ['png'],
- fileWatcher,
});
fs.__setMockFilesystem({
@@ -97,7 +93,6 @@ describe('AssetServer', () => {
const server = new AssetServer({
projectRoots: ['/root'],
assetExts: ['png', 'jpg'],
- fileWatcher,
});
fs.__setMockFilesystem({
@@ -124,7 +119,6 @@ describe('AssetServer', () => {
const server = new AssetServer({
projectRoots: ['/root'],
assetExts: ['png'],
- fileWatcher,
});
fs.__setMockFilesystem({
@@ -147,7 +141,6 @@ describe('AssetServer', () => {
const server = new AssetServer({
projectRoots: ['/root'],
assetExts: ['png'],
- fileWatcher,
});
fs.__setMockFilesystem({
@@ -179,7 +172,6 @@ describe('AssetServer', () => {
const server = new AssetServer({
projectRoots: ['/root', '/root2'],
assetExts: ['png'],
- fileWatcher,
});
fs.__setMockFilesystem({
@@ -208,7 +200,6 @@ describe('AssetServer', () => {
const server = new AssetServer({
projectRoots: ['/root'],
assetExts: ['png'],
- fileWatcher,
});
fs.__setMockFilesystem({
@@ -241,7 +232,6 @@ describe('AssetServer', () => {
const server = new AssetServer({
projectRoots: ['/root'],
assetExts: ['png', 'jpeg'],
- fileWatcher,
});
fs.__setMockFilesystem({
@@ -271,15 +261,14 @@ describe('AssetServer', () => {
});
describe('hash:', () => {
- let server, fileSystem;
+ let server, mockFS;
beforeEach(() => {
server = new AssetServer({
projectRoots: ['/root'],
assetExts: ['jpg'],
- fileWatcher,
});
- fileSystem = {
+ mockFS = {
'root': {
imgs: {
'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', () => {
const hash = crypto.createHash('md5');
- for (const name in fileSystem.root.imgs) {
- hash.update(fileSystem.root.imgs[name]);
+ for (const name in mockFS.root.imgs) {
+ hash.update(mockFS.root.imgs[name]);
}
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', () => {
return server.getAssetData('imgs/b.jpg').then(initialData => {
- fileSystem.root.imgs['b@4x.jpg'] = 'updated data';
- fileWatcher.emit('all', 'arbitrary', '/root', 'imgs/b@4x.jpg');
+ mockFS.root.imgs['b@4x.jpg'] = 'updated data';
+ server.onFileChange('all', '/root/imgs/b@4x.jpg');
return server.getAssetData('imgs/b.jpg').then(data =>
expect(data.hash).not.toEqual(initialData.hash)
);
diff --git a/packager/react-packager/src/AssetServer/index.js b/packager/react-packager/src/AssetServer/index.js
index e1a02f654..a267eea6b 100644
--- a/packager/react-packager/src/AssetServer/index.js
+++ b/packager/react-packager/src/AssetServer/index.js
@@ -42,10 +42,6 @@ const validateOpts = declareOpts({
type: 'array',
required: true,
},
- fileWatcher: {
- type: 'object',
- required: true,
- }
});
class AssetServer {
@@ -55,9 +51,6 @@ class AssetServer {
this._assetExts = opts.assetExts;
this._hashes = new Map();
this._files = new Map();
-
- opts.fileWatcher
- .on('all', (type, root, file) => this._onFileChange(type, root, file));
}
get(assetPath, platform = null) {
@@ -84,7 +77,6 @@ class AssetServer {
data.scales = record.scales;
data.files = record.files;
-
if (this._hashes.has(assetPath)) {
data.hash = this._hashes.get(assetPath);
return data;
@@ -106,9 +98,8 @@ class AssetServer {
});
}
- _onFileChange(type, root, file) {
- const asset = this._files.get(path.join(root, file));
- this._hashes.delete(asset);
+ onFileChange(type, filePath) {
+ this._hashes.delete(this._files.get(filePath));
}
/**
@@ -187,7 +178,10 @@ class AssetServer {
}
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})`,
+ );
});
}
diff --git a/packager/react-packager/src/Bundler/index.js b/packager/react-packager/src/Bundler/index.js
index 4165edcd9..26e9b6646 100644
--- a/packager/react-packager/src/Bundler/index.js
+++ b/packager/react-packager/src/Bundler/index.js
@@ -79,10 +79,6 @@ const validateOpts = declareOpts({
type: 'object',
required: false,
},
- nonPersistent: {
- type: 'boolean',
- default: false,
- },
assetRoots: {
type: 'array',
required: false,
@@ -91,9 +87,9 @@ const validateOpts = declareOpts({
type: 'array',
default: ['png'],
},
- fileWatcher: {
- type: 'object',
- required: true,
+ watch: {
+ type: 'boolean',
+ default: false,
},
assetServer: {
type: 'object',
@@ -124,10 +120,9 @@ type Options = {
resetCache: boolean,
transformModulePath: string,
extraNodeModules: {},
- nonPersistent: boolean,
assetRoots: Array,
assetExts: Array,
- fileWatcher: {},
+ watch: boolean,
assetServer: AssetServer,
transformTimeoutInterval: ?number,
allowBundleUpdates: boolean,
@@ -191,7 +186,7 @@ class Bundler {
blacklistRE: opts.blacklistRE,
cache: this._cache,
extraNodeModules: opts.extraNodeModules,
- fileWatcher: opts.fileWatcher,
+ watch: opts.watch,
minifyCode: this._transformer.minify,
moduleFormat: opts.moduleFormat,
polyfillModuleNames: opts.polyfillModuleNames,
@@ -217,9 +212,12 @@ class Bundler {
}
}
- kill() {
+ end() {
this._transformer.kill();
- return this._cache.end();
+ return Promise.all([
+ this._cache.end(),
+ this.getResolver().getDependencyGraph().getWatcher().end(),
+ ]);
}
bundle(options: {
diff --git a/packager/react-packager/src/Resolver/index.js b/packager/react-packager/src/Resolver/index.js
index 7588d2384..47c28123e 100644
--- a/packager/react-packager/src/Resolver/index.js
+++ b/packager/react-packager/src/Resolver/index.js
@@ -35,9 +35,9 @@ const validateOpts = declareOpts({
type: 'array',
default: [],
},
- fileWatcher: {
- type: 'object',
- required: true,
+ watch: {
+ type: 'boolean',
+ default: false,
},
assetExts: {
type: 'array',
@@ -101,14 +101,13 @@ class Resolver {
providesModuleNodeModules: defaults.providesModuleNodeModules,
platforms: defaults.platforms,
preferNativePlatform: true,
- fileWatcher: opts.fileWatcher,
+ watch: opts.watch,
cache: opts.cache,
shouldThrowOnUnresolvedErrors: (_, platform) => platform !== 'android',
transformCode: opts.transformCode,
transformCacheKey: opts.transformCacheKey,
extraNodeModules: opts.extraNodeModules,
assetDependencies: ['react-native/Libraries/Image/AssetRegistry'],
- // for jest-haste-map
resetCache: options.resetCache,
moduleOptions: {
cacheTransformResults: true,
@@ -260,7 +259,7 @@ class Resolver {
return this._minifyCode(path, code, map);
}
- getDependecyGraph() {
+ getDependencyGraph() {
return this._depGraph;
}
}
diff --git a/packager/react-packager/src/Server/__tests__/Server-test.js b/packager/react-packager/src/Server/__tests__/Server-test.js
index d7dfc2aa2..44f0cddf2 100644
--- a/packager/react-packager/src/Server/__tests__/Server-test.js
+++ b/packager/react-packager/src/Server/__tests__/Server-test.js
@@ -21,8 +21,6 @@ jest.setMock('worker-farm', function() { return () => {}; })
.mock('../../node-haste')
.mock('../../Logger');
-let FileWatcher;
-
describe('processRequest', () => {
let SourceMapConsumer, Bundler, Server, AssetServer, Promise;
beforeEach(() => {
@@ -62,12 +60,9 @@ describe('processRequest', () => {
);
const invalidatorFunc = jest.fn();
- const watcherFunc = jest.fn();
let requestHandler;
- let triggerFileChange;
beforeEach(() => {
- FileWatcher = require('../../node-haste').FileWatcher;
Bundler.prototype.bundle = jest.fn(() =>
Promise.resolve({
getSource: () => 'this is the source',
@@ -75,19 +70,10 @@ describe('processRequest', () => {
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.getResolver =
jest.fn().mockReturnValue({
- getDependecyGraph: jest.fn().mockReturnValue({
+ getDependencyGraph: jest.fn().mockReturnValue({
getHasteMap: jest.fn().mockReturnValue({on: jest.fn()}),
load: jest.fn(() => Promise.resolve()),
}),
@@ -97,7 +83,7 @@ describe('processRequest', () => {
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(
requestHandler,
'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(
requestHandler,
'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(
requestHandler,
'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(
requestHandler,
'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(
requestHandler,
'mybundle.map?runModule=true'
@@ -144,7 +130,7 @@ describe('processRequest', () => {
);
});
- pit('works with .ios.js extension', () => {
+ it('works with .ios.js extension', () => {
return makeRequest(
requestHandler,
'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(
requestHandler,
'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(
requestHandler,
'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', () => {
- pit('invalides files in bundle when file is updated', () => {
+ it('invalides files in bundle when file is updated', () => {
return makeRequest(
requestHandler,
'mybundle.bundle?runModule=true'
).then(() => {
- const onFileChange = watcherFunc.mock.calls[0][1];
- onFileChange('all','path/file.js', options.projectRoots[0]);
+ server.onFileChange('all', options.projectRoots[0] + '/path/file.js');
expect(invalidatorFunc.mock.calls[0][0]).toEqual('root/path/file.js');
});
});
@@ -273,7 +248,7 @@ describe('processRequest', () => {
jest.runAllTicks();
- triggerFileChange('all','path/file.js', options.projectRoots[0]);
+ server.onFileChange('all', options.projectRoots[0] + 'path/file.js');
jest.runAllTimers();
jest.runAllTicks();
@@ -322,7 +297,7 @@ describe('processRequest', () => {
jest.runAllTicks();
- triggerFileChange('all','path/file.js', options.projectRoots[0]);
+ server.onFileChange('all', options.projectRoots[0] + 'path/file.js');
jest.runAllTimers();
jest.runAllTicks();
@@ -355,7 +330,7 @@ describe('processRequest', () => {
it('should hold on to request and inform on change', () => {
server.processRequest(req, res);
- triggerFileChange('all', 'path/file.js', options.projectRoots[0]);
+ server.onFileChange('all', options.projectRoots[0] + 'path/file.js');
jest.runAllTimers();
expect(res.end).toBeCalledWith(JSON.stringify({changed: true}));
});
@@ -364,7 +339,7 @@ describe('processRequest', () => {
server.processRequest(req, res);
req.emit('close');
jest.runAllTimers();
- triggerFileChange('all', 'path/file.js', options.projectRoots[0]);
+ server.onFileChange('all', options.projectRoots[0] + 'path/file.js');
jest.runAllTimers();
expect(res.end).not.toBeCalled();
});
@@ -428,7 +403,7 @@ describe('processRequest', () => {
});
describe('buildbundle(options)', () => {
- pit('Calls the bundler with the correct args', () => {
+ it('Calls the bundler with the correct args', () => {
return server.buildBundle({
entryFile: 'foo file'
}).then(() =>
@@ -451,7 +426,7 @@ describe('processRequest', () => {
});
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')
.then(() =>
expect(Bundler.prototype.bundle).toBeCalledWith({
@@ -474,7 +449,7 @@ describe('processRequest', () => {
});
describe('/symbolicate endpoint', () => {
- pit('should symbolicate given stack trace', () => {
+ it('should symbolicate given stack trace', () => {
const body = JSON.stringify({stack: [{
file: 'http://foo.bundle?platform=ios',
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: [{
file: 'http://localhost:8081/debuggerWorker.js',
lineNumber: 123,
@@ -532,7 +507,7 @@ describe('processRequest', () => {
});
describe('/symbolicate handles errors', () => {
- pit('should symbolicate given stack trace', () => {
+ it('should symbolicate given stack trace', () => {
const body = 'clearly-not-json';
console.error = jest.fn();
diff --git a/packager/react-packager/src/Server/index.js b/packager/react-packager/src/Server/index.js
index cf63c47e3..7fdcffa92 100644
--- a/packager/react-packager/src/Server/index.js
+++ b/packager/react-packager/src/Server/index.js
@@ -9,7 +9,6 @@
'use strict';
const AssetServer = require('../AssetServer');
-const FileWatcher = require('../node-haste').FileWatcher;
const getPlatformExtension = require('../node-haste').getPlatformExtension;
const Bundler = require('../Bundler');
const MultipartResponse = require('./MultipartResponse');
@@ -76,7 +75,7 @@ const validateOpts = declareOpts({
type: 'object',
required: false,
},
- nonPersistent: {
+ watch: {
type: 'boolean',
default: false,
},
@@ -200,57 +199,32 @@ const NODE_MODULES = `${path.sep}node_modules${path.sep}`;
class Server {
constructor(options) {
const opts = this._opts = validateOpts(options);
+ const processFileChange =
+ ({type, filePath, stat}) => this.onFileChange(type, filePath, stat);
this._projectRoots = opts.projectRoots;
this._bundles = Object.create(null);
this._changeWatchers = [];
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({
assetExts: opts.assetExts,
- fileWatcher: this._fileWatcher,
projectRoots: opts.projectRoots,
});
const bundlerOpts = Object.create(opts);
- bundlerOpts.fileWatcher = this._fileWatcher;
bundlerOpts.assetServer = this._assetServer;
- bundlerOpts.allowBundleUpdates = !options.nonPersistent;
+ bundlerOpts.allowBundleUpdates = options.watch;
+ bundlerOpts.watch = options.watch;
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
- const dependencyGraph = this._bundler.getResolver().getDependecyGraph();
-
+ const dependencyGraph = this._bundler.getResolver().getDependencyGraph();
dependencyGraph.load().then(() => {
+ dependencyGraph.getWatcher().on(
+ 'change',
+ ({eventsQueue}) => eventsQueue.forEach(processFileChange),
+ );
dependencyGraph.getHasteMap().on('change', () => {
debug('Clearing bundle cache due to haste map change');
this._clearBundles();
@@ -282,10 +256,7 @@ class Server {
}
end() {
- Promise.all([
- this._fileWatcher.end(),
- this._bundler.kill(),
- ]);
+ return this._bundler.end();
}
setHMRFileChangeListener(listener) {
@@ -299,7 +270,7 @@ class Server {
}
buildBundle(options) {
- return this._bundler.getResolver().getDependecyGraph().load().then(() => {
+ return this._bundler.getResolver().getDependencyGraph().load().then(() => {
if (!options.platform) {
options.platform = getPlatformExtension(options.entryFile);
}
@@ -370,9 +341,9 @@ class Server {
});
}
- _onFileChange(type, filepath, root) {
- const absPath = path.join(root, filepath);
- this._bundler.invalidateFile(absPath);
+ onFileChange(type, filePath, stat) {
+ this._assetServer.onFileChange(type, filePath, stat);
+ this._bundler.invalidateFile(filePath);
// If Hot Loading is enabled avoid rebuilding bundles and sending live
// updates. Instead, send the HMR updates right away and clear the bundles
@@ -380,26 +351,26 @@ class Server {
if (this._hmrFileChangeListener) {
// Clear cached bundles in case user reloads
this._clearBundles();
- this._hmrFileChangeListener(absPath, this._bundler.stat(absPath));
+ this._hmrFileChangeListener(filePath, this._bundler.stat(filePath));
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
debug('Clearing bundles due to potential node_modules resolution change');
this._clearBundles();
}
Promise.all(
- this._fileChangeListeners.map(listener => listener(absPath))
+ this._fileChangeListeners.map(listener => listener(filePath))
).then(
- () => this._onFileChangeComplete(absPath),
- () => this._onFileChangeComplete(absPath)
+ () => this._onFileChangeComplete(filePath),
+ () => this._onFileChangeComplete(filePath)
);
}
- _onFileChangeComplete(absPath) {
+ _onFileChangeComplete(filePath) {
// Make sure the file watcher event runs through the system before
// we rebuild the bundles.
- this._debouncedFileChangeHandler(absPath);
+ this._debouncedFileChangeHandler(filePath);
}
_clearBundles() {
diff --git a/packager/react-packager/src/node-haste/DependencyGraph/DeprecatedAssetMap.js b/packager/react-packager/src/node-haste/DependencyGraph/DeprecatedAssetMap.js
index fc24c994f..e72829a0c 100644
--- a/packager/react-packager/src/node-haste/DependencyGraph/DeprecatedAssetMap.js
+++ b/packager/react-packager/src/node-haste/DependencyGraph/DeprecatedAssetMap.js
@@ -54,14 +54,14 @@ class DeprecatedAssetMap {
}
}
- processFileChange(type, filePath, root, fstat) {
+ processFileChange(type, filePath, fstat) {
const name = assetName(filePath);
if (type === 'change' || type === 'delete') {
delete this._map[name];
}
if (type === 'change' || type === 'add') {
- this._processAsset(path.join(root, filePath));
+ this._processAsset(filePath);
}
}
}
diff --git a/packager/react-packager/src/node-haste/FileWatcher/__mocks__/sane.js b/packager/react-packager/src/node-haste/FileWatcher/__mocks__/sane.js
deleted file mode 100644
index f59fa9104..000000000
--- a/packager/react-packager/src/node-haste/FileWatcher/__mocks__/sane.js
+++ /dev/null
@@ -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'),
-};
diff --git a/packager/react-packager/src/node-haste/FileWatcher/__tests__/FileWatcher-test.js b/packager/react-packager/src/node-haste/FileWatcher/__tests__/FileWatcher-test.js
deleted file mode 100644
index 03f2ad036..000000000
--- a/packager/react-packager/src/node-haste/FileWatcher/__tests__/FileWatcher-test.js
+++ /dev/null
@@ -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();
- });
- });
-});
diff --git a/packager/react-packager/src/node-haste/FileWatcher/index.js b/packager/react-packager/src/node-haste/FileWatcher/index.js
deleted file mode 100644
index 782aed442..000000000
--- a/packager/react-packager/src/node-haste/FileWatcher/index.js
+++ /dev/null
@@ -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;
diff --git a/packager/react-packager/src/node-haste/ModuleCache.js b/packager/react-packager/src/node-haste/ModuleCache.js
index 655b01d15..a963c24af 100644
--- a/packager/react-packager/src/node-haste/ModuleCache.js
+++ b/packager/react-packager/src/node-haste/ModuleCache.js
@@ -16,8 +16,6 @@ const Module = require('./Module');
const Package = require('./Package');
const Polyfill = require('./Polyfill');
-const path = require('path');
-
import type Cache from './Cache';
import type {
DepGraphHelpers,
@@ -69,8 +67,6 @@ class ModuleCache {
this._assetDependencies = assetDependencies;
this._moduleOptions = moduleOptions;
this._packageModuleMap = new WeakMap();
-
- fastfs.on('change', this._processFileChange.bind(this));
}
getModule(filePath: string) {
@@ -149,16 +145,14 @@ class ModuleCache {
});
}
- _processFileChange(type, filePath, root) {
- const absPath = path.join(root, filePath);
-
- if (this._moduleCache[absPath]) {
- this._moduleCache[absPath].invalidate();
- delete this._moduleCache[absPath];
+ processFileChange(type: string, filePath: string) {
+ if (this._moduleCache[filePath]) {
+ this._moduleCache[filePath].invalidate();
+ delete this._moduleCache[filePath];
}
- if (this._packageCache[absPath]) {
- this._packageCache[absPath].invalidate();
- delete this._packageCache[absPath];
+ if (this._packageCache[filePath]) {
+ this._packageCache[filePath].invalidate();
+ delete this._packageCache[filePath];
}
}
}
diff --git a/packager/react-packager/src/node-haste/__tests__/DependencyGraph-test.js b/packager/react-packager/src/node-haste/__tests__/DependencyGraph-test.js
index 8e58f3875..be472c9a8 100644
--- a/packager/react-packager/src/node-haste/__tests__/DependencyGraph-test.js
+++ b/packager/react-packager/src/node-haste/__tests__/DependencyGraph-test.js
@@ -61,12 +61,6 @@ describe('DependencyGraph', function() {
jest.resetModules();
Module = require('../Module');
- const fileWatcher = {
- on: function() {
- return this;
- },
- isWatchman: () => Promise.resolve(false),
- };
const Cache = jest.genMockFn().mockImplementation(function() {
this._maps = Object.create(null);
@@ -109,7 +103,6 @@ describe('DependencyGraph', function() {
defaults = {
assetExts: ['png', 'jpg'],
cache: new Cache(),
- fileWatcher,
forceNodeFilesystemAPI: true,
providesModuleNodeModules: [
'haste-fbjs',
@@ -4825,7 +4818,6 @@ describe('DependencyGraph', function() {
});
describe('file watch updating', function() {
- var triggerFileChange;
var mockStat = {
isDirectory: () => false,
};
@@ -4836,21 +4828,6 @@ describe('DependencyGraph', function() {
beforeEach(function() {
process.platform = 'linux';
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() {
@@ -4891,7 +4868,7 @@ describe('DependencyGraph', function() {
return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function() {
filesystem.root['index.js'] =
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) {
expect(deps)
.toEqual([
@@ -4954,7 +4931,7 @@ describe('DependencyGraph', function() {
return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function() {
filesystem.root['index.js'] =
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) {
expect(deps)
.toEqual([
@@ -5016,7 +4993,7 @@ describe('DependencyGraph', function() {
});
return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function() {
delete filesystem.root.foo;
- triggerFileChange('delete', 'foo.js', root);
+ dgraph.processFileChange('delete', root + '/foo.js');
return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) {
expect(deps)
.toEqual([
@@ -5083,10 +5060,10 @@ describe('DependencyGraph', function() {
' */',
'require("foo")',
].join('\n');
- triggerFileChange('add', 'bar.js', root, mockStat);
+ dgraph.processFileChange('add', root + '/bar.js', mockStat);
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) {
expect(deps)
@@ -5175,7 +5152,7 @@ describe('DependencyGraph', function() {
]);
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) {
expect(deps2)
@@ -5245,7 +5222,7 @@ describe('DependencyGraph', function() {
]);
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) {
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() {
var root = '/root';
var filesystem = setMockFileSystem({
@@ -5473,7 +5285,7 @@ describe('DependencyGraph', function() {
main: 'main.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) {
expect(deps)
@@ -5535,7 +5347,7 @@ describe('DependencyGraph', function() {
name: 'bPackage',
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) {
expect(deps)
@@ -5630,7 +5442,7 @@ describe('DependencyGraph', function() {
]);
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) {
expect(deps2)
@@ -5695,7 +5507,7 @@ describe('DependencyGraph', function() {
main: 'main.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) {
expect(deps2)
@@ -5752,7 +5564,7 @@ describe('DependencyGraph', 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');
});
});
diff --git a/packager/react-packager/src/node-haste/__tests__/Module-test.js b/packager/react-packager/src/node-haste/__tests__/Module-test.js
index f7aad9a09..53afd8a56 100644
--- a/packager/react-packager/src/node-haste/__tests__/Module-test.js
+++ b/packager/react-packager/src/node-haste/__tests__/Module-test.js
@@ -47,10 +47,6 @@ function mockIndexFile(indexJs) {
}
describe('Module', () => {
- const fileWatcher = {
- on: () => this,
- isWatchman: () => Promise.resolve(false),
- };
const fileName = '/root/index.js';
let cache, fastfs;
@@ -85,7 +81,6 @@ describe('Module', () => {
new Fastfs(
'test',
['/root'],
- fileWatcher,
['/root/index.js', '/root/package.json'],
{ignore: []},
);
@@ -119,22 +114,22 @@ describe('Module', () => {
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))
);
- pit('identifies the module as haste module', () =>
+ it('identifies the module as haste module', () =>
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 =
jest.genMockFn().mockReturnValue(Promise.resolve());
return createModule({transformCode}).getName()
.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 =
jest.genMockFn().mockReturnValue(Promise.resolve());
return createModule({transformCode}).isHaste()
@@ -147,22 +142,22 @@ describe('Module', () => {
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))
);
- pit('identifies the module as haste module', () =>
+ it('identifies the module as haste module', () =>
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 =
jest.genMockFn().mockReturnValue(Promise.resolve());
return createModule({transformCode}).getName()
.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 =
jest.genMockFn().mockReturnValue(Promise.resolve());
return createModule({transformCode}).isHaste()
@@ -175,22 +170,22 @@ describe('Module', () => {
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))
);
- 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))
);
- 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 =
jest.genMockFn().mockReturnValue(Promise.resolve());
return createModule({transformCode}).getName()
.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 =
jest.genMockFn().mockReturnValue(Promise.resolve());
return createModule({transformCode}).isHaste()
@@ -205,12 +200,12 @@ describe('Module', () => {
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}) =>
expect(code).toBe(fileContents))
);
- pit('exposes file contents via the `getCode()` method', () =>
+ it('exposes file contents via the `getCode()` method', () =>
createModule().getCode().then(code =>
expect(code).toBe(fileContents))
);
@@ -241,7 +236,7 @@ describe('Module', () => {
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});
return module.read()
.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 transformOptions = {arbitrary: Object()};
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 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();
const module = createJSONModule({transformCode});
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 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();
const module = createJSONModule({transformCode});
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'];
transformResult = {
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 = {
code: exampleCode,
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 = {
code: exampleCode,
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 = {
code: exampleCode,
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};
const module = createModule({transformCode});
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});
return module.read()
.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};
transformResult = {map, code: exampleCode};
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});
return module.read()
.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});
return module.read({foo: 1})
.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});
return module.read()
.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});
return module.read()
.then(() => {
diff --git a/packager/react-packager/src/node-haste/fastfs.js b/packager/react-packager/src/node-haste/fastfs.js
index e8e8220b5..58f381e2a 100644
--- a/packager/react-packager/src/node-haste/fastfs.js
+++ b/packager/react-packager/src/node-haste/fastfs.js
@@ -18,10 +18,6 @@ const {EventEmitter} = require('events');
const NOT_FOUND_IN_ROOTS = 'NotFoundInRootsError';
-interface FileWatcher {
- on(event: 'all', handler: (type: string, filePath: string, rootPath: string, fstat: fs.Stats) => void): void,
-}
-
const {
createActionStartEntry,
createActionEndEntry,
@@ -32,7 +28,6 @@ const {
class Fastfs extends EventEmitter {
_name: string;
- _fileWatcher: FileWatcher;
_ignore: (filePath: string) => boolean;
_roots: Array;
_fastPaths: {[filePath: string]: File};
@@ -40,7 +35,6 @@ class Fastfs extends EventEmitter {
constructor(
name: string,
roots: Array,
- fileWatcher: FileWatcher,
files: Array,
{ignore}: {
ignore: (filePath: string) => boolean,
@@ -48,7 +42,6 @@ class Fastfs extends EventEmitter {
) {
super();
this._name = name;
- this._fileWatcher = fileWatcher;
this._ignore = ignore;
this._roots = roots.map(root => {
// If the path ends in a separator ("/"), remove it to make string
@@ -82,10 +75,6 @@ class Fastfs extends EventEmitter {
});
print(log(createActionEndEntry(buildingInMemoryFSLogEntry)));
-
- if (this._fileWatcher) {
- this._fileWatcher.on('all', this._processFileChange.bind(this));
- }
}
stat(filePath: string) {
@@ -205,33 +194,23 @@ class Fastfs extends EventEmitter {
return this._fastPaths[filePath];
}
- _processFileChange(type, filePath, rootPath, fstat) {
- 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;
- }
-
+ processFileChange(type: string, filePath: string) {
if (type === 'delete' || type === 'change') {
- const file = this._getFile(absPath);
+ const file = this._getFile(filePath);
if (file) {
file.remove();
}
}
- delete this._fastPaths[path.resolve(absPath)];
+ delete this._fastPaths[path.resolve(filePath)];
if (type !== 'delete') {
- const file = new File(absPath, false);
- root.addChild(file, this._fastPaths);
+ const file = new File(filePath, false);
+ const root = this._getRoot(filePath);
+ if (root) {
+ root.addChild(file, this._fastPaths);
+ }
}
-
- this.emit('change', type, filePath, rootPath, fstat);
}
}
diff --git a/packager/react-packager/src/node-haste/index.js b/packager/react-packager/src/node-haste/index.js
index 9946e90aa..1397723bd 100644
--- a/packager/react-packager/src/node-haste/index.js
+++ b/packager/react-packager/src/node-haste/index.js
@@ -15,7 +15,6 @@ const Cache = require('./Cache');
const DependencyGraphHelpers = require('./DependencyGraph/DependencyGraphHelpers');
const DeprecatedAssetMap = require('./DependencyGraph/DeprecatedAssetMap');
const Fastfs = require('./fastfs');
-const FileWatcher = require('./FileWatcher');
const HasteMap = require('./DependencyGraph/HasteMap');
const JestHasteMap = require('jest-haste-map');
const Module = require('./Module');
@@ -47,12 +46,16 @@ const {
print,
} = require('../Logger');
+const escapePath = (p: string) => {
+ return (path.sep === '\\') ? p.replace(/(\/|\\(?!\.))/g, '\\\\') : p;
+};
+
class DependencyGraph {
_opts: {
roots: Array,
ignoreFilePath: (filePath: string) => boolean,
- fileWatcher: FileWatcher,
+ watch: boolean,
forceNodeFilesystemAPI: boolean,
assetRoots_DEPRECATED: Array,
assetExts: Array,
@@ -71,21 +74,23 @@ class DependencyGraph {
maxWorkers: number,
resetCache: boolean,
};
- _cache: Cache;
_assetDependencies: mixed;
- _helpers: DependencyGraphHelpers;
- _fastfs: Fastfs;
- _moduleCache: ModuleCache;
- _hasteMap: HasteMap;
+ _assetPattern: RegExp;
+ _cache: Cache;
_deprecatedAssetMap: DeprecatedAssetMap;
+ _fastfs: Fastfs;
+ _haste: JestHasteMap;
+ _hasteMap: HasteMap;
_hasteMapError: ?Error;
+ _helpers: DependencyGraphHelpers;
+ _moduleCache: ModuleCache;
- _loading: ?Promise;
+ _loading: Promise;
constructor({
roots,
ignoreFilePath,
- fileWatcher,
+ watch,
forceNodeFilesystemAPI,
assetRoots_DEPRECATED,
assetExts,
@@ -109,7 +114,7 @@ class DependencyGraph {
}: {
roots: Array,
ignoreFilePath: (filePath: string) => boolean,
- fileWatcher: FileWatcher,
+ watch: boolean,
forceNodeFilesystemAPI?: boolean,
assetRoots_DEPRECATED: Array,
assetExts: Array,
@@ -133,7 +138,7 @@ class DependencyGraph {
this._opts = {
roots,
ignoreFilePath: ignoreFilePath || (() => {}),
- fileWatcher,
+ watch: !!watch,
forceNodeFilesystemAPI: !!forceNodeFilesystemAPI,
assetRoots_DEPRECATED: assetRoots_DEPRECATED || [],
assetExts: assetExts || [],
@@ -155,6 +160,9 @@ class DependencyGraph {
maxWorkers,
resetCache,
};
+ this._assetPattern =
+ new RegExp('^' + this._opts.assetRoots_DEPRECATED.map(escapePath).join('|'));
+
this._cache = cache;
this._assetDependencies = assetDependencies;
this._helpers = new DependencyGraphHelpers(this._opts);
@@ -167,7 +175,7 @@ class DependencyGraph {
}
const mw = this._opts.maxWorkers;
- const haste = new JestHasteMap({
+ this._haste = new JestHasteMap({
extensions: this._opts.extensions.concat(this._opts.assetExts),
forceNodeFilesystemAPI: this._opts.forceNodeFilesystemAPI,
ignorePattern: {test: this._opts.ignoreFilePath},
@@ -180,9 +188,10 @@ class DependencyGraph {
retainAllFiles: true,
roots: this._opts.roots.concat(this._opts.assetRoots_DEPRECATED),
useWatchman: this._opts.useWatchman,
+ watch: this._opts.watch,
});
- this._loading = haste.build().then(hasteMap => {
+ this._loading = this._haste.build().then(hasteMap => {
const initializingPackagerLogEntry =
print(log(createActionStartEntry('Initializing Packager')));
@@ -191,15 +200,12 @@ class DependencyGraph {
this._fastfs = new Fastfs(
'JavaScript',
this._opts.roots,
- this._opts.fileWatcher,
hasteFSFiles,
{
ignore: this._opts.ignoreFilePath,
}
);
- this._fastfs.on('change', this._processFileChange.bind(this));
-
this._moduleCache = new ModuleCache({
fastfs: this._fastfs,
cache: this._cache,
@@ -219,14 +225,7 @@ class DependencyGraph {
platforms: this._opts.platforms,
});
- const escapePath = (p: string) => {
- return (path.sep === '\\') ? p.replace(/(\/|\\(?!\.))/g, '\\\\') : p;
- };
-
- const assetPattern =
- new RegExp('^' + this._opts.assetRoots_DEPRECATED.map(escapePath).join('|'));
-
- const assetFiles = hasteMap.hasteFS.matchFiles(assetPattern);
+ const assetFiles = hasteMap.hasteFS.matchFiles(this._assetPattern);
this._deprecatedAssetMap = new DeprecatedAssetMap({
helpers: this._helpers,
@@ -235,11 +234,11 @@ class DependencyGraph {
files: assetFiles,
});
- this._fastfs.on('change', (type, filePath, root, fstat) => {
- if (assetPattern.test(path.join(root, filePath))) {
- this._deprecatedAssetMap.processFileChange(type, filePath, root, fstat);
- }
- });
+ this._haste.on('change', ({eventsQueue}) =>
+ eventsQueue.forEach(({type, filePath, stat}) =>
+ this.processFileChange(type, filePath, stat)
+ )
+ );
const buildingHasteMapLogEntry =
print(log(createActionStartEntry('Building Haste Map')));
@@ -279,6 +278,10 @@ class DependencyGraph {
return this._fastfs;
}
+ getWatcher() {
+ return this._haste;
+ }
+
/**
* Returns the module object for the given path.
*/
@@ -365,21 +368,16 @@ class DependencyGraph {
);
}
- _processFileChange(type, filePath, root, fstat) {
- const absPath = path.join(root, filePath);
- if (fstat && fstat.isDirectory() ||
- this._opts.ignoreFilePath(absPath) ||
- this._helpers.isNodeModulesDir(absPath)) {
- return;
+ processFileChange(type: string, filePath: string, stat: Object) {
+ this._fastfs.processFileChange(type, filePath, stat);
+ this._moduleCache.processFileChange(type, filePath, stat);
+ if (this._assetPattern.test(filePath)) {
+ this._deprecatedAssetMap.processFileChange(type, filePath, stat);
}
- // Ok, this is some tricky promise code. Our requirements are:
- // * we need to report back failures
- // * failures shouldn't block recovery
- // * 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.
+ // This code reports failures but doesn't block recovery in the dev server
+ // mode. When the hasteMap is left in an incorrect state, we'll rebuild when
+ // the next file changes.
const resolve = () => {
if (this._hasteMapError) {
console.warn(
@@ -391,13 +389,14 @@ class DependencyGraph {
// Rebuild the entire map if last change resulted in an error.
this._loading = this._hasteMap.build();
} else {
- this._loading = this._hasteMap.processFileChange(type, absPath);
- this._loading.catch((e) => {this._hasteMapError = e;});
+ this._loading = this._hasteMap.processFileChange(type, filePath);
+ this._loading.catch(error => {
+ this._hasteMapError = error;
+ });
}
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);
}
@@ -411,7 +410,6 @@ class DependencyGraph {
static Cache;
static Fastfs;
- static FileWatcher;
static Module;
static Polyfill;
static getAssetDataFromName;
@@ -424,7 +422,6 @@ class DependencyGraph {
Object.assign(DependencyGraph, {
Cache,
Fastfs,
- FileWatcher,
Module,
Polyfill,
getAssetDataFromName,