metro-bundler: Server: report ambiguous module resolution better

Reviewed By: mjesun

Differential Revision: D5444129

fbshipit-source-id: 311d20c7ee4c00ec2d4c52d83bd6c5a94221b552
This commit is contained in:
Jean Lauliac 2017-07-20 10:13:58 -07:00 committed by Facebook Github Bot
parent 3cbc2f3ec4
commit 3cc83da403
4 changed files with 85 additions and 6 deletions

View File

@ -26,6 +26,10 @@ const path = require('path');
const symbolicate = require('./symbolicate'); const symbolicate = require('./symbolicate');
const url = require('url'); const url = require('url');
const {
AmbiguousModuleResolutionError,
} = require('../node-haste/DependencyGraph/ResolutionRequest');
import type Module, {HasteImpl} from '../node-haste/Module'; import type Module, {HasteImpl} from '../node-haste/Module';
import type {IncomingMessage, ServerResponse} from 'http'; import type {IncomingMessage, ServerResponse} from 'http';
import type ResolutionResponse from '../node-haste/DependencyGraph/ResolutionResponse'; import type ResolutionResponse from '../node-haste/DependencyGraph/ResolutionResponse';
@ -890,6 +894,22 @@ class Server {
'Content-Type': 'application/json; charset=UTF-8', 'Content-Type': 'application/json; charset=UTF-8',
}); });
if (error instanceof AmbiguousModuleResolutionError) {
const he = error.hasteError;
const message =
"Ambiguous resolution: module '" +
`${error.fromModulePath}\' tries to require \'${he.hasteName}\', but ` +
`there are several files providing this module. You can delete or ` +
'fix them: \n\n' +
Object.keys(he.duplicatesSet)
.sort()
.map(dupFilePath => `${dupFilePath}`)
.join('\n\n');
res.end(JSON.stringify({message, errors: [{description: message}]}));
this._reporter.update({error, type: 'bundling_error'});
return;
}
if ( if (
error instanceof Error && error instanceof Error &&
(error.type === 'TransformError' || (error.type === 'TransformError' ||

View File

@ -18,6 +18,10 @@ const reporting = require('./reporting');
const throttle = require('lodash/throttle'); const throttle = require('lodash/throttle');
const util = require('util'); const util = require('util');
const {
AmbiguousModuleResolutionError,
} = require('../node-haste/DependencyGraph/ResolutionRequest');
import type Transformer from '../JSTransformer'; import type Transformer from '../JSTransformer';
import type {BundleOptions} from '../Server'; import type {BundleOptions} from '../Server';
import type Terminal from './Terminal'; import type Terminal from './Terminal';
@ -250,6 +254,21 @@ class TerminalReporter {
* is not actionable to end users. * is not actionable to end users.
*/ */
_logBundlingError(error: Error | Transformer.TransformError) { _logBundlingError(error: Error | Transformer.TransformError) {
if (error instanceof AmbiguousModuleResolutionError) {
const he = error.hasteError;
const message =
'ambiguous resolution: module `' +
`${error.fromModulePath}\` tries to require \`${he.hasteName}\`, but ` +
`there are several files providing this module. You can delete or ` +
'fix them: \n\n' +
Object.keys(he.duplicatesSet)
.sort()
.map(dupFilePath => ` * \`${dupFilePath}\`\n`)
.join('');
this._logBundlingErrorMessage(message);
return;
}
//$FlowFixMe T19379628 //$FlowFixMe T19379628
let message = error.message; let message = error.message;
//$FlowFixMe T19379628 //$FlowFixMe T19379628
@ -264,7 +283,11 @@ class TerminalReporter {
str += '\n' + error.snippet; str += '\n' + error.snippet;
} }
reporting.logError(this.terminal, 'bundling failed: %s', str); this._logBundlingErrorMessage(str);
}
_logBundlingErrorMessage(message: string) {
reporting.logError(this.terminal, 'bundling failed: %s', message);
} }
_logWorkerChunk(origin: 'stdout' | 'stderr', chunk: string) { _logWorkerChunk(origin: 'stdout' | 'stderr', chunk: string) {

View File

@ -20,6 +20,10 @@ const debug = require('debug')('Metro:DependencyGraph');
const isAbsolutePath = require('absolute-path'); const isAbsolutePath = require('absolute-path');
const path = require('path'); const path = require('path');
const {
DuplicateHasteCandidatesError,
} = require('jest-haste-map/build/module_map');
import type DependencyGraphHelpers from './DependencyGraphHelpers'; import type DependencyGraphHelpers from './DependencyGraphHelpers';
import type ResolutionResponse from './ResolutionResponse'; import type ResolutionResponse from './ResolutionResponse';
import type {Options as TransformWorkerOptions} from '../../JSTransformer/worker'; import type {Options as TransformWorkerOptions} from '../../JSTransformer/worker';
@ -68,6 +72,7 @@ type Options<TModule, TPackage> = {|
class ResolutionRequest<TModule: Moduleish, TPackage: Packageish> { class ResolutionRequest<TModule: Moduleish, TPackage: Packageish> {
_immediateResolutionCache: {[key: string]: TModule}; _immediateResolutionCache: {[key: string]: TModule};
_options: Options<TModule, TPackage>; _options: Options<TModule, TPackage>;
static AmbiguousModuleResolutionError: Class<AmbiguousModuleResolutionError>;
constructor(options: Options<TModule, TPackage>) { constructor(options: Options<TModule, TPackage>) {
this._options = options; this._options = options;
@ -95,8 +100,7 @@ class ResolutionRequest<TModule: Moduleish, TPackage: Packageish> {
!(isRelativeImport(toModuleName) || isAbsolutePath(toModuleName)) !(isRelativeImport(toModuleName) || isAbsolutePath(toModuleName))
) { ) {
const result = ModuleResolution.tryResolveSync( const result = ModuleResolution.tryResolveSync(
() => () => this._resolveHasteDependency(fromModule, toModuleName, platform),
resolver.resolveHasteDependency(fromModule, toModuleName, platform),
() => () =>
resolver.resolveNodeDependency(fromModule, toModuleName, platform), resolver.resolveNodeDependency(fromModule, toModuleName, platform),
); );
@ -108,6 +112,22 @@ class ResolutionRequest<TModule: Moduleish, TPackage: Packageish> {
); );
} }
_resolveHasteDependency(
fromModule: TModule,
toModuleName: string,
platform: string | null,
): TModule {
const rs = this._options.moduleResolver;
try {
return rs.resolveHasteDependency(fromModule, toModuleName, platform);
} catch (error) {
if (error instanceof DuplicateHasteCandidatesError) {
throw new AmbiguousModuleResolutionError(fromModule.path, error);
}
throw error;
}
}
resolveModuleDependencies( resolveModuleDependencies(
module: TModule, module: TModule,
dependencyNames: $ReadOnlyArray<string>, dependencyNames: $ReadOnlyArray<string>,
@ -350,4 +370,20 @@ function resolutionHash(modulePath, depName) {
return `${path.resolve(modulePath)}:${depName}`; return `${path.resolve(modulePath)}:${depName}`;
} }
class AmbiguousModuleResolutionError extends Error {
fromModulePath: string;
hasteError: DuplicateHasteCandidatesError;
constructor(
fromModulePath: string,
hasteError: DuplicateHasteCandidatesError,
) {
super();
this.fromModulePath = fromModulePath;
this.hasteError = hasteError;
}
}
ResolutionRequest.AmbiguousModuleResolutionError = AmbiguousModuleResolutionError;
module.exports = ResolutionRequest; module.exports = ResolutionRequest;

View File

@ -5295,9 +5295,9 @@ describe('DependencyGraph', function() {
throw new Error('expected `getOrderedDependenciesAsJSON` to fail'); throw new Error('expected `getOrderedDependenciesAsJSON` to fail');
} catch (error) { } catch (error) {
const { const {
DuplicateHasteCandidatesError, AmbiguousModuleResolutionError,
} = require('jest-haste-map/build/module_map'); } = require('../DependencyGraph/ResolutionRequest');
if (!(error instanceof DuplicateHasteCandidatesError)) { if (!(error instanceof AmbiguousModuleResolutionError)) {
throw error; throw error;
} }
expect(console.warn).toBeCalled(); expect(console.warn).toBeCalled();