Skip to content

Commit 9341463

Browse files
robhoganmeta-codesync[bot]
authored andcommitted
Resolver: Avoid intermediate allocations in matchSubpathFromMainFields, improve overall resolver perf ~5%
Summary: `matchSubpathFromMainFields` is called on every resolution attempt (per source-extension via `redirectModulePath`, and per package entry point), and on every call it allocated a `.map()` array, a `.filter()` array, and an `Object.assign({}, ...spread)` - even in the overwhelmingly common case where the package declares no object-valued main field (no "browser" map) and the function returns `null`. Instead, rebuild the merged replacement map with a single reverse loop over `mainFields` (preserving the prior "earlier mainFields win on key conflict" semantics) such that nothing is allocated (`null` is still returned) unless and until an object-valued field is actually found. The list of subpath variants is likewise only built in that rare matched case. This improves the perf of `matchSubpathFromMainFields` by ~20% and ~5% on resolutions overall, based on Meta's product graph. ``` - **[Performance]**: Refactor to reduce allocs for a ~5% faster resolver Reviewed By: huntie Differential Revision: D107024445 fbshipit-source-id: cec2261dc588590eb12bb413a7bbb6fc6869401c
1 parent 7529585 commit 9341463

1 file changed

Lines changed: 20 additions & 8 deletions

File tree

packages/metro-resolver/src/PackageResolve.js

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -174,24 +174,36 @@ export function matchSubpathFromMainFields(
174174
pkg: PackageJson,
175175
mainFields: ReadonlyArray<string>,
176176
): string | false | null {
177-
const fieldValues = mainFields
177+
// Merge object-valued main fields ("browser"-style maps) into a single
178+
// replacement map. We iterate `mainFields` in reverse so that, on a key
179+
// conflict, earlier `mainFields` win, equivalent to
180+
// `Object.assign({}, ...fieldValues.reverse())`, but avoiding any allocation
181+
// in the the most common case (no object-valued field, e.g. only a string
182+
// "main"/"browser").
183+
let replacements: {[string]: string | false} | null = null;
184+
for (let i = mainFields.length - 1; i >= 0; i--) {
178185
// $FlowFixMe[invalid-computed-prop]
179-
.map(name => pkg[name])
180-
.filter(value => value != null && typeof value !== 'string');
186+
const value = pkg[mainFields[i]];
187+
if (value != null && typeof value !== 'string') {
188+
if (replacements == null) {
189+
replacements = {};
190+
}
191+
replacements = {...replacements, ...value};
192+
}
193+
}
181194

182-
if (!fieldValues.length) {
195+
if (replacements == null) {
183196
return null;
184197
}
185198

186-
// $FlowFixMe[unsafe-object-assign]
187-
const replacements = Object.assign({}, ...fieldValues.reverse());
199+
// The list of subpath variants is only built in this rare matched case (a
200+
// single subpath is expanded to its "browser"-spec variants; a pre-expanded
201+
// array is matched as-is).
188202
const variants = Array.isArray(subpath)
189203
? subpath
190204
: expandSubpathVariants(subpath);
191-
192205
for (const variant of variants) {
193206
const replacement = replacements[variant];
194-
195207
if (replacement != null) {
196208
return replacement;
197209
}

0 commit comments

Comments
 (0)