metro: ModuleResolution: break down more functions

Reviewed By: davidaurelio

Differential Revision: D6610652

fbshipit-source-id: 4c39fb69fe9b5eda96c1dca6697ff9da46df2cda
This commit is contained in:
Jean Lauliac 2017-12-24 03:06:05 -08:00 committed by Facebook Github Bot
parent e4cba8e2e2
commit 494422aa43
1 changed files with 104 additions and 77 deletions

View File

@ -295,7 +295,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 result = this._loadAsFileOrDir(fullSearchQueue[i], platform); const context = {
...this._options,
getPackageMainPath: this._getPackageMainPath,
};
const result = resolveFileOrDir(context, fullSearchQueue[i], platform);
// 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') {
@ -325,6 +329,11 @@ class ModuleResolver<TModule: Moduleish, TPackage: Packageish> {
); );
} }
_getPackageMainPath = (packageJsonPath: string): string => {
const package_ = this._options.moduleCache.getPackage(packageJsonPath);
return package_.getMain();
};
/** /**
* 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
@ -338,7 +347,11 @@ class ModuleResolver<TModule: Moduleish, TPackage: Packageish> {
toModuleName: string, toModuleName: string,
platform: string | null, platform: string | null,
): TModule { ): TModule {
const result = this._loadAsFileOrDir(potentialModulePath, platform); const context = {
...this._options,
getPackageMainPath: this._getPackageMainPath,
};
const result = resolveFileOrDir(context, potentialModulePath, platform);
if (result.type === 'resolved') { if (result.type === 'resolved') {
return this._getFileResolvedModule(result.resolution); return this._getFileResolvedModule(result.resolution);
} }
@ -379,30 +392,6 @@ class ModuleResolver<TModule: Moduleish, TPackage: Packageish> {
throw new Error('switch is not exhaustive'); throw new Error('switch is not exhaustive');
} }
/**
* 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,
): Result<FileResolution, FileAndDirCandidates> {
const dirPath = path.dirname(potentialModulePath);
const fileNameHint = path.basename(potentialModulePath);
const {_options} = this;
const fileResult = resolveFile(_options, 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});
}
_getEmptyModule(fromModule: TModule, toModuleName: string): TModule { _getEmptyModule(fromModule: TModule, toModuleName: string): TModule {
const {moduleCache} = this._options; const {moduleCache} = this._options;
const module = moduleCache.getModule(ModuleResolver.EMPTY_MODULE); const module = moduleCache.getModule(ModuleResolver.EMPTY_MODULE);
@ -415,63 +404,101 @@ class ModuleResolver<TModule: Moduleish, TPackage: Packageish> {
"could not resolve `${ModuleResolver.EMPTY_MODULE}'", "could not resolve `${ModuleResolver.EMPTY_MODULE}'",
); );
} }
}
type FileOrDirContext = FileContext & {
/** /**
* Try to resolve a potential path as if it was a directory-based module. * This should return the path of the "main" module of the specified
* Either this is a directory that contains a package, or that the directory * `package.json` file, after post-processing: for example, applying the
* contains an index file. If it fails to resolve these options, it returns * 'browser' field if necessary.
* `null` and fills the array of `candidates` that were tried.
* *
* For example we could try to resolve `/foo/bar`, that would eventually * FIXME: move the post-processing here. Right now it is
* resolve to `/foo/bar/lib/index.ios.js` if we're on platform iOS and that * located in `node-haste/Package.js`, and fully duplicated in
* `bar` contains a package which entry point is `./lib/index` (or `./lib`). * `ModuleGraph/node-haste/Package.js` (!)
*/ */
_loadAsDir( +getPackageMainPath: (packageJsonPath: string) => string,
potentialDirPath: string, };
platform: string | null,
): Result<FileResolution, DirCandidates> {
const packageJsonPath = path.join(potentialDirPath, 'package.json');
if (this._options.doesFileExist(packageJsonPath)) {
return this._loadAsPackage(packageJsonPath, platform);
}
const opts = this._options;
const result = resolveFile(opts, potentialDirPath, 'index', platform);
if (result.type === 'resolved') {
return result;
}
return failedFor({type: 'index', file: result.candidates});
}
/** /**
* Right now we just consider it a failure to resolve if we couldn't find the * In the NodeJS-style module resolution scheme we want to check potential
* file corresponding to the `main` indicated by a package. Argument can be * paths both as directories and as files. For example, `/foo/bar` may resolve
* made this should be changed so that failing to find the `main` is not a * to `/foo/bar.js` (preferred), but it might also be `/foo/bar/index.js`, or
* resolution failure, but identified instead as a corrupted or invalid * even a package directory.
* package (or that a package only supports a specific platform, etc.) */
*/ function resolveFileOrDir(
_loadAsPackage( context: FileOrDirContext,
packageJsonPath: string, potentialModulePath: string,
platform: string | null, platform: string | null,
): Result<FileResolution, DirCandidates> { ): Result<FileResolution, FileAndDirCandidates> {
const package_ = this._options.moduleCache.getPackage(packageJsonPath); const dirPath = path.dirname(potentialModulePath);
const mainPrefixPath = package_.getMain(); const fileNameHint = path.basename(potentialModulePath);
const dirPath = path.dirname(mainPrefixPath); const fileResult = resolveFile(context, dirPath, fileNameHint, platform);
const prefixName = path.basename(mainPrefixPath); if (fileResult.type === 'resolved') {
const opts = this._options; return fileResult;
const fileResult = resolveFile(opts, dirPath, prefixName, platform);
if (fileResult.type === 'resolved') {
return fileResult;
}
const dirResult = this._loadAsDir(mainPrefixPath, platform);
if (dirResult.type === 'resolved') {
return dirResult;
}
return failedFor({
type: 'package',
dir: dirResult.candidates,
file: fileResult.candidates,
});
} }
const dirResult = resolveDir(context, potentialModulePath, platform);
if (dirResult.type === 'resolved') {
return dirResult;
}
return failedFor({file: fileResult.candidates, dir: dirResult.candidates});
}
/**
* Try to resolve a potential path as if it was a directory-based module.
* Either this is a directory that contains a package, or that the directory
* contains an index file. If it fails to resolve these options, it returns
* `null` and fills the array of `candidates` that were tried.
*
* For example we could try to resolve `/foo/bar`, that would eventually
* resolve to `/foo/bar/lib/index.ios.js` if we're on platform iOS and that
* `bar` contains a package which entry point is `./lib/index` (or `./lib`).
*/
function resolveDir(
context: FileOrDirContext,
potentialDirPath: string,
platform: string | null,
): Result<FileResolution, DirCandidates> {
const packageJsonPath = path.join(potentialDirPath, 'package.json');
if (context.doesFileExist(packageJsonPath)) {
return resolvePackage(context, packageJsonPath, platform);
}
const result = resolveFile(context, potentialDirPath, 'index', platform);
if (result.type === 'resolved') {
return result;
}
return failedFor({type: 'index', file: result.candidates});
}
/**
* Resolve the main module of a package.
*
* Right now we just consider it a failure to resolve if we couldn't find the
* file corresponding to the `main` indicated by a package. This is incorrect:
* failing to find the `main` is not a resolution failure, but instead means the
* package is corrupted or invalid (or that a package only supports a specific
* platform, etc.)
*/
function resolvePackage(
context: FileOrDirContext,
packageJsonPath: string,
platform: string | null,
): Result<FileResolution, DirCandidates> {
const mainPrefixPath = context.getPackageMainPath(packageJsonPath);
const dirPath = path.dirname(mainPrefixPath);
const prefixName = path.basename(mainPrefixPath);
const fileResult = resolveFile(context, dirPath, prefixName, platform);
if (fileResult.type === 'resolved') {
return fileResult;
}
const dirResult = resolveDir(context, mainPrefixPath, platform);
if (dirResult.type === 'resolved') {
return dirResult;
}
return failedFor({
type: 'package',
dir: dirResult.candidates,
file: fileResult.candidates,
});
} }
type FileContext = { type FileContext = {