metro: ModuleResolution: get rid of TModule for inner file resolution functions

Summary: This is one of the first step towards a generic and composable resolution algorithm. We shall get rid of `TModule` as it's an abstraction that doesn't bring us any benefit in the resolution algo, and we actually want to get rid of `Module.js` eventually. This changeset gets rid of `TModule` up to the place where I already got rid of the exception-based control flow mechanism. Next steps are to push the boundaries higher up both for error handling, and for removing `TModule`.

Reviewed By: mjesun

Differential Revision: D6603157

fbshipit-source-id: 551e9ccd93628f45452148ed40a817bdde3740ea
This commit is contained in:
Jean Lauliac 2017-12-20 01:48:17 -08:00 committed by Facebook Github Bot
parent 730104b8ee
commit 5eeb88f5a9
2 changed files with 60 additions and 25 deletions

View File

@ -76,11 +76,11 @@ class AssetResolutionCache {
dirPath: string, dirPath: string,
assetName: string, assetName: string,
platform: string | null, platform: string | null,
): $ReadOnlyArray<string> { ): ?$ReadOnlyArray<string> {
const results = this._assetsByDirPath.get(dirPath); const results = this._assetsByDirPath.get(dirPath);
const assets = results.get(assetName); const assets = results.get(assetName);
if (assets == null) { if (assets == null) {
return EMPTY_ARRAY; return null;
} }
return assets return assets
.filter(asset => asset.platform == null || asset.platform === platform) .filter(asset => asset.platform == null || asset.platform === platform)

View File

@ -70,7 +70,7 @@ type Options<TModule, TPackage> = {|
dirPath: string, dirPath: string,
assetName: string, assetName: string,
platform: string | null, platform: string | null,
) => $ReadOnlyArray<string>, ) => ?$ReadOnlyArray<string>,
+sourceExts: Array<string>, +sourceExts: Array<string>,
|}; |};
@ -98,10 +98,15 @@ type DirCandidates =
type FileAndDirCandidates = {|+dir: DirCandidates, +file: FileCandidates|}; type FileAndDirCandidates = {|+dir: DirCandidates, +file: FileCandidates|};
type Resolution<TModule, TCandidates> = type Result<TResolution, TCandidates> =
| {|+type: 'resolved', +module: TModule|} | {|+type: 'resolved', +resolution: TResolution|}
| {|+type: 'failed', +candidates: TCandidates|}; | {|+type: 'failed', +candidates: TCandidates|};
type AssetFileResolution = $ReadOnlyArray<string>;
type FileResolution =
| {|+type: 'sourceFile', +filePath: string|}
| {|+type: 'assetFiles', +filePaths: AssetFileResolution|};
/** /**
* It may not be a great pattern to leverage exception just for "trying" things * It may not be a great pattern to leverage exception just for "trying" things
* out, notably for performance. We should consider replacing these functions * out, notably for performance. We should consider replacing these functions
@ -279,7 +284,7 @@ class ModuleResolver<TModule: Moduleish, TPackage: Packageish> {
// Eventually we should aggregate the candidates so that we can // Eventually we should aggregate the candidates so that we can
// report them with more accuracy in the error below. // report them with more accuracy in the error below.
if (result.type === 'resolved') { if (result.type === 'resolved') {
return result.module; return this._getFileResolvedModule(result.resolution);
} }
} }
@ -320,7 +325,7 @@ class ModuleResolver<TModule: Moduleish, TPackage: Packageish> {
): TModule { ): TModule {
const result = this._loadAsFileOrDir(potentialModulePath, platform); const result = this._loadAsFileOrDir(potentialModulePath, platform);
if (result.type === 'resolved') { if (result.type === 'resolved') {
return result.module; return this._getFileResolvedModule(result.resolution);
} }
// We ignore the `file` candidates as a temporary measure before this // We ignore the `file` candidates as a temporary measure before this
// function is gotten rid of, because it's historically been ignored anyway. // function is gotten rid of, because it's historically been ignored anyway.
@ -341,6 +346,24 @@ class ModuleResolver<TModule: Moduleish, TPackage: Packageish> {
); );
} }
/**
* FIXME: get rid of this function and of the reliance on `TModule`
* altogether, return strongly typed resolutions at the top-level instead.
*/
_getFileResolvedModule(resolution: FileResolution): TModule {
switch (resolution.type) {
case 'sourceFile':
return this._options.moduleCache.getModule(resolution.filePath);
case 'assetFiles':
// FIXME: we should forward ALL the paths/metadata,
// not just an arbitrary item!
const arbitrary = getArrayLowestItem(resolution.filePaths);
invariant(arbitrary != null, 'invalid asset resolution');
return this._options.moduleCache.getAssetModule(arbitrary);
}
throw new Error('switch is not exhaustive');
}
/** /**
* In the NodeJS-style module resolution scheme we want to check potential * 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 * paths both as directories and as files. For example, `foo/bar` may resolve
@ -350,7 +373,7 @@ class ModuleResolver<TModule: Moduleish, TPackage: Packageish> {
_loadAsFileOrDir( _loadAsFileOrDir(
potentialModulePath: string, potentialModulePath: string,
platform: string | null, platform: string | null,
): Resolution<TModule, FileAndDirCandidates> { ): Result<FileResolution, FileAndDirCandidates> {
const dirPath = path.dirname(potentialModulePath); const dirPath = path.dirname(potentialModulePath);
const fileNameHint = path.basename(potentialModulePath); const fileNameHint = path.basename(potentialModulePath);
const fileResult = this._loadAsFile(dirPath, fileNameHint, platform); const fileResult = this._loadAsFile(dirPath, fileNameHint, platform);
@ -372,17 +395,17 @@ class ModuleResolver<TModule: Moduleish, TPackage: Packageish> {
dirPath: string, dirPath: string,
fileNameHint: string, fileNameHint: string,
platform: string | null, platform: string | null,
): Resolution<TModule, FileCandidates> { ): Result<FileResolution, FileCandidates> {
if (this.isAssetFile(fileNameHint)) { if (this.isAssetFile(fileNameHint)) {
return this._loadAsAssetFile(dirPath, fileNameHint, platform); const result = this._loadAsAssetFile(dirPath, fileNameHint, platform);
return mapResult(result, filePaths => ({type: 'assetFiles', filePaths}));
} }
const candidateExts = []; const candidateExts = [];
const filePathPrefix = path.join(dirPath, fileNameHint); const filePathPrefix = path.join(dirPath, fileNameHint);
const context = {filePathPrefix, candidateExts}; const context = {filePathPrefix, candidateExts};
const filePath = this._tryToResolveSourceFile(context, platform); const filePath = this._tryToResolveSourceFile(context, platform);
if (filePath != null) { if (filePath != null) {
const module = this._options.moduleCache.getModule(filePath); return resolvedAs({type: 'sourceFile', filePath});
return resolvedAs(module);
} }
return failedFor({type: 'sourceFile', candidateExts}); return failedFor({type: 'sourceFile', candidateExts});
} }
@ -391,13 +414,15 @@ class ModuleResolver<TModule: Moduleish, TPackage: Packageish> {
dirPath: string, dirPath: string,
fileNameHint: string, fileNameHint: string,
platform: string | null, platform: string | null,
): Resolution<TModule, FileCandidates> { ): Result<AssetFileResolution, FileCandidates> {
const {resolveAsset} = this._options; const {resolveAsset} = this._options;
const assetNames = resolveAsset(dirPath, fileNameHint, platform); const assetNames = resolveAsset(dirPath, fileNameHint, platform);
const assetName = getArrayLowestItem(assetNames); if (assetNames != null) {
if (assetName != null) { return resolvedAs(
const assetPath = path.join(dirPath, assetName); assetNames.map(assetName => {
return resolvedAs(this._options.moduleCache.getAssetModule(assetPath)); return path.join(dirPath, assetName);
}),
);
} }
return failedFor({type: 'asset', name: fileNameHint}); return failedFor({type: 'asset', name: fileNameHint});
} }
@ -513,7 +538,7 @@ class ModuleResolver<TModule: Moduleish, TPackage: Packageish> {
_loadAsDir( _loadAsDir(
potentialDirPath: string, potentialDirPath: string,
platform: string | null, platform: string | null,
): Resolution<TModule, DirCandidates> { ): Result<FileResolution, DirCandidates> {
const packageJsonPath = path.join(potentialDirPath, 'package.json'); const packageJsonPath = path.join(potentialDirPath, 'package.json');
if (this._options.doesFileExist(packageJsonPath)) { if (this._options.doesFileExist(packageJsonPath)) {
return this._loadAsPackage(packageJsonPath, platform); return this._loadAsPackage(packageJsonPath, platform);
@ -535,7 +560,7 @@ class ModuleResolver<TModule: Moduleish, TPackage: Packageish> {
_loadAsPackage( _loadAsPackage(
packageJsonPath: string, packageJsonPath: string,
platform: string | null, platform: string | null,
): Resolution<TModule, DirCandidates> { ): Result<FileResolution, DirCandidates> {
const package_ = this._options.moduleCache.getPackage(packageJsonPath); const package_ = this._options.moduleCache.getPackage(packageJsonPath);
const mainPrefixPath = package_.getMain(); const mainPrefixPath = package_.getMain();
const dirPath = path.dirname(mainPrefixPath); const dirPath = path.dirname(mainPrefixPath);
@ -593,18 +618,28 @@ function getArrayLowestItem(a: $ReadOnlyArray<string>): string | void {
return lowest; return lowest;
} }
function resolvedAs<TModule, TCandidates>( function resolvedAs<TResolution, TCandidates>(
module: TModule, resolution: TResolution,
): Resolution<TModule, TCandidates> { ): Result<TResolution, TCandidates> {
return {type: 'resolved', module}; return {type: 'resolved', resolution};
} }
function failedFor<TModule, TCandidates>( function failedFor<TResolution, TCandidates>(
candidates: TCandidates, candidates: TCandidates,
): Resolution<TModule, TCandidates> { ): Result<TResolution, TCandidates> {
return {type: 'failed', candidates}; return {type: 'failed', candidates};
} }
function mapResult<TResolution, TNewResolution, TCandidates>(
result: Result<TResolution, TCandidates>,
mapper: TResolution => TNewResolution,
): Result<TNewResolution, TCandidates> {
if (result.type === 'failed') {
return result;
}
return {type: 'resolved', resolution: mapper(result.resolution)};
}
class UnableToResolveError extends Error { class UnableToResolveError extends Error {
/** /**
* File path of the module that tried to require a module, ex. `/js/foo.js`. * File path of the module that tried to require a module, ex. `/js/foo.js`.