metro-bundler: ModuleResolution: extract _loadAsFileOrDir()

Summary: Going one step further, we can start working around the throwing version. To simply some code, I also piggybacked the addition of helper functions `resolvedAs` and `failedFor` in this changeset, hope it's okay. I can split if necessary.

Reviewed By: davidaurelio

Differential Revision: D5415196

fbshipit-source-id: 1bd5955b5733866af52fa873bcd1d9e4ce8215cf
This commit is contained in:
Jean Lauliac 2017-07-13 08:22:34 -07:00 committed by Facebook Github Bot
parent 4a9c7b9f30
commit ec01441adf
1 changed files with 60 additions and 66 deletions

View File

@ -97,6 +97,8 @@ type DirCandidates =
| {|+type: 'package', +dir: DirCandidates, +file: FileCandidates|} | {|+type: 'package', +dir: DirCandidates, +file: FileCandidates|}
| {|+type: 'index', +file: FileCandidates|}; | {|+type: 'index', +file: FileCandidates|};
type FileAndDirCandidates = {|+dir: DirCandidates, +file: FileCandidates|};
type Resolution<TModule, TCandidates> = type Resolution<TModule, TCandidates> =
| {|+type: 'resolved', +module: TModule|} | {|+type: 'resolved', +module: TModule|}
| {|+type: 'failed', +candidates: TCandidates|}; | {|+type: 'failed', +candidates: TCandidates|};
@ -274,14 +276,11 @@ class ModuleResolver<TModule: Moduleish, TPackage: Packageish> {
const fullSearchQueue = searchQueue.concat(extraSearchQueue); const fullSearchQueue = searchQueue.concat(extraSearchQueue);
for (let i = 0; i < fullSearchQueue.length; ++i) { for (let i = 0; i < fullSearchQueue.length; ++i) {
const resolvedModule = this._tryResolveNodeDep( const result = this._loadAsFileOrDir(fullSearchQueue[i], platform);
fullSearchQueue[i], // Eventually we should aggregate the candidates so that we can
fromModule, // report them with more accuracy in the error below.
toModuleName, if (result.type === 'resolved') {
platform, return result.module;
);
if (resolvedModule != null) {
return resolvedModule;
} }
} }
@ -306,31 +305,6 @@ class ModuleResolver<TModule: Moduleish, TPackage: Packageish> {
); );
} }
/**
* This is written as a separate function because "try..catch" blocks cause
* the entire surrounding function to be deoptimized.
*/
_tryResolveNodeDep(
searchPath: string,
fromModule: TModule,
toModuleName: string,
platform: string | null,
): ?TModule {
try {
return this._loadAsFileOrDirOrThrow(
searchPath,
fromModule,
toModuleName,
platform,
);
} catch (error) {
if (error.type !== 'UnableToResolveError') {
throw error;
}
return null;
}
}
/** /**
* Eventually we'd like to remove all the exception being throw in the middle * Eventually we'd like to remove all the exception being throw in the middle
* of the resolution algorithm, instead keeping track of tentatives in a * of the resolution algorithm, instead keeping track of tentatives in a
@ -344,17 +318,14 @@ class ModuleResolver<TModule: Moduleish, TPackage: Packageish> {
toModuleName: string, toModuleName: string,
platform: string | null, platform: string | null,
): TModule { ): TModule {
const dirPath = path.dirname(potentialModulePath); const result = this._loadAsFileOrDir(potentialModulePath, platform);
const fileNameHint = path.basename(potentialModulePath); if (result.type === 'resolved') {
const fileResult = this._loadAsFile(dirPath, fileNameHint, platform); return result.module;
if (fileResult.type === 'resolved') {
return fileResult.module;
} }
const dirResult = this._loadAsDir(potentialModulePath, platform); // We ignore the `file` candidates as a temporary measure before this
if (dirResult.type === 'resolved') { // function is gotten rid of, because it's historically been ignored anyway.
return dirResult.module; const {dir} = result.candidates;
} if (dir.type === 'package') {
if (dirResult.candidates.type === 'package') {
throw new UnableToResolveError( throw new UnableToResolveError(
fromModule, fromModule,
toModuleName, toModuleName,
@ -362,7 +333,7 @@ class ModuleResolver<TModule: Moduleish, TPackage: Packageish> {
'contained a package, but its "main" could not be resolved', 'contained a package, but its "main" could not be resolved',
); );
} }
invariant(dirResult.candidates.type === 'index', 'invalid candidate type'); invariant(dir.type === 'index', 'invalid candidate type');
throw new UnableToResolveError( throw new UnableToResolveError(
fromModule, fromModule,
toModuleName, toModuleName,
@ -370,6 +341,29 @@ class ModuleResolver<TModule: Moduleish, TPackage: Packageish> {
); );
} }
/**
* In the NodeJS-style module resolution scheme we want to check potential
* paths both as directories and as files. For example, `foo/bar` may resolve
* to `foo/bar.js` (preferred), but it might also be `foo/bar/index.js`, or
* even a package directory.
*/
_loadAsFileOrDir(
potentialModulePath: string,
platform: string | null,
): Resolution<TModule, FileAndDirCandidates> {
const dirPath = path.dirname(potentialModulePath);
const fileNameHint = path.basename(potentialModulePath);
const fileResult = this._loadAsFile(dirPath, fileNameHint, platform);
if (fileResult.type === 'resolved') {
return fileResult;
}
const dirResult = this._loadAsDir(potentialModulePath, platform);
if (dirResult.type === 'resolved') {
return dirResult;
}
return failedFor({file: fileResult.candidates, dir: dirResult.candidates});
}
_loadAsFile( _loadAsFile(
dirPath: string, dirPath: string,
fileNameHint: string, fileNameHint: string,
@ -388,10 +382,10 @@ class ModuleResolver<TModule: Moduleish, TPackage: Packageish> {
if (fileName != null) { if (fileName != null) {
const filePath = path.join(dirPath, fileName); const filePath = path.join(dirPath, fileName);
const module = this._options.moduleCache.getModule(filePath); const module = this._options.moduleCache.getModule(filePath);
return {type: 'resolved', module}; return resolvedAs(module);
} }
const fileNames = resolver.getTentativeFileNames(); const fileNames = resolver.getTentativeFileNames();
return {type: 'failed', candidates: {type: 'sources', fileNames}}; return failedFor({type: 'sources', fileNames});
} }
_loadAsAssetFile( _loadAsAssetFile(
@ -404,15 +398,9 @@ class ModuleResolver<TModule: Moduleish, TPackage: Packageish> {
const assetName = getArrayLowestItem(assetNames); const assetName = getArrayLowestItem(assetNames);
if (assetName != null) { if (assetName != null) {
const assetPath = path.join(dirPath, assetName); const assetPath = path.join(dirPath, assetName);
return { return resolvedAs(this._options.moduleCache.getAssetModule(assetPath));
type: 'resolved',
module: this._options.moduleCache.getAssetModule(assetPath),
};
} }
return { return failedFor({type: 'asset', name: fileNameHint});
type: 'failed',
candidates: {type: 'asset', name: fileNameHint},
};
} }
/** /**
@ -507,10 +495,7 @@ class ModuleResolver<TModule: Moduleish, TPackage: Packageish> {
if (result.type === 'resolved') { if (result.type === 'resolved') {
return result; return result;
} }
return { return failedFor({type: 'index', file: result.candidates});
type: 'failed',
candidates: {type: 'index', file: result.candidates},
};
} }
/** /**
@ -536,14 +521,11 @@ class ModuleResolver<TModule: Moduleish, TPackage: Packageish> {
if (dirResult.type === 'resolved') { if (dirResult.type === 'resolved') {
return dirResult; return dirResult;
} }
return { return failedFor({
type: 'failed', type: 'package',
candidates: { dir: dirResult.candidates,
type: 'package', file: fileResult.candidates,
dir: dirResult.candidates, });
file: fileResult.candidates,
},
};
} }
} }
@ -584,6 +566,18 @@ function getArrayLowestItem(a: $ReadOnlyArray<string>): string | void {
return lowest; return lowest;
} }
function resolvedAs<TModule, TCandidates>(
module: TModule,
): Resolution<TModule, TCandidates> {
return {type: 'resolved', module};
}
function failedFor<TModule, TCandidates>(
candidates: TCandidates,
): Resolution<TModule, TCandidates> {
return {type: 'failed', candidates};
}
class UnableToResolveError<TModule: Moduleish> extends Error { class UnableToResolveError<TModule: Moduleish> extends Error {
type: string; type: string;
from: string; from: string;