Skip to content

feat: keep export mangling for modules whose namespace object escapes#21234

Merged
alexander-akait merged 2 commits into
mainfrom
claude/webpack-pr-19219-4mh8ue
Jun 24, 2026
Merged

feat: keep export mangling for modules whose namespace object escapes#21234
alexander-akait merged 2 commits into
mainfrom
claude/webpack-pr-19219-4mh8ue

Conversation

@alexander-akait

@alexander-akait alexander-akait commented Jun 20, 2026

Copy link
Copy Markdown
Member

Summary

When a module's namespace object is used as a whole value (for example re-exported and returned, spread, or passed around), export mangling was disabled for the entire module, so every reference in all other consumers kept full export names — bloating the output.

This PR keeps such modules mangleable: the escaping reference is rendered as a decoupled namespace object whose getters expose the original export names onto the mangled bindings, so access by the original name (including dynamic/Object.keys) keeps working. It is driven by optimization.mangleExports and works for both non-concatenated and concatenated builds (inner, external/standalone, and code-split shared modules). CommonJS interop, dynamic imports and deferred imports are left conservative (unchanged). Closes #19153.

On a barrel-with-escape build (1 escaping module, 300 exports, 150 static consumers, minified) the output drops from 38.4 KB → 17.2 KB raw (−55%) / 5.3 KB → 4.3 KB gzip (−19%), with no build-time or peak-memory regression.

What kind of change does this PR introduce?

feat

Did you add tests for your changes?

Yes — new test/configCases/mangle/mangle-escaping-namespace and …-concat cases covering ESM named/default exports, export * as re-exports, CommonJS interop, destructuring of an escaping module, dynamically imported namespaces, inlined-const exports, namespace enumeration, and delete ns.member on an escaping namespace; plus updated mangle-with-destructuring-assignment and code-generation/re-export-namespace-concat to the new (runtime-identical) output. Verified against the test262 module-namespace [[Delete]] cases.

Does this PR introduce a breaking change?

No. Runtime semantics are unchanged (verified across all the contexts above); only the generated output is smaller because more exports get mangled.

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

n/a — no new option; optimization.mangleExports now also applies to modules whose namespace object escapes.

Use of AI

AI (Claude Code) was used to implement the change, write the tests, run the test suite, measure output size / build time / memory, and investigate edge cases across rendering contexts. All output was reviewed and validated against the webpack test suite.


Generated by Claude Code

@changeset-bot

changeset-bot Bot commented Jun 20, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 29f6ab1

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

@github-actions

github-actions Bot commented Jun 20, 2026

Copy link
Copy Markdown
Contributor

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

Install it locally:

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

@codecov

codecov Bot commented Jun 20, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 96.59864% with 5 lines in your changes missing coverage. Please review.
✅ Project coverage is 92.81%. Comparing base (e8f9334) to head (29f6ab1).

Files with missing lines Patch % Lines
lib/RuntimeTemplate.js 90.00% 3 Missing ⚠️
lib/optimize/ConcatenatedModule.js 97.14% 2 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main   #21234      +/-   ##
==========================================
- Coverage   92.82%   92.81%   -0.01%     
==========================================
  Files         592      592              
  Lines       64829    64964     +135     
  Branches    18067    18135      +68     
==========================================
+ Hits        60175    60299     +124     
- Misses       4654     4665      +11     
Flag Coverage Δ
css-parsing 28.64% <6.12%> (-0.07%) ⬇️
html5lib 31.10% <6.12%> (-0.07%) ⬇️
integration 88.98% <96.59%> (+<0.01%) ⬆️
test262 45.58% <91.83%> (+0.17%) ⬆️
unit 41.39% <6.12%> (-0.09%) ⬇️

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.

@alexander-akait alexander-akait force-pushed the claude/webpack-pr-19219-4mh8ue branch from 2d6c314 to 8488e61 Compare June 20, 2026 21:40
@codspeed-hq

codspeed-hq Bot commented Jun 20, 2026

Copy link
Copy Markdown

Merging this PR will not alter performance

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

⚡ 3 improved benchmarks
❌ 5 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 "side-effects-reexport", scenario '{"name":"mode-development-rebuild","mode":"development","watch":true}' 130.2 KB 859.1 KB -84.84%
Memory benchmark "asset-modules-inline", scenario '{"name":"mode-development-rebuild","mode":"development","watch":true}' 383.8 KB 1,200.5 KB -68.03%
Memory benchmark "many-modules-esm", scenario '{"name":"mode-development","mode":"development"}' 1.2 MB 2 MB -40.9%
Memory benchmark "wasm-modules-async", scenario '{"name":"mode-development-rebuild","mode":"development","watch":true}' 189.1 KB 247.3 KB -23.55%
Memory benchmark "many-modules-esm", scenario '{"name":"mode-production","mode":"production"}' 7.5 MB 9.6 MB -21.69%
Memory benchmark "lodash", scenario '{"name":"mode-development-rebuild","mode":"development","watch":true}' 851.4 KB 129 KB ×6.6
Memory benchmark "wasm-modules-sync", scenario '{"name":"mode-development-rebuild","mode":"development","watch":true}' 360.3 KB 130.7 KB ×2.8
Memory benchmark "asset-modules-bytes", scenario '{"name":"mode-development-rebuild","mode":"development","watch":true}' 321.3 KB 247.9 KB +29.62%

Tip

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


Comparing claude/webpack-pr-19219-4mh8ue (29f6ab1) with main (ef03f5e)1

Open in CodSpeed

Footnotes

  1. No successful run was found on main (e8f9334) during the generation of this report, so ef03f5e was used instead as the comparison base. There might be some changes unrelated to this pull request in this report.

@alexander-akait alexander-akait force-pushed the claude/webpack-pr-19219-4mh8ue branch from 8488e61 to 933c64a Compare June 20, 2026 21:51
@hai-x

hai-x commented Jun 21, 2026

Copy link
Copy Markdown
Member

LGTM, it's nice improvement for the whole namespace accessing.

@alexander-akait alexander-akait force-pushed the claude/webpack-pr-19219-4mh8ue branch 2 times, most recently from 82fec1b to d962c7c Compare June 24, 2026 08:50
When a module's namespace object is used as a whole value (for example
re-exported and returned, spread, or passed around) export mangling was
disabled for the whole module, bloating every reference in all other
consumers.

Such modules now keep their exports mangleable: the escaping reference is
rendered as a decoupled namespace object that exposes the original export
names via getters onto the mangled bindings, so access by the original name
keeps working. Driven by optimization.mangleExports, for both
non-concatenated and concatenated builds. Left conservative (unchanged) for
CommonJS interop, dynamic imports and deferred imports.

To preserve ES module namespace semantics on the escaping module, its
exports stay non-inlinable and member access keeps a qualified property
access: a missing export reads as `ns.x` (not a bare `undefined`) and
`delete ns.x` hits a real non-configurable binding, so it stays valid and
throws where the spec requires.

Closes #19153.
Clarify the PR-body guidance to use the auto-closing `Closes #…`/`Fixes #…`
form when the PR actually fixes the bug or implements the requested feature,
reserving `Refs #…` for issues a PR only relates to.
@alexander-akait alexander-akait force-pushed the claude/webpack-pr-19219-4mh8ue branch from d962c7c to 29f6ab1 Compare June 24, 2026 10:39
@github-actions

Copy link
Copy Markdown
Contributor

Types Coverage

Coverage after merging claude/webpack-pr-19219-4mh8ue 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/module-federation
   test.filter.js100%100%100%100%
examples/reexport-components
   test.filter.js100%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-emscripten
   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%
   CircularModulesPlugin.js98.81%100%100%98.81%136
   CleanPlugin.js99.15%100%100%99.15%207, 227
   CodeGenerationResults.js100%100%100%100%
   CompatibilityPlugin.js100%100%100%100%
   Compilation.js98.43%100%100%98.43%1634, 1953, 1960, 1968, 1990, 1993, 2933, 3412–3413, 3445, 4111, 4141, 4194–4195, 4199, 4204, 4220–4221, 4235–4236, 4241–4242, 4719, 4745, 519, 524, 5553, 5585, 5602, 5618, 5634, 5649, 5674–5675, 5677, 6005, 6010, 6016, 6019, 6031, 6033, 6037, 6053, 6068, 6100, 6154, 6178, 6292, 770–771
   Compiler.js99.56%100%100%99.56%1147–1148, 1156
   ConcatenationScope.js98.65%100%100%98.65%195
   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.51%100%100%98.51%479, 525
   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%1062, 1065, 450–454, 456, 602
   ExternalModuleFactoryPlugin.js100%100%100%100%
   ExternalsPlugin.js100%100%100%100%
   FileSystemInfo.js99.52%100%100%99.52%182, 2382–2383, 2386, 2397, 2408, 2419, 280, 3823, 3838, 3862
   FlagAllModulesAsUsedPlugin.js100%100%100%100%
   FlagDependencyExportsPlugin.js98.42%100%100%98.42%413, 422, 424, 428
   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%661
   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%1237, 1240, 1257, 1274, 1521, 1555, 1571, 1658, 2014, 2313, 2318–2328, 418, 422, 576
   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%527–528, 533, 535, 599
   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, 889
   Stats.js100%100%100%100%
   Template.js100%100%100%100%
   TemplatedPathPlugin.js99.38%100%100%99.38%295–296
   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.63%100%100%98.63%10, 119, 549, 581, 631, 905
   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%282, 306, 309, 36, 361, 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/bun
   BunTargetPlugin.js100%100%100%100%
lib/cache
   AddBuildDependenciesPlugin.js100%100%100%100%
   AddManagedPathsPlugin.js100%100%100%100%
   IdleFileCachePlugin.js97.92%100%100%97.92%75, 87, 95
   MemoryCachePlugin.js95.83%100%100%95.83%33
   MemoryWithGcCachePlugin.js93.15%100%100%93.15%107, 114–115, 123, 90
   PackFileCacheStrategy.js96.40%100%100%96.40%1251, 1351, 1355, 1417, 628, 647, 657–659, 661, 677–678, 683, 686, 688, 693, 698, 723, 729, 763, 769, 775, 780, 791, 800, 805–806, 808, 825, 831–832, 834
   ResolverCachePlugin.js100%100%100%100%
   getLazyHashedEtag.js100%100%100%100%
   mergeEtags.js100%100%100%100%
lib/config
   browserslistTargetHandler.js100%100%100%100%
   defaults.js99.33%100%100%99.33%1468–1470, 1478, 274,

@alexander-akait alexander-akait merged commit eefaf44 into main Jun 24, 2026
68 checks passed
@alexander-akait alexander-akait deleted the claude/webpack-pr-19219-4mh8ue branch June 24, 2026 12:13
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.

Import/export of module without explicit references to symbols disables export mangling for module

2 participants