Skip to content

fix: force-load a module's new owning chunk when its only loaded chunk leaves a runtime during HMR#21127

Closed
alexander-akait wants to merge 7 commits into
mainfrom
test/hmr-orphan-chunk-migration
Closed

fix: force-load a module's new owning chunk when its only loaded chunk leaves a runtime during HMR#21127
alexander-akait wants to merge 7 commits into
mainfrom
test/hmr-orphan-chunk-migration

Conversation

@alexander-akait

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

Copy link
Copy Markdown
Member

Summary

Resolves the long-standing TODO in lib/HotModuleReplacementPlugin.js ("force load one of the chunks which contains the module"). In a multi-runtime build, when a module's only loaded chunk is removed from a runtime but the module survives there via a not-yet-loaded async chunk, the update removed the chunk (r) without disposing the module (m stayed empty) — leaving it loaded on the client with no installed chunk owning it, so later HMR updates to it were never delivered. The update manifest now carries a new f (force-load) field listing such chunks, and the runtime ensures them so the client keeps an installed owner. If the module no longer lives in the runtime at all, it is still disposed as before.

What kind of change does this PR introduce?

fix

Did you add tests for your changes?

Yes — unit tests in test/HotModuleReplacementPlugin.test.js (force-load, dispose-when-gone, and cross-runtime merge on filename collision) plus hotCases integration tests under test/hotCases/chunks/force-load-migrated-chunk and .../force-load-runtime-collision.

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 Code) was used to investigate the TODO, build the reproduction, implement the fix, and author the tests. All output was reviewed and verified by running the suite locally.

…k migrates runtimes

Encodes the scenario described by the TODO in HotModuleReplacementPlugin
(force load one of the chunks which contains the module): when a module's
only loaded chunk is removed from a runtime but the module survives in that
runtime via a not-yet-loaded async chunk, the update removes the chunk
without disposing the module, leaving it orphaned.
@changeset-bot

changeset-bot Bot commented Jun 8, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 121d021

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 8, 2026

Copy link
Copy Markdown
Contributor

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

Install it locally:

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

@codecov

codecov Bot commented Jun 8, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 72.72727% with 6 lines in your changes missing coverage. Please review.
✅ Project coverage is 92.54%. Comparing base (6d8b131) to head (121d021).
⚠️ Report is 9 commits behind head on main.

Files with missing lines Patch % Lines
lib/HotModuleReplacementPlugin.js 72.72% 6 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main   #21127      +/-   ##
==========================================
+ Coverage   92.00%   92.54%   +0.53%     
==========================================
  Files         581      581              
  Lines       61456    64876    +3420     
  Branches    16795    18541    +1746     
==========================================
+ Hits        56545    60040    +3495     
+ Misses       4911     4836      -75     
Flag Coverage Δ
css-parsing 28.56% <0.00%> (-0.12%) ⬇️
html5lib 32.79% <0.00%> (+4.92%) ⬆️
integration 88.40% <72.72%> (-1.11%) ⬇️
test262 45.23% <0.00%> (-0.06%) ⬇️
unit 42.53% <0.00%> (+2.91%) ⬆️

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 8, 2026

Copy link
Copy Markdown

Merging this PR will degrade performance by 22.74%

❌ 2 regressed benchmarks
✅ 142 untouched benchmarks

Warning

Please fix the performance issues or acknowledge them on CodSpeed.

Performance Changes

Mode Benchmark BASE HEAD Efficiency
Memory benchmark "css-modules", scenario '{"name":"mode-production","mode":"production"}' 7.4 MB 9.8 MB -25.06%
Memory benchmark "devtool-eval-source-map", scenario '{"name":"mode-production","mode":"production"}' 6.5 MB 8.1 MB -20.33%

Tip

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


Comparing test/hmr-orphan-chunk-migration (121d021) with main (d39efba)

Open in CodSpeed

When a module's only loaded chunk is removed from a runtime but the module
still lives there via another (not-yet-loaded) chunk, the hot update removed
the chunk without disposing the module, leaving it orphaned with no installed
chunk owning it — so later HMR updates never reached that runtime.

The plugin now emits the new owning chunk id in a manifest `f` field and the
JS HMR runtime force-loads those chunks during the update. Implements the
long-standing TODO in HotModuleReplacementPlugin.
Comment thread lib/hmr/JavascriptHotModuleReplacement.runtime.js Fixed
Comment thread lib/hmr/JavascriptHotModuleReplacement.runtime.js Fixed
Comment thread lib/hmr/JavascriptHotModuleReplacement.runtime.js Fixed
Comment thread lib/hmr/JavascriptHotModuleReplacement.runtime.js Fixed
…check

Fold the force-load into the existing ensureChunkHandlers guard and rely on
the idempotent ensure handlers (which skip already-installed chunks) instead
of re-checking installedChunks.
Comment thread lib/hmr/JavascriptHotModuleReplacement.runtime.js Fixed
…runtime check

Adds tests for the cross-runtime dispose path and the non-unique
hotUpdateMainFilename merge path (which now also carries force-load chunks),
and collapses the runtime-membership check to a single expression.
Runs the force-load runtime branch end-to-end on the web target: a module
migrates from a shared (multi-runtime) chunk into an async chunk on update,
and the runtime force-loads it so the module stays installed. Gated to the
web target since the node/webworker harness can't resolve the async chunk
file mid-update.
…e collision

Covers the branch that merges force-load chunk ids across runtimes when
output.hotUpdateMainFilename is not unique per runtime: one runtime migrates a
module into an async chunk (producing a force-load chunk) while another changes
differently, so their updates collide and are merged (with the expected
warning). Web target only, like the sibling force-load test.
@alexander-akait alexander-akait changed the title test: guard against HMR orphaning modules when their only loaded chunk migrates runtimes fix: force-load a module's new owning chunk when its only loaded chunk leaves a runtime during HMR Jun 8, 2026
@github-actions

github-actions Bot commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

Types Coverage

Coverage after merging test/hmr-orphan-chunk-migration into main will be
98.99%
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.49%100%100%98.49%1577, 1873, 1880, 1888, 1910, 2806, 3249, 3924, 3954, 4007–4008, 4012, 4017, 4033–4034, 4048–4049, 4054–4055, 4532, 4558, 512, 517, 5366, 5398, 5415, 5431, 5447, 5462, 5487–5488, 5490, 5818, 5823, 5829, 5832, 5844, 5846, 5850, 5866, 5881, 5913, 5967, 5991, 6105, 731–732
   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%170–171, 187, 206, 280
   DependenciesBlock.js100%100%100%100%
   Dependency.js98.15%100%100%98.15%379, 425
   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.97%100%100%98.97%425–429, 577
   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.85%100%100%98.85%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%
   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%1312, 1317, 1377, 1391, 1453, 1462
   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.js98.15%100%100%98.15%1212, 1215, 1232, 1249, 1496, 1530, 1546, 1633, 2288, 2293–2303, 569
   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.13%100%100%99.13%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%326
   cli.js98.62%100%100%98.62%10, 119, 545, 577, 627, 897
   index.js99.72%100%100%99.72%165
   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%
   AssetModulesPlugin.js97.32%100%100%97.32%283, 307, 310, 36, 362, 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.30%100%100%99.30%1428–1430, 1438, 273, 276, 281, 285
   normalization.js99.01%100%100%99.01%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%
   

if (forceLoadChunks) {
forceLoadChunks.forEach(function (chunkId) {
Object.keys($ensureChunkHandlers$).forEach(function (key) {
$ensureChunkHandlers$[key](chunkId, promises);

Copy link
Copy Markdown
Member Author

Superseded by #21131 — same commits, branch renamed from test/hmr-orphan-chunk-migration to fix/hmr-force-load-migrated-chunk so the prefix matches the change kind (this is now a fix, not a test-only PR). Closing in favor of #21131.


Generated by Claude Code

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.

1 participant