Skip to content

feat: defer building unused reexport targets of barrel modules#21165

Merged
alexander-akait merged 4 commits into
mainfrom
feat/lazy-barrel
Jun 17, 2026
Merged

feat: defer building unused reexport targets of barrel modules#21165
alexander-akait merged 4 commits into
mainfrom
feat/lazy-barrel

Conversation

@hai-x

@hai-x hai-x commented Jun 10, 2026

Copy link
Copy Markdown
Member

Summary

What kind of change does this PR introduce?

This is a port of rspack's lazy barrel optimization (RFC discussion, docs).

When importing from a side-effect-free barrel file, we defers factorizing/resolving/building the re-export target modules until an importer actually requests those export names, so unused re-export targets are never built, it can significantly speeding up builds that import a few names from large barrels (e.g. component libraries).

The local benchmark on a M4 MacBook Pro:

package             import     module counts without lazybarrel  module counts with lazybarrel  modules  time without lazybarrel  time with lazybarrel  speedup
------------------  ---------  --------------------------------  -----------------------------  -------  -----------------------  --------------------  -------
antd                Button                                 1449                            310     -79%                    541ms                 126ms    4.29x
lucide-react        Camera                                 1730                             15     -99%                    205ms                  23ms    8.91x
material-ui-core    Button                                  489                            137     -72%                    135ms                  47ms    2.87x
material-ui-icons   Menu                                   5712                            124     -98%                    655ms                  63ms   10.40x
react-use           useToggle                               146                              6     -96%                     31ms                   6ms    5.17x
recharts            LineChart                               565                            320     -43%                    155ms                  89ms    1.74x
rxjs                map                                     226                             19     -92%                     36ms                   8ms    4.50x
tabler-icons-react  IconHome                               6155                              8    -100%                    671ms                  34ms   19.74x

Did you add tests for your changes?

Yes

Does this PR introduce a breaking change?

No

If relevant, what needs to be documented once your changes are merged or what

Document experiments.lazyBarrel on the Experiments page: requires sideEffectodule.rules[].sideEffects), ESM only, and skipped modules never resolve/build so their resolve/loader errors and "export not found" warnings may not surface.

Use of AI

Partial

@changeset-bot

changeset-bot Bot commented Jun 10, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 755fe7d

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

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

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

@hai-x hai-x force-pushed the feat/lazy-barrel branch from 59c6a2e to 7be0e66 Compare June 10, 2026 18:43
@github-actions

github-actions Bot commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

This PR is packaged and the instant preview is available (671eb14).

Install it locally:

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

@codecov

codecov Bot commented Jun 10, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 97.50000% with 5 lines in your changes missing coverage. Please review.
✅ Project coverage is 92.72%. Comparing base (877f3dc) to head (755fe7d).
⚠️ Report is 7 commits behind head on main.

Files with missing lines Patch % Lines
lib/Compilation.js 91.42% 3 Missing ⚠️
lib/Dependency.js 90.90% 1 Missing ⚠️
lib/LazyBarrel.js 99.18% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main   #21165      +/-   ##
==========================================
+ Coverage   92.70%   92.72%   +0.01%     
==========================================
  Files         588      589       +1     
  Lines       64091    64335     +244     
  Branches    17785    17843      +58     
==========================================
+ Hits        59416    59652     +236     
- Misses       4675     4683       +8     
Flag Coverage Δ
css-parsing 28.66% <17.70%> (-0.04%) ⬇️
html5lib 31.12% <17.70%> (-0.08%) ⬇️
integration 88.70% <97.50%> (+0.02%) ⬆️
test262 45.43% <19.27%> (-0.11%) ⬇️
unit 40.96% <18.75%> (-0.07%) ⬇️

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.

@hai-x hai-x force-pushed the feat/lazy-barrel branch from 7be0e66 to 6a7d026 Compare June 10, 2026 18:48
@codspeed-hq

codspeed-hq Bot commented Jun 10, 2026

Copy link
Copy Markdown

Merging this PR will improve performance by ×3.4

⚠️ 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

⚡ 10 improved benchmarks
❌ 1 regressed benchmark
✅ 133 untouched benchmarks

Warning

Please fix the performance issues or acknowledge them on CodSpeed.

Performance Changes

Mode Benchmark BASE HEAD Efficiency
Memory benchmark "many-modules-commonjs", scenario '{"name":"mode-development-rebuild","mode":"development","watch":true}' 792.3 KB 1,193.1 KB -33.6%
Simulation benchmark "side-effects-reexport", scenario '{"name":"mode-development-rebuild","mode":"development","watch":true}' 504.5 ms 24.2 ms ×21
Simulation benchmark "side-effects-reexport", scenario '{"name":"mode-development","mode":"development"}' 2,035.4 ms 100.8 ms ×20
Simulation benchmark "side-effects-reexport", scenario '{"name":"mode-production","mode":"production"}' 1,965.9 ms 273.3 ms ×7.2
Memory benchmark "wasm-modules-sync", scenario '{"name":"mode-development-rebuild","mode":"development","watch":true}' 789.9 KB 131.8 KB ×6
Memory benchmark "side-effects-reexport", scenario '{"name":"mode-development","mode":"development"}' 4,455.4 KB 914.5 KB ×4.9
Memory benchmark "asset-modules-bytes", scenario '{"name":"mode-development-rebuild","mode":"development","watch":true}' 859.2 KB 321 KB ×2.7
Memory benchmark "wasm-modules-async", scenario '{"name":"mode-development-rebuild","mode":"development","watch":true}' 392.9 KB 195.2 KB ×2
Memory benchmark "many-modules-esm", scenario '{"name":"mode-development-rebuild","mode":"development","watch":true}' 929.2 KB 636.8 KB +45.92%
Memory benchmark "many-modules-commonjs", scenario '{"name":"mode-development","mode":"development"}' 1.8 MB 1.4 MB +26.01%
Memory benchmark "css-modules", scenario '{"name":"mode-production","mode":"production"}' 9 MB 7.2 MB +25.36%

Tip

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


Comparing feat/lazy-barrel (755fe7d) with main (7552543)

Open in CodSpeed

@hai-x hai-x force-pushed the feat/lazy-barrel branch from 6a7d026 to f264eb3 Compare June 14, 2026 19:51
@hai-x hai-x marked this pull request as ready for review June 14, 2026 20:02
@hai-x hai-x force-pushed the feat/lazy-barrel branch 4 times, most recently from 80ebe1b to c84dd73 Compare June 15, 2026 10:26
@hai-x hai-x changed the title feat: add experiments.lazyBarrel to skip building unused module feat: defer building unused reexport targets of barrel modules Jun 15, 2026
@hai-x hai-x force-pushed the feat/lazy-barrel branch 4 times, most recently from 4125aff to 4c1ef43 Compare June 15, 2026 16:18
@hai-x hai-x force-pushed the feat/lazy-barrel branch from 4c1ef43 to e16e0dd Compare June 16, 2026 08:04
Comment thread lib/dll/DllPlugin.js
new LibManifestPlugin({ ...this.options, entryOnly }).apply(compiler);
if (!entryOnly) {
new FlagAllModulesAsUsedPlugin(PLUGIN_NAME).apply(compiler);
compiler.hooks.compilation.tap(

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.

Set sideEffectFree in the earlier factorize stage to ensure it is available when processing the barrel module.

"a.js",
"index.js",
]);
expect(globValueModuleLog).toEqual(["index.js"]);

@hai-x hai-x Jun 16, 2026

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.

In barrel module, we will skip building the target module which isn't side-effect-free too. The behavior is aligned with rspack.

For example

// index.js
import { c } from "./barrel.js"

// barrel.js
import { b } from "./b" // We skip building `./b.js`, even if it has side effects.
export * from "./a"
export { c } from "./c"

[inactive] harmony export imported specifier ./c ./node_modules/pmodule/b.js 5:0-24
harmony import specifier pmodule ./index.js 3:17-18 (skipped side-effect-free modules)
[inactive] harmony export imported specifier ./b ./node_modules/pmodule/index.js 2:0-30 (skipped side-effect-free modules)
./node_modules/pmodule/a.js X bytes [orphan] [built]

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.

./node_modules/pmodule/a.js will be skipped.

modules by path ./components/src/ X bytes
orphan modules X bytes [orphan]
modules by path ./components/src/CompAB/*.js X bytes 2 modules
modules by path ./components/src/CompC/*.js X bytes 2 modules

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.

./components/src/CompC/*.js will be skipped.

@hai-x hai-x force-pushed the feat/lazy-barrel branch from 26397f2 to dca0b68 Compare June 17, 2026 08:43
@github-actions

Copy link
Copy Markdown
Contributor

Types Coverage

Coverage after merging feat/lazy-barrel into main will be
99.34%
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, 4093, 4123, 4176–4177, 4181, 4186, 4202–4203, 4217–4218, 4223–4224, 4701, 4727, 514, 519, 5535, 5567, 5584, 5600, 5616, 5631, 5656–5657, 5659, 5987, 5992, 5998, 6001, 6013, 6015, 6019, 6035, 6050, 6082, 6136, 6160, 6274, 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.28%100%100%98.28%425, 471
   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.67%100%100%98.67%1062, 1065, 448–452, 600
   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%179
   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
   normalization.js99.01%100%100%99.01%191–192, 258, 273
   target.js100%100%100%100%
lib/container
   ContainerEntryDependency.js100%100%100%100%
   ContainerEntryModule.

@alexander-akait alexander-akait merged commit 671eb14 into main Jun 17, 2026
66 checks passed
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