Skip to content

Commit

Permalink
Resolver perf: Weakly cache normalisation of exports field (4/n) (#…
Browse files Browse the repository at this point in the history
…1334)

Summary:

Typically, a given package is required multiple times within a project, but currently we normalise the same `exports` JSON for every dependency.

In RNTester, only three of its dependencies have non-primitive `exports` fields:

```
/node_modules/babel/runtime
/node_modules/react
/packages/react-native-test-library
```

But these are referenced 760 times, meaning 760 calls to the internal `normalizeExportsField`.

This diff uses the property of Metro's upstream `ModuleCache._packageCache`, which avoids reading/parsing `package.json` unless it has changed. This makes `exports` stable, and (when it is non-primitive), weakly referenceable.

By caching these values we reduce time spent in `normalizeExportsField` by ~10x and resolution time overall by 10% for RNTester, though the impact is likely to be larger for larger projects. By using a `WeakMap`, the cache can be GCed as corresponding packages are deleted from `ModuleCache`.

```
- **[Performance]**: Cache normalisation of `exports` fields for improved resolution performance.
```

Reviewed By: huntie

Differential Revision: D61841586
  • Loading branch information
robhogan authored and facebook-github-bot committed Aug 27, 2024
1 parent 6f3af82 commit 2279e7d
Showing 1 changed file with 39 additions and 3 deletions.
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

0 comments on commit 2279e7d

Please sign in to comment.