Skip to content

Support inline hash digest/length in path placeholders and CSS localIdentName#21259

Merged
alexander-akait merged 13 commits into
mainfrom
claude/webpack-21141-review-vwscik
Jun 23, 2026
Merged

Support inline hash digest/length in path placeholders and CSS localIdentName#21259
alexander-akait merged 13 commits into
mainfrom
claude/webpack-21141-review-vwscik

Conversation

@alexander-akait

Copy link
Copy Markdown
Member

Summary

Closes #21141 (both points). Adds css-loader / loader-utils style inline hash digest and length to webpack's path placeholders — [<hash>:<digest>:<length>] (e.g. [contenthash:base64:8], [fullhash:6]) — for output filenames and CSS Modules localIdentName:

  • Point 1: [fullhash:6] / [hash:base64:6] / [contenthash:…] now work in localIdentName.
  • Point 2: [hash] in localIdentName now resolves to the local ident hash (matching css-loader) instead of the module hash; [modulehash] yields the module hash.

[fullhash]/[chunkhash] and CSS [fullhash]/[hash] digests re-encode the untruncated hash (full entropy); [contenthash] re-encodes the stored hash and is rejected when optimization.realContentHash is enabled (it recomputes in output.hashDigest, which would silently drop the digest). Supported digests: hex, base64, base64url, base64safe, base26/32/36/49/52/58/62; an unknown digest throws. Placeholder-kind detection is centralized behind an exported getPresentKinds (reused by RuntimePlugin/SourceMapDevToolPlugin).

What kind of change does this PR introduce?

feat (also fixes the two bugs in #21141).

Did you add tests for your changes?

Yes — test/TemplatedPathPlugin.unittest.js, test/nonNumericOnlyHash.unittest.js, test/configCases/hash-length/digest/, and test/configCases/css/local-ident-name/.

Does this PR introduce a breaking change?

Yes, for experimental CSS: [hash] in localIdentName changes from the module hash to the local ident hash — migration: use [modulehash] for the previous behavior. Also, an unknown inline digest token now errors instead of being silently ignored.

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

Documented in webpack/docs.webpack.js.org (same branch): configuration/output.mdx and configuration/module.mdx — inline digest/length, [hash]/[fullhash] = local ident hash, the new [modulehash], and that hashDigestLength is the lever for content-hash entropy.

Use of AI

Yes. I used AI (Claude Code) to research loader-utils' placeholder grammar, draft the implementation, tests and docs, and run the test suites; every change was directed and reviewed by me. This complies with the webpack AI policy.

🤖 Generated with Claude Code
https://claude.ai/code/session_01Dqzs5d8if5LVWEEFp91KEA


Generated by Claude Code

…olders

Normalize css-loader / loader-utils style `[hash:<digest>:<length>]`
placeholders in the native CSS generator: fold the digest and length into the
local ident hash options and map `[hash]`/`[fullhash]` to the local ident hash
(css-loader semantics) so suffixed placeholders like `[fullhash:6]` and
`[hash:base64:6]` work.
Extend TemplatedPathPlugin.interpolate to accept `[hash:<digest>:<length>]`
(e.g. `[contenthash:base64:8]`) for filename/path templates, re-encoding the
stored hash into the requested digest. The source hash is already truncated to
output.hashDigestLength, so the result derives from those bytes.
Drop the css-loader-specific localIdentName hash remapping; inline digest and
length are handled generically in TemplatedPathPlugin.interpolate.
Make the localIdent hash-detection regexes suffix-tolerant so [fullhash:N] and
[contenthash:...] trigger hash computation, and pass the output digest into
interpolate so an inline digest on [hash]/[contenthash] re-encodes correctly.
Export getPresentKinds as the single source of truth for which [kind]
placeholders a template references, and route CssGenerator, RuntimePlugin and
SourceMapDevToolPlugin through it instead of their own hand-rolled regexes so
they can't drift from interpolate's grammar.
Re-encoding silently kept the source encoding for a typo'd digest; validate the
requested digest and throw a clear error instead, matching how interpolate
already rejects unimplemented path variables.
Inline-digest path re-encoded the hashDigestLength-truncated hash, capping
entropy below the requested length. Pass the untruncated compilation/chunk hash
(which already exists) so [fullhash:<digest>] and [chunkhash:<digest>] carry full
entropy and match an encode-then-slice result.
RealContentHashPlugin recomputes content hashes in output.hashDigest and would
silently overwrite an inline-digest [contenthash] with a hex hash. Throw a clear
error for that combination instead of emitting the wrong encoding.
The local ident hash uses localIdentHashDigest, not output.hashDigest, so an
inline [fullhash:<digest>] re-encoded from the wrong source. Pass the untruncated
local ident hash and its own digest (fullHashDigest) so interpolate re-encodes it
correctly with full entropy.
The contentHash producers all did nonNumericOnlyHash(hash.digest(d), n); wrap
that composition in one helper and route JavascriptModulesPlugin,
CssModulesPlugin, HtmlModulesPlugin and getLocalIdent through it. AssetGenerator
keeps the inline form since it also returns the untruncated digest.
…dule hash

Fixes issue point 2: the module-context block in interpolate repurposed [hash]
to the module hash, diverging from css-loader. Add a data.hashAsFullHash flag
that getLocalIdent sets so [hash] stays the local ident hash (like [fullhash]);
[modulehash] still yields the module hash. Closes #21141 (point 2).
@changeset-bot

changeset-bot Bot commented Jun 23, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 6f44114

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
webpack Minor

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

Copy link
Copy Markdown
Contributor

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

Install it locally:

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

@codecov

codecov Bot commented Jun 23, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 97.14286% with 3 lines in your changes missing coverage. Please review.
✅ Project coverage is 92.80%. Comparing base (2e43eb4) to head (6f44114).
⚠️ Report is 6 commits behind head on main.

Files with missing lines Patch % Lines
lib/TemplatedPathPlugin.js 95.65% 3 Missing ⚠️
Additional details and impacted files
@@           Coverage Diff           @@
##             main   #21259   +/-   ##
=======================================
  Coverage   92.80%   92.80%           
=======================================
  Files         590      591    +1     
  Lines       64647    64726   +79     
  Branches    17993    18040   +47     
=======================================
+ Hits        59996    60071   +75     
- Misses       4651     4655    +4     
Flag Coverage Δ
css-parsing 28.71% <47.57%> (+0.01%) ⬆️
html5lib 31.18% <41.17%> (+0.06%) ⬆️
integration 88.93% <92.38%> (+<0.01%) ⬆️
test262 45.40% <42.00%> (+<0.01%) ⬆️
unit 41.50% <78.00%> (+0.11%) ⬆️

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

Copy link
Copy Markdown

Merging this PR will improve performance by 25.06%

⚡ 3 improved benchmarks
❌ 2 regressed benchmarks
✅ 139 untouched benchmarks

Warning

Please fix the performance issues or acknowledge them on CodSpeed.

Performance Changes

Mode Benchmark BASE HEAD Efficiency
Memory benchmark "asset-modules-bytes", scenario '{"name":"mode-development-rebuild","mode":"development","watch":true}' 247.1 KB 318.9 KB -22.51%
Memory benchmark "many-chunks-commonjs", scenario '{"name":"mode-production","mode":"production"}' 7.8 MB 9.7 MB -20.45%
Memory benchmark "asset-modules-inline", scenario '{"name":"mode-development-rebuild","mode":"development","watch":true}' 1,232.2 KB 384.8 KB ×3.2
Memory benchmark "devtool-source-map", scenario '{"name":"mode-production","mode":"production"}' 8.1 MB 6.3 MB +28.51%
Memory benchmark "future-defaults", scenario '{"name":"mode-production","mode":"production"}' 8.7 MB 7.2 MB +20.6%

Tip

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


Comparing claude/webpack-21141-review-vwscik (6f44114) with main (053f2dc)

Open in CodSpeed

@github-actions

Copy link
Copy Markdown
Contributor

Types Coverage

Coverage after merging claude/webpack-21141-review-vwscik into main will be
99.35%
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-emscripten
   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%
   CircularModulesPlugin.js98.81%100%100%98.81%136
   CleanPlugin.js99.15%100%100%99.15%207, 227
   CodeGenerationResults.js100%100%100%100%
   CompatibilityPlugin.js100%100%100%100%
   Compilation.js98.43%100%100%98.43%1623, 1942, 1949, 1957, 1979, 1982, 2922, 3401–3402, 3434, 4100, 4130, 4183–4184, 4188, 4193, 4209–4210, 4224–4225, 4230–4231, 4708, 4734, 519, 524, 5542, 5574, 5591, 5607, 5623, 5638, 5663–5664, 5666, 5994, 5999, 6005, 6008, 6020, 6022, 6026, 6042, 6057, 6089, 6143, 6167, 6281, 769–770
   Compiler.js99.56%100%100%99.56%1142–1143, 1151
   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%171–172, 188, 207, 281
   DependenciesBlock.js100%100%100%100%
   Dependency.js98.50%100%100%98.50%470, 516
   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.50%100%100%98.50%1057, 1060, 445–449, 451, 597
   ExternalModuleFactoryPlugin.js100%100%100%100%
   ExternalsPlugin.js100%100%100%100%
   FileSystemInfo.js99.52%100%100%99.52%182, 2354–2355, 2358, 2369, 2380, 2391, 278, 3795, 3810, 3834
   FlagAllModulesAsUsedPlugin.js100%100%100%100%
   FlagDependencyExportsPlugin.js98.42%100%100%98.42%413, 422, 424, 428
   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%
   LazyBarrel.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%1285, 1290, 1350, 1364, 1426, 1435
   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.js97.90%100%100%97.90%1237, 1240, 1257, 1274, 1521, 1555, 1571, 1658, 2014, 2313, 2318–2328, 418, 422, 576
   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, 889
   Stats.js100%100%100%100%
   Template.js100%100%100%100%
   TemplatedPathPlugin.js99.38%100%100%99.38%295–296
   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%371
   cli.js98.62%100%100%98.62%10, 119, 545, 577, 627, 897
   index.js99.72%100%100%99.72%184
   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%
   AssetModule.js100%100%100%100%
   AssetModulesPlugin.js97.32%100%100%97.32%281, 305, 308, 36, 360, 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/bun
   BunTargetPlugin.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.33%100%100%99.33%1456–1458, 1466, 274, 277, 282, 286
   defineConfig.js100%100%100%100%
 

@alexander-akait alexander-akait merged commit ecc00c0 into main Jun 23, 2026
68 checks passed
@alexander-akait alexander-akait deleted the claude/webpack-21141-review-vwscik branch June 23, 2026 16:23
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.

Expermental CSS hash

1 participant