Skip to content

feat(html): generate output.html for entrypoints with dependOn support#21215

Merged
alexander-akait merged 9 commits into
mainfrom
feat/output-html-depend-on
Jun 18, 2026
Merged

feat(html): generate output.html for entrypoints with dependOn support#21215
alexander-akait merged 9 commits into
mainfrom
feat/output-html-depend-on

Conversation

@alexander-akait

Copy link
Copy Markdown
Member

Summary

output.html (and the per-entry html descriptor) emits an HTML file for every non-HTML entrypoint, injecting its initial JS (deferred, or type="module" for ESM output) and CSS chunks — part of the experiments.html roadmap. This builds on and supersedes #21125, additionally making the generated HTML respect dependOn: a dependant entry's page now loads its dependOn targets' 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 new output-html-depend-on, output-html-depend-on-transitive, output-html-depend-on-css, and output-html-split-chunks cases (load order, no shared-module duplication, runtime execution).

Does this PR introduce a breaking change?

No. output.html defaults to false.

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

The output.html output option and the per-entry html descriptor option should be added to the configuration docs.

Use of AI

AI (Claude Code) assisted with the dependOn investigation, implementation, and tests; all changes were reviewed, built, and verified manually per the webpack AI policy.


Generated by Claude Code

aryanraj45 and others added 3 commits June 18, 2026 11:40
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-bot

changeset-bot Bot commented Jun 18, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: fcb771e

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

Copy link
Copy Markdown
Contributor

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

Install it locally:

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

Comment thread test/configCases/html/output-html-depend-on-transitive/test.js Fixed
Comment thread test/configCases/html/output-html-depend-on/test.js Fixed
Comment thread test/configCases/html/output-html-split-chunks/test.js Fixed
@codecov

codecov Bot commented Jun 18, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 97.61905% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 92.76%. Comparing base (2b7d64e) to head (fcb771e).
⚠️ Report is 3 commits behind head on main.

Files with missing lines Patch % Lines
lib/dependencies/HtmlScriptSrcDependency.js 95.23% 1 Missing ⚠️
lib/html/HtmlModulesPlugin.js 97.82% 1 Missing ⚠️
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     
Flag Coverage Δ
css-parsing 28.70% <92.85%> (+0.01%) ⬆️
html5lib 31.15% <34.52%> (-0.01%) ⬇️
integration 88.73% <97.61%> (+0.03%) ⬆️
test262 45.47% <92.85%> (-0.05%) ⬇️
unit 41.09% <47.36%> (-0.01%) ⬇️

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.

…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.
@codspeed-hq

codspeed-hq Bot commented Jun 18, 2026

Copy link
Copy Markdown

Merging this PR will degrade performance by 38.53%

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

⚡ 2 improved benchmarks
❌ 4 regressed benchmarks
✅ 138 untouched benchmarks

Warning

Please fix the performance issues or acknowledge them on CodSpeed.

Performance Changes

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)

Open in CodSpeed

`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.
Comment thread test/configCases/html/output-html-crossorigin-template/test.js Fixed
Comment thread test/configCases/html/output-html-crossorigin/test.js Fixed
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.
@github-actions

Copy link
Copy Markdown
Contributor

Types Coverage

Coverage after merging feat/output-html-depend-on 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-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%
   CleanPlugin.js99.15%100%100%99.15%206, 226
   CodeGenerationResults.js100%100%100%100%
   CompatibilityPlugin.js100%100%100%100%
   Compilation.js98.43%100%100%98.43%1618, 1937, 1944, 1952, 1974, 1977, 2917, 3396–3397, 3429, 4095, 4125, 4178–4179, 4183, 4188, 4204–4205, 4219–4220, 4225–4226, 4703, 4729, 514, 519, 5537, 5569, 5586, 5602, 5618, 5633, 5658–5659, 5661, 5989, 5994, 6000, 6003, 6015, 6017, 6021, 6037, 6052, 6084, 6138, 6162, 6276, 764–765
   Compiler.js99.56%100%100%99.56%1135–1136, 1144
   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.35%100%100%98.35%435, 481
   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.50%100%100%99.50%182, 2252–2253, 2256, 2267, 2278, 2289, 278, 3693, 3708, 3732
   FlagAllModulesAsUsedPlugin.js100%100%100%100%
   FlagDependencyExportsPlugin.js98.46%100%100%98.46%425, 434, 436, 440
   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%1240, 1243, 1260, 1277, 1524, 1558, 1574, 1661, 2017, 2316, 2321–2331, 419, 423, 577
   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, 891
   Stats.js100%100%100%100%
   Template.js100%100%100%100%
   TemplatedPathPlugin.js99.17%100%100%99.17%176–177
   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/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.31%100%100%99.31%1444–1446, 1454, 274, 277, 282, 286
   defineConfig.js100%100%100%100%
   normalization.js99.02%100%100%99.02%191–192, 258, 273
   target.js100%100%100%100%
lib/container
   

@aryanraj45

Copy link
Copy Markdown
Contributor

@alexander-akait thanks for carrying this forward :))

@alexander-akait alexander-akait merged commit 868209f into main Jun 18, 2026
110 of 111 checks passed
@alexander-akait alexander-akait deleted the feat/output-html-depend-on branch June 18, 2026 20:04
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.

3 participants