| import * as path from "path"; |
| import * as TryPath from "./try-path"; |
| import * as MappingEntry from "./mapping-entry"; |
| import * as Filesystem from "./filesystem"; |
| |
| /** |
| * Function that can match a path async |
| */ |
| export interface MatchPathAsync { |
| ( |
| requestedModule: string, |
| readJson: Filesystem.ReadJsonAsync | undefined, |
| fileExists: Filesystem.FileExistsAsync | undefined, |
| extensions: ReadonlyArray<string> | undefined, |
| callback: MatchPathAsyncCallback |
| ): void; |
| } |
| |
| export interface MatchPathAsyncCallback { |
| (err?: Error, path?: string): void; |
| } |
| |
| /** |
| * See the sync version for docs. |
| */ |
| export function createMatchPathAsync( |
| absoluteBaseUrl: string, |
| paths: { [key: string]: Array<string> }, |
| mainFields: string[] = ["main"], |
| addMatchAll: boolean = true |
| ): MatchPathAsync { |
| const absolutePaths = MappingEntry.getAbsoluteMappingEntries( |
| absoluteBaseUrl, |
| paths, |
| addMatchAll |
| ); |
| |
| return ( |
| requestedModule: string, |
| readJson: Filesystem.ReadJsonAsync | undefined, |
| fileExists: Filesystem.FileExistsAsync | undefined, |
| extensions: ReadonlyArray<string> | undefined, |
| callback: MatchPathAsyncCallback |
| ) => |
| matchFromAbsolutePathsAsync( |
| absolutePaths, |
| requestedModule, |
| readJson, |
| fileExists, |
| extensions, |
| callback, |
| mainFields |
| ); |
| } |
| |
| /** |
| * See the sync version for docs. |
| */ |
| export function matchFromAbsolutePathsAsync( |
| absolutePathMappings: ReadonlyArray<MappingEntry.MappingEntry>, |
| requestedModule: string, |
| readJson: Filesystem.ReadJsonAsync = Filesystem.readJsonFromDiskAsync, |
| fileExists: Filesystem.FileExistsAsync = Filesystem.fileExistsAsync, |
| extensions: ReadonlyArray<string> = Object.keys(require.extensions), |
| callback: MatchPathAsyncCallback, |
| mainFields: string[] = ["main"] |
| ): void { |
| const tryPaths = TryPath.getPathsToTry( |
| extensions, |
| absolutePathMappings, |
| requestedModule |
| ); |
| |
| if (!tryPaths) { |
| return callback(); |
| } |
| |
| findFirstExistingPath( |
| tryPaths, |
| readJson, |
| fileExists, |
| callback, |
| 0, |
| mainFields |
| ); |
| } |
| |
| function findFirstExistingMainFieldMappedFile( |
| packageJson: Filesystem.PackageJson, |
| mainFields: string[], |
| packageJsonPath: string, |
| fileExistsAsync: Filesystem.FileExistsAsync, |
| doneCallback: (err?: Error, filepath?: string) => void, |
| index: number = 0 |
| ): void { |
| if (index >= mainFields.length) { |
| return doneCallback(undefined, undefined); |
| } |
| |
| const tryNext = () => |
| findFirstExistingMainFieldMappedFile( |
| packageJson, |
| mainFields, |
| packageJsonPath, |
| fileExistsAsync, |
| doneCallback, |
| index + 1 |
| ); |
| |
| const mainFieldMapping = packageJson[mainFields[index]]; |
| if (typeof mainFieldMapping !== "string") { |
| // Skip mappings that are not pointers to replacement files |
| return tryNext(); |
| } |
| |
| const mappedFilePath = path.join( |
| path.dirname(packageJsonPath), |
| mainFieldMapping |
| ); |
| fileExistsAsync(mappedFilePath, (err?: Error, exists?: boolean) => { |
| if (err) { |
| return doneCallback(err); |
| } |
| if (exists) { |
| return doneCallback(undefined, mappedFilePath); |
| } |
| return tryNext(); |
| }); |
| } |
| |
| // Recursive loop to probe for physical files |
| function findFirstExistingPath( |
| tryPaths: ReadonlyArray<TryPath.TryPath>, |
| readJson: Filesystem.ReadJsonAsync, |
| fileExists: Filesystem.FileExistsAsync, |
| doneCallback: MatchPathAsyncCallback, |
| index: number = 0, |
| mainFields: string[] = ["main"] |
| ): void { |
| const tryPath = tryPaths[index]; |
| if ( |
| tryPath.type === "file" || |
| tryPath.type === "extension" || |
| tryPath.type === "index" |
| ) { |
| fileExists(tryPath.path, (err: Error, exists: boolean) => { |
| if (err) { |
| return doneCallback(err); |
| } |
| if (exists) { |
| return doneCallback(undefined, TryPath.getStrippedPath(tryPath)); |
| } |
| if (index === tryPaths.length - 1) { |
| return doneCallback(); |
| } |
| // Continue with the next path |
| return findFirstExistingPath( |
| tryPaths, |
| readJson, |
| fileExists, |
| doneCallback, |
| index + 1, |
| mainFields |
| ); |
| }); |
| } else if (tryPath.type === "package") { |
| readJson(tryPath.path, (err, packageJson) => { |
| if (err) { |
| return doneCallback(err); |
| } |
| if (packageJson) { |
| return findFirstExistingMainFieldMappedFile( |
| packageJson, |
| mainFields, |
| tryPath.path, |
| fileExists, |
| (mainFieldErr?: Error, mainFieldMappedFile?: string) => { |
| if (mainFieldErr) { |
| return doneCallback(mainFieldErr); |
| } |
| if (mainFieldMappedFile) { |
| return doneCallback(undefined, mainFieldMappedFile); |
| } |
| |
| // No field in package json was a valid option. Continue with the next path. |
| return findFirstExistingPath( |
| tryPaths, |
| readJson, |
| fileExists, |
| doneCallback, |
| index + 1, |
| mainFields |
| ); |
| } |
| ); |
| } |
| |
| // This is async code, we need to return unconditionally, otherwise the code still falls |
| // through and keeps recursing. While this might work in general, libraries that use neo-async |
| // like Webpack will actually not allow you to call the same callback twice. |
| // |
| // An example of where this caused issues: |
| // https://github.com/dividab/tsconfig-paths-webpack-plugin/issues/11 |
| // |
| // Continue with the next path |
| return findFirstExistingPath( |
| tryPaths, |
| readJson, |
| fileExists, |
| doneCallback, |
| index + 1, |
| mainFields |
| ); |
| }); |
| } else { |
| TryPath.exhaustiveTypeException(tryPath.type); |
| } |
| } |