metro-resolver: simplify API with exceptions

Reviewed By: mjesun

Differential Revision: D7098249

fbshipit-source-id: 5eea3dfdc54a98d295b756296398fd3a1dd3890b
This commit is contained in:
Jean Lauliac 2018-02-28 02:44:38 -08:00 committed by Facebook Github Bot
parent a66a231dfc
commit 50356b7408
7 changed files with 163 additions and 97 deletions

View File

@ -0,0 +1,38 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
* @format
*/
'use strict';
const path = require('path');
class FailedToResolveNameError extends Error {
dirPaths: $ReadOnlyArray<string>;
extraPaths: $ReadOnlyArray<string>;
constructor(
dirPaths: $ReadOnlyArray<string>,
extraPaths: $ReadOnlyArray<string>,
) {
const displayDirPaths = dirPaths.concat(extraPaths);
const hint = displayDirPaths.length ? ' or in these directories:' : '';
super(
`Module does not exist in the Haste module map${hint}\n` +
displayDirPaths
.map(dirPath => ` ${path.dirname(dirPath)}\n`)
.join(', ') +
'\n',
);
this.dirPaths = dirPaths;
this.extraPaths = extraPaths;
}
}
module.exports = FailedToResolveNameError;

View File

@ -0,0 +1,30 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
* @format
*/
'use strict';
const formatFileCandidates = require('./formatFileCandidates');
import type {FileAndDirCandidates} from './types';
class FailedToResolvePathError extends Error {
candidates: FileAndDirCandidates;
constructor(candidates: FileAndDirCandidates) {
super(
`The module could not be resolved because none of these files exist:\n\n` +
` * \`${formatFileCandidates(candidates.file)}\`\n` +
` * \`${formatFileCandidates(candidates.dir)}\``,
);
this.candidates = candidates;
}
}
module.exports = FailedToResolvePathError;

View File

@ -11,6 +11,7 @@
'use strict';
const FailedToResolvePathError = require('../FailedToResolvePathError');
const Resolver = require('../index');
const path = require('path');
@ -69,47 +70,46 @@ const CONTEXT: ResolutionContext = (() => {
it('resolves relative path', () => {
expect(Resolver.resolve(CONTEXT, './bar', null)).toEqual({
type: 'resolved',
resolution: {type: 'sourceFile', filePath: '/root/project/bar.js'},
type: 'sourceFile',
filePath: '/root/project/bar.js',
});
});
it('resolves relative path in another folder', () => {
expect(Resolver.resolve(CONTEXT, '../smth/beep', null)).toEqual({
type: 'resolved',
resolution: {type: 'sourceFile', filePath: '/root/smth/beep.js'},
type: 'sourceFile',
filePath: '/root/smth/beep.js',
});
});
it('resolves a simple node_modules', () => {
expect(Resolver.resolve(CONTEXT, 'tadam', null)).toEqual({
type: 'resolved',
resolution: {
type: 'sourceFile',
filePath: '/root/node_modules/tadam/main.js',
},
type: 'sourceFile',
filePath: '/root/node_modules/tadam/main.js',
});
});
it('fails to resolve relative path', () => {
expect(Resolver.resolve(CONTEXT, './tadam', null)).toEqual({
type: 'failed',
candidates: {
type: 'modulePath',
which: {
dir: {
candidateExts: ['', '.js'],
filePathPrefix: '/root/project/tadam/index',
type: 'sourceFile',
},
file: {
candidateExts: ['', '.js'],
filePathPrefix: '/root/project/tadam',
type: 'sourceFile',
},
try {
Resolver.resolve(CONTEXT, './tadam', null);
throw new Error('should not reach');
} catch (error) {
if (!(error instanceof FailedToResolvePathError)) {
throw error;
}
expect(error.candidates).toEqual({
dir: {
candidateExts: ['', '.js'],
filePathPrefix: '/root/project/tadam/index',
type: 'sourceFile',
},
},
});
file: {
candidateExts: ['', '.js'],
filePathPrefix: '/root/project/tadam',
type: 'sourceFile',
},
});
}
});
it('throws on invalid node package', () => {

View File

@ -18,7 +18,6 @@ export type {
} from './resolve';
export type {
AssetFileResolution,
Candidates,
FileAndDirCandidates,
FileCandidates,
FileResolution,
@ -27,9 +26,11 @@ export type {
} from './types';
const Resolver = {
resolve: require('./resolve'),
InvalidPackageError: require('./InvalidPackageError'),
FailedToResolveNameError: require('./FailedToResolveNameError'),
FailedToResolvePathError: require('./FailedToResolvePathError'),
formatFileCandidates: require('./formatFileCandidates'),
InvalidPackageError: require('./InvalidPackageError'),
resolve: require('./resolve'),
};
module.exports = Resolver;

View File

@ -10,6 +10,8 @@
'use strict';
const FailedToResolveNameError = require('./FailedToResolveNameError');
const FailedToResolvePathError = require('./FailedToResolvePathError');
const InvalidPackageError = require('./InvalidPackageError');
const formatFileCandidates = require('./formatFileCandidates');
@ -18,7 +20,6 @@ const path = require('path');
import type {
AssetFileResolution,
Candidates,
FileAndDirCandidates,
FileCandidates,
FileResolution,
@ -37,14 +38,14 @@ function resolve(
context: ResolutionContext,
moduleName: string,
platform: string | null,
): Result<Resolution, Candidates> {
): Resolution {
if (isRelativeImport(moduleName) || isAbsolutePath(moduleName)) {
return resolveModulePath(context, moduleName, platform);
}
const realModuleName = context.redirectModulePath(moduleName);
// exclude
if (realModuleName === false) {
return resolvedAs({type: 'empty'});
return {type: 'empty'};
}
const {originModulePath} = context;
@ -66,7 +67,7 @@ function resolve(
const normalizedName = normalizePath(realModuleName);
const result = resolveHasteName(context, normalizedName, platform);
if (result.type === 'resolved') {
return result;
return result.resolution;
}
}
@ -95,10 +96,10 @@ function resolve(
for (let i = 0; i < allDirPaths.length; ++i) {
const result = resolveFileOrDir(context, allDirPaths[i], platform);
if (result.type === 'resolved') {
return result;
return result.resolution;
}
}
return failedFor({type: 'moduleName', dirPaths, extraPaths});
throw new FailedToResolveNameError(dirPaths, extraPaths);
}
type ModulePathContext = FileOrDirContext & {
@ -125,19 +126,19 @@ function resolveModulePath(
context: ModulePathContext,
toModuleName: string,
platform: string | null,
): Result<Resolution, Candidates> {
): Resolution {
const modulePath = isAbsolutePath(toModuleName)
? resolveWindowsPath(toModuleName)
: path.join(path.dirname(context.originModulePath), toModuleName);
const redirectedPath = context.redirectModulePath(modulePath);
if (redirectedPath === false) {
return resolvedAs({type: 'empty'});
return {type: 'empty'};
}
const result = resolveFileOrDir(context, redirectedPath, platform);
if (result.type === 'resolved') {
return result;
return result.resolution;
}
return failedFor({type: 'modulePath', which: result.candidates});
throw new FailedToResolvePathError(result.candidates);
}
type HasteContext = FileOrDirContext & {

View File

@ -15,13 +15,6 @@ export type Result<+TResolution, +TCandidates> =
| {|+type: 'failed', +candidates: TCandidates|};
export type Resolution = FileResolution | {|+type: 'empty'|};
export type Candidates =
| {|+type: 'modulePath', +which: FileAndDirCandidates|}
| {|
+type: 'moduleName',
+dirPaths: $ReadOnlyArray<string>,
+extraPaths: $ReadOnlyArray<string>,
|};
export type AssetFileResolution = $ReadOnlyArray<string>;
export type FileResolution =

View File

@ -96,60 +96,63 @@ class ModuleResolver<TModule: Moduleish, TPackage: Packageish> {
allowHaste: boolean,
platform: string | null,
): TModule {
const result = Resolver.resolve(
{
...this._options,
originModulePath: fromModule.path,
redirectModulePath: modulePath =>
this._redirectRequire(fromModule, modulePath),
allowHaste,
platform,
resolveHasteModule: name =>
this._options.moduleMap.getModule(name, platform, true),
resolveHastePackage: name =>
this._options.moduleMap.getPackage(name, platform, true),
getPackageMainPath: this._getPackageMainPath,
},
moduleName,
platform,
);
if (result.type === 'resolved') {
return this._getFileResolvedModule(result.resolution);
}
if (result.candidates.type === 'modulePath') {
const {which} = result.candidates;
throw new UnableToResolveError(
fromModule.path,
try {
const result = Resolver.resolve(
{
...this._options,
originModulePath: fromModule.path,
redirectModulePath: modulePath =>
this._redirectRequire(fromModule, modulePath),
allowHaste,
platform,
resolveHasteModule: name =>
this._options.moduleMap.getModule(name, platform, true),
resolveHastePackage: name =>
this._options.moduleMap.getPackage(name, platform, true),
getPackageMainPath: this._getPackageMainPath,
},
moduleName,
`The module \`${moduleName}\` could not be found ` +
`from \`${fromModule.path}\`. ` +
`Indeed, none of these files exist:\n\n` +
` * \`${Resolver.formatFileCandidates(which.file)}\`\n` +
` * \`${Resolver.formatFileCandidates(which.dir)}\``,
platform,
);
return this._getFileResolvedModule(result);
} catch (error) {
if (error instanceof Resolver.FailedToResolvePathError) {
const {candidates} = error;
throw new UnableToResolveError(
fromModule.path,
moduleName,
`The module \`${moduleName}\` could not be found ` +
`from \`${fromModule.path}\`. ` +
`Indeed, none of these files exist:\n\n` +
` * \`${Resolver.formatFileCandidates(candidates.file)}\`\n` +
` * \`${Resolver.formatFileCandidates(candidates.dir)}\``,
);
}
if (error instanceof Resolver.FailedToResolveNameError) {
const {dirPaths, extraPaths} = error;
const displayDirPaths = dirPaths
.filter(dirPath => this._options.dirExists(dirPath))
.concat(extraPaths);
const hint = displayDirPaths.length ? ' or in these directories:' : '';
throw new UnableToResolveError(
fromModule.path,
moduleName,
`Module \`${moduleName}\` does not exist in the Haste module map${hint}\n` +
displayDirPaths
.map(dirPath => ` ${path.dirname(dirPath)}\n`)
.join(', ') +
'\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 Metro Bundler cache: `rm -rf /tmp/metro-bundler-cache-*` or `npm start -- --reset-cache`.' +
' 4. Remove haste cache: `rm -rf /tmp/haste-map-react-native-packager-*`.',
);
}
throw error;
}
const {dirPaths, extraPaths} = result.candidates;
const displayDirPaths = dirPaths
.filter(dirPath => this._options.dirExists(dirPath))
.concat(extraPaths);
const hint = displayDirPaths.length ? ' or in these directories:' : '';
throw new UnableToResolveError(
fromModule.path,
moduleName,
`Module does not exist in the module map${hint}\n` +
displayDirPaths
.map(dirPath => ` ${path.dirname(dirPath)}\n`)
.join(', ') +
'\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 Metro Bundler cache: `rm -rf /tmp/metro-bundler-cache-*` or `npm start -- --reset-cache`.' +
' 4. Remove haste cache: `rm -rf /tmp/haste-map-react-native-packager-*`.',
);
}
_getPackageMainPath = (packageJsonPath: string): string => {