mirror of https://github.com/status-im/metro.git
metro-resolver: simplify API with exceptions
Reviewed By: mjesun Differential Revision: D7098249 fbshipit-source-id: 5eea3dfdc54a98d295b756296398fd3a1dd3890b
This commit is contained in:
parent
a66a231dfc
commit
50356b7408
|
@ -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;
|
|
@ -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;
|
|
@ -11,6 +11,7 @@
|
||||||
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
const FailedToResolvePathError = require('../FailedToResolvePathError');
|
||||||
const Resolver = require('../index');
|
const Resolver = require('../index');
|
||||||
|
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
|
@ -69,47 +70,46 @@ const CONTEXT: ResolutionContext = (() => {
|
||||||
|
|
||||||
it('resolves relative path', () => {
|
it('resolves relative path', () => {
|
||||||
expect(Resolver.resolve(CONTEXT, './bar', null)).toEqual({
|
expect(Resolver.resolve(CONTEXT, './bar', null)).toEqual({
|
||||||
type: 'resolved',
|
type: 'sourceFile',
|
||||||
resolution: {type: 'sourceFile', filePath: '/root/project/bar.js'},
|
filePath: '/root/project/bar.js',
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('resolves relative path in another folder', () => {
|
it('resolves relative path in another folder', () => {
|
||||||
expect(Resolver.resolve(CONTEXT, '../smth/beep', null)).toEqual({
|
expect(Resolver.resolve(CONTEXT, '../smth/beep', null)).toEqual({
|
||||||
type: 'resolved',
|
type: 'sourceFile',
|
||||||
resolution: {type: 'sourceFile', filePath: '/root/smth/beep.js'},
|
filePath: '/root/smth/beep.js',
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('resolves a simple node_modules', () => {
|
it('resolves a simple node_modules', () => {
|
||||||
expect(Resolver.resolve(CONTEXT, 'tadam', null)).toEqual({
|
expect(Resolver.resolve(CONTEXT, 'tadam', null)).toEqual({
|
||||||
type: 'resolved',
|
type: 'sourceFile',
|
||||||
resolution: {
|
filePath: '/root/node_modules/tadam/main.js',
|
||||||
type: 'sourceFile',
|
|
||||||
filePath: '/root/node_modules/tadam/main.js',
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('fails to resolve relative path', () => {
|
it('fails to resolve relative path', () => {
|
||||||
expect(Resolver.resolve(CONTEXT, './tadam', null)).toEqual({
|
try {
|
||||||
type: 'failed',
|
Resolver.resolve(CONTEXT, './tadam', null);
|
||||||
candidates: {
|
throw new Error('should not reach');
|
||||||
type: 'modulePath',
|
} catch (error) {
|
||||||
which: {
|
if (!(error instanceof FailedToResolvePathError)) {
|
||||||
dir: {
|
throw error;
|
||||||
candidateExts: ['', '.js'],
|
}
|
||||||
filePathPrefix: '/root/project/tadam/index',
|
expect(error.candidates).toEqual({
|
||||||
type: 'sourceFile',
|
dir: {
|
||||||
},
|
candidateExts: ['', '.js'],
|
||||||
file: {
|
filePathPrefix: '/root/project/tadam/index',
|
||||||
candidateExts: ['', '.js'],
|
type: 'sourceFile',
|
||||||
filePathPrefix: '/root/project/tadam',
|
|
||||||
type: 'sourceFile',
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
file: {
|
||||||
});
|
candidateExts: ['', '.js'],
|
||||||
|
filePathPrefix: '/root/project/tadam',
|
||||||
|
type: 'sourceFile',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
it('throws on invalid node package', () => {
|
it('throws on invalid node package', () => {
|
||||||
|
|
|
@ -18,7 +18,6 @@ export type {
|
||||||
} from './resolve';
|
} from './resolve';
|
||||||
export type {
|
export type {
|
||||||
AssetFileResolution,
|
AssetFileResolution,
|
||||||
Candidates,
|
|
||||||
FileAndDirCandidates,
|
FileAndDirCandidates,
|
||||||
FileCandidates,
|
FileCandidates,
|
||||||
FileResolution,
|
FileResolution,
|
||||||
|
@ -27,9 +26,11 @@ export type {
|
||||||
} from './types';
|
} from './types';
|
||||||
|
|
||||||
const Resolver = {
|
const Resolver = {
|
||||||
resolve: require('./resolve'),
|
FailedToResolveNameError: require('./FailedToResolveNameError'),
|
||||||
InvalidPackageError: require('./InvalidPackageError'),
|
FailedToResolvePathError: require('./FailedToResolvePathError'),
|
||||||
formatFileCandidates: require('./formatFileCandidates'),
|
formatFileCandidates: require('./formatFileCandidates'),
|
||||||
|
InvalidPackageError: require('./InvalidPackageError'),
|
||||||
|
resolve: require('./resolve'),
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = Resolver;
|
module.exports = Resolver;
|
||||||
|
|
|
@ -10,6 +10,8 @@
|
||||||
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
const FailedToResolveNameError = require('./FailedToResolveNameError');
|
||||||
|
const FailedToResolvePathError = require('./FailedToResolvePathError');
|
||||||
const InvalidPackageError = require('./InvalidPackageError');
|
const InvalidPackageError = require('./InvalidPackageError');
|
||||||
|
|
||||||
const formatFileCandidates = require('./formatFileCandidates');
|
const formatFileCandidates = require('./formatFileCandidates');
|
||||||
|
@ -18,7 +20,6 @@ const path = require('path');
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
AssetFileResolution,
|
AssetFileResolution,
|
||||||
Candidates,
|
|
||||||
FileAndDirCandidates,
|
FileAndDirCandidates,
|
||||||
FileCandidates,
|
FileCandidates,
|
||||||
FileResolution,
|
FileResolution,
|
||||||
|
@ -37,14 +38,14 @@ function resolve(
|
||||||
context: ResolutionContext,
|
context: ResolutionContext,
|
||||||
moduleName: string,
|
moduleName: string,
|
||||||
platform: string | null,
|
platform: string | null,
|
||||||
): Result<Resolution, Candidates> {
|
): Resolution {
|
||||||
if (isRelativeImport(moduleName) || isAbsolutePath(moduleName)) {
|
if (isRelativeImport(moduleName) || isAbsolutePath(moduleName)) {
|
||||||
return resolveModulePath(context, moduleName, platform);
|
return resolveModulePath(context, moduleName, platform);
|
||||||
}
|
}
|
||||||
const realModuleName = context.redirectModulePath(moduleName);
|
const realModuleName = context.redirectModulePath(moduleName);
|
||||||
// exclude
|
// exclude
|
||||||
if (realModuleName === false) {
|
if (realModuleName === false) {
|
||||||
return resolvedAs({type: 'empty'});
|
return {type: 'empty'};
|
||||||
}
|
}
|
||||||
|
|
||||||
const {originModulePath} = context;
|
const {originModulePath} = context;
|
||||||
|
@ -66,7 +67,7 @@ function resolve(
|
||||||
const normalizedName = normalizePath(realModuleName);
|
const normalizedName = normalizePath(realModuleName);
|
||||||
const result = resolveHasteName(context, normalizedName, platform);
|
const result = resolveHasteName(context, normalizedName, platform);
|
||||||
if (result.type === 'resolved') {
|
if (result.type === 'resolved') {
|
||||||
return result;
|
return result.resolution;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -95,10 +96,10 @@ function resolve(
|
||||||
for (let i = 0; i < allDirPaths.length; ++i) {
|
for (let i = 0; i < allDirPaths.length; ++i) {
|
||||||
const result = resolveFileOrDir(context, allDirPaths[i], platform);
|
const result = resolveFileOrDir(context, allDirPaths[i], platform);
|
||||||
if (result.type === 'resolved') {
|
if (result.type === 'resolved') {
|
||||||
return result;
|
return result.resolution;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return failedFor({type: 'moduleName', dirPaths, extraPaths});
|
throw new FailedToResolveNameError(dirPaths, extraPaths);
|
||||||
}
|
}
|
||||||
|
|
||||||
type ModulePathContext = FileOrDirContext & {
|
type ModulePathContext = FileOrDirContext & {
|
||||||
|
@ -125,19 +126,19 @@ function resolveModulePath(
|
||||||
context: ModulePathContext,
|
context: ModulePathContext,
|
||||||
toModuleName: string,
|
toModuleName: string,
|
||||||
platform: string | null,
|
platform: string | null,
|
||||||
): Result<Resolution, Candidates> {
|
): Resolution {
|
||||||
const modulePath = isAbsolutePath(toModuleName)
|
const modulePath = isAbsolutePath(toModuleName)
|
||||||
? resolveWindowsPath(toModuleName)
|
? resolveWindowsPath(toModuleName)
|
||||||
: path.join(path.dirname(context.originModulePath), toModuleName);
|
: path.join(path.dirname(context.originModulePath), toModuleName);
|
||||||
const redirectedPath = context.redirectModulePath(modulePath);
|
const redirectedPath = context.redirectModulePath(modulePath);
|
||||||
if (redirectedPath === false) {
|
if (redirectedPath === false) {
|
||||||
return resolvedAs({type: 'empty'});
|
return {type: 'empty'};
|
||||||
}
|
}
|
||||||
const result = resolveFileOrDir(context, redirectedPath, platform);
|
const result = resolveFileOrDir(context, redirectedPath, platform);
|
||||||
if (result.type === 'resolved') {
|
if (result.type === 'resolved') {
|
||||||
return result;
|
return result.resolution;
|
||||||
}
|
}
|
||||||
return failedFor({type: 'modulePath', which: result.candidates});
|
throw new FailedToResolvePathError(result.candidates);
|
||||||
}
|
}
|
||||||
|
|
||||||
type HasteContext = FileOrDirContext & {
|
type HasteContext = FileOrDirContext & {
|
||||||
|
|
|
@ -15,13 +15,6 @@ export type Result<+TResolution, +TCandidates> =
|
||||||
| {|+type: 'failed', +candidates: TCandidates|};
|
| {|+type: 'failed', +candidates: TCandidates|};
|
||||||
|
|
||||||
export type Resolution = FileResolution | {|+type: 'empty'|};
|
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 AssetFileResolution = $ReadOnlyArray<string>;
|
||||||
export type FileResolution =
|
export type FileResolution =
|
||||||
|
|
|
@ -96,60 +96,63 @@ class ModuleResolver<TModule: Moduleish, TPackage: Packageish> {
|
||||||
allowHaste: boolean,
|
allowHaste: boolean,
|
||||||
platform: string | null,
|
platform: string | null,
|
||||||
): TModule {
|
): TModule {
|
||||||
const result = Resolver.resolve(
|
try {
|
||||||
{
|
const result = Resolver.resolve(
|
||||||
...this._options,
|
{
|
||||||
originModulePath: fromModule.path,
|
...this._options,
|
||||||
redirectModulePath: modulePath =>
|
originModulePath: fromModule.path,
|
||||||
this._redirectRequire(fromModule, modulePath),
|
redirectModulePath: modulePath =>
|
||||||
allowHaste,
|
this._redirectRequire(fromModule, modulePath),
|
||||||
platform,
|
allowHaste,
|
||||||
resolveHasteModule: name =>
|
platform,
|
||||||
this._options.moduleMap.getModule(name, platform, true),
|
resolveHasteModule: name =>
|
||||||
resolveHastePackage: name =>
|
this._options.moduleMap.getModule(name, platform, true),
|
||||||
this._options.moduleMap.getPackage(name, platform, true),
|
resolveHastePackage: name =>
|
||||||
getPackageMainPath: this._getPackageMainPath,
|
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,
|
|
||||||
moduleName,
|
moduleName,
|
||||||
`The module \`${moduleName}\` could not be found ` +
|
platform,
|
||||||
`from \`${fromModule.path}\`. ` +
|
|
||||||
`Indeed, none of these files exist:\n\n` +
|
|
||||||
` * \`${Resolver.formatFileCandidates(which.file)}\`\n` +
|
|
||||||
` * \`${Resolver.formatFileCandidates(which.dir)}\``,
|
|
||||||
);
|
);
|
||||||
|
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 => {
|
_getPackageMainPath = (packageJsonPath: string): string => {
|
||||||
|
|
Loading…
Reference in New Issue