Improve performance of node-haste2 and react-packager

Reviewed By: davidaurelio

Differential Revision: D2911210

fb-gh-sync-id: 8ac2f213e8a9e089281bb065f9a7190d2e0f5b18
shipit-source-id: 8ac2f213e8a9e089281bb065f9a7190d2e0f5b18
This commit is contained in:
Christoph Pojer 2016-02-15 22:33:11 -08:00 committed by facebook-github-bot-8
parent 5357bd7d99
commit 715c5b9b81
22 changed files with 215 additions and 237 deletions

View File

@ -10,6 +10,7 @@
require('../babelRegisterOnly')([/react-packager\/src/]);
require('fast-path').replace();
useGracefulFs();
var debug = require('debug');

View File

@ -51,11 +51,9 @@ class Cache {
throw new Error('Use absolute paths');
}
var recordP = this.has(filepath, field)
return this.has(filepath, field)
? this._data[filepath].data[field]
: this.set(filepath, field, loaderCb(filepath));
return recordP.then(record => record);
}
invalidate(filepath, field) {

View File

@ -9,7 +9,7 @@
'use strict';
const crypto = require('crypto');
const path = require('path');
const path = require('fast-path');
function getCacheFilePath(tmpdir, ...args) {
const hash = crypto.createHash('md5');

View File

@ -8,7 +8,7 @@
*/
'use strict';
const path = require('path');
const path = require('fast-path');
class DependencyGraphHelpers {
constructor({ providesModuleNodeModules, assetExts }) {
@ -17,17 +17,13 @@ class DependencyGraphHelpers {
}
isNodeModulesDir(file) {
let parts = path.normalize(file).split(path.sep);
const indexOfNodeModules = parts.lastIndexOf('node_modules');
if (indexOfNodeModules === -1) {
const index = file.lastIndexOf('/node_modules/');
if (index === -1) {
return false;
}
parts = parts.slice(indexOfNodeModules + 1);
const parts = file.substr(index + 14).split(path.sep);
const dirs = this._providesModuleNodeModules;
for (let i = 0; i < dirs.length; i++) {
if (parts.indexOf(dirs[i]) > -1) {
return false;
@ -42,7 +38,7 @@ class DependencyGraphHelpers {
}
extname(name) {
return path.extname(name).replace(/^\./, '');
return path.extname(name).substr(1);
}
}

View File

@ -11,7 +11,7 @@
const AssetModule_DEPRECATED = require('../AssetModule_DEPRECATED');
const Fastfs = require('../fastfs');
const debug = require('debug')('ReactNativePackager:DependencyGraph');
const path = require('path');
const path = require('fast-path');
const Promise = require('promise');
class DeprecatedAssetMap {
@ -23,8 +23,9 @@ class DeprecatedAssetMap {
ignoreFilePath,
helpers,
activity,
enabled,
}) {
if (roots == null || roots.length === 0) {
if (roots == null || roots.length === 0 || !enabled) {
this._disabled = true;
return;
}
@ -32,15 +33,18 @@ class DeprecatedAssetMap {
this._helpers = helpers;
this._map = Object.create(null);
this._assetExts = assetExts;
this._fastfs = new Fastfs(
'Assets',
roots,
fileWatcher,
{ ignore: ignoreFilePath, crawling: fsCrawl, activity }
);
this._activity = activity;
this._fastfs.on('change', this._processFileChange.bind(this));
if (!this._disabled) {
this._fastfs = new Fastfs(
'Assets',
roots,
fileWatcher,
{ ignore: ignoreFilePath, crawling: fsCrawl, activity }
);
this._fastfs.on('change', this._processFileChange.bind(this));
}
}
build() {
@ -89,7 +93,7 @@ class DeprecatedAssetMap {
if (this._assetExts.indexOf(ext) !== -1) {
const name = assetName(file, ext);
if (this._map[name] != null) {
debug('Conflcting assets', name);
debug('Conflicting assets', name);
}
this._map[name] = new AssetModule_DEPRECATED({ file });

View File

@ -7,7 +7,7 @@
* of patent rights can be found in the PATENTS file in the same directory.
*/
'use strict';
const path = require('path');
const path = require('fast-path');
const getPlatformExtension = require('../lib/getPlatformExtension');
const Promise = require('promise');
@ -31,18 +31,18 @@ class HasteMap {
build() {
this._map = Object.create(null);
let promises = this._fastfs.findFilesByExts(this._extensions, {
ignore: (file) => this._helpers.isNodeModulesDir(file),
}).map(file => this._processHasteModule(file));
promises = promises.concat(
this._fastfs.findFilesByName('package.json', {
ignore: (file) => this._helpers.isNodeModulesDir(file),
}).map(file => this._processHastePackage(file))
);
return Promise.all(promises);
const promises = [];
this._fastfs.getAllFiles().forEach(filePath => {
if (!this._helpers.isNodeModulesDir(filePath)) {
if (this._extensions.indexOf(path.extname(filePath).substr(1)) !== -1) {
promises.push(this._processHasteModule(filePath));
}
if (filePath.endsWith('/package.json')) {
promises.push(this._processHastePackage(filePath));
}
}
});
return Promise.all(promises).then(() => this._map);
}
processFileChange(type, absPath) {
@ -106,7 +106,7 @@ class HasteMap {
_processHastePackage(file) {
file = path.resolve(file);
const p = this._moduleCache.getPackage(file, this._fastfs);
const p = this._moduleCache.getPackage(file);
return p.isHaste()
.then(isHaste => isHaste && p.getName()
.then(name => this._updateHasteMap(name, p)))

View File

@ -10,7 +10,8 @@
const debug = require('debug')('ReactNativePackager:DependencyGraph');
const util = require('util');
const path = require('path');
const path = require('fast-path');
const realPath = require('path');
const isAbsolutePath = require('absolute-path');
const getAssetDataFromName = require('../lib/getAssetDataFromName');
const Promise = require('promise');
@ -287,7 +288,7 @@ class ResolutionRequest {
const searchQueue = [];
for (let currDir = path.dirname(fromModule.path);
currDir !== path.parse(fromModule.path).root;
currDir !== realPath.parse(fromModule.path).root;
currDir = path.dirname(currDir)) {
searchQueue.push(
path.join(currDir, 'node_modules', realModuleName)

View File

@ -14,7 +14,7 @@ const Promise = require('promise');
const crawl = require('../crawlers');
const getPlatformExtension = require('../lib/getPlatformExtension');
const isAbsolutePath = require('absolute-path');
const path = require('path');
const path = require('fast-path');
const util = require('util');
const DependencyGraphHelpers = require('./DependencyGraphHelpers');
const ResolutionRequest = require('./ResolutionRequest');
@ -46,6 +46,7 @@ class DependencyGraph {
extractRequires,
transformCode,
shouldThrowOnUnresolvedErrors = () => true,
enableAssetMap,
}) {
this._opts = {
activity: activity || defaultActivity,
@ -60,8 +61,9 @@ class DependencyGraph {
extensions: extensions || ['js', 'json'],
mocksPattern,
extractRequires,
shouldThrowOnUnresolvedErrors,
transformCode,
shouldThrowOnUnresolvedErrors,
enableAssetMap: enableAssetMap || true,
};
this._cache = cache;
this._helpers = new DependencyGraphHelpers(this._opts);
@ -121,25 +123,33 @@ class DependencyGraph {
ignoreFilePath: this._opts.ignoreFilePath,
assetExts: this._opts.assetExts,
activity: this._opts.activity,
enabled: this._opts.enableAssetMap,
});
this._loading = Promise.all([
this._fastfs.build()
.then(() => {
const hasteActivity = activity.startEvent('Building Haste Map');
return this._hasteMap.build().then(() => activity.endEvent(hasteActivity));
return this._hasteMap.build().then(map => {
activity.endEvent(hasteActivity);
return map;
});
}),
this._deprecatedAssetMap.build(),
]).then(() =>
activity.endEvent(depGraphActivity)
).catch(err => {
const error = new Error(
`Failed to build DependencyGraph: ${err.message}`
);
error.type = ERROR_BUILDING_DEP_GRAPH;
error.stack = err.stack;
throw error;
});
]).then(
response => {
activity.endEvent(depGraphActivity);
return response[0]; // Return the haste map
},
err => {
const error = new Error(
`Failed to build DependencyGraph: ${err.message}`
);
error.type = ERROR_BUILDING_DEP_GRAPH;
error.stack = err.stack;
throw error;
}
);
return this._loading;
}

View File

@ -10,4 +10,5 @@
module.exports = {
WatchmanWatcher: jest.genMockFromModule('sane/src/watchman_watcher'),
NodeWatcher: jest.genMockFromModule('sane/src/node_watcher'),
};

View File

@ -13,23 +13,27 @@ jest
.dontMock('events')
.dontMock('../')
.setMock('child_process', {
exec: function(cmd, cb) {
cb(null, '/usr/bin/watchman');
},
exec: (cmd, cb) => cb(null, '/usr/bin/watchman'),
});
var sane = require('sane');
const sane = require('sane');
describe('FileWatcher', function() {
var Watcher;
var FileWatcher;
var config;
describe('FileWatcher', () => {
let WatchmanWatcher;
let NodeWatcher;
let FileWatcher;
let config;
beforeEach(() => {
WatchmanWatcher = sane.WatchmanWatcher;
WatchmanWatcher.prototype.once.mockImplementation(
(type, callback) => callback()
);
NodeWatcher = sane.NodeWatcher;
NodeWatcher.prototype.once.mockImplementation(
(type, callback) => callback()
);
beforeEach(function() {
Watcher = sane.WatchmanWatcher;
Watcher.prototype.once.mockImplementation(function(type, callback) {
callback();
});
FileWatcher = require('../');
config = [{
@ -41,38 +45,45 @@ describe('FileWatcher', function() {
}];
});
pit('it should get the watcher instance when ready', function() {
var fileWatcher = new FileWatcher(config);
return fileWatcher.getWatchers().then(function(watchers) {
watchers.forEach(function(watcher) {
expect(watcher instanceof Watcher).toBe(true);
pit('gets the watcher instance when ready', () => {
const fileWatcher = new FileWatcher(config, {useWatchman: true});
return fileWatcher.getWatchers().then(watchers => {
watchers.forEach(watcher => {
expect(watcher instanceof WatchmanWatcher).toBe(true);
});
});
});
pit('should emit events', function() {
var cb;
Watcher.prototype.on.mockImplementation(function(type, callback) {
pit('gets the node watcher if watchman is disabled', () => {
const fileWatcher = new FileWatcher(config, {useWatchman: false});
return fileWatcher.getWatchers().then(watchers => {
watchers.forEach(watcher => {
expect(watcher instanceof NodeWatcher).toBe(true);
});
});
});
pit('emits events', () => {
let cb;
WatchmanWatcher.prototype.on.mockImplementation((type, callback) => {
cb = callback;
});
var fileWatcher = new FileWatcher(config);
var handler = jest.genMockFn();
const fileWatcher = new FileWatcher(config, {useWatchman: true});
const handler = jest.genMockFn();
fileWatcher.on('all', handler);
return fileWatcher.getWatchers().then(function() {
return fileWatcher.getWatchers().then(watchers => {
cb(1, 2, 3, 4);
jest.runAllTimers();
expect(handler.mock.calls[0]).toEqual([1, 2, 3, 4]);
});
});
pit('it should end the watcher', function() {
var fileWatcher = new FileWatcher(config);
Watcher.prototype.close.mockImplementation(function(callback) {
callback();
});
pit('ends the watcher', () => {
const fileWatcher = new FileWatcher(config, {useWatchman: true});
WatchmanWatcher.prototype.close.mockImplementation(callback => callback());
return fileWatcher.end().then(function() {
expect(Watcher.prototype.close).toBeCalled();
return fileWatcher.end().then(() => {
expect(WatchmanWatcher.prototype.close).toBeCalled();
});
});
});

View File

@ -30,17 +30,18 @@ let inited = false;
class FileWatcher extends EventEmitter {
constructor(rootConfigs) {
constructor(rootConfigs, options) {
if (inited) {
throw new Error('FileWatcher can only be instantiated once');
}
inited = true;
super();
this._useWatchman = options.useWatchman;
this._watcherByRoot = Object.create(null);
this._loading = Promise.all(
rootConfigs.map(createWatcher)
rootConfigs.map(rootConfig => this._createWatcher(rootConfig))
).then(watchers => {
watchers.forEach((watcher, i) => {
this._watcherByRoot[rootConfigs[i].dir] = watcher;
@ -65,9 +66,9 @@ class FileWatcher extends EventEmitter {
}
isWatchman() {
return detectingWatcherClass.then(
return this._useWatchman ? detectingWatcherClass.then(
Watcher => Watcher === sane.WatchmanWatcher
);
) : Promise.resolve(false);
}
end() {
@ -79,6 +80,30 @@ class FileWatcher extends EventEmitter {
);
}
_createWatcher(rootConfig) {
return detectingWatcherClass.then(Watcher => {
if (!this._useWatchman) {
Watcher = sane.NodeWatcher;
}
const watcher = new Watcher(rootConfig.dir, {
glob: rootConfig.globs,
dot: false,
});
return new Promise((resolve, reject) => {
const rejectTimeout = setTimeout(
() => reject(new Error(timeoutMessage(Watcher))),
MAX_WAIT_TIME
);
watcher.once('ready', () => {
clearTimeout(rejectTimeout);
resolve(watcher);
});
});
});
}
static createDummyWatcher() {
return Object.assign(new EventEmitter(), {
isWatchman: () => Promise.resolve(false),
@ -87,26 +112,6 @@ class FileWatcher extends EventEmitter {
}
}
function createWatcher(rootConfig) {
return detectingWatcherClass.then(function(Watcher) {
const watcher = new Watcher(rootConfig.dir, {
glob: rootConfig.globs,
dot: false,
});
return new Promise(function(resolve, reject) {
const rejectTimeout = setTimeout(function() {
reject(new Error(timeoutMessage(Watcher)));
}, MAX_WAIT_TIME);
watcher.once('ready', function() {
clearTimeout(rejectTimeout);
resolve(watcher);
});
});
});
}
function timeoutMessage(Watcher) {
const lines = [
'Watcher took too long to load (' + Watcher.name + ')',

View File

@ -12,7 +12,7 @@ const crypto = require('crypto');
const docblock = require('./DependencyGraph/docblock');
const isAbsolutePath = require('absolute-path');
const jsonStableStringify = require('json-stable-stringify');
const path = require('path');
const path = require('fast-path');
const extractRequires = require('./lib/extractRequires');
class Module {
@ -30,7 +30,7 @@ class Module {
throw new Error('Expected file to be absolute path but got ' + file);
}
this.path = path.resolve(file);
this.path = file;
this.type = 'Module';
this._fastfs = fastfs;
@ -132,7 +132,7 @@ class Module {
const fileContentPromise = this._fastfs.readFile(this.path);
return Promise.all([
fileContentPromise,
this._readDocBlock(fileContentPromise)
this._readDocBlock(fileContentPromise),
]).then(([code, {id, moduleDocBlock}]) => {
// Ignore requires in JSON files or generated code. An example of this
// is prebuilt files like the SourceMap library.
@ -151,7 +151,7 @@ class Module {
return {id, code, dependencies, map};
});
}
})
});
}
);
}

View File

@ -3,7 +3,7 @@
const AssetModule = require('./AssetModule');
const Package = require('./Package');
const Module = require('./Module');
const path = require('path');
const path = require('fast-path');
class ModuleCache {
@ -26,7 +26,6 @@ class ModuleCache {
}
getModule(filePath) {
filePath = path.resolve(filePath);
if (!this._moduleCache[filePath]) {
this._moduleCache[filePath] = new Module({
file: filePath,
@ -46,7 +45,6 @@ class ModuleCache {
}
getAssetModule(filePath) {
filePath = path.resolve(filePath);
if (!this._moduleCache[filePath]) {
this._moduleCache[filePath] = new AssetModule({
file: filePath,
@ -59,7 +57,6 @@ class ModuleCache {
}
getPackage(filePath) {
filePath = path.resolve(filePath);
if (!this._packageCache[filePath]) {
this._packageCache[filePath] = new Package({
file: filePath,

View File

@ -1,12 +1,12 @@
'use strict';
const isAbsolutePath = require('absolute-path');
const path = require('path');
const path = require('fast-path');
class Package {
constructor({ file, fastfs, cache }) {
this.path = path.resolve(file);
this.path = file;
this.root = path.dirname(this.path);
this._fastfs = fastfs;
this.type = 'Package';

View File

@ -202,9 +202,9 @@ describe('Module', () => {
let transformCode;
const fileContents = 'arbitrary(code);';
const exampleCode = `
require('a');
arbitrary.code('b');
require('c');`;
${'require'}('a');
${'System.import'}('b');
${'require'}('c');`;
beforeEach(function() {
transformCode = jest.genMockFn();
@ -281,7 +281,7 @@ describe('Module', () => {
const callsEqual = ([path1, key1], [path2, key2]) => {
expect(path1).toEqual(path2);
expect(key1).toEqual(key2);
}
};
it('gets dependencies from the cache with the same cache key for the same transform options', () => {
const options = {some: 'options'};
@ -293,7 +293,6 @@ describe('Module', () => {
});
it('gets dependencies from the cache with the same cache key for the equivalent transform options', () => {
const options = {some: 'options'};
module.getDependencies({a: 'b', c: 'd'}); // first call
module.getDependencies({c: 'd', a: 'b'}); // second call
@ -318,7 +317,6 @@ describe('Module', () => {
});
it('gets code from the cache with the same cache key for the equivalent transform options', () => {
const options = {some: 'options'};
module.getCode({a: 'b', c: 'd'}); // first call
module.getCode({c: 'd', a: 'b'}); // second call

View File

@ -3,7 +3,7 @@
const Promise = require('promise');
const debug = require('debug')('ReactNativePackager:DependencyGraph');
const fs = require('graceful-fs');
const path = require('path');
const path = require('fast-path');
const readDir = Promise.denodeify(fs.readdir);
const stat = Promise.denodeify(fs.stat);

View File

@ -1,7 +1,7 @@
'use strict';
const Promise = require('promise');
const path = require('path');
const path = require('fast-path');
function watchmanRecReadDir(roots, {ignore, fileWatcher, exts}) {
const files = [];
@ -38,20 +38,16 @@ function watchmanRecReadDir(roots, {ignore, fileWatcher, exts}) {
const cmd = Promise.denodeify(watcher.client.command.bind(watcher.client));
return cmd(['query', watchedRoot, {
'suffix': exts,
'expression': ['allof', ['type', 'f'], 'exists', dirExpr],
'fields': ['name'],
suffix: exts,
expression: ['allof', ['type', 'f'], 'exists', dirExpr],
fields: ['name'],
}]).then(resp => {
if ('warning' in resp) {
console.warn('watchman warning: ', resp.warning);
}
resp.files.forEach(filePath => {
filePath = path.join(
watchedRoot,
filePath
);
filePath = watchedRoot + path.sep + filePath;
if (!ignore(filePath)) {
files.push(filePath);
}
@ -64,7 +60,7 @@ function watchmanRecReadDir(roots, {ignore, fileWatcher, exts}) {
}
function isDescendant(root, child) {
return path.relative(root, child).indexOf('..') !== 0;
return child.startsWith(root);
}
module.exports = watchmanRecReadDir;

View File

@ -12,7 +12,7 @@ const Promise = require('promise');
const {EventEmitter} = require('events');
const fs = require('graceful-fs');
const path = require('path');
const path = require('fast-path');
// workaround for https://github.com/isaacs/node-graceful-fs/issues/56
// fs.close is patched, whereas graceful-fs.close is not.
@ -21,8 +21,6 @@ const fsClose = require('fs').close;
const readFile = Promise.denodeify(fs.readFile);
const stat = Promise.denodeify(fs.stat);
const hasOwn = Object.prototype.hasOwnProperty;
const NOT_FOUND_IN_ROOTS = 'NotFoundInRootsError';
class Fastfs extends EventEmitter {
@ -31,17 +29,20 @@ class Fastfs extends EventEmitter {
this._name = name;
this._fileWatcher = fileWatcher;
this._ignore = ignore;
this._roots = roots.map(root => new File(root, { isDir: true }));
this._roots = roots.map(root => {
// If the path ends in a separator ("/"), remove it to make string
// operations on paths safer.
if (root.endsWith(path.sep)) {
root = root.substr(0, root.length - 1);
}
return new File(root, true);
});
this._fastPaths = Object.create(null);
this._crawling = crawling;
this._activity = activity;
}
build() {
const rootsPattern = new RegExp(
'^(' + this._roots.map(root => escapeRegExp(root.path)).join('|') + ')'
);
return this._crawling.then(files => {
let fastfsActivity;
const activity = this._activity;
@ -49,18 +50,16 @@ class Fastfs extends EventEmitter {
fastfsActivity = activity.startEvent('Building in-memory fs for ' + this._name);
}
files.forEach(filePath => {
if (filePath.match(rootsPattern)) {
const newFile = new File(filePath, { isDir: false });
const parent = this._fastPaths[path.dirname(filePath)];
const root = this._getRoot(filePath);
if (root) {
const newFile = new File(filePath, false);
const dirname = filePath.substr(0, filePath.lastIndexOf(path.sep));
const parent = this._fastPaths[dirname];
this._fastPaths[filePath] = newFile;
if (parent) {
parent.addChild(newFile);
parent.addChild(newFile, this._fastPaths);
} else {
this._add(newFile);
for (let file = newFile; file; file = file.parent) {
if (!this._fastPaths[file.path]) {
this._fastPaths[file.path] = file;
}
}
root.addChild(newFile, this._fastPaths);
}
}
});
@ -72,42 +71,24 @@ class Fastfs extends EventEmitter {
}
stat(filePath) {
return Promise.resolve().then(() => {
const file = this._getFile(filePath);
return file.stat();
});
return Promise.resolve().then(() => this._getFile(filePath).stat());
}
getAllFiles() {
// one-level-deep flatten of files
return [].concat(...this._roots.map(root => root.getFiles()));
}
findFilesByExt(ext, { ignore } = {}) {
return this.findFilesByExts([ext], {ignore});
return Object.keys(this._fastPaths)
.filter(filePath => !this._fastPaths[filePath].isDir);
}
findFilesByExts(exts, { ignore } = {}) {
return this.getAllFiles()
.filter(file => (
exts.indexOf(file.ext()) !== -1 && (!ignore || !ignore(file.path))
))
.map(file => file.path);
}
findFilesByName(name, { ignore } = {}) {
return this.getAllFiles()
.filter(
file => path.basename(file.path) === name &&
(!ignore || !ignore(file.path))
)
.map(file => file.path);
.filter(filePath => (
exts.indexOf(path.extname(filePath).substr(1)) !== -1 &&
(!ignore || !ignore(filePath))
));
}
matchFilesByPattern(pattern) {
return this.getAllFiles()
.filter(file => file.path.match(pattern))
.map(file => file.path);
return this.getAllFiles().filter(file => file.match(pattern));
}
readFile(filePath) {
@ -198,25 +179,25 @@ class Fastfs extends EventEmitter {
_getFile(filePath) {
filePath = path.normalize(filePath);
if (!hasOwn.call(this._fastPaths, filePath)) {
this._fastPaths[filePath] = this._getAndAssertRoot(filePath).getFileFromPath(filePath);
if (!this._fastPaths[filePath]) {
const file = this._getAndAssertRoot(filePath).getFileFromPath(filePath);
if (file) {
this._fastPaths[filePath] = file;
}
}
return this._fastPaths[filePath];
}
_add(file) {
this._getAndAssertRoot(file.path).addChild(file);
}
_processFileChange(type, filePath, root, fstat) {
const absPath = path.join(root, 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.
if (!this._getRoot(absPath)) {
const root = this._getRoot(absPath);
if (!root) {
return;
}
@ -230,20 +211,19 @@ class Fastfs extends EventEmitter {
delete this._fastPaths[path.normalize(absPath)];
if (type !== 'delete') {
this._add(new File(absPath, { isDir: false }));
const file = new File(absPath, false);
root.addChild(file, this._fastPaths);
}
this.emit('change', type, filePath, root, fstat);
this.emit('change', type, filePath, rootPath, fstat);
}
}
class File {
constructor(filePath, { isDir }) {
constructor(filePath, isDir) {
this.path = filePath;
this.isDir = Boolean(isDir);
if (this.isDir) {
this.children = Object.create(null);
}
this.isDir = isDir;
this.children = this.isDir ? Object.create(null) : null;
}
read() {
@ -270,29 +250,24 @@ class File {
return this._stat;
}
addChild(file) {
const parts = path.relative(this.path, file.path).split(path.sep);
if (parts.length === 0) {
return;
}
addChild(file, fileMap) {
const parts = file.path.substr(this.path.length + 1).split(path.sep);
if (parts.length === 1) {
this.children[parts[0]] = file;
file.parent = this;
} else if (this.children[parts[0]]) {
this.children[parts[0]].addChild(file);
this.children[parts[0]].addChild(file, fileMap);
} else {
const dir = new File(path.join(this.path, parts[0]), { isDir: true });
const dir = new File(this.path + path.sep + parts[0], true);
dir.parent = this;
this.children[parts[0]] = dir;
dir.addChild(file);
fileMap[dir.path] = dir;
dir.addChild(file, fileMap);
}
}
getFileFromPath(filePath) {
const parts = path.relative(this.path, filePath)
.split(path.sep);
const parts = path.relative(this.path, filePath).split(path.sep);
/*eslint consistent-this:0*/
let file = this;
@ -313,21 +288,8 @@ class File {
return file;
}
getFiles() {
let files = [];
Object.keys(this.children).forEach(key => {
const file = this.children[key];
if (file.isDir) {
files = files.concat(file.getFiles());
} else {
files.push(file);
}
});
return files;
}
ext() {
return path.extname(this.path).replace(/^\./, '');
return path.extname(this.path).substr(1);
}
remove() {
@ -392,11 +354,7 @@ function makeReadCallback(fd, predicate, callback) {
}
function isDescendant(root, child) {
return path.relative(root, child).indexOf('..') !== 0;
}
function escapeRegExp(str) {
return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&');
return child.startsWith(root);
}
module.exports = Fastfs;

View File

@ -39,7 +39,7 @@ function extractRequires(code) {
.replace(replacePatterns.REQUIRE_RE, (match, pre, quot, dep, post) => {
deps.sync.push(dep);
return match;
})
});
return {code, deps};
}

View File

@ -8,7 +8,7 @@
*/
'use strict';
const path = require('path');
const path = require('fast-path');
const getPlatformExtension = require('./getPlatformExtension');
function getAssetDataFromName(filename) {

View File

@ -8,19 +8,21 @@
*/
'use strict';
const SUPPORTED_PLATFORM_EXTS = ['android', 'ios', 'web'];
const re = new RegExp(
'[^\\.]+\\.(' + SUPPORTED_PLATFORM_EXTS.join('|') + ')\\.\\w+$'
);
const SUPPORTED_PLATFORM_EXTS = {
android: true,
ios: true,
web: true,
};
// Extract platform extension: index.ios.js -> ios
function getPlatformExtension(file) {
const match = file.match(re);
if (match && match[1]) {
return match[1];
const last = file.lastIndexOf('.');
const secondToLast = file.lastIndexOf('.', last - 1);
if (secondToLast === -1) {
return null;
}
return null;
const platform = file.substring(secondToLast + 1, last);
return SUPPORTED_PLATFORM_EXTS[platform] ? platform : null;
}
module.exports = getPlatformExtension;

View File

@ -182,7 +182,7 @@ class Server {
this._fileWatcher = options.nonPersistent
? FileWatcher.createDummyWatcher()
: new FileWatcher(watchRootConfigs);
: new FileWatcher(watchRootConfigs, {useWatchman: true});
this._assetServer = new AssetServer({
projectRoots: opts.projectRoots,