diff --git a/packages/metro-resolver/src/FailedToResolveNameError.js b/packages/metro-resolver/src/FailedToResolveNameError.js new file mode 100644 index 00000000..34e28b1a --- /dev/null +++ b/packages/metro-resolver/src/FailedToResolveNameError.js @@ -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; + extraPaths: $ReadOnlyArray; + + constructor( + dirPaths: $ReadOnlyArray, + extraPaths: $ReadOnlyArray, + ) { + 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; diff --git a/packages/metro-resolver/src/FailedToResolvePathError.js b/packages/metro-resolver/src/FailedToResolvePathError.js new file mode 100644 index 00000000..98dc3baa --- /dev/null +++ b/packages/metro-resolver/src/FailedToResolvePathError.js @@ -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; diff --git a/packages/metro-resolver/src/__tests__/index-test.js b/packages/metro-resolver/src/__tests__/index-test.js index 175dd8d8..f7e9c1ff 100644 --- a/packages/metro-resolver/src/__tests__/index-test.js +++ b/packages/metro-resolver/src/__tests__/index-test.js @@ -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', () => { diff --git a/packages/metro-resolver/src/index.js b/packages/metro-resolver/src/index.js index b03be2fc..af348463 100644 --- a/packages/metro-resolver/src/index.js +++ b/packages/metro-resolver/src/index.js @@ -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; diff --git a/packages/metro-resolver/src/resolve.js b/packages/metro-resolver/src/resolve.js index 108b6cae..b7fe03a5 100644 --- a/packages/metro-resolver/src/resolve.js +++ b/packages/metro-resolver/src/resolve.js @@ -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 { 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 { 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 & { diff --git a/packages/metro-resolver/src/types.js b/packages/metro-resolver/src/types.js index c5f10a92..a1a8ce50 100644 --- a/packages/metro-resolver/src/types.js +++ b/packages/metro-resolver/src/types.js @@ -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, - +extraPaths: $ReadOnlyArray, - |}; export type AssetFileResolution = $ReadOnlyArray; export type FileResolution = diff --git a/packages/metro/src/node-haste/DependencyGraph/ModuleResolution.js b/packages/metro/src/node-haste/DependencyGraph/ModuleResolution.js index fb3de2d5..017da557 100644 --- a/packages/metro/src/node-haste/DependencyGraph/ModuleResolution.js +++ b/packages/metro/src/node-haste/DependencyGraph/ModuleResolution.js @@ -96,60 +96,63 @@ class ModuleResolver { 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 => {