feat(html): generate output.html for entrypoints with dependOn support#21215
Conversation
When output.html (or an entry's `html` option) is enabled, the entrypoint is wrapped in a synthetic HTML module (<script src> for JS, <link> for CSS) that flows through the existing HtmlModulesPlugin/HtmlGenerator pipeline, so chunk injection, publicPath and the template option work like a real `entry: "./index.html"`. Adds `html` to the entry descriptor for per-entry control.
Move the output.html entry-wrapping logic out of EntryOptionPlugin into a tap on a new EntryOptionPlugin.getHooks(compiler).entry SyncBailHook, and register it from HtmlModulesPlugin. Other plugins can now redirect a non-HTML entry to a custom request (e.g. markdown).
The output.html wrapper turned each entry's imports into a synthetic HTML module, but dropped the entry's dependOn relationship. A dependant page neither loaded its dependOn target's chunk nor deduplicated the shared modules — the shared code was inlined into the dependant's own chunk. Resolve the transitive dependOn chain when wrapping an entry and inject the ancestors' scripts/styles first, so the existing leader-only sub-entry chaining and getEntrypointChunksInLoadOrder deduplicate the shared chunks. Diamond graphs load each shared file once; CSS links go in <head>, scripts in <body>. Add config cases for basic/transitive/diamond dependOn, CSS in a dependOn target, and runtimeChunk + splitChunks injection.
🦋 Changeset detectedLatest commit: fcb771e 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 (868209f). Install it locally:
npm i -D webpack@https://pkg.pr.new/webpack@868209f
yarn add -D webpack@https://pkg.pr.new/webpack@868209f
pnpm add -D webpack@https://pkg.pr.new/webpack@868209f |
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #21215 +/- ##
==========================================
+ Coverage 92.74% 92.76% +0.01%
==========================================
Files 591 591
Lines 64330 64444 +114
Branches 17874 17914 +40
==========================================
+ Hits 59665 59782 +117
+ Misses 4665 4662 -3
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
…ests CodeQL's bad-HTML-tag-filter rule flagged the `<script ...></script>` regexp. These tests parse webpack's own deterministic output, so it's a false positive, but matching the `src` attribute (and using indexOf for the CSS ordering check) avoids the alert and is more robust to whitespace.
Merging this PR will degrade performance by 38.53%
|
| Mode | Benchmark | BASE |
HEAD |
Efficiency | |
|---|---|---|---|---|---|
| ❌ | Memory | benchmark "lodash", scenario '{"name":"mode-development-rebuild","mode":"development","watch":true}' |
125.8 KB | 858.4 KB | -85.34% |
| ❌ | Memory | benchmark "wasm-modules-sync", scenario '{"name":"mode-development-rebuild","mode":"development","watch":true}' |
129.3 KB | 250.5 KB | -48.39% |
| ❌ | Memory | benchmark "wasm-modules-async", scenario '{"name":"mode-development-rebuild","mode":"development","watch":true}' |
190 KB | 330.5 KB | -42.52% |
| ❌ | Memory | benchmark "asset-modules-bytes", scenario '{"name":"mode-development-rebuild","mode":"development","watch":true}' |
246.6 KB | 323.8 KB | -23.83% |
| ⚡ | Memory | benchmark "future-defaults", scenario '{"name":"mode-production","mode":"production"}' |
10.4 MB | 8.1 MB | +28.5% |
| ⚡ | Memory | benchmark "context-esm", scenario '{"name":"mode-production","mode":"production"}' |
9.1 MB | 7.2 MB | +26.76% |
Tip
Investigate this regression by commenting @codspeedbot fix this regression on this PR, or directly use the CodSpeed MCP with your agent.
Comparing feat/output-html-depend-on (fcb771e) with main (eef17e3)
`String.prototype.matchAll` is Node 12+, but webpack's integration tests run on Node 10.x. Replace it with an `exec` loop in the output.html dependOn/split-chunks tests.
…l tags When output.crossOriginLoading is set, the synthetic output.html wrapper now adds a matching crossorigin attribute to the injected <script>/<link> tags. Because crossorigin is a copyable sibling attribute, it propagates to every cloned JS sibling and synthesized CSS link too. integrity is still dropped (content-specific); left a TODO to emit per-chunk SRI once a core option exists.
Move crossorigin handling out of the synthetic output.html wrapper and into HtmlScriptSrcDependency so it covers every injected tag — the rewritten entry tag plus all cloned/synthesized sibling <script>/<link> tags — for both output.html and real .html template entries. An author-set crossorigin is preserved; otherwise output.crossOriginLoading is used. Matches Vite (emits crossorigin on all injected tags) and webpack's runtime chunk loading.
CodeQL's bad-HTML-tag-filter rule flagged the `<script ...></script>` matcher. These tests parse webpack's own deterministic output, so assert via attribute matching and occurrence counts instead of a tag regexp.
Replace the tag-name and crossorigin-detection regexps in HtmlScriptSrcDependency with data captured by HtmlParser: the tag-name end offset and a hasOwnCrossOrigin flag. The template now inserts the attribute at a known offset instead of re-scanning the tag text.
|
@alexander-akait thanks for carrying this forward :)) |
Summary
output.html(and the per-entryhtmldescriptor) emits an HTML file for every non-HTML entrypoint, injecting its initial JS (deferred, ortype="module"for ESM output) and CSS chunks — part of theexperiments.htmlroadmap. This builds on and supersedes #21125, additionally making the generated HTML respectdependOn: a dependant entry's page now loads itsdependOntargets' chunks first (transitively, with diamond graphs deduplicated) and no longer inlines the shared modules into the dependant's own chunk. Closes #21125.What kind of change does this PR introduce?
feat
Did you add tests for your changes?
Yes —
test/configCases/html/output-html-*, including newoutput-html-depend-on,output-html-depend-on-transitive,output-html-depend-on-css, andoutput-html-split-chunkscases (load order, no shared-module duplication, runtime execution).Does this PR introduce a breaking change?
No.
output.htmldefaults tofalse.If relevant, what needs to be documented once your changes are merged or what have you already documented?
The
output.htmloutput option and the per-entryhtmldescriptor option should be added to the configuration docs.Use of AI
AI (Claude Code) assisted with the
dependOninvestigation, implementation, and tests; all changes were reviewed, built, and verified manually per the webpack AI policy.Generated by Claude Code