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: Weakly cache normalisation of exports field (4/n) #1334

Closed
wants to merge 1 commit into from
Closed
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
42 changes: 39 additions & 3 deletions packages/metro-resolver/src/PackageExportsResolve.js
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,25 @@ function getExportsSubpath(packageSubpath: string): string {
return packageSubpath === '' ? '.' : './' + toPosixPath(packageSubpath);
}

/**
* Maintain a WeakMap cache of the results of normalizedExportsField.
* Particularly in a large project, many source files depend on the same
* packages (eg @babel/runtime), and this avoids normalising the same JSON
* many times. Note that ExportsField is immutable, and the upstream package
* cache gives us a stable reference.
*
* The case where ExportsField is a string (not weakly referencable) has to be
* excluded, but those are very cheap to process anyway.
*
* (Ultimately this should be coupled more closely to the package cache, so that
* we can clean up immediately rather than on GC.)
*/
type ExcludeString<T> = T extends string ? empty : T;
const _normalizedExportsFields: WeakMap<
ExcludeString<ExportsField>,
ExportMap,
> = new WeakMap();

/**
* Normalise an "exports"-like field by parsing string shorthand and conditions
* shorthand at root, and flattening any legacy Node.js <13.7 array values.
Expand All @@ -153,6 +172,15 @@ function normalizeExportsField(
): ExportMap {
let rootValue;

if (typeof exportsField === 'string') {
return {'.': exportsField};
}

const cachedValue = _normalizedExportsFields.get(exportsField);
if (cachedValue) {
return cachedValue;
}

if (Array.isArray(exportsField)) {
// If an array of strings, use first value with valid specifier (root shorthand)
if (exportsField.every(value => typeof value === 'string')) {
Expand All @@ -173,7 +201,9 @@ function normalizeExportsField(
}

if (typeof rootValue === 'string') {
return {'.': rootValue};
const result = {'.': rootValue};
_normalizedExportsFields.set(exportsField, result);
return result;
}

const firstLevelKeys = Object.keys(rootValue);
Expand All @@ -182,7 +212,9 @@ function normalizeExportsField(
);

if (subpathKeys.length === firstLevelKeys.length) {
return flattenLegacySubpathValues(rootValue, createConfigError);
const result = flattenLegacySubpathValues(rootValue, createConfigError);
_normalizedExportsFields.set(exportsField, result);
return result;
}

if (subpathKeys.length !== 0) {
Expand All @@ -192,7 +224,11 @@ function normalizeExportsField(
);
}

return {'.': flattenLegacySubpathValues(rootValue, createConfigError)};
const result = {
'.': flattenLegacySubpathValues(rootValue, createConfigError),
};
_normalizedExportsFields.set(exportsField, result);
return result;
}

/**
Expand Down
Loading