metro: ModuleResolution: break down functions out of the class

Reviewed By: davidaurelio

Differential Revision: D6610568

fbshipit-source-id: b9f9c6803026442e557d6422fe0589fdeacc8311
This commit is contained in:
Jean Lauliac 2017-12-24 03:06:04 -08:00 committed by Facebook Github Bot
parent 1ce70bb695
commit e4cba8e2e2
3 changed files with 178 additions and 138 deletions

View File

@ -141,7 +141,7 @@ exports.createResolveFn = function(options: ResolveOptions): ResolveFn {
dirExists: filePath => hasteFS.dirExists(filePath),
doesFileExist: filePath => hasteFS.exists(filePath),
extraNodeModules,
helpers,
isAssetFile: filePath => helpers.isAssetFile(filePath),
moduleCache,
moduleMap: new ModuleMap({
duplicates: Object.create(null),

View File

@ -180,7 +180,7 @@ class DependencyGraph extends EventEmitter {
},
doesFileExist: this._doesFileExist,
extraNodeModules: this._opts.extraNodeModules,
helpers: this._helpers,
isAssetFile: filePath => this._helpers.isAssetFile(filePath),
moduleCache: this._moduleCache,
moduleMap: this._moduleMap,
preferNativePlatform: this._opts.preferNativePlatform,
@ -232,7 +232,7 @@ class DependencyGraph extends EventEmitter {
}
getModuleForPath(entryFile: string) {
if (this._moduleResolver.isAssetFile(entryFile)) {
if (this._helpers.isAssetFile(entryFile)) {
return this._moduleCache.getAssetModule(entryFile);
}

View File

@ -58,19 +58,34 @@ export type ModuleishCache<TModule, TPackage> = {
getAssetModule(path: string): TModule,
};
/**
* Given a directory path and the base asset name, return a list of all the
* asset file names that match the given base name in that directory. Return
* null if there's no such named asset. `platform` is used to identify
* platform-specific assets, ex. `foo.ios.js` instead of a generic `foo.js`.
*/
type ResolveAsset = (
dirPath: string,
assetName: string,
platform: string | null,
) => ?$ReadOnlyArray<string>;
/**
* Check existence of a single file.
*/
type DoesFileExist = (filePath: string) => boolean;
type IsAssetFile = (fileName: string) => boolean;
type Options<TModule, TPackage> = {|
+dirExists: DirExistsFn,
+doesFileExist: (filePath: string) => boolean,
+doesFileExist: DoesFileExist,
+extraNodeModules: ?Object,
+helpers: DependencyGraphHelpers,
+isAssetFile: IsAssetFile,
+moduleCache: ModuleishCache<TModule, TPackage>,
+preferNativePlatform: boolean,
+moduleMap: ModuleMap,
+resolveAsset: (
dirPath: string,
assetName: string,
platform: string | null,
) => ?$ReadOnlyArray<string>,
+resolveAsset: ResolveAsset,
+sourceExts: Array<string>,
|};
@ -376,7 +391,8 @@ class ModuleResolver<TModule: Moduleish, TPackage: Packageish> {
): Result<FileResolution, FileAndDirCandidates> {
const dirPath = path.dirname(potentialModulePath);
const fileNameHint = path.basename(potentialModulePath);
const fileResult = this._loadAsFile(dirPath, fileNameHint, platform);
const {_options} = this;
const fileResult = resolveFile(_options, dirPath, fileNameHint, platform);
if (fileResult.type === 'resolved') {
return fileResult;
}
@ -387,131 +403,6 @@ class ModuleResolver<TModule: Moduleish, TPackage: Packageish> {
return failedFor({file: fileResult.candidates, dir: dirResult.candidates});
}
isAssetFile(filename: string): boolean {
return this._options.helpers.isAssetFile(filename);
}
_loadAsFile(
dirPath: string,
fileNameHint: string,
platform: string | null,
): Result<FileResolution, FileCandidates> {
if (this.isAssetFile(fileNameHint)) {
const result = this._loadAsAssetFile(dirPath, fileNameHint, platform);
return mapResult(result, filePaths => ({type: 'assetFiles', filePaths}));
}
const candidateExts = [];
const filePathPrefix = path.join(dirPath, fileNameHint);
const context = {filePathPrefix, candidateExts};
const filePath = this._tryToResolveSourceFile(context, platform);
if (filePath != null) {
return resolvedAs({type: 'sourceFile', filePath});
}
return failedFor({type: 'sourceFile', candidateExts});
}
_loadAsAssetFile(
dirPath: string,
fileNameHint: string,
platform: string | null,
): Result<AssetFileResolution, FileCandidates> {
const {resolveAsset} = this._options;
const assetNames = resolveAsset(dirPath, fileNameHint, platform);
if (assetNames != null) {
return resolvedAs(
assetNames.map(assetName => {
return path.join(dirPath, assetName);
}),
);
}
return failedFor({type: 'asset', name: fileNameHint});
}
/**
* A particular 'base path' can resolve to a number of possibilities depending
* on the context. For example `foo/bar` could resolve to `foo/bar.ios.js`, or
* to `foo/bar.js`. If can also resolve to the bare path `foo/bar` itself, as
* supported by Node.js resolution. On the other hand it doesn't support
* `foo/bar.ios`, for historical reasons.
*
* Return the full path of the resolved module, `null` if no resolution could
* be found.
*/
_tryToResolveSourceFile(
context: {|
+candidateExts: Array<string>,
+filePathPrefix: string,
|},
platform: ?string,
): ?string {
let filePath = this._tryToResolveFileForExt(context, '');
if (filePath) {
return filePath;
}
const {sourceExts} = this._options;
for (let i = 0; i < sourceExts.length; i++) {
const ext = `.${sourceExts[i]}`;
filePath = this._tryToResolveFileForExts(context, ext, platform);
if (filePath != null) {
return filePath;
}
}
return null;
}
/**
* For a particular extension, ex. `js`, we want to try a few possibilities,
* such as `foo.ios.js`, `foo.native.js`, and of course `foo.js`.
*
* Return the full path of the resolved module, `null` if no resolution could
* be found.
*/
_tryToResolveFileForExts(
context: {|
+candidateExts: Array<string>,
+filePathPrefix: string,
|},
sourceExt: string,
platform: ?string,
): ?string {
const {preferNativePlatform} = this._options;
if (platform != null) {
const platExt = `.${platform}${sourceExt}`;
const filePath = this._tryToResolveFileForExt(context, platExt);
if (filePath) {
return filePath;
}
}
if (preferNativePlatform) {
const nativeExt = `.native${sourceExt}`;
const filePath = this._tryToResolveFileForExt(context, nativeExt);
if (filePath) {
return filePath;
}
}
const filePath = this._tryToResolveFileForExt(context, sourceExt);
return filePath;
}
/**
* We try to resolve a single possible extension. If it doesn't exist, then
* we make sure to add the extension to a list of candidates for reporting.
*/
_tryToResolveFileForExt(
context: {|
+candidateExts: Array<string>,
+filePathPrefix: string,
|},
extension: string,
): ?string {
const filePath = `${context.filePathPrefix}${extension}`;
if (this._options.doesFileExist(filePath)) {
return filePath;
}
context.candidateExts.push(filePath);
return null;
}
_getEmptyModule(fromModule: TModule, toModuleName: string): TModule {
const {moduleCache} = this._options;
const module = moduleCache.getModule(ModuleResolver.EMPTY_MODULE);
@ -543,7 +434,8 @@ class ModuleResolver<TModule: Moduleish, TPackage: Packageish> {
if (this._options.doesFileExist(packageJsonPath)) {
return this._loadAsPackage(packageJsonPath, platform);
}
const result = this._loadAsFile(potentialDirPath, 'index', platform);
const opts = this._options;
const result = resolveFile(opts, potentialDirPath, 'index', platform);
if (result.type === 'resolved') {
return result;
}
@ -565,7 +457,8 @@ class ModuleResolver<TModule: Moduleish, TPackage: Packageish> {
const mainPrefixPath = package_.getMain();
const dirPath = path.dirname(mainPrefixPath);
const prefixName = path.basename(mainPrefixPath);
const fileResult = this._loadAsFile(dirPath, prefixName, platform);
const opts = this._options;
const fileResult = resolveFile(opts, dirPath, prefixName, platform);
if (fileResult.type === 'resolved') {
return fileResult;
}
@ -581,6 +474,153 @@ class ModuleResolver<TModule: Moduleish, TPackage: Packageish> {
}
}
type FileContext = {
+doesFileExist: DoesFileExist,
+isAssetFile: IsAssetFile,
+preferNativePlatform: boolean,
+resolveAsset: ResolveAsset,
+sourceExts: $ReadOnlyArray<string>,
};
/**
* Given a file name for a particular directory, return a resolution result
* depending on whether or not we found the corresponding module as a file. For
* example, we might ask for `foo.png`, that resolves to
* `['/js/beep/foo.ios.png']`. Or we may ask for `boop`, that resolves to
* `/js/boop.android.ts`. On the other hand this function does not resolve
* directory-based module names: for example `boop` will not resolve to
* `/js/boop/index.js` (see `_loadAsDir` for that).
*/
function resolveFile(
context: FileContext,
dirPath: string,
fileNameHint: string,
platform: string | null,
): Result<FileResolution, FileCandidates> {
const {isAssetFile, resolveAsset} = context;
if (isAssetFile(fileNameHint)) {
const result = resolveAssetFiles(
resolveAsset,
dirPath,
fileNameHint,
platform,
);
return mapResult(result, filePaths => ({type: 'assetFiles', filePaths}));
}
const candidateExts = [];
const filePathPrefix = path.join(dirPath, fileNameHint);
const sfContext = {...context, candidateExts, filePathPrefix};
const filePath = resolveSourceFile(sfContext, platform);
if (filePath != null) {
return resolvedAs({type: 'sourceFile', filePath});
}
return failedFor({type: 'sourceFile', candidateExts});
}
type SourceFileContext = SourceFileForAllExtsContext & {
+sourceExts: $ReadOnlyArray<string>,
};
/**
* A particular 'base path' can resolve to a number of possibilities depending
* on the context. For example `foo/bar` could resolve to `foo/bar.ios.js`, or
* to `foo/bar.js`. If can also resolve to the bare path `foo/bar` itself, as
* supported by Node.js resolution. On the other hand it doesn't support
* `foo/bar.ios`, for historical reasons.
*
* Return the full path of the resolved module, `null` if no resolution could
* be found.
*/
function resolveSourceFile(
context: SourceFileContext,
platform: ?string,
): ?string {
let filePath = resolveSourceFileForAllExts(context, '');
if (filePath) {
return filePath;
}
const {sourceExts} = context;
for (let i = 0; i < sourceExts.length; i++) {
const ext = `.${sourceExts[i]}`;
filePath = resolveSourceFileForAllExts(context, ext, platform);
if (filePath != null) {
return filePath;
}
}
return null;
}
type SourceFileForAllExtsContext = SourceFileForExtContext & {
+preferNativePlatform: boolean,
};
/**
* For a particular extension, ex. `js`, we want to try a few possibilities,
* such as `foo.ios.js`, `foo.native.js`, and of course `foo.js`. Return the
* full path of the resolved module, `null` if no resolution could be found.
*/
function resolveSourceFileForAllExts(
context: SourceFileForAllExtsContext,
sourceExt: string,
platform: ?string,
): ?string {
if (platform != null) {
const ext = `.${platform}${sourceExt}`;
const filePath = resolveSourceFileForExt(context, ext);
if (filePath) {
return filePath;
}
}
if (context.preferNativePlatform) {
const filePath = resolveSourceFileForExt(context, `.native${sourceExt}`);
if (filePath) {
return filePath;
}
}
const filePath = resolveSourceFileForExt(context, sourceExt);
return filePath;
}
type SourceFileForExtContext = {
+candidateExts: Array<string>,
+doesFileExist: DoesFileExist,
+filePathPrefix: string,
};
/**
* We try to resolve a single possible extension. If it doesn't exist, then
* we make sure to add the extension to a list of candidates for reporting.
*/
function resolveSourceFileForExt(
context: SourceFileForExtContext,
extension: string,
): ?string {
const filePath = `${context.filePathPrefix}${extension}`;
if (context.doesFileExist(filePath)) {
return filePath;
}
context.candidateExts.push(filePath);
return null;
}
/**
* Find all the asset files corresponding to the file base name, and return
* it wrapped as a resolution result.
*/
function resolveAssetFiles(
resolveAsset: ResolveAsset,
dirPath: string,
fileNameHint: string,
platform: string | null,
): Result<AssetFileResolution, FileCandidates> {
const assetNames = resolveAsset(dirPath, fileNameHint, platform);
if (assetNames != null) {
const res = assetNames.map(assetName => path.join(dirPath, assetName));
return resolvedAs(res);
}
return failedFor({type: 'asset', name: fileNameHint});
}
// HasteFS stores paths with backslashes on Windows, this ensures the path is in
// the proper format. Will also add drive letter if not present so `/root` will
// resolve to `C:\root`. Noop on other platforms.