[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:
Amjad Masad 2015-06-19 18:01:21 -07:00
parent 0405c82798
commit 8faa406e96
32 changed files with 4881 additions and 2894 deletions

View File

@ -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',

24
react-packager/.babelrc Normal file
View File

@ -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
}

View File

@ -8,6 +8,10 @@
*/
'use strict';
require('babel/register')({
only: /react-packager\/src/
});
useGracefulFs();
var Activity = require('./src/Activity');

View File

@ -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;
}
);
}

View File

@ -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;

View File

@ -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;

File diff suppressed because it is too large Load Diff

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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'));
});
});
});
});

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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);
});
});
};

View File

@ -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) {

View File

@ -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() {

View File

@ -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];
});