perf: skip side-effect-free modules for nested namespace re-exports#21085
Conversation
Resolve the "improve for nested imports" TODO in SideEffectsFlagPlugin: getTarget stops at a namespace boundary (export * as ns), so a nested id like ns.x previously left the dependency pointing at an intermediate side-effect-free module unless the module-processing order happened to revisit it. Now the chain is resolved fully in a single pass. The common single-id path is unchanged (no extra allocations or side-effects lookups); the loop only continues across a namespace boundary when nested ids remain.
Resolve the "improve for export *" TODO in SideEffectsFlagPlugin. A plain `export *` into a side-effect-free module that is a pure single-star passthrough (no own exports, exactly one `export *`) is equivalent to a star into that module's source, so the passthrough can be skipped: move the re-exported names' targets and repoint the star past the chain. This lets namespace imports through such chains drop the intermediate module (e.g. a barrel that only `export *`s another barrel). Named imports were already resolved directly via per-name target moving; this extends the same skipping to the star itself and to namespace consumers. The detection short-circuits cheaply (leaf modules aren't passthroughs), so the optimize-dependencies phase stays flat on star-heavy graphs.
When many modules import the same names from a shared barrel, the nested import-specifier branch re-ran exportInfo.getTarget for each consumer even though the side-effect-free resolution is idempotent within a pass. Cache it per export info so each name resolves once instead of once per consumer. Turns the getTarget portion from O(imports) into O(unique names): ~7-12% off the plugin's own time on barrel-heavy graphs, neutral on graphs with no repeated imports (the map lookup replaces an equivalent walk). The cache is pass-scoped and released after optimizeDependencies.
🦋 Changeset detectedLatest commit: c2024b3 The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
|
This PR is packaged and the instant preview is available (2a7c5ca). Install it locally:
npm i -D webpack@https://pkg.pr.new/webpack@2a7c5ca
yarn add -D webpack@https://pkg.pr.new/webpack@2a7c5ca
pnpm add -D webpack@https://pkg.pr.new/webpack@2a7c5ca |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #21085 +/- ##
==========================================
+ Coverage 91.66% 91.68% +0.02%
==========================================
Files 581 581
Lines 61089 61137 +48
Branches 16659 16679 +20
==========================================
+ Hits 55995 56053 +58
+ Misses 5094 5084 -10
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
Merging this PR will improve performance by 23.78%
Warning Please fix the performance issues or acknowledge them on CodSpeed. Performance Changes
Tip Investigate this regression by commenting Comparing Footnotes
|
The nested-imports loop added earlier produced no observable improvement: optimizeIncomingConnections already recurses into a connection's origin, so the original single-pass branch resolves nested namespace re-exports (ns.x, ns.deep.y, export * as ns) to the same target and same module set in every case tested. Restore the original branch and keep only the pass-scoped re-export resolution cache on top of it. export * passthrough elimination and the cache are unaffected.
Resolve the "improve for nested imports" TODO in SideEffectsFlagPlugin:
getTarget stops at a namespace boundary (export * as ns), so a nested id
like ns.x previously left the dependency pointing at an intermediate
side-effect-free module unless the module-processing order happened to
revisit it. Now the chain is resolved fully in a single pass.
The common single-id path is unchanged (no extra allocations or
side-effects lookups); the loop only continues across a namespace
boundary when nested ids remain.