mirror of https://github.com/status-im/metro.git
Bring back node-haste to fbsource
Summary: Since jest stopped using node-haste a while ago, we are the only client left. This brings back node-haste back to fbsource to allow us to iterate faster. Reviewed By: bestander Differential Revision: D3641341 fbshipit-source-id: a859f8834765723a3515e2cf265581b9dd83997c
This commit is contained in:
parent
6501f74652
commit
f34bb8fb94
|
@ -10,7 +10,7 @@
|
||||||
|
|
||||||
require('../babelRegisterOnly')([/react-packager\/src/]);
|
require('../babelRegisterOnly')([/react-packager\/src/]);
|
||||||
|
|
||||||
require('node-haste/lib/fastpath').replace();
|
require('./src/node-haste/fastpath').replace();
|
||||||
useGracefulFs();
|
useGracefulFs();
|
||||||
|
|
||||||
var debug = require('debug');
|
var debug = require('debug');
|
||||||
|
|
|
@ -23,8 +23,8 @@ var fs = require('fs');
|
||||||
|
|
||||||
describe('AssetServer', () => {
|
describe('AssetServer', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
const NodeHaste = require('node-haste');
|
const NodeHaste = require('../../node-haste');
|
||||||
NodeHaste.getAssetDataFromName = require.requireActual('node-haste/lib/lib/getAssetDataFromName');
|
NodeHaste.getAssetDataFromName = require.requireActual('../../node-haste/lib/getAssetDataFromName');
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('assetServer.get', () => {
|
describe('assetServer.get', () => {
|
||||||
|
|
|
@ -13,7 +13,7 @@ const Promise = require('promise');
|
||||||
const crypto = require('crypto');
|
const crypto = require('crypto');
|
||||||
const declareOpts = require('../lib/declareOpts');
|
const declareOpts = require('../lib/declareOpts');
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const getAssetDataFromName = require('node-haste').getAssetDataFromName;
|
const getAssetDataFromName = require('../node-haste').getAssetDataFromName;
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
|
|
||||||
const createTimeoutPromise = (timeout) => new Promise((resolve, reject) => {
|
const createTimeoutPromise = (timeout) => new Promise((resolve, reject) => {
|
||||||
|
|
|
@ -17,7 +17,7 @@ jest
|
||||||
.mock('fs')
|
.mock('fs')
|
||||||
.mock('assert')
|
.mock('assert')
|
||||||
.mock('progress')
|
.mock('progress')
|
||||||
.mock('node-haste')
|
.mock('../../node-haste')
|
||||||
.mock('../../JSTransformer')
|
.mock('../../JSTransformer')
|
||||||
.mock('../../lib/declareOpts')
|
.mock('../../lib/declareOpts')
|
||||||
.mock('../../Resolver')
|
.mock('../../Resolver')
|
||||||
|
|
|
@ -13,7 +13,7 @@ const fs = require('fs');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const Promise = require('promise');
|
const Promise = require('promise');
|
||||||
const ProgressBar = require('progress');
|
const ProgressBar = require('progress');
|
||||||
const Cache = require('node-haste').Cache;
|
const Cache = require('../node-haste').Cache;
|
||||||
const Transformer = require('../JSTransformer');
|
const Transformer = require('../JSTransformer');
|
||||||
const Resolver = require('../Resolver');
|
const Resolver = require('../Resolver');
|
||||||
const Bundle = require('./Bundle');
|
const Bundle = require('./Bundle');
|
||||||
|
|
|
@ -13,7 +13,7 @@ jest.mock('path');
|
||||||
|
|
||||||
|
|
||||||
const DependencyGraph = jest.fn();
|
const DependencyGraph = jest.fn();
|
||||||
jest.setMock('node-haste', DependencyGraph);
|
jest.setMock('../../node-haste', DependencyGraph);
|
||||||
let Module;
|
let Module;
|
||||||
let Polyfill;
|
let Polyfill;
|
||||||
|
|
||||||
|
@ -36,7 +36,7 @@ describe('Resolver', function() {
|
||||||
return polyfill;
|
return polyfill;
|
||||||
});
|
});
|
||||||
|
|
||||||
DependencyGraph.replacePatterns = require.requireActual('node-haste/lib/lib/replacePatterns');
|
DependencyGraph.replacePatterns = require.requireActual('../../node-haste/lib/replacePatterns');
|
||||||
DependencyGraph.prototype.createPolyfill = jest.fn();
|
DependencyGraph.prototype.createPolyfill = jest.fn();
|
||||||
DependencyGraph.prototype.getDependencies = jest.fn();
|
DependencyGraph.prototype.getDependencies = jest.fn();
|
||||||
|
|
||||||
|
|
|
@ -11,7 +11,7 @@
|
||||||
|
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const Activity = require('../Activity');
|
const Activity = require('../Activity');
|
||||||
const DependencyGraph = require('node-haste');
|
const DependencyGraph = require('../node-haste');
|
||||||
const declareOpts = require('../lib/declareOpts');
|
const declareOpts = require('../lib/declareOpts');
|
||||||
const Promise = require('promise');
|
const Promise = require('promise');
|
||||||
|
|
||||||
|
|
|
@ -18,7 +18,7 @@ jest.setMock('worker-farm', function() { return () => {}; })
|
||||||
.mock('../../Bundler')
|
.mock('../../Bundler')
|
||||||
.mock('../../AssetServer')
|
.mock('../../AssetServer')
|
||||||
.mock('../../lib/declareOpts')
|
.mock('../../lib/declareOpts')
|
||||||
.mock('node-haste')
|
.mock('../../node-haste')
|
||||||
.mock('../../Activity');
|
.mock('../../Activity');
|
||||||
|
|
||||||
let FileWatcher;
|
let FileWatcher;
|
||||||
|
@ -64,7 +64,7 @@ describe('processRequest', () => {
|
||||||
let triggerFileChange;
|
let triggerFileChange;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
FileWatcher = require('node-haste').FileWatcher;
|
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',
|
||||||
|
|
|
@ -10,8 +10,8 @@
|
||||||
|
|
||||||
const Activity = require('../Activity');
|
const Activity = require('../Activity');
|
||||||
const AssetServer = require('../AssetServer');
|
const AssetServer = require('../AssetServer');
|
||||||
const FileWatcher = require('node-haste').FileWatcher;
|
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 Promise = require('promise');
|
const Promise = require('promise');
|
||||||
const SourceMapConsumer = require('source-map').SourceMapConsumer;
|
const SourceMapConsumer = require('source-map').SourceMapConsumer;
|
||||||
|
|
|
@ -8,4 +8,4 @@
|
||||||
*/
|
*/
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
module.exports = require.requireActual('node-haste/mocks/graceful-fs');
|
module.exports = require.requireActual('../node-haste/__mocks__/graceful-fs');
|
||||||
|
|
|
@ -0,0 +1,47 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const Module = require('./Module');
|
||||||
|
const getAssetDataFromName = require('./lib/getAssetDataFromName');
|
||||||
|
|
||||||
|
class AssetModule extends Module {
|
||||||
|
constructor(args, platforms) {
|
||||||
|
super(args);
|
||||||
|
const { resolution, name, type } = getAssetDataFromName(this.path, platforms);
|
||||||
|
this.resolution = resolution;
|
||||||
|
this._name = name;
|
||||||
|
this._type = type;
|
||||||
|
this._dependencies = args.dependencies || [];
|
||||||
|
}
|
||||||
|
|
||||||
|
isHaste() {
|
||||||
|
return Promise.resolve(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
getDependencies() {
|
||||||
|
return Promise.resolve(this._dependencies);
|
||||||
|
}
|
||||||
|
|
||||||
|
read() {
|
||||||
|
return Promise.resolve({});
|
||||||
|
}
|
||||||
|
|
||||||
|
getName() {
|
||||||
|
return super.getName().then(
|
||||||
|
id => id.replace(/\/[^\/]+$/, `/${this._name}.${this._type}`)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
hash() {
|
||||||
|
return `AssetModule : ${this.path}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
isJSON() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
isAsset() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = AssetModule;
|
|
@ -0,0 +1,45 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const Module = require('./Module');
|
||||||
|
const getAssetDataFromName = require('./lib/getAssetDataFromName');
|
||||||
|
|
||||||
|
class AssetModule_DEPRECATED extends Module {
|
||||||
|
constructor(args, platforms) {
|
||||||
|
super(args);
|
||||||
|
const {resolution, name} = getAssetDataFromName(this.path, platforms);
|
||||||
|
this.resolution = resolution;
|
||||||
|
this.name = name;
|
||||||
|
this.platforms = platforms;
|
||||||
|
}
|
||||||
|
|
||||||
|
isHaste() {
|
||||||
|
return Promise.resolve(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
getName() {
|
||||||
|
return Promise.resolve(`image!${this.name}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
getDependencies() {
|
||||||
|
return Promise.resolve([]);
|
||||||
|
}
|
||||||
|
|
||||||
|
hash() {
|
||||||
|
return `AssetModule_DEPRECATED : ${this.path}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
isJSON() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
isAsset_DEPRECATED() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
resolution() {
|
||||||
|
return getAssetDataFromName(this.path, this.platforms).resolution;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = AssetModule_DEPRECATED;
|
|
@ -0,0 +1,20 @@
|
||||||
|
/**
|
||||||
|
* 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';
|
||||||
|
|
||||||
|
class Cache {
|
||||||
|
get(filepath, field, cb) {
|
||||||
|
return cb(filepath);
|
||||||
|
}
|
||||||
|
|
||||||
|
invalidate(filepath) { }
|
||||||
|
end() { }
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = Cache;
|
|
@ -0,0 +1,336 @@
|
||||||
|
/**
|
||||||
|
* 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('absolute-path')
|
||||||
|
.dontMock('../');
|
||||||
|
|
||||||
|
jest
|
||||||
|
.mock('fs')
|
||||||
|
.setMock('os', {
|
||||||
|
tmpDir() { return 'tmpDir'; },
|
||||||
|
});
|
||||||
|
|
||||||
|
jest.useRealTimers();
|
||||||
|
|
||||||
|
|
||||||
|
describe('Cache', () => {
|
||||||
|
let Cache, fs;
|
||||||
|
beforeEach(() => {
|
||||||
|
Cache = require('../');
|
||||||
|
fs = require('graceful-fs');
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getting/setting', () => {
|
||||||
|
pit('calls loader callback for uncached file', () => {
|
||||||
|
fs.stat.mockImpl((file, callback) => {
|
||||||
|
callback(null, {
|
||||||
|
mtime: {
|
||||||
|
getTime: () => {},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
var cache = new Cache({
|
||||||
|
cacheKey: 'cache',
|
||||||
|
});
|
||||||
|
var loaderCb = jest.genMockFn().mockImpl(() => Promise.resolve());
|
||||||
|
|
||||||
|
return cache
|
||||||
|
.get('/rootDir/someFile', 'field', loaderCb)
|
||||||
|
.then($ =>
|
||||||
|
expect(loaderCb).toBeCalledWith('/rootDir/someFile')
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
pit('supports storing multiple fields', () => {
|
||||||
|
fs.stat.mockImpl((file, callback) => {
|
||||||
|
callback(null, {
|
||||||
|
mtime: {
|
||||||
|
getTime: () => {},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
var cache = new Cache({
|
||||||
|
cacheKey: 'cache',
|
||||||
|
});
|
||||||
|
var index = 0;
|
||||||
|
var loaderCb = jest.genMockFn().mockImpl(() =>
|
||||||
|
Promise.resolve(index++)
|
||||||
|
);
|
||||||
|
|
||||||
|
return cache
|
||||||
|
.get('/rootDir/someFile', 'field1', loaderCb)
|
||||||
|
.then(value => {
|
||||||
|
expect(value).toBe(0);
|
||||||
|
return cache
|
||||||
|
.get('/rootDir/someFile', 'field2', loaderCb)
|
||||||
|
.then(value2 => expect(value2).toBe(1));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
pit('gets the value from the loader callback', () => {
|
||||||
|
fs.stat.mockImpl((file, callback) =>
|
||||||
|
callback(null, {
|
||||||
|
mtime: {
|
||||||
|
getTime: () => {},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
var cache = new Cache({
|
||||||
|
cacheKey: 'cache',
|
||||||
|
});
|
||||||
|
var loaderCb = jest.genMockFn().mockImpl(() =>
|
||||||
|
Promise.resolve('lol')
|
||||||
|
);
|
||||||
|
|
||||||
|
return cache
|
||||||
|
.get('/rootDir/someFile', 'field', loaderCb)
|
||||||
|
.then(value => expect(value).toBe('lol'));
|
||||||
|
});
|
||||||
|
|
||||||
|
pit('caches the value after the first call', () => {
|
||||||
|
fs.stat.mockImpl((file, callback) => {
|
||||||
|
callback(null, {
|
||||||
|
mtime: {
|
||||||
|
getTime: () => {},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
var cache = new Cache({
|
||||||
|
cacheKey: 'cache',
|
||||||
|
});
|
||||||
|
var loaderCb = jest.genMockFn().mockImpl(() =>
|
||||||
|
Promise.resolve('lol')
|
||||||
|
);
|
||||||
|
|
||||||
|
return cache
|
||||||
|
.get('/rootDir/someFile', 'field', loaderCb)
|
||||||
|
.then(() => {
|
||||||
|
var shouldNotBeCalled = jest.genMockFn();
|
||||||
|
return cache.get('/rootDir/someFile', 'field', shouldNotBeCalled)
|
||||||
|
.then(value => {
|
||||||
|
expect(shouldNotBeCalled).not.toBeCalled();
|
||||||
|
expect(value).toBe('lol');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
pit('clears old field when getting new field and mtime changed', () => {
|
||||||
|
var mtime = 0;
|
||||||
|
fs.stat.mockImpl((file, callback) => {
|
||||||
|
callback(null, {
|
||||||
|
mtime: {
|
||||||
|
getTime: () => mtime++,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
var cache = new Cache({
|
||||||
|
cacheKey: 'cache',
|
||||||
|
});
|
||||||
|
var loaderCb = jest.genMockFn().mockImpl(() =>
|
||||||
|
Promise.resolve('lol' + mtime)
|
||||||
|
);
|
||||||
|
|
||||||
|
return cache
|
||||||
|
.get('/rootDir/someFile', 'field1', loaderCb)
|
||||||
|
.then(value => cache
|
||||||
|
.get('/rootDir/someFile', 'field2', loaderCb)
|
||||||
|
.then(value2 => cache
|
||||||
|
.get('/rootDir/someFile', 'field1', loaderCb)
|
||||||
|
.then(value3 => expect(value3).toBe('lol2'))
|
||||||
|
)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('loading cache from disk', () => {
|
||||||
|
var fileStats;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fileStats = {
|
||||||
|
'/rootDir/someFile': {
|
||||||
|
mtime: {
|
||||||
|
getTime: () => 22,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'/rootDir/foo': {
|
||||||
|
mtime: {
|
||||||
|
getTime: () => 11,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
fs.existsSync.mockImpl(() => true);
|
||||||
|
|
||||||
|
fs.statSync.mockImpl(filePath => fileStats[filePath]);
|
||||||
|
|
||||||
|
fs.readFileSync.mockImpl(() => JSON.stringify({
|
||||||
|
'/rootDir/someFile': {
|
||||||
|
metadata: {mtime: 22},
|
||||||
|
data: {field: 'oh hai'},
|
||||||
|
},
|
||||||
|
'/rootDir/foo': {
|
||||||
|
metadata: {mtime: 11},
|
||||||
|
data: {field: 'lol wat'},
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
pit('should load cache from disk', () => {
|
||||||
|
var cache = new Cache({
|
||||||
|
cacheKey: 'cache',
|
||||||
|
});
|
||||||
|
var loaderCb = jest.genMockFn();
|
||||||
|
|
||||||
|
return cache
|
||||||
|
.get('/rootDir/someFile', 'field', loaderCb)
|
||||||
|
.then(value => {
|
||||||
|
expect(loaderCb).not.toBeCalled();
|
||||||
|
expect(value).toBe('oh hai');
|
||||||
|
|
||||||
|
return cache
|
||||||
|
.get('/rootDir/foo', 'field', loaderCb)
|
||||||
|
.then(val => {
|
||||||
|
expect(loaderCb).not.toBeCalled();
|
||||||
|
expect(val).toBe('lol wat');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
pit('should not load outdated cache', () => {
|
||||||
|
fs.stat.mockImpl((file, callback) =>
|
||||||
|
callback(null, {
|
||||||
|
mtime: {
|
||||||
|
getTime: () => {},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
fileStats['/rootDir/foo'].mtime.getTime = () => 123;
|
||||||
|
|
||||||
|
var cache = new Cache({
|
||||||
|
cacheKey: 'cache',
|
||||||
|
});
|
||||||
|
var loaderCb = jest.genMockFn().mockImpl(() =>
|
||||||
|
Promise.resolve('new value')
|
||||||
|
);
|
||||||
|
|
||||||
|
return cache
|
||||||
|
.get('/rootDir/someFile', 'field', loaderCb)
|
||||||
|
.then(value => {
|
||||||
|
expect(loaderCb).not.toBeCalled();
|
||||||
|
expect(value).toBe('oh hai');
|
||||||
|
|
||||||
|
return cache
|
||||||
|
.get('/rootDir/foo', 'field', loaderCb)
|
||||||
|
.then(val => {
|
||||||
|
expect(loaderCb).toBeCalled();
|
||||||
|
expect(val).toBe('new value');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('writing cache to disk', () => {
|
||||||
|
it('should write cache to disk', (done) => {
|
||||||
|
var index = 0;
|
||||||
|
var mtimes = [10, 20, 30];
|
||||||
|
|
||||||
|
fs.stat.mockImpl((file, callback) =>
|
||||||
|
callback(null, {
|
||||||
|
mtime: {
|
||||||
|
getTime: () => mtimes[index++],
|
||||||
|
},
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
var cache = new Cache({
|
||||||
|
cacheKey: 'cache',
|
||||||
|
});
|
||||||
|
|
||||||
|
cache.get('/rootDir/bar', 'field', () =>
|
||||||
|
Promise.resolve('bar value')
|
||||||
|
);
|
||||||
|
cache.get('/rootDir/foo', 'field', () =>
|
||||||
|
Promise.resolve('foo value')
|
||||||
|
);
|
||||||
|
cache.get('/rootDir/baz', 'field', () =>
|
||||||
|
Promise.resolve('baz value')
|
||||||
|
);
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
expect(fs.writeFile).toBeCalled();
|
||||||
|
done();
|
||||||
|
}, 2020);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('check for cache presence', () => {
|
||||||
|
it('synchronously resolves cache presence', () => {
|
||||||
|
fs.stat.mockImpl((file, callback) =>
|
||||||
|
callback(null, {
|
||||||
|
mtime: {
|
||||||
|
getTime: () => {},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
var cache = new Cache({
|
||||||
|
cacheKey: 'cache',
|
||||||
|
});
|
||||||
|
var loaderCb = jest.genMockFn().mockImpl(() =>
|
||||||
|
Promise.resolve('banana')
|
||||||
|
);
|
||||||
|
var file = '/rootDir/someFile';
|
||||||
|
|
||||||
|
return cache
|
||||||
|
.get(file, 'field', loaderCb)
|
||||||
|
.then(() => {
|
||||||
|
expect(cache.has(file)).toBe(true);
|
||||||
|
expect(cache.has(file, 'field')).toBe(true);
|
||||||
|
expect(cache.has(file, 'foo')).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('invalidate', () => {
|
||||||
|
it('invalidates the cache per file or per-field', () => {
|
||||||
|
fs.stat.mockImpl((file, callback) =>
|
||||||
|
callback(null, {
|
||||||
|
mtime: {
|
||||||
|
getTime: () => {},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
var cache = new Cache({
|
||||||
|
cacheKey: 'cache',
|
||||||
|
});
|
||||||
|
var loaderCb = jest.genMockFn().mockImpl(() =>
|
||||||
|
Promise.resolve('banana')
|
||||||
|
);
|
||||||
|
var file = '/rootDir/someFile';
|
||||||
|
|
||||||
|
return cache.get(file, 'field', loaderCb).then(() => {
|
||||||
|
expect(cache.has(file)).toBe(true);
|
||||||
|
cache.invalidate(file, 'field');
|
||||||
|
expect(cache.has(file)).toBe(true);
|
||||||
|
expect(cache.has(file, 'field')).toBe(false);
|
||||||
|
cache.invalidate(file);
|
||||||
|
expect(cache.has(file)).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,217 @@
|
||||||
|
/**
|
||||||
|
* 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 denodeify = require('denodeify');
|
||||||
|
const crypto = require('crypto');
|
||||||
|
const fs = require('graceful-fs');
|
||||||
|
const isAbsolutePath = require('absolute-path');
|
||||||
|
const path = require('../fastpath');
|
||||||
|
const tmpDir = require('os').tmpDir();
|
||||||
|
|
||||||
|
function getObjectValues(object) {
|
||||||
|
return Object.keys(object).map(key => object[key]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function debounce(fn, delay) {
|
||||||
|
var timeout;
|
||||||
|
return () => {
|
||||||
|
clearTimeout(timeout);
|
||||||
|
timeout = setTimeout(fn, delay);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
class Cache {
|
||||||
|
constructor({
|
||||||
|
resetCache,
|
||||||
|
cacheKey,
|
||||||
|
cacheDirectory = tmpDir,
|
||||||
|
}) {
|
||||||
|
this._cacheFilePath = Cache.getCacheFilePath(cacheDirectory, cacheKey);
|
||||||
|
if (!resetCache) {
|
||||||
|
this._data = this._loadCacheSync(this._cacheFilePath);
|
||||||
|
} else {
|
||||||
|
this._data = Object.create(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
this._persistEventually = debounce(this._persistCache.bind(this), 2000);
|
||||||
|
}
|
||||||
|
|
||||||
|
static getCacheFilePath(tmpdir, ...args) {
|
||||||
|
const hash = crypto.createHash('md5');
|
||||||
|
args.forEach(arg => hash.update(arg));
|
||||||
|
return path.join(tmpdir, hash.digest('hex'));
|
||||||
|
}
|
||||||
|
|
||||||
|
get(filepath, field, loaderCb) {
|
||||||
|
if (!isAbsolutePath(filepath)) {
|
||||||
|
throw new Error('Use absolute paths');
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.has(filepath, field)
|
||||||
|
? this._data[filepath].data[field]
|
||||||
|
: this.set(filepath, field, loaderCb(filepath));
|
||||||
|
}
|
||||||
|
|
||||||
|
invalidate(filepath, field) {
|
||||||
|
if (this.has(filepath, field)) {
|
||||||
|
if (field == null) {
|
||||||
|
delete this._data[filepath];
|
||||||
|
} else {
|
||||||
|
delete this._data[filepath].data[field];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
end() {
|
||||||
|
return this._persistCache();
|
||||||
|
}
|
||||||
|
|
||||||
|
has(filepath, field) {
|
||||||
|
return Object.prototype.hasOwnProperty.call(this._data, filepath) &&
|
||||||
|
(field == null || Object.prototype.hasOwnProperty.call(this._data[filepath].data, field));
|
||||||
|
}
|
||||||
|
|
||||||
|
set(filepath, field, loaderPromise) {
|
||||||
|
let record = this._data[filepath];
|
||||||
|
if (!record) {
|
||||||
|
record = Object.create(null);
|
||||||
|
this._data[filepath] = record;
|
||||||
|
this._data[filepath].data = Object.create(null);
|
||||||
|
this._data[filepath].metadata = Object.create(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
record.data[field] = loaderPromise
|
||||||
|
.then(data => Promise.all([
|
||||||
|
data,
|
||||||
|
denodeify(fs.stat)(filepath),
|
||||||
|
]))
|
||||||
|
.then(([data, stat]) => {
|
||||||
|
this._persistEventually();
|
||||||
|
|
||||||
|
// Evict all existing field data from the cache if we're putting new
|
||||||
|
// more up to date data
|
||||||
|
var mtime = stat.mtime.getTime();
|
||||||
|
if (record.metadata.mtime !== mtime) {
|
||||||
|
record.data = Object.create(null);
|
||||||
|
}
|
||||||
|
record.metadata.mtime = mtime;
|
||||||
|
|
||||||
|
return data;
|
||||||
|
});
|
||||||
|
|
||||||
|
return record.data[field];
|
||||||
|
}
|
||||||
|
|
||||||
|
_persistCache() {
|
||||||
|
if (this._persisting != null) {
|
||||||
|
return this._persisting;
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = this._data;
|
||||||
|
const cacheFilepath = this._cacheFilePath;
|
||||||
|
|
||||||
|
const allPromises = getObjectValues(data)
|
||||||
|
.map(record => {
|
||||||
|
const fieldNames = Object.keys(record.data);
|
||||||
|
const fieldValues = getObjectValues(record.data);
|
||||||
|
|
||||||
|
return Promise
|
||||||
|
.all(fieldValues)
|
||||||
|
.then(ref => {
|
||||||
|
const ret = Object.create(null);
|
||||||
|
ret.metadata = record.metadata;
|
||||||
|
ret.data = Object.create(null);
|
||||||
|
fieldNames.forEach((field, index) =>
|
||||||
|
ret.data[field] = ref[index]
|
||||||
|
);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
this._persisting = Promise.all(allPromises)
|
||||||
|
.then(values => {
|
||||||
|
const json = Object.create(null);
|
||||||
|
Object.keys(data).forEach((key, i) => {
|
||||||
|
// make sure the key wasn't added nor removed after we started
|
||||||
|
// persisting the cache
|
||||||
|
const value = values[i];
|
||||||
|
if (!value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
json[key] = Object.create(null);
|
||||||
|
json[key].metadata = data[key].metadata;
|
||||||
|
json[key].data = value.data;
|
||||||
|
});
|
||||||
|
return denodeify(fs.writeFile)(cacheFilepath, JSON.stringify(json));
|
||||||
|
})
|
||||||
|
.catch(e => console.error(
|
||||||
|
'[node-haste] Encountered an error while persisting cache:\n%s',
|
||||||
|
e.stack.split('\n').map(line => '> ' + line).join('\n')
|
||||||
|
))
|
||||||
|
.then(() => {
|
||||||
|
this._persisting = null;
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
return this._persisting;
|
||||||
|
}
|
||||||
|
|
||||||
|
_loadCacheSync(cachePath) {
|
||||||
|
var ret = Object.create(null);
|
||||||
|
var cacheOnDisk = loadCacheSync(cachePath);
|
||||||
|
|
||||||
|
// Filter outdated cache and convert to promises.
|
||||||
|
Object.keys(cacheOnDisk).forEach(key => {
|
||||||
|
if (!fs.existsSync(key)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var record = cacheOnDisk[key];
|
||||||
|
var stat = fs.statSync(key);
|
||||||
|
if (stat.mtime.getTime() === record.metadata.mtime) {
|
||||||
|
ret[key] = Object.create(null);
|
||||||
|
ret[key].metadata = Object.create(null);
|
||||||
|
ret[key].data = Object.create(null);
|
||||||
|
ret[key].metadata.mtime = record.metadata.mtime;
|
||||||
|
|
||||||
|
Object.keys(record.data).forEach(field => {
|
||||||
|
ret[key].data[field] = Promise.resolve(record.data[field]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadCacheSync(cachePath) {
|
||||||
|
if (!fs.existsSync(cachePath)) {
|
||||||
|
return Object.create(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
return JSON.parse(fs.readFileSync(cachePath));
|
||||||
|
} catch (e) {
|
||||||
|
if (e instanceof SyntaxError) {
|
||||||
|
console.warn('Unable to parse cache file. Will clear and continue.');
|
||||||
|
try {
|
||||||
|
fs.unlinkSync(cachePath);
|
||||||
|
} catch (err) {
|
||||||
|
// Someone else might've deleted it.
|
||||||
|
}
|
||||||
|
return Object.create(null);
|
||||||
|
}
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = Cache;
|
|
@ -0,0 +1,47 @@
|
||||||
|
/**
|
||||||
|
* 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 path = require('../fastpath');
|
||||||
|
|
||||||
|
const NODE_MODULES = path.sep + 'node_modules' + path.sep;
|
||||||
|
|
||||||
|
class DependencyGraphHelpers {
|
||||||
|
constructor({ providesModuleNodeModules, assetExts }) {
|
||||||
|
this._providesModuleNodeModules = providesModuleNodeModules;
|
||||||
|
this._assetExts = assetExts;
|
||||||
|
}
|
||||||
|
|
||||||
|
isNodeModulesDir(file) {
|
||||||
|
const index = file.lastIndexOf(NODE_MODULES);
|
||||||
|
if (index === -1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
isAssetFile(file) {
|
||||||
|
return this._assetExts.indexOf(this.extname(file)) !== -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
extname(name) {
|
||||||
|
return path.extname(name).substr(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = DependencyGraphHelpers;
|
|
@ -0,0 +1,120 @@
|
||||||
|
/**
|
||||||
|
* 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 AssetModule_DEPRECATED = require('../AssetModule_DEPRECATED');
|
||||||
|
const Fastfs = require('../fastfs');
|
||||||
|
const debug = require('debug')('ReactNativePackager:DependencyGraph');
|
||||||
|
const path = require('../fastpath');
|
||||||
|
|
||||||
|
class DeprecatedAssetMap {
|
||||||
|
constructor({
|
||||||
|
fsCrawl,
|
||||||
|
roots,
|
||||||
|
assetExts,
|
||||||
|
fileWatcher,
|
||||||
|
ignoreFilePath,
|
||||||
|
helpers,
|
||||||
|
activity,
|
||||||
|
enabled,
|
||||||
|
platforms,
|
||||||
|
}) {
|
||||||
|
if (roots == null || roots.length === 0 || !enabled) {
|
||||||
|
this._disabled = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._helpers = helpers;
|
||||||
|
this._map = Object.create(null);
|
||||||
|
this._assetExts = assetExts;
|
||||||
|
this._activity = activity;
|
||||||
|
this._platforms = platforms;
|
||||||
|
|
||||||
|
if (!this._disabled) {
|
||||||
|
this._fastfs = new Fastfs(
|
||||||
|
'Assets',
|
||||||
|
roots,
|
||||||
|
fileWatcher,
|
||||||
|
{ ignore: ignoreFilePath, crawling: fsCrawl, activity }
|
||||||
|
);
|
||||||
|
|
||||||
|
this._fastfs.on('change', this._processFileChange.bind(this));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
build() {
|
||||||
|
if (this._disabled) {
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
return this._fastfs.build().then(
|
||||||
|
() => {
|
||||||
|
const activity = this._activity;
|
||||||
|
let processAsset_DEPRECATEDActivity;
|
||||||
|
if (activity) {
|
||||||
|
processAsset_DEPRECATEDActivity = activity.startEvent(
|
||||||
|
'Building (deprecated) Asset Map',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
this._fastfs.findFilesByExts(this._assetExts).forEach(
|
||||||
|
file => this._processAsset(file)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (activity) {
|
||||||
|
activity.endEvent(processAsset_DEPRECATEDActivity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve(fromModule, toModuleName) {
|
||||||
|
if (this._disabled) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const assetMatch = toModuleName.match(/^image!(.+)/);
|
||||||
|
if (assetMatch && assetMatch[1]) {
|
||||||
|
if (!this._map[assetMatch[1]]) {
|
||||||
|
debug('WARINING: Cannot find asset:', assetMatch[1]);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return this._map[assetMatch[1]];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_processAsset(file) {
|
||||||
|
const ext = this._helpers.extname(file);
|
||||||
|
if (this._assetExts.indexOf(ext) !== -1) {
|
||||||
|
const name = assetName(file, ext);
|
||||||
|
if (this._map[name] != null) {
|
||||||
|
debug('Conflicting assets', name);
|
||||||
|
}
|
||||||
|
|
||||||
|
this._map[name] = new AssetModule_DEPRECATED({ file }, this._platforms);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_processFileChange(type, filePath, root, fstat) {
|
||||||
|
const name = assetName(filePath);
|
||||||
|
if (type === 'change' || type === 'delete') {
|
||||||
|
delete this._map[name];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type === 'change' || type === 'add') {
|
||||||
|
this._processAsset(path.join(root, filePath));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function assetName(file, ext) {
|
||||||
|
return path.basename(file, '.' + ext).replace(/@[\d\.]+x/, '');
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = DeprecatedAssetMap;
|
|
@ -0,0 +1,147 @@
|
||||||
|
/**
|
||||||
|
* 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 path = require('../fastpath');
|
||||||
|
const getPlatformExtension = require('../lib/getPlatformExtension');
|
||||||
|
|
||||||
|
const GENERIC_PLATFORM = 'generic';
|
||||||
|
const NATIVE_PLATFORM = 'native';
|
||||||
|
const PACKAGE_JSON = path.sep + 'package.json';
|
||||||
|
|
||||||
|
class HasteMap {
|
||||||
|
constructor({
|
||||||
|
extensions,
|
||||||
|
fastfs,
|
||||||
|
moduleCache,
|
||||||
|
preferNativePlatform,
|
||||||
|
helpers,
|
||||||
|
platforms,
|
||||||
|
}) {
|
||||||
|
this._extensions = extensions;
|
||||||
|
this._fastfs = fastfs;
|
||||||
|
this._moduleCache = moduleCache;
|
||||||
|
this._preferNativePlatform = preferNativePlatform;
|
||||||
|
this._helpers = helpers;
|
||||||
|
this._platforms = platforms;
|
||||||
|
}
|
||||||
|
|
||||||
|
build() {
|
||||||
|
this._map = Object.create(null);
|
||||||
|
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) {
|
||||||
|
return Promise.resolve().then(() => {
|
||||||
|
/*eslint no-labels: 0 */
|
||||||
|
if (type === 'delete' || type === 'change') {
|
||||||
|
loop: for (const name in this._map) {
|
||||||
|
const modulesMap = this._map[name];
|
||||||
|
for (const platform in modulesMap) {
|
||||||
|
const module = modulesMap[platform];
|
||||||
|
if (module.path === absPath) {
|
||||||
|
delete modulesMap[platform];
|
||||||
|
break loop;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type === 'delete') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this._extensions.indexOf(this._helpers.extname(absPath)) !== -1) {
|
||||||
|
if (path.basename(absPath) === 'package.json') {
|
||||||
|
return this._processHastePackage(absPath);
|
||||||
|
} else {
|
||||||
|
return this._processHasteModule(absPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
getModule(name, platform = null) {
|
||||||
|
const modulesMap = this._map[name];
|
||||||
|
if (modulesMap == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If platform is 'ios', we prefer .ios.js to .native.js which we prefer to
|
||||||
|
// a plain .js file.
|
||||||
|
let module = undefined;
|
||||||
|
if (module == null && platform != null) {
|
||||||
|
module = modulesMap[platform];
|
||||||
|
}
|
||||||
|
if (module == null && this._preferNativePlatform) {
|
||||||
|
module = modulesMap[NATIVE_PLATFORM];
|
||||||
|
}
|
||||||
|
if (module == null) {
|
||||||
|
module = modulesMap[GENERIC_PLATFORM];
|
||||||
|
}
|
||||||
|
return module;
|
||||||
|
}
|
||||||
|
|
||||||
|
_processHasteModule(file) {
|
||||||
|
const module = this._moduleCache.getModule(file);
|
||||||
|
return module.isHaste().then(
|
||||||
|
isHaste => isHaste && module.getName()
|
||||||
|
.then(name => this._updateHasteMap(name, module))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
_processHastePackage(file) {
|
||||||
|
file = path.resolve(file);
|
||||||
|
const p = this._moduleCache.getPackage(file);
|
||||||
|
return p.isHaste()
|
||||||
|
.then(isHaste => isHaste && p.getName()
|
||||||
|
.then(name => this._updateHasteMap(name, p)))
|
||||||
|
.catch(e => {
|
||||||
|
if (e instanceof SyntaxError) {
|
||||||
|
// Malformed package.json.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
throw e;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
_updateHasteMap(name, mod) {
|
||||||
|
if (this._map[name] == null) {
|
||||||
|
this._map[name] = Object.create(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
const moduleMap = this._map[name];
|
||||||
|
const modulePlatform = getPlatformExtension(mod.path, this._platforms) || GENERIC_PLATFORM;
|
||||||
|
const existingModule = moduleMap[modulePlatform];
|
||||||
|
|
||||||
|
if (existingModule && existingModule.path !== mod.path) {
|
||||||
|
throw new Error(
|
||||||
|
`@providesModule naming collision:\n` +
|
||||||
|
` Duplicate module name: ${name}\n` +
|
||||||
|
` Paths: ${mod.path} collides with ${existingModule.path}\n\n` +
|
||||||
|
`This error is caused by a @providesModule declaration ` +
|
||||||
|
`with the same name across two different files.`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
moduleMap[modulePlatform] = mod;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = HasteMap;
|
|
@ -0,0 +1,529 @@
|
||||||
|
/**
|
||||||
|
* 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 AsyncTaskGroup = require('../lib/AsyncTaskGroup');
|
||||||
|
const MapWithDefaults = require('../lib/MapWithDefaults');
|
||||||
|
const debug = require('debug')('ReactNativePackager:DependencyGraph');
|
||||||
|
const util = require('util');
|
||||||
|
const path = require('../fastpath');
|
||||||
|
const realPath = require('path');
|
||||||
|
const isAbsolutePath = require('absolute-path');
|
||||||
|
const getAssetDataFromName = require('../lib/getAssetDataFromName');
|
||||||
|
|
||||||
|
class ResolutionRequest {
|
||||||
|
constructor({
|
||||||
|
platform,
|
||||||
|
platforms,
|
||||||
|
preferNativePlatform,
|
||||||
|
entryPath,
|
||||||
|
hasteMap,
|
||||||
|
deprecatedAssetMap,
|
||||||
|
helpers,
|
||||||
|
moduleCache,
|
||||||
|
fastfs,
|
||||||
|
shouldThrowOnUnresolvedErrors,
|
||||||
|
extraNodeModules,
|
||||||
|
}) {
|
||||||
|
this._platform = platform;
|
||||||
|
this._platforms = platforms;
|
||||||
|
this._preferNativePlatform = preferNativePlatform;
|
||||||
|
this._entryPath = entryPath;
|
||||||
|
this._hasteMap = hasteMap;
|
||||||
|
this._deprecatedAssetMap = deprecatedAssetMap;
|
||||||
|
this._helpers = helpers;
|
||||||
|
this._moduleCache = moduleCache;
|
||||||
|
this._fastfs = fastfs;
|
||||||
|
this._shouldThrowOnUnresolvedErrors = shouldThrowOnUnresolvedErrors;
|
||||||
|
this._extraNodeModules = extraNodeModules;
|
||||||
|
this._resetResolutionCache();
|
||||||
|
}
|
||||||
|
|
||||||
|
_tryResolve(action, secondaryAction) {
|
||||||
|
return action().catch((error) => {
|
||||||
|
if (error.type !== 'UnableToResolveError') {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
return secondaryAction();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
resolveDependency(fromModule, toModuleName) {
|
||||||
|
const resHash = resolutionHash(fromModule.path, toModuleName);
|
||||||
|
|
||||||
|
if (this._immediateResolutionCache[resHash]) {
|
||||||
|
return Promise.resolve(this._immediateResolutionCache[resHash]);
|
||||||
|
}
|
||||||
|
|
||||||
|
const asset_DEPRECATED = this._deprecatedAssetMap.resolve(
|
||||||
|
fromModule,
|
||||||
|
toModuleName
|
||||||
|
);
|
||||||
|
if (asset_DEPRECATED) {
|
||||||
|
return Promise.resolve(asset_DEPRECATED);
|
||||||
|
}
|
||||||
|
|
||||||
|
const cacheResult = (result) => {
|
||||||
|
this._immediateResolutionCache[resHash] = result;
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
const forgive = (error) => {
|
||||||
|
if (
|
||||||
|
error.type !== 'UnableToResolveError' ||
|
||||||
|
this._shouldThrowOnUnresolvedErrors(this._entryPath, this._platform)
|
||||||
|
) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
|
debug(
|
||||||
|
'Unable to resolve module %s from %s',
|
||||||
|
toModuleName,
|
||||||
|
fromModule.path
|
||||||
|
);
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!this._helpers.isNodeModulesDir(fromModule.path)
|
||||||
|
&& !(isRelativeImport(toModuleName) || isAbsolutePath(toModuleName))) {
|
||||||
|
return this._tryResolve(
|
||||||
|
() => this._resolveHasteDependency(fromModule, toModuleName),
|
||||||
|
() => this._resolveNodeDependency(fromModule, toModuleName)
|
||||||
|
).then(
|
||||||
|
cacheResult,
|
||||||
|
forgive,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this._resolveNodeDependency(fromModule, toModuleName)
|
||||||
|
.then(
|
||||||
|
cacheResult,
|
||||||
|
forgive,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
getOrderedDependencies({
|
||||||
|
response,
|
||||||
|
mocksPattern,
|
||||||
|
transformOptions,
|
||||||
|
onProgress,
|
||||||
|
recursive = true,
|
||||||
|
}) {
|
||||||
|
return this._getAllMocks(mocksPattern).then(allMocks => {
|
||||||
|
const entry = this._moduleCache.getModule(this._entryPath);
|
||||||
|
const mocks = Object.create(null);
|
||||||
|
|
||||||
|
response.pushDependency(entry);
|
||||||
|
let totalModules = 1;
|
||||||
|
let finishedModules = 0;
|
||||||
|
|
||||||
|
const resolveDependencies = module =>
|
||||||
|
module.getDependencies(transformOptions)
|
||||||
|
.then(dependencyNames =>
|
||||||
|
Promise.all(
|
||||||
|
dependencyNames.map(name => this.resolveDependency(module, name))
|
||||||
|
).then(dependencies => [dependencyNames, dependencies])
|
||||||
|
);
|
||||||
|
|
||||||
|
const addMockDependencies = !allMocks
|
||||||
|
? (module, result) => result
|
||||||
|
: (module, [dependencyNames, dependencies]) => {
|
||||||
|
const list = [module.getName()];
|
||||||
|
const pkg = module.getPackage();
|
||||||
|
if (pkg) {
|
||||||
|
list.push(pkg.getName());
|
||||||
|
}
|
||||||
|
return Promise.all(list).then(names => {
|
||||||
|
names.forEach(name => {
|
||||||
|
if (allMocks[name] && !mocks[name]) {
|
||||||
|
const mockModule = this._moduleCache.getModule(allMocks[name]);
|
||||||
|
dependencyNames.push(name);
|
||||||
|
dependencies.push(mockModule);
|
||||||
|
mocks[name] = allMocks[name];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return [dependencyNames, dependencies];
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const collectedDependencies = new MapWithDefaults(module => collect(module));
|
||||||
|
const crawlDependencies = (mod, [depNames, dependencies]) => {
|
||||||
|
const filteredPairs = [];
|
||||||
|
|
||||||
|
dependencies.forEach((modDep, i) => {
|
||||||
|
const name = depNames[i];
|
||||||
|
if (modDep == null) {
|
||||||
|
// It is possible to require mocks that don't have a real
|
||||||
|
// module backing them. If a dependency cannot be found but there
|
||||||
|
// exists a mock with the desired ID, resolve it and add it as
|
||||||
|
// a dependency.
|
||||||
|
if (allMocks && allMocks[name] && !mocks[name]) {
|
||||||
|
const mockModule = this._moduleCache.getModule(allMocks[name]);
|
||||||
|
mocks[name] = allMocks[name];
|
||||||
|
return filteredPairs.push([name, mockModule]);
|
||||||
|
}
|
||||||
|
|
||||||
|
debug(
|
||||||
|
'WARNING: Cannot find required module `%s` from module `%s`',
|
||||||
|
name,
|
||||||
|
mod.path
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return filteredPairs.push([name, modDep]);
|
||||||
|
});
|
||||||
|
|
||||||
|
response.setResolvedDependencyPairs(mod, filteredPairs);
|
||||||
|
|
||||||
|
const dependencyModules = filteredPairs.map(([, m]) => m);
|
||||||
|
const newDependencies =
|
||||||
|
dependencyModules.filter(m => !collectedDependencies.has(m));
|
||||||
|
|
||||||
|
if (onProgress) {
|
||||||
|
finishedModules += 1;
|
||||||
|
totalModules += newDependencies.length;
|
||||||
|
onProgress(finishedModules, totalModules);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (recursive) {
|
||||||
|
// doesn't block the return of this function invocation, but defers
|
||||||
|
// the resulution of collectionsInProgress.done.then(…)
|
||||||
|
dependencyModules
|
||||||
|
.forEach(dependency => collectedDependencies.get(dependency));
|
||||||
|
}
|
||||||
|
return dependencyModules;
|
||||||
|
};
|
||||||
|
|
||||||
|
const collectionsInProgress = new AsyncTaskGroup();
|
||||||
|
function collect(module) {
|
||||||
|
collectionsInProgress.start(module);
|
||||||
|
const result = resolveDependencies(module)
|
||||||
|
.then(result => addMockDependencies(module, result))
|
||||||
|
.then(result => crawlDependencies(module, result));
|
||||||
|
const end = () => collectionsInProgress.end(module);
|
||||||
|
result.then(end, end);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Promise.all([
|
||||||
|
// kicks off recursive dependency discovery, but doesn't block until it's done
|
||||||
|
collectedDependencies.get(entry),
|
||||||
|
|
||||||
|
// resolves when there are no more modules resolving dependencies
|
||||||
|
collectionsInProgress.done,
|
||||||
|
]).then(([rootDependencies]) => {
|
||||||
|
return Promise.all(
|
||||||
|
Array.from(collectedDependencies, resolveKeyWithPromise)
|
||||||
|
).then(moduleToDependenciesPairs =>
|
||||||
|
[rootDependencies, new MapWithDefaults(() => [], moduleToDependenciesPairs)]
|
||||||
|
);
|
||||||
|
}).then(([rootDependencies, moduleDependencies]) => {
|
||||||
|
// serialize dependencies, and make sure that every single one is only
|
||||||
|
// included once
|
||||||
|
const seen = new Set([entry]);
|
||||||
|
function traverse(dependencies) {
|
||||||
|
dependencies.forEach(dependency => {
|
||||||
|
if (seen.has(dependency)) { return; }
|
||||||
|
|
||||||
|
seen.add(dependency);
|
||||||
|
response.pushDependency(dependency);
|
||||||
|
traverse(moduleDependencies.get(dependency));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
traverse(rootDependencies);
|
||||||
|
response.setMocks(mocks);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
_getAllMocks(pattern) {
|
||||||
|
// Take all mocks in all the roots into account. This is necessary
|
||||||
|
// because currently mocks are global: any module can be mocked by
|
||||||
|
// any mock in the system.
|
||||||
|
let mocks = null;
|
||||||
|
if (pattern) {
|
||||||
|
mocks = Object.create(null);
|
||||||
|
this._fastfs.matchFilesByPattern(pattern).forEach(file =>
|
||||||
|
mocks[path.basename(file, path.extname(file))] = file
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return Promise.resolve(mocks);
|
||||||
|
}
|
||||||
|
|
||||||
|
_resolveHasteDependency(fromModule, toModuleName) {
|
||||||
|
toModuleName = normalizePath(toModuleName);
|
||||||
|
|
||||||
|
let p = fromModule.getPackage();
|
||||||
|
if (p) {
|
||||||
|
p = p.redirectRequire(toModuleName);
|
||||||
|
} else {
|
||||||
|
p = Promise.resolve(toModuleName);
|
||||||
|
}
|
||||||
|
|
||||||
|
return p.then((realModuleName) => {
|
||||||
|
let dep = this._hasteMap.getModule(realModuleName, this._platform);
|
||||||
|
if (dep && dep.type === 'Module') {
|
||||||
|
return dep;
|
||||||
|
}
|
||||||
|
|
||||||
|
let packageName = realModuleName;
|
||||||
|
while (packageName && packageName !== '.') {
|
||||||
|
dep = this._hasteMap.getModule(packageName, this._platform);
|
||||||
|
if (dep && dep.type === 'Package') {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
packageName = path.dirname(packageName);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dep && dep.type === 'Package') {
|
||||||
|
const potentialModulePath = path.join(
|
||||||
|
dep.root,
|
||||||
|
path.relative(packageName, realModuleName)
|
||||||
|
);
|
||||||
|
return this._tryResolve(
|
||||||
|
() => this._loadAsFile(
|
||||||
|
potentialModulePath,
|
||||||
|
fromModule,
|
||||||
|
toModuleName,
|
||||||
|
),
|
||||||
|
() => this._loadAsDir(potentialModulePath, fromModule, toModuleName),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new UnableToResolveError(
|
||||||
|
fromModule,
|
||||||
|
toModuleName,
|
||||||
|
'Unable to resolve dependency',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
_redirectRequire(fromModule, modulePath) {
|
||||||
|
return Promise.resolve(fromModule.getPackage()).then(p => {
|
||||||
|
if (p) {
|
||||||
|
return p.redirectRequire(modulePath);
|
||||||
|
}
|
||||||
|
return modulePath;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
_resolveFileOrDir(fromModule, toModuleName) {
|
||||||
|
const potentialModulePath = isAbsolutePath(toModuleName) ?
|
||||||
|
toModuleName :
|
||||||
|
path.join(path.dirname(fromModule.path), toModuleName);
|
||||||
|
|
||||||
|
return this._redirectRequire(fromModule, potentialModulePath).then(
|
||||||
|
realModuleName => {
|
||||||
|
if (realModuleName === false) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this._tryResolve(
|
||||||
|
() => this._loadAsFile(realModuleName, fromModule, toModuleName),
|
||||||
|
() => this._loadAsDir(realModuleName, fromModule, toModuleName)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
_resolveNodeDependency(fromModule, toModuleName) {
|
||||||
|
if (isRelativeImport(toModuleName) || isAbsolutePath(toModuleName)) {
|
||||||
|
return this._resolveFileOrDir(fromModule, toModuleName);
|
||||||
|
} else {
|
||||||
|
return this._redirectRequire(fromModule, toModuleName).then(
|
||||||
|
realModuleName => {
|
||||||
|
// exclude
|
||||||
|
if (realModuleName === false) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isRelativeImport(realModuleName) || isAbsolutePath(realModuleName)) {
|
||||||
|
// derive absolute path /.../node_modules/fromModuleDir/realModuleName
|
||||||
|
const fromModuleParentIdx = fromModule.path.lastIndexOf('node_modules/') + 13;
|
||||||
|
const fromModuleDir = fromModule.path.slice(0, fromModule.path.indexOf('/', fromModuleParentIdx));
|
||||||
|
const absPath = path.join(fromModuleDir, realModuleName);
|
||||||
|
return this._resolveFileOrDir(fromModule, absPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
const searchQueue = [];
|
||||||
|
for (let currDir = path.dirname(fromModule.path);
|
||||||
|
currDir !== realPath.parse(fromModule.path).root;
|
||||||
|
currDir = path.dirname(currDir)) {
|
||||||
|
searchQueue.push(
|
||||||
|
path.join(currDir, 'node_modules', realModuleName)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this._extraNodeModules) {
|
||||||
|
const bits = toModuleName.split('/');
|
||||||
|
const packageName = bits[0];
|
||||||
|
if (this._extraNodeModules[packageName]) {
|
||||||
|
bits[0] = this._extraNodeModules[packageName];
|
||||||
|
searchQueue.push(path.join.apply(path, bits));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let p = Promise.reject(new UnableToResolveError(
|
||||||
|
fromModule,
|
||||||
|
toModuleName,
|
||||||
|
'Node module not found',
|
||||||
|
));
|
||||||
|
searchQueue.forEach(potentialModulePath => {
|
||||||
|
p = this._tryResolve(
|
||||||
|
() => this._tryResolve(
|
||||||
|
() => p,
|
||||||
|
() => this._loadAsFile(potentialModulePath, fromModule, toModuleName),
|
||||||
|
),
|
||||||
|
() => this._loadAsDir(potentialModulePath, fromModule, toModuleName)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
return p;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_loadAsFile(potentialModulePath, fromModule, toModule) {
|
||||||
|
return Promise.resolve().then(() => {
|
||||||
|
if (this._helpers.isAssetFile(potentialModulePath)) {
|
||||||
|
const dirname = path.dirname(potentialModulePath);
|
||||||
|
if (!this._fastfs.dirExists(dirname)) {
|
||||||
|
throw new UnableToResolveError(
|
||||||
|
fromModule,
|
||||||
|
toModule,
|
||||||
|
`Directory ${dirname} doesn't exist`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const {name, type} = getAssetDataFromName(potentialModulePath, this._platforms);
|
||||||
|
|
||||||
|
let pattern = '^' + name + '(@[\\d\\.]+x)?';
|
||||||
|
if (this._platform != null) {
|
||||||
|
pattern += '(\\.' + this._platform + ')?';
|
||||||
|
}
|
||||||
|
pattern += '\\.' + type;
|
||||||
|
|
||||||
|
// We arbitrarly grab the first one, because scale selection
|
||||||
|
// will happen somewhere
|
||||||
|
const [assetFile] = this._fastfs.matches(
|
||||||
|
dirname,
|
||||||
|
new RegExp(pattern)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (assetFile) {
|
||||||
|
return this._moduleCache.getAssetModule(assetFile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let file;
|
||||||
|
if (this._fastfs.fileExists(potentialModulePath)) {
|
||||||
|
file = potentialModulePath;
|
||||||
|
} else if (this._platform != null &&
|
||||||
|
this._fastfs.fileExists(potentialModulePath + '.' + this._platform + '.js')) {
|
||||||
|
file = potentialModulePath + '.' + this._platform + '.js';
|
||||||
|
} else if (this._preferNativePlatform &&
|
||||||
|
this._fastfs.fileExists(potentialModulePath + '.native.js')) {
|
||||||
|
file = potentialModulePath + '.native.js';
|
||||||
|
} else if (this._fastfs.fileExists(potentialModulePath + '.js')) {
|
||||||
|
file = potentialModulePath + '.js';
|
||||||
|
} else if (this._fastfs.fileExists(potentialModulePath + '.json')) {
|
||||||
|
file = potentialModulePath + '.json';
|
||||||
|
} else {
|
||||||
|
throw new UnableToResolveError(
|
||||||
|
fromModule,
|
||||||
|
toModule,
|
||||||
|
`File ${potentialModulePath} doesnt exist`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this._moduleCache.getModule(file);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
_loadAsDir(potentialDirPath, fromModule, toModule) {
|
||||||
|
return Promise.resolve().then(() => {
|
||||||
|
if (!this._fastfs.dirExists(potentialDirPath)) {
|
||||||
|
throw new UnableToResolveError(
|
||||||
|
fromModule,
|
||||||
|
toModule,
|
||||||
|
`Unable to find this module in its module map or any of the node_modules directories under ${potentialDirPath} and its parent directories
|
||||||
|
|
||||||
|
This might be related to https://github.com/facebook/react-native/issues/4968
|
||||||
|
To resolve try the following:
|
||||||
|
1. Clear watchman watches: \`watchman watch-del-all\`.
|
||||||
|
2. Delete the \`node_modules\` folder: \`rm -rf node_modules && npm install\`.
|
||||||
|
3. Reset packager cache: \`rm -fr $TMPDIR/react-*\` or \`npm start -- --reset-cache\`.`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const packageJsonPath = path.join(potentialDirPath, 'package.json');
|
||||||
|
if (this._fastfs.fileExists(packageJsonPath)) {
|
||||||
|
return this._moduleCache.getPackage(packageJsonPath)
|
||||||
|
.getMain().then(
|
||||||
|
(main) => this._tryResolve(
|
||||||
|
() => this._loadAsFile(main, fromModule, toModule),
|
||||||
|
() => this._loadAsDir(main, fromModule, toModule)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this._loadAsFile(
|
||||||
|
path.join(potentialDirPath, 'index'),
|
||||||
|
fromModule,
|
||||||
|
toModule,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
_resetResolutionCache() {
|
||||||
|
this._immediateResolutionCache = Object.create(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function resolutionHash(modulePath, depName) {
|
||||||
|
return `${path.resolve(modulePath)}:${depName}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function UnableToResolveError(fromModule, toModule, message) {
|
||||||
|
Error.call(this);
|
||||||
|
Error.captureStackTrace(this, this.constructor);
|
||||||
|
this.message = util.format(
|
||||||
|
'Unable to resolve module %s from %s: %s',
|
||||||
|
toModule,
|
||||||
|
fromModule.path,
|
||||||
|
message,
|
||||||
|
);
|
||||||
|
this.type = this.name = 'UnableToResolveError';
|
||||||
|
}
|
||||||
|
|
||||||
|
util.inherits(UnableToResolveError, Error);
|
||||||
|
|
||||||
|
function normalizePath(modulePath) {
|
||||||
|
if (path.sep === '/') {
|
||||||
|
modulePath = path.normalize(modulePath);
|
||||||
|
} else if (path.posix) {
|
||||||
|
modulePath = path.posix.normalize(modulePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
return modulePath.replace(/\/$/, '');
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolveKeyWithPromise([key, promise]) {
|
||||||
|
return promise.then(value => [key, value]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function isRelativeImport(path) {
|
||||||
|
return /^[.][.]?[/]/.test(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = ResolutionRequest;
|
|
@ -0,0 +1,97 @@
|
||||||
|
/**
|
||||||
|
* 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';
|
||||||
|
|
||||||
|
class ResolutionResponse {
|
||||||
|
constructor({transformOptions}) {
|
||||||
|
this.transformOptions = transformOptions;
|
||||||
|
this.dependencies = [];
|
||||||
|
this.mainModuleId = null;
|
||||||
|
this.mocks = null;
|
||||||
|
this.numPrependedDependencies = 0;
|
||||||
|
this._mappings = Object.create(null);
|
||||||
|
this._finalized = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
copy(properties) {
|
||||||
|
const {
|
||||||
|
dependencies = this.dependencies,
|
||||||
|
mainModuleId = this.mainModuleId,
|
||||||
|
mocks = this.mocks,
|
||||||
|
} = properties;
|
||||||
|
|
||||||
|
const numPrependedDependencies = dependencies === this.dependencies
|
||||||
|
? this.numPrependedDependencies : 0;
|
||||||
|
|
||||||
|
return Object.assign(
|
||||||
|
new this.constructor({transformOptions: this.transformOptions}),
|
||||||
|
this,
|
||||||
|
{
|
||||||
|
dependencies,
|
||||||
|
mainModuleId,
|
||||||
|
mocks,
|
||||||
|
numPrependedDependencies,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
_assertNotFinalized() {
|
||||||
|
if (this._finalized) {
|
||||||
|
throw new Error('Attempted to mutate finalized response.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_assertFinalized() {
|
||||||
|
if (!this._finalized) {
|
||||||
|
throw new Error('Attempted to access unfinalized response.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
finalize() {
|
||||||
|
return this._mainModule.getName().then(id => {
|
||||||
|
this.mainModuleId = id;
|
||||||
|
this._finalized = true;
|
||||||
|
return this;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
pushDependency(module) {
|
||||||
|
this._assertNotFinalized();
|
||||||
|
if (this.dependencies.length === 0) {
|
||||||
|
this._mainModule = module;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.dependencies.push(module);
|
||||||
|
}
|
||||||
|
|
||||||
|
prependDependency(module) {
|
||||||
|
this._assertNotFinalized();
|
||||||
|
this.dependencies.unshift(module);
|
||||||
|
this.numPrependedDependencies += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
setResolvedDependencyPairs(module, pairs) {
|
||||||
|
this._assertNotFinalized();
|
||||||
|
const hash = module.hash();
|
||||||
|
if (this._mappings[hash] == null) {
|
||||||
|
this._mappings[hash] = pairs;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setMocks(mocks) {
|
||||||
|
this.mocks = mocks;
|
||||||
|
}
|
||||||
|
|
||||||
|
getResolvedDependencyPairs(module) {
|
||||||
|
this._assertFinalized();
|
||||||
|
return this._mappings[module.hash()];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = ResolutionResponse;
|
|
@ -0,0 +1,83 @@
|
||||||
|
/**
|
||||||
|
* 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';
|
||||||
|
|
||||||
|
var docblockRe = /^\s*(\/\*\*(.|\r?\n)*?\*\/)/;
|
||||||
|
|
||||||
|
var ltrimRe = /^\s*/;
|
||||||
|
/**
|
||||||
|
* @param {String} contents
|
||||||
|
* @return {String}
|
||||||
|
*/
|
||||||
|
function extract(contents) {
|
||||||
|
var match = contents.match(docblockRe);
|
||||||
|
if (match) {
|
||||||
|
return match[0].replace(ltrimRe, '') || '';
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
var commentStartRe = /^\/\*\*/;
|
||||||
|
var commentEndRe = /\*\/$/;
|
||||||
|
var wsRe = /[\t ]+/g;
|
||||||
|
var stringStartRe = /(\r?\n|^) *\*/g;
|
||||||
|
var multilineRe =
|
||||||
|
/(?:^|\r?\n) *(@[^\r\n]*?) *\r?\n *([^@\r\n\s][^@\r\n]+?) *\r?\n/g;
|
||||||
|
var propertyRe = /(?:^|\r?\n) *@(\S+) *([^\r\n]*)/g;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {String} contents
|
||||||
|
* @return {Array}
|
||||||
|
*/
|
||||||
|
function parse(docblock) {
|
||||||
|
docblock = docblock
|
||||||
|
.replace(commentStartRe, '')
|
||||||
|
.replace(commentEndRe, '')
|
||||||
|
.replace(wsRe, ' ')
|
||||||
|
.replace(stringStartRe, '$1');
|
||||||
|
|
||||||
|
// Normalize multi-line directives
|
||||||
|
var prev = '';
|
||||||
|
while (prev !== docblock) {
|
||||||
|
prev = docblock;
|
||||||
|
docblock = docblock.replace(multilineRe, '\n$1 $2\n');
|
||||||
|
}
|
||||||
|
docblock = docblock.trim();
|
||||||
|
|
||||||
|
var result = [];
|
||||||
|
var match;
|
||||||
|
while ((match = propertyRe.exec(docblock))) {
|
||||||
|
result.push([match[1], match[2]]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Same as parse but returns an object of prop: value instead of array of paris
|
||||||
|
* If a property appers more than once the last one will be returned
|
||||||
|
*
|
||||||
|
* @param {String} contents
|
||||||
|
* @return {Object}
|
||||||
|
*/
|
||||||
|
function parseAsObject(docblock) {
|
||||||
|
var pairs = parse(docblock);
|
||||||
|
var result = {};
|
||||||
|
for (var i = 0; i < pairs.length; i++) {
|
||||||
|
result[pairs[i][0]] = pairs[i][1];
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
exports.extract = extract;
|
||||||
|
exports.parse = parse;
|
||||||
|
exports.parseAsObject = parseAsObject;
|
|
@ -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,74 @@
|
||||||
|
/**
|
||||||
|
* 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(() => {
|
||||||
|
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 = 120000;
|
||||||
|
|
||||||
|
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;
|
|
@ -0,0 +1,232 @@
|
||||||
|
/**
|
||||||
|
* 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 crypto = require('crypto');
|
||||||
|
const docblock = require('./DependencyGraph/docblock');
|
||||||
|
const isAbsolutePath = require('absolute-path');
|
||||||
|
const jsonStableStringify = require('json-stable-stringify');
|
||||||
|
const path = require('./fastpath');
|
||||||
|
const extractRequires = require('./lib/extractRequires');
|
||||||
|
|
||||||
|
class Module {
|
||||||
|
|
||||||
|
constructor({
|
||||||
|
file,
|
||||||
|
fastfs,
|
||||||
|
moduleCache,
|
||||||
|
cache,
|
||||||
|
extractor = extractRequires,
|
||||||
|
transformCode,
|
||||||
|
depGraphHelpers,
|
||||||
|
options,
|
||||||
|
}) {
|
||||||
|
if (!isAbsolutePath(file)) {
|
||||||
|
throw new Error('Expected file to be absolute path but got ' + file);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.path = file;
|
||||||
|
this.type = 'Module';
|
||||||
|
|
||||||
|
this._fastfs = fastfs;
|
||||||
|
this._moduleCache = moduleCache;
|
||||||
|
this._cache = cache;
|
||||||
|
this._extractor = extractor;
|
||||||
|
this._transformCode = transformCode;
|
||||||
|
this._depGraphHelpers = depGraphHelpers;
|
||||||
|
this._options = options;
|
||||||
|
}
|
||||||
|
|
||||||
|
isHaste() {
|
||||||
|
return this._cache.get(
|
||||||
|
this.path,
|
||||||
|
'isHaste',
|
||||||
|
() => this._readDocBlock().then(({id}) => !!id)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
getCode(transformOptions) {
|
||||||
|
return this.read(transformOptions).then(({code}) => code);
|
||||||
|
}
|
||||||
|
|
||||||
|
getMap(transformOptions) {
|
||||||
|
return this.read(transformOptions).then(({map}) => map);
|
||||||
|
}
|
||||||
|
|
||||||
|
getName() {
|
||||||
|
return this._cache.get(
|
||||||
|
this.path,
|
||||||
|
'name',
|
||||||
|
() => this._readDocBlock().then(({id}) => {
|
||||||
|
if (id) {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
const p = this.getPackage();
|
||||||
|
|
||||||
|
if (!p) {
|
||||||
|
// Name is full path
|
||||||
|
return this.path;
|
||||||
|
}
|
||||||
|
|
||||||
|
return p.getName()
|
||||||
|
.then(name => {
|
||||||
|
if (!name) {
|
||||||
|
return this.path;
|
||||||
|
}
|
||||||
|
|
||||||
|
return path.join(name, path.relative(p.root, this.path)).replace(/\\/g, '/');
|
||||||
|
});
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
getPackage() {
|
||||||
|
return this._moduleCache.getPackageForModule(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
getDependencies(transformOptions) {
|
||||||
|
return this.read(transformOptions).then(({dependencies}) => dependencies);
|
||||||
|
}
|
||||||
|
|
||||||
|
invalidate() {
|
||||||
|
this._cache.invalidate(this.path);
|
||||||
|
}
|
||||||
|
|
||||||
|
_parseDocBlock(docBlock) {
|
||||||
|
// Extract an id for the module if it's using @providesModule syntax
|
||||||
|
// and if it's NOT in node_modules (and not a whitelisted node_module).
|
||||||
|
// This handles the case where a project may have a dep that has @providesModule
|
||||||
|
// docblock comments, but doesn't want it to conflict with whitelisted @providesModule
|
||||||
|
// modules, such as react-haste, fbjs-haste, or react-native or with non-dependency,
|
||||||
|
// project-specific code that is using @providesModule.
|
||||||
|
const moduleDocBlock = docblock.parseAsObject(docBlock);
|
||||||
|
const provides = moduleDocBlock.providesModule || moduleDocBlock.provides;
|
||||||
|
|
||||||
|
const id = provides && !this._depGraphHelpers.isNodeModulesDir(this.path)
|
||||||
|
? /^\S+/.exec(provides)[0]
|
||||||
|
: undefined;
|
||||||
|
return {id, moduleDocBlock};
|
||||||
|
}
|
||||||
|
|
||||||
|
_readDocBlock(contentPromise) {
|
||||||
|
if (!this._docBlock) {
|
||||||
|
if (!contentPromise) {
|
||||||
|
contentPromise = this._fastfs.readWhile(this.path, whileInDocBlock);
|
||||||
|
}
|
||||||
|
this._docBlock = contentPromise
|
||||||
|
.then(docBlock => this._parseDocBlock(docBlock));
|
||||||
|
}
|
||||||
|
return this._docBlock;
|
||||||
|
}
|
||||||
|
|
||||||
|
read(transformOptions) {
|
||||||
|
return this._cache.get(
|
||||||
|
this.path,
|
||||||
|
cacheKey('moduleData', transformOptions),
|
||||||
|
() => {
|
||||||
|
const fileContentPromise = this._fastfs.readFile(this.path);
|
||||||
|
return Promise.all([
|
||||||
|
fileContentPromise,
|
||||||
|
this._readDocBlock(fileContentPromise),
|
||||||
|
]).then(([source, {id, moduleDocBlock}]) => {
|
||||||
|
// Ignore requires in JSON files or generated code. An example of this
|
||||||
|
// is prebuilt files like the SourceMap library.
|
||||||
|
const extern = this.isJSON() || 'extern' in moduleDocBlock;
|
||||||
|
if (extern) {
|
||||||
|
transformOptions = {...transformOptions, extern};
|
||||||
|
}
|
||||||
|
const transformCode = this._transformCode;
|
||||||
|
const codePromise = transformCode
|
||||||
|
? transformCode(this, source, transformOptions)
|
||||||
|
: Promise.resolve({code: source});
|
||||||
|
return codePromise.then(result => {
|
||||||
|
const {
|
||||||
|
code,
|
||||||
|
dependencies = extern ? [] : this._extractor(code).deps.sync,
|
||||||
|
} = result;
|
||||||
|
if (this._options && this._options.cacheTransformResults === false) {
|
||||||
|
return {dependencies};
|
||||||
|
} else {
|
||||||
|
return {...result, dependencies, id, source};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
hash() {
|
||||||
|
return `Module : ${this.path}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
isJSON() {
|
||||||
|
return path.extname(this.path) === '.json';
|
||||||
|
}
|
||||||
|
|
||||||
|
isAsset() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
isPolyfill() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
isAsset_DEPRECATED() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
toJSON() {
|
||||||
|
return {
|
||||||
|
hash: this.hash(),
|
||||||
|
isJSON: this.isJSON(),
|
||||||
|
isAsset: this.isAsset(),
|
||||||
|
isAsset_DEPRECATED: this.isAsset_DEPRECATED(),
|
||||||
|
type: this.type,
|
||||||
|
path: this.path,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function whileInDocBlock(chunk, i, result) {
|
||||||
|
// consume leading whitespace
|
||||||
|
if (!/\S/.test(result)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// check for start of doc block
|
||||||
|
if (!/^\s*\/(\*{2}|\*?$)/.test(result)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// check for end of doc block
|
||||||
|
return !/\*\//.test(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
// use weak map to speed up hash creation of known objects
|
||||||
|
const knownHashes = new WeakMap();
|
||||||
|
function stableObjectHash(object) {
|
||||||
|
let digest = knownHashes.get(object);
|
||||||
|
if (!digest) {
|
||||||
|
digest = crypto.createHash('md5')
|
||||||
|
.update(jsonStableStringify(object))
|
||||||
|
.digest('base64');
|
||||||
|
knownHashes.set(object, digest);
|
||||||
|
}
|
||||||
|
|
||||||
|
return digest;
|
||||||
|
}
|
||||||
|
|
||||||
|
function cacheKey(field, transformOptions) {
|
||||||
|
return transformOptions !== undefined
|
||||||
|
? stableObjectHash(transformOptions) + '\0' + field
|
||||||
|
: field;
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = Module;
|
|
@ -0,0 +1,123 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const AssetModule = require('./AssetModule');
|
||||||
|
const Package = require('./Package');
|
||||||
|
const Module = require('./Module');
|
||||||
|
const Polyfill = require('./Polyfill');
|
||||||
|
const path = require('./fastpath');
|
||||||
|
|
||||||
|
class ModuleCache {
|
||||||
|
|
||||||
|
constructor({
|
||||||
|
fastfs,
|
||||||
|
cache,
|
||||||
|
extractRequires,
|
||||||
|
transformCode,
|
||||||
|
depGraphHelpers,
|
||||||
|
assetDependencies,
|
||||||
|
moduleOptions,
|
||||||
|
}, platforms) {
|
||||||
|
this._moduleCache = Object.create(null);
|
||||||
|
this._packageCache = Object.create(null);
|
||||||
|
this._fastfs = fastfs;
|
||||||
|
this._cache = cache;
|
||||||
|
this._extractRequires = extractRequires;
|
||||||
|
this._transformCode = transformCode;
|
||||||
|
this._depGraphHelpers = depGraphHelpers;
|
||||||
|
this._platforms = platforms;
|
||||||
|
this._assetDependencies = assetDependencies;
|
||||||
|
this._moduleOptions = moduleOptions;
|
||||||
|
this._packageModuleMap = new WeakMap();
|
||||||
|
|
||||||
|
fastfs.on('change', this._processFileChange.bind(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
getModule(filePath) {
|
||||||
|
if (!this._moduleCache[filePath]) {
|
||||||
|
this._moduleCache[filePath] = new Module({
|
||||||
|
file: filePath,
|
||||||
|
fastfs: this._fastfs,
|
||||||
|
moduleCache: this,
|
||||||
|
cache: this._cache,
|
||||||
|
extractor: this._extractRequires,
|
||||||
|
transformCode: this._transformCode,
|
||||||
|
depGraphHelpers: this._depGraphHelpers,
|
||||||
|
options: this._moduleOptions,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return this._moduleCache[filePath];
|
||||||
|
}
|
||||||
|
|
||||||
|
getAllModules() {
|
||||||
|
return this._moduleCache;
|
||||||
|
}
|
||||||
|
|
||||||
|
getAssetModule(filePath) {
|
||||||
|
if (!this._moduleCache[filePath]) {
|
||||||
|
this._moduleCache[filePath] = new AssetModule({
|
||||||
|
file: filePath,
|
||||||
|
fastfs: this._fastfs,
|
||||||
|
moduleCache: this,
|
||||||
|
cache: this._cache,
|
||||||
|
dependencies: this._assetDependencies,
|
||||||
|
}, this._platforms);
|
||||||
|
}
|
||||||
|
return this._moduleCache[filePath];
|
||||||
|
}
|
||||||
|
|
||||||
|
getPackage(filePath) {
|
||||||
|
if (!this._packageCache[filePath]) {
|
||||||
|
this._packageCache[filePath] = new Package({
|
||||||
|
file: filePath,
|
||||||
|
fastfs: this._fastfs,
|
||||||
|
cache: this._cache,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return this._packageCache[filePath];
|
||||||
|
}
|
||||||
|
|
||||||
|
getPackageForModule(module) {
|
||||||
|
if (this._packageModuleMap.has(module)) {
|
||||||
|
const packagePath = this._packageModuleMap.get(module);
|
||||||
|
if (this._packageCache[packagePath]) {
|
||||||
|
return this._packageCache[packagePath];
|
||||||
|
} else {
|
||||||
|
this._packageModuleMap.delete(module);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const packagePath = this._fastfs.closest(module.path, 'package.json');
|
||||||
|
if (!packagePath) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._packageModuleMap.set(module, packagePath);
|
||||||
|
return this.getPackage(packagePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
createPolyfill({file}) {
|
||||||
|
return new Polyfill({
|
||||||
|
file,
|
||||||
|
cache: this._cache,
|
||||||
|
depGraphHelpers: this._depGraphHelpers,
|
||||||
|
fastfs: this._fastfs,
|
||||||
|
moduleCache: this,
|
||||||
|
transformCode: this._transformCode,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
_processFileChange(type, filePath, root) {
|
||||||
|
const absPath = path.join(root, filePath);
|
||||||
|
|
||||||
|
if (this._moduleCache[absPath]) {
|
||||||
|
this._moduleCache[absPath].invalidate();
|
||||||
|
delete this._moduleCache[absPath];
|
||||||
|
}
|
||||||
|
if (this._packageCache[absPath]) {
|
||||||
|
this._packageCache[absPath].invalidate();
|
||||||
|
delete this._packageCache[absPath];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = ModuleCache;
|
|
@ -0,0 +1,134 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const isAbsolutePath = require('absolute-path');
|
||||||
|
const path = require('./fastpath');
|
||||||
|
|
||||||
|
class Package {
|
||||||
|
|
||||||
|
constructor({ file, fastfs, cache }) {
|
||||||
|
this.path = path.resolve(file);
|
||||||
|
this.root = path.dirname(this.path);
|
||||||
|
this._fastfs = fastfs;
|
||||||
|
this.type = 'Package';
|
||||||
|
this._cache = cache;
|
||||||
|
}
|
||||||
|
|
||||||
|
getMain() {
|
||||||
|
return this.read().then(json => {
|
||||||
|
var replacements = getReplacements(json);
|
||||||
|
if (typeof replacements === 'string') {
|
||||||
|
return path.join(this.root, replacements);
|
||||||
|
}
|
||||||
|
|
||||||
|
let main = json.main || 'index';
|
||||||
|
|
||||||
|
if (replacements && typeof replacements === 'object') {
|
||||||
|
main = replacements[main] ||
|
||||||
|
replacements[main + '.js'] ||
|
||||||
|
replacements[main + '.json'] ||
|
||||||
|
replacements[main.replace(/(\.js|\.json)$/, '')] ||
|
||||||
|
main;
|
||||||
|
}
|
||||||
|
|
||||||
|
return path.join(this.root, main);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
isHaste() {
|
||||||
|
return this._cache.get(this.path, 'package-haste', () =>
|
||||||
|
this.read().then(json => !!json.name)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
getName() {
|
||||||
|
return this._cache.get(this.path, 'package-name', () =>
|
||||||
|
this.read().then(json => json.name)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
invalidate() {
|
||||||
|
this._cache.invalidate(this.path);
|
||||||
|
}
|
||||||
|
|
||||||
|
redirectRequire(name) {
|
||||||
|
return this.read().then(json => {
|
||||||
|
var replacements = getReplacements(json);
|
||||||
|
|
||||||
|
if (!replacements || typeof replacements !== 'object') {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (name[0] !== '/') {
|
||||||
|
const replacement = replacements[name];
|
||||||
|
// support exclude with "someDependency": false
|
||||||
|
return replacement === false
|
||||||
|
? false
|
||||||
|
: replacement || name;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isAbsolutePath(name)) {
|
||||||
|
throw new Error(`Expected ${name} to be absolute path`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const relPath = './' + path.relative(this.root, name);
|
||||||
|
let redirect = replacements[relPath];
|
||||||
|
|
||||||
|
// false is a valid value
|
||||||
|
if (redirect == null) {
|
||||||
|
redirect = replacements[relPath + '.js'];
|
||||||
|
if (redirect == null) {
|
||||||
|
redirect = replacements[relPath + '.json'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// support exclude with "./someFile": false
|
||||||
|
if (redirect === false) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (redirect) {
|
||||||
|
return path.join(
|
||||||
|
this.root,
|
||||||
|
redirect
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return name;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
read() {
|
||||||
|
if (!this._reading) {
|
||||||
|
this._reading = this._fastfs.readFile(this.path)
|
||||||
|
.then(jsonStr => JSON.parse(jsonStr));
|
||||||
|
}
|
||||||
|
|
||||||
|
return this._reading;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getReplacements(pkg) {
|
||||||
|
let rn = pkg['react-native'];
|
||||||
|
let browser = pkg.browser;
|
||||||
|
if (rn == null) {
|
||||||
|
return browser;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (browser == null) {
|
||||||
|
return rn;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof rn === 'string') {
|
||||||
|
rn = { [pkg.main]: rn };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof browser === 'string') {
|
||||||
|
browser = { [pkg.main]: browser };
|
||||||
|
}
|
||||||
|
|
||||||
|
// merge with "browser" as default,
|
||||||
|
// "react-native" as override
|
||||||
|
return { ...browser, ...rn };
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = Package;
|
|
@ -0,0 +1,37 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const Module = require('./Module');
|
||||||
|
|
||||||
|
class Polyfill extends Module {
|
||||||
|
constructor(options) {
|
||||||
|
super(options);
|
||||||
|
this._id = options.id;
|
||||||
|
this._dependencies = options.dependencies;
|
||||||
|
}
|
||||||
|
|
||||||
|
isHaste() {
|
||||||
|
return Promise.resolve(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
getName() {
|
||||||
|
return Promise.resolve(this._id);
|
||||||
|
}
|
||||||
|
|
||||||
|
getPackage() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
getDependencies() {
|
||||||
|
return Promise.resolve(this._dependencies);
|
||||||
|
}
|
||||||
|
|
||||||
|
isJSON() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
isPolyfill() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = Polyfill;
|
|
@ -0,0 +1,11 @@
|
||||||
|
/**
|
||||||
|
* 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 = () => () => {};
|
|
@ -0,0 +1,11 @@
|
||||||
|
/**
|
||||||
|
* 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 = require('graceful-fs');
|
|
@ -0,0 +1,211 @@
|
||||||
|
/**
|
||||||
|
* 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 fs = jest.genMockFromModule('fs');
|
||||||
|
const noop = () => {};
|
||||||
|
|
||||||
|
function asyncCallback(cb) {
|
||||||
|
return function() {
|
||||||
|
setImmediate(() => cb.apply(this, arguments));
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const mtime = {
|
||||||
|
getTime: () => Math.ceil(Math.random() * 10000000),
|
||||||
|
};
|
||||||
|
|
||||||
|
fs.realpath.mockImpl((filepath, callback) => {
|
||||||
|
callback = asyncCallback(callback);
|
||||||
|
let node;
|
||||||
|
try {
|
||||||
|
node = getToNode(filepath);
|
||||||
|
} catch (e) {
|
||||||
|
return callback(e);
|
||||||
|
}
|
||||||
|
if (node && typeof node === 'object' && node.SYMLINK != null) {
|
||||||
|
return callback(null, node.SYMLINK);
|
||||||
|
}
|
||||||
|
callback(null, filepath);
|
||||||
|
});
|
||||||
|
|
||||||
|
fs.readdirSync.mockImpl((filepath) => Object.keys(getToNode(filepath)));
|
||||||
|
|
||||||
|
fs.readdir.mockImpl((filepath, callback) => {
|
||||||
|
callback = asyncCallback(callback);
|
||||||
|
let node;
|
||||||
|
try {
|
||||||
|
node = getToNode(filepath);
|
||||||
|
if (node && typeof node === 'object' && node.SYMLINK != null) {
|
||||||
|
node = getToNode(node.SYMLINK);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
return callback(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(node && typeof node === 'object' && node.SYMLINK == null)) {
|
||||||
|
return callback(new Error(filepath + ' is not a directory.'));
|
||||||
|
}
|
||||||
|
|
||||||
|
callback(null, Object.keys(node));
|
||||||
|
});
|
||||||
|
|
||||||
|
fs.readFile.mockImpl(function(filepath, encoding, callback) {
|
||||||
|
callback = asyncCallback(callback);
|
||||||
|
if (arguments.length === 2) {
|
||||||
|
callback = encoding;
|
||||||
|
encoding = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
let node;
|
||||||
|
try {
|
||||||
|
node = getToNode(filepath);
|
||||||
|
// dir check
|
||||||
|
if (node && typeof node === 'object' && node.SYMLINK == null) {
|
||||||
|
callback(new Error('Error readFile a dir: ' + filepath));
|
||||||
|
}
|
||||||
|
return callback(null, node);
|
||||||
|
} catch (e) {
|
||||||
|
return callback(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
fs.stat.mockImpl((filepath, callback) => {
|
||||||
|
callback = asyncCallback(callback);
|
||||||
|
let node;
|
||||||
|
try {
|
||||||
|
node = getToNode(filepath);
|
||||||
|
} catch (e) {
|
||||||
|
callback(e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (node.SYMLINK) {
|
||||||
|
fs.stat(node.SYMLINK, callback);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (node && typeof node === 'object') {
|
||||||
|
callback(null, {
|
||||||
|
isDirectory: () => true,
|
||||||
|
isSymbolicLink: () => false,
|
||||||
|
mtime,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
callback(null, {
|
||||||
|
isDirectory: () => false,
|
||||||
|
isSymbolicLink: () => false,
|
||||||
|
mtime,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
fs.statSync.mockImpl((filepath) => {
|
||||||
|
const node = getToNode(filepath);
|
||||||
|
|
||||||
|
if (node.SYMLINK) {
|
||||||
|
return fs.statSync(node.SYMLINK);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
isDirectory: () => node && typeof node === 'object',
|
||||||
|
isSymbolicLink: () => false,
|
||||||
|
mtime,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
fs.lstatSync.mockImpl((filepath) => {
|
||||||
|
const node = getToNode(filepath);
|
||||||
|
|
||||||
|
if (node.SYMLINK) {
|
||||||
|
return {
|
||||||
|
isDirectory: () => false,
|
||||||
|
isSymbolicLink: () => true,
|
||||||
|
mtime,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
isDirectory: () => node && typeof node === 'object',
|
||||||
|
isSymbolicLink: () => false,
|
||||||
|
mtime,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
fs.open.mockImpl(function(path) {
|
||||||
|
const callback = arguments[arguments.length - 1] || noop;
|
||||||
|
let data, error, fd;
|
||||||
|
try {
|
||||||
|
data = getToNode(path);
|
||||||
|
} catch (e) {
|
||||||
|
error = e;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error || data == null) {
|
||||||
|
error = Error(`ENOENT: no such file or directory, open ${path}`);
|
||||||
|
}
|
||||||
|
if (data != null) {
|
||||||
|
/* global Buffer: true */
|
||||||
|
fd = {buffer: new Buffer(data, 'utf8'), position: 0};
|
||||||
|
}
|
||||||
|
|
||||||
|
callback(error, fd);
|
||||||
|
});
|
||||||
|
|
||||||
|
fs.read.mockImpl((fd, buffer, writeOffset, length, position, callback = noop) => {
|
||||||
|
let bytesWritten;
|
||||||
|
try {
|
||||||
|
if (position == null || position < 0) {
|
||||||
|
({position} = fd);
|
||||||
|
}
|
||||||
|
bytesWritten = fd.buffer.copy(buffer, writeOffset, position, position + length);
|
||||||
|
fd.position = position + bytesWritten;
|
||||||
|
} catch (e) {
|
||||||
|
callback(Error('invalid argument'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
callback(null, bytesWritten, buffer);
|
||||||
|
});
|
||||||
|
|
||||||
|
fs.close.mockImpl((fd, callback = noop) => {
|
||||||
|
try {
|
||||||
|
fd.buffer = fs.position = undefined;
|
||||||
|
} catch (e) {
|
||||||
|
callback(Error('invalid argument'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
callback(null);
|
||||||
|
});
|
||||||
|
|
||||||
|
let filesystem;
|
||||||
|
|
||||||
|
fs.__setMockFilesystem = (object) => filesystem = object;
|
||||||
|
|
||||||
|
function getToNode(filepath) {
|
||||||
|
// Ignore the drive for Windows paths.
|
||||||
|
if (filepath.match(/^[a-zA-Z]:\\/)) {
|
||||||
|
filepath = filepath.substring(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
const parts = filepath.split(/[\/\\]/);
|
||||||
|
if (parts[0] !== '') {
|
||||||
|
throw new Error('Make sure all paths are absolute.');
|
||||||
|
}
|
||||||
|
let node = filesystem;
|
||||||
|
parts.slice(1).forEach((part) => {
|
||||||
|
if (node && node.SYMLINK) {
|
||||||
|
node = getToNode(node.SYMLINK);
|
||||||
|
}
|
||||||
|
node = node[part];
|
||||||
|
});
|
||||||
|
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = fs;
|
|
@ -0,0 +1,28 @@
|
||||||
|
/**
|
||||||
|
* 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.autoMockOff();
|
||||||
|
|
||||||
|
const AssetModule = require('../AssetModule');
|
||||||
|
|
||||||
|
describe('AssetModule:', () => {
|
||||||
|
const defaults = {file: '/arbitrary'};
|
||||||
|
|
||||||
|
pit('has no dependencies by default', () => {
|
||||||
|
return new AssetModule(defaults).getDependencies()
|
||||||
|
.then(deps => expect(deps).toEqual([]));
|
||||||
|
});
|
||||||
|
|
||||||
|
pit('can be parametrized with dependencies', () => {
|
||||||
|
const dependencies = ['arbitrary', 'dependencies'];
|
||||||
|
return new AssetModule({...defaults, dependencies}).getDependencies()
|
||||||
|
.then(deps => expect(deps).toEqual(dependencies));
|
||||||
|
});
|
||||||
|
});
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,502 @@
|
||||||
|
/**
|
||||||
|
* 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('absolute-path')
|
||||||
|
.dontMock('json-stable-stringify')
|
||||||
|
.dontMock('../fastfs')
|
||||||
|
.dontMock('../lib/extractRequires')
|
||||||
|
.dontMock('../lib/replacePatterns')
|
||||||
|
.dontMock('../DependencyGraph/docblock')
|
||||||
|
.dontMock('../Module');
|
||||||
|
|
||||||
|
jest
|
||||||
|
.mock('fs');
|
||||||
|
|
||||||
|
const Fastfs = require('../fastfs');
|
||||||
|
const Module = require('../Module');
|
||||||
|
const ModuleCache = require('../ModuleCache');
|
||||||
|
const DependencyGraphHelpers = require('../DependencyGraph/DependencyGraphHelpers');
|
||||||
|
const fs = require('graceful-fs');
|
||||||
|
|
||||||
|
const packageJson =
|
||||||
|
JSON.stringify({
|
||||||
|
name: 'arbitrary',
|
||||||
|
version: '1.0.0',
|
||||||
|
description: "A require('foo') story",
|
||||||
|
});
|
||||||
|
|
||||||
|
function mockFS(rootChildren) {
|
||||||
|
fs.__setMockFilesystem({root: rootChildren});
|
||||||
|
}
|
||||||
|
|
||||||
|
function mockPackageFile() {
|
||||||
|
mockFS({'package.json': packageJson});
|
||||||
|
}
|
||||||
|
|
||||||
|
function mockIndexFile(indexJs) {
|
||||||
|
mockFS({'index.js': indexJs});
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('Module', () => {
|
||||||
|
const fileWatcher = {
|
||||||
|
on: () => this,
|
||||||
|
isWatchman: () => Promise.resolve(false),
|
||||||
|
};
|
||||||
|
const fileName = '/root/index.js';
|
||||||
|
|
||||||
|
let cache, fastfs;
|
||||||
|
|
||||||
|
const createCache = () => ({
|
||||||
|
get: jest.genMockFn().mockImplementation(
|
||||||
|
(filepath, field, cb) => cb(filepath)
|
||||||
|
),
|
||||||
|
invalidate: jest.genMockFn(),
|
||||||
|
end: jest.genMockFn(),
|
||||||
|
});
|
||||||
|
|
||||||
|
const createModule = (options) =>
|
||||||
|
new Module({
|
||||||
|
options: {
|
||||||
|
cacheTransformResults: true,
|
||||||
|
},
|
||||||
|
...options,
|
||||||
|
cache,
|
||||||
|
fastfs,
|
||||||
|
file: options && options.file || fileName,
|
||||||
|
depGraphHelpers: new DependencyGraphHelpers(),
|
||||||
|
moduleCache: new ModuleCache({fastfs, cache}),
|
||||||
|
});
|
||||||
|
|
||||||
|
const createJSONModule =
|
||||||
|
(options) => createModule({...options, file: '/root/package.json'});
|
||||||
|
|
||||||
|
beforeEach(function(done) {
|
||||||
|
process.platform = 'linux';
|
||||||
|
cache = createCache();
|
||||||
|
fastfs = new Fastfs(
|
||||||
|
'test',
|
||||||
|
['/root'],
|
||||||
|
fileWatcher,
|
||||||
|
{crawling: Promise.resolve([fileName, '/root/package.json']), ignore: []},
|
||||||
|
);
|
||||||
|
|
||||||
|
fastfs.build().then(done);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Module ID', () => {
|
||||||
|
const moduleId = 'arbitraryModule';
|
||||||
|
const source =
|
||||||
|
`/**
|
||||||
|
* @providesModule ${moduleId}
|
||||||
|
*/
|
||||||
|
`;
|
||||||
|
|
||||||
|
let module;
|
||||||
|
beforeEach(() => {
|
||||||
|
module = createModule();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('@providesModule annotations', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
mockIndexFile(source);
|
||||||
|
});
|
||||||
|
|
||||||
|
pit('extracts the module name from the header', () =>
|
||||||
|
module.getName().then(name => expect(name).toEqual(moduleId))
|
||||||
|
);
|
||||||
|
|
||||||
|
pit('identifies the module as haste module', () =>
|
||||||
|
module.isHaste().then(isHaste => expect(isHaste).toBe(true))
|
||||||
|
);
|
||||||
|
|
||||||
|
pit('does not transform the file in order to access the name', () => {
|
||||||
|
const transformCode =
|
||||||
|
jest.genMockFn().mockReturnValue(Promise.resolve());
|
||||||
|
return createModule({transformCode}).getName()
|
||||||
|
.then(() => expect(transformCode).not.toBeCalled());
|
||||||
|
});
|
||||||
|
|
||||||
|
pit('does not transform the file in order to access the haste status', () => {
|
||||||
|
const transformCode =
|
||||||
|
jest.genMockFn().mockReturnValue(Promise.resolve());
|
||||||
|
return createModule({transformCode}).isHaste()
|
||||||
|
.then(() => expect(transformCode).not.toBeCalled());
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('@provides annotations', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
mockIndexFile(source.replace(/@providesModule/, '@provides'));
|
||||||
|
});
|
||||||
|
|
||||||
|
pit('extracts the module name from the header if it has a @provides annotation', () =>
|
||||||
|
module.getName().then(name => expect(name).toEqual(moduleId))
|
||||||
|
);
|
||||||
|
|
||||||
|
pit('identifies the module as haste module', () =>
|
||||||
|
module.isHaste().then(isHaste => expect(isHaste).toBe(true))
|
||||||
|
);
|
||||||
|
|
||||||
|
pit('does not transform the file in order to access the name', () => {
|
||||||
|
const transformCode =
|
||||||
|
jest.genMockFn().mockReturnValue(Promise.resolve());
|
||||||
|
return createModule({transformCode}).getName()
|
||||||
|
.then(() => expect(transformCode).not.toBeCalled());
|
||||||
|
});
|
||||||
|
|
||||||
|
pit('does not transform the file in order to access the haste status', () => {
|
||||||
|
const transformCode =
|
||||||
|
jest.genMockFn().mockReturnValue(Promise.resolve());
|
||||||
|
return createModule({transformCode}).isHaste()
|
||||||
|
.then(() => expect(transformCode).not.toBeCalled());
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('no annotation', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
mockIndexFile('arbitrary(code);');
|
||||||
|
});
|
||||||
|
|
||||||
|
pit('uses the file name as module name', () =>
|
||||||
|
module.getName().then(name => expect(name).toEqual(fileName))
|
||||||
|
);
|
||||||
|
|
||||||
|
pit('does not identify the module as haste module', () =>
|
||||||
|
module.isHaste().then(isHaste => expect(isHaste).toBe(false))
|
||||||
|
);
|
||||||
|
|
||||||
|
pit('does not transform the file in order to access the name', () => {
|
||||||
|
const transformCode =
|
||||||
|
jest.genMockFn().mockReturnValue(Promise.resolve());
|
||||||
|
return createModule({transformCode}).getName()
|
||||||
|
.then(() => expect(transformCode).not.toBeCalled());
|
||||||
|
});
|
||||||
|
|
||||||
|
pit('does not transform the file in order to access the haste status', () => {
|
||||||
|
const transformCode =
|
||||||
|
jest.genMockFn().mockReturnValue(Promise.resolve());
|
||||||
|
return createModule({transformCode}).isHaste()
|
||||||
|
.then(() => expect(transformCode).not.toBeCalled());
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Code', () => {
|
||||||
|
const fileContents = 'arbitrary(code)';
|
||||||
|
beforeEach(function() {
|
||||||
|
mockIndexFile(fileContents);
|
||||||
|
});
|
||||||
|
|
||||||
|
pit('exposes file contents as `code` property on the data exposed by `read()`', () =>
|
||||||
|
createModule().read().then(({code}) =>
|
||||||
|
expect(code).toBe(fileContents))
|
||||||
|
);
|
||||||
|
|
||||||
|
pit('exposes file contents via the `getCode()` method', () =>
|
||||||
|
createModule().getCode().then(code =>
|
||||||
|
expect(code).toBe(fileContents))
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Extractors', () => {
|
||||||
|
|
||||||
|
pit('uses custom require extractors if specified', () => {
|
||||||
|
mockIndexFile('');
|
||||||
|
const module = createModule({
|
||||||
|
extractor: code => ({deps: {sync: ['foo', 'bar']}}),
|
||||||
|
});
|
||||||
|
|
||||||
|
return module.getDependencies().then(actual =>
|
||||||
|
expect(actual).toEqual(['foo', 'bar']));
|
||||||
|
});
|
||||||
|
|
||||||
|
pit('uses a default extractor to extract dependencies', () => {
|
||||||
|
mockIndexFile(`
|
||||||
|
require('dependency-a');
|
||||||
|
import * as b from "dependency-b";
|
||||||
|
export {something} from 'dependency-c';
|
||||||
|
`);
|
||||||
|
|
||||||
|
const module = createModule();
|
||||||
|
return module.getDependencies().then(dependencies =>
|
||||||
|
expect(dependencies.sort())
|
||||||
|
.toEqual(['dependency-a', 'dependency-b', 'dependency-c'])
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
pit('does not extract dependencies from files annotated with @extern', () => {
|
||||||
|
mockIndexFile(`
|
||||||
|
/**
|
||||||
|
* @extern
|
||||||
|
*/
|
||||||
|
require('dependency-a');
|
||||||
|
import * as b from "dependency-b";
|
||||||
|
export {something} from 'dependency-c';
|
||||||
|
`);
|
||||||
|
|
||||||
|
const module = createModule();
|
||||||
|
return module.getDependencies().then(dependencies =>
|
||||||
|
expect(dependencies).toEqual([])
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
pit('does not extract dependencies from JSON files', () => {
|
||||||
|
mockPackageFile();
|
||||||
|
const module = createJSONModule();
|
||||||
|
return module.getDependencies().then(dependencies =>
|
||||||
|
expect(dependencies).toEqual([])
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Custom Code Transform', () => {
|
||||||
|
let transformCode;
|
||||||
|
const fileContents = 'arbitrary(code);';
|
||||||
|
const exampleCode = `
|
||||||
|
${'require'}('a');
|
||||||
|
${'System.import'}('b');
|
||||||
|
${'require'}('c');`;
|
||||||
|
|
||||||
|
beforeEach(function() {
|
||||||
|
transformCode = jest.genMockFn();
|
||||||
|
mockIndexFile(fileContents);
|
||||||
|
transformCode.mockReturnValue(Promise.resolve({code: ''}));
|
||||||
|
});
|
||||||
|
|
||||||
|
pit('passes the module and file contents to the transform function when reading', () => {
|
||||||
|
const module = createModule({transformCode});
|
||||||
|
return module.read()
|
||||||
|
.then(() => {
|
||||||
|
expect(transformCode).toBeCalledWith(module, fileContents, undefined);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
pit('passes any additional options to the transform function when reading', () => {
|
||||||
|
const module = createModule({transformCode});
|
||||||
|
const transformOptions = {arbitrary: Object()};
|
||||||
|
return module.read(transformOptions)
|
||||||
|
.then(() =>
|
||||||
|
expect(transformCode.mock.calls[0][2]).toBe(transformOptions)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
pit('passes the module and file contents to the transform if the file is annotated with @extern', () => {
|
||||||
|
const module = createModule({transformCode});
|
||||||
|
const fileContents = `
|
||||||
|
/**
|
||||||
|
* @extern
|
||||||
|
*/
|
||||||
|
`;
|
||||||
|
mockIndexFile(fileContents);
|
||||||
|
return module.read().then(() => {
|
||||||
|
expect(transformCode).toBeCalledWith(module, fileContents, {extern: true});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
pit('passes the module and file contents to the transform for JSON files', () => {
|
||||||
|
mockPackageFile();
|
||||||
|
const module = createJSONModule({transformCode});
|
||||||
|
return module.read().then(() => {
|
||||||
|
expect(transformCode).toBeCalledWith(module, packageJson, {extern: true});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
pit('does not extend the passed options object if the file is annotated with @extern', () => {
|
||||||
|
const module = createModule({transformCode});
|
||||||
|
const fileContents = `
|
||||||
|
/**
|
||||||
|
* @extern
|
||||||
|
*/
|
||||||
|
`;
|
||||||
|
mockIndexFile(fileContents);
|
||||||
|
const options = {arbitrary: 'foo'};
|
||||||
|
return module.read(options).then(() => {
|
||||||
|
expect(options).not.toEqual(jasmine.objectContaining({extern: true}));
|
||||||
|
expect(transformCode)
|
||||||
|
.toBeCalledWith(module, fileContents, {...options, extern: true});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
pit('does not extend the passed options object for JSON files', () => {
|
||||||
|
mockPackageFile();
|
||||||
|
const module = createJSONModule({transformCode});
|
||||||
|
const options = {arbitrary: 'foo'};
|
||||||
|
return module.read(options).then(() => {
|
||||||
|
expect(options).not.toEqual(jasmine.objectContaining({extern: true}));
|
||||||
|
expect(transformCode)
|
||||||
|
.toBeCalledWith(module, packageJson, {...options, extern: true});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
pit('uses the code that `transformCode` resolves to to extract dependencies', () => {
|
||||||
|
transformCode.mockReturnValue(Promise.resolve({code: exampleCode}));
|
||||||
|
const module = createModule({transformCode});
|
||||||
|
|
||||||
|
return module.getDependencies().then(dependencies => {
|
||||||
|
expect(dependencies).toEqual(['a', 'c']);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
pit('uses dependencies that `transformCode` resolves to, instead of extracting them', () => {
|
||||||
|
const mockedDependencies = ['foo', 'bar'];
|
||||||
|
transformCode.mockReturnValue(Promise.resolve({
|
||||||
|
code: exampleCode,
|
||||||
|
dependencies: mockedDependencies,
|
||||||
|
}));
|
||||||
|
const module = createModule({transformCode});
|
||||||
|
|
||||||
|
return module.getDependencies().then(dependencies => {
|
||||||
|
expect(dependencies).toEqual(mockedDependencies);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
pit('forwards all additional properties of the result provided by `transformCode`', () => {
|
||||||
|
const mockedResult = {
|
||||||
|
code: exampleCode,
|
||||||
|
arbitrary: 'arbitrary',
|
||||||
|
dependencyOffsets: [12, 764],
|
||||||
|
map: {version: 3},
|
||||||
|
subObject: {foo: 'bar'},
|
||||||
|
};
|
||||||
|
transformCode.mockReturnValue(Promise.resolve(mockedResult));
|
||||||
|
const module = createModule({transformCode});
|
||||||
|
|
||||||
|
return module.read().then((result) => {
|
||||||
|
expect(result).toEqual(jasmine.objectContaining(mockedResult));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
pit('does not store anything but dependencies if the `cacheTransformResults` option is disabled', () => {
|
||||||
|
const mockedResult = {
|
||||||
|
code: exampleCode,
|
||||||
|
arbitrary: 'arbitrary',
|
||||||
|
dependencies: ['foo', 'bar'],
|
||||||
|
dependencyOffsets: [12, 764],
|
||||||
|
map: {version: 3},
|
||||||
|
subObject: {foo: 'bar'},
|
||||||
|
};
|
||||||
|
transformCode.mockReturnValue(Promise.resolve(mockedResult));
|
||||||
|
const module = createModule({transformCode, options: {
|
||||||
|
cacheTransformResults: false,
|
||||||
|
}});
|
||||||
|
|
||||||
|
return module.read().then((result) => {
|
||||||
|
expect(result).toEqual({
|
||||||
|
dependencies: ['foo', 'bar'],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
pit('stores all things if options is undefined', () => {
|
||||||
|
const mockedResult = {
|
||||||
|
code: exampleCode,
|
||||||
|
arbitrary: 'arbitrary',
|
||||||
|
dependencies: ['foo', 'bar'],
|
||||||
|
dependencyOffsets: [12, 764],
|
||||||
|
map: {version: 3},
|
||||||
|
subObject: {foo: 'bar'},
|
||||||
|
};
|
||||||
|
transformCode.mockReturnValue(Promise.resolve(mockedResult));
|
||||||
|
const module = createModule({transformCode, options: undefined});
|
||||||
|
|
||||||
|
return module.read().then((result) => {
|
||||||
|
expect(result).toEqual({ ...mockedResult, source: 'arbitrary(code);'});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
pit('exposes the transformed code rather than the raw file contents', () => {
|
||||||
|
transformCode.mockReturnValue(Promise.resolve({code: exampleCode}));
|
||||||
|
const module = createModule({transformCode});
|
||||||
|
return Promise.all([module.read(), module.getCode()])
|
||||||
|
.then(([data, code]) => {
|
||||||
|
expect(data.code).toBe(exampleCode);
|
||||||
|
expect(code).toBe(exampleCode);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
pit('exposes the raw file contents as `source` property', () => {
|
||||||
|
const module = createModule({transformCode});
|
||||||
|
return module.read()
|
||||||
|
.then(data => expect(data.source).toBe(fileContents));
|
||||||
|
});
|
||||||
|
|
||||||
|
pit('exposes a source map returned by the transform', () => {
|
||||||
|
const map = {version: 3};
|
||||||
|
transformCode.mockReturnValue(Promise.resolve({map, code: exampleCode}));
|
||||||
|
const module = createModule({transformCode});
|
||||||
|
return Promise.all([module.read(), module.getMap()])
|
||||||
|
.then(([data, sourceMap]) => {
|
||||||
|
expect(data.map).toBe(map);
|
||||||
|
expect(sourceMap).toBe(map);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Caching based on options', () => {
|
||||||
|
let module;
|
||||||
|
beforeEach(function() {
|
||||||
|
module = createModule({transformCode});
|
||||||
|
});
|
||||||
|
|
||||||
|
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'};
|
||||||
|
module.getDependencies(options); // first call
|
||||||
|
module.getDependencies(options); // second call
|
||||||
|
|
||||||
|
const {calls} = cache.get.mock;
|
||||||
|
callsEqual(calls[0], calls[1]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('gets dependencies from the cache with the same cache key for the equivalent transform options', () => {
|
||||||
|
module.getDependencies({a: 'b', c: 'd'}); // first call
|
||||||
|
module.getDependencies({c: 'd', a: 'b'}); // second call
|
||||||
|
|
||||||
|
const {calls} = cache.get.mock;
|
||||||
|
callsEqual(calls[0], calls[1]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('gets dependencies from the cache with different cache keys for different transform options', () => {
|
||||||
|
module.getDependencies({some: 'options'});
|
||||||
|
module.getDependencies({other: 'arbitrary options'});
|
||||||
|
const {calls} = cache.get.mock;
|
||||||
|
expect(calls[0][1]).not.toEqual(calls[1][1]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('gets code from the cache with the same cache key for the same transform options', () => {
|
||||||
|
const options = {some: 'options'};
|
||||||
|
module.getCode(options); // first call
|
||||||
|
module.getCode(options); // second call
|
||||||
|
|
||||||
|
const {calls} = cache.get.mock;
|
||||||
|
callsEqual(calls[0], calls[1]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('gets code from the cache with the same cache key for the equivalent transform options', () => {
|
||||||
|
module.getCode({a: 'b', c: 'd'}); // first call
|
||||||
|
module.getCode({c: 'd', a: 'b'}); // second call
|
||||||
|
|
||||||
|
const {calls} = cache.get.mock;
|
||||||
|
callsEqual(calls[0], calls[1]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('gets code from the cache with different cache keys for different transform options', () => {
|
||||||
|
module.getCode({some: 'options'});
|
||||||
|
module.getCode({other: 'arbitrary options'});
|
||||||
|
const {calls} = cache.get.mock;
|
||||||
|
expect(calls[0][1]).not.toEqual(calls[1][1]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,39 @@
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
* An arbitrary module header
|
||||||
|
* @providesModule
|
||||||
|
*/
|
||||||
|
|
||||||
|
const some: string = 'arbitrary code';
|
||||||
|
const containing: string = 'ūñïčødę';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An arbitrary class that extends some thing
|
||||||
|
* It exposes a random number, which may be reset at the callers discretion
|
||||||
|
*/
|
||||||
|
class Arbitrary extends Something {
|
||||||
|
constructor() {
|
||||||
|
this.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the random number
|
||||||
|
* @returns number
|
||||||
|
*/
|
||||||
|
get random(): number {
|
||||||
|
return this._random;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Re-creates the internal random number
|
||||||
|
* @returns void
|
||||||
|
*/
|
||||||
|
reset(): void {
|
||||||
|
this._random = Math.random();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,87 @@
|
||||||
|
/**
|
||||||
|
* 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.autoMockOff()
|
||||||
|
.dontMock('graceful-fs');
|
||||||
|
|
||||||
|
const Fastfs = require('../fastfs');
|
||||||
|
|
||||||
|
const {EventEmitter} = require('events');
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
const fileName = path.resolve(__dirname, 'fastfs-data');
|
||||||
|
const contents = fs.readFileSync(fileName, 'utf-8');
|
||||||
|
|
||||||
|
describe('fastfs:', function() {
|
||||||
|
let fastfs;
|
||||||
|
const crawling = Promise.resolve([fileName]);
|
||||||
|
const roots = [__dirname];
|
||||||
|
const watcher = new EventEmitter();
|
||||||
|
|
||||||
|
beforeEach(function(done) {
|
||||||
|
fastfs = new Fastfs('arbitrary', roots, watcher, {crawling});
|
||||||
|
fastfs.build().then(done);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('partial reading', () => {
|
||||||
|
// these are integrated tests that read real files from disk
|
||||||
|
|
||||||
|
pit('reads a file while a predicate returns true', function() {
|
||||||
|
return fastfs.readWhile(fileName, () => true).then(readContent =>
|
||||||
|
expect(readContent).toEqual(contents)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
pit('invokes the predicate with the new chunk, the invocation index, and the result collected so far', () => {
|
||||||
|
const predicate = jest.genMockFn().mockReturnValue(true);
|
||||||
|
return fastfs.readWhile(fileName, predicate).then(() => {
|
||||||
|
let aggregated = '';
|
||||||
|
const {calls} = predicate.mock;
|
||||||
|
expect(calls).not.toEqual([]);
|
||||||
|
|
||||||
|
calls.forEach((call, i) => {
|
||||||
|
const [chunk] = call;
|
||||||
|
aggregated += chunk;
|
||||||
|
expect(chunk).not.toBe('');
|
||||||
|
expect(call).toEqual([chunk, i, aggregated]);
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(aggregated).toEqual(contents);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
pit('stops reading when the predicate returns false', () => {
|
||||||
|
const predicate = jest.genMockFn().mockImpl((_, i) => i !== 0);
|
||||||
|
return fastfs.readWhile(fileName, predicate).then((readContent) => {
|
||||||
|
const {calls} = predicate.mock;
|
||||||
|
expect(calls.length).toBe(1);
|
||||||
|
expect(readContent).toBe(calls[0][2]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
pit('after reading the whole file with `readWhile`, `read()` still works', () => {
|
||||||
|
// this test allows to reuse the results of `readWhile` for `readFile`
|
||||||
|
return fastfs.readWhile(fileName, () => true).then(() => {
|
||||||
|
fastfs.readFile(fileName).then(readContent =>
|
||||||
|
expect(readContent).toEqual(contents)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
pit('after reading parts of the file with `readWhile`, `read()` still works', () => {
|
||||||
|
return fastfs.readWhile(fileName, () => false).then(() => {
|
||||||
|
fastfs.readFile(fileName).then(readContent =>
|
||||||
|
expect(readContent).toEqual(contents)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,525 @@
|
||||||
|
// Copyright Joyent, Inc. and other Node contributors.
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a
|
||||||
|
// copy of this software and associated documentation files (the
|
||||||
|
// "Software"), to deal in the Software without restriction, including
|
||||||
|
// without limitation the rights to use, copy, modify, merge, publish,
|
||||||
|
// distribute, sublicense, and/or sell copies of the Software, and to permit
|
||||||
|
// persons to whom the Software is furnished to do so, subject to the
|
||||||
|
// following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included
|
||||||
|
// in all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||||
|
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
|
||||||
|
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
||||||
|
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
||||||
|
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
|
||||||
|
// USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
|
/*
|
||||||
|
* original author: dead_horse <dead_horse@qq.com>
|
||||||
|
* ported by: yaycmyk <evan@yaycmyk.com>
|
||||||
|
*/
|
||||||
|
|
||||||
|
jest.dontMock('../fastpath');
|
||||||
|
const fp = () => require('../fastpath');
|
||||||
|
|
||||||
|
describe('fast-path', () => {
|
||||||
|
const actual_platform = process.platform;
|
||||||
|
const invalidInputTests = [
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
7,
|
||||||
|
null,
|
||||||
|
{},
|
||||||
|
undefined,
|
||||||
|
[],
|
||||||
|
NaN,
|
||||||
|
() => {},
|
||||||
|
];
|
||||||
|
|
||||||
|
const getErrorMessage = (fnName, test, expected, actual) => {
|
||||||
|
return [
|
||||||
|
`fastpath.${fnName}('${test}')`,
|
||||||
|
`expect=${JSON.stringify(expected)}`,
|
||||||
|
`actual=${JSON.stringify(actual)}`,
|
||||||
|
].join('\n');
|
||||||
|
};
|
||||||
|
|
||||||
|
beforeEach(function() {
|
||||||
|
process.platform = 'linux';
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(function() {
|
||||||
|
process.platform = actual_platform;
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('basename', () => {
|
||||||
|
it('should resolve given paths', () => {
|
||||||
|
process.platform = actual_platform;
|
||||||
|
|
||||||
|
expect(fp().basename(__filename)).toEqual('fastpath-test.js');
|
||||||
|
expect(fp().basename(__filename, '.js')).toEqual('fastpath-test');
|
||||||
|
expect(fp().basename('')).toEqual('');
|
||||||
|
expect(fp().basename('/dir/basename.ext')).toEqual('basename.ext');
|
||||||
|
expect(fp().basename('/basename.ext')).toEqual('basename.ext');
|
||||||
|
expect(fp().basename('basename.ext')).toEqual('basename.ext');
|
||||||
|
expect(fp().basename('basename.ext/')).toEqual('basename.ext');
|
||||||
|
expect(fp().basename('basename.ext//')).toEqual('basename.ext');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle backslashes properly (win32)', () => {
|
||||||
|
process.platform = 'win32';
|
||||||
|
|
||||||
|
expect(fp().basename('\\dir\\basename.ext')).toEqual('basename.ext');
|
||||||
|
expect(fp().basename('\\basename.ext')).toEqual('basename.ext');
|
||||||
|
expect(fp().basename('basename.ext')).toEqual('basename.ext');
|
||||||
|
expect(fp().basename('basename.ext\\')).toEqual('basename.ext');
|
||||||
|
expect(fp().basename('basename.ext\\\\')).toEqual('basename.ext');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle backslashes properly (posix)', () => {
|
||||||
|
expect(fp().basename('\\dir\\basename.ext')).toEqual('\\dir\\basename.ext');
|
||||||
|
expect(fp().basename('\\basename.ext')).toEqual('\\basename.ext');
|
||||||
|
expect(fp().basename('basename.ext')).toEqual('basename.ext');
|
||||||
|
expect(fp().basename('basename.ext\\')).toEqual('basename.ext\\');
|
||||||
|
expect(fp().basename('basename.ext\\\\')).toEqual('basename.ext\\\\');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle control characters (posix)', () => {
|
||||||
|
// POSIX filenames may include control characters
|
||||||
|
// c.f. http://www.dwheeler.com/essays/fixing-unix-linux-filenames.html
|
||||||
|
const name = 'Icon' + String.fromCharCode(13);
|
||||||
|
expect(fp().basename('/a/b/' + name)).toEqual(name);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('extname', () => {
|
||||||
|
it('should extract the extension from a path', () => {
|
||||||
|
process.platform = actual_platform;
|
||||||
|
|
||||||
|
expect(fp().extname(__filename)).toEqual('.js');
|
||||||
|
expect(fp().extname('')).toEqual('');
|
||||||
|
expect(fp().extname('/path/to/file')).toEqual('');
|
||||||
|
expect(fp().extname('/path/to/file.ext')).toEqual('.ext');
|
||||||
|
expect(fp().extname('/path.to/file.ext')).toEqual('.ext');
|
||||||
|
expect(fp().extname('/path.to/file')).toEqual('');
|
||||||
|
expect(fp().extname('/path.to/.file')).toEqual('');
|
||||||
|
expect(fp().extname('/path.to/.file.ext')).toEqual('.ext');
|
||||||
|
expect(fp().extname('/path/to/f.ext')).toEqual('.ext');
|
||||||
|
expect(fp().extname('/path/to/..ext')).toEqual('.ext');
|
||||||
|
expect(fp().extname('file')).toEqual('');
|
||||||
|
expect(fp().extname('file.ext')).toEqual('.ext');
|
||||||
|
expect(fp().extname('.file')).toEqual('');
|
||||||
|
expect(fp().extname('.file.ext')).toEqual('.ext');
|
||||||
|
expect(fp().extname('/file')).toEqual('');
|
||||||
|
expect(fp().extname('/file.ext')).toEqual('.ext');
|
||||||
|
expect(fp().extname('/.file')).toEqual('');
|
||||||
|
expect(fp().extname('/.file.ext')).toEqual('.ext');
|
||||||
|
expect(fp().extname('.path/file.ext')).toEqual('.ext');
|
||||||
|
expect(fp().extname('file.ext.ext')).toEqual('.ext');
|
||||||
|
expect(fp().extname('file.')).toEqual('.');
|
||||||
|
expect(fp().extname('.')).toEqual('');
|
||||||
|
expect(fp().extname('./')).toEqual('');
|
||||||
|
expect(fp().extname('.file.ext')).toEqual('.ext');
|
||||||
|
expect(fp().extname('.file')).toEqual('');
|
||||||
|
expect(fp().extname('.file.')).toEqual('.');
|
||||||
|
expect(fp().extname('.file..')).toEqual('.');
|
||||||
|
expect(fp().extname('..')).toEqual('');
|
||||||
|
expect(fp().extname('../')).toEqual('');
|
||||||
|
expect(fp().extname('..file.ext')).toEqual('.ext');
|
||||||
|
expect(fp().extname('..file')).toEqual('.file');
|
||||||
|
expect(fp().extname('..file.')).toEqual('.');
|
||||||
|
expect(fp().extname('..file..')).toEqual('.');
|
||||||
|
expect(fp().extname('...')).toEqual('.');
|
||||||
|
expect(fp().extname('...ext')).toEqual('.ext');
|
||||||
|
expect(fp().extname('....')).toEqual('.');
|
||||||
|
expect(fp().extname('file.ext/')).toEqual('.ext');
|
||||||
|
expect(fp().extname('file.ext//')).toEqual('.ext');
|
||||||
|
expect(fp().extname('file/')).toEqual('');
|
||||||
|
expect(fp().extname('file//')).toEqual('');
|
||||||
|
expect(fp().extname('file./')).toEqual('.');
|
||||||
|
expect(fp().extname('file.//')).toEqual('.');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle path backslashes (win32)', () => {
|
||||||
|
process.platform = 'win32';
|
||||||
|
|
||||||
|
// On windows, backspace is a path separator.
|
||||||
|
expect(fp().extname('.\\')).toEqual('');
|
||||||
|
expect(fp().extname('..\\')).toEqual('');
|
||||||
|
expect(fp().extname('file.ext\\')).toEqual('.ext');
|
||||||
|
expect(fp().extname('file.ext\\\\')).toEqual('.ext');
|
||||||
|
expect(fp().extname('file\\')).toEqual('');
|
||||||
|
expect(fp().extname('file\\\\')).toEqual('');
|
||||||
|
expect(fp().extname('file.\\')).toEqual('.');
|
||||||
|
expect(fp().extname('file.\\\\')).toEqual('.');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle path backslashes (posix)', () => {
|
||||||
|
// On unix, backspace is a valid name component like any other character.
|
||||||
|
expect(fp().extname('.\\')).toEqual('');
|
||||||
|
expect(fp().extname('..\\')).toEqual('.\\');
|
||||||
|
expect(fp().extname('file.ext\\')).toEqual('.ext\\');
|
||||||
|
expect(fp().extname('file.ext\\\\')).toEqual('.ext\\\\');
|
||||||
|
expect(fp().extname('file\\')).toEqual('');
|
||||||
|
expect(fp().extname('file\\\\')).toEqual('');
|
||||||
|
expect(fp().extname('file.\\')).toEqual('.\\');
|
||||||
|
expect(fp().extname('file.\\\\')).toEqual('.\\\\');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('dirname', () => {
|
||||||
|
it('should isolate the directory name from a path (posix)', () => {
|
||||||
|
expect(fp().dirname('/a/b/')).toEqual('/a');
|
||||||
|
expect(fp().dirname('/a/b')).toEqual('/a');
|
||||||
|
expect(fp().dirname('/a')).toEqual('/');
|
||||||
|
expect(fp().dirname('')).toEqual('.');
|
||||||
|
expect(fp().dirname('/')).toEqual('/');
|
||||||
|
expect(fp().dirname('////')).toEqual('/');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should isolate the directory name from a path (win32)', () => {
|
||||||
|
process.platform = 'win32';
|
||||||
|
|
||||||
|
expect(fp().dirname('C:\\')).toEqual('C:\\');
|
||||||
|
expect(fp().dirname('c:\\')).toEqual('c:\\');
|
||||||
|
expect(fp().dirname('c:\\foo')).toEqual('c:\\');
|
||||||
|
expect(fp().dirname('c:\\foo\\')).toEqual('c:\\');
|
||||||
|
expect(fp().dirname('c:\\foo\\bar')).toEqual('c:\\foo');
|
||||||
|
expect(fp().dirname('c:\\foo\\bar\\')).toEqual('c:\\foo');
|
||||||
|
expect(fp().dirname('c:\\foo\\bar\\baz')).toEqual('c:\\foo\\bar');
|
||||||
|
expect(fp().dirname('\\')).toEqual('\\');
|
||||||
|
expect(fp().dirname('\\foo')).toEqual('\\');
|
||||||
|
expect(fp().dirname('\\foo\\')).toEqual('\\');
|
||||||
|
expect(fp().dirname('\\foo\\bar')).toEqual('\\foo');
|
||||||
|
expect(fp().dirname('\\foo\\bar\\')).toEqual('\\foo');
|
||||||
|
expect(fp().dirname('\\foo\\bar\\baz')).toEqual('\\foo\\bar');
|
||||||
|
expect(fp().dirname('c:')).toEqual('c:');
|
||||||
|
expect(fp().dirname('c:foo')).toEqual('c:');
|
||||||
|
expect(fp().dirname('c:foo\\')).toEqual('c:');
|
||||||
|
expect(fp().dirname('c:foo\\bar')).toEqual('c:foo');
|
||||||
|
expect(fp().dirname('c:foo\\bar\\')).toEqual('c:foo');
|
||||||
|
expect(fp().dirname('c:foo\\bar\\baz')).toEqual('c:foo\\bar');
|
||||||
|
expect(fp().dirname('\\\\unc\\share')).toEqual('\\\\unc\\share');
|
||||||
|
expect(fp().dirname('\\\\unc\\share\\foo')).toEqual('\\\\unc\\share\\');
|
||||||
|
expect(fp().dirname('\\\\unc\\share\\foo\\')).toEqual('\\\\unc\\share\\');
|
||||||
|
expect(fp().dirname('\\\\unc\\share\\foo\\bar')).toEqual('\\\\unc\\share\\foo');
|
||||||
|
expect(fp().dirname('\\\\unc\\share\\foo\\bar\\')).toEqual('\\\\unc\\share\\foo');
|
||||||
|
expect(fp().dirname('\\\\unc\\share\\foo\\bar\\baz')).toEqual('\\\\unc\\share\\foo\\bar');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('join', () => {
|
||||||
|
const posixJoinTests = [
|
||||||
|
// arguments result
|
||||||
|
[['.', 'x/b', '..', '/b/c.js'], 'x/b/c.js'],
|
||||||
|
[['/.', 'x/b', '..', '/b/c.js'], '/x/b/c.js'],
|
||||||
|
[['/foo', '../../../bar'], '/bar'],
|
||||||
|
[['foo', '../../../bar'], '../../bar'],
|
||||||
|
[['foo/', '../../../bar'], '../../bar'],
|
||||||
|
[['foo/x', '../../../bar'], '../bar'],
|
||||||
|
[['foo/x', './bar'], 'foo/x/bar'],
|
||||||
|
[['foo/x/', './bar'], 'foo/x/bar'],
|
||||||
|
[['foo/x/', '.', 'bar'], 'foo/x/bar'],
|
||||||
|
[['./'], './'],
|
||||||
|
[['.', './'], './'],
|
||||||
|
[['.', '.', '.'], '.'],
|
||||||
|
[['.', './', '.'], '.'],
|
||||||
|
[['.', '/./', '.'], '.'],
|
||||||
|
[['.', '/////./', '.'], '.'],
|
||||||
|
[['.'], '.'],
|
||||||
|
[['', '.'], '.'],
|
||||||
|
[['', 'foo'], 'foo'],
|
||||||
|
[['foo', '/bar'], 'foo/bar'],
|
||||||
|
[['', '/foo'], '/foo'],
|
||||||
|
[['', '', '/foo'], '/foo'],
|
||||||
|
[['', '', 'foo'], 'foo'],
|
||||||
|
[['foo', ''], 'foo'],
|
||||||
|
[['foo/', ''], 'foo/'],
|
||||||
|
[['foo', '', '/bar'], 'foo/bar'],
|
||||||
|
[['./', '..', '/foo'], '../foo'],
|
||||||
|
[['./', '..', '..', '/foo'], '../../foo'],
|
||||||
|
[['.', '..', '..', '/foo'], '../../foo'],
|
||||||
|
[['', '..', '..', '/foo'], '../../foo'],
|
||||||
|
[['/'], '/'],
|
||||||
|
[['/', '.'], '/'],
|
||||||
|
[['/', '..'], '/'],
|
||||||
|
[['/', '..', '..'], '/'],
|
||||||
|
[[''], '.'],
|
||||||
|
[['', ''], '.'],
|
||||||
|
[[' /foo'], ' /foo'],
|
||||||
|
[[' ', 'foo'], ' /foo'],
|
||||||
|
[[' ', '.'], ' '],
|
||||||
|
[[' ', '/'], ' /'],
|
||||||
|
[[' ', ''], ' '],
|
||||||
|
[['/', 'foo'], '/foo'],
|
||||||
|
[['/', '/foo'], '/foo'],
|
||||||
|
[['/', '//foo'], '/foo'],
|
||||||
|
[['/', '', '/foo'], '/foo'],
|
||||||
|
[['', '/', 'foo'], '/foo'],
|
||||||
|
[['', '/', '/foo'], '/foo'],
|
||||||
|
];
|
||||||
|
|
||||||
|
const win32JoinTests = posixJoinTests.concat([
|
||||||
|
// UNC path expected
|
||||||
|
[['//foo/bar'], '//foo/bar/'],
|
||||||
|
[['\\/foo/bar'], '//foo/bar/'],
|
||||||
|
[['\\\\foo/bar'], '//foo/bar/'],
|
||||||
|
// UNC path expected - server and share separate
|
||||||
|
[['//foo', 'bar'], '//foo/bar/'],
|
||||||
|
[['//foo/', 'bar'], '//foo/bar/'],
|
||||||
|
[['//foo', '/bar'], '//foo/bar/'],
|
||||||
|
// UNC path expected - questionable
|
||||||
|
[['//foo', '', 'bar'], '//foo/bar/'],
|
||||||
|
[['//foo/', '', 'bar'], '//foo/bar/'],
|
||||||
|
[['//foo/', '', '/bar'], '//foo/bar/'],
|
||||||
|
// UNC path expected - even more questionable
|
||||||
|
[['', '//foo', 'bar'], '//foo/bar/'],
|
||||||
|
[['', '//foo/', 'bar'], '//foo/bar/'],
|
||||||
|
[['', '//foo/', '/bar'], '//foo/bar/'],
|
||||||
|
// No UNC path expected (no double slash in first component)
|
||||||
|
[['\\', 'foo/bar'], '/foo/bar'],
|
||||||
|
[['\\', '/foo/bar'], '/foo/bar'],
|
||||||
|
[['', '/', '/foo/bar'], '/foo/bar'],
|
||||||
|
// No UNC path expected (no non-slashes in first component - questionable)
|
||||||
|
[['//', 'foo/bar'], '/foo/bar'],
|
||||||
|
[['//', '/foo/bar'], '/foo/bar'],
|
||||||
|
[['\\\\', '/', '/foo/bar'], '/foo/bar'],
|
||||||
|
[['//'], '/'],
|
||||||
|
// No UNC path expected (share name missing - questionable).
|
||||||
|
[['//foo'], '/foo'],
|
||||||
|
[['//foo/'], '/foo/'],
|
||||||
|
[['//foo', '/'], '/foo/'],
|
||||||
|
[['//foo', '', '/'], '/foo/'],
|
||||||
|
// No UNC path expected (too many leading slashes - questionable)
|
||||||
|
[['///foo/bar'], '/foo/bar'],
|
||||||
|
[['////foo', 'bar'], '/foo/bar'],
|
||||||
|
[['\\\\\\/foo/bar'], '/foo/bar'],
|
||||||
|
// Drive-relative vs drive-absolute paths. This merely describes the
|
||||||
|
// status quo, rather than being obviously right
|
||||||
|
[['C:'], 'C:.'],
|
||||||
|
[['c:'], 'c:.'],
|
||||||
|
[['c:.'], 'c:.'],
|
||||||
|
[['c:', ''], 'c:.'],
|
||||||
|
[['', 'c:'], 'c:.'],
|
||||||
|
[['c:.', '/'], 'c:./'],
|
||||||
|
[['c:.', 'file'], 'c:file'],
|
||||||
|
[['c:', '/'], 'c:/'],
|
||||||
|
[['c:', 'file'], 'c:/file'],
|
||||||
|
]);
|
||||||
|
|
||||||
|
it('should join the paths correctly (posix)', () => {
|
||||||
|
posixJoinTests.forEach((test) => {
|
||||||
|
const actual = fp().join(...test[0]);
|
||||||
|
const expected = test[1];
|
||||||
|
|
||||||
|
expect(actual).toEqual(expected, getErrorMessage('join',
|
||||||
|
test[0].map(JSON.stringify).join(','),
|
||||||
|
expected,
|
||||||
|
actual,
|
||||||
|
));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should join the paths correctly (win32)', () => {
|
||||||
|
process.platform = 'win32';
|
||||||
|
|
||||||
|
win32JoinTests.forEach((test) => {
|
||||||
|
const actual = fp().join(...test[0]);
|
||||||
|
const expected = test[1].replace(/\//g, '\\');
|
||||||
|
|
||||||
|
expect(actual).toEqual(expected, getErrorMessage('join',
|
||||||
|
test[0].map(JSON.stringify).join(','),
|
||||||
|
expected,
|
||||||
|
actual,
|
||||||
|
));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw for invalid input', () => {
|
||||||
|
invalidInputTests.forEach((test) => {
|
||||||
|
expect(() => fp().join(test)).toThrow();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('normalize', () => {
|
||||||
|
it('should return a valid path (posix)', () => {
|
||||||
|
expect(fp().normalize('./fixtures///b/../b/c.js')).toEqual('fixtures/b/c.js');
|
||||||
|
expect(fp().normalize('/foo/../../../bar')).toEqual('/bar');
|
||||||
|
expect(fp().normalize('a//b//../b')).toEqual('a/b');
|
||||||
|
expect(fp().normalize('a//b//./c')).toEqual('a/b/c');
|
||||||
|
expect(fp().normalize('a//b//.')).toEqual('a/b');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return a valid path (win32)', () => {
|
||||||
|
process.platform = 'win32';
|
||||||
|
expect(fp().normalize('./fixtures///b/../b/c.js')).toEqual('fixtures\\b\\c.js');
|
||||||
|
expect(fp().normalize('/foo/../../../bar')).toEqual('\\bar');
|
||||||
|
expect(fp().normalize('a//b//../b')).toEqual('a\\b');
|
||||||
|
expect(fp().normalize('a//b//./c')).toEqual('a\\b\\c');
|
||||||
|
expect(fp().normalize('a//b//.')).toEqual('a\\b');
|
||||||
|
expect(fp().normalize('//server/share/dir/file.ext')).toEqual('\\\\server\\share\\dir\\file.ext');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('resolve', () => {
|
||||||
|
const win32ResolveTests = [
|
||||||
|
// arguments result
|
||||||
|
[['C:/blah\\blah', 'D:/games', 'C:../a'], 'C:\\blah\\a'],
|
||||||
|
[['c:/blah\\blah', 'd:/games', 'c:../a'], 'c:\\blah\\a'],
|
||||||
|
[['c:/ignore', 'd:\\a/b\\c/d', '\\e.exe'], 'd:\\e.exe'],
|
||||||
|
[['c:/ignore', 'c:/some/file'], 'c:\\some\\file'],
|
||||||
|
[['d:/ignore', 'd:some/dir//'], 'd:\\ignore\\some\\dir'],
|
||||||
|
[['//server/share', '..', 'relative\\'], '\\\\server\\share\\relative'],
|
||||||
|
[['c:/', '//'], 'c:\\'],
|
||||||
|
[['c:/', '//dir'], 'c:\\dir'],
|
||||||
|
[['c:/', '//server/share'], '\\\\server\\share\\'],
|
||||||
|
[['c:/', '//server//share'], '\\\\server\\share\\'],
|
||||||
|
[['c:/', '///some//dir'], 'c:\\some\\dir'],
|
||||||
|
];
|
||||||
|
|
||||||
|
const posixResolveTests = [
|
||||||
|
// arguments result
|
||||||
|
[['/var/lib', '../', 'file/'], '/var/file'],
|
||||||
|
[['/var/lib', '/../', 'file/'], '/file'],
|
||||||
|
[['/some/dir', '.', '/absolute/'], '/absolute'],
|
||||||
|
];
|
||||||
|
|
||||||
|
it('should resolve the current working directory', () => {
|
||||||
|
process.platform = actual_platform;
|
||||||
|
|
||||||
|
const actual = fp().resolve('.');
|
||||||
|
const expected = process.cwd();
|
||||||
|
|
||||||
|
expect(actual).toEqual(expected, getErrorMessage('resolve', '.', expected, actual));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should resolve paths (posix)', () => {
|
||||||
|
posixResolveTests.forEach((test) => {
|
||||||
|
const actual = fp().resolve(...test[0]);
|
||||||
|
const expected = test[1];
|
||||||
|
|
||||||
|
expect(actual).toEqual(expected, getErrorMessage('resolve', test, expected, actual));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should resolve paths (win32)', () => {
|
||||||
|
process.platform = 'win32';
|
||||||
|
|
||||||
|
win32ResolveTests.forEach((test) => {
|
||||||
|
const actual = fp().resolve(...test[0]);
|
||||||
|
const expected = test[1];
|
||||||
|
|
||||||
|
expect(actual).toEqual(expected, getErrorMessage('resolve', test, expected, actual));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw for invalid input', () => {
|
||||||
|
invalidInputTests.forEach((test) => {
|
||||||
|
expect(() => fp().resolve(test)).toThrow();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('isAbsolute', () => {
|
||||||
|
it('should work (posix)', () => {
|
||||||
|
expect(fp().isAbsolute('/home/foo')).toEqual(true);
|
||||||
|
expect(fp().isAbsolute('/home/foo/..')).toEqual(true);
|
||||||
|
expect(fp().isAbsolute('bar/')).toEqual(false);
|
||||||
|
expect(fp().isAbsolute('./baz')).toEqual(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should work (win32)', () => {
|
||||||
|
process.platform = 'win32';
|
||||||
|
expect(fp().isAbsolute('//server/file')).toEqual(true);
|
||||||
|
expect(fp().isAbsolute('\\\\server\\file')).toEqual(true);
|
||||||
|
expect(fp().isAbsolute('C:/Users/')).toEqual(true);
|
||||||
|
expect(fp().isAbsolute('C:\\Users\\')).toEqual(true);
|
||||||
|
expect(fp().isAbsolute('C:cwd/another')).toEqual(false);
|
||||||
|
expect(fp().isAbsolute('C:cwd\\another')).toEqual(false);
|
||||||
|
expect(fp().isAbsolute('directory/directory')).toEqual(false);
|
||||||
|
expect(fp().isAbsolute('directory\\directory')).toEqual(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('relative', () => {
|
||||||
|
const win32RelativeTests = [
|
||||||
|
// arguments result
|
||||||
|
['C:/blah\\blah', 'D:/games', 'D:\\games'],
|
||||||
|
['c:/blah\\blah', 'd:/games', 'd:\\games'],
|
||||||
|
['c:/aaaa/bbbb', 'c:/aaaa', '..'],
|
||||||
|
['c:/aaaa/bbbb', 'c:/cccc', '..\\..\\cccc'],
|
||||||
|
['c:/aaaa/bbbb', 'c:/aaaa/bbbb', ''],
|
||||||
|
['c:/aaaa/bbbb', 'c:/aaaa/cccc', '..\\cccc'],
|
||||||
|
['c:/aaaa/', 'c:/aaaa/cccc', 'cccc'],
|
||||||
|
['c:/', 'c:\\aaaa\\bbbb', 'aaaa\\bbbb'],
|
||||||
|
['c:/aaaa/bbbb', 'd:\\', 'd:\\'],
|
||||||
|
];
|
||||||
|
|
||||||
|
const posixRelativeTests = [
|
||||||
|
// arguments result
|
||||||
|
['/var/lib', '/var', '..'],
|
||||||
|
['/var/lib', '/bin', '../../bin'],
|
||||||
|
['/var/lib', '/var/lib', ''],
|
||||||
|
['/var/lib', '/var/apache', '../apache'],
|
||||||
|
['/var/', '/var/lib', 'lib'],
|
||||||
|
['/', '/var/lib', 'var/lib'],
|
||||||
|
];
|
||||||
|
|
||||||
|
it('should work (posix)', () => {
|
||||||
|
posixRelativeTests.forEach((test) => {
|
||||||
|
const actual = fp().relative(test[0], test[1]);
|
||||||
|
const expected = test[2];
|
||||||
|
|
||||||
|
expect(actual).toEqual(expected, getErrorMessage('relative',
|
||||||
|
test.slice(0, 2).map(JSON.stringify).join(','),
|
||||||
|
expected,
|
||||||
|
actual,
|
||||||
|
));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should work (win32)', () => {
|
||||||
|
process.platform = 'win32';
|
||||||
|
|
||||||
|
win32RelativeTests.forEach((test) => {
|
||||||
|
const actual = fp().relative(test[0], test[1]);
|
||||||
|
const expected = test[2];
|
||||||
|
|
||||||
|
expect(actual).toEqual(expected, getErrorMessage('relative',
|
||||||
|
test.slice(0, 2).map(JSON.stringify).join(','),
|
||||||
|
expected,
|
||||||
|
actual,
|
||||||
|
));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('(static export) sep', () => {
|
||||||
|
it('should be a backslash (win32)', () => {
|
||||||
|
process.platform = 'win32';
|
||||||
|
|
||||||
|
expect(fp().sep).toEqual('\\');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be a forward slash (posix)', () => {
|
||||||
|
expect(fp().sep).toEqual('/');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('(static export) delimiter', () => {
|
||||||
|
it('should be a semicolon (win32)', () => {
|
||||||
|
process.platform = 'win32';
|
||||||
|
expect(fp().delimiter).toEqual(';');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be a colon (posix)', () => {
|
||||||
|
expect(fp().delimiter).toEqual(':');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,13 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const nodeCrawl = require('./node');
|
||||||
|
const watchmanCrawl = require('./watchman');
|
||||||
|
|
||||||
|
function crawl(roots, options) {
|
||||||
|
const {fileWatcher} = options;
|
||||||
|
return (fileWatcher ? fileWatcher.isWatchman() : Promise.resolve(false)).then(
|
||||||
|
isWatchman => isWatchman ? watchmanCrawl(roots, options) : nodeCrawl(roots, options)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = crawl;
|
|
@ -0,0 +1,61 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const denodeify = require('denodeify');
|
||||||
|
const debug = require('debug')('ReactNativePackager:DependencyGraph');
|
||||||
|
const fs = require('graceful-fs');
|
||||||
|
const path = require('../fastpath');
|
||||||
|
|
||||||
|
const readDir = denodeify(fs.readdir);
|
||||||
|
const stat = denodeify(fs.stat);
|
||||||
|
|
||||||
|
function nodeRecReadDir(roots, {ignore, exts}) {
|
||||||
|
const queue = roots.slice();
|
||||||
|
const retFiles = [];
|
||||||
|
const extPattern = new RegExp(
|
||||||
|
'\.(' + exts.join('|') + ')$'
|
||||||
|
);
|
||||||
|
|
||||||
|
function search() {
|
||||||
|
const currDir = queue.shift();
|
||||||
|
if (!currDir) {
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
return readDir(currDir)
|
||||||
|
.then(files => files.map(f => path.join(currDir, f)))
|
||||||
|
.then(files => Promise.all(
|
||||||
|
files.map(f => stat(f).catch(handleBrokenLink))
|
||||||
|
).then(stats => [
|
||||||
|
// Remove broken links.
|
||||||
|
files.filter((file, i) => !!stats[i]),
|
||||||
|
stats.filter(Boolean),
|
||||||
|
]))
|
||||||
|
.then(([files, stats]) => {
|
||||||
|
files.forEach((filePath, i) => {
|
||||||
|
if (ignore(filePath)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stats[i].isDirectory()) {
|
||||||
|
queue.push(filePath);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filePath.match(extPattern)) {
|
||||||
|
retFiles.push(path.resolve(filePath));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return search();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return search().then(() => retFiles);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleBrokenLink(e) {
|
||||||
|
debug('WARNING: error stating, possibly broken symlink', e.message);
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = nodeRecReadDir;
|
|
@ -0,0 +1,76 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const denodeify = require('denodeify');
|
||||||
|
const path = require('../fastpath');
|
||||||
|
|
||||||
|
const watchmanURL = 'https://facebook.github.io/watchman/docs/troubleshooting.html';
|
||||||
|
|
||||||
|
function watchmanRecReadDir(roots, {ignore, fileWatcher, exts}) {
|
||||||
|
const files = [];
|
||||||
|
return Promise.all(
|
||||||
|
roots.map(
|
||||||
|
root => fileWatcher.getWatcherForRoot(root)
|
||||||
|
)
|
||||||
|
).then(
|
||||||
|
watchers => {
|
||||||
|
// All watchman roots for all watches we have.
|
||||||
|
const watchmanRoots = watchers.map(
|
||||||
|
watcher => watcher.watchProjectInfo.root
|
||||||
|
);
|
||||||
|
|
||||||
|
// Actual unique watchers (because we use watch-project we may end up with
|
||||||
|
// duplicate "real" watches, and that's by design).
|
||||||
|
// TODO(amasad): push this functionality into the `FileWatcher`.
|
||||||
|
const uniqueWatchers = watchers.filter(
|
||||||
|
(watcher, i) => watchmanRoots.indexOf(watcher.watchProjectInfo.root) === i
|
||||||
|
);
|
||||||
|
|
||||||
|
return Promise.all(
|
||||||
|
uniqueWatchers.map(watcher => {
|
||||||
|
const watchedRoot = watcher.watchProjectInfo.root;
|
||||||
|
|
||||||
|
// Build up an expression to filter the output by the relevant roots.
|
||||||
|
const dirExpr = ['anyof'];
|
||||||
|
for (let i = 0; i < roots.length; i++) {
|
||||||
|
const root = roots[i];
|
||||||
|
if (isDescendant(watchedRoot, root)) {
|
||||||
|
dirExpr.push(['dirname', path.relative(watchedRoot, root)]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const cmd = denodeify(watcher.client.command.bind(watcher.client));
|
||||||
|
return cmd(['query', watchedRoot, {
|
||||||
|
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 = watchedRoot + path.sep + filePath;
|
||||||
|
if (!ignore(filePath)) {
|
||||||
|
files.push(filePath);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}).then(
|
||||||
|
() => files,
|
||||||
|
error => {
|
||||||
|
throw new Error(
|
||||||
|
`Watchman error: ${error.message.trim()}. Make sure watchman ` +
|
||||||
|
`is running for this project. See ${watchmanURL}.`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function isDescendant(root, child) {
|
||||||
|
return child.startsWith(root);
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = watchmanRecReadDir;
|
|
@ -0,0 +1,362 @@
|
||||||
|
/**
|
||||||
|
* 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 denodeify = require('denodeify');
|
||||||
|
const {EventEmitter} = require('events');
|
||||||
|
|
||||||
|
const fs = require('graceful-fs');
|
||||||
|
const path = require('./fastpath');
|
||||||
|
|
||||||
|
const readFile = denodeify(fs.readFile);
|
||||||
|
const stat = denodeify(fs.stat);
|
||||||
|
|
||||||
|
const NOT_FOUND_IN_ROOTS = 'NotFoundInRootsError';
|
||||||
|
|
||||||
|
class Fastfs extends EventEmitter {
|
||||||
|
constructor(name, roots, fileWatcher, {ignore, crawling, activity}) {
|
||||||
|
super();
|
||||||
|
this._name = name;
|
||||||
|
this._fileWatcher = fileWatcher;
|
||||||
|
this._ignore = ignore;
|
||||||
|
this._roots = roots.map(root => {
|
||||||
|
// If the path ends in a separator ("/"), remove it to make string
|
||||||
|
// operations on paths safer.
|
||||||
|
if (root.endsWith(path.sep)) {
|
||||||
|
root = root.substr(0, root.length - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
root = path.resolve(root);
|
||||||
|
|
||||||
|
return new File(root, true);
|
||||||
|
});
|
||||||
|
this._fastPaths = Object.create(null);
|
||||||
|
this._crawling = crawling;
|
||||||
|
this._activity = activity;
|
||||||
|
}
|
||||||
|
|
||||||
|
build() {
|
||||||
|
return this._crawling.then(files => {
|
||||||
|
let fastfsActivity;
|
||||||
|
const activity = this._activity;
|
||||||
|
if (activity) {
|
||||||
|
fastfsActivity = activity.startEvent('Building in-memory fs for ' + this._name);
|
||||||
|
}
|
||||||
|
files.forEach(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, this._fastPaths);
|
||||||
|
} else {
|
||||||
|
root.addChild(newFile, this._fastPaths);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (activity) {
|
||||||
|
activity.endEvent(fastfsActivity);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this._fileWatcher) {
|
||||||
|
this._fileWatcher.on('all', this._processFileChange.bind(this));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
stat(filePath) {
|
||||||
|
return Promise.resolve().then(() => this._getFile(filePath).stat());
|
||||||
|
}
|
||||||
|
|
||||||
|
getAllFiles() {
|
||||||
|
return Object.keys(this._fastPaths)
|
||||||
|
.filter(filePath => !this._fastPaths[filePath].isDir);
|
||||||
|
}
|
||||||
|
|
||||||
|
findFilesByExts(exts, { ignore } = {}) {
|
||||||
|
return this.getAllFiles()
|
||||||
|
.filter(filePath => (
|
||||||
|
exts.indexOf(path.extname(filePath).substr(1)) !== -1 &&
|
||||||
|
(!ignore || !ignore(filePath))
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
matchFilesByPattern(pattern) {
|
||||||
|
return this.getAllFiles().filter(file => file.match(pattern));
|
||||||
|
}
|
||||||
|
|
||||||
|
readFile(filePath) {
|
||||||
|
const file = this._getFile(filePath);
|
||||||
|
if (!file) {
|
||||||
|
throw new Error(`Unable to find file with path: ${filePath}`);
|
||||||
|
}
|
||||||
|
return file.read();
|
||||||
|
}
|
||||||
|
|
||||||
|
readWhile(filePath, predicate) {
|
||||||
|
const file = this._getFile(filePath);
|
||||||
|
if (!file) {
|
||||||
|
throw new Error(`Unable to find file with path: ${filePath}`);
|
||||||
|
}
|
||||||
|
return file.readWhile(predicate);
|
||||||
|
}
|
||||||
|
|
||||||
|
closest(filePath, name) {
|
||||||
|
for (let file = this._getFile(filePath).parent;
|
||||||
|
file;
|
||||||
|
file = file.parent) {
|
||||||
|
if (file.children[name]) {
|
||||||
|
return file.children[name].path;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
fileExists(filePath) {
|
||||||
|
let file;
|
||||||
|
try {
|
||||||
|
file = this._getFile(filePath);
|
||||||
|
} catch (e) {
|
||||||
|
if (e.type === NOT_FOUND_IN_ROOTS) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
|
||||||
|
return file && !file.isDir;
|
||||||
|
}
|
||||||
|
|
||||||
|
dirExists(filePath) {
|
||||||
|
let file;
|
||||||
|
try {
|
||||||
|
file = this._getFile(filePath);
|
||||||
|
} catch (e) {
|
||||||
|
if (e.type === NOT_FOUND_IN_ROOTS) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
|
||||||
|
return file && file.isDir;
|
||||||
|
}
|
||||||
|
|
||||||
|
matches(dir, pattern) {
|
||||||
|
const dirFile = this._getFile(dir);
|
||||||
|
if (!dirFile.isDir) {
|
||||||
|
throw new Error(`Expected file ${dirFile.path} to be a directory`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Object.keys(dirFile.children)
|
||||||
|
.filter(name => name.match(pattern))
|
||||||
|
.map(name => path.join(dirFile.path, name));
|
||||||
|
}
|
||||||
|
|
||||||
|
_getRoot(filePath) {
|
||||||
|
for (let i = 0; i < this._roots.length; i++) {
|
||||||
|
const possibleRoot = this._roots[i];
|
||||||
|
if (isDescendant(possibleRoot.path, filePath)) {
|
||||||
|
return possibleRoot;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
_getAndAssertRoot(filePath) {
|
||||||
|
const root = this._getRoot(filePath);
|
||||||
|
if (!root) {
|
||||||
|
const error = new Error(`File ${filePath} not found in any of the roots`);
|
||||||
|
error.type = NOT_FOUND_IN_ROOTS;
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
|
||||||
|
_getFile(filePath) {
|
||||||
|
filePath = path.resolve(filePath);
|
||||||
|
if (!this._fastPaths[filePath]) {
|
||||||
|
const file = this._getAndAssertRoot(filePath).getFileFromPath(filePath);
|
||||||
|
if (file) {
|
||||||
|
this._fastPaths[filePath] = file;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return this._fastPaths[filePath];
|
||||||
|
}
|
||||||
|
|
||||||
|
_processFileChange(type, filePath, rootPath, fstat) {
|
||||||
|
const absPath = path.join(rootPath, filePath);
|
||||||
|
if (this._ignore(absPath) || (fstat && fstat.isDirectory())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure this event belongs to one of our roots.
|
||||||
|
const root = this._getRoot(absPath);
|
||||||
|
if (!root) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type === 'delete' || type === 'change') {
|
||||||
|
const file = this._getFile(absPath);
|
||||||
|
if (file) {
|
||||||
|
file.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
delete this._fastPaths[path.resolve(absPath)];
|
||||||
|
|
||||||
|
if (type !== 'delete') {
|
||||||
|
const file = new File(absPath, false);
|
||||||
|
root.addChild(file, this._fastPaths);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.emit('change', type, filePath, rootPath, fstat);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class File {
|
||||||
|
constructor(filePath, isDir) {
|
||||||
|
this.path = filePath;
|
||||||
|
this.isDir = isDir;
|
||||||
|
this.children = this.isDir ? Object.create(null) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
read() {
|
||||||
|
if (!this._read) {
|
||||||
|
this._read = readFile(this.path, 'utf8');
|
||||||
|
}
|
||||||
|
return this._read;
|
||||||
|
}
|
||||||
|
|
||||||
|
readWhile(predicate) {
|
||||||
|
return readWhile(this.path, predicate).then(({result, completed}) => {
|
||||||
|
if (completed && !this._read) {
|
||||||
|
this._read = Promise.resolve(result);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
stat() {
|
||||||
|
if (!this._stat) {
|
||||||
|
this._stat = stat(this.path);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this._stat;
|
||||||
|
}
|
||||||
|
|
||||||
|
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, fileMap);
|
||||||
|
} else {
|
||||||
|
const dir = new File(this.path + path.sep + parts[0], true);
|
||||||
|
dir.parent = this;
|
||||||
|
this.children[parts[0]] = dir;
|
||||||
|
fileMap[dir.path] = dir;
|
||||||
|
dir.addChild(file, fileMap);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getFileFromPath(filePath) {
|
||||||
|
const parts = path.relative(this.path, filePath).split(path.sep);
|
||||||
|
|
||||||
|
/*eslint consistent-this:0*/
|
||||||
|
let file = this;
|
||||||
|
for (let i = 0; i < parts.length; i++) {
|
||||||
|
const fileName = parts[i];
|
||||||
|
if (!fileName) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!file || !file.isDir) {
|
||||||
|
// File not found.
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
file = file.children[fileName];
|
||||||
|
}
|
||||||
|
|
||||||
|
return file;
|
||||||
|
}
|
||||||
|
|
||||||
|
ext() {
|
||||||
|
return path.extname(this.path).substr(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
remove() {
|
||||||
|
if (!this.parent) {
|
||||||
|
throw new Error(`No parent to delete ${this.path} from`);
|
||||||
|
}
|
||||||
|
|
||||||
|
delete this.parent.children[path.basename(this.path)];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function readWhile(filePath, predicate) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
fs.open(filePath, 'r', (openError, fd) => {
|
||||||
|
if (openError) {
|
||||||
|
reject(openError);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
read(
|
||||||
|
fd,
|
||||||
|
/*global Buffer: true*/
|
||||||
|
new Buffer(512),
|
||||||
|
makeReadCallback(fd, predicate, (readError, result, completed) => {
|
||||||
|
if (readError) {
|
||||||
|
reject(readError);
|
||||||
|
} else {
|
||||||
|
resolve({result, completed});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function read(fd, buffer, callback) {
|
||||||
|
fs.read(fd, buffer, 0, buffer.length, -1, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
function close(fd, error, result, complete, callback) {
|
||||||
|
fs.close(fd, closeError => callback(error || closeError, result, complete));
|
||||||
|
}
|
||||||
|
|
||||||
|
function makeReadCallback(fd, predicate, callback) {
|
||||||
|
let result = '';
|
||||||
|
let index = 0;
|
||||||
|
return function readCallback(error, bytesRead, buffer) {
|
||||||
|
if (error) {
|
||||||
|
close(fd, error, undefined, false, callback);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const completed = bytesRead === 0;
|
||||||
|
const chunk = completed ? '' : buffer.toString('utf8', 0, bytesRead);
|
||||||
|
result += chunk;
|
||||||
|
if (completed || !predicate(chunk, index++, result)) {
|
||||||
|
close(fd, null, result, completed, callback);
|
||||||
|
} else {
|
||||||
|
read(fd, buffer, readCallback);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function isDescendant(root, child) {
|
||||||
|
return child.startsWith(root);
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = Fastfs;
|
|
@ -0,0 +1,619 @@
|
||||||
|
/*!
|
||||||
|
* fast-path - index.js
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright Joyent, Inc. and other Node contributors.
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a
|
||||||
|
* copy of this software and associated documentation files (the
|
||||||
|
* "Software"), to deal in the Software without restriction, including
|
||||||
|
* without limitation the rights to use, copy, modify, merge, publish,
|
||||||
|
* distribute, sublicense, and/or sell copies of the Software, and to permit
|
||||||
|
* persons to whom the Software is furnished to do so, subject to the
|
||||||
|
* following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included
|
||||||
|
* in all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||||
|
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
|
||||||
|
* NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
||||||
|
* DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
||||||
|
* OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
|
||||||
|
* USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*
|
||||||
|
* original author: dead_horse <dead_horse@qq.com>
|
||||||
|
* ported by: yaycmyk <evan@yaycmyk.com>
|
||||||
|
*
|
||||||
|
* VERSION 1.2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
const util = require('util');
|
||||||
|
|
||||||
|
const all = Object.keys(exports).filter((name) => name !== 'replace');
|
||||||
|
const IS_WINDOWS = process.platform === 'win32';
|
||||||
|
|
||||||
|
function isString(arg) {
|
||||||
|
return typeof arg === 'string';
|
||||||
|
}
|
||||||
|
|
||||||
|
const splitDeviceRe =
|
||||||
|
/^([a-zA-Z]:|[\\\/]{2}[^\\\/]+[\\\/]+[^\\\/]+)?([\\\/])?([\s\S]*?)$/;
|
||||||
|
|
||||||
|
function getDevice(filename) {
|
||||||
|
const result = splitDeviceRe.exec(filename);
|
||||||
|
|
||||||
|
return (result[1] || '') + (result[2] || '');
|
||||||
|
}
|
||||||
|
|
||||||
|
// resolves . and .. elements in a path array with directory names there
|
||||||
|
// must be no slashes, or device names (c:\) in the array
|
||||||
|
// (so also no leading and trailing slashes - it does not distinguish
|
||||||
|
// relative and absolute paths)
|
||||||
|
function normalizeArray(parts, allowAboveRoot) {
|
||||||
|
const nonEmptyParts = [];
|
||||||
|
let nonBack = true;
|
||||||
|
|
||||||
|
for (let i = 0; i < parts.length; i++) {
|
||||||
|
const p = parts[i];
|
||||||
|
|
||||||
|
if (p && p !== '.') {
|
||||||
|
nonEmptyParts.push(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (p === '..') {
|
||||||
|
nonBack = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
parts = nonEmptyParts;
|
||||||
|
|
||||||
|
// if the path does not contain ..
|
||||||
|
if (nonBack) {
|
||||||
|
return parts;
|
||||||
|
}
|
||||||
|
|
||||||
|
// if the path tries to go ab ove the root, `up` ends up > 0
|
||||||
|
let up = 0;
|
||||||
|
const res = [];
|
||||||
|
|
||||||
|
for (let i = parts.length - 1; i >= 0; i--) {
|
||||||
|
if (parts[i] === '..') {
|
||||||
|
up++;
|
||||||
|
} else if (up) {
|
||||||
|
up--;
|
||||||
|
} else {
|
||||||
|
res.push(parts[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// if the path is allowed to go above the root, restore leading ..s
|
||||||
|
if (allowAboveRoot) {
|
||||||
|
for (; up--; up) {
|
||||||
|
res.push('..');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.reverse();
|
||||||
|
}
|
||||||
|
|
||||||
|
function trim(arr) {
|
||||||
|
let start = 0;
|
||||||
|
|
||||||
|
for (; start < arr.length; start++) {
|
||||||
|
if (arr[start] !== '') { break; }
|
||||||
|
}
|
||||||
|
|
||||||
|
let end = arr.length - 1;
|
||||||
|
|
||||||
|
for (; end >= 0; end--) {
|
||||||
|
if (arr[end] !== '') { break; }
|
||||||
|
}
|
||||||
|
|
||||||
|
return start <= end ? arr.slice(start, end + 1) : [];
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeUNCRoot(device) {
|
||||||
|
return '\\\\' + device.replace(/^[\\\/]+/, '').replace(/[\\\/]+/g, '\\');
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.sep = IS_WINDOWS ? '\\' : '/';
|
||||||
|
exports.delimiter = IS_WINDOWS ? ';' : ':';
|
||||||
|
|
||||||
|
if (IS_WINDOWS) {
|
||||||
|
// path.resolve([from ...], to)
|
||||||
|
// windows version
|
||||||
|
exports.resolve = function resolveWIN32() {
|
||||||
|
let resolvedDevice = '';
|
||||||
|
let resolvedTail = '';
|
||||||
|
let resolvedAbsolute = false;
|
||||||
|
|
||||||
|
for (let i = arguments.length - 1; i >= -1; i--) {
|
||||||
|
let path;
|
||||||
|
|
||||||
|
if (i >= 0) {
|
||||||
|
path = arguments[i];
|
||||||
|
} else if (!resolvedDevice) {
|
||||||
|
path = process.cwd();
|
||||||
|
} else {
|
||||||
|
// Windows has the concept of drive-specific current working
|
||||||
|
// directories. If we've resolved a drive letter but not yet an
|
||||||
|
// absolute path, get cwd for that drive. We're sure the device is not
|
||||||
|
// an unc path at this points, because unc paths are always absolute.
|
||||||
|
path = process.env['=' + resolvedDevice];
|
||||||
|
|
||||||
|
// Verify that a drive-local cwd was found and that it actually points
|
||||||
|
// to our drive. If not, default to the drive's root.
|
||||||
|
if (!path || path.substr(0, 3).toLowerCase() !==
|
||||||
|
resolvedDevice.toLowerCase() + '\\') {
|
||||||
|
path = resolvedDevice + '\\';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip empty and invalid entries
|
||||||
|
if (!isString(path)) {
|
||||||
|
throw new TypeError('Arguments to path.resolve must be strings');
|
||||||
|
} else if (!path) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = splitDeviceRe.exec(path);
|
||||||
|
const device = result[1] || '';
|
||||||
|
const isUnc = device && device.charAt(1) !== ':';
|
||||||
|
const isAbsolute = exports.isAbsolute(path);
|
||||||
|
const tail = result[3];
|
||||||
|
|
||||||
|
if (device &&
|
||||||
|
resolvedDevice &&
|
||||||
|
device.toLowerCase() !== resolvedDevice.toLowerCase()) {
|
||||||
|
// This path points to another device so it is not applicable
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!resolvedDevice) {
|
||||||
|
resolvedDevice = device;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!resolvedAbsolute) {
|
||||||
|
resolvedTail = tail + '\\' + resolvedTail;
|
||||||
|
resolvedAbsolute = isAbsolute;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert slashes to backslashes when `resolvedDevice` points to an UNC
|
||||||
|
// root. Also squash multiple slashes into a single one where appropriate.
|
||||||
|
if (isUnc) {
|
||||||
|
resolvedDevice = normalizeUNCRoot(resolvedDevice);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (resolvedDevice && resolvedAbsolute) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// At this point the path should be resolved to a full absolute path,
|
||||||
|
// but handle relative paths to be safe (might happen when process.cwd()
|
||||||
|
// fails)
|
||||||
|
|
||||||
|
// Normalize the tail path
|
||||||
|
resolvedTail = normalizeArray(
|
||||||
|
resolvedTail.split(/[\\\/]+/),
|
||||||
|
!resolvedAbsolute
|
||||||
|
).join('\\');
|
||||||
|
|
||||||
|
return (resolvedDevice + (resolvedAbsolute ? '\\' : '') + resolvedTail)
|
||||||
|
|| '.';
|
||||||
|
};
|
||||||
|
|
||||||
|
// windows version
|
||||||
|
exports.normalize = function normalizeWIN32(path) {
|
||||||
|
const result = splitDeviceRe.exec(path);
|
||||||
|
let device = result[1] || '';
|
||||||
|
const isUnc = device && device.charAt(1) !== ':';
|
||||||
|
const isAbsolute = exports.isAbsolute(path);
|
||||||
|
let tail = result[3];
|
||||||
|
const trailingSlash = /[\\\/]$/.test(tail);
|
||||||
|
|
||||||
|
// Normalize the tail path
|
||||||
|
tail = normalizeArray(tail.split(/[\\\/]+/), !isAbsolute).join('\\');
|
||||||
|
|
||||||
|
if (!tail && !isAbsolute) {
|
||||||
|
tail = '.';
|
||||||
|
}
|
||||||
|
if (tail && trailingSlash) {
|
||||||
|
tail += '\\';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert slashes to backslashes when `device` points to an UNC root.
|
||||||
|
// Also squash multiple slashes into a single one where appropriate.
|
||||||
|
if (isUnc) {
|
||||||
|
device = normalizeUNCRoot(device);
|
||||||
|
}
|
||||||
|
|
||||||
|
return device + (isAbsolute ? '\\' : '') + tail;
|
||||||
|
};
|
||||||
|
|
||||||
|
// windows version
|
||||||
|
exports.isAbsolute = function isAbsoluteWIN32(path) {
|
||||||
|
const result = splitDeviceRe.exec(path);
|
||||||
|
const device = result[1] || '';
|
||||||
|
const isUnc = !!device && device.charAt(1) !== ':';
|
||||||
|
|
||||||
|
// UNC paths are always absolute
|
||||||
|
return !!result[2] || isUnc;
|
||||||
|
};
|
||||||
|
|
||||||
|
// windows version
|
||||||
|
exports.join = function joinWIN32() {
|
||||||
|
function f(p) {
|
||||||
|
if (!isString(p)) {
|
||||||
|
throw new TypeError('Arguments to path.join must be strings');
|
||||||
|
}
|
||||||
|
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
|
||||||
|
const paths = Array.prototype.filter.call(arguments, f);
|
||||||
|
let joined = paths.join('\\');
|
||||||
|
|
||||||
|
// Make sure that the joined path doesn't start with two slashes, because
|
||||||
|
// normalize() will mistake it for an UNC path then.
|
||||||
|
//
|
||||||
|
// This step is skipped when it is very clear that the user actually
|
||||||
|
// intended to point at an UNC path. This is assumed when the first
|
||||||
|
// non-empty string arguments starts with exactly two slashes followed by
|
||||||
|
// at least one more non-slash character.
|
||||||
|
//
|
||||||
|
// Note that for normalize() to treat a path as an UNC path it needs to
|
||||||
|
// have at least 2 components, so we don't filter for that here.
|
||||||
|
// This means that the user can use join to construct UNC paths from
|
||||||
|
// a server name and a share name; for example:
|
||||||
|
// path.join('//server', 'share') -> '\\\\server\\share\')
|
||||||
|
if (!/^[\\\/]{2}[^\\\/]/.test(paths[0])) {
|
||||||
|
joined = joined.replace(/^[\\\/]{2,}/, '\\');
|
||||||
|
}
|
||||||
|
|
||||||
|
return exports.normalize(joined);
|
||||||
|
};
|
||||||
|
|
||||||
|
// path.relative(from, to)
|
||||||
|
// it will solve the relative path from 'from' to 'to', for instance:
|
||||||
|
// from = 'C:\\orandea\\test\\aaa'
|
||||||
|
// to = 'C:\\orandea\\impl\\bbb'
|
||||||
|
// The output of the function should be: '..\\..\\impl\\bbb'
|
||||||
|
// windows version
|
||||||
|
exports.relative = function relativeWIN32(from, to) {
|
||||||
|
from = exports.resolve(from);
|
||||||
|
to = exports.resolve(to);
|
||||||
|
|
||||||
|
// windows is not case sensitive
|
||||||
|
const lowerFrom = from.toLowerCase();
|
||||||
|
const lowerTo = to.toLowerCase();
|
||||||
|
|
||||||
|
const toParts = trim(to.split('\\'));
|
||||||
|
|
||||||
|
const lowerFromParts = trim(lowerFrom.split('\\'));
|
||||||
|
const lowerToParts = trim(lowerTo.split('\\'));
|
||||||
|
|
||||||
|
const length = Math.min(lowerFromParts.length, lowerToParts.length);
|
||||||
|
let samePartsLength = length;
|
||||||
|
|
||||||
|
for (let i = 0; i < length; i++) {
|
||||||
|
if (lowerFromParts[i] !== lowerToParts[i]) {
|
||||||
|
samePartsLength = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (samePartsLength == 0) {
|
||||||
|
return to;
|
||||||
|
}
|
||||||
|
|
||||||
|
let outputParts = [];
|
||||||
|
|
||||||
|
for (let i = samePartsLength; i < lowerFromParts.length; i++) {
|
||||||
|
outputParts.push('..');
|
||||||
|
}
|
||||||
|
|
||||||
|
outputParts = outputParts.concat(toParts.slice(samePartsLength));
|
||||||
|
|
||||||
|
return outputParts.join('\\');
|
||||||
|
};
|
||||||
|
|
||||||
|
} else /* posix */ {
|
||||||
|
|
||||||
|
// path.resolve([from ...], to)
|
||||||
|
// posix version
|
||||||
|
exports.resolve = function resolvePOSIX() {
|
||||||
|
let resolvedPath = '';
|
||||||
|
let resolvedAbsolute = false;
|
||||||
|
|
||||||
|
for (let i = arguments.length - 1; i >= -1 && !resolvedAbsolute; i--) {
|
||||||
|
const path = i >= 0 ? arguments[i] : process.cwd();
|
||||||
|
|
||||||
|
// Skip empty and invalid entries
|
||||||
|
if (!isString(path)) {
|
||||||
|
throw new TypeError('Arguments to path.resolve must be strings');
|
||||||
|
} else if (!path) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
resolvedPath = path + '/' + resolvedPath;
|
||||||
|
resolvedAbsolute = path.charAt(0) === '/';
|
||||||
|
}
|
||||||
|
|
||||||
|
// At this point the path should be resolved to a full absolute path, but
|
||||||
|
// handle relative paths to be safe (might happen when process.cwd() fails)
|
||||||
|
|
||||||
|
// Normalize the path
|
||||||
|
resolvedPath = normalizeArray(
|
||||||
|
resolvedPath.split('/'),
|
||||||
|
!resolvedAbsolute
|
||||||
|
).join('/');
|
||||||
|
|
||||||
|
return ((resolvedAbsolute ? '/' : '') + resolvedPath) || '.';
|
||||||
|
};
|
||||||
|
|
||||||
|
// path.normalize(path)
|
||||||
|
// posix version
|
||||||
|
exports.normalize = function normalizePOSIX(path) {
|
||||||
|
const isAbsolute = exports.isAbsolute(path);
|
||||||
|
const trailingSlash = path[path.length - 1] === '/';
|
||||||
|
|
||||||
|
// normalize the path
|
||||||
|
path = normalizeArray(path.split('/'), !isAbsolute).join('/');
|
||||||
|
|
||||||
|
if (!path && !isAbsolute) {
|
||||||
|
path = '.';
|
||||||
|
}
|
||||||
|
if (path && trailingSlash) {
|
||||||
|
path += '/';
|
||||||
|
}
|
||||||
|
|
||||||
|
return (isAbsolute ? '/' : '') + path;
|
||||||
|
};
|
||||||
|
|
||||||
|
// posix version
|
||||||
|
exports.isAbsolute = function isAbsolutePOSIX(path) {
|
||||||
|
return path.charAt(0) === '/';
|
||||||
|
};
|
||||||
|
|
||||||
|
// posix version
|
||||||
|
exports.join = function joinPOSIX() {
|
||||||
|
let path = '';
|
||||||
|
|
||||||
|
for (let i = 0; i < arguments.length; i++) {
|
||||||
|
const segment = arguments[i];
|
||||||
|
|
||||||
|
if (!isString(segment)) {
|
||||||
|
throw new TypeError('Arguments to path.join must be strings');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (segment) {
|
||||||
|
path += `${path ? '/' : ''}${segment}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return exports.normalize(path);
|
||||||
|
};
|
||||||
|
|
||||||
|
// path.relative(from, to)
|
||||||
|
// posix version
|
||||||
|
exports.relative = function relativePOSIX(from, to) {
|
||||||
|
from = exports.resolve(from).substr(1);
|
||||||
|
to = exports.resolve(to).substr(1);
|
||||||
|
|
||||||
|
const fromParts = trim(from.split('/'));
|
||||||
|
const toParts = trim(to.split('/'));
|
||||||
|
|
||||||
|
const length = Math.min(fromParts.length, toParts.length);
|
||||||
|
let samePartsLength = length;
|
||||||
|
|
||||||
|
for (let i = 0; i < length; i++) {
|
||||||
|
if (fromParts[i] !== toParts[i]) {
|
||||||
|
samePartsLength = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let outputParts = [];
|
||||||
|
|
||||||
|
for (let i = samePartsLength; i < fromParts.length; i++) {
|
||||||
|
outputParts.push('..');
|
||||||
|
}
|
||||||
|
|
||||||
|
outputParts = outputParts.concat(toParts.slice(samePartsLength));
|
||||||
|
|
||||||
|
return outputParts.join('/');
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.exists = util.deprecate(function exists(path, callback) {
|
||||||
|
return fs.exists(path, callback);
|
||||||
|
}, 'path.exists is now called `fs.exists`.');
|
||||||
|
|
||||||
|
exports.existsSync = util.deprecate(function existsSync(path) {
|
||||||
|
return fs.existsSync(path);
|
||||||
|
}, 'path.existsSync is now called `fs.existsSync`.');
|
||||||
|
|
||||||
|
if (IS_WINDOWS) {
|
||||||
|
exports._makeLong = function _makeLongWIN32(path) {
|
||||||
|
// Note: this will *probably* throw somewhere.
|
||||||
|
if (!isString(path)) { return path; }
|
||||||
|
if (!path) { return ''; }
|
||||||
|
|
||||||
|
const resolvedPath = exports.resolve(path);
|
||||||
|
|
||||||
|
if (/^[a-zA-Z]\:\\/.test(resolvedPath)) {
|
||||||
|
// path is local filesystem path, which needs to be converted
|
||||||
|
// to long UNC path.
|
||||||
|
return '\\\\?\\' + resolvedPath;
|
||||||
|
} else if (/^\\\\[^?.]/.test(resolvedPath)) {
|
||||||
|
// path is network UNC path, which needs to be converted
|
||||||
|
// to long UNC path.
|
||||||
|
return '\\\\?\\UNC\\' + resolvedPath.substring(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
return path;
|
||||||
|
};
|
||||||
|
|
||||||
|
} else {
|
||||||
|
exports._makeLong = function _makeLongPOSIX(path) {
|
||||||
|
return path;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.extname = function _extname(filename) {
|
||||||
|
if (!filename) { return ''; }
|
||||||
|
|
||||||
|
// /a.js///
|
||||||
|
let end = filename.length;
|
||||||
|
let c = filename[end - 1];
|
||||||
|
|
||||||
|
while (c === exports.sep || c === '/') {
|
||||||
|
end--;
|
||||||
|
c = filename[end - 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
let lastDot = -1;
|
||||||
|
let lastSep = -1;
|
||||||
|
|
||||||
|
for (let i = end; i--;) {
|
||||||
|
const ch = filename[i];
|
||||||
|
|
||||||
|
if (lastDot === -1 && ch === '.') {
|
||||||
|
lastDot = i;
|
||||||
|
} else if (lastSep === -1 && ch === '/') {
|
||||||
|
lastSep = i;
|
||||||
|
} else if (IS_WINDOWS && lastSep === -1 && ch === '\\') {
|
||||||
|
lastSep = i;
|
||||||
|
}
|
||||||
|
|
||||||
|
// /xxx
|
||||||
|
if (lastSep !== -1 && lastDot === -1) { return ''; }
|
||||||
|
// /*.js
|
||||||
|
if (lastDot !== -1 && i === lastDot - 2) { break; }
|
||||||
|
// /.js
|
||||||
|
if (lastSep !== -1 && lastDot !== -1) { break; }
|
||||||
|
}
|
||||||
|
|
||||||
|
// ./js and /.js
|
||||||
|
if (lastDot < lastSep + 2) { return ''; }
|
||||||
|
|
||||||
|
const extname = filename.slice(lastDot, end);
|
||||||
|
|
||||||
|
if (extname === '.' && filename[lastDot - 1] === '.') {
|
||||||
|
// ..
|
||||||
|
if (lastDot === 1) { return ''; }
|
||||||
|
|
||||||
|
const pre = filename[lastDot - 2];
|
||||||
|
// [//\/]..
|
||||||
|
if (pre === '/' || pre === exports.sep) { return ''; }
|
||||||
|
}
|
||||||
|
|
||||||
|
return extname;
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.basename = function _basename(filename, ext) {
|
||||||
|
if (!filename) { return ''; }
|
||||||
|
|
||||||
|
// /a.js///
|
||||||
|
let end = filename.length;
|
||||||
|
let c = filename[end - 1];
|
||||||
|
|
||||||
|
while (c === exports.sep || c === '/') {
|
||||||
|
end--;
|
||||||
|
c = filename[end - 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
let lastSep = -1;
|
||||||
|
|
||||||
|
for (let i = end; i--;) {
|
||||||
|
const ch = filename[i];
|
||||||
|
|
||||||
|
if (lastSep === -1 && ch === '/') {
|
||||||
|
lastSep = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IS_WINDOWS && lastSep === -1 && ch === '\\') {
|
||||||
|
lastSep = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const basename = filename.slice(lastSep + 1, end);
|
||||||
|
|
||||||
|
if (ext) {
|
||||||
|
const match = basename.lastIndexOf(ext);
|
||||||
|
|
||||||
|
if (match === -1 || match !== basename.length - ext.length) {
|
||||||
|
return basename;
|
||||||
|
}
|
||||||
|
|
||||||
|
return basename.slice(0, basename.length - ext.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
return basename;
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.dirname = function _dirname(filename) {
|
||||||
|
if (!filename) { return '.'; }
|
||||||
|
|
||||||
|
let start = 0;
|
||||||
|
let device = '';
|
||||||
|
|
||||||
|
if (IS_WINDOWS) {
|
||||||
|
// need to get device in windows
|
||||||
|
device = getDevice(filename);
|
||||||
|
|
||||||
|
if (device) { start = device.length; }
|
||||||
|
}
|
||||||
|
|
||||||
|
// /a.js///
|
||||||
|
let end = filename.length;
|
||||||
|
let c = filename[end - 1];
|
||||||
|
|
||||||
|
while (end >= start && c === exports.sep || c === '/') {
|
||||||
|
end--;
|
||||||
|
c = filename[end - 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
let lastSep = -1;
|
||||||
|
|
||||||
|
for (let i = end; i-- > start;) {
|
||||||
|
const ch = filename[i];
|
||||||
|
|
||||||
|
if (lastSep === -1 && ch === '/') {
|
||||||
|
lastSep = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IS_WINDOWS && lastSep === -1 && ch === '\\') {
|
||||||
|
lastSep = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lastSep <= start) {
|
||||||
|
if (device) { return device; }
|
||||||
|
if (filename[0] === '/' || filename[0] === exports.sep) { return filename[0]; }
|
||||||
|
|
||||||
|
return '.';
|
||||||
|
}
|
||||||
|
|
||||||
|
return device + filename.slice(start, lastSep);
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.replace = function(props) {
|
||||||
|
if (!props) { props = all; }
|
||||||
|
if (!Array.isArray(props)) { props = [props]; }
|
||||||
|
|
||||||
|
props.forEach(function(name) {
|
||||||
|
if (exports[name]) { path[name] = exports[name]; }
|
||||||
|
});
|
||||||
|
};
|
|
@ -0,0 +1,332 @@
|
||||||
|
/**
|
||||||
|
* 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 Cache = require('./Cache');
|
||||||
|
const Fastfs = require('./fastfs');
|
||||||
|
const FileWatcher = require('./FileWatcher');
|
||||||
|
const Module = require('./Module');
|
||||||
|
const ModuleCache = require('./ModuleCache');
|
||||||
|
const Polyfill = require('./Polyfill');
|
||||||
|
const crawl = require('./crawlers');
|
||||||
|
const extractRequires = require('./lib/extractRequires');
|
||||||
|
const getAssetDataFromName = require('./lib/getAssetDataFromName');
|
||||||
|
const getInverseDependencies = require('./lib/getInverseDependencies');
|
||||||
|
const getPlatformExtension = require('./lib/getPlatformExtension');
|
||||||
|
const isAbsolutePath = require('absolute-path');
|
||||||
|
const replacePatterns = require('./lib/replacePatterns');
|
||||||
|
const path = require('./fastpath');
|
||||||
|
const util = require('util');
|
||||||
|
const DependencyGraphHelpers = require('./DependencyGraph/DependencyGraphHelpers');
|
||||||
|
const ResolutionRequest = require('./DependencyGraph/ResolutionRequest');
|
||||||
|
const ResolutionResponse = require('./DependencyGraph/ResolutionResponse');
|
||||||
|
const HasteMap = require('./DependencyGraph/HasteMap');
|
||||||
|
const DeprecatedAssetMap = require('./DependencyGraph/DeprecatedAssetMap');
|
||||||
|
|
||||||
|
const ERROR_BUILDING_DEP_GRAPH = 'DependencyGraphError';
|
||||||
|
|
||||||
|
const defaultActivity = {
|
||||||
|
startEvent: () => {},
|
||||||
|
endEvent: () => {},
|
||||||
|
};
|
||||||
|
|
||||||
|
class DependencyGraph {
|
||||||
|
constructor({
|
||||||
|
activity,
|
||||||
|
roots,
|
||||||
|
ignoreFilePath,
|
||||||
|
fileWatcher,
|
||||||
|
assetRoots_DEPRECATED,
|
||||||
|
assetExts,
|
||||||
|
providesModuleNodeModules,
|
||||||
|
platforms,
|
||||||
|
preferNativePlatform,
|
||||||
|
cache,
|
||||||
|
extensions,
|
||||||
|
mocksPattern,
|
||||||
|
extractRequires,
|
||||||
|
transformCode,
|
||||||
|
shouldThrowOnUnresolvedErrors = () => true,
|
||||||
|
enableAssetMap,
|
||||||
|
assetDependencies,
|
||||||
|
moduleOptions,
|
||||||
|
extraNodeModules,
|
||||||
|
}) {
|
||||||
|
this._opts = {
|
||||||
|
activity: activity || defaultActivity,
|
||||||
|
roots,
|
||||||
|
ignoreFilePath: ignoreFilePath || (() => {}),
|
||||||
|
fileWatcher,
|
||||||
|
assetRoots_DEPRECATED: assetRoots_DEPRECATED || [],
|
||||||
|
assetExts: assetExts || [],
|
||||||
|
providesModuleNodeModules,
|
||||||
|
platforms: new Set(platforms || []),
|
||||||
|
preferNativePlatform: preferNativePlatform || false,
|
||||||
|
extensions: extensions || ['js', 'json'],
|
||||||
|
mocksPattern,
|
||||||
|
extractRequires,
|
||||||
|
transformCode,
|
||||||
|
shouldThrowOnUnresolvedErrors,
|
||||||
|
enableAssetMap: enableAssetMap || true,
|
||||||
|
moduleOptions: moduleOptions || {
|
||||||
|
cacheTransformResults: true,
|
||||||
|
},
|
||||||
|
extraNodeModules,
|
||||||
|
};
|
||||||
|
this._cache = cache;
|
||||||
|
this._assetDependencies = assetDependencies;
|
||||||
|
this._helpers = new DependencyGraphHelpers(this._opts);
|
||||||
|
this.load();
|
||||||
|
}
|
||||||
|
|
||||||
|
load() {
|
||||||
|
if (this._loading) {
|
||||||
|
return this._loading;
|
||||||
|
}
|
||||||
|
|
||||||
|
const {activity} = this._opts;
|
||||||
|
const depGraphActivity = activity.startEvent('Building Dependency Graph');
|
||||||
|
const crawlActivity = activity.startEvent('Crawling File System');
|
||||||
|
const allRoots = this._opts.roots.concat(this._opts.assetRoots_DEPRECATED);
|
||||||
|
this._crawling = crawl(allRoots, {
|
||||||
|
ignore: this._opts.ignoreFilePath,
|
||||||
|
exts: this._opts.extensions.concat(this._opts.assetExts),
|
||||||
|
fileWatcher: this._opts.fileWatcher,
|
||||||
|
});
|
||||||
|
this._crawling.then((files) => activity.endEvent(crawlActivity));
|
||||||
|
|
||||||
|
this._fastfs = new Fastfs(
|
||||||
|
'JavaScript',
|
||||||
|
this._opts.roots,
|
||||||
|
this._opts.fileWatcher,
|
||||||
|
{
|
||||||
|
ignore: this._opts.ignoreFilePath,
|
||||||
|
crawling: this._crawling,
|
||||||
|
activity: activity,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
this._fastfs.on('change', this._processFileChange.bind(this));
|
||||||
|
|
||||||
|
this._moduleCache = new ModuleCache({
|
||||||
|
fastfs: this._fastfs,
|
||||||
|
cache: this._cache,
|
||||||
|
extractRequires: this._opts.extractRequires,
|
||||||
|
transformCode: this._opts.transformCode,
|
||||||
|
depGraphHelpers: this._helpers,
|
||||||
|
assetDependencies: this._assetDependencies,
|
||||||
|
moduleOptions: this._opts.moduleOptions,
|
||||||
|
}, this._opts.platfomrs);
|
||||||
|
|
||||||
|
this._hasteMap = new HasteMap({
|
||||||
|
fastfs: this._fastfs,
|
||||||
|
extensions: this._opts.extensions,
|
||||||
|
moduleCache: this._moduleCache,
|
||||||
|
preferNativePlatform: this._opts.preferNativePlatform,
|
||||||
|
helpers: this._helpers,
|
||||||
|
platforms: this._opts.platforms,
|
||||||
|
});
|
||||||
|
|
||||||
|
this._deprecatedAssetMap = new DeprecatedAssetMap({
|
||||||
|
fsCrawl: this._crawling,
|
||||||
|
roots: this._opts.assetRoots_DEPRECATED,
|
||||||
|
helpers: this._helpers,
|
||||||
|
fileWatcher: this._opts.fileWatcher,
|
||||||
|
ignoreFilePath: this._opts.ignoreFilePath,
|
||||||
|
assetExts: this._opts.assetExts,
|
||||||
|
activity: this._opts.activity,
|
||||||
|
enabled: this._opts.enableAssetMap,
|
||||||
|
platforms: this._opts.platforms,
|
||||||
|
});
|
||||||
|
|
||||||
|
this._loading = Promise.all([
|
||||||
|
this._fastfs.build()
|
||||||
|
.then(() => {
|
||||||
|
const hasteActivity = activity.startEvent('Building Haste Map');
|
||||||
|
return this._hasteMap.build().then(map => {
|
||||||
|
activity.endEvent(hasteActivity);
|
||||||
|
return map;
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
this._deprecatedAssetMap.build(),
|
||||||
|
]).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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a promise with the direct dependencies the module associated to
|
||||||
|
* the given entryPath has.
|
||||||
|
*/
|
||||||
|
getShallowDependencies(entryPath, transformOptions) {
|
||||||
|
return this._moduleCache
|
||||||
|
.getModule(entryPath)
|
||||||
|
.getDependencies(transformOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
getFS() {
|
||||||
|
return this._fastfs;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the module object for the given path.
|
||||||
|
*/
|
||||||
|
getModuleForPath(entryFile) {
|
||||||
|
return this._moduleCache.getModule(entryFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
getAllModules() {
|
||||||
|
return this.load().then(() => this._moduleCache.getAllModules());
|
||||||
|
}
|
||||||
|
|
||||||
|
getDependencies({
|
||||||
|
entryPath,
|
||||||
|
platform,
|
||||||
|
transformOptions,
|
||||||
|
onProgress,
|
||||||
|
recursive = true,
|
||||||
|
}) {
|
||||||
|
return this.load().then(() => {
|
||||||
|
platform = this._getRequestPlatform(entryPath, platform);
|
||||||
|
const absPath = this._getAbsolutePath(entryPath);
|
||||||
|
const req = new ResolutionRequest({
|
||||||
|
platform,
|
||||||
|
platforms: this._opts.platforms,
|
||||||
|
preferNativePlatform: this._opts.preferNativePlatform,
|
||||||
|
entryPath: absPath,
|
||||||
|
deprecatedAssetMap: this._deprecatedAssetMap,
|
||||||
|
hasteMap: this._hasteMap,
|
||||||
|
helpers: this._helpers,
|
||||||
|
moduleCache: this._moduleCache,
|
||||||
|
fastfs: this._fastfs,
|
||||||
|
shouldThrowOnUnresolvedErrors: this._opts.shouldThrowOnUnresolvedErrors,
|
||||||
|
extraNodeModules: this._opts.extraNodeModules,
|
||||||
|
});
|
||||||
|
|
||||||
|
const response = new ResolutionResponse({transformOptions});
|
||||||
|
|
||||||
|
return req.getOrderedDependencies({
|
||||||
|
response,
|
||||||
|
mocksPattern: this._opts.mocksPattern,
|
||||||
|
transformOptions,
|
||||||
|
onProgress,
|
||||||
|
recursive,
|
||||||
|
}).then(() => response);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
matchFilesByPattern(pattern) {
|
||||||
|
return this.load().then(() => this._fastfs.matchFilesByPattern(pattern));
|
||||||
|
}
|
||||||
|
|
||||||
|
_getRequestPlatform(entryPath, platform) {
|
||||||
|
if (platform == null) {
|
||||||
|
platform = getPlatformExtension(entryPath, this._opts.platforms);
|
||||||
|
} else if (!this._opts.platforms.has(platform)) {
|
||||||
|
throw new Error('Unrecognized platform: ' + platform);
|
||||||
|
}
|
||||||
|
return platform;
|
||||||
|
}
|
||||||
|
|
||||||
|
_getAbsolutePath(filePath) {
|
||||||
|
if (isAbsolutePath(filePath)) {
|
||||||
|
return path.resolve(filePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = 0; i < this._opts.roots.length; i++) {
|
||||||
|
const root = this._opts.roots[i];
|
||||||
|
const potentialAbsPath = path.join(root, filePath);
|
||||||
|
if (this._fastfs.fileExists(potentialAbsPath)) {
|
||||||
|
return path.resolve(potentialAbsPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new NotFoundError(
|
||||||
|
'Cannot find entry file %s in any of the roots: %j',
|
||||||
|
filePath,
|
||||||
|
this._opts.roots
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
_processFileChange(type, filePath, root, fstat) {
|
||||||
|
const absPath = path.join(root, filePath);
|
||||||
|
if (fstat && fstat.isDirectory() ||
|
||||||
|
this._opts.ignoreFilePath(absPath) ||
|
||||||
|
this._helpers.isNodeModulesDir(absPath)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ok, this is some tricky promise code. Our requirements are:
|
||||||
|
// * we need to report back failures
|
||||||
|
// * failures shouldn't block recovery
|
||||||
|
// * Errors can leave `hasteMap` in an incorrect state, and we need to rebuild
|
||||||
|
// After we process a file change we record any errors which will also be
|
||||||
|
// reported via the next request. On the next file change, we'll see that
|
||||||
|
// we are in an error state and we should decide to do a full rebuild.
|
||||||
|
const resolve = () => {
|
||||||
|
if (this._hasteMapError) {
|
||||||
|
console.warn(
|
||||||
|
'Rebuilding haste map to recover from error:\n' +
|
||||||
|
this._hasteMapError.stack
|
||||||
|
);
|
||||||
|
this._hasteMapError = null;
|
||||||
|
|
||||||
|
// Rebuild the entire map if last change resulted in an error.
|
||||||
|
this._loading = this._hasteMap.build();
|
||||||
|
} else {
|
||||||
|
this._loading = this._hasteMap.processFileChange(type, absPath);
|
||||||
|
this._loading.catch((e) => this._hasteMapError = e);
|
||||||
|
}
|
||||||
|
return this._loading;
|
||||||
|
};
|
||||||
|
this._loading = this._loading.then(resolve, resolve);
|
||||||
|
}
|
||||||
|
|
||||||
|
createPolyfill(options) {
|
||||||
|
return this._moduleCache.createPolyfill(options);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.assign(DependencyGraph, {
|
||||||
|
Cache,
|
||||||
|
Fastfs,
|
||||||
|
FileWatcher,
|
||||||
|
Module,
|
||||||
|
Polyfill,
|
||||||
|
extractRequires,
|
||||||
|
getAssetDataFromName,
|
||||||
|
getPlatformExtension,
|
||||||
|
replacePatterns,
|
||||||
|
getInverseDependencies,
|
||||||
|
});
|
||||||
|
|
||||||
|
function NotFoundError() {
|
||||||
|
Error.call(this);
|
||||||
|
Error.captureStackTrace(this, this.constructor);
|
||||||
|
var msg = util.format.apply(util, arguments);
|
||||||
|
this.message = msg;
|
||||||
|
this.type = this.name = 'NotFoundError';
|
||||||
|
this.status = 404;
|
||||||
|
}
|
||||||
|
util.inherits(NotFoundError, Error);
|
||||||
|
|
||||||
|
module.exports = DependencyGraph;
|
|
@ -0,0 +1,28 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) 2016-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 = class AsyncTaskGroup {
|
||||||
|
constructor() {
|
||||||
|
this._runningTasks = new Set();
|
||||||
|
this._resolve = null;
|
||||||
|
this.done = new Promise(resolve => this._resolve = resolve);
|
||||||
|
}
|
||||||
|
|
||||||
|
start(taskHandle) {
|
||||||
|
this._runningTasks.add(taskHandle);
|
||||||
|
}
|
||||||
|
|
||||||
|
end(taskHandle) {
|
||||||
|
const runningTasks = this._runningTasks;
|
||||||
|
if (runningTasks.delete(taskHandle) && runningTasks.size === 0) {
|
||||||
|
this._resolve();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
|
@ -0,0 +1,30 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) 2016-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 = function MapWithDefaults(factory, iterable) {
|
||||||
|
// This can't be `MapWithDefaults extends Map`, b/c the way babel transforms
|
||||||
|
// super calls in constructors: Map.call(this, iterable) throws for native
|
||||||
|
// Map objects in node 4+.
|
||||||
|
// TODO(davidaurelio) switch to a transform that does not transform classes
|
||||||
|
// and super calls, and change this into a class
|
||||||
|
|
||||||
|
const map = iterable ? new Map(iterable) : new Map();
|
||||||
|
const {get} = map;
|
||||||
|
map.get = key => {
|
||||||
|
if (map.has(key)) {
|
||||||
|
return get.call(map, key);
|
||||||
|
}
|
||||||
|
|
||||||
|
const value = factory(key);
|
||||||
|
map.set(key, value);
|
||||||
|
return value;
|
||||||
|
};
|
||||||
|
return map;
|
||||||
|
};
|
|
@ -0,0 +1,106 @@
|
||||||
|
/**
|
||||||
|
* 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('../extractRequires');
|
||||||
|
jest.dontMock('../replacePatterns');
|
||||||
|
|
||||||
|
const extractRequires = require('../extractRequires');
|
||||||
|
|
||||||
|
describe('extractRequires', () => {
|
||||||
|
it('should extract both requires and imports from code', () => {
|
||||||
|
const code = `
|
||||||
|
import module1 from 'module1';
|
||||||
|
const module2 = require('module2');
|
||||||
|
const module3 = require(\`module3\`);
|
||||||
|
`;
|
||||||
|
|
||||||
|
expect(extractRequires(code)).toEqual({
|
||||||
|
code,
|
||||||
|
deps: {sync: ['module1', 'module2', 'module3']},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should extract requires in order', () => {
|
||||||
|
const code = `
|
||||||
|
const module1 = require('module1');
|
||||||
|
const module2 = require('module2');
|
||||||
|
const module3 = require('module3');
|
||||||
|
`;
|
||||||
|
|
||||||
|
expect(extractRequires(code)).toEqual({
|
||||||
|
code,
|
||||||
|
deps: {sync: ['module1', 'module2', 'module3']},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should strip out comments from code', () => {
|
||||||
|
const code = '// comment';
|
||||||
|
|
||||||
|
expect(extractRequires(code)).toEqual({
|
||||||
|
code: '',
|
||||||
|
deps: {sync: []},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should ignore requires in comments', () => {
|
||||||
|
const code = [
|
||||||
|
'// const module1 = require("module1");',
|
||||||
|
'/**',
|
||||||
|
' * const module2 = require("module2");',
|
||||||
|
' */',
|
||||||
|
].join('\n');
|
||||||
|
|
||||||
|
expect(extractRequires(code)).toEqual({
|
||||||
|
code: '\n',
|
||||||
|
deps: {sync: []},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should ignore requires in comments with Windows line endings', () => {
|
||||||
|
const code = [
|
||||||
|
'// const module1 = require("module1");',
|
||||||
|
'/**',
|
||||||
|
' * const module2 = require("module2");',
|
||||||
|
' */',
|
||||||
|
].join('\r\n');
|
||||||
|
|
||||||
|
expect(extractRequires(code)).toEqual({
|
||||||
|
code: '\r\n',
|
||||||
|
deps: {sync: []},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should ignore requires in comments with unicode line endings', () => {
|
||||||
|
const code = [
|
||||||
|
'// const module1 = require("module1");\u2028',
|
||||||
|
'// const module1 = require("module2");\u2029',
|
||||||
|
'/*\u2028',
|
||||||
|
'const module2 = require("module3");\u2029',
|
||||||
|
' */',
|
||||||
|
].join('');
|
||||||
|
|
||||||
|
expect(extractRequires(code)).toEqual({
|
||||||
|
code: '\u2028\u2029',
|
||||||
|
deps: {sync: []},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should dedup duplicated requires', () => {
|
||||||
|
const code = `
|
||||||
|
const module1 = require('module1');
|
||||||
|
const module1Dup = require('module1');
|
||||||
|
`;
|
||||||
|
|
||||||
|
expect(extractRequires(code)).toEqual({
|
||||||
|
code,
|
||||||
|
deps: {sync: ['module1']},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,119 @@
|
||||||
|
/**
|
||||||
|
* 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('../getPlatformExtension')
|
||||||
|
.dontMock('../getAssetDataFromName');
|
||||||
|
|
||||||
|
var getAssetDataFromName = require('../getAssetDataFromName');
|
||||||
|
|
||||||
|
describe('getAssetDataFromName', () => {
|
||||||
|
it('should get data from name', () => {
|
||||||
|
expect(getAssetDataFromName('a/b/c.png')).toEqual({
|
||||||
|
resolution: 1,
|
||||||
|
assetName: 'a/b/c.png',
|
||||||
|
type: 'png',
|
||||||
|
name: 'c',
|
||||||
|
platform: null,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(getAssetDataFromName('a/b/c@1x.png')).toEqual({
|
||||||
|
resolution: 1,
|
||||||
|
assetName: 'a/b/c.png',
|
||||||
|
type: 'png',
|
||||||
|
name: 'c',
|
||||||
|
platform: null,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(getAssetDataFromName('a/b/c@2.5x.png')).toEqual({
|
||||||
|
resolution: 2.5,
|
||||||
|
assetName: 'a/b/c.png',
|
||||||
|
type: 'png',
|
||||||
|
name: 'c',
|
||||||
|
platform: null,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(getAssetDataFromName('a/b/c.ios.png')).toEqual({
|
||||||
|
resolution: 1,
|
||||||
|
assetName: 'a/b/c.png',
|
||||||
|
type: 'png',
|
||||||
|
name: 'c',
|
||||||
|
platform: 'ios',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(getAssetDataFromName('a/b/c@1x.ios.png')).toEqual({
|
||||||
|
resolution: 1,
|
||||||
|
assetName: 'a/b/c.png',
|
||||||
|
type: 'png',
|
||||||
|
name: 'c',
|
||||||
|
platform: 'ios',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(getAssetDataFromName('a/b/c@2.5x.ios.png')).toEqual({
|
||||||
|
resolution: 2.5,
|
||||||
|
assetName: 'a/b/c.png',
|
||||||
|
type: 'png',
|
||||||
|
name: 'c',
|
||||||
|
platform: 'ios',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('resolution extraction', () => {
|
||||||
|
it('should extract resolution simple case', () => {
|
||||||
|
var data = getAssetDataFromName('test@2x.png');
|
||||||
|
expect(data).toEqual({
|
||||||
|
assetName: 'test.png',
|
||||||
|
resolution: 2,
|
||||||
|
type: 'png',
|
||||||
|
name: 'test',
|
||||||
|
platform: null,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should default resolution to 1', () => {
|
||||||
|
var data = getAssetDataFromName('test.png');
|
||||||
|
expect(data).toEqual({
|
||||||
|
assetName: 'test.png',
|
||||||
|
resolution: 1,
|
||||||
|
type: 'png',
|
||||||
|
name: 'test',
|
||||||
|
platform: null,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should support float', () => {
|
||||||
|
var data = getAssetDataFromName('test@1.1x.png');
|
||||||
|
expect(data).toEqual({
|
||||||
|
assetName: 'test.png',
|
||||||
|
resolution: 1.1,
|
||||||
|
type: 'png',
|
||||||
|
name: 'test',
|
||||||
|
platform: null,
|
||||||
|
});
|
||||||
|
|
||||||
|
data = getAssetDataFromName('test@.1x.png');
|
||||||
|
expect(data).toEqual({
|
||||||
|
assetName: 'test.png',
|
||||||
|
resolution: 0.1,
|
||||||
|
type: 'png',
|
||||||
|
name: 'test',
|
||||||
|
platform: null,
|
||||||
|
});
|
||||||
|
|
||||||
|
data = getAssetDataFromName('test@0.2x.png');
|
||||||
|
expect(data).toEqual({
|
||||||
|
assetName: 'test.png',
|
||||||
|
resolution: 0.2,
|
||||||
|
type: 'png',
|
||||||
|
name: 'test',
|
||||||
|
platform: null,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,55 @@
|
||||||
|
/**
|
||||||
|
* 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('../getInverseDependencies');
|
||||||
|
|
||||||
|
const getInverseDependencies = require('../getInverseDependencies');
|
||||||
|
|
||||||
|
describe('getInverseDependencies', () => {
|
||||||
|
it('', () => {
|
||||||
|
const module1 = createModule('module1', ['module2', 'module3']);
|
||||||
|
const module2 = createModule('module2', ['module3', 'module4']);
|
||||||
|
const module3 = createModule('module3', ['module4']);
|
||||||
|
const module4 = createModule('module4', []);
|
||||||
|
|
||||||
|
const modulePairs = {
|
||||||
|
'module1': [['module2', module2], ['module3', module3]],
|
||||||
|
'module2': [['module3', module3], ['module4', module4]],
|
||||||
|
'module3': [['module4', module4]],
|
||||||
|
'module4': [],
|
||||||
|
};
|
||||||
|
|
||||||
|
const resolutionResponse = {
|
||||||
|
dependencies: [module1, module2, module3, module4],
|
||||||
|
getResolvedDependencyPairs: (module) => {
|
||||||
|
return modulePairs[module.hash()];
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const dependencies = getInverseDependencies(resolutionResponse);
|
||||||
|
const actual = // jest can't compare maps and sets
|
||||||
|
Array.from(dependencies.entries())
|
||||||
|
.map(([key, value]) => [key, Array.from(value)]);
|
||||||
|
|
||||||
|
expect(actual).toEqual([
|
||||||
|
[module2, [module1]],
|
||||||
|
[module3, [module1, module2]],
|
||||||
|
[module4, [module2, module3]],
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
function createModule(name, dependencies) {
|
||||||
|
return {
|
||||||
|
hash: () => name,
|
||||||
|
getName: () => Promise.resolve(name),
|
||||||
|
getDependencies: () => Promise.resolve(dependencies),
|
||||||
|
};
|
||||||
|
}
|
|
@ -0,0 +1,33 @@
|
||||||
|
/**
|
||||||
|
* 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('../getPlatformExtension');
|
||||||
|
|
||||||
|
var getPlatformExtension = require('../getPlatformExtension');
|
||||||
|
|
||||||
|
describe('getPlatformExtension', function() {
|
||||||
|
it('should get platform ext', function() {
|
||||||
|
expect(getPlatformExtension('a.ios.js')).toBe('ios');
|
||||||
|
expect(getPlatformExtension('a.android.js')).toBe('android');
|
||||||
|
expect(getPlatformExtension('/b/c/a.ios.js')).toBe('ios');
|
||||||
|
expect(getPlatformExtension('/b/c.android/a.ios.js')).toBe('ios');
|
||||||
|
expect(getPlatformExtension('/b/c/a@1.5x.ios.png')).toBe('ios');
|
||||||
|
expect(getPlatformExtension('/b/c/a@1.5x.lol.png')).toBe(null);
|
||||||
|
expect(getPlatformExtension('/b/c/a.lol.png')).toBe(null);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should optionally accept supported platforms', function() {
|
||||||
|
expect(getPlatformExtension('a.ios.js', new Set(['ios']))).toBe('ios');
|
||||||
|
expect(getPlatformExtension('a.android.js', new Set(['android']))).toBe('android');
|
||||||
|
expect(getPlatformExtension('/b/c/a.ios.js', new Set(['ios', 'android']))).toBe('ios');
|
||||||
|
expect(getPlatformExtension('a.ios.js', new Set(['ubuntu']))).toBe(null);
|
||||||
|
expect(getPlatformExtension('a.ubuntu.js', new Set(['ubuntu']))).toBe('ubuntu');
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,55 @@
|
||||||
|
/**
|
||||||
|
* 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 replacePatterns = require('./replacePatterns');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract all required modules from a `code` string.
|
||||||
|
*/
|
||||||
|
const blockCommentRe = /\/\*[^]*?\*\//g;
|
||||||
|
const lineCommentRe = /\/\/.*/g;
|
||||||
|
function extractRequires(code) {
|
||||||
|
const cache = Object.create(null);
|
||||||
|
var deps = {
|
||||||
|
sync: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
const addDependency = (dep) => {
|
||||||
|
if (!cache[dep]) {
|
||||||
|
cache[dep] = true;
|
||||||
|
deps.sync.push(dep);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
code = code
|
||||||
|
.replace(blockCommentRe, '')
|
||||||
|
.replace(lineCommentRe, '')
|
||||||
|
// Parse the sync dependencies this module has. When the module is
|
||||||
|
// required, all it's sync dependencies will be loaded into memory.
|
||||||
|
// Sync dependencies can be defined either using `require` or the ES6
|
||||||
|
// `import` or `export` syntaxes:
|
||||||
|
// var dep1 = require('dep1');
|
||||||
|
.replace(replacePatterns.IMPORT_RE, (match, pre, quot, dep, post) => {
|
||||||
|
addDependency(dep);
|
||||||
|
return match;
|
||||||
|
})
|
||||||
|
.replace(replacePatterns.EXPORT_RE, (match, pre, quot, dep, post) => {
|
||||||
|
addDependency(dep);
|
||||||
|
return match;
|
||||||
|
})
|
||||||
|
.replace(replacePatterns.REQUIRE_RE, (match, pre, quot, dep, post) => {
|
||||||
|
addDependency(dep);
|
||||||
|
return match;
|
||||||
|
});
|
||||||
|
|
||||||
|
return {code, deps};
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = extractRequires;
|
|
@ -0,0 +1,55 @@
|
||||||
|
/**
|
||||||
|
* 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 path = require('../fastpath');
|
||||||
|
const getPlatformExtension = require('./getPlatformExtension');
|
||||||
|
|
||||||
|
function getAssetDataFromName(filename, platforms) {
|
||||||
|
const ext = path.extname(filename);
|
||||||
|
const platformExt = getPlatformExtension(filename, platforms);
|
||||||
|
|
||||||
|
let pattern = '@([\\d\\.]+)x';
|
||||||
|
if (platformExt != null) {
|
||||||
|
pattern += '(\\.' + platformExt + ')?';
|
||||||
|
}
|
||||||
|
pattern += '\\' + ext + '$';
|
||||||
|
const re = new RegExp(pattern);
|
||||||
|
|
||||||
|
const match = filename.match(re);
|
||||||
|
let resolution;
|
||||||
|
|
||||||
|
if (!(match && match[1])) {
|
||||||
|
resolution = 1;
|
||||||
|
} else {
|
||||||
|
resolution = parseFloat(match[1], 10);
|
||||||
|
if (isNaN(resolution)) {
|
||||||
|
resolution = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let assetName;
|
||||||
|
if (match) {
|
||||||
|
assetName = filename.replace(re, ext);
|
||||||
|
} else if (platformExt != null) {
|
||||||
|
assetName = filename.replace(new RegExp(`\\.${platformExt}\\${ext}`), ext);
|
||||||
|
} else {
|
||||||
|
assetName = filename;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
resolution: resolution,
|
||||||
|
assetName: assetName,
|
||||||
|
type: ext.slice(1),
|
||||||
|
name: path.basename(assetName, ext),
|
||||||
|
platform: platformExt,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = getAssetDataFromName;
|
|
@ -0,0 +1,42 @@
|
||||||
|
/**
|
||||||
|
* 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';
|
||||||
|
|
||||||
|
function resolveModuleRequires(resolutionResponse, module) {
|
||||||
|
const pairs = resolutionResponse.getResolvedDependencyPairs(module);
|
||||||
|
return pairs
|
||||||
|
? pairs.map(([, dependencyModule]) => dependencyModule)
|
||||||
|
: [];
|
||||||
|
}
|
||||||
|
|
||||||
|
function getModuleDependents(cache, module) {
|
||||||
|
let dependents = cache.get(module);
|
||||||
|
if (!dependents) {
|
||||||
|
dependents = new Set();
|
||||||
|
cache.set(module, dependents);
|
||||||
|
}
|
||||||
|
return dependents;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an object that indicates in which module each module is required.
|
||||||
|
*/
|
||||||
|
function getInverseDependencies(resolutionResponse) {
|
||||||
|
const cache = new Map();
|
||||||
|
|
||||||
|
resolutionResponse.dependencies.forEach(module => {
|
||||||
|
resolveModuleRequires(resolutionResponse, module).forEach(dependency => {
|
||||||
|
getModuleDependents(cache, dependency).add(module);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return cache;
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = getInverseDependencies;
|
|
@ -0,0 +1,28 @@
|
||||||
|
/**
|
||||||
|
* 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 SUPPORTED_PLATFORM_EXTS = new Set([
|
||||||
|
'android',
|
||||||
|
'ios',
|
||||||
|
'web',
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Extract platform extension: index.ios.js -> ios
|
||||||
|
function getPlatformExtension(file, platforms = SUPPORTED_PLATFORM_EXTS) {
|
||||||
|
const last = file.lastIndexOf('.');
|
||||||
|
const secondToLast = file.lastIndexOf('.', last - 1);
|
||||||
|
if (secondToLast === -1) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const platform = file.substring(secondToLast + 1, last);
|
||||||
|
return platforms.has(platform) ? platform : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = getPlatformExtension;
|
|
@ -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';
|
||||||
|
|
||||||
|
exports.IMPORT_RE = /(\bimport\s+(?:[^'"]+\s+from\s+)??)(['"])([^'"]+)(\2)/g;
|
||||||
|
exports.EXPORT_RE = /(\bexport\s+(?:[^'"]+\s+from\s+)??)(['"])([^'"]+)(\2)/g;
|
||||||
|
exports.REQUIRE_RE = /(\brequire\s*?\(\s*?)(['"`])([^'"`]+)(\2\s*?\))/g;
|
Loading…
Reference in New Issue