From 9fc9bf004d389a40ce80ba2b8c880446980444ba Mon Sep 17 00:00:00 2001 From: Rob Hogan Date: Mon, 12 Aug 2024 08:48:31 -0700 Subject: [PATCH] Resolution: Replace context.unstable_getRealPath with context.unstable_fileSystemLookup Summary: Replace `unstable_getRealPath`, which was introduced to bring symlink support to the resolver, with the more powerful `unstable_fileSystemLookup`. We're going to need API(s) that can - Check existence of a file or directory - Get the real path, to allow resolvers to return real paths. This API aims to roll everything into one to reduce multiple lookups as much as possible. (I plan to deprecate `context.fileExists` for that reason, because currently using it in practice necessitates a second lookup to determine the real path for a correct resolution.) This mirrors some previous underlying implementation changes in D52255863 and D52390401 and brings the context API more in line, without exposing more than necessary. As with D52255863, we use `{exists: false}` rather than a `null` return to allow for extended information in a non-breaking change in future - in particular whether the lookup goes outside a watched folder. Changelog: Internal Reviewed By: huntie Differential Revision: D59003947 fbshipit-source-id: 02db458be7701fb8c5fd5feac8eff109ebc6bf1a --- packages/metro-file-map/types/flow-types.d.ts | 30 ++++++++++++++++++- .../src/PackageExportsResolve.js | 8 ++--- .../src/__tests__/package-exports-test.js | 2 +- .../metro-resolver/src/__tests__/utils.js | 18 ++++++++--- packages/metro-resolver/src/index.js | 2 +- packages/metro-resolver/src/resolve.js | 8 ++--- packages/metro-resolver/src/types.js | 12 ++++++-- packages/metro-resolver/types/types.d.ts | 11 +++++-- .../metro/src/node-haste/DependencyGraph.js | 21 ++++++++----- .../DependencyGraph/ModuleResolution.js | 8 ++--- 10 files changed, 90 insertions(+), 30 deletions(-) diff --git a/packages/metro-file-map/types/flow-types.d.ts b/packages/metro-file-map/types/flow-types.d.ts index c7598ded58..c5bc339b08 100644 --- a/packages/metro-file-map/types/flow-types.d.ts +++ b/packages/metro-file-map/types/flow-types.d.ts @@ -162,7 +162,6 @@ export interface FileSystem { getAllFiles(): Path[]; getDependencies(file: Path): string[] | null; getModuleName(file: Path): string | null; - getRealPath(file: Path): string | null; getSerializableSnapshot(): FileData; getSha1(file: Path): string | null; @@ -208,6 +207,12 @@ export interface FileSystem { */ linkStats(file: Path): FileStats | null; + /** + * Return information about the given path, whether a directory or file. + * Always follow symlinks, and return a real path if it exists. + */ + lookup(mixedPath: Path): LookupResult; + matchFiles(opts: { /* Filter relative paths against a pattern. */ filter?: RegExp | null; @@ -226,6 +231,29 @@ export interface FileSystem { export type Glob = string; +export type LookupResult = + | { + // The node is missing from the FileSystem implementation (note this + // could indicate an unwatched path, or a directory containing no watched + // files). + exists: false; + // The real, normal, absolute paths of any symlinks traversed. + links: ReadonlySet; + // The real, normal, absolute path of the first path segment + // encountered that does not exist, or cannot be navigated through. + missing: string; + } + | { + exists: true; + // The real, normal, absolute paths of any symlinks traversed. + links: ReadonlySet; + // The real, normal, absolute path of the file or directory. + realPath: string; + // Currently lookup always follows symlinks, so can only return + // directories or regular files, but this may be extended. + type: 'd' | 'f'; + }; + export interface HasteMap { getModule( name: string, diff --git a/packages/metro-resolver/src/PackageExportsResolve.js b/packages/metro-resolver/src/PackageExportsResolve.js index 1fe1b45d1d..8bd2c463da 100644 --- a/packages/metro-resolver/src/PackageExportsResolve.js +++ b/packages/metro-resolver/src/PackageExportsResolve.js @@ -105,12 +105,12 @@ export function resolvePackageTargetFromExports( } } - if (context.unstable_getRealPath != null) { - const maybeRealPath = context.unstable_getRealPath(filePath); - if (maybeRealPath != null) { + if (context.unstable_fileSystemLookup != null) { + const lookupResult = context.unstable_fileSystemLookup(filePath); + if (lookupResult.exists && lookupResult.type === 'f') { return { type: 'sourceFile', - filePath: maybeRealPath, + filePath: lookupResult.realPath, }; } } else if (context.doesFileExist(filePath)) { diff --git a/packages/metro-resolver/src/__tests__/package-exports-test.js b/packages/metro-resolver/src/__tests__/package-exports-test.js index e036da88e3..28bd196529 100644 --- a/packages/metro-resolver/src/__tests__/package-exports-test.js +++ b/packages/metro-resolver/src/__tests__/package-exports-test.js @@ -34,7 +34,7 @@ describe('with package exports resolution disabled', () => { }), originModulePath: '/root/src/main.js', unstable_enablePackageExports: false, - unstable_getRealPath: null, + unstable_fileSystemLookup: null, }; expect(Resolver.resolve(context, 'test-pkg', null)).toEqual({ diff --git a/packages/metro-resolver/src/__tests__/utils.js b/packages/metro-resolver/src/__tests__/utils.js index dfea8cf122..c66f81eee6 100644 --- a/packages/metro-resolver/src/__tests__/utils.js +++ b/packages/metro-resolver/src/__tests__/utils.js @@ -56,10 +56,20 @@ export function createResolutionContext( web: ['browser'], }, unstable_enablePackageExports: false, - unstable_getRealPath: filePath => - typeof fileMap[filePath] === 'string' - ? filePath - : fileMap[filePath]?.realPath, + unstable_fileSystemLookup: filePath => { + const candidate = fileMap[filePath]; + if (typeof candidate === 'string') { + return {exists: true, type: 'f', realPath: filePath}; + } + if (candidate == null || candidate.realPath == null) { + return {exists: false}; + } + return { + exists: true, + type: 'f', + realPath: candidate.realPath, + }; + }, unstable_logWarning: () => {}, ...createPackageAccessors(fileMap), }; diff --git a/packages/metro-resolver/src/index.js b/packages/metro-resolver/src/index.js index bfd375d824..b22ba1aaba 100644 --- a/packages/metro-resolver/src/index.js +++ b/packages/metro-resolver/src/index.js @@ -20,7 +20,7 @@ export type { FileAndDirCandidates, FileCandidates, FileResolution, - GetRealPath, + FileSystemLookup, ResolutionContext, Resolution, ResolveAsset, diff --git a/packages/metro-resolver/src/resolve.js b/packages/metro-resolver/src/resolve.js index d908fee37a..900ba7ef87 100644 --- a/packages/metro-resolver/src/resolve.js +++ b/packages/metro-resolver/src/resolve.js @@ -500,10 +500,10 @@ function resolveSourceFileForExt( if (redirectedPath === false) { return {type: 'empty'}; } - if (context.unstable_getRealPath) { - const maybeRealPath = context.unstable_getRealPath(redirectedPath); - if (maybeRealPath != null) { - return maybeRealPath; + if (context.unstable_fileSystemLookup) { + const lookupResult = context.unstable_fileSystemLookup(redirectedPath); + if (lookupResult.exists && lookupResult.type === 'f') { + return lookupResult.realPath; } } else if (context.doesFileExist(redirectedPath)) { return redirectedPath; diff --git a/packages/metro-resolver/src/types.js b/packages/metro-resolver/src/types.js index ef70462fbf..fa4545cd13 100644 --- a/packages/metro-resolver/src/types.js +++ b/packages/metro-resolver/src/types.js @@ -97,7 +97,15 @@ export type PackageForModule = $ReadOnly<{ * Check existence of a single file. */ export type DoesFileExist = (filePath: string) => boolean; -export type GetRealPath = (path: string) => ?string; + +/** + * Performs a lookup against an absolute or project-relative path to determine + * whether it exists as a file or directory. Follows any symlinks, and returns + * a real absolute path on existence. + */ +export type FileSystemLookup = ( + absoluteOrProjectRelativePath: string, +) => {exists: false} | {exists: true, type: 'f' | 'd', realPath: string}; /** * Given a directory path and the base asset name, return a list of all the @@ -181,7 +189,7 @@ export type ResolutionContext = $ReadOnly<{ [platform: string]: $ReadOnlyArray, }>, unstable_enablePackageExports: boolean, - unstable_getRealPath?: ?GetRealPath, + unstable_fileSystemLookup?: ?FileSystemLookup, unstable_logWarning: (message: string) => void, }>; diff --git a/packages/metro-resolver/types/types.d.ts b/packages/metro-resolver/types/types.d.ts index 80fd2ff695..112191ed01 100644 --- a/packages/metro-resolver/types/types.d.ts +++ b/packages/metro-resolver/types/types.d.ts @@ -74,8 +74,15 @@ export interface PackageForModule extends PackageInfo { * Check existence of a single file. */ export type DoesFileExist = (filePath: string) => boolean; -export type GetRealPath = (path: string) => string | null; export type IsAssetFile = (fileName: string) => boolean; +/** + * Performs a lookup against an absolute or project-relative path to determine + * whether it exists as a file or directory. Follows any symlinks, and returns + * a real absolute path on existence. + */ +export type FileSystemLookup = ( + absoluteOrProjectRelativePath: string, +) => {exists: false} | {exists: true; type: 'f' | 'd'; realPath: string}; /** * Given a directory path and the base asset name, return a list of all the @@ -158,7 +165,7 @@ export interface ResolutionContext { [platform: string]: ReadonlyArray; }>; unstable_enablePackageExports: boolean; - unstable_getRealPath?: GetRealPath | null; + unstable_fileSystemLookup?: FileSystemLookup | null; unstable_logWarning: (message: string) => void; } diff --git a/packages/metro/src/node-haste/DependencyGraph.js b/packages/metro/src/node-haste/DependencyGraph.js index 9a1068f9a5..f846b93a94 100644 --- a/packages/metro/src/node-haste/DependencyGraph.js +++ b/packages/metro/src/node-haste/DependencyGraph.js @@ -160,9 +160,16 @@ class DependencyGraph extends EventEmitter { } _createModuleResolver() { - const getRealPathIfFile = (path: string) => { + const fileSystemLookup = (path: string) => { const result = this._fileSystem.lookup(path); - return result.exists && result.type === 'f' ? result.realPath : null; + if (result.exists) { + return { + exists: true, + realPath: result.realPath, + type: result.type, + }; + } + return {exists: false}; }; this._moduleResolver = new ModuleResolver({ @@ -190,14 +197,14 @@ class DependencyGraph extends EventEmitter { reporter: this._config.reporter, resolveAsset: (dirPath: string, assetName: string, extension: string) => { const basePath = dirPath + path.sep + assetName; - let assets = [ + const assets = [ basePath + extension, ...this._config.resolver.assetResolutions.map( resolution => basePath + '@' + resolution + 'x' + extension, ), - ]; - - assets = assets.map(getRealPathIfFile).filter(Boolean); + ] + .map(assetPath => fileSystemLookup(assetPath).realPath) + .filter(Boolean); return assets.length ? assets : null; }, @@ -208,7 +215,7 @@ class DependencyGraph extends EventEmitter { this._config.resolver.unstable_conditionsByPlatform, unstable_enablePackageExports: this._config.resolver.unstable_enablePackageExports, - unstable_getRealPath: getRealPathIfFile, + unstable_fileSystemLookup: fileSystemLookup, }); } diff --git a/packages/metro/src/node-haste/DependencyGraph/ModuleResolution.js b/packages/metro/src/node-haste/DependencyGraph/ModuleResolution.js index 4b7e852db9..8e4982cc7b 100644 --- a/packages/metro/src/node-haste/DependencyGraph/ModuleResolution.js +++ b/packages/metro/src/node-haste/DependencyGraph/ModuleResolution.js @@ -21,7 +21,7 @@ import type { CustomResolver, DoesFileExist, FileCandidates, - GetRealPath, + FileSystemLookup, Resolution, ResolveAsset, } from 'metro-resolver'; @@ -82,7 +82,7 @@ type Options = $ReadOnly<{ [platform: string]: $ReadOnlyArray, }>, unstable_enablePackageExports: boolean, - unstable_getRealPath: ?GetRealPath, + unstable_fileSystemLookup: ?FileSystemLookup, }>; class ModuleResolver { @@ -151,7 +151,7 @@ class ModuleResolver { unstable_conditionNames, unstable_conditionsByPlatform, unstable_enablePackageExports, - unstable_getRealPath, + unstable_fileSystemLookup, } = this._options; try { @@ -173,7 +173,7 @@ class ModuleResolver { unstable_conditionNames, unstable_conditionsByPlatform, unstable_enablePackageExports, - unstable_getRealPath, + unstable_fileSystemLookup, unstable_logWarning: this._logWarning, customResolverOptions: resolverOptions.customResolverOptions ?? {}, originModulePath: fromModule.path,