Commit 26e346a
authored
fix: include referenced module's hash in HTML source/inline-style updateHash (#21018)
* fix: include referenced module's hash in HTML source/inline-style updateHash
`HtmlSourceDependency` (`<img src>`, `<link href>`, `<audio src>`, …) and
`HtmlInlineStyleDependency` (`<style>`) substitute content sourced from
the referenced module into the rendered HTML at code-generation time —
the asset's hashed filename for `HtmlSourceDependency`, the rendered CSS
text for `HtmlInlineStyleDependency`. Without `updateHash`, the HTML
module's hash didn't reflect changes to the referenced module, so the
extracted HTML's `[contenthash]` stayed pinned across incremental
rebuilds even when the rendered bytes had changed — the same long-term
caching break `CssUrlDependency.updateHash` was added to prevent for CSS
`url(...)`.
Fold the referenced module's `buildInfo.hash` into each dependency's
`updateHash`, matching the `CssUrlDependency` pattern. Adds two watch
test cases under `test/watchCases/long-term-caching/`:
`html-contenthash-asset-url` (regression for the fix) and
`js-contenthash-asset-url` (the analogous case for `new URL(asset,
import.meta.url)`, which already worked because the asset module's hash
flows through the JS chunk hash — kept as a regression guard).
* docs: require changeset descriptions to be sentence-case with a trailing period
Recent changesets (`cjs-require-binding-tree-shake.md`, `public-path-fullhash-length-suffix.md`,
`html-entry-css-chunks-link-tags.md`, `align-html-lexer-script-data.md`, …) all
open with a capital letter and end with a period — that's what makes them read
as proper changelog entries when changesets is concatenated into the release
`CHANGELOG.md`. Document the rule explicitly and update the inline examples
("Fix split-chunks cache key collision.", "Add `module.generator.html.extract`
option.") so the guide matches the convention reviewers already enforce.
Also restyles the changeset added in the previous commit to match.
* refactor: drop redundant `buildInfo` undefined check in HTML updateHash
`module.buildInfo` is populated by `NormalModule#build` (and the equivalent
hook on other module types) before any code path that calls `updateHash`
runs — module hashing happens after the build phase. Cast straight to
`BuildInfo` and keep only the `.hash` check (the field itself is still
typed as optional on `KnownBuildInfo`). Matches what reviewers were
flagging on the prior commit.
* fix: recurse into inline-style module updateHash so transitive url() asset changes invalidate the HTML
`HtmlInlineStyleDependency.Template.apply` pulls the rendered CSS text
out of `codeGenerationResults` and substitutes it into the HTML — the
rendered text already has every `url(...)` rewritten to its hashed asset
filename by `CssUrlDependency`. The previous `updateHash` only folded
the CSS module's `buildInfo.hash`, which captures the CSS *source*. The
CSS source doesn't change when only the referenced asset's bytes change
— that change rides on `CssUrlDependency.updateHash`, which contributes
to the CSS module's *module* hash, not its `buildInfo.hash`. So the
HTML's `[contenthash]` stayed pinned even though the rendered HTML
embedded a new asset filename.
Recurse into the inline-CSS module's full `updateHash` so the same
asset-hash chain that already invalidates a standalone CSS chunk's
contenthash now also invalidates the host HTML's.
Adds `test/watchCases/long-term-caching/html-contenthash-inline-style-url/`,
which fails before the fix (HTML filename stayed `page.36e6bef5…html`
across the asset swap) and passes after.
* fix: defer chunk-URL substitution in extracted HTML until chunk hashes are computed
`HtmlInlineScriptDependency` and `HtmlScriptSrcDependency` (the new
experimental HTML pipeline) used to call `compilation.getPath(chunk
filenameTemplate, …)` from their `Template#apply` — i.e. during
`Compilation#codeGeneration()`. That runs before `createHash()`, so
`chunk.hash` and `chunk.contentHash[type]` are still `null`. Any
`output.chunkFilename` (or `output.filename`) containing
`[contenthash]` / `[chunkhash]` / `[fullhash]` would throw "Path
variable [contenthash] not implemented in this context" from
`TemplatedPathPlugin`, breaking every HTML compile that wanted hashed
JS/CSS filenames. Even when the template happened to resolve, the
HTML module's own `[contenthash]` was computed from
placeholder-substituted bytes — so changing only an inline-script's
transitive dep flipped the embedded chunk URL but left the HTML's
`[contenthash]` pinned.
Defer the substitution instead: dep templates now emit a sentinel
(`__WEBPACK_HTML_CHUNK_URL__<hexChunkId>__<contentHashType>__END__`)
via `makeHtmlChunkUrlSentinel` from a new `lib/html/htmlChunkUrl.js`
helper, and `HtmlModulesPlugin#renderManifest` swaps every sentinel
for `${PUBLIC_PATH_AUTO}<chunkFilename>` *before* hashing the HTML
output — by that point `createHash()` has populated every chunk's
`chunk.contentHash[type]`/`chunk.hash`/`compilation.hash`, so the
chunk filenames' placeholders all resolve. Hashing the resolved
content (rather than the raw placeholder source) is what makes the
HTML's `[contenthash]` invalidate when a referenced chunk's filename
changes. `HtmlGenerator#_renderHtml`'s JS-export path resolves
sentinels inline too via the same helper; sentinels whose templates
can't be resolved yet (e.g. an unbuildable `[contenthash]` placeholder
at code-gen time) are left in place rather than thrown — `extract:
true` (the HTML output path) is the one that supports dynamic
filenames, and the JS export of the HTML string carries the
restriction that previously surfaced as a compile-time error.
Adds `test/watchCases/long-term-caching/html-contenthash-inline-script/`,
which fails before the fix (compile errors with `[contenthash]` in
`chunkFilename`) and after step 1 verifies the HTML invalidates when
the inline-script's transitive dep flips.
* refactor(html): extract duplicated `[contenthash]` recipe into HtmlModulesPlugin.computeContentHash
`renderManifest` hashed HTML bytes twice with identical boilerplate —
once for the `[contenthash]` substituted into `output.htmlFilename` and
once for the final asset cache key — each call re-implementing the
`createHash(hashFunction)` + `hashSalt` + `digest(hashDigest)` +
`nonNumericOnlyHash(_, hashDigestLength)` recipe. Pull both call sites
into a single static method on `HtmlModulesPlugin` so the recipe can't
drift between them, and so anyone reaching for "the HTML pipeline's
`[contenthash]` recipe" has a named entry point.
No behaviour change; the previously inlined `createHash` /
`nonNumericOnlyHash` requires are now inside the static method.
* refactor(html): fold chunk-URL sentinel helpers into HtmlGenerator as static methods
`lib/html/htmlChunkUrl.js` was a single-file module exporting the
`makeHtmlChunkUrlSentinel` / `resolveHtmlChunkUrlSentinels` pair created
in the previous commit. Inline them onto `HtmlGenerator` as
`HtmlGenerator.makeChunkUrlSentinel` and `HtmlGenerator.resolveChunkUrlSentinels`
— that's the class responsible for emitting both sides of the
substitution (`_renderHtml` writes sentinels via dep templates and
resolves them on the JS-export path), so it's the natural home for the
sentinel format. `HtmlGenerator` doesn't import the HTML dependencies,
so the two dep templates (`HtmlInlineScriptDependency`,
`HtmlScriptSrcDependency`) can require it directly without introducing
a circular import. `HtmlModulesPlugin` already imports `HtmlGenerator`
and reaches the resolver through the same static method.
No behaviour change; just removes the standalone helper file.
* refactor(html): drop unnecessary chunk.id null-checks in chunk-URL sentinel helpers
Both `HtmlGenerator.makeChunkUrlSentinel` (called from dep templates
during `Compilation#codeGeneration()`) and
`HtmlGenerator.resolveChunkUrlSentinels` (called from `_renderHtml`'s
JS-export path and from `HtmlModulesPlugin#renderManifest`) run after
`Compilation#seal`'s `optimizeChunkIds` hook, which is what
`chunkIds`/`NamedChunkIdsPlugin`/`DeterministicChunkIdsPlugin` use to
populate every chunk's `.id`. By the time these helpers run, every
chunk has a non-null id, so the defensive `chunk.id == null` branches
were dead code.
* docs: require code comments to stay short and informative
Adds a REQUIRED "Code comments" section to `AGENTS.md`: comments inside
`lib/`, `hot/`, `tooling/`, `test/` must be one or at most two short
lines and every line must add information not obvious from the code
(invariant, ordering constraint, pinned-bug workaround, higher-level
concept name). No multi-paragraph essays, no restating the next line,
no diff narration, no PR-body restatements, no task-framing quotes.
JSDoc on exported symbols is exempt because it's the type contract.
Also trims the verbose comments added in this branch (chunk-URL
sentinel docs on `HtmlGenerator`, the dep-template apply blocks, the
`HtmlInlineStyleDependency` / `HtmlSourceDependency` `updateHash`
explanations, `HtmlModulesPlugin.computeContentHash` JSDoc, and the
renderManifest "resolve before hashing" comment) to fit the rule —
105 lines removed, no behaviour change. Pre-existing comments in
other functions are left for their respective authors to revisit.
* fix: address Copilot review on PR #21018
- `HtmlModulesPlugin.computeContentHash` passes `outputOptions.hashFunction`
through to `createHash` typed as `HashFunction` instead of casting to
`string`, so the constructor-function form of `output.hashFunction` keeps
working.
- Shorten the inline-script-chunk-url-sentinel changeset to fit the
≤80-char/no-commas guidance the previous commit added to AGENTS.md.
- Trim the test-fixture explanatory comments Copilot flagged in
`js-contenthash-asset-url/{0,2}`, `html-contenthash-asset-url/{0,2}`,
`html-contenthash-inline-style-url/{0,2}`, and
`html-contenthash-inline-script/{0/page.html,1/index.js}` — the test
names already convey intent. No assertion logic changed.
* fix: resolve chunk-URL sentinels in JS-export bundles too via processAssets
The JS-export path in `HtmlGenerator._renderHtml` was leaving unresolved
`__WEBPACK_HTML_CHUNK_URL__…__END__` sentinels in `module.exports = "<html>"`
when chunk filenames carried `[contenthash]` / `[chunkhash]` /
`[fullhash]` — chunk hashes don't exist at code-gen time, so the inline
`compilation.getPath` fell into the catch and left the sentinel. Consumers
doing `require("./page.html")` then saw the sentinel string at runtime.
Drop the JS-export path's inline sentinel resolution entirely (it always
leaves sentinels now) and add a global `processAssets` pass in
`HtmlModulesPlugin` at `PROCESS_ASSETS_STAGE_OPTIMIZE_INLINE` that sweeps
every asset, resolves any surviving sentinels via
`HtmlGenerator.resolveChunkUrlSentinels`, and collapses the freshly-emitted
`[webpack/auto]` placeholders to `""` (root-relative — matching the prior
JS-export behaviour). Stage choice matters: it runs after `createHash()`
has populated every `chunk.hash` / `chunk.contentHash[type]`, so chunk
filename templates resolve, and before `PROCESS_ASSETS_STAGE_OPTIMIZE_HASH`
where `realContentHash` rehashes affected chunk filenames against the
resolved bytes.
Adds a step-0 assertion in `html-contenthash-inline-script/0/index.js`
that reads `main.<hash>.js` from disk and verifies no
`__WEBPACK_HTML_CHUNK_URL__<hex>__<type>__END__` survives into the JS
bundle's HTML export.
* fix: reuse sentinel-resolved asset source across compilations
The new `processAssets` sweep was minting a fresh `RawSource` on every
compilation, even when the resolved bytes were byte-identical to the
previous run. `getLazyHashedEtag`'s WeakMap is keyed by source identity,
and `MemoryCachePlugin` / `PackFileCacheStrategy` compare etags with
reference equality, so a new source object meant a fresh
`LazyHashedEtag` → fresh `MergedEtag` → cache miss → `set()` on
`RealContentHashPlugin|analyse|<asset>` → "Pack got invalid because of
write to" infra log. `ConfigCacheTestCases` treats that log as a failure
on the 2nd (warm) run, so every HTML test with an inline script chunk
regressed.
Cache the resolved `RawSource` per asset name in a plugin-scoped Map
and reuse it whenever the freshly-computed `resolved` string matches
the prior content. Same bytes ⇒ same source object ⇒ same etag ⇒ the
analyse cache hits and no infrastructure log fires.
* refactor: address Copilot perf + comment-length feedback
- `HtmlGenerator.resolveChunkUrlSentinels` now caches the per-compilation
`chunksById` map in a module-level `WeakMap` instead of rebuilding it
on every call (was O(assets × chunks) with the new `processAssets`
sweep, now O(chunks) per compilation).
- `HtmlModulesPlugin`'s `processAssets` pass uses `Buffer#indexOf` on
Buffer-backed sources and skips them when the ASCII sentinel marker
isn't present, avoiding a full UTF-8 decode of large binary blobs.
- Trim multi-line explanatory comments down to ≤2 lines per the
REQUIRED `AGENTS.md` rule in `lib/` and `test/`.
- Shorten the changeset description to ≤80 chars.
* test: drop unused step-2 `tag` in js-contenthash-asset-url
* refactor: resolve sentinels at JS chunk render time, not processAssets
`processAssets` is too late: any plugin that reads the chunk source
between `createChunkAssets` and the `OPTIMIZE_INLINE` stage —
`SourceMapDevToolPlugin` (stage 500), size-optimize plugins (stage 400),
banner injection, etc. — would see unresolved
`__WEBPACK_HTML_CHUNK_URL__…__END__` placeholders embedded in the JS
module's export string.
Tap `JavascriptModulesPlugin.getCompilationHooks(compilation).render`
instead — fires for every JS chunk after `createHash` populates the
content hashes (so `getPath` resolves `[contenthash]` / `[chunkhash]`
/ `[fullhash]`) and *during* chunk asset assembly, so the source every
later pass reads is already sentinel-free. The per-chunk RawSource
identity cache stays in place so warm rebuilds with byte-identical
output keep the same source object and don't invalidate
`RealContentHashPlugin|analyse`.
* refactor: prune sentinel-resolved source cache in afterSeal
Address Copilot review: in long-running watch sessions, chunks that
get removed from the graph (e.g. a dynamic import the user just
deleted) would leave their entries behind in the per-chunk RawSource
cache forever. Tap `compilation.hooks.afterSeal` to drop entries for
chunk IDs no longer in `compilation.chunks`. Also shorten the render-
tap comment to two lines per the AGENTS.md rule.
* refactor: trim two more multi-line comments to two lines
Address Copilot review — shorten the `sentinelResolvedSourceCache`
declaration comment (4 lines → 2) and the `afterSeal` prune comment
(3 lines → 2) per the AGENTS.md "Code comments" REQUIRED rule.
* docs: refresh stale processAssets mention in JS-export comment
The JS-export-path comment in HtmlGenerator still pointed at the old
`processAssets` resolution pass; that moved to `JavascriptModulesPlugin.render`
in abaaf97. Update the comment to match.1 parent c05beb9 commit 26e346a
41 files changed
Lines changed: 698 additions & 116 deletions
File tree
- .changeset
- lib
- dependencies
- html
- test/watchCases/long-term-caching
- html-contenthash-asset-url
- 0
- 1
- 2
- html-contenthash-inline-script
- 0
- 1
- html-contenthash-inline-style-url
- 0
- 1
- 2
- js-contenthash-asset-url
- 0
- 1
- 2
Some content is hidden
Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
101 | 101 | | |
102 | 102 | | |
103 | 103 | | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
104 | 114 | | |
105 | 115 | | |
106 | 116 | | |
| |||
177 | 187 | | |
178 | 188 | | |
179 | 189 | | |
180 | | - | |
| 190 | + | |
181 | 191 | | |
182 | 192 | | |
183 | 193 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
4 | 4 | | |
5 | 5 | | |
6 | 6 | | |
| 7 | + | |
7 | 8 | | |
8 | | - | |
9 | 9 | | |
10 | 10 | | |
11 | 11 | | |
| |||
101 | 101 | | |
102 | 102 | | |
103 | 103 | | |
104 | | - | |
105 | | - | |
106 | | - | |
107 | | - | |
108 | | - | |
109 | | - | |
110 | | - | |
111 | | - | |
112 | | - | |
113 | | - | |
114 | | - | |
115 | | - | |
116 | | - | |
| 104 | + | |
| 105 | + | |
117 | 106 | | |
118 | 107 | | |
119 | 108 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
10 | 10 | | |
11 | 11 | | |
12 | 12 | | |
| 13 | + | |
13 | 14 | | |
14 | 15 | | |
15 | 16 | | |
| 17 | + | |
16 | 18 | | |
17 | 19 | | |
18 | 20 | | |
| 21 | + | |
19 | 22 | | |
20 | 23 | | |
21 | 24 | | |
| |||
45 | 48 | | |
46 | 49 | | |
47 | 50 | | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
48 | 65 | | |
49 | 66 | | |
50 | 67 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
9 | 9 | | |
10 | 10 | | |
11 | 11 | | |
| 12 | + | |
12 | 13 | | |
13 | | - | |
14 | 14 | | |
15 | 15 | | |
16 | 16 | | |
| |||
95 | 95 | | |
96 | 96 | | |
97 | 97 | | |
98 | | - | |
99 | | - | |
100 | | - | |
101 | | - | |
102 | | - | |
103 | | - | |
104 | | - | |
105 | | - | |
106 | | - | |
107 | | - | |
108 | | - | |
109 | | - | |
110 | | - | |
111 | | - | |
112 | | - | |
113 | | - | |
114 | | - | |
115 | | - | |
116 | | - | |
117 | | - | |
118 | | - | |
119 | | - | |
120 | | - | |
121 | | - | |
122 | | - | |
123 | | - | |
124 | | - | |
125 | | - | |
126 | | - | |
127 | | - | |
128 | | - | |
129 | | - | |
130 | | - | |
131 | 98 | | |
132 | 99 | | |
133 | 100 | | |
| |||
410 | 377 | | |
411 | 378 | | |
412 | 379 | | |
413 | | - | |
414 | | - | |
415 | | - | |
| 380 | + | |
| 381 | + | |
416 | 382 | | |
417 | | - | |
| 383 | + | |
418 | 384 | | |
419 | | - | |
420 | 385 | | |
421 | | - | |
| 386 | + | |
422 | 387 | | |
423 | 388 | | |
424 | 389 | | |
| |||
446 | 411 | | |
447 | 412 | | |
448 | 413 | | |
449 | | - | |
450 | | - | |
451 | | - | |
452 | | - | |
453 | | - | |
| 414 | + | |
454 | 415 | | |
455 | 416 | | |
456 | 417 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
13 | 13 | | |
14 | 14 | | |
15 | 15 | | |
| 16 | + | |
16 | 17 | | |
17 | 18 | | |
18 | 19 | | |
| 20 | + | |
19 | 21 | | |
20 | 22 | | |
21 | 23 | | |
22 | 24 | | |
23 | 25 | | |
| 26 | + | |
24 | 27 | | |
25 | 28 | | |
26 | 29 | | |
| |||
55 | 58 | | |
56 | 59 | | |
57 | 60 | | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
58 | 76 | | |
59 | 77 | | |
60 | 78 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
17 | 17 | | |
18 | 18 | | |
19 | 19 | | |
| 20 | + | |
| 21 | + | |
20 | 22 | | |
21 | 23 | | |
22 | 24 | | |
| |||
39 | 41 | | |
40 | 42 | | |
41 | 43 | | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
42 | 47 | | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
43 | 114 | | |
44 | 115 | | |
45 | 116 | | |
| |||
268 | 339 | | |
269 | 340 | | |
270 | 341 | | |
| 342 | + | |
271 | 343 | | |
272 | 344 | | |
273 | 345 | | |
274 | | - | |
275 | | - | |
276 | | - | |
277 | | - | |
278 | | - | |
279 | | - | |
280 | | - | |
281 | | - | |
282 | | - | |
283 | | - | |
284 | | - | |
285 | | - | |
286 | | - | |
287 | | - | |
288 | | - | |
289 | | - | |
290 | | - | |
291 | | - | |
292 | | - | |
| 346 | + | |
| 347 | + | |
| 348 | + | |
| 349 | + | |
| 350 | + | |
293 | 351 | | |
294 | 352 | | |
295 | 353 | | |
| |||
306 | 364 | | |
307 | 365 | | |
308 | 366 | | |
309 | | - | |
310 | | - | |
311 | | - | |
| 367 | + | |
312 | 368 | | |
313 | 369 | | |
314 | 370 | | |
315 | 371 | | |
316 | 372 | | |
317 | | - | |
318 | | - | |
| 373 | + | |
319 | 374 | | |
320 | 375 | | |
321 | 376 | | |
| |||
346 | 401 | | |
347 | 402 | | |
348 | 403 | | |
349 | | - | |
350 | | - | |
351 | | - | |
352 | | - | |
| 404 | + | |
353 | 405 | | |
354 | 406 | | |
355 | 407 | | |
| |||
365 | 417 | | |
366 | 418 | | |
367 | 419 | | |
368 | | - | |
369 | | - | |
370 | | - | |
371 | | - | |
372 | | - | |
| 420 | + | |
373 | 421 | | |
374 | 422 | | |
375 | 423 | | |
| |||
0 commit comments