Skip to content

fix(css): resolve [hash]/[fullhash] in publicPath for url() references#20879

Merged
alexander-akait merged 5 commits into
mainfrom
claude/fix-css-publicpath-urls-7Ufwk
Apr 27, 2026
Merged

fix(css): resolve [hash]/[fullhash] in publicPath for url() references#20879
alexander-akait merged 5 commits into
mainfrom
claude/fix-css-publicpath-urls-7Ufwk

Conversation

@alexander-akait
Copy link
Copy Markdown
Member

experiments.css code-generates asset URLs during the codeGeneration
phase, before compilation.hash is computed. When output.publicPath was
a function that referenced pathData.hash (or a string containing
[hash]/[fullhash]), the hash was undefined at that point so url()
references were emitted with "undefined" or an un-substituted [hash]
placeholder.

Substitute a placeholder token for the hash during code generation and
replace it with the real compilation hash in CssModulesPlugin.renderModule
once the hash is known. hashWithLength is also plumbed through to
support [fullhash:N] truncation.

https://claude.ai/code/session_01LPU9TnJW4mam4PgL6dVrtq

Copilot AI review requested due to automatic review settings April 26, 2026 14:08
@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented Apr 26, 2026

🦋 Changeset detected

Latest commit: 3dab49b

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

@linux-foundation-easycla
Copy link
Copy Markdown

linux-foundation-easycla Bot commented Apr 26, 2026

CLA Signed

The committers listed above are authorized under a signed CLA.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Apr 26, 2026

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

Install it locally:

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

@codecov
Copy link
Copy Markdown

codecov Bot commented Apr 26, 2026

Codecov Report

❌ Patch coverage is 93.02326% with 3 lines in your changes missing coverage. Please review.
✅ Project coverage is 91.35%. Comparing base (1d9d029) to head (3dab49b).
⚠️ Report is 9 commits behind head on main.

Files with missing lines Patch % Lines
lib/css/CssModulesPlugin.js 93.75% 2 Missing ⚠️
lib/asset/AssetGenerator.js 80.00% 1 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff             @@
##             main   #20879       +/-   ##
===========================================
+ Coverage   80.69%   91.35%   +10.65%     
===========================================
  Files         528      561       +33     
  Lines       53747    55542     +1795     
  Branches    14189    14670      +481     
===========================================
+ Hits        43372    50738     +7366     
+ Misses      10375     4804     -5571     
Flag Coverage Δ
integration 90.31% <93.02%> (+12.20%) ⬆️
test262 45.94% <15.78%> (-0.08%) ⬇️
unit 36.21% <2.63%> (-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

Fixes experiments.css URL generation when output.publicPath depends on the compilation hash (via [hash]/[fullhash] placeholders or publicPath(pathData).hash) by deferring hash substitution until the CSS render stage (when the hash is available).

Changes:

  • Add CSS-specific placeholder tokens for [fullhash] and [fullhash:N] during code generation when compilation.hash is not yet known.
  • Replace those placeholders with the real compilation hash in CssModulesPlugin.renderModule, and include hash in the module render cache key.
  • Extend the css/public-path config case to cover [fullhash] / [fullhash:N] (string + function) and update snapshots; add a changeset entry.

Reviewed changes

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

Show a summary per file
File Description
lib/asset/AssetGenerator.js Emits deterministic placeholder tokens for CSS URL publicPath hash interpolation when compilation.hash is unavailable.
lib/css/CssModulesPlugin.js Replaces placeholder tokens with the final compilation hash during CSS module rendering; updates cache keying.
lib/dependencies/CssUrlDependency.js Defines new internal placeholder token constants for full hash and truncated full hash.
test/configCases/css/public-path/webpack.config.js Adds new publicPath variants covering [fullhash] and [fullhash:N] (string + function).
test/configCases/css/public-path/__snapshots__/ConfigTest.snap Updates/extends snapshots to assert correct hash substitution in generated CSS URLs.
test/configCases/css/public-path/__snapshots__/ConfigCacheTest.snap Mirrors snapshot updates for cached builds.
.changeset/css-publicpath-hash.md Records the patch-level change in release notes.

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

@codspeed-hq
Copy link
Copy Markdown

codspeed-hq Bot commented Apr 26, 2026

Merging this PR will degrade performance by 83.55%

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

⚡ 8 improved benchmarks
❌ 8 regressed benchmarks
✅ 128 untouched benchmarks

⚠️ Please fix the performance issues or acknowledge them on CodSpeed.

Performance Changes

Mode Benchmark BASE HEAD Efficiency
Memory benchmark "many-chunks-esm", scenario '{"name":"mode-development-rebuild","mode":"development","watch":true}' 889.2 KB 137 KB ×6.5
Memory benchmark "asset-modules-source", scenario '{"name":"mode-development-rebuild","mode":"development","watch":true}' 162.1 KB 368.2 KB -55.98%
Memory benchmark "many-chunks-commonjs", scenario '{"name":"mode-development-rebuild","mode":"development","watch":true}' 461.2 KB 919.9 KB -49.86%
Memory benchmark "many-modules-esm", scenario '{"name":"mode-production","mode":"production"}' 9.9 MB 7.4 MB +34.16%
Memory benchmark "side-effects-reexport", scenario '{"name":"mode-development-rebuild","mode":"development","watch":true}' 847.8 KB 1,158.7 KB -26.83%
Memory benchmark "side-effects-reexport", scenario '{"name":"mode-production","mode":"production"}' 4.4 MB 5.9 MB -24.67%
Memory benchmark "devtool-eval-source-map", scenario '{"name":"mode-development","mode":"development"}' 1.9 MB 1.4 MB +37.43%
Memory benchmark "asset-modules-resource", scenario '{"name":"mode-development-rebuild","mode":"development","watch":true}' 685.8 KB 392.4 KB +74.8%
Memory benchmark "context-commonjs", scenario '{"name":"mode-development-rebuild","mode":"development","watch":true}' 232.8 KB 138.6 KB +67.97%
Memory benchmark "devtool-eval-source-map", scenario '{"name":"mode-development-rebuild","mode":"development","watch":true}' 278.4 KB 858.4 KB -67.57%
Memory benchmark "many-modules-commonjs", scenario '{"name":"mode-development","mode":"development"}' 1,146.2 KB 851.7 KB +34.58%
Memory benchmark "asset-modules-source", scenario '{"name":"mode-development","mode":"development"}' 711.9 KB 4,328.3 KB -83.55%
Memory benchmark "devtool-source-map", scenario '{"name":"mode-development","mode":"development"}' 706.6 KB 1,081.9 KB -34.68%
Memory benchmark "many-chunks-commonjs", scenario '{"name":"mode-development","mode":"development"}' 1,306.9 KB 805.1 KB +62.32%
Memory benchmark "devtool-eval", scenario '{"name":"mode-development","mode":"development"}' 737.9 KB 1,147.5 KB -35.7%
Memory benchmark "many-chunks-esm", scenario '{"name":"mode-production","mode":"production"}' 8.7 MB 6.6 MB +30.65%

Comparing claude/fix-css-publicpath-urls-7Ufwk (3dab49b) with main (f08f19a)

Open in CodSpeed

experiments.css produced broken url() values (containing `undefined`
or an un-substituted `[hash]`) when output.publicPath was a function
referencing pathData.hash, or a string template using [hash] /
[fullhash]. The asset URL was baked in during code generation, but
compilation.hash is not yet computed at that phase.

Substitute placeholders during code generation (PUBLIC_PATH_FULL_HASH
and PUBLIC_PATH_FULL_HASH_WITH_LENGTH_<n>__), then replace them with
the real compilation hash inside CssModulesPlugin.renderModule, where
the hash is already available. Placeholder lookup uses indexOf rather
than RegExp for ~40-80% lower overhead in microbenchmarks.
@alexander-akait alexander-akait force-pushed the claude/fix-css-publicpath-urls-7Ufwk branch from 8ca7727 to 46b9cc0 Compare April 26, 2026 16:46
Collapse the two separate placeholders (full hash, full hash with
length) into a single PUBLIC_PATH_FULL_HASH prefix that is always
suffixed with a length and a closing __. Length 0 means "use the
entire hash"; non-zero lengths slice. The substitution loop in
CssModulesPlugin.renderModule now scans once for the unified prefix
and decides full vs. sliced based on the parsed length.
The new test cases use [fullhash] / [fullhash:8] / pathData.hash in
publicPath, so the snapshot includes the actual compilation hash. On
Windows checkouts the .js fixtures (index.js, webpack.config.js,
test.config.js) were converted to CRLF by the text=auto baseline,
which changed the bundled JS module content and therefore the
compilation hash, causing snapshot mismatches in CI.

Pin those .js files to LF so the snapshot is platform-stable.
…or message

When a runtime module's generate() / getGeneratedCode() returns null
(e.g. CssLoadingRuntimeModule when the chunk needs no loading or HMR),
RuntimeModule.updateHash was passing null to hash.update(), which
threw a TypeError that the surrounding try/catch then hashed via
err.message. Node 14 and earlier produce
  "Cannot read property 'length' of null"
while Node 16+ produce
  "Cannot read properties of null (reading 'length')"
so the same compilation produced different fullhashes on different
Node versions. With the actual hash now appearing in CSS url()
references that resolve [hash]/[fullhash] in publicPath, this
surfaced as snapshot mismatches on the Node 10/12/14 CI legs.

Skip hash.update when the generated code is null/undefined so the
runtime module hash reflects an empty contribution rather than a
JavaScript error string. Updates the public-path snapshots to the
new (now Node-stable) hash.
@github-actions
Copy link
Copy Markdown
Contributor

Types Coverage

Coverage after merging claude/fix-css-publicpath-urls-7Ufwk into main will be
98.93%
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%104
   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%1559, 1855, 1862, 1870, 1892, 2788, 3213, 3868, 3897, 3950–3951, 3955, 3960, 3976–3977, 3991–3992, 3997–3998, 4471, 4497, 498, 503, 5205, 5286, 5301, 5326–5327, 5329, 5651, 5656, 5662, 5665, 5677, 5679, 5683, 5699, 5714, 5746, 5800, 5824, 5938, 717–718
   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%
   CssModule.js81.32%100%100%81.32%157, 162–177
   DefinePlugin.js98.92%100%100%98.92%158–159, 175, 194, 268
   DependenciesBlock.js100%100%100%100%
   Dependency.js98.13%100%100%98.13%371, 404
   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%
   GraphHelpers.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.68%100%100%97.68%1004, 1038, 1054, 1141, 1792, 1797–1807, 214, 726, 729, 746, 763
   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%
   RequireJsStuffPlugin.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%
   SizeFormatHelpers.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%326
   cli.js98.71%100%100%98.71%117, 469, 501, 543, 813
   formatLocation.js100%100%100%100%
   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%1291–1293, 1301, 270, 273, 278, 282, 467
   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%
   

@alexander-akait alexander-akait merged commit 54db222 into main Apr 27, 2026
61 checks passed
@alexander-akait alexander-akait deleted the claude/fix-css-publicpath-urls-7Ufwk branch April 27, 2026 17:43
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