You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
feat(html): generate output.html for entrypoints with dependOn support (#21215)
* feat: add output.html to generate HTML for non-HTML entrypoints
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.
* refactor: make entry wrapping pluggable via EntryOptionPlugin hook
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).
* feat(html): respect dependOn when generating output.html entrypoints
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.
* test: match src attribute instead of full script tag in output.html tests
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.
* test: use RegExp.exec instead of matchAll for Node 10 compatibility
`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.
* feat(html): mirror output.crossOriginLoading onto injected output.html 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.
* feat(html): apply output.crossOriginLoading to all injected html tags
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.
* test: avoid tag-shaped regexp in output.html crossorigin tests
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.
* refactor(html): drive crossorigin injection from parse-time offsets
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.
---------
Co-authored-by: aryanraj45 <143009186+aryanraj45@users.noreply.github.com>
* Specifies the filename of the output file on disk. You must **not** specify an absolute path here, but the path may contain folders separated by '/'! The specified path is joined with the value of the 'output.path' option to determine the location on disk.
1231
1231
*/
1232
1232
filename?: EntryFilename;
1233
+
/**
1234
+
* Generate an HTML file for this entrypoint with its JS and CSS output chunks injected. Overrides `output.html` for this entry.
1235
+
*/
1236
+
html?: boolean;
1233
1237
/**
1234
1238
* Module(s) that are loaded upon startup.
1235
1239
*/
@@ -2476,6 +2480,10 @@ export interface Output {
2476
2480
* The filename of the Hot Update Main File. It is inside the 'output.path' directory.
2477
2481
*/
2478
2482
hotUpdateMainFilename?: HotUpdateMainFilename;
2483
+
/**
2484
+
* Generate an HTML file for each non-HTML entrypoint with its JS and CSS output chunks injected. Can be overridden per entry via the entry descriptor `html` option.
2485
+
*/
2486
+
html?: boolean;
2479
2487
/**
2480
2488
* Specifies the filename template of non-initial output html files on disk. You must **not** specify an absolute path here, but the path may contain folders separated by '/'! The specified path is joined with the value of the 'output.path' option to determine the location on disk.
* Specifies the filename of output files on disk. You must **not** specify an absolute path here, but the path may contain folders separated by '/'! The specified path is joined with the value of the 'output.path' option to determine the location on disk.
3466
3474
*/
3467
3475
filename?: Filename;
3476
+
/**
3477
+
* Generate an HTML file for this entrypoint with its JS and CSS output chunks injected. Overrides `output.html` for this entry.
3478
+
*/
3479
+
html?: boolean;
3468
3480
/**
3469
3481
* Module(s) that are loaded upon startup. The last one is exported.
* The filename of the Hot Update Main File. It is inside the 'output.path' directory.
4099
4111
*/
4100
4112
hotUpdateMainFilename?: HotUpdateMainFilename;
4113
+
/**
4114
+
* Generate an HTML file for each non-HTML entrypoint with its JS and CSS output chunks injected. Can be overridden per entry via the entry descriptor `html` option.
4115
+
*/
4116
+
html?: boolean;
4101
4117
/**
4102
4118
* Specifies the filename template of non-initial output html files on disk. You must **not** specify an absolute path here, but the path may contain folders separated by '/'! The specified path is joined with the value of the 'output.path' option to determine the location on disk.
* @property {SyncBailHook<[string, string, EntryDescription], string | undefined>} entry transform an entry into a different request (e.g. wrap a non-HTML entry in a synthetic HTML module); return `undefined` to keep the default behavior
@@ -37,6 +37,8 @@ class HtmlScriptSrcDependency extends ModuleDependency {
37
37
* @param {number=} tagOpenEnd position of the character immediately after the opening tag's `>` in the source; combined with `tagStart` this lets the template clone the original opening tag verbatim (preserving attributes like `nonce`, `crossorigin`, `referrerpolicy`, `defer`, `async`) when generating sibling tags
38
38
* @param {boolean=} tagIsNative whether the originating element is the native tag for `elementKind` (`<script>` / `<link>`); decided at parse time from the tag name so the template needn't re-parse the source text. A custom element mapped to a `script`/`stylesheet` source `type` is non-native and gets a freshly synthesized sibling tag instead of a verbatim clone
39
39
* @param {string=} copyableAttrsText the originating tag's `nonce`/`crossorigin`/`referrerpolicy` attribute source spans (leading-space-prefixed, in that fixed order), captured at parse time so synthesized sibling `<link>`/`<script>` tags carry the same CSP/fetch policy without the template re-parsing the tag text; empty when none are present
40
+
* @param {number=} tagNameEnd position right after the originating tag's name (e.g. after `<script`), captured at parse time so the template can insert a `crossorigin` attribute without re-scanning the tag text for the name boundary
41
+
* @param {boolean=} hasOwnCrossOrigin whether the originating tag already carries a `crossorigin` attribute; when true the author's value wins and `output.crossOriginLoading` is not applied
40
42
*/
41
43
constructor(
42
44
request,
@@ -47,7 +49,9 @@ class HtmlScriptSrcDependency extends ModuleDependency {
47
49
tagStart,
48
50
tagOpenEnd,
49
51
tagIsNative,
50
-
copyableAttrsText
52
+
copyableAttrsText,
53
+
tagNameEnd,
54
+
hasOwnCrossOrigin
51
55
){
52
56
super(request);
53
57
this.range=range;
@@ -64,6 +68,10 @@ class HtmlScriptSrcDependency extends ModuleDependency {
0 commit comments