fix(css): resolve [hash]/[fullhash] in publicPath for url() references#20879
Conversation
🦋 Changeset detectedLatest commit: 3dab49b The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
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 |
|
This PR is packaged and the instant preview is available (3dab49b). Install it locally:
npm i -D webpack@https://pkg.pr.new/webpack@3dab49b
yarn add -D webpack@https://pkg.pr.new/webpack@3dab49b
pnpm add -D webpack@https://pkg.pr.new/webpack@3dab49b |
Codecov Report❌ Patch coverage is
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
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
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 whencompilation.hashis not yet known. - Replace those placeholders with the real compilation hash in
CssModulesPlugin.renderModule, and includehashin the module render cache key. - Extend the
css/public-pathconfig 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.
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.
8ca7727 to
46b9cc0
Compare
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.
Types CoverageCoverage after merging claude/fix-css-publicpath-urls-7Ufwk into main will be
Coverage Report
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
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