Skip to content

fix: spec-compliant defer namespace [[Set]] and identity#20913

Merged
alexander-akait merged 5 commits into
mainfrom
claude/fix-defer-tests-ZGm5c
May 5, 2026
Merged

fix: spec-compliant defer namespace [[Set]] and identity#20913
alexander-akait merged 5 commits into
mainfrom
claude/fix-defer-tests-ZGm5c

Conversation

@alexander-akait
Copy link
Copy Markdown
Member

ns.foo = value previously generated <importVar>.a.foo = value
whose .a getter eagerly required the deferred module, violating
the TC39 import-defer [[Set]] algorithm. Tap assignMemberChain
for harmonySpecifierTag to walk only the bare ns identifier so
it resolves to the deferred-namespace proxy (whose set trap
returns false without triggering evaluation), and leave the
.foo = value assignment as plain code.

Cache the deferred-namespace proxy per-moduleId in a new global
__webpack_module_deferred_namespace_cache__, and stop the
__webpack_require__.z cached-module fast path from short-circuiting
to the underlying exports for namespace-mode imports. This keeps
the deferred and eager namespaces as distinct objects (per spec)
and shares one Deferred Module Namespace Exotic Object across all
defer-import call sites for the same module.

`ns.foo = value` previously generated `<importVar>.a.foo = value`
whose `.a` getter eagerly required the deferred module, violating
the TC39 import-defer `[[Set]]` algorithm. Tap `assignMemberChain`
for `harmonySpecifierTag` to walk only the bare `ns` identifier so
it resolves to the deferred-namespace proxy (whose `set` trap
returns false without triggering evaluation), and leave the
`.foo = value` assignment as plain code.

Cache the deferred-namespace proxy per-`moduleId` in a new global
`__webpack_module_deferred_namespace_cache__`, and stop the
`__webpack_require__.z` cached-module fast path from short-circuiting
to the underlying exports for namespace-mode imports. This keeps
the deferred and eager namespaces as distinct objects (per spec)
and shares one Deferred Module Namespace Exotic Object across all
defer-import call sites for the same module.
Copilot AI review requested due to automatic review settings May 5, 2026 15:19
@linux-foundation-easycla
Copy link
Copy Markdown

linux-foundation-easycla Bot commented May 5, 2026

CLA Not Signed

@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented May 5, 2026

🦋 Changeset detected

Latest commit: 80cf471

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
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 5, 2026

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

Install it locally:

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

@codecov
Copy link
Copy Markdown

codecov Bot commented May 5, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 91.37%. Comparing base (b6f54d9) to head (80cf471).
⚠️ Report is 3 commits behind head on main.

Additional details and impacted files
@@           Coverage Diff           @@
##             main   #20913   +/-   ##
=======================================
  Coverage   91.37%   91.37%           
=======================================
  Files         563      563           
  Lines       55822    55868   +46     
  Branches    14788    14814   +26     
=======================================
+ Hits        51006    51049   +43     
- Misses       4816     4819    +3     
Flag Coverage Δ
integration 90.32% <100.00%> (+<0.01%) ⬆️
test262 45.86% <100.00%> (-0.02%) ⬇️
unit 36.14% <7.69%> (-0.04%) ⬇️

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

☔ View full report in Codecov by Sentry.
📢 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.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR improves webpack’s import defer * as ns behavior to better align with the TC39 import-defer proposal, particularly around (1) not triggering evaluation on ns.x = value, and (2) deferred-namespace identity being stable and distinct from eager namespaces.

Changes:

  • Adjust parsing of simple ns.foo = value assignments so webpack doesn’t rewrite them via the eager-evaluating .a getter path.
  • Add a new runtime-level deferred-namespace cache to share a single Deferred Module Namespace proxy per module across defer-import call sites.
  • Update tests/known-skips to reflect the improved spec compliance (with production-concatenation still listed as problematic for identity).

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
test/test262.spectest.js Unskips defer [[Set]] / identity tests where fixed; keeps identity skipped for production concatenation.
test/configCases/defer-import/same-module/index.js Updates expectation: eager and deferred namespaces for same module must not be the same object.
lib/runtime/MakeDeferredNamespaceObjectRuntime.js Caches deferred namespace proxy per module and avoids returning underlying exports for mode & 8.
lib/javascript/JavascriptModulesPlugin.js Emits new __webpack_module_deferred_namespace_cache__ runtime global when needed.
lib/dependencies/HarmonyImportDependencyParserPlugin.js Special-cases ns.foo = value for defer imports to avoid eager evaluation via .a.
.changeset/defer-import-spec-compliant-set-and-identity.md Adds a patch changeset describing the behavior change.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 129 to 133
"var cached = __webpack_module_deferred_namespace_cache__[moduleId];",
"if (cached !== undefined) return cached;",
"var cachedModule = __webpack_module_cache__[moduleId];",
"if (cachedModule && cachedModule.error === undefined) {",
"if (cachedModule && cachedModule.error === undefined && !(mode & 8)) {",
Template.indent([
Address Copilot's review on #20913: key the deferred-namespace cache
by `(moduleId, shapeMode)` instead of just `moduleId`. `shapeMode` is
`mode & ~16` so the irrelevant `createFakeNamespaceObject` async-wrap
bit is normalized out — static defer (mode 8) and dynamic
`await import.defer` (mode 8 | 16) for the same module still share
the same Deferred Module Namespace object — but distinct
exports-type shapes (e.g. one importer treats a CJS module as
"default-with-named", another as "namespace") get distinct cache
entries instead of returning the wrong shape.

Add config tests:
- `identity-across-call-sites`: same module imported defer multiple
  times, plus static defer + dynamic `import.defer`, must yield the
  same object; defer must stay distinct from eager.
- `set-does-not-trigger-evaluation`: assigning to a deferred
  namespace property (exported or not) throws TypeError in strict
  mode without triggering module evaluation; subsequent property
  read still evaluates.
@codspeed-hq
Copy link
Copy Markdown

codspeed-hq Bot commented May 5, 2026

Merging this PR will degrade performance by 55.16%

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

⚡ 7 improved benchmarks
❌ 4 regressed benchmarks
✅ 133 untouched benchmarks

⚠️ 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}' 847 KB 1,543.8 KB -45.13%
Memory benchmark "devtool-eval-source-map", scenario '{"name":"mode-development","mode":"development"}' 1,587.6 KB 1,008.8 KB +57.38%
Memory benchmark "concatenate-modules", scenario '{"name":"mode-development","mode":"development"}' 752 KB 554.7 KB +35.58%
Memory benchmark "react", scenario '{"name":"mode-development-rebuild","mode":"development","watch":true}' 238.6 KB 171 KB +39.52%
Memory benchmark "three-long", scenario '{"name":"mode-development-rebuild","mode":"development","watch":true}' 1,143.6 KB 856.7 KB +33.49%
Memory benchmark "concatenate-modules", scenario '{"name":"mode-development-rebuild","mode":"development","watch":true}' 298.6 KB 181.7 KB +64.31%
Memory benchmark "many-modules-commonjs", scenario '{"name":"mode-development","mode":"development"}' 833.7 KB 1,140.4 KB -26.9%
Memory benchmark "context-commonjs", scenario '{"name":"mode-development-rebuild","mode":"development","watch":true}' 133.3 KB 297.3 KB -55.16%
Memory benchmark "asset-modules-source", scenario '{"name":"mode-development-rebuild","mode":"development","watch":true}' 368.3 KB 256.2 KB +43.75%
Memory benchmark "many-chunks-commonjs", scenario '{"name":"mode-production","mode":"production"}' 7.3 MB 9.6 MB -23.92%
Memory benchmark "devtool-eval", scenario '{"name":"mode-development","mode":"development"}' 844 KB 599 KB +40.9%

Comparing claude/fix-defer-tests-ZGm5c (80cf471) with main (b34c597)

Open in CodSpeed

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 13 out of 13 changed files in this pull request and generated 2 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +140 to +143
"var shapeMode = mode & ~16;",
"var byMode = __webpack_module_deferred_namespace_cache__[moduleId];",
"if (byMode && byMode[shapeMode] !== undefined) return byMode[shapeMode];",
"if (!byMode) byMode = __webpack_module_deferred_namespace_cache__[moduleId] = {};",
: "",
"if (mode & 8) return exports;",
`return ${RuntimeGlobals.createFakeNamespaceObject}(exports, mode);`
`return byMode[shapeMode] = ${RuntimeGlobals.createFakeNamespaceObject}(exports, mode);`
Address Copilot follow-up on #20913: the cache key was masking off
bit 16 (`shapeMode = mode & ~16`) but the original `mode` was still
being passed to `createFakeNamespaceObject`. Whichever call site ran
first would decide whether the cached entry had `createFakeNamespace
Object`'s "return value when it's Promise-like" short-circuit
applied, so static `import defer` and dynamic `await import.defer`
could observe different wrapping semantics for the same cache slot.

Strip bit 16 once at the top of the runtime helper so the cache
key, the gate that decides between the cached-module fast path and
the lazy Proxy, and the value passed to `createFakeNamespaceObject`
all use the same `mode`. The bit is irrelevant in this runtime
helper anyway — the value passed into `createFakeNamespaceObject`
here is always the resolved module exports (after unwrapping the
async-module export symbol when present), never a Promise.
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 13 out of 13 changed files in this pull request and generated 1 comment.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread lib/runtime/MakeDeferredNamespaceObjectRuntime.js
Address Copilot follow-up on #20913: now that the deferred-namespace
Proxy is cached globally, mutations to its handler at init time
persist across all defer-import call sites for the lifetime of the
bundle. The previous post-init optimization deleted *all* handler
traps, including `set`, `deleteProperty`, and `defineProperty`,
which caused `ns.notExported = "x"` to silently mutate the proxy
target after the first read had triggered evaluation — `[[Set]]` on
a Module Namespace Exotic Object must always return false per the
TC39 import-defer spec.

Keep the three mutation traps and only drop the read-side traps
(`get`, `has`, `ownKeys`, `getOwnPropertyDescriptor`); with the
resolved namespace's own keys mirrored onto `ns_target`, the
default `Reflect` behavior already returns the right values via
the live-binding getters.

Add a `set-does-not-trigger-evaluation` regression test asserting
that strict-mode assignment to both an exported and a non-exported
key still throws TypeError after the deferred module has been
evaluated, and that no spurious property is created on the target.
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 13 out of 13 changed files in this pull request and generated no new comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

`set-does-not-trigger-evaluation` reads `globalThis.evaluations`
which is unavailable on Node.js 10, and `identity-across-call-sites`
uses `await import.defer(...)` whose nested async/await pattern
hits the Node.js 10 bug `supportsAsync` already gates against.
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 5, 2026

Types Coverage

Coverage after merging claude/fix-defer-tests-ZGm5c into main will be
98.92%
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.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%37
   ChunkGraph.js100%100%100%100%
   ChunkGroup.js100%100%100%100%
   ChunkTemplate.js100%100%100%100%
   CleanPlugin.js98.72%100%100%98.72%206, 226, 382
   CodeGenerationResults.js100%100%100%100%
   CompatibilityPlugin.js100%100%100%100%
   Compilation.js98.55%100%100%98.55%1554, 1850, 1857, 1865, 1887, 2783, 3208, 3870, 3899, 3952–3953, 3957, 3962, 3978–3979, 3993–3994, 3999–4000, 4477, 4503, 493, 498, 5211, 5292, 5307, 5332–5333, 5335, 5659, 5664, 5670, 5673, 5685, 5687, 5691, 5707, 5722, 5754, 5808, 5832, 5946, 712–713
   Compiler.js99.55%100%100%99.55%1116–1117, 1125
   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.75%100%100%97.75%258, 393, 418, 443, 447, 458
   ContextReplacementPlugin.js100%100%100%100%
   DefinePlugin.js98.92%100%100%98.92%158–159, 175, 194, 268
   DependenciesBlock.js100%100%100%100%
   Dependency.js98.20%100%100%98.20%379, 425
   DependencyTemplate.js100%100%100%100%
   DependencyTemplates.js100%100%100%100%
   DotenvPlugin.js97.88%100%100%97.88%237, 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.89%100%100%98.89%399–403, 542
   ExternalModuleFactoryPlugin.js100%100%100%100%
   ExternalsPlugin.js100%100%100%100%
   FileSystemInfo.js99.49%100%100%99.49%182, 2213–2214, 2217, 2228, 2239, 2250, 278, 3596, 3611, 3635
   FlagAllModulesAsUsedPlugin.js100%100%100%100%
   FlagDependencyExportsPlugin.js98.74%100%100%98.74%399, 401, 405
   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%
   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%1304, 1309, 1370, 1384, 1446, 1455
   ModuleFactory.js100%100%100%100%
   ModuleFilenameHelpers.js98.85%100%100%98.85%106, 108
   ModuleGraph.js99.73%100%100%99.73%1004
   ModuleGraphConnection.js100%100%100%100%
   ModuleInfoHeaderPlugin.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%645
   MultiStats.js100%100%100%100%
   MultiWatching.js100%100%100%100%
   NoEmitOnErrorsPlugin.js100%100%100%100%
   NodeStuffPlugin.js100%100%100%100%
   NormalModule.js97.78%100%100%97.78%1020, 1036, 1123, 1774, 1779–1789, 708, 711, 728, 745, 986
   NormalModuleFactory.js99.47%100%100%99.47%1067, 1376, 466, 478
   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.75%100%100%98.75%446–447, 452, 454, 518
   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.js99.16%100%100%99.16%267–268, 610
   Stats.js100%100%100%100%
   Template.js100%100%100%100%
   TemplatedPathPlugin.js98.86%100%100%98.86%134–135
   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%325
   cli.js98.71%100%100%98.71%117, 469, 501, 543, 813
   index.js100%100%100%100%
   validateSchema.js94.67%100%100%94.67%100, 87, 89, 98
   webpack.js97.22%100%100%97.22%196, 218, 220
lib/asset
   AssetBytesGenerator.js100%100%100%100%
   AssetBytesParser.js100%100%100%100%
   AssetGenerator.js100%100%100%100%
   AssetModulesPlugin.js97.77%100%100%97.77%285, 309, 312, 364, 40
   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.13%100%100%99.13%1297–1299, 1307, 270, 273, 278, 282, 468
   normalization.js99%100%100%99%191–192, 258, 273
   target.js100%100%100%100%
lib/container
   ContainerEntryDependency.js100%100%100%100%
   ContainerEntryModule.js100%100%100%100%
   ContainerEntryModuleFactory.js100%100%100%100%
   ContainerExposedDependency.js100%100%100%100%
   ContainerPlugin.js100%100%100%100%
   ContainerReferencePlugin.js100%100%100%100%
   FallbackDependency.js100%100%100%100%
   FallbackItemDependency.js100%100%100%100%
   FallbackModule.js100%100%100%100%
   FallbackModuleFactory.js100%100%100%100%
   HoistContainerReferencesPlugin.js100%100%100%100%
   ModuleFederationPlugin.js100%100%100%100%
   RemoteModule.js100%100%100%100%
   RemoteRuntimeModule.js100%100%100%100%
   

@alexander-akait alexander-akait merged commit 2fd88dd into main May 5, 2026
59 of 61 checks passed
@alexander-akait alexander-akait deleted the claude/fix-defer-tests-ZGm5c branch May 5, 2026 18:33
aryanraj45 pushed a commit to aryanraj45/webpack that referenced this pull request May 8, 2026
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.

3 participants