Skip to content

perf: reduce always-on overhead of the lazy barrel optimization#21213

Merged
alexander-akait merged 5 commits into
mainfrom
claude/pr-21165-perf-memory-x1iq5b
Jun 18, 2026
Merged

perf: reduce always-on overhead of the lazy barrel optimization#21213
alexander-akait merged 5 commits into
mainfrom
claude/pr-21165-perf-memory-x1iq5b

Conversation

@alexander-akait

Copy link
Copy Markdown
Member

Summary

Follow-up perf/memory work on the lazy barrel optimization merged in #21165, which runs for every side-effect-free ESM module. This trims that always-on path: dependency classification is now allocation-free (a string tag + getLazyName() instead of a fresh { id } / { local } object per dependency), LazyBarrelInfo is allocated only when a module actually defers something, terminal export names are recorded only when a star re-export is present, the per-module lookups are released once targets are built, and the controller's per-module state is cleared after the make phase.

Net: barrel-heavy builds keep the large win from #21165 (e.g. importing 3 of 600 exports: ~8x faster, ~45% lower peak RSS), while builds that don't benefit (side-effect-free heavy, no barrels) stay flat on CPU and flat-or-lower on retained memory. Refs #21165.

What kind of change does this PR introduce?

perf

Did you add tests for your changes?

No new tests — behavior is unchanged and is covered by the existing test/configCases/lazy-barrel, test/watchCases/lazy-barrel, side-effects and stats suites (all passing).

Does this PR introduce a breaking change?

No.

If relevant, what needs to be documented once your changes are merged or what have you already documented?

n/a

Use of AI

AI (Claude) was used to analyze #21165 for allocation/retention hotspots on the always-on path, implement these optimizations, and benchmark before/after; every change was measured and verified against the existing test suite.


Generated by Claude Code

Free LazyBarrelInfo's export-name lookups (and drop the per-module
LazyBarrelInfo reference) as soon as no re-export target stays deferred,
so a resolved side-effect-free barrel no longer retains a per-export
Map for the lifetime of the compilation.
Make dependency classification allocation-free: getLazyUntil now returns a
string tag instead of a fresh { id } / { local } object per dependency, with
the name read separately via getLazyName. classify() also skips allocating
LazyBarrelInfo for side-effect-free modules that defer nothing (e.g. leaf
modules with only local exports) and records terminal export names only when
a star re-export is present.

On a side-effect-free-heavy build this lowers CPU ~9% and retained heap ~2MB;
barrel builds are unchanged.
Two zero-behavior-change reductions on the always-on path: skip the
`setLazy in dep` probe for the common non-deferrable dependency (only the
rare deferrable branch needs it), and check `factoryMeta.sideEffectFree`
before the WeakMap lookup in request() so non-side-effect-free modules
return without a map probe (state only ever exists for side-effect-free
modules). The full classify+request machinery measures <0.7% of build time.
A side-effect-free module's deferred side-effect imports keep their
LazyBarrelInfo for the whole compilation when never requested by name,
retaining a per-module Map/Set that is dead weight once the module graph
is built. Clear the controller's per-module state in seal()'s final
callback (lazy barrel only acts during make). On a 5000-module
side-effect-free build this removes ~6MB of retained heap, bringing it
back to (slightly below) the pre-feature baseline.
@changeset-bot

changeset-bot Bot commented Jun 18, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 4308dcb

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
webpack Patch

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

@github-actions

github-actions Bot commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

This PR is packaged and the instant preview is available (03063b8).

Install it locally:

  • npm
npm i -D webpack@https://pkg.pr.new/webpack@03063b8
  • yarn
yarn add -D webpack@https://pkg.pr.new/webpack@03063b8
  • pnpm
pnpm add -D webpack@https://pkg.pr.new/webpack@03063b8

@github-actions

Copy link
Copy Markdown
Contributor

Types Coverage

Coverage after merging claude/pr-21165-perf-memory-x1iq5b into main will be
99.35%
Coverage Report
FileStmtsBranchesFuncsLinesUncovered Lines
bin
   webpack.js98.77%100%100%98.77%91
examples
   build-common.js100%100%100%100%
   buildAll.js100%100%100%100%
   examples.js100%100%100%100%
   template-common.js98.21%100%100%98.21%72
examples/custom-javascript-parser
   test.filter.js100%100%100%100%
examples/custom-javascript-parser/internals
   acorn-parse.js100%100%100%100%
   meriyah-parse.js100%100%100%100%
   oxc-parse.js91.30%100%100%91.30%140, 142–143, 145, 147, 153–154, 161, 168, 90
examples/markdown
   webpack.config.mjs100%100%100%100%
examples/typescript
   test.filter.js100%100%100%100%
examples/typescript-non-erasable
   test.filter.js50%100%100%50%5
examples/virtual-modules
   test.filter.js100%100%100%100%
examples/wasm-bindgen-esm
   test.filter.js100%100%100%100%
examples/wasm-complex
   test.filter.js100%100%100%100%
examples/wasm-simple
   test.filter.js100%100%100%100%
examples/wasm-simple-source-phase
   test.filter.js100%100%100%100%
lib
   APIPlugin.js100%100%100%100%
   AsyncDependenciesBlock.js100%100%100%100%
   AutomaticPrefetchPlugin.js100%100%100%100%
   BannerPlugin.js100%100%100%100%
   Cache.js98.21%100%100%98.21%101
   CacheFacade.js100%100%100%100%
   Chunk.js99.72%100%100%99.72%39
   ChunkGraph.js100%100%100%100%
   ChunkGroup.js100%100%100%100%
   ChunkTemplate.js100%100%100%100%
   CleanPlugin.js99.15%100%100%99.15%206, 226
   CodeGenerationResults.js100%100%100%100%
   CompatibilityPlugin.js100%100%100%100%
   Compilation.js98.43%100%100%98.43%1618, 1937, 1944, 1952, 1974, 1977, 2917, 3396–3397, 3429, 4095, 4125, 4178–4179, 4183, 4188, 4204–4205, 4219–4220, 4225–4226, 4703, 4729, 514, 519, 5537, 5569, 5586, 5602, 5618, 5633, 5658–5659, 5661, 5989, 5994, 6000, 6003, 6015, 6017, 6021, 6037, 6052, 6084, 6138, 6162, 6276, 764–765
   Compiler.js99.56%100%100%99.56%1135–1136, 1144
   ConcatenationScope.js98.59%100%100%98.59%189
   ConditionalInitFragment.js100%100%100%100%
   ConstPlugin.js100%100%100%100%
   ContextExclusionPlugin.js100%100%100%100%
   ContextModule.js100%100%100%100%
   ContextModuleFactory.js97.40%100%100%97.40%258, 395, 418, 420, 424, 433–434
   ContextReplacementPlugin.js100%100%100%100%
   DefinePlugin.js99%100%100%99%171–172, 188, 207, 281
   DependenciesBlock.js100%100%100%100%
   Dependency.js98.35%100%100%98.35%435, 481
   DependencyTemplate.js100%100%100%100%
   DependencyTemplates.js100%100%100%100%
   DotenvPlugin.js98.41%100%100%98.41%378, 391–392
   DynamicEntryPlugin.js100%100%100%100%
   EntryOptionPlugin.js100%100%100%100%
   EntryPlugin.js100%100%100%100%
   Entrypoint.js100%100%100%100%
   EnvironmentPlugin.js97.14%100%100%97.14%49
   ErrorHelpers.js100%100%100%100%
   EvalDevToolModulePlugin.js100%100%100%100%
   EvalSourceMapDevToolPlugin.js100%100%100%100%
   ExportsInfo.js100%100%100%100%
   ExportsInfoApiPlugin.js100%100%100%100%
   ExternalModule.js98.50%100%100%98.50%1057, 1060, 445–449, 451, 597
   ExternalModuleFactoryPlugin.js100%100%100%100%
   ExternalsPlugin.js100%100%100%100%
   FileSystemInfo.js99.50%100%100%99.50%182, 2252–2253, 2256, 2267, 2278, 2289, 278, 3693, 3708, 3732
   FlagAllModulesAsUsedPlugin.js100%100%100%100%
   FlagDependencyExportsPlugin.js98.46%100%100%98.46%425, 434, 436, 440
   FlagDependencyUsagePlugin.js100%100%100%100%
   FlagEntryExportAsUsedPlugin.js100%100%100%100%
   Generator.js100%100%100%100%
   HotModuleReplacementPlugin.js100%100%100%100%
   HotUpdateChunk.js100%100%100%100%
   IgnorePlugin.js100%100%100%100%
   IgnoreWarningsPlugin.js100%100%100%100%
   InitFragment.js100%100%100%100%
   JavascriptMetaInfoPlugin.js100%100%100%100%
   LazyBarrel.js100%100%100%100%
   LibraryTemplatePlugin.js100%100%100%100%
   LoaderOptionsPlugin.js100%100%100%100%
   LoaderTargetPlugin.js100%100%100%100%
   MainTemplate.js100%100%100%100%
   ManifestPlugin.js100%100%100%100%
   Module.js98.50%100%100%98.50%1285, 1290, 1350, 1364, 1426, 1435
   ModuleFactory.js100%100%100%100%
   ModuleFilenameHelpers.js98.85%100%100%98.85%106, 108
   ModuleGraph.js99.73%100%100%99.73%1005
   ModuleGraphConnection.js100%100%100%100%
   ModuleInfoHeaderPlugin.js100%100%100%100%
   ModuleNotFoundError.js100%100%100%100%
   ModuleProfile.js100%100%100%100%
   ModuleSourceTypeConstants.js100%100%100%100%
   ModuleTemplate.js100%100%100%100%
   ModuleTypeConstants.js100%100%100%100%
   MultiCompiler.js99.69%100%100%99.69%659
   MultiStats.js100%100%100%100%
   MultiWatching.js100%100%100%100%
   NoEmitOnErrorsPlugin.js100%100%100%100%
   NodeStuffPlugin.js100%100%100%100%
   NormalModule.js97.90%100%100%97.90%1240, 1243, 1260, 1277, 1524, 1558, 1574, 1661, 2017, 2316, 2321–2331, 419, 423, 577
   NormalModuleFactory.js99.47%100%100%99.47%1083, 1392, 486, 498
   NormalModuleReplacementPlugin.js100%100%100%100%
   NullFactory.js100%100%100%100%
   OptimizationStages.js100%100%100%100%
   OptionsApply.js100%100%100%100%
   Parser.js100%100%100%100%
   PlatformPlugin.js100%100%100%100%
   PrefetchPlugin.js100%100%100%100%
   ProgressPlugin.js98.85%100%100%98.85%519–520, 525, 527, 591
   ProvidePlugin.js100%100%100%100%
   RawModule.js100%100%100%100%
   RecordIdsPlugin.js100%100%100%100%
   RequestShortener.js100%100%100%100%
   ResolverFactory.js100%100%100%100%
   RuntimeGlobals.js100%100%100%100%
   RuntimeModule.js100%100%100%100%
   RuntimePlugin.js100%100%100%100%
   RuntimeTemplate.js100%100%100%100%
   SelfModuleFactory.js100%100%100%100%
   SingleEntryPlugin.js100%100%100%100%
   SourceMapDevToolModuleOptionsPlugin.js100%100%100%100%
   SourceMapDevToolPlugin.js98.62%100%100%98.62%220, 224, 226, 419, 430, 891
   Stats.js100%100%100%100%
   Template.js100%100%100%100%
   TemplatedPathPlugin.js99.17%100%100%99.17%176–177
   UseStrictPlugin.js100%100%100%100%
   WarnCaseSensitiveModulesPlugin.js100%100%100%100%
   WarnDeprecatedOptionPlugin.js100%100%100%100%
   WarnNoModeSetPlugin.js100%100%100%100%
   WatchIgnorePlugin.js100%100%100%100%
   Watching.js100%100%100%100%
   WebpackError.js100%100%100%100%
   WebpackIsIncludedPlugin.js100%100%100%100%
   WebpackOptionsApply.js100%100%100%100%
   WebpackOptionsDefaulter.js100%100%100%100%
   buildChunkGraph.js99.87%100%100%99.87%371
   cli.js98.62%100%100%98.62%10, 119, 545, 577, 627, 897
   index.js99.72%100%100%99.72%184
   validateSchema.js94.67%100%100%94.67%100, 87, 89, 98
   webpack.js96.33%100%100%96.33%10, 198, 220, 222
lib/asset
   AssetBytesGenerator.js100%100%100%100%
   AssetBytesParser.js100%100%100%100%
   AssetGenerator.js100%100%100%100%
   AssetModule.js100%100%100%100%
   AssetModulesPlugin.js97.32%100%100%97.32%281, 305, 308, 36, 360, 41
   AssetParser.js100%100%100%100%
   AssetSourceGenerator.js100%100%100%100%
   AssetSourceParser.js100%100%100%100%
   RawDataUrlModule.js100%100%100%100%
lib/async-modules
   AsyncModuleHelpers.js100%100%100%100%
   AwaitDependenciesInitFragment.js100%100%100%100%
   InferAsyncModulesPlugin.js100%100%100%100%
lib/cache
   AddBuildDependenciesPlugin.js100%100%100%100%
   AddManagedPathsPlugin.js100%100%100%100%
   IdleFileCachePlugin.js97.92%100%100%97.92%71, 83, 91
   MemoryCachePlugin.js95.83%100%100%95.83%33
   MemoryWithGcCachePlugin.js93.15%100%100%93.15%106, 113–114, 122, 89
   PackFileCacheStrategy.js96.40%100%100%96.40%1250, 1350, 1354, 1416, 628, 647, 657–659, 661, 677–678, 683, 686, 688, 693, 698, 722, 728, 762, 768, 774, 779, 790, 799, 804–805, 807, 824, 830–831, 833
   ResolverCachePlugin.js100%100%100%100%
   getLazyHashedEtag.js100%100%100%100%
   mergeEtags.js100%100%100%100%
lib/config
   browserslistTargetHandler.js100%100%100%100%
   defaults.js99.31%100%100%99.31%1439–1441, 1449, 274, 277, 282, 286
   defineConfig.js100%100%100%100%
   normalization.js99.01%100%100%99.01%191–192, 258, 273
   target.js100%100%100%100%
lib/container
   

@codecov

codecov Bot commented Jun 18, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 95.83333% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 92.74%. Comparing base (671eb14) to head (4308dcb).
⚠️ Report is 7 commits behind head on main.

Files with missing lines Patch % Lines
lib/Dependency.js 66.66% 2 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main   #21213      +/-   ##
==========================================
+ Coverage   92.73%   92.74%   +0.01%     
==========================================
  Files         590      591       +1     
  Lines       64381    64362      -19     
  Branches    17858    17881      +23     
==========================================
- Hits        59701    59695       -6     
+ Misses       4680     4667      -13     
Flag Coverage Δ
css-parsing 28.69% <22.91%> (+0.02%) ⬆️
html5lib 31.15% <22.91%> (+0.03%) ⬆️
integration 88.70% <95.83%> (-0.01%) ⬇️
test262 45.46% <22.91%> (+0.04%) ⬆️
unit 41.08% <22.91%> (+0.12%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@codspeed-hq

codspeed-hq Bot commented Jun 18, 2026

Copy link
Copy Markdown

Merging this PR will degrade performance by 35.42%

⚠️ Different runtime environments detected

Some benchmarks with significant performance changes were compared across different runtime environments,
which may affect the accuracy of the results.

Open the report in CodSpeed to investigate

⚡ 4 improved benchmarks
❌ 4 regressed benchmarks
✅ 136 untouched benchmarks

Warning

Please fix the performance issues or acknowledge them on CodSpeed.

Performance Changes

Mode Benchmark BASE HEAD Efficiency
Memory benchmark "lodash", scenario '{"name":"mode-development-rebuild","mode":"development","watch":true}' 125.1 KB 858.5 KB -85.43%
Memory benchmark "asset-modules-inline", scenario '{"name":"mode-development-rebuild","mode":"development","watch":true}' 327 KB 1,295.9 KB -74.77%
Memory benchmark "wasm-modules-sync", scenario '{"name":"mode-development-rebuild","mode":"development","watch":true}' 129.3 KB 357 KB -63.78%
Memory benchmark "many-chunks-commonjs", scenario '{"name":"mode-production","mode":"production"}' 7 MB 10.4 MB -32.44%
Memory benchmark "side-effects-reexport", scenario '{"name":"mode-development","mode":"development"}' 914.7 KB 574.9 KB +59.11%
Memory benchmark "many-modules-esm", scenario '{"name":"mode-production","mode":"production"}' 9.9 MB 7.4 MB +33.17%
Memory benchmark "asset-modules-bytes", scenario '{"name":"mode-development-rebuild","mode":"development","watch":true}' 321.2 KB 248.2 KB +29.38%
Memory benchmark "many-chunks-esm", scenario '{"name":"mode-production","mode":"production"}' 9.1 MB 7.4 MB +22.64%

Tip

Investigate this regression by commenting @codspeedbot fix this regression on this PR, or directly use the CodSpeed MCP with your agent.


Comparing claude/pr-21165-perf-memory-x1iq5b (4308dcb) with main (2b7d64e)

Open in CodSpeed

@alexander-akait alexander-akait merged commit 03063b8 into main Jun 18, 2026
67 checks passed
@alexander-akait alexander-akait deleted the claude/pr-21165-perf-memory-x1iq5b branch June 18, 2026 12:41
Comment thread lib/LazyBarrel.js
if (state !== undefined) modules.delete(module);
return false;
}
// terminals only matter together with a star re-export (to avoid building it

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should always add addTerminal when lazyUntil === LAZY_UNTIL_LOCAL . The terminalId tells the importing module that the imported module already has a default export (which is terminal), so when forwardedIds: "default", there's no need to request other dependencies.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@hai-x hm, looks like claude bug, can you send a PR?

@hai-x hai-x Jun 19, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry for the misleading, i just understand this purpose and logic. This seems to be to avoid creating unnecessary terminalId and the logic is also correct. We already have the covered test cases -> test/configCases/lazy-barrel/basic/mixed-barrel.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants