mirror of https://github.com/status-im/metro.git
[react-packager] Rewrite dependency graph (support node_modules, speed, fix bugs etc)
Summary: @public Fixes #773, #1055 The resolver was getting a bit unwieldy because a lot has changed since the initial writing (porting node-haste). This also splits up a large complex file into the following: * Makes use of classes: Module, AssetModule, Package, and AssetModule_DEPRECATED (`image!` modules) * DependencyGraph is lazy for everything that isn't haste modules and packages (need to read ahead of time) * Lazy makes it fast, easier to reason about, and easier to add new loaders * Has a centralized filesystem wrapper: fast-fs (ffs) * ffs is async and lazy for any read operation and sync for directory/file lookup which makes it fast * we can easily drop in different adapters for ffs to be able to build up the tree: watchman, git ls-files, etc * use es6 for classes and easier to read promise-based code Follow up diffs will include: * Using new types (Module, AssetModule etc) in the rest of the codebase (currently we convert to plain object which is a bit of a hack) * using watchman to build up the fs * some caching at the object creation level (we are recreating Modules and Packages many times, we can cache them) * A plugin system for loaders (e.g. @tadeuzagallo wants to add a native module loader) Test Plan: * ./runJestTests.sh react-packager * ./runJestTests.sh PackagerIntegration * Export open source and run the e2e test * reset cache * ./fbrnios.sh run and click around
This commit is contained in:
parent
0405c82798
commit
8faa406e96
|
@ -11,7 +11,6 @@
|
|||
// Don't forget to everything listed here to `testConfig.json`
|
||||
// modulePathIgnorePatterns.
|
||||
var sharedBlacklist = [
|
||||
__dirname,
|
||||
'website',
|
||||
'node_modules/react-tools/src/utils/ImmutableObject.js',
|
||||
'node_modules/react-tools/src/core/ReactInstanceHandles.js',
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
// Keep in sync with packager/transformer.js
|
||||
{
|
||||
"retainLines": true,
|
||||
"compact": true,
|
||||
"comments": false,
|
||||
"whitelist": [
|
||||
"es6.arrowFunctions",
|
||||
"es6.blockScoping",
|
||||
// This is the only place where we differ from transformer.js
|
||||
"es6.constants",
|
||||
"es6.classes",
|
||||
"es6.destructuring",
|
||||
"es6.parameters.rest",
|
||||
"es6.properties.computed",
|
||||
"es6.properties.shorthand",
|
||||
"es6.spread",
|
||||
"es6.templateLiterals",
|
||||
"es7.trailingFunctionCommas",
|
||||
"es7.objectRestSpread",
|
||||
"flow",
|
||||
"react"
|
||||
],
|
||||
"sourceMaps": false
|
||||
}
|
|
@ -8,6 +8,10 @@
|
|||
*/
|
||||
'use strict';
|
||||
|
||||
require('babel/register')({
|
||||
only: /react-packager\/src/
|
||||
});
|
||||
|
||||
useGracefulFs();
|
||||
|
||||
var Activity = require('./src/Activity');
|
||||
|
|
|
@ -15,7 +15,7 @@ var Promise = require('bluebird');
|
|||
var fs = require('fs');
|
||||
var crypto = require('crypto');
|
||||
|
||||
var lstat = Promise.promisify(fs.lstat);
|
||||
var stat = Promise.promisify(fs.stat);
|
||||
var readDir = Promise.promisify(fs.readdir);
|
||||
var readFile = Promise.promisify(fs.readFile);
|
||||
|
||||
|
@ -98,14 +98,14 @@ AssetServer.prototype.getAssetData = function(assetPath) {
|
|||
|
||||
return Promise.all(
|
||||
record.files.map(function(file) {
|
||||
return lstat(file);
|
||||
return stat(file);
|
||||
})
|
||||
);
|
||||
}).then(function(stats) {
|
||||
var hash = crypto.createHash('md5');
|
||||
|
||||
stats.forEach(function(stat) {
|
||||
hash.update(stat.mtime.getTime().toString());
|
||||
stats.forEach(function(fstat) {
|
||||
hash.update(fstat.mtime.getTime().toString());
|
||||
});
|
||||
|
||||
data.hash = hash.digest('hex');
|
||||
|
@ -117,18 +117,18 @@ function findRoot(roots, dir) {
|
|||
return Promise.some(
|
||||
roots.map(function(root) {
|
||||
var absPath = path.join(root, dir);
|
||||
return lstat(absPath).then(function(stat) {
|
||||
if (!stat.isDirectory()) {
|
||||
return stat(absPath).then(function(fstat) {
|
||||
if (!fstat.isDirectory()) {
|
||||
throw new Error('Looking for dirs');
|
||||
}
|
||||
stat._path = absPath;
|
||||
return stat;
|
||||
fstat._path = absPath;
|
||||
return fstat;
|
||||
});
|
||||
}),
|
||||
1
|
||||
).spread(
|
||||
function(stat) {
|
||||
return stat._path;
|
||||
function(fstat) {
|
||||
return fstat._path;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,46 @@
|
|||
'use strict';
|
||||
|
||||
const Module = require('./Module');
|
||||
const Promise = require('bluebird');
|
||||
const getAssetDataFromName = require('../lib/getAssetDataFromName');
|
||||
|
||||
class AssetModule extends Module {
|
||||
|
||||
isHaste() {
|
||||
return Promise.resolve(false);
|
||||
}
|
||||
|
||||
getDependencies() {
|
||||
return Promise.resolve([]);
|
||||
}
|
||||
|
||||
_read() {
|
||||
return Promise.resolve({});
|
||||
}
|
||||
|
||||
getName() {
|
||||
return super.getName().then(id => {
|
||||
const {name, type} = getAssetDataFromName(this.path);
|
||||
return id.replace(/\/[^\/]+$/, `/${name}.${type}`);
|
||||
});
|
||||
}
|
||||
|
||||
getPlainObject() {
|
||||
return this.getName().then(name => this.addReference({
|
||||
path: this.path,
|
||||
isJSON: false,
|
||||
isAsset: true,
|
||||
isAsset_DEPRECATED: false,
|
||||
isPolyfill: false,
|
||||
resolution: getAssetDataFromName(this.path).resolution,
|
||||
id: name,
|
||||
dependencies: [],
|
||||
}));
|
||||
}
|
||||
|
||||
hash() {
|
||||
return `AssetModule : ${this.path}`;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = AssetModule;
|
|
@ -0,0 +1,40 @@
|
|||
'use strict';
|
||||
|
||||
const Module = require('./Module');
|
||||
const Promise = require('bluebird');
|
||||
const getAssetDataFromName = require('../lib/getAssetDataFromName');
|
||||
|
||||
class AssetModule_DEPRECATED extends Module {
|
||||
isHaste() {
|
||||
return Promise.resolve(false);
|
||||
}
|
||||
|
||||
getName() {
|
||||
return Promise.resolve(this.name);
|
||||
}
|
||||
|
||||
getDependencies() {
|
||||
return Promise.resolve([]);
|
||||
}
|
||||
|
||||
getPlainObject() {
|
||||
const {name, resolution} = getAssetDataFromName(this.path);
|
||||
|
||||
return Promise.resolve(this.addReference({
|
||||
path: this.path,
|
||||
id: `image!${name}`,
|
||||
resolution,
|
||||
isAsset_DEPRECATED: true,
|
||||
dependencies: [],
|
||||
isJSON: false,
|
||||
isPolyfill: false,
|
||||
isAsset: false,
|
||||
}));
|
||||
}
|
||||
|
||||
hash() {
|
||||
return `AssetModule_DEPRECATED : ${this.path}`;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = AssetModule_DEPRECATED;
|
3241
react-packager/src/DependencyResolver/DependencyGraph/__tests__/DependencyGraph-test.js
vendored
Normal file
3241
react-packager/src/DependencyResolver/DependencyGraph/__tests__/DependencyGraph-test.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,556 @@
|
|||
/**
|
||||
* 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('path');
|
||||
const Fastfs = require('../fastfs');
|
||||
const ModuleCache = require('../ModuleCache');
|
||||
const AssetModule_DEPRECATED = require('../AssetModule_DEPRECATED');
|
||||
const declareOpts = require('../../lib/declareOpts');
|
||||
const isAbsolutePath = require('absolute-path');
|
||||
const debug = require('debug')('DependencyGraph');
|
||||
const getAssetDataFromName = require('../../lib/getAssetDataFromName');
|
||||
const util = require('util');
|
||||
const Promise = require('bluebird');
|
||||
const _ = require('underscore');
|
||||
|
||||
const validateOpts = declareOpts({
|
||||
roots: {
|
||||
type: 'array',
|
||||
required: true,
|
||||
},
|
||||
ignoreFilePath: {
|
||||
type: 'function',
|
||||
default: function(){}
|
||||
},
|
||||
fileWatcher: {
|
||||
type: 'object',
|
||||
required: true,
|
||||
},
|
||||
assetRoots_DEPRECATED: {
|
||||
type: 'array',
|
||||
default: [],
|
||||
},
|
||||
assetExts: {
|
||||
type: 'array',
|
||||
required: true,
|
||||
},
|
||||
providesModuleNodeModules: {
|
||||
type: 'array',
|
||||
default: [
|
||||
'react-tools',
|
||||
'react-native',
|
||||
// Parse requires AsyncStorage. They will
|
||||
// change that to require('react-native') which
|
||||
// should work after this release and we can
|
||||
// remove it from here.
|
||||
'parse',
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
class DependencyGraph {
|
||||
constructor(options) {
|
||||
this._opts = validateOpts(options);
|
||||
this._hasteMap = Object.create(null);
|
||||
this._immediateResolutionCache = Object.create(null);
|
||||
this.load();
|
||||
}
|
||||
|
||||
load() {
|
||||
if (this._loading) {
|
||||
return this._loading;
|
||||
}
|
||||
|
||||
const modulePattern = new RegExp(
|
||||
'\.(' + ['js', 'json'].concat(this._assetExts).join('|') + ')$'
|
||||
);
|
||||
|
||||
this._fastfs = new Fastfs(this._opts.roots,this._opts.fileWatcher, {
|
||||
pattern: modulePattern,
|
||||
ignore: this._opts.ignoreFilePath,
|
||||
});
|
||||
|
||||
this._fastfs.on('change', this._processFileChange.bind(this));
|
||||
|
||||
this._moduleCache = new ModuleCache(this._fastfs);
|
||||
|
||||
this._loading = Promise.all([
|
||||
this._fastfs.build().then(() => this._buildHasteMap()),
|
||||
this._buildAssetMap_DEPRECATED(),
|
||||
]);
|
||||
|
||||
return this._loading;
|
||||
}
|
||||
|
||||
resolveDependency(fromModule, toModuleName) {
|
||||
if (fromModule._ref) {
|
||||
fromModule = fromModule._ref;
|
||||
}
|
||||
|
||||
const resHash = resolutionHash(fromModule.path, toModuleName);
|
||||
|
||||
if (this._immediateResolutionCache[resHash]) {
|
||||
return Promise.resolve(this._immediateResolutionCache[resHash]);
|
||||
}
|
||||
|
||||
const asset_DEPRECATED = this._resolveAsset_DEPRECATED(
|
||||
fromModule,
|
||||
toModuleName
|
||||
);
|
||||
if (asset_DEPRECATED) {
|
||||
return Promise.resolve(asset_DEPRECATED);
|
||||
}
|
||||
|
||||
const cacheResult = (result) => {
|
||||
this._immediateResolutionCache[resHash] = result;
|
||||
return result;
|
||||
};
|
||||
|
||||
const forgive = () => {
|
||||
console.warn(
|
||||
'Unable to resolve module %s from %s',
|
||||
toModuleName,
|
||||
fromModule.path
|
||||
);
|
||||
return null;
|
||||
};
|
||||
|
||||
if (!this._isNodeModulesDir(fromModule.path)
|
||||
&& toModuleName[0] !== '.' &&
|
||||
toModuleName[0] !== '/') {
|
||||
return this._resolveHasteDependency(fromModule, toModuleName).catch(
|
||||
() => this._resolveNodeDependency(fromModule, toModuleName)
|
||||
).then(
|
||||
cacheResult,
|
||||
forgive
|
||||
);
|
||||
}
|
||||
|
||||
return this._resolveNodeDependency(fromModule, toModuleName)
|
||||
.then(
|
||||
cacheResult,
|
||||
forgive
|
||||
);
|
||||
}
|
||||
|
||||
getOrderedDependencies(entryPath) {
|
||||
return this.load().then(() => {
|
||||
const absolutePath = path.resolve(this._getAbsolutePath(entryPath));
|
||||
|
||||
if (absolutePath == null) {
|
||||
throw new NotFoundError(
|
||||
'Cannot find entry file %s in any of the roots: %j',
|
||||
entryPath,
|
||||
this._opts.roots
|
||||
);
|
||||
}
|
||||
|
||||
const entry = this._moduleCache.getModule(absolutePath);
|
||||
const deps = [];
|
||||
const visited = Object.create(null);
|
||||
visited[entry.hash()] = true;
|
||||
|
||||
const collect = (mod) => {
|
||||
deps.push(mod);
|
||||
return mod.getDependencies().then(
|
||||
depNames => Promise.all(
|
||||
depNames.map(name => this.resolveDependency(mod, name))
|
||||
).then((dependencies) => [depNames, dependencies])
|
||||
).then(([depNames, dependencies]) => {
|
||||
let p = Promise.resolve();
|
||||
dependencies.forEach((modDep, i) => {
|
||||
if (modDep == null) {
|
||||
debug(
|
||||
'WARNING: Cannot find required module `%s` from module `%s`',
|
||||
depNames[i],
|
||||
mod.path
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
p = p.then(() => {
|
||||
if (!visited[modDep.hash()]) {
|
||||
visited[modDep.hash()] = true;
|
||||
return collect(modDep);
|
||||
}
|
||||
return null;
|
||||
});
|
||||
});
|
||||
|
||||
return p;
|
||||
});
|
||||
};
|
||||
|
||||
return collect(entry)
|
||||
.then(() => Promise.all(deps.map(dep => dep.getPlainObject())));
|
||||
});
|
||||
}
|
||||
|
||||
_getAbsolutePath(filePath) {
|
||||
if (isAbsolutePath(filePath)) {
|
||||
return filePath;
|
||||
}
|
||||
|
||||
for (let i = 0; i < this._opts.roots.length; i++) {
|
||||
const root = this._opts.roots[i];
|
||||
const absPath = path.join(root, filePath);
|
||||
if (this._fastfs.fileExists(absPath)) {
|
||||
return absPath;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
_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[realModuleName];
|
||||
|
||||
if (dep && dep.type === 'Module') {
|
||||
return dep;
|
||||
}
|
||||
|
||||
let packageName = realModuleName;
|
||||
|
||||
while (packageName && packageName !== '.') {
|
||||
dep = this._hasteMap[packageName];
|
||||
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._loadAsFile(potentialModulePath)
|
||||
.catch(() => this._loadAsDir(potentialModulePath));
|
||||
}
|
||||
|
||||
throw new Error('Unable to resolve dependency');
|
||||
});
|
||||
}
|
||||
|
||||
_redirectRequire(fromModule, modulePath) {
|
||||
return Promise.resolve(fromModule.getPackage()).then(p => {
|
||||
if (p) {
|
||||
return p.redirectRequire(modulePath);
|
||||
}
|
||||
return modulePath;
|
||||
});
|
||||
}
|
||||
|
||||
_resolveNodeDependency(fromModule, toModuleName) {
|
||||
if (toModuleName[0] === '.' || toModuleName[1] === '/') {
|
||||
const potentialModulePath = isAbsolutePath(toModuleName) ?
|
||||
toModuleName :
|
||||
path.join(path.dirname(fromModule.path), toModuleName);
|
||||
return this._redirectRequire(fromModule, potentialModulePath).then(
|
||||
realModuleName => this._loadAsFile(realModuleName)
|
||||
.catch(() => this._loadAsDir(realModuleName))
|
||||
);
|
||||
} else {
|
||||
return this._redirectRequire(fromModule, toModuleName).then(
|
||||
realModuleName => {
|
||||
const searchQueue = [];
|
||||
for (let currDir = path.dirname(fromModule.path);
|
||||
currDir !== '/';
|
||||
currDir = path.dirname(currDir)) {
|
||||
searchQueue.push(
|
||||
path.join(currDir, 'node_modules', realModuleName)
|
||||
);
|
||||
}
|
||||
|
||||
let p = Promise.reject(new Error('Node module not found'));
|
||||
searchQueue.forEach(potentialModulePath => {
|
||||
p = p.catch(
|
||||
() => this._loadAsFile(potentialModulePath)
|
||||
).catch(
|
||||
() => this._loadAsDir(potentialModulePath)
|
||||
);
|
||||
});
|
||||
|
||||
return p;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
_resolveAsset_DEPRECATED(fromModule, toModuleName) {
|
||||
if (this._assetMap_DEPRECATED != null) {
|
||||
const assetMatch = toModuleName.match(/^image!(.+)/);
|
||||
// Process DEPRECATED global asset requires.
|
||||
if (assetMatch && assetMatch[1]) {
|
||||
if (!this._assetMap_DEPRECATED[assetMatch[1]]) {
|
||||
debug('WARINING: Cannot find asset:', assetMatch[1]);
|
||||
return null;
|
||||
}
|
||||
return this._assetMap_DEPRECATED[assetMatch[1]];
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
_isAssetFile(file) {
|
||||
return this._opts.assetExts.indexOf(extname(file)) !== -1;
|
||||
}
|
||||
|
||||
_loadAsFile(potentialModulePath) {
|
||||
return Promise.resolve().then(() => {
|
||||
if (this._isAssetFile(potentialModulePath)) {
|
||||
const {name, type} = getAssetDataFromName(potentialModulePath);
|
||||
const pattern = new RegExp('^' + name + '(@[\\d\\.]+x)?\\.' + type);
|
||||
// We arbitrarly grab the first one, because scale selection
|
||||
// will happen somewhere
|
||||
const [assetFile] = this._fastfs.matches(
|
||||
path.dirname(potentialModulePath),
|
||||
pattern
|
||||
);
|
||||
|
||||
if (assetFile) {
|
||||
return this._moduleCache.getAssetModule(assetFile);
|
||||
}
|
||||
}
|
||||
|
||||
let file;
|
||||
if (this._fastfs.fileExists(potentialModulePath)) {
|
||||
file = potentialModulePath;
|
||||
} else if (this._fastfs.fileExists(potentialModulePath + '.js')) {
|
||||
file = potentialModulePath + '.js';
|
||||
} else if (this._fastfs.fileExists(potentialModulePath + '.json')) {
|
||||
file = potentialModulePath + '.json';
|
||||
} else {
|
||||
throw new Error(`File ${potentialModulePath} doesnt exist`);
|
||||
}
|
||||
|
||||
return this._moduleCache.getModule(file);
|
||||
});
|
||||
}
|
||||
|
||||
_loadAsDir(potentialDirPath) {
|
||||
return Promise.resolve().then(() => {
|
||||
if (!this._fastfs.dirExists(potentialDirPath)) {
|
||||
throw new Error(`Invalid directory ${potentialDirPath}`);
|
||||
}
|
||||
|
||||
const packageJsonPath = path.join(potentialDirPath, 'package.json');
|
||||
if (this._fastfs.fileExists(packageJsonPath)) {
|
||||
return this._moduleCache.getPackage(packageJsonPath)
|
||||
.getMain().then(
|
||||
(main) => this._loadAsFile(main).catch(
|
||||
() => this._loadAsDir(main)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return this._loadAsFile(path.join(potentialDirPath, 'index'));
|
||||
});
|
||||
}
|
||||
|
||||
_buildHasteMap() {
|
||||
let promises = this._fastfs.findFilesByExt('js', {
|
||||
ignore: (file) => this._isNodeModulesDir(file)
|
||||
}).map(file => this._processHasteModule(file));
|
||||
|
||||
promises = promises.concat(
|
||||
this._fastfs.findFilesByName('package.json', {
|
||||
ignore: (file) => this._isNodeModulesDir(file)
|
||||
}).map(file => this._processHastePackage(file))
|
||||
);
|
||||
|
||||
return Promise.all(promises);
|
||||
}
|
||||
|
||||
_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, this._fastfs);
|
||||
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._hasteMap[name]) {
|
||||
debug('WARNING: conflicting haste modules: ' + name);
|
||||
if (mod.type === 'Package' &&
|
||||
this._hasteMap[name].type === 'Module') {
|
||||
// Modules takes precendence over packages.
|
||||
return;
|
||||
}
|
||||
}
|
||||
this._hasteMap[name] = mod;
|
||||
}
|
||||
|
||||
_isNodeModulesDir(file) {
|
||||
const inNodeModules = file.indexOf('/node_modules/') !== -1;
|
||||
|
||||
if (!inNodeModules) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const dirs = this._opts.providesModuleNodeModules;
|
||||
|
||||
for (let i = 0; i < dirs.length; i++) {
|
||||
const index = file.indexOf(dirs[i]);
|
||||
if (index !== -1) {
|
||||
return file.slice(index).indexOf('/node_modules/') !== -1;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
_processAsset_DEPRECATED(file) {
|
||||
let ext = extname(file);
|
||||
if (this._opts.assetExts.indexOf(ext) !== -1) {
|
||||
let name = assetName(file, ext);
|
||||
if (this._assetMap_DEPRECATED[name] != null) {
|
||||
debug('Conflcting assets', name);
|
||||
}
|
||||
|
||||
this._assetMap_DEPRECATED[name] = new AssetModule_DEPRECATED(file);
|
||||
}
|
||||
}
|
||||
|
||||
_buildAssetMap_DEPRECATED() {
|
||||
if (this._opts.assetRoots_DEPRECATED == null ||
|
||||
this._opts.assetRoots_DEPRECATED.length === 0) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
this._assetMap_DEPRECATED = Object.create(null);
|
||||
|
||||
const pattern = new RegExp(
|
||||
'\.(' + this._opts.assetExts.join('|') + ')$'
|
||||
);
|
||||
|
||||
const fastfs = new Fastfs(
|
||||
this._opts.assetRoots_DEPRECATED,
|
||||
this._opts.fileWatcher,
|
||||
{ pattern, ignore: this._opts.ignoreFilePath }
|
||||
);
|
||||
|
||||
fastfs.on('change', this._processAssetChange_DEPRECATED.bind(this));
|
||||
|
||||
return fastfs.build().then(
|
||||
() => fastfs.findFilesByExts(this._opts.assetExts).map(
|
||||
file => this._processAsset_DEPRECATED(file)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
_processAssetChange_DEPRECATED(type, filePath, root, fstat) {
|
||||
const name = assetName(filePath);
|
||||
if (type === 'change' || type === 'delete') {
|
||||
delete this._assetMap_DEPRECATED[name];
|
||||
}
|
||||
|
||||
if (type === 'change' || type === 'add') {
|
||||
this._loading = this._loading.then(
|
||||
() => this._processAsset_DEPRECATED(path.join(root, filePath))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
_processFileChange(type, filePath, root, fstat) {
|
||||
// It's really hard to invalidate the right module resolution cache
|
||||
// so we just blow it up with every file change.
|
||||
this._immediateResolutionCache = Object.create(null);
|
||||
|
||||
const absPath = path.join(root, filePath);
|
||||
if ((fstat && fstat.isDirectory()) ||
|
||||
this._opts.ignoreFilePath(absPath) ||
|
||||
this._isNodeModulesDir(absPath)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (type === 'delete' || type === 'change') {
|
||||
_.each(this._hasteMap, (mod, name) => {
|
||||
if (mod.path === absPath) {
|
||||
delete this._hasteMap[name];
|
||||
}
|
||||
});
|
||||
|
||||
if (type === 'delete') {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (extname(absPath) === 'js' || extname(absPath) === 'json') {
|
||||
this._loading = this._loading.then(() => {
|
||||
if (path.basename(filePath) === 'package.json') {
|
||||
return this._processHastePackage(absPath);
|
||||
} else {
|
||||
return this._processHasteModule(absPath);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function assetName(file, ext) {
|
||||
return path.basename(file, '.' + ext).replace(/@[\d\.]+x/, '');
|
||||
}
|
||||
|
||||
function extname(name) {
|
||||
return path.extname(name).replace(/^\./, '');
|
||||
}
|
||||
|
||||
function resolutionHash(modulePath, depName) {
|
||||
return `${path.resolve(modulePath)}:${depName}`;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
function normalizePath(modulePath) {
|
||||
if (path.sep === '/') {
|
||||
modulePath = path.normalize(modulePath);
|
||||
} else if (path.posix) {
|
||||
modulePath = path.posix.normalize(modulePath);
|
||||
}
|
||||
|
||||
return modulePath.replace(/\/$/, '');
|
||||
}
|
||||
|
||||
util.inherits(NotFoundError, Error);
|
||||
|
||||
module.exports = DependencyGraph;
|
|
@ -0,0 +1,133 @@
|
|||
'use strict';
|
||||
|
||||
const Promise = require('bluebird');
|
||||
const docblock = require('./DependencyGraph/docblock');
|
||||
const isAbsolutePath = require('absolute-path');
|
||||
const path = require('path');
|
||||
const replacePatterns = require('./replacePatterns');
|
||||
|
||||
class Module {
|
||||
|
||||
constructor(file, fastfs, moduleCache) {
|
||||
if (!isAbsolutePath(file)) {
|
||||
throw new Error('Expected file to be absolute path but got ' + file);
|
||||
}
|
||||
|
||||
this.path = path.resolve(file);
|
||||
this.type = 'Module';
|
||||
|
||||
this._fastfs = fastfs;
|
||||
this._moduleCache = moduleCache;
|
||||
}
|
||||
|
||||
isHaste() {
|
||||
return this._read().then(data => !!data.id);
|
||||
}
|
||||
|
||||
getName() {
|
||||
return this._read().then(data => {
|
||||
if (data.id) {
|
||||
return data.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));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
getPackage() {
|
||||
return this._moduleCache.getPackageForModule(this);
|
||||
}
|
||||
|
||||
getDependencies() {
|
||||
return this._read().then(data => data.dependencies);
|
||||
}
|
||||
|
||||
_read() {
|
||||
if (!this._reading) {
|
||||
this._reading = this._fastfs.readFile(this.path).then(content => {
|
||||
const data = {};
|
||||
const moduleDocBlock = docblock.parseAsObject(content);
|
||||
if (moduleDocBlock.providesModule || moduleDocBlock.provides) {
|
||||
data.id = /^(\S*)/.exec(
|
||||
moduleDocBlock.providesModule || moduleDocBlock.provides
|
||||
)[1];
|
||||
}
|
||||
|
||||
// Ignore requires in generated code. An example of this is prebuilt
|
||||
// files like the SourceMap library.
|
||||
if ('extern' in moduleDocBlock) {
|
||||
data.dependencies = [];
|
||||
} else {
|
||||
data.dependencies = extractRequires(content);
|
||||
}
|
||||
|
||||
return data;
|
||||
});
|
||||
}
|
||||
|
||||
return this._reading;
|
||||
}
|
||||
|
||||
getPlainObject() {
|
||||
return Promise.all([
|
||||
this.getName(),
|
||||
this.getDependencies(),
|
||||
]).then(([name, dependencies]) => this.addReference({
|
||||
path: this.path,
|
||||
isJSON: path.extname(this.path) === '.json',
|
||||
isAsset: false,
|
||||
isAsset_DEPRECATED: false,
|
||||
isPolyfill: false,
|
||||
resolution: undefined,
|
||||
id: name,
|
||||
dependencies
|
||||
}));
|
||||
}
|
||||
|
||||
hash() {
|
||||
return `Module : ${this.path}`;
|
||||
}
|
||||
|
||||
addReference(obj) {
|
||||
Object.defineProperty(obj, '_ref', { value: this });
|
||||
return obj;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract all required modules from a `code` string.
|
||||
*/
|
||||
var blockCommentRe = /\/\*(.|\n)*?\*\//g;
|
||||
var lineCommentRe = /\/\/.+(\n|$)/g;
|
||||
function extractRequires(code /*: string*/) /*: Array<string>*/ {
|
||||
var deps = [];
|
||||
|
||||
code
|
||||
.replace(blockCommentRe, '')
|
||||
.replace(lineCommentRe, '')
|
||||
.replace(replacePatterns.IMPORT_RE, (match, pre, quot, dep, post) => {
|
||||
deps.push(dep);
|
||||
return match;
|
||||
})
|
||||
.replace(replacePatterns.REQUIRE_RE, function(match, pre, quot, dep, post) {
|
||||
deps.push(dep);
|
||||
});
|
||||
|
||||
return deps;
|
||||
}
|
||||
|
||||
module.exports = Module;
|
|
@ -0,0 +1,72 @@
|
|||
'use strict';
|
||||
|
||||
const AssetModule = require('./AssetModule');
|
||||
const Package = require('./Package');
|
||||
const Module = require('./Module');
|
||||
const path = require('path');
|
||||
|
||||
class ModuleCache {
|
||||
|
||||
constructor(fastfs) {
|
||||
this._moduleCache = Object.create(null);
|
||||
this._packageCache = Object.create(null);
|
||||
this._fastfs = fastfs;
|
||||
fastfs.on('change', this._processFileChange.bind(this));
|
||||
}
|
||||
|
||||
getModule(filePath) {
|
||||
filePath = path.resolve(filePath);
|
||||
if (!this._moduleCache[filePath]) {
|
||||
this._moduleCache[filePath] = new Module(filePath, this._fastfs, this);
|
||||
}
|
||||
return this._moduleCache[filePath];
|
||||
}
|
||||
|
||||
getAssetModule(filePath) {
|
||||
filePath = path.resolve(filePath);
|
||||
if (!this._moduleCache[filePath]) {
|
||||
this._moduleCache[filePath] = new AssetModule(
|
||||
filePath,
|
||||
this._fastfs,
|
||||
this
|
||||
);
|
||||
}
|
||||
return this._moduleCache[filePath];
|
||||
}
|
||||
|
||||
getPackage(filePath) {
|
||||
filePath = path.resolve(filePath);
|
||||
if (!this._packageCache[filePath]){
|
||||
this._packageCache[filePath] = new Package(filePath, this._fastfs);
|
||||
}
|
||||
return this._packageCache[filePath];
|
||||
}
|
||||
|
||||
getPackageForModule(module) {
|
||||
// TODO(amasad): use ES6 Map.
|
||||
if (module.__package) {
|
||||
if (this._packageCache[module.__package]) {
|
||||
return this._packageCache[module.__package];
|
||||
} else {
|
||||
delete module.__package;
|
||||
}
|
||||
}
|
||||
|
||||
const packagePath = this._fastfs.closest(module.path, 'package.json');
|
||||
|
||||
if (!packagePath) {
|
||||
return null;
|
||||
}
|
||||
|
||||
module.__package = packagePath;
|
||||
return this.getPackage(packagePath);
|
||||
}
|
||||
|
||||
_processFileChange(type, filePath, root) {
|
||||
const absPath = path.join(root, filePath);
|
||||
delete this._moduleCache[absPath];
|
||||
delete this._packageCache[absPath];
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = ModuleCache;
|
|
@ -1,61 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
function ModuleDescriptor(fields) {
|
||||
if (!fields.id) {
|
||||
throw new Error('Missing required fields id');
|
||||
}
|
||||
this.id = fields.id;
|
||||
|
||||
if (!fields.path) {
|
||||
throw new Error('Missing required fields path');
|
||||
}
|
||||
this.path = fields.path;
|
||||
|
||||
if (!fields.dependencies) {
|
||||
throw new Error('Missing required fields dependencies');
|
||||
}
|
||||
this.dependencies = fields.dependencies;
|
||||
|
||||
this.resolveDependency = fields.resolveDependency;
|
||||
|
||||
this.entry = fields.entry || false;
|
||||
|
||||
this.isPolyfill = fields.isPolyfill || false;
|
||||
|
||||
this.isAsset_DEPRECATED = fields.isAsset_DEPRECATED || false;
|
||||
this.isAsset = fields.isAsset || false;
|
||||
|
||||
if (this.isAsset_DEPRECATED && this.isAsset) {
|
||||
throw new Error('Cannot be an asset and a deprecated asset');
|
||||
}
|
||||
|
||||
this.resolution = fields.resolution;
|
||||
|
||||
if (this.isAsset && isNaN(this.resolution)) {
|
||||
throw new Error('Expected resolution to be a number for asset modules');
|
||||
}
|
||||
|
||||
this.altId = fields.altId;
|
||||
|
||||
this.isJSON = fields.isJSON;
|
||||
|
||||
this._fields = fields;
|
||||
}
|
||||
|
||||
ModuleDescriptor.prototype.toJSON = function() {
|
||||
return {
|
||||
id: this.id,
|
||||
path: this.path,
|
||||
dependencies: this.dependencies
|
||||
};
|
||||
};
|
||||
|
||||
module.exports = ModuleDescriptor;
|
|
@ -0,0 +1,84 @@
|
|||
'use strict';
|
||||
|
||||
const isAbsolutePath = require('absolute-path');
|
||||
const path = require('path');
|
||||
|
||||
class Package {
|
||||
|
||||
constructor(file, fastfs) {
|
||||
this.path = path.resolve(file);
|
||||
this.root = path.dirname(this.path);
|
||||
this._fastfs = fastfs;
|
||||
this.type = 'Package';
|
||||
}
|
||||
|
||||
getMain() {
|
||||
return this._read().then(json => {
|
||||
if (typeof json.browser === 'string') {
|
||||
return path.join(this.root, json.browser);
|
||||
}
|
||||
|
||||
let main = json.main || 'index';
|
||||
|
||||
if (json.browser && typeof json.browser === 'object') {
|
||||
main = json.browser[main] ||
|
||||
json.browser[main + '.js'] ||
|
||||
json.browser[main + '.json'] ||
|
||||
json.browser[main.replace(/(\.js|\.json)$/, '')] ||
|
||||
main;
|
||||
}
|
||||
|
||||
return path.join(this.root, main);
|
||||
});
|
||||
}
|
||||
|
||||
isHaste() {
|
||||
return this._read().then(json => !!json.name);
|
||||
}
|
||||
|
||||
getName() {
|
||||
return this._read().then(json => json.name);
|
||||
}
|
||||
|
||||
redirectRequire(name) {
|
||||
return this._read().then(json => {
|
||||
const {browser} = json;
|
||||
|
||||
if (!browser || typeof browser !== 'object') {
|
||||
return name;
|
||||
}
|
||||
|
||||
if (name[0] !== '/') {
|
||||
return browser[name] || name;
|
||||
}
|
||||
|
||||
if (!isAbsolutePath(name)) {
|
||||
throw new Error(`Expected ${name} to be absolute path`);
|
||||
}
|
||||
|
||||
const relPath = './' + path.relative(this.root, name);
|
||||
const redirect = browser[relPath] ||
|
||||
browser[relPath + '.js'] ||
|
||||
browser[relPath + '.json'];
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Package;
|
|
@ -9,9 +9,8 @@
|
|||
'use strict';
|
||||
|
||||
jest.dontMock('../')
|
||||
.dontMock('q')
|
||||
.dontMock('../replacePatterns')
|
||||
.setMock('../../ModuleDescriptor', function(data) {return data;});
|
||||
.dontMock('q')
|
||||
.dontMock('../replacePatterns');
|
||||
|
||||
jest.mock('path');
|
||||
|
||||
|
@ -20,6 +19,11 @@ var Promise = require('bluebird');
|
|||
describe('HasteDependencyResolver', function() {
|
||||
var HasteDependencyResolver;
|
||||
|
||||
function createModule(o) {
|
||||
o.getPlainObject = () => Promise.resolve(o);
|
||||
return o;
|
||||
}
|
||||
|
||||
beforeEach(function() {
|
||||
// For the polyfillDeps
|
||||
require('path').join.mockImpl(function(a, b) {
|
||||
|
@ -30,7 +34,10 @@ describe('HasteDependencyResolver', function() {
|
|||
|
||||
describe('getDependencies', function() {
|
||||
pit('should get dependencies with polyfills', function() {
|
||||
var module = {id: 'index', path: '/root/index.js', dependencies: ['a']};
|
||||
var module = createModule({
|
||||
id: 'index',
|
||||
path: '/root/index.js', dependencies: ['a']
|
||||
});
|
||||
var deps = [module];
|
||||
|
||||
var depResolver = new HasteDependencyResolver({
|
||||
|
@ -40,7 +47,7 @@ describe('HasteDependencyResolver', function() {
|
|||
// Is there a better way? How can I mock the prototype instead?
|
||||
var depGraph = depResolver._depGraph;
|
||||
depGraph.getOrderedDependencies.mockImpl(function() {
|
||||
return deps;
|
||||
return Promise.resolve(deps);
|
||||
});
|
||||
depGraph.load.mockImpl(function() {
|
||||
return Promise.resolve();
|
||||
|
@ -113,7 +120,12 @@ describe('HasteDependencyResolver', function() {
|
|||
});
|
||||
|
||||
pit('should get dependencies with polyfills', function() {
|
||||
var module = {id: 'index', path: '/root/index.js', dependencies: ['a']};
|
||||
var module = createModule({
|
||||
id: 'index',
|
||||
path: '/root/index.js',
|
||||
dependencies: ['a'],
|
||||
});
|
||||
|
||||
var deps = [module];
|
||||
|
||||
var depResolver = new HasteDependencyResolver({
|
||||
|
@ -123,7 +135,7 @@ describe('HasteDependencyResolver', function() {
|
|||
// Is there a better way? How can I mock the prototype instead?
|
||||
var depGraph = depResolver._depGraph;
|
||||
depGraph.getOrderedDependencies.mockImpl(function() {
|
||||
return deps;
|
||||
return Promise.resolve(deps);
|
||||
});
|
||||
depGraph.load.mockImpl(function() {
|
||||
return Promise.resolve();
|
||||
|
@ -196,7 +208,11 @@ describe('HasteDependencyResolver', function() {
|
|||
});
|
||||
|
||||
pit('should pass in more polyfills', function() {
|
||||
var module = {id: 'index', path: '/root/index.js', dependencies: ['a']};
|
||||
var module = createModule({
|
||||
id: 'index',
|
||||
path: '/root/index.js',
|
||||
dependencies: ['a']
|
||||
});
|
||||
var deps = [module];
|
||||
|
||||
var depResolver = new HasteDependencyResolver({
|
||||
|
@ -207,7 +223,7 @@ describe('HasteDependencyResolver', function() {
|
|||
// Is there a better way? How can I mock the prototype instead?
|
||||
var depGraph = depResolver._depGraph;
|
||||
depGraph.getOrderedDependencies.mockImpl(function() {
|
||||
return deps;
|
||||
return Promise.resolve(deps);
|
||||
});
|
||||
depGraph.load.mockImpl(function() {
|
||||
return Promise.resolve();
|
||||
|
@ -294,7 +310,7 @@ describe('HasteDependencyResolver', function() {
|
|||
});
|
||||
|
||||
describe('wrapModule', function() {
|
||||
it('should resolve modules', function() {
|
||||
pit('should resolve modules', function() {
|
||||
var depResolver = new HasteDependencyResolver({
|
||||
projectRoot: '/root',
|
||||
});
|
||||
|
@ -446,163 +462,165 @@ describe('HasteDependencyResolver', function() {
|
|||
|
||||
depGraph.resolveDependency.mockImpl(function(fromModule, toModuleName) {
|
||||
if (toModuleName === 'x') {
|
||||
return {
|
||||
return Promise.resolve(createModule({
|
||||
id: 'changed'
|
||||
};
|
||||
}));
|
||||
} else if (toModuleName === 'y') {
|
||||
return { id: 'Y' };
|
||||
return Promise.resolve(createModule({ id: 'Y' }));
|
||||
}
|
||||
return null;
|
||||
|
||||
return Promise.resolve(null);
|
||||
});
|
||||
|
||||
var processedCode = depResolver.wrapModule({
|
||||
return depResolver.wrapModule({
|
||||
id: 'test module',
|
||||
path: '/root/test.js',
|
||||
dependencies: dependencies
|
||||
}, code);
|
||||
}, code).then(processedCode => {
|
||||
|
||||
expect(processedCode).toEqual([
|
||||
'__d(\'test module\',["changed","Y"],function(global,' +
|
||||
' require, requireDynamic, requireLazy, module, exports) { ' +
|
||||
"import'x';",
|
||||
"import 'changed';",
|
||||
"import 'changed' ;",
|
||||
"import Default from 'changed';",
|
||||
"import * as All from 'changed';",
|
||||
"import {} from 'changed';",
|
||||
"import { } from 'changed';",
|
||||
"import {Foo} from 'changed';",
|
||||
"import { Foo } from 'changed';",
|
||||
"import { Foo, } from 'changed';",
|
||||
"import {Foo as Bar} from 'changed';",
|
||||
"import { Foo as Bar } from 'changed';",
|
||||
"import { Foo as Bar, } from 'changed';",
|
||||
"import { Foo, Bar } from 'changed';",
|
||||
"import { Foo, Bar, } from 'changed';",
|
||||
"import { Foo as Bar, Baz } from 'changed';",
|
||||
"import { Foo as Bar, Baz, } from 'changed';",
|
||||
"import { Foo, Bar as Baz } from 'changed';",
|
||||
"import { Foo, Bar as Baz, } from 'changed';",
|
||||
"import { Foo as Bar, Baz as Qux } from 'changed';",
|
||||
"import { Foo as Bar, Baz as Qux, } from 'changed';",
|
||||
"import { Foo, Bar, Baz } from 'changed';",
|
||||
"import { Foo, Bar, Baz, } from 'changed';",
|
||||
"import { Foo as Bar, Baz, Qux } from 'changed';",
|
||||
"import { Foo as Bar, Baz, Qux, } from 'changed';",
|
||||
"import { Foo, Bar as Baz, Qux } from 'changed';",
|
||||
"import { Foo, Bar as Baz, Qux, } from 'changed';",
|
||||
"import { Foo, Bar, Baz as Qux } from 'changed';",
|
||||
"import { Foo, Bar, Baz as Qux, } from 'changed';",
|
||||
"import { Foo as Bar, Baz as Qux, Norf } from 'changed';",
|
||||
"import { Foo as Bar, Baz as Qux, Norf, } from 'changed';",
|
||||
"import { Foo as Bar, Baz, Qux as Norf } from 'changed';",
|
||||
"import { Foo as Bar, Baz, Qux as Norf, } from 'changed';",
|
||||
"import { Foo, Bar as Baz, Qux as Norf } from 'changed';",
|
||||
"import { Foo, Bar as Baz, Qux as Norf, } from 'changed';",
|
||||
"import { Foo as Bar, Baz as Qux, Norf as Enuf } from 'changed';",
|
||||
"import { Foo as Bar, Baz as Qux, Norf as Enuf, } from 'changed';",
|
||||
"import Default, * as All from 'changed';",
|
||||
"import Default, { } from 'changed';",
|
||||
"import Default, { Foo } from 'changed';",
|
||||
"import Default, { Foo, } from 'changed';",
|
||||
"import Default, { Foo as Bar } from 'changed';",
|
||||
"import Default, { Foo as Bar, } from 'changed';",
|
||||
"import Default, { Foo, Bar } from 'changed';",
|
||||
"import Default, { Foo, Bar, } from 'changed';",
|
||||
"import Default, { Foo as Bar, Baz } from 'changed';",
|
||||
"import Default, { Foo as Bar, Baz, } from 'changed';",
|
||||
"import Default, { Foo, Bar as Baz } from 'changed';",
|
||||
"import Default, { Foo, Bar as Baz, } from 'changed';",
|
||||
"import Default, { Foo as Bar, Baz as Qux } from 'changed';",
|
||||
"import Default, { Foo as Bar, Baz as Qux, } from 'changed';",
|
||||
"import Default, { Foo, Bar, Baz } from 'changed';",
|
||||
"import Default, { Foo, Bar, Baz, } from 'changed';",
|
||||
"import Default, { Foo as Bar, Baz, Qux } from 'changed';",
|
||||
"import Default, { Foo as Bar, Baz, Qux, } from 'changed';",
|
||||
"import Default, { Foo, Bar as Baz, Qux } from 'changed';",
|
||||
"import Default, { Foo, Bar as Baz, Qux, } from 'changed';",
|
||||
"import Default, { Foo, Bar, Baz as Qux } from 'changed';",
|
||||
"import Default, { Foo, Bar, Baz as Qux, } from 'changed';",
|
||||
"import Default, { Foo as Bar, Baz as Qux, Norf } from 'changed';",
|
||||
"import Default, { Foo as Bar, Baz as Qux, Norf, } from 'changed';",
|
||||
"import Default, { Foo as Bar, Baz, Qux as Norf } from 'changed';",
|
||||
"import Default, { Foo as Bar, Baz, Qux as Norf, } from 'changed';",
|
||||
"import Default, { Foo, Bar as Baz, Qux as Norf } from 'changed';",
|
||||
"import Default, { Foo, Bar as Baz, Qux as Norf, } from 'changed';",
|
||||
"import Default, { Foo as Bar, Baz as Qux, Norf as NoMore } from 'changed';",
|
||||
"import Default, { Foo as Bar, Baz as Qux, Norf as NoMore, } from 'changed';",
|
||||
"import Default , { } from 'changed';",
|
||||
'import "changed";',
|
||||
'import Default from "changed";',
|
||||
'import * as All from "changed";',
|
||||
'import { } from "changed";',
|
||||
'import { Foo } from "changed";',
|
||||
'import { Foo, } from "changed";',
|
||||
'import { Foo as Bar } from "changed";',
|
||||
'import { Foo as Bar, } from "changed";',
|
||||
'import { Foo, Bar } from "changed";',
|
||||
'import { Foo, Bar, } from "changed";',
|
||||
'import { Foo as Bar, Baz } from "changed";',
|
||||
'import { Foo as Bar, Baz, } from "changed";',
|
||||
'import { Foo, Bar as Baz } from "changed";',
|
||||
'import { Foo, Bar as Baz, } from "changed";',
|
||||
'import { Foo as Bar, Baz as Qux } from "changed";',
|
||||
'import { Foo as Bar, Baz as Qux, } from "changed";',
|
||||
'import { Foo, Bar, Baz } from "changed";',
|
||||
'import { Foo, Bar, Baz, } from "changed";',
|
||||
'import { Foo as Bar, Baz, Qux } from "changed";',
|
||||
'import { Foo as Bar, Baz, Qux, } from "changed";',
|
||||
'import { Foo, Bar as Baz, Qux } from "changed";',
|
||||
'import { Foo, Bar as Baz, Qux, } from "changed";',
|
||||
'import { Foo, Bar, Baz as Qux } from "changed";',
|
||||
'import { Foo, Bar, Baz as Qux, } from "changed";',
|
||||
'import { Foo as Bar, Baz as Qux, Norf } from "changed";',
|
||||
'import { Foo as Bar, Baz as Qux, Norf, } from "changed";',
|
||||
'import { Foo as Bar, Baz, Qux as Norf } from "changed";',
|
||||
'import { Foo as Bar, Baz, Qux as Norf, } from "changed";',
|
||||
'import { Foo, Bar as Baz, Qux as Norf } from "changed";',
|
||||
'import { Foo, Bar as Baz, Qux as Norf, } from "changed";',
|
||||
'import { Foo as Bar, Baz as Qux, Norf as NoMore } from "changed";',
|
||||
'import { Foo as Bar, Baz as Qux, Norf as NoMore, } from "changed";',
|
||||
'import Default, * as All from "changed";',
|
||||
'import Default, { } from "changed";',
|
||||
'import Default, { Foo } from "changed";',
|
||||
'import Default, { Foo, } from "changed";',
|
||||
'import Default, { Foo as Bar } from "changed";',
|
||||
'import Default, { Foo as Bar, } from "changed";',
|
||||
'import Default, { Foo, Bar } from "changed";',
|
||||
'import Default, { Foo, Bar, } from "changed";',
|
||||
'import Default, { Foo as Bar, Baz } from "changed";',
|
||||
'import Default, { Foo as Bar, Baz, } from "changed";',
|
||||
'import Default, { Foo, Bar as Baz } from "changed";',
|
||||
'import Default, { Foo, Bar as Baz, } from "changed";',
|
||||
'import Default, { Foo as Bar, Baz as Qux } from "changed";',
|
||||
'import Default, { Foo as Bar, Baz as Qux, } from "changed";',
|
||||
'import Default, { Foo, Bar, Baz } from "changed";',
|
||||
'import Default, { Foo, Bar, Baz, } from "changed";',
|
||||
'import Default, { Foo as Bar, Baz, Qux } from "changed";',
|
||||
'import Default, { Foo as Bar, Baz, Qux, } from "changed";',
|
||||
'import Default, { Foo, Bar as Baz, Qux } from "changed";',
|
||||
'import Default, { Foo, Bar as Baz, Qux, } from "changed";',
|
||||
'import Default, { Foo, Bar, Baz as Qux } from "changed";',
|
||||
'import Default, { Foo, Bar, Baz as Qux, } from "changed";',
|
||||
'import Default, { Foo as Bar, Baz as Qux, Norf } from "changed";',
|
||||
'import Default, { Foo as Bar, Baz as Qux, Norf, } from "changed";',
|
||||
'import Default, { Foo as Bar, Baz, Qux as Norf } from "changed";',
|
||||
'import Default, { Foo as Bar, Baz, Qux as Norf, } from "changed";',
|
||||
'import Default, { Foo, Bar as Baz, Qux as Norf } from "changed";',
|
||||
'import Default, { Foo, Bar as Baz, Qux as Norf, } from "changed";',
|
||||
'import Default, { Foo as Bar, Baz as Qux, Norf as Enuf } from "changed";',
|
||||
'import Default, { Foo as Bar, Baz as Qux, Norf as Enuf, } from "changed";',
|
||||
'import Default from "Y";',
|
||||
'import * as All from \'z\';',
|
||||
'require("changed")',
|
||||
'require("Y")',
|
||||
'require( \'z\' )',
|
||||
'require( "a")',
|
||||
'require("b" )',
|
||||
'});',
|
||||
].join('\n'));
|
||||
expect(processedCode).toEqual([
|
||||
'__d(\'test module\',["changed","Y"],function(global,' +
|
||||
' require, requireDynamic, requireLazy, module, exports) { ' +
|
||||
"import'x';",
|
||||
"import 'changed';",
|
||||
"import 'changed' ;",
|
||||
"import Default from 'changed';",
|
||||
"import * as All from 'changed';",
|
||||
"import {} from 'changed';",
|
||||
"import { } from 'changed';",
|
||||
"import {Foo} from 'changed';",
|
||||
"import { Foo } from 'changed';",
|
||||
"import { Foo, } from 'changed';",
|
||||
"import {Foo as Bar} from 'changed';",
|
||||
"import { Foo as Bar } from 'changed';",
|
||||
"import { Foo as Bar, } from 'changed';",
|
||||
"import { Foo, Bar } from 'changed';",
|
||||
"import { Foo, Bar, } from 'changed';",
|
||||
"import { Foo as Bar, Baz } from 'changed';",
|
||||
"import { Foo as Bar, Baz, } from 'changed';",
|
||||
"import { Foo, Bar as Baz } from 'changed';",
|
||||
"import { Foo, Bar as Baz, } from 'changed';",
|
||||
"import { Foo as Bar, Baz as Qux } from 'changed';",
|
||||
"import { Foo as Bar, Baz as Qux, } from 'changed';",
|
||||
"import { Foo, Bar, Baz } from 'changed';",
|
||||
"import { Foo, Bar, Baz, } from 'changed';",
|
||||
"import { Foo as Bar, Baz, Qux } from 'changed';",
|
||||
"import { Foo as Bar, Baz, Qux, } from 'changed';",
|
||||
"import { Foo, Bar as Baz, Qux } from 'changed';",
|
||||
"import { Foo, Bar as Baz, Qux, } from 'changed';",
|
||||
"import { Foo, Bar, Baz as Qux } from 'changed';",
|
||||
"import { Foo, Bar, Baz as Qux, } from 'changed';",
|
||||
"import { Foo as Bar, Baz as Qux, Norf } from 'changed';",
|
||||
"import { Foo as Bar, Baz as Qux, Norf, } from 'changed';",
|
||||
"import { Foo as Bar, Baz, Qux as Norf } from 'changed';",
|
||||
"import { Foo as Bar, Baz, Qux as Norf, } from 'changed';",
|
||||
"import { Foo, Bar as Baz, Qux as Norf } from 'changed';",
|
||||
"import { Foo, Bar as Baz, Qux as Norf, } from 'changed';",
|
||||
"import { Foo as Bar, Baz as Qux, Norf as Enuf } from 'changed';",
|
||||
"import { Foo as Bar, Baz as Qux, Norf as Enuf, } from 'changed';",
|
||||
"import Default, * as All from 'changed';",
|
||||
"import Default, { } from 'changed';",
|
||||
"import Default, { Foo } from 'changed';",
|
||||
"import Default, { Foo, } from 'changed';",
|
||||
"import Default, { Foo as Bar } from 'changed';",
|
||||
"import Default, { Foo as Bar, } from 'changed';",
|
||||
"import Default, { Foo, Bar } from 'changed';",
|
||||
"import Default, { Foo, Bar, } from 'changed';",
|
||||
"import Default, { Foo as Bar, Baz } from 'changed';",
|
||||
"import Default, { Foo as Bar, Baz, } from 'changed';",
|
||||
"import Default, { Foo, Bar as Baz } from 'changed';",
|
||||
"import Default, { Foo, Bar as Baz, } from 'changed';",
|
||||
"import Default, { Foo as Bar, Baz as Qux } from 'changed';",
|
||||
"import Default, { Foo as Bar, Baz as Qux, } from 'changed';",
|
||||
"import Default, { Foo, Bar, Baz } from 'changed';",
|
||||
"import Default, { Foo, Bar, Baz, } from 'changed';",
|
||||
"import Default, { Foo as Bar, Baz, Qux } from 'changed';",
|
||||
"import Default, { Foo as Bar, Baz, Qux, } from 'changed';",
|
||||
"import Default, { Foo, Bar as Baz, Qux } from 'changed';",
|
||||
"import Default, { Foo, Bar as Baz, Qux, } from 'changed';",
|
||||
"import Default, { Foo, Bar, Baz as Qux } from 'changed';",
|
||||
"import Default, { Foo, Bar, Baz as Qux, } from 'changed';",
|
||||
"import Default, { Foo as Bar, Baz as Qux, Norf } from 'changed';",
|
||||
"import Default, { Foo as Bar, Baz as Qux, Norf, } from 'changed';",
|
||||
"import Default, { Foo as Bar, Baz, Qux as Norf } from 'changed';",
|
||||
"import Default, { Foo as Bar, Baz, Qux as Norf, } from 'changed';",
|
||||
"import Default, { Foo, Bar as Baz, Qux as Norf } from 'changed';",
|
||||
"import Default, { Foo, Bar as Baz, Qux as Norf, } from 'changed';",
|
||||
"import Default, { Foo as Bar, Baz as Qux, Norf as NoMore } from 'changed';",
|
||||
"import Default, { Foo as Bar, Baz as Qux, Norf as NoMore, } from 'changed';",
|
||||
"import Default , { } from 'changed';",
|
||||
'import "changed";',
|
||||
'import Default from "changed";',
|
||||
'import * as All from "changed";',
|
||||
'import { } from "changed";',
|
||||
'import { Foo } from "changed";',
|
||||
'import { Foo, } from "changed";',
|
||||
'import { Foo as Bar } from "changed";',
|
||||
'import { Foo as Bar, } from "changed";',
|
||||
'import { Foo, Bar } from "changed";',
|
||||
'import { Foo, Bar, } from "changed";',
|
||||
'import { Foo as Bar, Baz } from "changed";',
|
||||
'import { Foo as Bar, Baz, } from "changed";',
|
||||
'import { Foo, Bar as Baz } from "changed";',
|
||||
'import { Foo, Bar as Baz, } from "changed";',
|
||||
'import { Foo as Bar, Baz as Qux } from "changed";',
|
||||
'import { Foo as Bar, Baz as Qux, } from "changed";',
|
||||
'import { Foo, Bar, Baz } from "changed";',
|
||||
'import { Foo, Bar, Baz, } from "changed";',
|
||||
'import { Foo as Bar, Baz, Qux } from "changed";',
|
||||
'import { Foo as Bar, Baz, Qux, } from "changed";',
|
||||
'import { Foo, Bar as Baz, Qux } from "changed";',
|
||||
'import { Foo, Bar as Baz, Qux, } from "changed";',
|
||||
'import { Foo, Bar, Baz as Qux } from "changed";',
|
||||
'import { Foo, Bar, Baz as Qux, } from "changed";',
|
||||
'import { Foo as Bar, Baz as Qux, Norf } from "changed";',
|
||||
'import { Foo as Bar, Baz as Qux, Norf, } from "changed";',
|
||||
'import { Foo as Bar, Baz, Qux as Norf } from "changed";',
|
||||
'import { Foo as Bar, Baz, Qux as Norf, } from "changed";',
|
||||
'import { Foo, Bar as Baz, Qux as Norf } from "changed";',
|
||||
'import { Foo, Bar as Baz, Qux as Norf, } from "changed";',
|
||||
'import { Foo as Bar, Baz as Qux, Norf as NoMore } from "changed";',
|
||||
'import { Foo as Bar, Baz as Qux, Norf as NoMore, } from "changed";',
|
||||
'import Default, * as All from "changed";',
|
||||
'import Default, { } from "changed";',
|
||||
'import Default, { Foo } from "changed";',
|
||||
'import Default, { Foo, } from "changed";',
|
||||
'import Default, { Foo as Bar } from "changed";',
|
||||
'import Default, { Foo as Bar, } from "changed";',
|
||||
'import Default, { Foo, Bar } from "changed";',
|
||||
'import Default, { Foo, Bar, } from "changed";',
|
||||
'import Default, { Foo as Bar, Baz } from "changed";',
|
||||
'import Default, { Foo as Bar, Baz, } from "changed";',
|
||||
'import Default, { Foo, Bar as Baz } from "changed";',
|
||||
'import Default, { Foo, Bar as Baz, } from "changed";',
|
||||
'import Default, { Foo as Bar, Baz as Qux } from "changed";',
|
||||
'import Default, { Foo as Bar, Baz as Qux, } from "changed";',
|
||||
'import Default, { Foo, Bar, Baz } from "changed";',
|
||||
'import Default, { Foo, Bar, Baz, } from "changed";',
|
||||
'import Default, { Foo as Bar, Baz, Qux } from "changed";',
|
||||
'import Default, { Foo as Bar, Baz, Qux, } from "changed";',
|
||||
'import Default, { Foo, Bar as Baz, Qux } from "changed";',
|
||||
'import Default, { Foo, Bar as Baz, Qux, } from "changed";',
|
||||
'import Default, { Foo, Bar, Baz as Qux } from "changed";',
|
||||
'import Default, { Foo, Bar, Baz as Qux, } from "changed";',
|
||||
'import Default, { Foo as Bar, Baz as Qux, Norf } from "changed";',
|
||||
'import Default, { Foo as Bar, Baz as Qux, Norf, } from "changed";',
|
||||
'import Default, { Foo as Bar, Baz, Qux as Norf } from "changed";',
|
||||
'import Default, { Foo as Bar, Baz, Qux as Norf, } from "changed";',
|
||||
'import Default, { Foo, Bar as Baz, Qux as Norf } from "changed";',
|
||||
'import Default, { Foo, Bar as Baz, Qux as Norf, } from "changed";',
|
||||
'import Default, { Foo as Bar, Baz as Qux, Norf as Enuf } from "changed";',
|
||||
'import Default, { Foo as Bar, Baz as Qux, Norf as Enuf, } from "changed";',
|
||||
'import Default from "Y";',
|
||||
'import * as All from \'z\';',
|
||||
'require("changed")',
|
||||
'require("Y")',
|
||||
'require( \'z\' )',
|
||||
'require( "a")',
|
||||
'require("b" )',
|
||||
'});',
|
||||
].join('\n'));
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,302 @@
|
|||
'use strict';
|
||||
|
||||
const Promise = require('bluebird');
|
||||
const {EventEmitter} = require('events');
|
||||
|
||||
const _ = require('underscore');
|
||||
const debug = require('debug')('DependencyGraph');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const readDir = Promise.promisify(fs.readdir);
|
||||
const readFile = Promise.promisify(fs.readFile);
|
||||
const stat = Promise.promisify(fs.stat);
|
||||
|
||||
class Fastfs extends EventEmitter {
|
||||
constructor(roots, fileWatcher, {ignore, pattern}) {
|
||||
super();
|
||||
this._fileWatcher = fileWatcher;
|
||||
this._ignore = ignore;
|
||||
this._pattern = pattern;
|
||||
this._roots = roots.map(root => new File(root, { isDir: true }));
|
||||
}
|
||||
|
||||
build() {
|
||||
const queue = this._roots.slice();
|
||||
return this._search(queue).then(() => {
|
||||
this._fileWatcher.on('all', this._processFileChange.bind(this));
|
||||
});
|
||||
}
|
||||
|
||||
stat(filePath) {
|
||||
return Promise.resolve().then(() => {
|
||||
const file = this._getFile(filePath);
|
||||
return file.stat();
|
||||
});
|
||||
}
|
||||
|
||||
getAllFiles() {
|
||||
return _.chain(this._roots)
|
||||
.map(root => root.getFiles())
|
||||
.flatten()
|
||||
.value();
|
||||
}
|
||||
|
||||
findFilesByExt(ext, { ignore }) {
|
||||
return this.getAllFiles()
|
||||
.filter(
|
||||
file => file.ext() === ext && (!ignore || !ignore(file.path))
|
||||
)
|
||||
.map(file => file.path);
|
||||
}
|
||||
|
||||
findFilesByExts(exts) {
|
||||
return this.getAllFiles()
|
||||
.filter(file => exts.indexOf(file.ext()) !== -1)
|
||||
.map(file => file.path);
|
||||
}
|
||||
|
||||
findFilesByName(name, { ignore }) {
|
||||
return this.getAllFiles()
|
||||
.filter(
|
||||
file => path.basename(file.path) === name &&
|
||||
(!ignore || !ignore(file.path))
|
||||
)
|
||||
.map(file => file.path);
|
||||
}
|
||||
|
||||
readFile(filePath) {
|
||||
return this._getFile(filePath).read();
|
||||
}
|
||||
|
||||
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) {
|
||||
const file = this._getFile(filePath);
|
||||
return file && !file.isDir;
|
||||
}
|
||||
|
||||
dirExists(filePath) {
|
||||
const file = this._getFile(filePath);
|
||||
return file && file.isDir;
|
||||
}
|
||||
|
||||
matches(dir, pattern) {
|
||||
let 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++) {
|
||||
let possibleRoot = this._roots[i];
|
||||
if (isDescendant(possibleRoot.path, filePath)) {
|
||||
return possibleRoot;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
_getAndAssertRoot(filePath) {
|
||||
const root = this._getRoot(filePath);
|
||||
if (!root) {
|
||||
throw new Error(`File ${filePath} not found in any of the roots`);
|
||||
}
|
||||
return root;
|
||||
}
|
||||
|
||||
_getFile(filePath) {
|
||||
return this._getAndAssertRoot(filePath).getFileFromPath(filePath);
|
||||
}
|
||||
|
||||
_add(file) {
|
||||
this._getAndAssertRoot(file.path).addChild(file);
|
||||
}
|
||||
|
||||
_search(queue) {
|
||||
const dir = queue.shift();
|
||||
if (!dir) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
return readAndStatDir(dir.path).then(([filePaths, stats]) => {
|
||||
filePaths.forEach((filePath, i) => {
|
||||
if (this._ignore(filePath)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (stats[i].isDirectory()) {
|
||||
queue.push(
|
||||
new File(filePath, { isDir: true, fstat: stats[i] })
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (filePath.match(this._pattern)) {
|
||||
this._add(new File(filePath, { fstat: stats[i] }));
|
||||
}
|
||||
});
|
||||
return this._search(queue);
|
||||
});
|
||||
}
|
||||
|
||||
_processFileChange(type, filePath, root, fstat) {
|
||||
const absPath = path.join(root, filePath);
|
||||
if (this._ignore(absPath) || (fstat && fstat.isDirectory())) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Make sure this event belongs to one of our roots.
|
||||
|
||||
if (!this._getRoot(absPath)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (type === 'delete' || type === 'change') {
|
||||
const file = this._getFile(absPath);
|
||||
if (file) {
|
||||
file.remove();
|
||||
}
|
||||
}
|
||||
|
||||
if (type !== 'delete') {
|
||||
this._add(new File(absPath, {
|
||||
isDir: false,
|
||||
fstat
|
||||
}));
|
||||
}
|
||||
|
||||
this.emit('change', type, filePath, root, fstat);
|
||||
}
|
||||
}
|
||||
|
||||
class File {
|
||||
constructor(filePath, {isDir, fstat}) {
|
||||
this.path = filePath;
|
||||
this.isDir = Boolean(isDir);
|
||||
if (this.isDir) {
|
||||
this.children = Object.create(null);
|
||||
}
|
||||
|
||||
if (fstat) {
|
||||
this._stat = Promise.resolve(fstat);
|
||||
}
|
||||
}
|
||||
|
||||
read() {
|
||||
if (!this._read) {
|
||||
this._read = readFile(this.path, 'utf8');
|
||||
}
|
||||
return this._read;
|
||||
}
|
||||
|
||||
stat() {
|
||||
if (!this._stat) {
|
||||
this._stat = stat(this.path);
|
||||
}
|
||||
|
||||
return this._stat;
|
||||
}
|
||||
|
||||
addChild(file) {
|
||||
const parts = path.relative(this.path, file.path).split(path.sep);
|
||||
|
||||
if (parts.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (parts.length === 1) {
|
||||
this.children[parts[0]] = file;
|
||||
file.parent = this;
|
||||
} else if (this.children[parts[0]]) {
|
||||
this.children[parts[0]].addChild(file);
|
||||
} else {
|
||||
const dir = new File(path.join(this.path, parts[0]), { isDir: true });
|
||||
dir.parent = this;
|
||||
this.children[parts[0]] = dir;
|
||||
dir.addChild(file);
|
||||
}
|
||||
}
|
||||
|
||||
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++) {
|
||||
let fileName = parts[i];
|
||||
if (!fileName) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!file || !file.isDir) {
|
||||
// File not found.
|
||||
return null;
|
||||
}
|
||||
|
||||
file = file.children[fileName];
|
||||
}
|
||||
|
||||
return file;
|
||||
}
|
||||
|
||||
getFiles() {
|
||||
return _.flatten(_.values(this.children).map(file => {
|
||||
if (file.isDir) {
|
||||
return file.getFiles();
|
||||
} else {
|
||||
return file;
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
ext() {
|
||||
return path.extname(this.path).replace(/^\./, '');
|
||||
}
|
||||
|
||||
remove() {
|
||||
if (!this.parent) {
|
||||
throw new Error(`No parent to delete ${this.path} from`);
|
||||
}
|
||||
|
||||
delete this.parent.children[path.basename(this.path)];
|
||||
}
|
||||
}
|
||||
|
||||
function isDescendant(root, child) {
|
||||
return path.relative(root, child).indexOf('..') !== 0;
|
||||
}
|
||||
|
||||
function readAndStatDir(dir) {
|
||||
return readDir(dir)
|
||||
.then(files => Promise.all(files.map(f => path.join(dir, 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),
|
||||
]));
|
||||
}
|
||||
|
||||
function handleBrokenLink(e) {
|
||||
debug('WARNING: error stating, possibly broken symlink', e.message);
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
module.exports = Fastfs;
|
File diff suppressed because it is too large
Load Diff
|
@ -1,798 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
var ModuleDescriptor = require('../../ModuleDescriptor');
|
||||
var Promise = require('bluebird');
|
||||
var fs = require('fs');
|
||||
var docblock = require('./docblock');
|
||||
var replacePatterns = require('../replacePatterns');
|
||||
var path = require('path');
|
||||
var isAbsolutePath = require('absolute-path');
|
||||
var debug = require('debug')('DependecyGraph');
|
||||
var util = require('util');
|
||||
var declareOpts = require('../../../lib/declareOpts');
|
||||
var getAssetDataFromName = require('../../../lib/getAssetDataFromName');
|
||||
|
||||
var readFile = Promise.promisify(fs.readFile);
|
||||
var readDir = Promise.promisify(fs.readdir);
|
||||
var lstat = Promise.promisify(fs.lstat);
|
||||
var realpath = Promise.promisify(fs.realpath);
|
||||
|
||||
var validateOpts = declareOpts({
|
||||
roots: {
|
||||
type: 'array',
|
||||
required: true,
|
||||
},
|
||||
ignoreFilePath: {
|
||||
type: 'function',
|
||||
default: function(){}
|
||||
},
|
||||
fileWatcher: {
|
||||
type: 'object',
|
||||
required: true,
|
||||
},
|
||||
assetRoots_DEPRECATED: {
|
||||
type: 'array',
|
||||
default: [],
|
||||
},
|
||||
assetExts: {
|
||||
type: 'array',
|
||||
required: true,
|
||||
}
|
||||
});
|
||||
|
||||
function DependecyGraph(options) {
|
||||
var opts = validateOpts(options);
|
||||
|
||||
this._roots = opts.roots;
|
||||
this._assetRoots_DEPRECATED = opts.assetRoots_DEPRECATED;
|
||||
this._assetExts = opts.assetExts;
|
||||
this._ignoreFilePath = opts.ignoreFilePath;
|
||||
this._fileWatcher = options.fileWatcher;
|
||||
|
||||
this._loaded = false;
|
||||
this._queue = this._roots.slice();
|
||||
this._graph = Object.create(null);
|
||||
this._packageByRoot = Object.create(null);
|
||||
this._packagesById = Object.create(null);
|
||||
this._moduleById = Object.create(null);
|
||||
this._debugUpdateEvents = [];
|
||||
|
||||
this._moduleExtPattern = new RegExp(
|
||||
'\.(' + ['js', 'json'].concat(this._assetExts).join('|') + ')$'
|
||||
);
|
||||
|
||||
// Kick off the search process to precompute the dependency graph.
|
||||
this._init();
|
||||
}
|
||||
|
||||
DependecyGraph.prototype.load = function() {
|
||||
if (this._loading != null) {
|
||||
return this._loading;
|
||||
}
|
||||
|
||||
this._loading = Promise.all([
|
||||
this._search(),
|
||||
this._buildAssetMap_DEPRECATED(),
|
||||
]);
|
||||
|
||||
return this._loading;
|
||||
};
|
||||
|
||||
/**
|
||||
* Given an entry file return an array of all the dependent module descriptors.
|
||||
*/
|
||||
DependecyGraph.prototype.getOrderedDependencies = function(entryPath) {
|
||||
var absolutePath = this._getAbsolutePath(entryPath);
|
||||
if (absolutePath == null) {
|
||||
throw new NotFoundError(
|
||||
'Cannot find entry file %s in any of the roots: %j',
|
||||
entryPath,
|
||||
this._roots
|
||||
);
|
||||
}
|
||||
|
||||
var module = this._graph[absolutePath];
|
||||
if (module == null) {
|
||||
throw new Error('Module with path "' + entryPath + '" is not in graph');
|
||||
}
|
||||
|
||||
var self = this;
|
||||
var deps = [];
|
||||
var visited = Object.create(null);
|
||||
|
||||
// Node haste sucks. Id's aren't unique. So to make sure our entry point
|
||||
// is the thing that ends up in our dependency list.
|
||||
var graphMap = Object.create(this._moduleById);
|
||||
graphMap[module.id] = module;
|
||||
|
||||
// Recursively collect the dependency list.
|
||||
function collect(module) {
|
||||
deps.push(module);
|
||||
|
||||
module.dependencies.forEach(function(name) {
|
||||
var id = sansExtJs(name);
|
||||
var dep = self.resolveDependency(module, id);
|
||||
|
||||
if (dep == null) {
|
||||
debug(
|
||||
'WARNING: Cannot find required module `%s` from module `%s`.',
|
||||
name,
|
||||
module.id
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!visited[dep.id]) {
|
||||
visited[dep.id] = true;
|
||||
collect(dep);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
visited[module.id] = true;
|
||||
collect(module);
|
||||
|
||||
return deps;
|
||||
};
|
||||
|
||||
/**
|
||||
* Given a module descriptor `fromModule` return the module descriptor for
|
||||
* the required module `depModuleId`. It could be top-level or relative,
|
||||
* or both.
|
||||
*/
|
||||
DependecyGraph.prototype.resolveDependency = function(
|
||||
fromModule,
|
||||
depModuleId
|
||||
) {
|
||||
if (this._assetMap_DEPRECATED != null) {
|
||||
var assetMatch = depModuleId.match(/^image!(.+)/);
|
||||
// Process DEPRECATED global asset requires.
|
||||
if (assetMatch && assetMatch[1]) {
|
||||
if (!this._assetMap_DEPRECATED[assetMatch[1]]) {
|
||||
debug('WARINING: Cannot find asset:', assetMatch[1]);
|
||||
return null;
|
||||
}
|
||||
return this._assetMap_DEPRECATED[assetMatch[1]];
|
||||
}
|
||||
}
|
||||
|
||||
var packageJson, modulePath, dep;
|
||||
|
||||
// Package relative modules starts with '.' or '..'.
|
||||
if (depModuleId[0] !== '.') {
|
||||
|
||||
// Check if we need to map the dependency to something else via the
|
||||
// `browser` field in package.json
|
||||
var fromPackageJson = this._lookupPackage(fromModule.path);
|
||||
if (fromPackageJson && fromPackageJson.browser &&
|
||||
fromPackageJson.browser[depModuleId]) {
|
||||
depModuleId = fromPackageJson.browser[depModuleId];
|
||||
}
|
||||
|
||||
// `depModuleId` is simply a top-level `providesModule`.
|
||||
// `depModuleId` is a package module but given the full path from the
|
||||
// package, i.e. package_name/module_name
|
||||
if (this._moduleById[sansExtJs(depModuleId)]) {
|
||||
return this._moduleById[sansExtJs(depModuleId)];
|
||||
}
|
||||
|
||||
// `depModuleId` is a package and it's depending on the "main" resolution.
|
||||
packageJson = this._packagesById[depModuleId];
|
||||
|
||||
// We are being forgiving here and raising an error because we could be
|
||||
// processing a file that uses it's own require system.
|
||||
if (packageJson == null) {
|
||||
debug(
|
||||
'WARNING: Cannot find required module `%s` from module `%s`.',
|
||||
depModuleId,
|
||||
fromModule.id
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
||||
var main;
|
||||
|
||||
// We prioritize the `browser` field if it's a module path.
|
||||
if (typeof packageJson.browser === 'string') {
|
||||
main = packageJson.browser;
|
||||
} else {
|
||||
main = packageJson.main || 'index';
|
||||
}
|
||||
|
||||
// If there is a mapping for main in the `browser` field.
|
||||
if (packageJson.browser && typeof packageJson.browser === 'object') {
|
||||
var tmpMain = packageJson.browser[main] ||
|
||||
packageJson.browser[withExtJs(main)] ||
|
||||
packageJson.browser[sansExtJs(main)];
|
||||
if (tmpMain) {
|
||||
main = tmpMain;
|
||||
}
|
||||
}
|
||||
|
||||
modulePath = withExtJs(path.join(packageJson._root, main));
|
||||
dep = this._graph[modulePath];
|
||||
|
||||
// Some packages use just a dir and rely on an index.js inside that dir.
|
||||
if (dep == null) {
|
||||
dep = this._graph[path.join(packageJson._root, main, 'index.js')];
|
||||
}
|
||||
|
||||
if (dep == null) {
|
||||
throw new Error(
|
||||
'Cannot find package main file for package: ' + packageJson._root
|
||||
);
|
||||
}
|
||||
return dep;
|
||||
} else {
|
||||
|
||||
// `depModuleId` is a module defined in a package relative to `fromModule`.
|
||||
packageJson = this._lookupPackage(fromModule.path);
|
||||
|
||||
if (packageJson == null) {
|
||||
throw new Error(
|
||||
'Expected relative module lookup from ' + fromModule.id + ' to ' +
|
||||
depModuleId + ' to be within a package but no package.json found.'
|
||||
);
|
||||
}
|
||||
|
||||
// Example: depModuleId: ../a/b
|
||||
// fromModule.path: /x/y/z
|
||||
// modulePath: /x/y/a/b
|
||||
var dir = path.dirname(fromModule.path);
|
||||
modulePath = path.join(dir, depModuleId);
|
||||
|
||||
if (packageJson.browser && typeof packageJson.browser === 'object') {
|
||||
var relPath = './' + path.relative(packageJson._root, modulePath);
|
||||
var tmpModulePath = packageJson.browser[withExtJs(relPath)] ||
|
||||
packageJson.browser[sansExtJs(relPath)];
|
||||
if (tmpModulePath) {
|
||||
modulePath = path.join(packageJson._root, tmpModulePath);
|
||||
}
|
||||
}
|
||||
|
||||
// JS modules can be required without extensios.
|
||||
if (!this._isFileAsset(modulePath) && !modulePath.match(/\.json$/)) {
|
||||
modulePath = withExtJs(modulePath);
|
||||
}
|
||||
|
||||
dep = this._graph[modulePath];
|
||||
|
||||
// Maybe the dependency is a directory and there is an index.js inside it.
|
||||
if (dep == null) {
|
||||
dep = this._graph[path.join(dir, depModuleId, 'index.js')];
|
||||
}
|
||||
|
||||
// Maybe it's an asset with @n.nx resolution and the path doesn't map
|
||||
// to the id
|
||||
if (dep == null && this._isFileAsset(modulePath)) {
|
||||
dep = this._moduleById[this._lookupName(modulePath)];
|
||||
}
|
||||
|
||||
if (dep == null) {
|
||||
debug(
|
||||
'WARNING: Cannot find required module `%s` from module `%s`.' +
|
||||
' Inferred required module path is %s',
|
||||
depModuleId,
|
||||
fromModule.id,
|
||||
modulePath
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
||||
return dep;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Intiates the filewatcher and kicks off the search process.
|
||||
*/
|
||||
DependecyGraph.prototype._init = function() {
|
||||
var processChange = this._processFileChange.bind(this);
|
||||
var watcher = this._fileWatcher;
|
||||
|
||||
this._loading = this.load().then(function() {
|
||||
watcher.on('all', processChange);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Implements a DFS over the file system looking for modules and packages.
|
||||
*/
|
||||
DependecyGraph.prototype._search = function() {
|
||||
var self = this;
|
||||
var dir = this._queue.shift();
|
||||
|
||||
if (dir == null) {
|
||||
return Promise.resolve(this._graph);
|
||||
}
|
||||
|
||||
// Steps:
|
||||
// 1. Read a dir and stat all the entries.
|
||||
// 2. Filter the files and queue up the directories.
|
||||
// 3. Process any package.json in the files
|
||||
// 4. recur.
|
||||
return readAndStatDir(dir)
|
||||
.spread(function(files, stats) {
|
||||
var modulePaths = files.filter(function(filePath, i) {
|
||||
if (self._ignoreFilePath(filePath)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (stats[i].isDirectory()) {
|
||||
self._queue.push(filePath);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (stats[i].isSymbolicLink()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return filePath.match(self._moduleExtPattern);
|
||||
});
|
||||
|
||||
var processing = self._findAndProcessPackage(files, dir)
|
||||
.then(function() {
|
||||
return Promise.all(modulePaths.map(self._processModule.bind(self)));
|
||||
});
|
||||
|
||||
return Promise.all([
|
||||
processing,
|
||||
self._search()
|
||||
]);
|
||||
})
|
||||
.then(function() {
|
||||
return self;
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Given a list of files find a `package.json` file, and if found parse it
|
||||
* and update indices.
|
||||
*/
|
||||
DependecyGraph.prototype._findAndProcessPackage = function(files, root) {
|
||||
var self = this;
|
||||
|
||||
var packagePath;
|
||||
for (var i = 0; i < files.length ; i++) {
|
||||
var file = files[i];
|
||||
if (path.basename(file) === 'package.json') {
|
||||
packagePath = file;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (packagePath != null) {
|
||||
return this._processPackage(packagePath);
|
||||
} else {
|
||||
return Promise.resolve();
|
||||
}
|
||||
};
|
||||
|
||||
DependecyGraph.prototype._processPackage = function(packagePath) {
|
||||
var packageRoot = path.dirname(packagePath);
|
||||
var self = this;
|
||||
return readFile(packagePath, 'utf8')
|
||||
.then(function(content) {
|
||||
var packageJson;
|
||||
try {
|
||||
packageJson = JSON.parse(content);
|
||||
} catch (e) {
|
||||
debug('WARNING: malformed package.json: ', packagePath);
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
if (packageJson.name == null) {
|
||||
debug(
|
||||
'WARNING: package.json `%s` is missing a name field',
|
||||
packagePath
|
||||
);
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
packageJson._root = packageRoot;
|
||||
self._addPackageToIndices(packageJson);
|
||||
|
||||
return packageJson;
|
||||
});
|
||||
};
|
||||
|
||||
DependecyGraph.prototype._addPackageToIndices = function(packageJson) {
|
||||
this._packageByRoot[packageJson._root] = packageJson;
|
||||
this._packagesById[packageJson.name] = packageJson;
|
||||
};
|
||||
|
||||
DependecyGraph.prototype._removePackageFromIndices = function(packageJson) {
|
||||
delete this._packageByRoot[packageJson._root];
|
||||
delete this._packagesById[packageJson.name];
|
||||
};
|
||||
|
||||
/**
|
||||
* Parse a module and update indices.
|
||||
*/
|
||||
DependecyGraph.prototype._processModule = function(modulePath) {
|
||||
var moduleData = { path: path.resolve(modulePath) };
|
||||
var module;
|
||||
|
||||
if (this._assetExts.indexOf(extname(modulePath)) > -1) {
|
||||
var assetData = getAssetDataFromName(this._lookupName(modulePath));
|
||||
moduleData.id = assetData.assetName;
|
||||
moduleData.resolution = assetData.resolution;
|
||||
moduleData.isAsset = true;
|
||||
moduleData.dependencies = [];
|
||||
module = new ModuleDescriptor(moduleData);
|
||||
this._updateGraphWithModule(module);
|
||||
return Promise.resolve(module);
|
||||
}
|
||||
|
||||
if (extname(modulePath) === 'json') {
|
||||
moduleData.id = this._lookupName(modulePath);
|
||||
moduleData.isJSON = true;
|
||||
moduleData.dependencies = [];
|
||||
module = new ModuleDescriptor(moduleData);
|
||||
this._updateGraphWithModule(module);
|
||||
return Promise.resolve(module);
|
||||
}
|
||||
|
||||
var self = this;
|
||||
return readFile(modulePath, 'utf8')
|
||||
.then(function(content) {
|
||||
var moduleDocBlock = docblock.parseAsObject(content);
|
||||
if (moduleDocBlock.providesModule || moduleDocBlock.provides) {
|
||||
moduleData.id = /^(\S*)/.exec(
|
||||
moduleDocBlock.providesModule || moduleDocBlock.provides
|
||||
)[1];
|
||||
|
||||
// Incase someone wants to require this module via
|
||||
// packageName/path/to/module
|
||||
moduleData.altId = self._lookupName(modulePath);
|
||||
} else {
|
||||
moduleData.id = self._lookupName(modulePath);
|
||||
}
|
||||
moduleData.dependencies = extractRequires(content);
|
||||
|
||||
module = new ModuleDescriptor(moduleData);
|
||||
self._updateGraphWithModule(module);
|
||||
return module;
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Compute the name of module relative to a package it may belong to.
|
||||
*/
|
||||
DependecyGraph.prototype._lookupName = function(modulePath) {
|
||||
var packageJson = this._lookupPackage(modulePath);
|
||||
if (packageJson == null) {
|
||||
return path.resolve(modulePath);
|
||||
} else {
|
||||
var relativePath =
|
||||
sansExtJs(path.relative(packageJson._root, modulePath));
|
||||
return path.join(packageJson.name, relativePath);
|
||||
}
|
||||
};
|
||||
|
||||
DependecyGraph.prototype._deleteModule = function(module) {
|
||||
delete this._graph[module.path];
|
||||
|
||||
// Others may keep a reference so we mark it as deleted.
|
||||
module.deleted = true;
|
||||
|
||||
// Haste allows different module to have the same id.
|
||||
if (this._moduleById[module.id] === module) {
|
||||
delete this._moduleById[module.id];
|
||||
}
|
||||
|
||||
if (module.altId && this._moduleById[module.altId] === module) {
|
||||
delete this._moduleById[module.altId];
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Update the graph and indices with the module.
|
||||
*/
|
||||
DependecyGraph.prototype._updateGraphWithModule = function(module) {
|
||||
if (this._graph[module.path]) {
|
||||
this._deleteModule(this._graph[module.path]);
|
||||
}
|
||||
|
||||
this._graph[module.path] = module;
|
||||
|
||||
if (this._moduleById[module.id]) {
|
||||
debug(
|
||||
'WARNING: Top-level module name conflict `%s`.\n' +
|
||||
'module with path `%s` will replace `%s`',
|
||||
module.id,
|
||||
module.path,
|
||||
this._moduleById[module.id].path
|
||||
);
|
||||
}
|
||||
|
||||
this._moduleById[module.id] = module;
|
||||
|
||||
// Some module maybe refrenced by both @providesModule and
|
||||
// require(package/moduleName).
|
||||
if (module.altId != null && this._moduleById[module.altId] == null) {
|
||||
this._moduleById[module.altId] = module;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Find the nearest package to a module.
|
||||
*/
|
||||
DependecyGraph.prototype._lookupPackage = function(modulePath) {
|
||||
var packageByRoot = this._packageByRoot;
|
||||
|
||||
/**
|
||||
* Auxiliary function to recursively lookup a package.
|
||||
*/
|
||||
function lookupPackage(currDir) {
|
||||
// ideally we stop once we're outside root and this can be a simple child
|
||||
// dir check. However, we have to support modules that was symlinked inside
|
||||
// our project root.
|
||||
if (currDir === '/') {
|
||||
return null;
|
||||
} else {
|
||||
var packageJson = packageByRoot[currDir];
|
||||
if (packageJson) {
|
||||
return packageJson;
|
||||
} else {
|
||||
return lookupPackage(path.dirname(currDir));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return lookupPackage(path.dirname(modulePath));
|
||||
};
|
||||
|
||||
/**
|
||||
* Process a filewatcher change event.
|
||||
*/
|
||||
DependecyGraph.prototype._processFileChange = function(
|
||||
eventType,
|
||||
filePath,
|
||||
root,
|
||||
stat
|
||||
) {
|
||||
var absPath = path.join(root, filePath);
|
||||
if (this._ignoreFilePath(absPath)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._debugUpdateEvents.push({event: eventType, path: filePath});
|
||||
|
||||
if (this._assetExts.indexOf(extname(filePath)) > -1) {
|
||||
this._processAssetChange_DEPRECATED(eventType, absPath);
|
||||
// Fall through because new-style assets are actually modules.
|
||||
}
|
||||
|
||||
var isPackage = path.basename(filePath) === 'package.json';
|
||||
if (eventType === 'delete') {
|
||||
if (isPackage) {
|
||||
var packageJson = this._packageByRoot[path.dirname(absPath)];
|
||||
if (packageJson) {
|
||||
this._removePackageFromIndices(packageJson);
|
||||
}
|
||||
} else {
|
||||
var module = this._graph[absPath];
|
||||
if (module == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._deleteModule(module);
|
||||
}
|
||||
} else if (!(stat && stat.isDirectory())) {
|
||||
var self = this;
|
||||
this._loading = this._loading.then(function() {
|
||||
if (isPackage) {
|
||||
return self._processPackage(absPath);
|
||||
}
|
||||
return self._processModule(absPath);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
DependecyGraph.prototype.getDebugInfo = function() {
|
||||
return '<h1>FileWatcher Update Events</h1>' +
|
||||
'<pre>' + util.inspect(this._debugUpdateEvents) + '</pre>' +
|
||||
'<h1> Graph dump </h1>' +
|
||||
'<pre>' + util.inspect(this._graph) + '</pre>';
|
||||
};
|
||||
|
||||
/**
|
||||
* Searches all roots for the file and returns the first one that has file of
|
||||
* the same path.
|
||||
*/
|
||||
DependecyGraph.prototype._getAbsolutePath = function(filePath) {
|
||||
if (isAbsolutePath(filePath)) {
|
||||
return filePath;
|
||||
}
|
||||
|
||||
for (var i = 0; i < this._roots.length; i++) {
|
||||
var root = this._roots[i];
|
||||
var absPath = path.join(root, filePath);
|
||||
if (this._graph[absPath]) {
|
||||
return absPath;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
DependecyGraph.prototype._buildAssetMap_DEPRECATED = function() {
|
||||
if (this._assetRoots_DEPRECATED == null ||
|
||||
this._assetRoots_DEPRECATED.length === 0) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
this._assetMap_DEPRECATED = Object.create(null);
|
||||
return buildAssetMap_DEPRECATED(
|
||||
this._assetRoots_DEPRECATED,
|
||||
this._processAsset_DEPRECATED.bind(this)
|
||||
);
|
||||
};
|
||||
|
||||
DependecyGraph.prototype._processAsset_DEPRECATED = function(file) {
|
||||
var ext = extname(file);
|
||||
if (this._assetExts.indexOf(ext) !== -1) {
|
||||
var name = assetName(file, ext);
|
||||
if (this._assetMap_DEPRECATED[name] != null) {
|
||||
debug('Conflcting assets', name);
|
||||
}
|
||||
|
||||
this._assetMap_DEPRECATED[name] = new ModuleDescriptor({
|
||||
id: 'image!' + name,
|
||||
path: path.resolve(file),
|
||||
isAsset_DEPRECATED: true,
|
||||
dependencies: [],
|
||||
resolution: getAssetDataFromName(file).resolution,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
DependecyGraph.prototype._processAssetChange_DEPRECATED = function(eventType, file) {
|
||||
if (this._assetMap_DEPRECATED == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
var name = assetName(file, extname(file));
|
||||
if (eventType === 'change' || eventType === 'delete') {
|
||||
delete this._assetMap_DEPRECATED[name];
|
||||
}
|
||||
|
||||
if (eventType === 'change' || eventType === 'add') {
|
||||
this._processAsset_DEPRECATED(file);
|
||||
}
|
||||
};
|
||||
|
||||
DependecyGraph.prototype._isFileAsset = function(file) {
|
||||
return this._assetExts.indexOf(extname(file)) !== -1;
|
||||
};
|
||||
|
||||
/**
|
||||
* Extract all required modules from a `code` string.
|
||||
*/
|
||||
var blockCommentRe = /\/\*(.|\n)*?\*\//g;
|
||||
var lineCommentRe = /\/\/.+(\n|$)/g;
|
||||
function extractRequires(code) {
|
||||
var deps = [];
|
||||
|
||||
code
|
||||
.replace(blockCommentRe, '')
|
||||
.replace(lineCommentRe, '')
|
||||
.replace(replacePatterns.IMPORT_RE, function(match, pre, quot, dep, post) {
|
||||
deps.push(dep);
|
||||
return match;
|
||||
})
|
||||
.replace(replacePatterns.REQUIRE_RE, function(match, pre, quot, dep, post) {
|
||||
deps.push(dep);
|
||||
});
|
||||
|
||||
return deps;
|
||||
}
|
||||
|
||||
/**
|
||||
* `file` without the .js extension.
|
||||
*/
|
||||
function sansExtJs(file) {
|
||||
if (file.match(/\.js$/)) {
|
||||
return file.slice(0, -3);
|
||||
} else {
|
||||
return file;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* `file` with the .js extension.
|
||||
*/
|
||||
function withExtJs(file) {
|
||||
if (file.match(/\.js$/)) {
|
||||
return file;
|
||||
} else {
|
||||
return file + '.js';
|
||||
}
|
||||
}
|
||||
|
||||
function handleBrokenLink(e) {
|
||||
debug('WARNING: error stating, possibly broken symlink', e.message);
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
function readAndStatDir(dir) {
|
||||
return readDir(dir)
|
||||
.then(function(files){
|
||||
return Promise.all(files.map(function(filePath) {
|
||||
return realpath(path.join(dir, filePath)).catch(handleBrokenLink);
|
||||
}));
|
||||
}).then(function(files) {
|
||||
files = files.filter(function(f) {
|
||||
return !!f;
|
||||
});
|
||||
|
||||
var stats = files.map(function(filePath) {
|
||||
return lstat(filePath).catch(handleBrokenLink);
|
||||
});
|
||||
|
||||
return [
|
||||
files,
|
||||
Promise.all(stats),
|
||||
];
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a list of roots and list of extensions find all the files in
|
||||
* the directory with that extension and build a map of those assets.
|
||||
*/
|
||||
function buildAssetMap_DEPRECATED(roots, processAsset) {
|
||||
var queue = roots.slice(0);
|
||||
|
||||
function search() {
|
||||
var root = queue.shift();
|
||||
|
||||
if (root == null) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
return readAndStatDir(root).spread(function(files, stats) {
|
||||
files.forEach(function(file, i) {
|
||||
if (stats[i].isDirectory()) {
|
||||
queue.push(file);
|
||||
} else {
|
||||
processAsset(file);
|
||||
}
|
||||
});
|
||||
|
||||
return search();
|
||||
});
|
||||
}
|
||||
|
||||
return search();
|
||||
}
|
||||
|
||||
function assetName(file, ext) {
|
||||
return path.basename(file, '.' + ext).replace(/@[\d\.]+x/, '');
|
||||
}
|
||||
|
||||
function extname(name) {
|
||||
return path.extname(name).replace(/^\./, '');
|
||||
}
|
||||
|
||||
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 = DependecyGraph;
|
|
@ -1,176 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
var path = require('path');
|
||||
var DependencyGraph = require('./DependencyGraph');
|
||||
var replacePatterns = require('./replacePatterns');
|
||||
var ModuleDescriptor = require('../ModuleDescriptor');
|
||||
var declareOpts = require('../../lib/declareOpts');
|
||||
|
||||
var DEFINE_MODULE_CODE = [
|
||||
'__d(',
|
||||
'\'_moduleName_\',',
|
||||
'_deps_,',
|
||||
'function(global, require, requireDynamic, requireLazy, module, exports) {',
|
||||
' _code_',
|
||||
'\n});',
|
||||
].join('');
|
||||
|
||||
var DEFINE_MODULE_REPLACE_RE = /_moduleName_|_code_|_deps_/g;
|
||||
|
||||
var validateOpts = declareOpts({
|
||||
projectRoots: {
|
||||
type: 'array',
|
||||
required: true,
|
||||
},
|
||||
blacklistRE: {
|
||||
type: 'object', // typeof regex is object
|
||||
},
|
||||
polyfillModuleNames: {
|
||||
type: 'array',
|
||||
default: [],
|
||||
},
|
||||
nonPersistent: {
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
},
|
||||
moduleFormat: {
|
||||
type: 'string',
|
||||
default: 'haste',
|
||||
},
|
||||
assetRoots: {
|
||||
type: 'array',
|
||||
default: [],
|
||||
},
|
||||
fileWatcher: {
|
||||
type: 'object',
|
||||
required: true,
|
||||
},
|
||||
assetExts: {
|
||||
type: 'array',
|
||||
required: true,
|
||||
}
|
||||
});
|
||||
|
||||
function HasteDependencyResolver(options) {
|
||||
var opts = validateOpts(options);
|
||||
|
||||
this._depGraph = new DependencyGraph({
|
||||
roots: opts.projectRoots,
|
||||
assetRoots_DEPRECATED: opts.assetRoots,
|
||||
assetExts: opts.assetExts,
|
||||
ignoreFilePath: function(filepath) {
|
||||
return filepath.indexOf('__tests__') !== -1 ||
|
||||
(opts.blacklistRE && opts.blacklistRE.test(filepath));
|
||||
},
|
||||
fileWatcher: opts.fileWatcher,
|
||||
});
|
||||
|
||||
|
||||
this._polyfillModuleNames = opts.polyfillModuleNames || [];
|
||||
}
|
||||
|
||||
var getDependenciesValidateOpts = declareOpts({
|
||||
dev: {
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
},
|
||||
});
|
||||
|
||||
HasteDependencyResolver.prototype.getDependencies = function(main, options) {
|
||||
var opts = getDependenciesValidateOpts(options);
|
||||
|
||||
var depGraph = this._depGraph;
|
||||
var self = this;
|
||||
|
||||
return depGraph.load()
|
||||
.then(function() {
|
||||
var dependencies = depGraph.getOrderedDependencies(main);
|
||||
var mainModuleId = dependencies[0].id;
|
||||
|
||||
self._prependPolyfillDependencies(dependencies, opts.dev);
|
||||
|
||||
return {
|
||||
mainModuleId: mainModuleId,
|
||||
dependencies: dependencies
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
HasteDependencyResolver.prototype._prependPolyfillDependencies = function(
|
||||
dependencies,
|
||||
isDev
|
||||
) {
|
||||
var polyfillModuleNames = [
|
||||
isDev
|
||||
? path.join(__dirname, 'polyfills/prelude_dev.js')
|
||||
: path.join(__dirname, 'polyfills/prelude.js'),
|
||||
path.join(__dirname, 'polyfills/require.js'),
|
||||
path.join(__dirname, 'polyfills/polyfills.js'),
|
||||
path.join(__dirname, 'polyfills/console.js'),
|
||||
path.join(__dirname, 'polyfills/error-guard.js'),
|
||||
path.join(__dirname, 'polyfills/String.prototype.es6.js'),
|
||||
path.join(__dirname, 'polyfills/Array.prototype.es6.js'),
|
||||
].concat(this._polyfillModuleNames);
|
||||
|
||||
var polyfillModules = polyfillModuleNames.map(
|
||||
function(polyfillModuleName, idx) {
|
||||
return new ModuleDescriptor({
|
||||
path: polyfillModuleName,
|
||||
id: polyfillModuleName,
|
||||
dependencies: polyfillModuleNames.slice(0, idx),
|
||||
isPolyfill: true
|
||||
});
|
||||
}
|
||||
);
|
||||
dependencies.unshift.apply(dependencies, polyfillModules);
|
||||
};
|
||||
|
||||
HasteDependencyResolver.prototype.wrapModule = function(module, code) {
|
||||
if (module.isPolyfill) {
|
||||
return code;
|
||||
}
|
||||
|
||||
var resolvedDeps = Object.create(null);
|
||||
var resolvedDepsArr = [];
|
||||
|
||||
for (var i = 0; i < module.dependencies.length; i++) {
|
||||
var depName = module.dependencies[i];
|
||||
var dep = this._depGraph.resolveDependency(module, depName);
|
||||
if (dep) {
|
||||
resolvedDeps[depName] = dep.id;
|
||||
resolvedDepsArr.push(dep.id);
|
||||
}
|
||||
}
|
||||
|
||||
var relativizeCode = function(codeMatch, pre, quot, depName, post) {
|
||||
var depId = resolvedDeps[depName];
|
||||
if (depId) {
|
||||
return pre + quot + depId + post;
|
||||
} else {
|
||||
return codeMatch;
|
||||
}
|
||||
};
|
||||
|
||||
return DEFINE_MODULE_CODE.replace(DEFINE_MODULE_REPLACE_RE, function(key) {
|
||||
return {
|
||||
'_moduleName_': module.id,
|
||||
'_code_': code.replace(replacePatterns.IMPORT_RE, relativizeCode)
|
||||
.replace(replacePatterns.REQUIRE_RE, relativizeCode),
|
||||
'_deps_': JSON.stringify(resolvedDepsArr),
|
||||
}[key];
|
||||
});
|
||||
};
|
||||
|
||||
HasteDependencyResolver.prototype.getDebugInfo = function() {
|
||||
return this._depGraph.getDebugInfo();
|
||||
};
|
||||
|
||||
module.exports = HasteDependencyResolver;
|
|
@ -8,15 +8,174 @@
|
|||
*/
|
||||
'use strict';
|
||||
|
||||
var HasteDependencyResolver = require('./haste');
|
||||
var NodeDependencyResolver = require('./node');
|
||||
var path = require('path');
|
||||
var DependencyGraph = require('./DependencyGraph');
|
||||
var replacePatterns = require('./replacePatterns');
|
||||
var declareOpts = require('../lib/declareOpts');
|
||||
var Promise = require('bluebird');
|
||||
|
||||
module.exports = function createDependencyResolver(options) {
|
||||
if (options.moduleFormat === 'haste') {
|
||||
return new HasteDependencyResolver(options);
|
||||
} else if (options.moduleFormat === 'node') {
|
||||
return new NodeDependencyResolver(options);
|
||||
} else {
|
||||
throw new Error('unsupported');
|
||||
var validateOpts = declareOpts({
|
||||
projectRoots: {
|
||||
type: 'array',
|
||||
required: true,
|
||||
},
|
||||
blacklistRE: {
|
||||
type: 'object', // typeof regex is object
|
||||
},
|
||||
polyfillModuleNames: {
|
||||
type: 'array',
|
||||
default: [],
|
||||
},
|
||||
nonPersistent: {
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
},
|
||||
moduleFormat: {
|
||||
type: 'string',
|
||||
default: 'haste',
|
||||
},
|
||||
assetRoots: {
|
||||
type: 'array',
|
||||
default: [],
|
||||
},
|
||||
fileWatcher: {
|
||||
type: 'object',
|
||||
required: true,
|
||||
},
|
||||
assetExts: {
|
||||
type: 'array',
|
||||
required: true,
|
||||
}
|
||||
});
|
||||
|
||||
function HasteDependencyResolver(options) {
|
||||
var opts = validateOpts(options);
|
||||
|
||||
this._depGraph = new DependencyGraph({
|
||||
roots: opts.projectRoots,
|
||||
assetRoots_DEPRECATED: opts.assetRoots,
|
||||
assetExts: opts.assetExts,
|
||||
ignoreFilePath: function(filepath) {
|
||||
return filepath.indexOf('__tests__') !== -1 ||
|
||||
(opts.blacklistRE && opts.blacklistRE.test(filepath));
|
||||
},
|
||||
fileWatcher: opts.fileWatcher,
|
||||
});
|
||||
|
||||
|
||||
this._polyfillModuleNames = opts.polyfillModuleNames || [];
|
||||
}
|
||||
|
||||
var getDependenciesValidateOpts = declareOpts({
|
||||
dev: {
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
},
|
||||
});
|
||||
|
||||
HasteDependencyResolver.prototype.getDependencies = function(main, options) {
|
||||
var opts = getDependenciesValidateOpts(options);
|
||||
|
||||
var depGraph = this._depGraph;
|
||||
var self = this;
|
||||
return depGraph.load().then(
|
||||
() => depGraph.getOrderedDependencies(main).then(
|
||||
dependencies => {
|
||||
const mainModuleId = dependencies[0].id;
|
||||
self._prependPolyfillDependencies(
|
||||
dependencies,
|
||||
opts.dev
|
||||
);
|
||||
|
||||
return {
|
||||
mainModuleId: mainModuleId,
|
||||
dependencies: dependencies
|
||||
};
|
||||
}
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
HasteDependencyResolver.prototype._prependPolyfillDependencies = function(
|
||||
dependencies,
|
||||
isDev
|
||||
) {
|
||||
var polyfillModuleNames = [
|
||||
isDev
|
||||
? path.join(__dirname, 'polyfills/prelude_dev.js')
|
||||
: path.join(__dirname, 'polyfills/prelude.js'),
|
||||
path.join(__dirname, 'polyfills/require.js'),
|
||||
path.join(__dirname, 'polyfills/polyfills.js'),
|
||||
path.join(__dirname, 'polyfills/console.js'),
|
||||
path.join(__dirname, 'polyfills/error-guard.js'),
|
||||
path.join(__dirname, 'polyfills/String.prototype.es6.js'),
|
||||
path.join(__dirname, 'polyfills/Array.prototype.es6.js'),
|
||||
].concat(this._polyfillModuleNames);
|
||||
|
||||
var polyfillModules = polyfillModuleNames.map(
|
||||
(polyfillModuleName, idx) => ({
|
||||
path: polyfillModuleName,
|
||||
id: polyfillModuleName,
|
||||
dependencies: polyfillModuleNames.slice(0, idx),
|
||||
isPolyfill: true,
|
||||
})
|
||||
);
|
||||
|
||||
dependencies.unshift.apply(dependencies, polyfillModules);
|
||||
};
|
||||
|
||||
HasteDependencyResolver.prototype.wrapModule = function(module, code) {
|
||||
if (module.isPolyfill) {
|
||||
return Promise.resolve(code);
|
||||
}
|
||||
|
||||
const resolvedDeps = Object.create(null);
|
||||
const resolvedDepsArr = [];
|
||||
|
||||
return Promise.all(
|
||||
module.dependencies.map(depName => {
|
||||
return this._depGraph.resolveDependency(module, depName)
|
||||
.then((dep) => dep && dep.getPlainObject().then(mod => {
|
||||
if (mod) {
|
||||
resolvedDeps[depName] = mod.id;
|
||||
resolvedDepsArr.push(mod.id);
|
||||
}
|
||||
}));
|
||||
})
|
||||
).then(() => {
|
||||
const relativizeCode = (codeMatch, pre, quot, depName, post) => {
|
||||
const depId = resolvedDeps[depName];
|
||||
if (depId) {
|
||||
return pre + quot + depId + post;
|
||||
} else {
|
||||
return codeMatch;
|
||||
}
|
||||
};
|
||||
|
||||
return defineModuleCode({
|
||||
code: code
|
||||
.replace(replacePatterns.IMPORT_RE, relativizeCode)
|
||||
.replace(replacePatterns.REQUIRE_RE, relativizeCode),
|
||||
deps: JSON.stringify(resolvedDepsArr),
|
||||
moduleName: module.id,
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
HasteDependencyResolver.prototype.getDebugInfo = function() {
|
||||
return this._depGraph.getDebugInfo();
|
||||
};
|
||||
|
||||
function defineModuleCode({moduleName, code, deps}) {
|
||||
return [
|
||||
`__d(`,
|
||||
`'${moduleName}',`,
|
||||
`${deps},`,
|
||||
'function(global, require, ',
|
||||
'requireDynamic, requireLazy, module, exports) {',
|
||||
` ${code}`,
|
||||
'\n});',
|
||||
].join('');
|
||||
}
|
||||
|
||||
module.exports = HasteDependencyResolver;
|
||||
|
|
|
@ -1,51 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
var Promise = require('bluebird');
|
||||
var ModuleDescriptor = require('../ModuleDescriptor');
|
||||
|
||||
var mdeps = require('module-deps');
|
||||
var path = require('path');
|
||||
|
||||
exports.getRuntimeCode = function() {};
|
||||
|
||||
exports.wrapModule = function(id, source) {
|
||||
return Promise.resolve(
|
||||
'define(' + JSON.stringify(id) + ',' + ' function(exports, module) {\n'
|
||||
+ source + '\n});'
|
||||
);
|
||||
};
|
||||
|
||||
exports.getDependencies = function(root, fileEntryPath) {
|
||||
return new Promise(function(resolve) {
|
||||
fileEntryPath = path.join(process.cwd(), root, fileEntryPath);
|
||||
|
||||
var md = mdeps();
|
||||
|
||||
md.end({file: fileEntryPath});
|
||||
|
||||
var deps = [];
|
||||
|
||||
md.on('data', function(data) {
|
||||
deps.push(
|
||||
new ModuleDescriptor({
|
||||
id: data.id,
|
||||
deps: data.deps,
|
||||
path: data.file,
|
||||
entry: data.entry
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
md.on('end', function() {
|
||||
resolve(deps);
|
||||
});
|
||||
});
|
||||
};
|
|
@ -101,7 +101,7 @@ describe('Packager', function() {
|
|||
});
|
||||
|
||||
wrapModule.mockImpl(function(module, code) {
|
||||
return 'lol ' + code + ' lol';
|
||||
return Promise.resolve('lol ' + code + ' lol');
|
||||
});
|
||||
|
||||
require('image-size').mockImpl(function(path, cb) {
|
||||
|
|
|
@ -159,16 +159,17 @@ Packager.prototype._transformModule = function(ppackage, module) {
|
|||
}
|
||||
|
||||
var resolver = this._resolver;
|
||||
return transform.then(function(transformed) {
|
||||
var code = resolver.wrapModule(module, transformed.code);
|
||||
return new ModuleTransport({
|
||||
code: code,
|
||||
map: transformed.map,
|
||||
sourceCode: transformed.sourceCode,
|
||||
sourcePath: transformed.sourcePath,
|
||||
virtual: transformed.virtual,
|
||||
});
|
||||
});
|
||||
return transform.then(
|
||||
transformed => resolver.wrapModule(module, transformed.code).then(
|
||||
code => new ModuleTransport({
|
||||
code: code,
|
||||
map: transformed.map,
|
||||
sourceCode: transformed.sourceCode,
|
||||
sourcePath: transformed.sourcePath,
|
||||
virtual: transformed.virtual,
|
||||
})
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
Packager.prototype.getGraphDebugInfo = function() {
|
||||
|
|
|
@ -51,7 +51,7 @@ fs.readFile.mockImpl(function(filepath, encoding, callback) {
|
|||
var node = getToNode(filepath);
|
||||
// dir check
|
||||
if (node && typeof node === 'object' && node.SYMLINK == null) {
|
||||
callback(new Error('Trying to read a dir, ESIDR, or whatever'));
|
||||
callback(new Error('Error readFile a dir: ' + filepath));
|
||||
}
|
||||
return callback(null, node);
|
||||
} catch (e) {
|
||||
|
@ -59,12 +59,13 @@ fs.readFile.mockImpl(function(filepath, encoding, callback) {
|
|||
}
|
||||
});
|
||||
|
||||
fs.lstat.mockImpl(function(filepath, callback) {
|
||||
fs.stat.mockImpl(function(filepath, callback) {
|
||||
var node;
|
||||
try {
|
||||
node = getToNode(filepath);
|
||||
} catch (e) {
|
||||
return callback(e);
|
||||
callback(e);
|
||||
return;
|
||||
}
|
||||
|
||||
var mtime = {
|
||||
|
@ -73,7 +74,12 @@ fs.lstat.mockImpl(function(filepath, callback) {
|
|||
}
|
||||
};
|
||||
|
||||
if (node && typeof node === 'object' && node.SYMLINK == null) {
|
||||
if (node.SYMLINK) {
|
||||
fs.stat(node.SYMLINK, callback);
|
||||
return;
|
||||
}
|
||||
|
||||
if (node && typeof node === 'object') {
|
||||
callback(null, {
|
||||
isDirectory: function() {
|
||||
return true;
|
||||
|
@ -89,9 +95,6 @@ fs.lstat.mockImpl(function(filepath, callback) {
|
|||
return false;
|
||||
},
|
||||
isSymbolicLink: function() {
|
||||
if (typeof node === 'object' && node.SYMLINK) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
mtime: mtime,
|
||||
|
@ -113,6 +116,9 @@ function getToNode(filepath) {
|
|||
}
|
||||
var node = filesystem;
|
||||
parts.slice(1).forEach(function(part) {
|
||||
if (node && node.SYMLINK) {
|
||||
node = getToNode(node.SYMLINK);
|
||||
}
|
||||
node = node[part];
|
||||
});
|
||||
|
||||
|
|
Loading…
Reference in New Issue