mirror of https://github.com/status-im/metro.git
Revert D4161662: [RNP] New file watching implementation
Differential Revision: D4161662 fbshipit-source-id: 00604387b4f4b808f95275458f1c653981f91b86
This commit is contained in:
parent
f50b4d7876
commit
855a74d2b1
27
package.json
27
package.json
|
@ -5,5 +5,32 @@
|
||||||
"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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -45,14 +45,26 @@ 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(options);
|
return new Server(omit(options, ['verbose']));
|
||||||
}
|
}
|
||||||
|
|
||||||
function createNonPersistentServer(options) {
|
function createNonPersistentServer(options) {
|
||||||
Logger.disablePrinting();
|
Logger.disablePrinting();
|
||||||
options.watch = options.nonPersistent != null;
|
// Don't start the filewatcher or the cache.
|
||||||
|
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;
|
||||||
|
}, {});
|
||||||
|
}
|
||||||
|
|
|
@ -15,15 +15,17 @@ 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 =
|
NodeHaste.getAssetDataFromName = require.requireActual('../../node-haste/lib/getAssetDataFromName');
|
||||||
require.requireActual('../../node-haste/lib/getAssetDataFromName');
|
fileWatcher = new EventEmitter();
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('assetServer.get', () => {
|
describe('assetServer.get', () => {
|
||||||
|
@ -31,6 +33,7 @@ describe('AssetServer', () => {
|
||||||
const server = new AssetServer({
|
const server = new AssetServer({
|
||||||
projectRoots: ['/root'],
|
projectRoots: ['/root'],
|
||||||
assetExts: ['png'],
|
assetExts: ['png'],
|
||||||
|
fileWatcher,
|
||||||
});
|
});
|
||||||
|
|
||||||
fs.__setMockFilesystem({
|
fs.__setMockFilesystem({
|
||||||
|
@ -56,6 +59,7 @@ describe('AssetServer', () => {
|
||||||
const server = new AssetServer({
|
const server = new AssetServer({
|
||||||
projectRoots: ['/root'],
|
projectRoots: ['/root'],
|
||||||
assetExts: ['png'],
|
assetExts: ['png'],
|
||||||
|
fileWatcher,
|
||||||
});
|
});
|
||||||
|
|
||||||
fs.__setMockFilesystem({
|
fs.__setMockFilesystem({
|
||||||
|
@ -93,6 +97,7 @@ 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({
|
||||||
|
@ -119,6 +124,7 @@ describe('AssetServer', () => {
|
||||||
const server = new AssetServer({
|
const server = new AssetServer({
|
||||||
projectRoots: ['/root'],
|
projectRoots: ['/root'],
|
||||||
assetExts: ['png'],
|
assetExts: ['png'],
|
||||||
|
fileWatcher,
|
||||||
});
|
});
|
||||||
|
|
||||||
fs.__setMockFilesystem({
|
fs.__setMockFilesystem({
|
||||||
|
@ -141,6 +147,7 @@ describe('AssetServer', () => {
|
||||||
const server = new AssetServer({
|
const server = new AssetServer({
|
||||||
projectRoots: ['/root'],
|
projectRoots: ['/root'],
|
||||||
assetExts: ['png'],
|
assetExts: ['png'],
|
||||||
|
fileWatcher,
|
||||||
});
|
});
|
||||||
|
|
||||||
fs.__setMockFilesystem({
|
fs.__setMockFilesystem({
|
||||||
|
@ -172,6 +179,7 @@ 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({
|
||||||
|
@ -200,6 +208,7 @@ describe('AssetServer', () => {
|
||||||
const server = new AssetServer({
|
const server = new AssetServer({
|
||||||
projectRoots: ['/root'],
|
projectRoots: ['/root'],
|
||||||
assetExts: ['png'],
|
assetExts: ['png'],
|
||||||
|
fileWatcher,
|
||||||
});
|
});
|
||||||
|
|
||||||
fs.__setMockFilesystem({
|
fs.__setMockFilesystem({
|
||||||
|
@ -232,6 +241,7 @@ 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({
|
||||||
|
@ -261,14 +271,15 @@ describe('AssetServer', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('hash:', () => {
|
describe('hash:', () => {
|
||||||
let server, mockFS;
|
let server, fileSystem;
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
server = new AssetServer({
|
server = new AssetServer({
|
||||||
projectRoots: ['/root'],
|
projectRoots: ['/root'],
|
||||||
assetExts: ['jpg'],
|
assetExts: ['jpg'],
|
||||||
|
fileWatcher,
|
||||||
});
|
});
|
||||||
|
|
||||||
mockFS = {
|
fileSystem = {
|
||||||
'root': {
|
'root': {
|
||||||
imgs: {
|
imgs: {
|
||||||
'b@1x.jpg': 'b1 image',
|
'b@1x.jpg': 'b1 image',
|
||||||
|
@ -279,13 +290,13 @@ describe('AssetServer', () => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
fs.__setMockFilesystem(mockFS);
|
fs.__setMockFilesystem(fileSystem);
|
||||||
});
|
});
|
||||||
|
|
||||||
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 mockFS.root.imgs) {
|
for (const name in fileSystem.root.imgs) {
|
||||||
hash.update(mockFS.root.imgs[name]);
|
hash.update(fileSystem.root.imgs[name]);
|
||||||
}
|
}
|
||||||
|
|
||||||
return server.getAssetData('imgs/b.jpg').then(data =>
|
return server.getAssetData('imgs/b.jpg').then(data =>
|
||||||
|
@ -295,8 +306,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 => {
|
||||||
mockFS.root.imgs['b@4x.jpg'] = 'updated data';
|
fileSystem.root.imgs['b@4x.jpg'] = 'updated data';
|
||||||
server.onFileChange('all', '/root/imgs/b@4x.jpg');
|
fileWatcher.emit('all', 'arbitrary', '/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)
|
||||||
);
|
);
|
||||||
|
|
|
@ -42,6 +42,10 @@ const validateOpts = declareOpts({
|
||||||
type: 'array',
|
type: 'array',
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
|
fileWatcher: {
|
||||||
|
type: 'object',
|
||||||
|
required: true,
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
class AssetServer {
|
class AssetServer {
|
||||||
|
@ -51,6 +55,9 @@ 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) {
|
||||||
|
@ -77,6 +84,7 @@ 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;
|
||||||
|
@ -98,8 +106,9 @@ class AssetServer {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
onFileChange(type, filePath) {
|
_onFileChange(type, root, file) {
|
||||||
this._hashes.delete(this._files.get(filePath));
|
const asset = this._files.get(path.join(root, file));
|
||||||
|
this._hashes.delete(asset);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -178,10 +187,7 @@ class AssetServer {
|
||||||
}
|
}
|
||||||
|
|
||||||
const rootsString = roots.map(s => `'${s}'`).join(', ');
|
const rootsString = roots.map(s => `'${s}'`).join(', ');
|
||||||
throw new Error(
|
throw new Error(`'${debugInfoFile}' could not be found, because '${dir}' is not a subdirectory of any of the roots (${rootsString})`);
|
||||||
`'${debugInfoFile}' could not be found, because '${dir}' is not a ` +
|
|
||||||
`subdirectory of any of the roots (${rootsString})`,
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -79,6 +79,10 @@ 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,
|
||||||
|
@ -87,9 +91,9 @@ const validateOpts = declareOpts({
|
||||||
type: 'array',
|
type: 'array',
|
||||||
default: ['png'],
|
default: ['png'],
|
||||||
},
|
},
|
||||||
watch: {
|
fileWatcher: {
|
||||||
type: 'boolean',
|
type: 'object',
|
||||||
default: false,
|
required: true,
|
||||||
},
|
},
|
||||||
assetServer: {
|
assetServer: {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
|
@ -120,9 +124,10 @@ 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>,
|
||||||
watch: boolean,
|
fileWatcher: {},
|
||||||
assetServer: AssetServer,
|
assetServer: AssetServer,
|
||||||
transformTimeoutInterval: ?number,
|
transformTimeoutInterval: ?number,
|
||||||
allowBundleUpdates: boolean,
|
allowBundleUpdates: boolean,
|
||||||
|
@ -186,7 +191,7 @@ class Bundler {
|
||||||
blacklistRE: opts.blacklistRE,
|
blacklistRE: opts.blacklistRE,
|
||||||
cache: this._cache,
|
cache: this._cache,
|
||||||
extraNodeModules: opts.extraNodeModules,
|
extraNodeModules: opts.extraNodeModules,
|
||||||
watch: opts.watch,
|
fileWatcher: opts.fileWatcher,
|
||||||
minifyCode: this._transformer.minify,
|
minifyCode: this._transformer.minify,
|
||||||
moduleFormat: opts.moduleFormat,
|
moduleFormat: opts.moduleFormat,
|
||||||
polyfillModuleNames: opts.polyfillModuleNames,
|
polyfillModuleNames: opts.polyfillModuleNames,
|
||||||
|
@ -212,12 +217,9 @@ class Bundler {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
end() {
|
kill() {
|
||||||
this._transformer.kill();
|
this._transformer.kill();
|
||||||
return Promise.all([
|
return this._cache.end();
|
||||||
this._cache.end(),
|
|
||||||
this.getResolver().getDependencyGraph().getWatcher().end(),
|
|
||||||
]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bundle(options: {
|
bundle(options: {
|
||||||
|
|
|
@ -35,9 +35,9 @@ const validateOpts = declareOpts({
|
||||||
type: 'array',
|
type: 'array',
|
||||||
default: [],
|
default: [],
|
||||||
},
|
},
|
||||||
watch: {
|
fileWatcher: {
|
||||||
type: 'boolean',
|
type: 'object',
|
||||||
default: false,
|
required: true,
|
||||||
},
|
},
|
||||||
assetExts: {
|
assetExts: {
|
||||||
type: 'array',
|
type: 'array',
|
||||||
|
@ -101,13 +101,14 @@ class Resolver {
|
||||||
providesModuleNodeModules: defaults.providesModuleNodeModules,
|
providesModuleNodeModules: defaults.providesModuleNodeModules,
|
||||||
platforms: defaults.platforms,
|
platforms: defaults.platforms,
|
||||||
preferNativePlatform: true,
|
preferNativePlatform: true,
|
||||||
watch: opts.watch,
|
fileWatcher: opts.fileWatcher,
|
||||||
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,
|
||||||
|
@ -259,7 +260,7 @@ class Resolver {
|
||||||
return this._minifyCode(path, code, map);
|
return this._minifyCode(path, code, map);
|
||||||
}
|
}
|
||||||
|
|
||||||
getDependencyGraph() {
|
getDependecyGraph() {
|
||||||
return this._depGraph;
|
return this._depGraph;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,6 +21,8 @@ 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(() => {
|
||||||
|
@ -60,9 +62,12 @@ 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',
|
||||||
|
@ -70,10 +75,19 @@ 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({
|
||||||
getDependencyGraph: jest.fn().mockReturnValue({
|
getDependecyGraph: 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()),
|
||||||
}),
|
}),
|
||||||
|
@ -83,7 +97,7 @@ describe('processRequest', () => {
|
||||||
requestHandler = server.processRequest.bind(server);
|
requestHandler = server.processRequest.bind(server);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('returns JS bundle source on request of *.bundle', () => {
|
pit('returns JS bundle source on request of *.bundle', () => {
|
||||||
return makeRequest(
|
return makeRequest(
|
||||||
requestHandler,
|
requestHandler,
|
||||||
'mybundle.bundle?runModule=true',
|
'mybundle.bundle?runModule=true',
|
||||||
|
@ -93,7 +107,7 @@ describe('processRequest', () => {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('returns JS bundle source on request of *.bundle (compat)', () => {
|
pit('returns JS bundle source on request of *.bundle (compat)', () => {
|
||||||
return makeRequest(
|
return makeRequest(
|
||||||
requestHandler,
|
requestHandler,
|
||||||
'mybundle.runModule.bundle'
|
'mybundle.runModule.bundle'
|
||||||
|
@ -102,7 +116,7 @@ describe('processRequest', () => {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('returns ETag header on request of *.bundle', () => {
|
pit('returns ETag header on request of *.bundle', () => {
|
||||||
return makeRequest(
|
return makeRequest(
|
||||||
requestHandler,
|
requestHandler,
|
||||||
'mybundle.bundle?runModule=true'
|
'mybundle.bundle?runModule=true'
|
||||||
|
@ -111,7 +125,7 @@ describe('processRequest', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('returns 304 on request of *.bundle when if-none-match equals the ETag', () => {
|
pit('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',
|
||||||
|
@ -121,7 +135,7 @@ describe('processRequest', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('returns sourcemap on request of *.map', () => {
|
pit('returns sourcemap on request of *.map', () => {
|
||||||
return makeRequest(
|
return makeRequest(
|
||||||
requestHandler,
|
requestHandler,
|
||||||
'mybundle.map?runModule=true'
|
'mybundle.map?runModule=true'
|
||||||
|
@ -130,7 +144,7 @@ describe('processRequest', () => {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('works with .ios.js extension', () => {
|
pit('works with .ios.js extension', () => {
|
||||||
return makeRequest(
|
return makeRequest(
|
||||||
requestHandler,
|
requestHandler,
|
||||||
'index.ios.includeRequire.bundle'
|
'index.ios.includeRequire.bundle'
|
||||||
|
@ -155,7 +169,7 @@ describe('processRequest', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('passes in the platform param', function() {
|
pit('passes in the platform param', function() {
|
||||||
return makeRequest(
|
return makeRequest(
|
||||||
requestHandler,
|
requestHandler,
|
||||||
'index.bundle?platform=ios'
|
'index.bundle?platform=ios'
|
||||||
|
@ -180,7 +194,7 @@ describe('processRequest', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('passes in the assetPlugin param', function() {
|
pit('passes in the assetPlugin param', function() {
|
||||||
return makeRequest(
|
return makeRequest(
|
||||||
requestHandler,
|
requestHandler,
|
||||||
'index.bundle?assetPlugin=assetPlugin1&assetPlugin=assetPlugin2'
|
'index.bundle?assetPlugin=assetPlugin1&assetPlugin=assetPlugin2'
|
||||||
|
@ -205,13 +219,24 @@ 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', () => {
|
||||||
it('invalides files in bundle when file is updated', () => {
|
pit('invalides files in bundle when file is updated', () => {
|
||||||
return makeRequest(
|
return makeRequest(
|
||||||
requestHandler,
|
requestHandler,
|
||||||
'mybundle.bundle?runModule=true'
|
'mybundle.bundle?runModule=true'
|
||||||
).then(() => {
|
).then(() => {
|
||||||
server.onFileChange('all', options.projectRoots[0] + '/path/file.js');
|
const onFileChange = watcherFunc.mock.calls[0][1];
|
||||||
|
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');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -248,7 +273,7 @@ describe('processRequest', () => {
|
||||||
|
|
||||||
jest.runAllTicks();
|
jest.runAllTicks();
|
||||||
|
|
||||||
server.onFileChange('all', options.projectRoots[0] + 'path/file.js');
|
triggerFileChange('all','path/file.js', options.projectRoots[0]);
|
||||||
jest.runAllTimers();
|
jest.runAllTimers();
|
||||||
jest.runAllTicks();
|
jest.runAllTicks();
|
||||||
|
|
||||||
|
@ -297,7 +322,7 @@ describe('processRequest', () => {
|
||||||
|
|
||||||
jest.runAllTicks();
|
jest.runAllTicks();
|
||||||
|
|
||||||
server.onFileChange('all', options.projectRoots[0] + 'path/file.js');
|
triggerFileChange('all','path/file.js', options.projectRoots[0]);
|
||||||
jest.runAllTimers();
|
jest.runAllTimers();
|
||||||
jest.runAllTicks();
|
jest.runAllTicks();
|
||||||
|
|
||||||
|
@ -330,7 +355,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);
|
||||||
server.onFileChange('all', options.projectRoots[0] + 'path/file.js');
|
triggerFileChange('all', 'path/file.js', options.projectRoots[0]);
|
||||||
jest.runAllTimers();
|
jest.runAllTimers();
|
||||||
expect(res.end).toBeCalledWith(JSON.stringify({changed: true}));
|
expect(res.end).toBeCalledWith(JSON.stringify({changed: true}));
|
||||||
});
|
});
|
||||||
|
@ -339,7 +364,7 @@ describe('processRequest', () => {
|
||||||
server.processRequest(req, res);
|
server.processRequest(req, res);
|
||||||
req.emit('close');
|
req.emit('close');
|
||||||
jest.runAllTimers();
|
jest.runAllTimers();
|
||||||
server.onFileChange('all', options.projectRoots[0] + 'path/file.js');
|
triggerFileChange('all', 'path/file.js', options.projectRoots[0]);
|
||||||
jest.runAllTimers();
|
jest.runAllTimers();
|
||||||
expect(res.end).not.toBeCalled();
|
expect(res.end).not.toBeCalled();
|
||||||
});
|
});
|
||||||
|
@ -403,7 +428,7 @@ describe('processRequest', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('buildbundle(options)', () => {
|
describe('buildbundle(options)', () => {
|
||||||
it('Calls the bundler with the correct args', () => {
|
pit('Calls the bundler with the correct args', () => {
|
||||||
return server.buildBundle({
|
return server.buildBundle({
|
||||||
entryFile: 'foo file'
|
entryFile: 'foo file'
|
||||||
}).then(() =>
|
}).then(() =>
|
||||||
|
@ -426,7 +451,7 @@ describe('processRequest', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('buildBundleFromUrl(options)', () => {
|
describe('buildBundleFromUrl(options)', () => {
|
||||||
it('Calls the bundler with the correct args', () => {
|
pit('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({
|
||||||
|
@ -449,7 +474,7 @@ describe('processRequest', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('/symbolicate endpoint', () => {
|
describe('/symbolicate endpoint', () => {
|
||||||
it('should symbolicate given stack trace', () => {
|
pit('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,
|
||||||
|
@ -483,7 +508,7 @@ describe('processRequest', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('ignores `/debuggerWorker.js` stack frames', () => {
|
pit('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,
|
||||||
|
@ -507,7 +532,7 @@ describe('processRequest', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('/symbolicate handles errors', () => {
|
describe('/symbolicate handles errors', () => {
|
||||||
it('should symbolicate given stack trace', () => {
|
pit('should symbolicate given stack trace', () => {
|
||||||
const body = 'clearly-not-json';
|
const body = 'clearly-not-json';
|
||||||
console.error = jest.fn();
|
console.error = jest.fn();
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
'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');
|
||||||
|
@ -75,7 +76,7 @@ const validateOpts = declareOpts({
|
||||||
type: 'object',
|
type: 'object',
|
||||||
required: false,
|
required: false,
|
||||||
},
|
},
|
||||||
watch: {
|
nonPersistent: {
|
||||||
type: 'boolean',
|
type: 'boolean',
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
|
@ -199,32 +200,57 @@ 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.watch;
|
bundlerOpts.allowBundleUpdates = !options.nonPersistent;
|
||||||
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().getDependencyGraph();
|
const dependencyGraph = this._bundler.getResolver().getDependecyGraph();
|
||||||
|
|
||||||
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();
|
||||||
|
@ -256,7 +282,10 @@ class Server {
|
||||||
}
|
}
|
||||||
|
|
||||||
end() {
|
end() {
|
||||||
return this._bundler.end();
|
Promise.all([
|
||||||
|
this._fileWatcher.end(),
|
||||||
|
this._bundler.kill(),
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
setHMRFileChangeListener(listener) {
|
setHMRFileChangeListener(listener) {
|
||||||
|
@ -270,7 +299,7 @@ class Server {
|
||||||
}
|
}
|
||||||
|
|
||||||
buildBundle(options) {
|
buildBundle(options) {
|
||||||
return this._bundler.getResolver().getDependencyGraph().load().then(() => {
|
return this._bundler.getResolver().getDependecyGraph().load().then(() => {
|
||||||
if (!options.platform) {
|
if (!options.platform) {
|
||||||
options.platform = getPlatformExtension(options.entryFile);
|
options.platform = getPlatformExtension(options.entryFile);
|
||||||
}
|
}
|
||||||
|
@ -341,9 +370,9 @@ class Server {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
onFileChange(type, filePath, stat) {
|
_onFileChange(type, filepath, root) {
|
||||||
this._assetServer.onFileChange(type, filePath, stat);
|
const absPath = path.join(root, filepath);
|
||||||
this._bundler.invalidateFile(filePath);
|
this._bundler.invalidateFile(absPath);
|
||||||
|
|
||||||
// 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
|
||||||
|
@ -351,26 +380,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(filePath, this._bundler.stat(filePath));
|
this._hmrFileChangeListener(absPath, this._bundler.stat(absPath));
|
||||||
return;
|
return;
|
||||||
} else if (type !== 'change' && filePath.indexOf(NODE_MODULES) !== -1) {
|
} else if (type !== 'change' && absPath.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(filePath))
|
this._fileChangeListeners.map(listener => listener(absPath))
|
||||||
).then(
|
).then(
|
||||||
() => this._onFileChangeComplete(filePath),
|
() => this._onFileChangeComplete(absPath),
|
||||||
() => this._onFileChangeComplete(filePath)
|
() => this._onFileChangeComplete(absPath)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
_onFileChangeComplete(filePath) {
|
_onFileChangeComplete(absPath) {
|
||||||
// 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(filePath);
|
this._debouncedFileChangeHandler(absPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
_clearBundles() {
|
_clearBundles() {
|
||||||
|
|
|
@ -54,14 +54,14 @@ class DeprecatedAssetMap {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
processFileChange(type, filePath, fstat) {
|
processFileChange(type, filePath, root, 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(filePath);
|
this._processAsset(path.join(root, filePath));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
/**
|
||||||
|
* 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'),
|
||||||
|
};
|
|
@ -0,0 +1,75 @@
|
||||||
|
/**
|
||||||
|
* 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();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,123 @@
|
||||||
|
/**
|
||||||
|
* 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;
|
|
@ -16,6 +16,8 @@ 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,
|
||||||
|
@ -67,6 +69,8 @@ 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) {
|
||||||
|
@ -145,14 +149,16 @@ class ModuleCache {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
processFileChange(type: string, filePath: string) {
|
_processFileChange(type, filePath, root) {
|
||||||
if (this._moduleCache[filePath]) {
|
const absPath = path.join(root, filePath);
|
||||||
this._moduleCache[filePath].invalidate();
|
|
||||||
delete this._moduleCache[filePath];
|
if (this._moduleCache[absPath]) {
|
||||||
|
this._moduleCache[absPath].invalidate();
|
||||||
|
delete this._moduleCache[absPath];
|
||||||
}
|
}
|
||||||
if (this._packageCache[filePath]) {
|
if (this._packageCache[absPath]) {
|
||||||
this._packageCache[filePath].invalidate();
|
this._packageCache[absPath].invalidate();
|
||||||
delete this._packageCache[filePath];
|
delete this._packageCache[absPath];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -61,6 +61,12 @@ 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);
|
||||||
|
@ -103,6 +109,7 @@ 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',
|
||||||
|
@ -4818,6 +4825,7 @@ describe('DependencyGraph', function() {
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('file watch updating', function() {
|
describe('file watch updating', function() {
|
||||||
|
var triggerFileChange;
|
||||||
var mockStat = {
|
var mockStat = {
|
||||||
isDirectory: () => false,
|
isDirectory: () => false,
|
||||||
};
|
};
|
||||||
|
@ -4828,6 +4836,21 @@ 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() {
|
||||||
|
@ -4868,7 +4891,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")', '');
|
||||||
dgraph.processFileChange('change', root + '/index.js', mockStat);
|
triggerFileChange('change', 'index.js', root, 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([
|
||||||
|
@ -4931,7 +4954,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")', '');
|
||||||
dgraph.processFileChange('change', root + '/index.js', mockStat);
|
triggerFileChange('change', 'index.js', root, 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([
|
||||||
|
@ -4993,7 +5016,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;
|
||||||
dgraph.processFileChange('delete', root + '/foo.js');
|
triggerFileChange('delete', 'foo.js', root);
|
||||||
return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) {
|
return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) {
|
||||||
expect(deps)
|
expect(deps)
|
||||||
.toEqual([
|
.toEqual([
|
||||||
|
@ -5060,10 +5083,10 @@ describe('DependencyGraph', function() {
|
||||||
' */',
|
' */',
|
||||||
'require("foo")',
|
'require("foo")',
|
||||||
].join('\n');
|
].join('\n');
|
||||||
dgraph.processFileChange('add', root + '/bar.js', mockStat);
|
triggerFileChange('add', 'bar.js', root, mockStat);
|
||||||
|
|
||||||
filesystem.root.aPackage['main.js'] = 'require("bar")';
|
filesystem.root.aPackage['main.js'] = 'require("bar")';
|
||||||
dgraph.processFileChange('change', root + '/aPackage/main.js', mockStat);
|
triggerFileChange('change', 'aPackage/main.js', root, mockStat);
|
||||||
|
|
||||||
return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) {
|
return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) {
|
||||||
expect(deps)
|
expect(deps)
|
||||||
|
@ -5152,7 +5175,7 @@ describe('DependencyGraph', function() {
|
||||||
]);
|
]);
|
||||||
|
|
||||||
filesystem.root['foo.png'] = '';
|
filesystem.root['foo.png'] = '';
|
||||||
dgraph.processFileChange('add', root + '/foo.png', mockStat);
|
triggerFileChange('add', 'foo.png', root, mockStat);
|
||||||
|
|
||||||
return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps2) {
|
return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps2) {
|
||||||
expect(deps2)
|
expect(deps2)
|
||||||
|
@ -5222,7 +5245,7 @@ describe('DependencyGraph', function() {
|
||||||
]);
|
]);
|
||||||
|
|
||||||
filesystem.root['foo.png'] = '';
|
filesystem.root['foo.png'] = '';
|
||||||
dgraph.processFileChange('add', root + '/foo.png', mockStat);
|
triggerFileChange('add', 'foo.png', root, mockStat);
|
||||||
|
|
||||||
return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps2) {
|
return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps2) {
|
||||||
expect(deps2)
|
expect(deps2)
|
||||||
|
@ -5254,6 +5277,171 @@ 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({
|
||||||
|
@ -5285,7 +5473,7 @@ describe('DependencyGraph', function() {
|
||||||
main: 'main.js',
|
main: 'main.js',
|
||||||
browser: 'browser.js',
|
browser: 'browser.js',
|
||||||
});
|
});
|
||||||
dgraph.processFileChange('change', root + '/aPackage/package.json', mockStat);
|
triggerFileChange('change', 'package.json', '/root/aPackage', mockStat);
|
||||||
|
|
||||||
return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) {
|
return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) {
|
||||||
expect(deps)
|
expect(deps)
|
||||||
|
@ -5347,7 +5535,7 @@ describe('DependencyGraph', function() {
|
||||||
name: 'bPackage',
|
name: 'bPackage',
|
||||||
main: 'main.js',
|
main: 'main.js',
|
||||||
});
|
});
|
||||||
dgraph.processFileChange('change', root + '/aPackage/package.json', mockStat);
|
triggerFileChange('change', 'package.json', '/root/aPackage', mockStat);
|
||||||
|
|
||||||
return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) {
|
return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) {
|
||||||
expect(deps)
|
expect(deps)
|
||||||
|
@ -5442,7 +5630,7 @@ describe('DependencyGraph', function() {
|
||||||
]);
|
]);
|
||||||
|
|
||||||
filesystem.root.node_modules.foo['main.js'] = 'lol';
|
filesystem.root.node_modules.foo['main.js'] = 'lol';
|
||||||
dgraph.processFileChange('change', root + '/node_modules/foo/main.js', mockStat);
|
triggerFileChange('change', 'main.js', '/root/node_modules/foo', mockStat);
|
||||||
|
|
||||||
return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps2) {
|
return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps2) {
|
||||||
expect(deps2)
|
expect(deps2)
|
||||||
|
@ -5507,7 +5695,7 @@ describe('DependencyGraph', function() {
|
||||||
main: 'main.js',
|
main: 'main.js',
|
||||||
browser: 'browser.js',
|
browser: 'browser.js',
|
||||||
});
|
});
|
||||||
dgraph.processFileChange('change', root + '/node_modules/foo/package.json', mockStat);
|
triggerFileChange('change', 'package.json', '/root/node_modules/foo', mockStat);
|
||||||
|
|
||||||
return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps2) {
|
return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps2) {
|
||||||
expect(deps2)
|
expect(deps2)
|
||||||
|
@ -5564,7 +5752,7 @@ describe('DependencyGraph', function() {
|
||||||
});
|
});
|
||||||
|
|
||||||
return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function() {
|
return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function() {
|
||||||
dgraph.processFileChange('add', root + '/index.js', mockStat);
|
triggerFileChange('add', 'index.js', root, mockStat);
|
||||||
return getOrderedDependenciesAsJSON(dgraph, '/root/index.js');
|
return getOrderedDependenciesAsJSON(dgraph, '/root/index.js');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -47,6 +47,10 @@ 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;
|
||||||
|
@ -81,6 +85,7 @@ 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: []},
|
||||||
);
|
);
|
||||||
|
@ -114,22 +119,22 @@ describe('Module', () => {
|
||||||
mockIndexFile(source);
|
mockIndexFile(source);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('extracts the module name from the header', () =>
|
pit('extracts the module name from the header', () =>
|
||||||
module.getName().then(name => expect(name).toEqual(moduleId))
|
module.getName().then(name => expect(name).toEqual(moduleId))
|
||||||
);
|
);
|
||||||
|
|
||||||
it('identifies the module as haste module', () =>
|
pit('identifies the module as haste module', () =>
|
||||||
module.isHaste().then(isHaste => expect(isHaste).toBe(true))
|
module.isHaste().then(isHaste => expect(isHaste).toBe(true))
|
||||||
);
|
);
|
||||||
|
|
||||||
it('does not transform the file in order to access the name', () => {
|
pit('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());
|
||||||
});
|
});
|
||||||
|
|
||||||
it('does not transform the file in order to access the haste status', () => {
|
pit('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()
|
||||||
|
@ -142,22 +147,22 @@ describe('Module', () => {
|
||||||
mockIndexFile(source.replace(/@providesModule/, '@provides'));
|
mockIndexFile(source.replace(/@providesModule/, '@provides'));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('extracts the module name from the header if it has a @provides annotation', () =>
|
pit('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))
|
||||||
);
|
);
|
||||||
|
|
||||||
it('identifies the module as haste module', () =>
|
pit('identifies the module as haste module', () =>
|
||||||
module.isHaste().then(isHaste => expect(isHaste).toBe(true))
|
module.isHaste().then(isHaste => expect(isHaste).toBe(true))
|
||||||
);
|
);
|
||||||
|
|
||||||
it('does not transform the file in order to access the name', () => {
|
pit('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());
|
||||||
});
|
});
|
||||||
|
|
||||||
it('does not transform the file in order to access the haste status', () => {
|
pit('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()
|
||||||
|
@ -170,22 +175,22 @@ describe('Module', () => {
|
||||||
mockIndexFile('arbitrary(code);');
|
mockIndexFile('arbitrary(code);');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('uses the file name as module name', () =>
|
pit('uses the file name as module name', () =>
|
||||||
module.getName().then(name => expect(name).toEqual(fileName))
|
module.getName().then(name => expect(name).toEqual(fileName))
|
||||||
);
|
);
|
||||||
|
|
||||||
it('does not identify the module as haste module', () =>
|
pit('does not identify the module as haste module', () =>
|
||||||
module.isHaste().then(isHaste => expect(isHaste).toBe(false))
|
module.isHaste().then(isHaste => expect(isHaste).toBe(false))
|
||||||
);
|
);
|
||||||
|
|
||||||
it('does not transform the file in order to access the name', () => {
|
pit('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());
|
||||||
});
|
});
|
||||||
|
|
||||||
it('does not transform the file in order to access the haste status', () => {
|
pit('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()
|
||||||
|
@ -200,12 +205,12 @@ describe('Module', () => {
|
||||||
mockIndexFile(fileContents);
|
mockIndexFile(fileContents);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('exposes file contents as `code` property on the data exposed by `read()`', () =>
|
pit('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))
|
||||||
);
|
);
|
||||||
|
|
||||||
it('exposes file contents via the `getCode()` method', () =>
|
pit('exposes file contents via the `getCode()` method', () =>
|
||||||
createModule().getCode().then(code =>
|
createModule().getCode().then(code =>
|
||||||
expect(code).toBe(fileContents))
|
expect(code).toBe(fileContents))
|
||||||
);
|
);
|
||||||
|
@ -236,7 +241,7 @@ describe('Module', () => {
|
||||||
mockIndexFile(fileContents);
|
mockIndexFile(fileContents);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('passes the module and file contents to the transform function when reading', () => {
|
pit('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(() => {
|
||||||
|
@ -244,7 +249,7 @@ describe('Module', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('passes any additional options to the transform function when reading', () => {
|
pit('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)
|
||||||
|
@ -253,7 +258,7 @@ describe('Module', () => {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('passes module and file contents if the file is annotated with @extern', () => {
|
pit('passes the module and file contents to the transform if the file is annotated with @extern', () => {
|
||||||
const module = createModule({transformCode});
|
const module = createModule({transformCode});
|
||||||
const customFileContents = `
|
const customFileContents = `
|
||||||
/**
|
/**
|
||||||
|
@ -266,7 +271,7 @@ describe('Module', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('passes the module and file contents to the transform for JSON files', () => {
|
pit('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(() => {
|
||||||
|
@ -274,7 +279,7 @@ describe('Module', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('does not extend the passed options object if the file is annotated with @extern', () => {
|
pit('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 = `
|
||||||
/**
|
/**
|
||||||
|
@ -290,7 +295,7 @@ describe('Module', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('does not extend the passed options object for JSON files', () => {
|
pit('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'};
|
||||||
|
@ -301,7 +306,7 @@ describe('Module', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('uses dependencies that `transformCode` resolves to, instead of extracting them', () => {
|
pit('uses dependencies that `transformCode` resolves to, instead of extracting them', () => {
|
||||||
const mockedDependencies = ['foo', 'bar'];
|
const mockedDependencies = ['foo', 'bar'];
|
||||||
transformResult = {
|
transformResult = {
|
||||||
code: exampleCode,
|
code: exampleCode,
|
||||||
|
@ -314,7 +319,7 @@ describe('Module', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('forwards all additional properties of the result provided by `transformCode`', () => {
|
pit('forwards all additional properties of the result provided by `transformCode`', () => {
|
||||||
transformResult = {
|
transformResult = {
|
||||||
code: exampleCode,
|
code: exampleCode,
|
||||||
arbitrary: 'arbitrary',
|
arbitrary: 'arbitrary',
|
||||||
|
@ -329,7 +334,7 @@ describe('Module', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('only stores dependencies if `cacheTransformResults` option is disabled', () => {
|
pit('does not store anything but dependencies if the `cacheTransformResults` option is disabled', () => {
|
||||||
transformResult = {
|
transformResult = {
|
||||||
code: exampleCode,
|
code: exampleCode,
|
||||||
arbitrary: 'arbitrary',
|
arbitrary: 'arbitrary',
|
||||||
|
@ -349,7 +354,7 @@ describe('Module', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('stores all things if options is undefined', () => {
|
pit('stores all things if options is undefined', () => {
|
||||||
transformResult = {
|
transformResult = {
|
||||||
code: exampleCode,
|
code: exampleCode,
|
||||||
arbitrary: 'arbitrary',
|
arbitrary: 'arbitrary',
|
||||||
|
@ -365,7 +370,7 @@ describe('Module', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('exposes the transformed code rather than the raw file contents', () => {
|
pit('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()])
|
||||||
|
@ -375,13 +380,13 @@ describe('Module', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('exposes the raw file contents as `source` property', () => {
|
pit('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));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('exposes a source map returned by the transform', () => {
|
pit('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});
|
||||||
|
@ -392,7 +397,7 @@ describe('Module', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('caches the transform result for the same transform options', () => {
|
pit('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(() => {
|
||||||
|
@ -407,7 +412,7 @@ describe('Module', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('triggers a new transform for different transform options', () => {
|
pit('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(() => {
|
||||||
|
@ -419,7 +424,7 @@ describe('Module', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('triggers a new transform for different source code', () => {
|
pit('triggers a new transform for different source code', () => {
|
||||||
let module = createModule({transformCode});
|
let module = createModule({transformCode});
|
||||||
return module.read()
|
return module.read()
|
||||||
.then(() => {
|
.then(() => {
|
||||||
|
@ -435,7 +440,7 @@ describe('Module', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('triggers a new transform for different transform cache key', () => {
|
pit('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(() => {
|
||||||
|
|
|
@ -18,6 +18,10 @@ 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,
|
||||||
|
@ -28,6 +32,7 @@ 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};
|
||||||
|
@ -35,6 +40,7 @@ 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,
|
||||||
|
@ -42,6 +48,7 @@ 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
|
||||||
|
@ -75,6 +82,10 @@ 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) {
|
||||||
|
@ -194,23 +205,33 @@ class Fastfs extends EventEmitter {
|
||||||
return this._fastPaths[filePath];
|
return this._fastPaths[filePath];
|
||||||
}
|
}
|
||||||
|
|
||||||
processFileChange(type: string, filePath: string) {
|
_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;
|
||||||
|
}
|
||||||
|
|
||||||
if (type === 'delete' || type === 'change') {
|
if (type === 'delete' || type === 'change') {
|
||||||
const file = this._getFile(filePath);
|
const file = this._getFile(absPath);
|
||||||
if (file) {
|
if (file) {
|
||||||
file.remove();
|
file.remove();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
delete this._fastPaths[path.resolve(filePath)];
|
delete this._fastPaths[path.resolve(absPath)];
|
||||||
|
|
||||||
if (type !== 'delete') {
|
if (type !== 'delete') {
|
||||||
const file = new File(filePath, false);
|
const file = new File(absPath, false);
|
||||||
const root = this._getRoot(filePath);
|
root.addChild(file, this._fastPaths);
|
||||||
if (root) {
|
|
||||||
root.addChild(file, this._fastPaths);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.emit('change', type, filePath, rootPath, fstat);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -15,6 +15,7 @@ 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');
|
||||||
|
@ -46,16 +47,12 @@ 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,
|
||||||
watch: boolean,
|
fileWatcher: FileWatcher,
|
||||||
forceNodeFilesystemAPI: boolean,
|
forceNodeFilesystemAPI: boolean,
|
||||||
assetRoots_DEPRECATED: Array<string>,
|
assetRoots_DEPRECATED: Array<string>,
|
||||||
assetExts: Array<string>,
|
assetExts: Array<string>,
|
||||||
|
@ -74,23 +71,21 @@ class DependencyGraph {
|
||||||
maxWorkers: number,
|
maxWorkers: number,
|
||||||
resetCache: boolean,
|
resetCache: boolean,
|
||||||
};
|
};
|
||||||
_assetDependencies: mixed;
|
|
||||||
_assetPattern: RegExp;
|
|
||||||
_cache: Cache;
|
_cache: Cache;
|
||||||
_deprecatedAssetMap: DeprecatedAssetMap;
|
_assetDependencies: mixed;
|
||||||
_fastfs: Fastfs;
|
|
||||||
_haste: JestHasteMap;
|
|
||||||
_hasteMap: HasteMap;
|
|
||||||
_hasteMapError: ?Error;
|
|
||||||
_helpers: DependencyGraphHelpers;
|
_helpers: DependencyGraphHelpers;
|
||||||
|
_fastfs: Fastfs;
|
||||||
_moduleCache: ModuleCache;
|
_moduleCache: ModuleCache;
|
||||||
|
_hasteMap: HasteMap;
|
||||||
|
_deprecatedAssetMap: DeprecatedAssetMap;
|
||||||
|
_hasteMapError: ?Error;
|
||||||
|
|
||||||
_loading: Promise<mixed>;
|
_loading: ?Promise<mixed>;
|
||||||
|
|
||||||
constructor({
|
constructor({
|
||||||
roots,
|
roots,
|
||||||
ignoreFilePath,
|
ignoreFilePath,
|
||||||
watch,
|
fileWatcher,
|
||||||
forceNodeFilesystemAPI,
|
forceNodeFilesystemAPI,
|
||||||
assetRoots_DEPRECATED,
|
assetRoots_DEPRECATED,
|
||||||
assetExts,
|
assetExts,
|
||||||
|
@ -114,7 +109,7 @@ class DependencyGraph {
|
||||||
}: {
|
}: {
|
||||||
roots: Array<string>,
|
roots: Array<string>,
|
||||||
ignoreFilePath: (filePath: string) => boolean,
|
ignoreFilePath: (filePath: string) => boolean,
|
||||||
watch: boolean,
|
fileWatcher: FileWatcher,
|
||||||
forceNodeFilesystemAPI?: boolean,
|
forceNodeFilesystemAPI?: boolean,
|
||||||
assetRoots_DEPRECATED: Array<string>,
|
assetRoots_DEPRECATED: Array<string>,
|
||||||
assetExts: Array<string>,
|
assetExts: Array<string>,
|
||||||
|
@ -138,7 +133,7 @@ class DependencyGraph {
|
||||||
this._opts = {
|
this._opts = {
|
||||||
roots,
|
roots,
|
||||||
ignoreFilePath: ignoreFilePath || (() => {}),
|
ignoreFilePath: ignoreFilePath || (() => {}),
|
||||||
watch: !!watch,
|
fileWatcher,
|
||||||
forceNodeFilesystemAPI: !!forceNodeFilesystemAPI,
|
forceNodeFilesystemAPI: !!forceNodeFilesystemAPI,
|
||||||
assetRoots_DEPRECATED: assetRoots_DEPRECATED || [],
|
assetRoots_DEPRECATED: assetRoots_DEPRECATED || [],
|
||||||
assetExts: assetExts || [],
|
assetExts: assetExts || [],
|
||||||
|
@ -160,9 +155,6 @@ 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);
|
||||||
|
@ -175,7 +167,7 @@ class DependencyGraph {
|
||||||
}
|
}
|
||||||
|
|
||||||
const mw = this._opts.maxWorkers;
|
const mw = this._opts.maxWorkers;
|
||||||
this._haste = new JestHasteMap({
|
const 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},
|
||||||
|
@ -188,10 +180,9 @@ 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 = this._haste.build().then(hasteMap => {
|
this._loading = haste.build().then(hasteMap => {
|
||||||
const initializingPackagerLogEntry =
|
const initializingPackagerLogEntry =
|
||||||
print(log(createActionStartEntry('Initializing Packager')));
|
print(log(createActionStartEntry('Initializing Packager')));
|
||||||
|
|
||||||
|
@ -200,12 +191,15 @@ 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,
|
||||||
|
@ -225,7 +219,14 @@ class DependencyGraph {
|
||||||
platforms: this._opts.platforms,
|
platforms: this._opts.platforms,
|
||||||
});
|
});
|
||||||
|
|
||||||
const assetFiles = hasteMap.hasteFS.matchFiles(this._assetPattern);
|
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);
|
||||||
|
|
||||||
this._deprecatedAssetMap = new DeprecatedAssetMap({
|
this._deprecatedAssetMap = new DeprecatedAssetMap({
|
||||||
helpers: this._helpers,
|
helpers: this._helpers,
|
||||||
|
@ -234,11 +235,11 @@ class DependencyGraph {
|
||||||
files: assetFiles,
|
files: assetFiles,
|
||||||
});
|
});
|
||||||
|
|
||||||
this._haste.on('change', ({eventsQueue}) =>
|
this._fastfs.on('change', (type, filePath, root, fstat) => {
|
||||||
eventsQueue.forEach(({type, filePath, stat}) =>
|
if (assetPattern.test(path.join(root, filePath))) {
|
||||||
this.processFileChange(type, filePath, stat)
|
this._deprecatedAssetMap.processFileChange(type, filePath, root, fstat);
|
||||||
)
|
}
|
||||||
);
|
});
|
||||||
|
|
||||||
const buildingHasteMapLogEntry =
|
const buildingHasteMapLogEntry =
|
||||||
print(log(createActionStartEntry('Building Haste Map')));
|
print(log(createActionStartEntry('Building Haste Map')));
|
||||||
|
@ -278,10 +279,6 @@ 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.
|
||||||
*/
|
*/
|
||||||
|
@ -368,16 +365,21 @@ class DependencyGraph {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
processFileChange(type: string, filePath: string, stat: Object) {
|
_processFileChange(type, filePath, root, fstat) {
|
||||||
this._fastfs.processFileChange(type, filePath, stat);
|
const absPath = path.join(root, filePath);
|
||||||
this._moduleCache.processFileChange(type, filePath, stat);
|
if (fstat && fstat.isDirectory() ||
|
||||||
if (this._assetPattern.test(filePath)) {
|
this._opts.ignoreFilePath(absPath) ||
|
||||||
this._deprecatedAssetMap.processFileChange(type, filePath, stat);
|
this._helpers.isNodeModulesDir(absPath)) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// This code reports failures but doesn't block recovery in the dev server
|
// Ok, this is some tricky promise code. Our requirements are:
|
||||||
// mode. When the hasteMap is left in an incorrect state, we'll rebuild when
|
// * we need to report back failures
|
||||||
// the next file changes.
|
// * 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.
|
||||||
const resolve = () => {
|
const resolve = () => {
|
||||||
if (this._hasteMapError) {
|
if (this._hasteMapError) {
|
||||||
console.warn(
|
console.warn(
|
||||||
|
@ -389,14 +391,13 @@ 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, filePath);
|
this._loading = this._hasteMap.processFileChange(type, absPath);
|
||||||
this._loading.catch(error => {
|
this._loading.catch((e) => {this._hasteMapError = e;});
|
||||||
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -410,6 +411,7 @@ class DependencyGraph {
|
||||||
|
|
||||||
static Cache;
|
static Cache;
|
||||||
static Fastfs;
|
static Fastfs;
|
||||||
|
static FileWatcher;
|
||||||
static Module;
|
static Module;
|
||||||
static Polyfill;
|
static Polyfill;
|
||||||
static getAssetDataFromName;
|
static getAssetDataFromName;
|
||||||
|
@ -422,6 +424,7 @@ class DependencyGraph {
|
||||||
Object.assign(DependencyGraph, {
|
Object.assign(DependencyGraph, {
|
||||||
Cache,
|
Cache,
|
||||||
Fastfs,
|
Fastfs,
|
||||||
|
FileWatcher,
|
||||||
Module,
|
Module,
|
||||||
Polyfill,
|
Polyfill,
|
||||||
getAssetDataFromName,
|
getAssetDataFromName,
|
||||||
|
|
Loading…
Reference in New Issue