Skip to content

fix(ConcatenatedModule): include runtimeCondition of external infos in updateHash#21023

Merged
alexander-akait merged 9 commits into
mainfrom
claude/easy-todo-fixes-TRVLy
May 22, 2026
Merged

fix(ConcatenatedModule): include runtimeCondition of external infos in updateHash#21023
alexander-akait merged 9 commits into
mainfrom
claude/easy-todo-fixes-TRVLy

Conversation

@alexander-akait
Copy link
Copy Markdown
Member

@alexander-akait alexander-akait commented May 22, 2026

Summary

Addresses the long-standing // TODO runtimeCondition in ConcatenatedModule#updateHash (introduced with the original concatenation work). For an info.type === "external" entry, the hash previously only fed in the external module's id and dropped its runtimeCondition. Two ConcatenatedModule states that differ only in the runtime condition of a concatenated external therefore produced identical hashes, which can slip past persistent-cache invalidation.

This PR feeds runtimeConditionToString(info.runtimeCondition) into the hash alongside the module id, so the condition contributes to the digest the same way it already does for other parts of the graph.

Also includes a small unrelated comment fix on lib/javascript/JavascriptParser.js: the ImportExpression cast was tagged with a TODO mentioning "import assertions", but the cast actually adds the phase property (defer / source). Renamed to "import phases".

What kind of change does this PR introduce?

fix

Did you add tests for your changes?

Yes — test/configCases/concatenate-modules/external-runtime-condition-hash/. The case builds a real UMD-external project (UMD externals bail out of concatenation via ExternalModule#getConcatenationBailoutReason, so a real info.type === "external" entry appears in the concat list), then asserts in a plugin that swapping only runtimeCondition on the otherwise-real list produces a different digest. Verified the case fails on the unmodified baseline ("More errors (1 instead of 0) ... Expected: not ") and passes with the fix, under both ConfigTestCases.basictest.js and ConfigCacheTestCases.basictest.js.

Does this PR introduce a breaking change?

No. The hash now incorporates additional state; module hashes for concatenations involving a non-concatenatable external will change once, then stay stable.

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

n/a

Use of AI

Claude Code was used under human review to locate the TODO, implement the fix, and author the configCases regression test. All changes were reviewed and the regression was double-checked by reverting the fix locally and confirming the new test fails with a hash-mismatch error before re-applying.

Copilot AI review requested due to automatic review settings May 22, 2026 15:42
@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented May 22, 2026

🦋 Changeset detected

Latest commit: 4ab1bf8

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

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

Install it locally:

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

@alexander-akait alexander-akait changed the title docs(JavascriptParser): correct TODO referring to import phases not assertions fix(ConcatenatedModule): include runtimeCondition of external infos in updateHash May 22, 2026
@codecov
Copy link
Copy Markdown

codecov Bot commented May 22, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 91.58%. Comparing base (c05beb9) to head (4ab1bf8).
⚠️ Report is 2 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main   #21023      +/-   ##
==========================================
+ Coverage   91.55%   91.58%   +0.02%     
==========================================
  Files         573      573              
  Lines       59502    59541      +39     
  Branches    16069    16077       +8     
==========================================
+ Hits        54479    54531      +52     
+ Misses       5023     5010      -13     
Flag Coverage Δ
integration 89.50% <100.00%> (+0.02%) ⬆️
test262 45.32% <100.00%> (-0.03%) ⬇️
unit 37.95% <0.00%> (-0.01%) ⬇️

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 fixes persistent-cache invalidation for ConcatenatedModule by ensuring runtime-conditional state for concatenated external infos contributes to the module hash, addressing a long-standing TODO in ConcatenatedModule#updateHash. It also adds a regression test case and includes a small comment correction in the JS parser.

Changes:

  • Include runtimeCondition for info.type === "external" entries in ConcatenatedModule#updateHash.
  • Add a configCases regression test that asserts the concatenated module hash changes when only an external-info runtimeCondition changes.
  • Update a TODO comment in JavascriptParser to reference import phases (not assertions) and add a changeset entry.

Reviewed changes

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

Show a summary per file
File Description
lib/optimize/ConcatenatedModule.js Hash now incorporates external-info runtimeCondition so runtime-conditional externals affect cache invalidation.
test/configCases/concatenate-modules/external-runtime-condition-hash/webpack.config.js New config-case plugin asserts hash differs when external runtimeCondition differs.
test/configCases/concatenate-modules/external-runtime-condition-hash/index.js Test entry verifies compilation works with an external excluded from concatenation.
test/configCases/concatenate-modules/external-runtime-condition-hash/lib.js Creates an external import/re-export to force an external info entry in concatenation list.
test/configCases/concatenate-modules/external-runtime-condition-hash/errors.js Declares no expected errors for the new config-case.
lib/javascript/JavascriptParser.js Comment-only TODO clarification (import phases).
.changeset/concatenated-module-hash-runtime-condition.md Records the patch-level change for release notes.

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

Comment on lines 2319 to 2322
case "external":
hash.update(`${chunkGraph.getModuleId(info.module)}`);
// TODO runtimeCondition
hash.update(runtimeConditionToString(info.runtimeCondition));
break;
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.

Good catch — fixed in ba077ce by combining moduleId and runtimeCondition into a single hash.update with a | delimiter, so the two segments can no longer slide across each other in the streaming hash.


Generated by Claude Code

@codspeed-hq
Copy link
Copy Markdown

codspeed-hq Bot commented May 22, 2026

Merging this PR will not alter performance

⚡ 5 improved benchmarks
❌ 5 regressed benchmarks
✅ 134 untouched benchmarks
⏩ 72 skipped benchmarks1

Warning

Please fix the performance issues or acknowledge them on CodSpeed.

Performance Changes

Mode Benchmark BASE HEAD Efficiency
Memory benchmark "many-modules-esm", scenario '{"name":"mode-development","mode":"development"}' 1,095 KB 840.7 KB +30.25%
Memory benchmark "context-esm", scenario '{"name":"mode-development-rebuild","mode":"development","watch":true}' 663.2 KB 251.5 KB ×2.6
Memory benchmark "asset-modules-source", scenario '{"name":"mode-development-rebuild","mode":"development","watch":true}' 3,716.3 KB 480.5 KB ×7.7
Memory benchmark "many-chunks-commonjs", scenario '{"name":"mode-production","mode":"production"}' 6.7 MB 8.6 MB -21.63%
Memory benchmark "css-modules", scenario '{"name":"mode-development","mode":"development"}' 1,074.1 KB 537.2 KB +99.95%
Memory benchmark "future-defaults", scenario '{"name":"mode-development-rebuild","mode":"development","watch":true}' 145.4 KB 310.5 KB -53.19%
Memory benchmark "cache-filesystem", scenario '{"name":"mode-development-rebuild","mode":"development","watch":true}' 1,031.6 KB 843.8 KB +22.25%
Memory benchmark "concatenate-modules", scenario '{"name":"mode-development-rebuild","mode":"development","watch":true}' 130.7 KB 167.9 KB -22.14%
Memory benchmark "concatenate-modules", scenario '{"name":"mode-development","mode":"development"}' 784.6 KB 1,111.7 KB -29.42%
Memory benchmark "wasm-modules-async", scenario '{"name":"mode-development-rebuild","mode":"development","watch":true}' 398.3 KB 1,543.5 KB -74.2%

Tip

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


Comparing claude/easy-todo-fixes-TRVLy (4ab1bf8) with main (26e346a)

Open in CodSpeed

Footnotes

  1. 72 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

Copilot AI review requested due to automatic review settings May 22, 2026 15:56
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 7 out of 7 changed files in this pull request and generated 1 comment.

Comment on lines 2319 to +2322
case "external":
hash.update(`${chunkGraph.getModuleId(info.module)}`);
// TODO runtimeCondition
hash.update(
`${chunkGraph.getModuleId(info.module)}|${runtimeConditionToString(info.runtimeCondition)}`
);
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.

Good point — verified both fields actually drive code generation:

  • info.deferred (set from moduleGraph.isDeferred(info.module)) gates the deferred-external branch in renderConcatenatedModule (if (info.type === "external" && info.deferred), if (!info.deferred))
  • info.nonDeferAccess together with info.deferred triggers the extra non-defer-access path (if (info.deferred && nonDeferAccess))

Both can change without the module id changing (e.g. when a different consumer adds/removes an import.defer), so the same persistent-cache staleness applies. Folded both into the hash update in 4ab1bf8 — same single hash.update with |-delimited segments.


Generated by Claude Code

@github-actions
Copy link
Copy Markdown
Contributor

Types Coverage

Coverage after merging claude/easy-todo-fixes-TRVLy into main will be
98.96%
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.45%100%100%98.45%1572, 1868, 1875, 1883, 1905, 2801, 3226, 3888, 3917, 3970–3971, 3975, 3980, 3996–3997, 4011–4012, 4017–4018, 4495, 4521, 511, 516, 5229, 5261, 5278, 5294, 5310, 5325, 5350–5351, 5353, 5681, 5686, 5692, 5695, 5707, 5709, 5713, 5729, 5744, 5776, 5830, 5854, 5968, 730–731
   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.40%100%100%97.40%258, 395, 418, 420, 424, 433–434
   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.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, 3694, 3709, 3733
   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%1305, 1310, 1371, 1385, 1447, 1456
   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%
   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%645
   MultiStats.js100%100%100%100%
   MultiWatching.js100%100%100%100%
   NoEmitOnErrorsPlugin.js100%100%100%100%
   NodeStuffPlugin.js100%100%100%100%
   NormalModule.js98.11%100%100%98.11%1208, 1211, 1228, 1245, 1492, 1526, 1542, 1629, 2252, 2257–2267, 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.js98.86%100%100%98.86%136–137
   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.46%100%100%98.46%10, 119, 471, 503, 545, 815
   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.29%100%100%99.29%1411–1413, 1421, 271, 274, 279, 283
   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%
   

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 7 out of 7 changed files in this pull request and generated no new comments.

@alexander-akait alexander-akait merged commit 75f60f6 into main May 22, 2026
60 of 61 checks passed
@alexander-akait alexander-akait deleted the claude/easy-todo-fixes-TRVLy branch May 22, 2026 16:54
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