mirror of
https://github.com/zhigang1992/react-native.git
synced 2026-04-08 09:37:21 +08:00
Summary: This makes ResolutionRequest stop calling `dirname` when arriving at `"."`, not only at `parse(path).root`. Needed for Buck integration, as we are using relative paths there Reviewed By: cpojer Differential Revision: D4058723 fbshipit-source-id: d1856043193cfad3f750577ba0f63f03f8effe7a
540 lines
17 KiB
JavaScript
540 lines
17 KiB
JavaScript
/**
|
|
* Copyright (c) 2015-present, Facebook, Inc.
|
|
* All rights reserved.
|
|
*
|
|
* This source code is licensed under the BSD-style license found in the
|
|
* LICENSE file in the root directory of this source tree. An additional grant
|
|
* of patent rights can be found in the PATENTS file in the same directory.
|
|
*/
|
|
'use strict';
|
|
|
|
const AsyncTaskGroup = require('../lib/AsyncTaskGroup');
|
|
const MapWithDefaults = require('../lib/MapWithDefaults');
|
|
const debug = require('debug')('ReactNativePackager:DependencyGraph');
|
|
const util = require('util');
|
|
const path = require('path');
|
|
const realPath = require('path');
|
|
const isAbsolutePath = require('absolute-path');
|
|
const getAssetDataFromName = require('../lib/getAssetDataFromName');
|
|
|
|
const emptyModule = require.resolve('./assets/empty-module.js');
|
|
|
|
class ResolutionRequest {
|
|
constructor({
|
|
platform,
|
|
platforms,
|
|
preferNativePlatform,
|
|
entryPath,
|
|
hasteMap,
|
|
deprecatedAssetMap,
|
|
helpers,
|
|
moduleCache,
|
|
fastfs,
|
|
shouldThrowOnUnresolvedErrors,
|
|
extraNodeModules,
|
|
}) {
|
|
this._platform = platform;
|
|
this._platforms = platforms;
|
|
this._preferNativePlatform = preferNativePlatform;
|
|
this._entryPath = entryPath;
|
|
this._hasteMap = hasteMap;
|
|
this._deprecatedAssetMap = deprecatedAssetMap;
|
|
this._helpers = helpers;
|
|
this._moduleCache = moduleCache;
|
|
this._fastfs = fastfs;
|
|
this._shouldThrowOnUnresolvedErrors = shouldThrowOnUnresolvedErrors;
|
|
this._extraNodeModules = extraNodeModules;
|
|
this._resetResolutionCache();
|
|
}
|
|
|
|
_tryResolve(action, secondaryAction) {
|
|
return action().catch((error) => {
|
|
if (error.type !== 'UnableToResolveError') {
|
|
throw error;
|
|
}
|
|
return secondaryAction();
|
|
});
|
|
}
|
|
|
|
resolveDependency(fromModule, toModuleName) {
|
|
const resHash = resolutionHash(fromModule.path, toModuleName);
|
|
|
|
if (this._immediateResolutionCache[resHash]) {
|
|
return Promise.resolve(this._immediateResolutionCache[resHash]);
|
|
}
|
|
|
|
const asset_DEPRECATED = this._deprecatedAssetMap.resolve(
|
|
fromModule,
|
|
toModuleName
|
|
);
|
|
if (asset_DEPRECATED) {
|
|
return Promise.resolve(asset_DEPRECATED);
|
|
}
|
|
|
|
const cacheResult = (result) => {
|
|
this._immediateResolutionCache[resHash] = result;
|
|
return result;
|
|
};
|
|
|
|
const forgive = (error) => {
|
|
if (
|
|
error.type !== 'UnableToResolveError' ||
|
|
this._shouldThrowOnUnresolvedErrors(this._entryPath, this._platform)
|
|
) {
|
|
throw error;
|
|
}
|
|
|
|
debug(
|
|
'Unable to resolve module %s from %s',
|
|
toModuleName,
|
|
fromModule.path
|
|
);
|
|
return null;
|
|
};
|
|
|
|
if (!this._helpers.isNodeModulesDir(fromModule.path)
|
|
&& !(isRelativeImport(toModuleName) || isAbsolutePath(toModuleName))) {
|
|
return this._tryResolve(
|
|
() => this._resolveHasteDependency(fromModule, toModuleName),
|
|
() => this._resolveNodeDependency(fromModule, toModuleName)
|
|
).then(
|
|
cacheResult,
|
|
forgive,
|
|
);
|
|
}
|
|
|
|
return this._resolveNodeDependency(fromModule, toModuleName)
|
|
.then(
|
|
cacheResult,
|
|
forgive,
|
|
);
|
|
}
|
|
|
|
getOrderedDependencies({
|
|
response,
|
|
mocksPattern,
|
|
transformOptions,
|
|
onProgress,
|
|
recursive = true,
|
|
}) {
|
|
return this._getAllMocks(mocksPattern).then(allMocks => {
|
|
const entry = this._moduleCache.getModule(this._entryPath);
|
|
const mocks = Object.create(null);
|
|
|
|
response.pushDependency(entry);
|
|
let totalModules = 1;
|
|
let finishedModules = 0;
|
|
|
|
const resolveDependencies = module =>
|
|
module.getDependencies(transformOptions)
|
|
.then(dependencyNames =>
|
|
Promise.all(
|
|
dependencyNames.map(name => this.resolveDependency(module, name))
|
|
).then(dependencies => [dependencyNames, dependencies])
|
|
);
|
|
|
|
const addMockDependencies = !allMocks
|
|
? (module, result) => result
|
|
: (module, [dependencyNames, dependencies]) => {
|
|
const list = [module.getName()];
|
|
const pkg = module.getPackage();
|
|
if (pkg) {
|
|
list.push(pkg.getName());
|
|
}
|
|
return Promise.all(list).then(names => {
|
|
names.forEach(name => {
|
|
if (allMocks[name] && !mocks[name]) {
|
|
const mockModule = this._moduleCache.getModule(allMocks[name]);
|
|
dependencyNames.push(name);
|
|
dependencies.push(mockModule);
|
|
mocks[name] = allMocks[name];
|
|
}
|
|
});
|
|
return [dependencyNames, dependencies];
|
|
});
|
|
};
|
|
|
|
const collectedDependencies = new MapWithDefaults(module => collect(module));
|
|
const crawlDependencies = (mod, [depNames, dependencies]) => {
|
|
const filteredPairs = [];
|
|
|
|
dependencies.forEach((modDep, i) => {
|
|
const name = depNames[i];
|
|
if (modDep == null) {
|
|
// It is possible to require mocks that don't have a real
|
|
// module backing them. If a dependency cannot be found but there
|
|
// exists a mock with the desired ID, resolve it and add it as
|
|
// a dependency.
|
|
if (allMocks && allMocks[name] && !mocks[name]) {
|
|
const mockModule = this._moduleCache.getModule(allMocks[name]);
|
|
mocks[name] = allMocks[name];
|
|
return filteredPairs.push([name, mockModule]);
|
|
}
|
|
|
|
debug(
|
|
'WARNING: Cannot find required module `%s` from module `%s`',
|
|
name,
|
|
mod.path
|
|
);
|
|
return false;
|
|
}
|
|
return filteredPairs.push([name, modDep]);
|
|
});
|
|
|
|
response.setResolvedDependencyPairs(mod, filteredPairs);
|
|
|
|
const dependencyModules = filteredPairs.map(([, m]) => m);
|
|
const newDependencies =
|
|
dependencyModules.filter(m => !collectedDependencies.has(m));
|
|
|
|
if (onProgress) {
|
|
finishedModules += 1;
|
|
totalModules += newDependencies.length;
|
|
onProgress(finishedModules, totalModules);
|
|
}
|
|
|
|
if (recursive) {
|
|
// doesn't block the return of this function invocation, but defers
|
|
// the resulution of collectionsInProgress.done.then(...)
|
|
dependencyModules
|
|
.forEach(dependency => collectedDependencies.get(dependency));
|
|
}
|
|
return dependencyModules;
|
|
};
|
|
|
|
const collectionsInProgress = new AsyncTaskGroup();
|
|
function collect(module) {
|
|
collectionsInProgress.start(module);
|
|
const result = resolveDependencies(module)
|
|
.then(result => addMockDependencies(module, result))
|
|
.then(result => crawlDependencies(module, result));
|
|
const end = () => collectionsInProgress.end(module);
|
|
result.then(end, end);
|
|
return result;
|
|
}
|
|
|
|
return Promise.all([
|
|
// kicks off recursive dependency discovery, but doesn't block until it's done
|
|
collectedDependencies.get(entry),
|
|
|
|
// resolves when there are no more modules resolving dependencies
|
|
collectionsInProgress.done,
|
|
]).then(([rootDependencies]) => {
|
|
return Promise.all(
|
|
Array.from(collectedDependencies, resolveKeyWithPromise)
|
|
).then(moduleToDependenciesPairs =>
|
|
[rootDependencies, new MapWithDefaults(() => [], moduleToDependenciesPairs)]
|
|
);
|
|
}).then(([rootDependencies, moduleDependencies]) => {
|
|
// serialize dependencies, and make sure that every single one is only
|
|
// included once
|
|
const seen = new Set([entry]);
|
|
function traverse(dependencies) {
|
|
dependencies.forEach(dependency => {
|
|
if (seen.has(dependency)) { return; }
|
|
|
|
seen.add(dependency);
|
|
response.pushDependency(dependency);
|
|
traverse(moduleDependencies.get(dependency));
|
|
});
|
|
}
|
|
|
|
traverse(rootDependencies);
|
|
response.setMocks(mocks);
|
|
});
|
|
});
|
|
}
|
|
|
|
_getAllMocks(pattern) {
|
|
// Take all mocks in all the roots into account. This is necessary
|
|
// because currently mocks are global: any module can be mocked by
|
|
// any mock in the system.
|
|
let mocks = null;
|
|
if (pattern) {
|
|
mocks = Object.create(null);
|
|
this._fastfs.matchFilesByPattern(pattern).forEach(file =>
|
|
mocks[path.basename(file, path.extname(file))] = file
|
|
);
|
|
}
|
|
return Promise.resolve(mocks);
|
|
}
|
|
|
|
_resolveHasteDependency(fromModule, toModuleName) {
|
|
toModuleName = normalizePath(toModuleName);
|
|
|
|
let p = fromModule.getPackage();
|
|
if (p) {
|
|
p = p.redirectRequire(toModuleName);
|
|
} else {
|
|
p = Promise.resolve(toModuleName);
|
|
}
|
|
|
|
return p.then((realModuleName) => {
|
|
let dep = this._hasteMap.getModule(realModuleName, this._platform);
|
|
if (dep && dep.type === 'Module') {
|
|
return dep;
|
|
}
|
|
|
|
let packageName = realModuleName;
|
|
while (packageName && packageName !== '.') {
|
|
dep = this._hasteMap.getModule(packageName, this._platform);
|
|
if (dep && dep.type === 'Package') {
|
|
break;
|
|
}
|
|
packageName = path.dirname(packageName);
|
|
}
|
|
|
|
if (dep && dep.type === 'Package') {
|
|
const potentialModulePath = path.join(
|
|
dep.root,
|
|
path.relative(packageName, realModuleName)
|
|
);
|
|
return this._tryResolve(
|
|
() => this._loadAsFile(
|
|
potentialModulePath,
|
|
fromModule,
|
|
toModuleName,
|
|
),
|
|
() => this._loadAsDir(potentialModulePath, fromModule, toModuleName),
|
|
);
|
|
}
|
|
|
|
throw new UnableToResolveError(
|
|
fromModule,
|
|
toModuleName,
|
|
'Unable to resolve dependency',
|
|
);
|
|
});
|
|
}
|
|
|
|
_redirectRequire(fromModule, modulePath) {
|
|
return Promise.resolve(fromModule.getPackage()).then(p => {
|
|
if (p) {
|
|
return p.redirectRequire(modulePath);
|
|
}
|
|
return modulePath;
|
|
});
|
|
}
|
|
|
|
_resolveFileOrDir(fromModule, toModuleName) {
|
|
const potentialModulePath = isAbsolutePath(toModuleName) ?
|
|
toModuleName :
|
|
path.join(path.dirname(fromModule.path), toModuleName);
|
|
|
|
return this._redirectRequire(fromModule, potentialModulePath).then(
|
|
realModuleName => {
|
|
if (realModuleName === false) {
|
|
return this._loadAsFile(emptyModule, fromModule, toModuleName);
|
|
}
|
|
|
|
return this._tryResolve(
|
|
() => this._loadAsFile(realModuleName, fromModule, toModuleName),
|
|
() => this._loadAsDir(realModuleName, fromModule, toModuleName)
|
|
);
|
|
}
|
|
);
|
|
}
|
|
|
|
_resolveNodeDependency(fromModule, toModuleName) {
|
|
if (isRelativeImport(toModuleName) || isAbsolutePath(toModuleName)) {
|
|
return this._resolveFileOrDir(fromModule, toModuleName);
|
|
} else {
|
|
return this._redirectRequire(fromModule, toModuleName).then(
|
|
realModuleName => {
|
|
// exclude
|
|
if (realModuleName === false) {
|
|
return this._loadAsFile(emptyModule, fromModule, toModuleName);
|
|
}
|
|
|
|
if (isRelativeImport(realModuleName) || isAbsolutePath(realModuleName)) {
|
|
// derive absolute path /.../node_modules/fromModuleDir/realModuleName
|
|
const fromModuleParentIdx = fromModule.path.lastIndexOf('node_modules/') + 13;
|
|
const fromModuleDir = fromModule.path.slice(0, fromModule.path.indexOf('/', fromModuleParentIdx));
|
|
const absPath = path.join(fromModuleDir, realModuleName);
|
|
return this._resolveFileOrDir(fromModule, absPath);
|
|
}
|
|
|
|
const searchQueue = [];
|
|
for (let currDir = path.dirname(fromModule.path);
|
|
currDir !== '.' && currDir !== realPath.parse(fromModule.path).root;
|
|
currDir = path.dirname(currDir)) {
|
|
const searchPath = path.join(currDir, 'node_modules');
|
|
if (this._fastfs.dirExists(searchPath)) {
|
|
searchQueue.push(
|
|
path.join(searchPath, realModuleName)
|
|
);
|
|
}
|
|
}
|
|
|
|
if (this._extraNodeModules) {
|
|
const bits = toModuleName.split('/');
|
|
const packageName = bits[0];
|
|
if (this._extraNodeModules[packageName]) {
|
|
bits[0] = this._extraNodeModules[packageName];
|
|
searchQueue.push(path.join.apply(path, bits));
|
|
}
|
|
}
|
|
|
|
let p = Promise.reject(new UnableToResolveError(fromModule, toModuleName));
|
|
searchQueue.forEach(potentialModulePath => {
|
|
p = this._tryResolve(
|
|
() => this._tryResolve(
|
|
() => p,
|
|
() => this._loadAsFile(potentialModulePath, fromModule, toModuleName),
|
|
),
|
|
() => this._loadAsDir(potentialModulePath, fromModule, toModuleName)
|
|
);
|
|
});
|
|
|
|
return p.catch(error => {
|
|
if (error.type !== 'UnableToResolveError') {
|
|
throw error;
|
|
}
|
|
throw new UnableToResolveError(
|
|
fromModule,
|
|
toModuleName,
|
|
`Module does not exist in the module map ${searchQueue.length ? 'or in these directories:' : ''}\n` +
|
|
searchQueue.map(searchPath => ` ${path.dirname(searchPath)}\n`) + '\n' +
|
|
`This might be related to https://github.com/facebook/react-native/issues/4968\n` +
|
|
`To resolve try the following:\n` +
|
|
` 1. Clear watchman watches: \`watchman watch-del-all\`.\n` +
|
|
` 2. Delete the \`node_modules\` folder: \`rm -rf node_modules && npm install\`.\n` +
|
|
' 3. Reset packager cache: `rm -fr $TMPDIR/react-*` or `npm start -- --reset-cache`.'
|
|
);
|
|
});
|
|
});
|
|
}
|
|
}
|
|
|
|
_loadAsFile(potentialModulePath, fromModule, toModule) {
|
|
return Promise.resolve().then(() => {
|
|
if (this._helpers.isAssetFile(potentialModulePath)) {
|
|
const dirname = path.dirname(potentialModulePath);
|
|
if (!this._fastfs.dirExists(dirname)) {
|
|
throw new UnableToResolveError(
|
|
fromModule,
|
|
toModule,
|
|
`Directory ${dirname} doesn't exist`,
|
|
);
|
|
}
|
|
|
|
const {name, type} = getAssetDataFromName(potentialModulePath, this._platforms);
|
|
|
|
let pattern = '^' + name + '(@[\\d\\.]+x)?';
|
|
if (this._platform != null) {
|
|
pattern += '(\\.' + this._platform + ')?';
|
|
}
|
|
pattern += '\\.' + type;
|
|
|
|
// We arbitrarly grab the first one, because scale selection
|
|
// will happen somewhere
|
|
const [assetFile] = this._fastfs.matches(
|
|
dirname,
|
|
new RegExp(pattern)
|
|
);
|
|
|
|
if (assetFile) {
|
|
return this._moduleCache.getAssetModule(assetFile);
|
|
}
|
|
}
|
|
|
|
let file;
|
|
if (this._fastfs.fileExists(potentialModulePath)) {
|
|
file = potentialModulePath;
|
|
} else if (this._platform != null &&
|
|
this._fastfs.fileExists(potentialModulePath + '.' + this._platform + '.js')) {
|
|
file = potentialModulePath + '.' + this._platform + '.js';
|
|
} else if (this._preferNativePlatform &&
|
|
this._fastfs.fileExists(potentialModulePath + '.native.js')) {
|
|
file = potentialModulePath + '.native.js';
|
|
} else if (this._fastfs.fileExists(potentialModulePath + '.js')) {
|
|
file = potentialModulePath + '.js';
|
|
} else if (this._fastfs.fileExists(potentialModulePath + '.json')) {
|
|
file = potentialModulePath + '.json';
|
|
} else {
|
|
throw new UnableToResolveError(
|
|
fromModule,
|
|
toModule,
|
|
`File ${potentialModulePath} doesnt exist`,
|
|
);
|
|
}
|
|
|
|
return this._moduleCache.getModule(file);
|
|
});
|
|
}
|
|
|
|
_loadAsDir(potentialDirPath, fromModule, toModule) {
|
|
return Promise.resolve().then(() => {
|
|
if (!this._fastfs.dirExists(potentialDirPath)) {
|
|
throw new UnableToResolveError(
|
|
fromModule,
|
|
toModule,
|
|
`Directory ${potentialDirPath} doesnt exist`,
|
|
);
|
|
}
|
|
|
|
const packageJsonPath = path.join(potentialDirPath, 'package.json');
|
|
if (this._fastfs.fileExists(packageJsonPath)) {
|
|
return this._moduleCache.getPackage(packageJsonPath)
|
|
.getMain().then(
|
|
(main) => this._tryResolve(
|
|
() => this._loadAsFile(main, fromModule, toModule),
|
|
() => this._loadAsDir(main, fromModule, toModule)
|
|
)
|
|
);
|
|
}
|
|
|
|
return this._loadAsFile(
|
|
path.join(potentialDirPath, 'index'),
|
|
fromModule,
|
|
toModule,
|
|
);
|
|
});
|
|
}
|
|
|
|
_resetResolutionCache() {
|
|
this._immediateResolutionCache = Object.create(null);
|
|
}
|
|
|
|
}
|
|
|
|
|
|
function resolutionHash(modulePath, depName) {
|
|
return `${path.resolve(modulePath)}:${depName}`;
|
|
}
|
|
|
|
|
|
function UnableToResolveError(fromModule, toModule, message) {
|
|
Error.call(this);
|
|
Error.captureStackTrace(this, this.constructor);
|
|
this.message = util.format(
|
|
'Unable to resolve module %s from %s: %s',
|
|
toModule,
|
|
fromModule.path,
|
|
message,
|
|
);
|
|
this.type = this.name = 'UnableToResolveError';
|
|
}
|
|
|
|
util.inherits(UnableToResolveError, Error);
|
|
|
|
function normalizePath(modulePath) {
|
|
if (path.sep === '/') {
|
|
modulePath = path.normalize(modulePath);
|
|
} else if (path.posix) {
|
|
modulePath = path.posix.normalize(modulePath);
|
|
}
|
|
|
|
return modulePath.replace(/\/$/, '');
|
|
}
|
|
|
|
function resolveKeyWithPromise([key, promise]) {
|
|
return promise.then(value => [key, value]);
|
|
}
|
|
|
|
function isRelativeImport(path) {
|
|
return /^[.][.]?(?:[/]|$)/.test(path);
|
|
}
|
|
|
|
module.exports = ResolutionRequest;
|