feat: experimental module splitting for async-only exports#21462
feat: experimental module splitting for async-only exports#21462alexander-akait wants to merge 2 commits intomainwebpack/webpack:mainfrom claude/issue-20537-investigation-noT6Jwebpack/webpack:claude/issue-20537-investigation-noT6JCopy head branch name to clipboard
Conversation
🦋 Changeset detectedLatest commit: 83d4a9e 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 (83d4a9e). Install it locally:
npm i -D webpack@https://pkg.pr.new/webpack@83d4a9e
yarn add -D webpack@https://pkg.pr.new/webpack@83d4a9e
pnpm add -D webpack@https://pkg.pr.new/webpack@83d4a9e |
| .getExportsInfo(entry.sourceModule) | ||
| .getUsedName(entry.sourceName, runtime) || entry.sourceName; | ||
| const access = `${varName}[${JSON.stringify(used)}]`; | ||
| definitions.push(`${JSON.stringify(entry.exportName)}: () => ${access}`); |
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #21462 +/- ##
==========================================
+ Coverage 93.45% 93.50% +0.05%
==========================================
Files 614 617 +3
Lines 72417 72976 +559
Branches 20780 20941 +161
==========================================
+ Hits 67678 68239 +561
+ Misses 4739 4737 -2
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:
|
Add experiments.moduleSplitting (off by default): move self-contained,
side-effect-free exports that are only reached through an async boundary out of
the initial chunk into synthetic part-modules, so they follow the async chunk
instead of inflating the initial one.
- Named and `export default` exports; exports consumed via dynamic import() /
import * behind a synthetic namespace facade; barrel re-exports resolved to
their origin via ExportInfo.getTarget.
- Turbopack-style gate: only split modules proven side-effect-free (by the
`sideEffects` declaration or source analysis). Override which modules split via
experiments.moduleSplitting { include, exclude }, a `module.rules`
`moduleSplitting` flag, or a `/* webpackSplit */` source hint.
- Reuses JavascriptParser.isPure (no re-parse); the plugin is registered only
when the experiment is enabled, so non-users pay nothing.
Covered by test/configCases/module-splitting/* (12 cases).
4aa7165 to
d250030
Compare
Merging this PR will regress 1 benchmark
Warning Please fix the performance issues or acknowledge them on CodSpeed. Performance Changes
Tip Investigate this regression by commenting Comparing |
Fix a Node 10 incompatibility in the sideeffect-source fixture (globalThis is undefined there) by using a plain statement-level side effect, and add coverage for the split analyzer's AST-shape branches (ast-shapes fixture) and the synthetic part/facade modules' serialize and needBuild paths (ModuleSplitting.unittest.js).
541b54e to
83d4a9e
Compare
Types CoverageCoverage after merging claude/issue-20537-investigation-noT6J into main will be
Coverage Report |
Summary
A module is the atomic unit of chunk placement and export usage is tracked per-runtime, so an export reached only through an async boundary still lands in the initial chunk (Refs #20537). This adds an opt-in
experiments.moduleSplitting(off by default) that moves self-contained, side-effect-free exports into synthetic part-modules — and, for namespace consumers (import()/import *), behind a synthetic namespace facade — so those exports follow the async chunk instead of inflating the initial one. It also resolves barrel re-exports to their origin viaExportInfo.getTarget. This covers the tractable subset; the full vue-loader reproduction (an assembled, impure component) still needs general statement-level fragmentation and is intentionally left to a follow-up.What kind of change does this PR introduce?
feat
Did you add tests for your changes?
Yes —
test/configCases/module-splitting/*(12 cases): named/default export split, dynamic-import andimport *namespace facades, barrel re-export, thesideEffects/source-analysis gate, theinclude/exclude+module.rulesflag +/* webpackSplit */controls, andlet/live-binding safety.Does this PR introduce a breaking change?
No. Everything is behind
experiments.moduleSplitting(off by default); when disabled the plugin is never registered, so there is no cost or behavior change for existing builds.If relevant, what needs to be documented once your changes are merged or what have you already documented?
The new
experiments.moduleSplittingoption, its Turbopack-stylesideEffectsgate, and the controls for which modules split ({ include, exclude }, themodule.rulesmoduleSplittingflag, and the/* webpackSplit */source hint).Use of AI
Yes. This change was written with Claude (Claude Code); the design, implementation, and tests were AI-assisted and human-directed and reviewed. It reuses existing webpack machinery (
JavascriptParser.isPure,ExportInfo.getTarget,ModuleGraphconnection APIs) rather than introducing new analysis, and is gated behind an experiment. Per the webpack AI policy, flagging it explicitly.Generated by Claude Code