Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Resolver perf: Use hierarchicalLookup for closestPackage (3/n) #1283

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions packages/metro-file-map/src/flow-types.js
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,42 @@ export interface FileSystem {
getSerializableSnapshot(): CacheData['fileSystemData'];
getSha1(file: Path): ?string;

/**
* Given a start path (which need not exist), a subpath and type, and
* optionally a 'breakOnSegment', performs the following:
*
* X = mixedStartPath
* do
* if basename(X) === opts.breakOnSegment
* return null
* if X + subpath exists and has type opts.subpathType
* return {
* absolutePath: realpath(X + subpath)
* containerRelativePath: relative(mixedStartPath, X)
* }
* X = dirname(X)
* while X !== dirname(X)
*
* If opts.invalidatedBy is given, collects all absolute, real paths that if
* added or removed may invalidate this result.
*
* Useful for finding the closest package scope (subpath: package.json,
* type f, breakOnSegment: node_modules) or closest potential package root
* (subpath: node_modules/pkg, type: d) in Node.js resolution.
*/
hierarchicalLookup(
mixedStartPath: string,
subpath: string,
opts: {
breakOnSegment: ?string,
invalidatedBy: ?Set<string>,
subpathType: 'f' | 'd',
},
): ?{
absolutePath: string,
containerRelativePath: string,
};

/**
* Analogous to posix lstat. If the file at `file` is a symlink, return
* information about the symlink without following it.
Expand Down
71 changes: 60 additions & 11 deletions packages/metro-file-map/src/lib/RootPathUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
* @flow strict
*/

import invariant from 'invariant';
import * as path from 'path';

/**
Expand Down Expand Up @@ -71,6 +72,10 @@ export class RootPathUtils {
return this.#rootParts[this.#rootParts.length - 1 - n];
}

getParts(): $ReadOnlyArray<string> {
return this.#rootParts;
}

// absolutePath may be any well-formed absolute path.
absoluteToNormal(absolutePath: string): string {
let endOfMatchingPrefix = 0;
Expand Down Expand Up @@ -108,7 +113,7 @@ export class RootPathUtils {
absolutePath,
endOfMatchingPrefix,
upIndirectionsToPrepend,
) ?? path.relative(this.#rootDir, absolutePath)
)?.collapsedPath ?? path.relative(this.#rootDir, absolutePath)
);
}

Expand Down Expand Up @@ -139,25 +144,58 @@ export class RootPathUtils {

relativeToNormal(relativePath: string): string {
return (
this.#tryCollapseIndirectionsInSuffix(relativePath, 0, 0) ??
this.#tryCollapseIndirectionsInSuffix(relativePath, 0, 0)
?.collapsedPath ??
path.relative(this.#rootDir, path.join(this.#rootDir, relativePath))
);
}

getAncestorOfRootIdx(normalPath: string): ?number {
let pos = -3;
let nextSegment = normalPath.slice(pos);
let n = 1;
while (nextSegment === SEP_UP_FRAGMENT) {
pos -= 3;
nextSegment = normalPath.slice(pos, pos + 3);
n++;
}
if (nextSegment !== '..') {
return null;
}
return n;
}

// Takes a normal and relative path, and joins them efficiently into a normal
// path, including collapsing trailing '..' in the first part with leading
// project root segments in the relative part.
joinNormalToRelative(normalPath: string, relativePath: string): string {
joinNormalToRelative(
normalPath: string,
relativePath: string,
): {normalPath: string, collapsedSegments: number} {
if (normalPath === '') {
return relativePath;
return {collapsedSegments: 0, normalPath: relativePath};
}
if (relativePath === '') {
return normalPath;
return {collapsedSegments: 0, normalPath};
}
const left = normalPath + path.sep;
const rawPath = left + relativePath;
if (normalPath === '..' || normalPath.endsWith(SEP_UP_FRAGMENT)) {
return this.relativeToNormal(normalPath + path.sep + relativePath);
const collapsed = this.#tryCollapseIndirectionsInSuffix(rawPath, 0, 0);
invariant(collapsed != null, 'Failed to collapse');
return {
collapsedSegments: collapsed.collapsedSegments,
normalPath: collapsed.collapsedPath,
};
}
return normalPath + path.sep + relativePath;
return {
collapsedSegments: 0,
normalPath: rawPath,
};
}

relative(from: string, to: string): string {
return path.relative(from, to);
}

// Internal: Tries to collapse sequences like `../root/foo` for root
Expand All @@ -166,8 +204,9 @@ export class RootPathUtils {
fullPath: string, // A string ending with the relative path to process
startOfRelativePart: number, // Index of the start of part to process
implicitUpIndirections: number, // 0=root-relative, 1=dirname(root)-relative...
): ?string {
): ?{collapsedPath: string, collapsedSegments: number} {
let totalUpIndirections = implicitUpIndirections;
let collapsedSegments = 0;
// Allow any sequence of indirection fragments at the start of the
// unmatched suffix e.g /project/[../../foo], but bail out to Node's
// path.relative if we find a possible indirection after any later segment,
Expand All @@ -194,6 +233,7 @@ export class RootPathUtils {
fullPath[segmentToMaybeCollapse.length + pos] === path.sep)
) {
pos += segmentToMaybeCollapse.length + 1;
collapsedSegments++;
totalUpIndirections--;
} else {
break;
Expand All @@ -206,13 +246,22 @@ export class RootPathUtils {
) {
// If we have no right side (or an indirection that would take us
// below the root), just ensure we don't include a trailing separtor.
return UP_FRAGMENT_SEP.repeat(totalUpIndirections).slice(0, -1);
return {
collapsedPath: UP_FRAGMENT_SEP.repeat(totalUpIndirections).slice(
0,
-1,
),
collapsedSegments,
};
}
// Optimisation for the common case, saves a concatenation.
if (totalUpIndirections === 0) {
return right;
return {collapsedPath: right, collapsedSegments};
}
return UP_FRAGMENT_SEP.repeat(totalUpIndirections) + right;
return {
collapsedPath: UP_FRAGMENT_SEP.repeat(totalUpIndirections) + right,
collapsedSegments,
};
}

// Cap the number of indirections at the total number of root segments.
Expand Down
Loading