webpack 5.108.4 → 5.109.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/webpack.js +12 -0
- package/lib/APIPlugin.js +48 -29
- package/lib/CacheFacade.js +8 -0
- package/lib/ChunkGraph.js +17 -15
- package/lib/ChunkGroup.js +1 -0
- package/lib/CompatibilityPlugin.js +62 -29
- package/lib/Compilation.js +58 -6
- package/lib/ConstPlugin.js +11 -11
- package/lib/ContextModule.js +239 -6
- package/lib/ContextModuleFactory.js +139 -14
- package/lib/DefinePlugin.js +433 -112
- package/lib/Entrypoint.js +1 -1
- package/lib/ExportsInfo.js +22 -5
- package/lib/ExportsInfoApiPlugin.js +2 -2
- package/lib/ExternalModule.js +107 -5
- package/lib/ExternalModuleFactoryPlugin.js +20 -1
- package/lib/FileSystemInfo.js +256 -100
- package/lib/FlagDependencyUsagePlugin.js +33 -12
- package/lib/HotModuleReplacementPlugin.js +18 -15
- package/lib/InitFragment.js +20 -32
- package/lib/Module.js +3 -0
- package/lib/ModuleFilenameHelpers.js +2 -2
- package/lib/ModuleTypeConstants.js +8 -1
- package/lib/MultiCompiler.js +13 -2
- package/lib/MultiWatching.js +1 -0
- package/lib/NodeStuffPlugin.js +51 -33
- package/lib/NormalModule.js +175 -401
- package/lib/NormalModuleFactory.js +76 -2
- package/lib/ProgressPlugin.js +211 -119
- package/lib/ProvidePlugin.js +2 -2
- package/lib/RuntimeGlobals.js +47 -0
- package/lib/RuntimePlugin.js +67 -107
- package/lib/RuntimeTemplate.js +304 -16
- package/lib/Template.js +23 -3
- package/lib/UseStrictPlugin.js +1 -1
- package/lib/WebpackIsIncludedPlugin.js +1 -1
- package/lib/WebpackOptionsApply.js +24 -19
- package/lib/asset/AssetGenerator.js +110 -28
- package/lib/asset/AssetModulesPlugin.js +92 -2
- package/lib/asset/WebManifestGenerator.js +164 -0
- package/lib/asset/WebManifestParser.js +130 -0
- package/lib/async-modules/AwaitDependenciesInitFragment.js +14 -4
- package/lib/async-modules/isGeneratorLowered.js +26 -0
- package/lib/cache/PackFileCacheStrategy.js +9 -3
- package/lib/config/browserslistTargetHandler.js +40 -0
- package/lib/config/defaults.js +219 -29
- package/lib/config/normalization.js +26 -0
- package/lib/config/target.js +9 -1
- package/lib/container/HoistContainerReferencesPlugin.js +47 -55
- package/lib/css/CssGenerator.js +40 -31
- package/lib/css/CssLoadingRuntimeModule.js +5 -1
- package/lib/css/CssModulesPlugin.js +13 -0
- package/lib/css/CssParser.js +729 -313
- package/lib/css/syntax.js +1371 -861
- package/lib/dependencies/AMDDefineDependencyParserPlugin.js +6 -6
- package/lib/dependencies/AMDPlugin.js +2 -2
- package/lib/dependencies/AMDRequireDependenciesBlockParserPlugin.js +13 -13
- package/lib/dependencies/CommonJsDependencyHelpers.js +39 -0
- package/lib/dependencies/CommonJsExportRequireDependency.js +19 -0
- package/lib/dependencies/CommonJsExportsDependency.js +47 -2
- package/lib/dependencies/CommonJsExportsParserPlugin.js +10 -10
- package/lib/dependencies/CommonJsImportsParserPlugin.js +18 -18
- package/lib/dependencies/CommonJsPlugin.js +2 -2
- package/lib/dependencies/CommonJsRequireDependency.js +26 -1
- package/lib/dependencies/CommonJsSelfReferenceDependency.js +32 -1
- package/lib/dependencies/ContextDependencyHelpers.js +3 -3
- package/lib/dependencies/CreateRequireParserPlugin.js +6 -6
- package/lib/dependencies/CssIcssExportDependency.js +162 -71
- package/lib/dependencies/CssUrlDependency.js +35 -4
- package/lib/dependencies/ExternalModuleInitFragmentDependency.js +2 -2
- package/lib/dependencies/HarmonyCompatibilityDependency.js +32 -14
- package/lib/dependencies/HarmonyDetectionParserPlugin.js +43 -13
- package/lib/dependencies/HarmonyEvaluatedImportSpecifierDependency.js +2 -1
- package/lib/dependencies/HarmonyExportDependencyParserPlugin.js +7 -19
- package/lib/dependencies/HarmonyExportImportedSpecifierDependency.js +17 -9
- package/lib/dependencies/HarmonyImportDependency.js +13 -2
- package/lib/dependencies/HarmonyImportDependencyParserPlugin.js +11 -11
- package/lib/dependencies/HarmonyImportSpecifierDependency.js +10 -7
- package/lib/dependencies/HarmonyModulesPlugin.js +6 -0
- package/lib/dependencies/HarmonyTopLevelThisParserPlugin.js +1 -1
- package/lib/dependencies/HtmlEntryDependency.js +797 -172
- package/lib/dependencies/HtmlSourceDependency.js +26 -0
- package/lib/dependencies/ImportDependency.js +43 -1
- package/lib/dependencies/ImportMetaContextDependencyParserPlugin.js +31 -18
- package/lib/dependencies/ImportMetaContextPlugin.js +38 -7
- package/lib/dependencies/ImportMetaGlobDependency.js +81 -0
- package/lib/dependencies/ImportMetaGlobDependencyParserPlugin.js +78 -0
- package/lib/dependencies/ImportMetaGlobHelpers.js +660 -0
- package/lib/dependencies/ImportMetaPlugin.js +532 -180
- package/lib/dependencies/ImportMetaResolveDependency.js +90 -0
- package/lib/dependencies/ImportParserPlugin.js +80 -17
- package/lib/dependencies/ImportPhase.js +10 -8
- package/lib/dependencies/LoaderPlugin.js +5 -1
- package/lib/dependencies/RequireContextDependencyParserPlugin.js +1 -1
- package/lib/dependencies/RequireEnsureDependenciesBlockParserPlugin.js +3 -6
- package/lib/dependencies/RequireIncludeDependencyParserPlugin.js +4 -11
- package/lib/dependencies/SystemPlugin.js +2 -4
- package/lib/dependencies/TopLevelAwaitDependency.js +93 -0
- package/lib/dependencies/URLDependency.js +141 -21
- package/lib/dependencies/{WorkerPlugin.js → WorkerAndWorkletPlugin.js} +324 -112
- package/lib/dependencies/WorkerDependency.js +86 -18
- package/lib/dependencies/WorkletDependency.js +278 -0
- package/lib/dll/DelegatedModuleFactoryPlugin.js +19 -0
- package/lib/errors/EnvironmentNotSupportAsyncWarning.js +1 -1
- package/lib/errors/LoaderLoadingError.js +20 -0
- package/lib/errors/ModuleParseError.js +39 -22
- package/lib/errors/NodeStuffInWebError.js +1 -1
- package/lib/esm/ModuleChunkFormatPlugin.js +47 -14
- package/lib/esm/ModuleChunkLoadingPlugin.js +8 -1
- package/lib/esm/ModuleChunkLoadingRuntimeModule.js +29 -1
- package/lib/hmr/lazyCompilationBackend.js +51 -12
- package/lib/html/HtmlGenerator.js +907 -15
- package/lib/html/HtmlModule.js +2 -2
- package/lib/html/HtmlModulesPlugin.js +610 -166
- package/lib/html/HtmlParser.js +603 -247
- package/lib/html/favicon.svg +1 -0
- package/lib/html/syntax.js +3451 -2762
- package/lib/ids/IdHelpers.js +25 -11
- package/lib/index.js +6 -0
- package/lib/javascript/ArrayPushCallbackChunkFormatPlugin.js +4 -5
- package/lib/javascript/BasicEvaluatedExpression.js +366 -48
- package/lib/javascript/JavascriptGenerator.js +197 -49
- package/lib/javascript/JavascriptModulesPlugin.js +178 -24
- package/lib/javascript/JavascriptParser.js +704 -355
- package/lib/javascript/JavascriptParserHelpers.js +3 -6
- package/lib/javascript/syntax.js +5037 -0
- package/lib/library/ModuleLibraryPlugin.js +38 -1
- package/lib/loaders/LoaderRunner.js +714 -0
- package/lib/loaders/loadLoader.js +100 -0
- package/lib/node/ReadFileCompileAsyncWasmPlugin.js +29 -2
- package/lib/node/ReadFileCompileWasmPlugin.js +24 -2
- package/lib/optimize/AggressiveMergingPlugin.js +19 -15
- package/lib/optimize/ConcatenatedModule.js +139 -31
- package/lib/optimize/FlagIncludedChunksPlugin.js +13 -3
- package/lib/optimize/InnerGraphPlugin.js +50 -30
- package/lib/optimize/ModuleConcatenationPlugin.js +139 -24
- package/lib/optimize/SideEffectsFlagPlugin.js +86 -43
- package/lib/optimize/SplitChunksPlugin.js +146 -44
- package/lib/prefetch/ChunkPrefetchPreloadPlugin.js +15 -0
- package/lib/prefetch/ChunkPreloadTriggerRuntimeModule.js +24 -13
- package/lib/prefetch/ResourceHintPlugin.js +687 -0
- package/lib/prefetch/ResourceHintRuntimeModule.js +149 -0
- package/lib/prefetch/StartupAssetHintRuntimeModule.js +210 -0
- package/lib/prefetch/parseResourceHintOptions.js +103 -0
- package/lib/rules/RuleSetCompiler.js +114 -0
- package/lib/runtime/AsyncModuleGeneratorRuntimeModule.js +56 -0
- package/lib/runtime/AsyncModuleRuntimeModule.js +24 -8
- package/lib/runtime/AutoPublicPathRuntimeModule.js +7 -1
- package/lib/runtime/CommonJsWrapRuntimeModule.js +37 -0
- package/lib/runtime/GetWorkletBootstrapRuntimeModule.js +59 -0
- package/lib/runtime/MakeDeferredNamespaceObjectRuntime.js +204 -8
- package/lib/runtime/SetAnonymousDefaultNameRuntimeModule.js +4 -1
- package/lib/schemes/HttpUriPlugin.js +40 -1
- package/lib/schemes/VirtualUrlPlugin.js +1 -2
- package/lib/serialization/BinaryMiddleware.js +270 -938
- package/lib/serialization/FileMiddleware.js +29 -3
- package/lib/stats/DefaultStatsFactoryPlugin.js +229 -194
- package/lib/stats/DefaultStatsPresetPlugin.js +2 -0
- package/lib/stats/DefaultStatsPrinterPlugin.js +36 -168
- package/lib/stats/StatsFactory.js +46 -17
- package/lib/url/URLParserPlugin.js +66 -13
- package/lib/util/AsyncQueue.js +122 -72
- package/lib/util/SetHelpers.js +1 -1
- package/lib/util/StackedMap.js +122 -48
- package/lib/util/binarySearchBounds.js +2 -2
- package/lib/util/concatenate.js +40 -14
- package/lib/util/createHooksRegistry.js +13 -5
- package/lib/util/dataURL.js +2 -2
- package/lib/util/deterministicGrouping.js +7 -5
- package/lib/util/formatSize.js +6 -2
- package/lib/util/globUtils.js +710 -0
- package/lib/util/identifier.js +23 -6
- package/lib/util/implicitTypeLoaderFallback.js +62 -0
- package/lib/util/internalSerializables.js +88 -71
- package/lib/util/magicComment.js +121 -25
- package/lib/util/numberHash.js +2 -2
- package/lib/wasm-async/AsyncWasmCompileRuntimeModule.js +24 -18
- package/lib/wasm-async/AsyncWasmLoadingRuntimeModule.js +33 -22
- package/lib/wasm-sync/WasmChunkLoadingRuntimeModule.js +79 -16
- package/lib/web/FetchCompileAsyncWasmPlugin.js +19 -0
- package/lib/web/FetchCompileWasmPlugin.js +14 -0
- package/lib/web/JsonpChunkLoadingRuntimeModule.js +2 -0
- package/lib/webpack.js +65 -0
- package/lib/webworker/ImportScriptsChunkLoadingRuntimeModule.js +3 -0
- package/module.d.ts +21 -0
- package/package.json +12 -11
- package/schemas/WebpackOptions.check.js +1 -1
- package/schemas/WebpackOptions.json +759 -36
- package/schemas/plugins/HtmlParserOptions.check.js +1 -1
- package/schemas/plugins/ProgressPlugin.check.js +1 -1
- package/schemas/plugins/ProgressPlugin.json +17 -1
- package/schemas/plugins/container/ContainerReferencePlugin.check.js +1 -1
- package/schemas/plugins/container/ContainerReferencePlugin.json +1 -0
- package/schemas/plugins/container/ExternalsType.check.js +1 -1
- package/schemas/plugins/container/ModuleFederationPlugin.check.js +1 -1
- package/schemas/plugins/container/ModuleFederationPlugin.json +1 -0
- package/schemas/plugins/css/CssAutoOrModuleParserOptions.check.js +1 -1
- package/schemas/plugins/css/CssModuleParserOptions.check.js +1 -1
- package/schemas/plugins/css/CssParserOptions.check.js +1 -1
- package/types.d.ts +1212 -99
|
@@ -4,7 +4,10 @@
|
|
|
4
4
|
|
|
5
5
|
"use strict";
|
|
6
6
|
|
|
7
|
+
const { pathToFileURL } = require("url");
|
|
8
|
+
const { AsyncSeriesHook, AsyncSeriesWaterfallHook } = require("tapable");
|
|
7
9
|
const { RawSource } = require("webpack-sources");
|
|
10
|
+
const Compilation = require("../Compilation");
|
|
8
11
|
const EntryOptionPlugin = require("../EntryOptionPlugin");
|
|
9
12
|
const EntryPlugin = require("../EntryPlugin");
|
|
10
13
|
const HotUpdateChunk = require("../HotUpdateChunk");
|
|
@@ -19,21 +22,149 @@ const HtmlInlineStyleDependency = require("../dependencies/HtmlInlineStyleDepend
|
|
|
19
22
|
const HtmlSourceDependency = require("../dependencies/HtmlSourceDependency");
|
|
20
23
|
const StaticExportsDependency = require("../dependencies/StaticExportsDependency");
|
|
21
24
|
const WebpackError = require("../errors/WebpackError");
|
|
25
|
+
const JavascriptModulesPlugin = require("../javascript/JavascriptModulesPlugin");
|
|
22
26
|
const { compareModulesByFullName } = require("../util/comparators");
|
|
23
27
|
const createHash = require("../util/createHash");
|
|
28
|
+
const createHooksRegistry = require("../util/createHooksRegistry");
|
|
29
|
+
const { getUndoPath, makePathsRelative } = require("../util/identifier");
|
|
30
|
+
const implicitTypeLoaderFallback = require("../util/implicitTypeLoaderFallback");
|
|
31
|
+
const memoize = require("../util/memoize");
|
|
24
32
|
const { digestNonNumericOnly } = require("../util/nonNumericOnlyHash");
|
|
33
|
+
const {
|
|
34
|
+
PUBLIC_PATH_AUTO: autoPlaceholder
|
|
35
|
+
} = require("../util/publicPathPlaceholder");
|
|
25
36
|
const removeBOM = require("../util/removeBOM");
|
|
26
37
|
const HtmlGenerator = require("./HtmlGenerator");
|
|
27
38
|
const HtmlModule = require("./HtmlModule");
|
|
28
39
|
const HtmlParser = require("./HtmlParser");
|
|
40
|
+
const { escapeAttribute } = require("./syntax");
|
|
29
41
|
|
|
30
42
|
/** @typedef {import("../../declarations/WebpackOptions").EntryDescriptionNormalized} EntryDescriptionNormalized */
|
|
43
|
+
/** @typedef {import("../../declarations/WebpackOptions").OutputHtmlOptions["favicon"]} FaviconOption */
|
|
44
|
+
/** @typedef {import("../../declarations/WebpackOptions").OutputHtmlOptions["manifest"]} ManifestOption */
|
|
45
|
+
/** @typedef {import("../../declarations/WebpackOptions").HtmlFaviconIcon} HtmlFaviconIcon */
|
|
46
|
+
/** @typedef {Exclude<HtmlFaviconIcon, string>} FaviconIcon */
|
|
31
47
|
/** @typedef {import("../Compiler")} Compiler */
|
|
32
48
|
/** @typedef {import("./HtmlModule").HtmlModuleBuildInfo} HtmlModuleBuildInfo */
|
|
33
|
-
/** @typedef {{ request: string, entryName: string, type: "script" | "script-module" | "modulepreload" | "stylesheet" }}
|
|
49
|
+
/** @typedef {{ request: string, entryName: string, type: "script" | "script-module" | "modulepreload" | "stylesheet" | "html" | "preload" | "prefetch", css?: boolean }} HtmlEntryInfo */
|
|
50
|
+
|
|
51
|
+
/** @typedef {{ outputName: string }} HtmlTransformHtmlContext */
|
|
52
|
+
/** @typedef {{ outputName: string }} HtmlEmittedContext */
|
|
53
|
+
/**
|
|
54
|
+
* A tag to inject into an emitted page as a structured descriptor. Injected
|
|
55
|
+
* verbatim (not re-bundled) at the `injectTo` position.
|
|
56
|
+
* @typedef {object} HtmlTagDescriptor
|
|
57
|
+
* @property {string} tag tag name, e.g. `"script"` / `"link"` / `"meta"`
|
|
58
|
+
* @property {Record<string, string | boolean | undefined>=} attrs attributes; `true` renders a bare boolean attribute, `false`/`undefined` is omitted
|
|
59
|
+
* @property {string=} children inner content (ignored for void elements like `<link>`/`<meta>`)
|
|
60
|
+
* @property {("head" | "body" | "head-prepend" | "body-prepend")=} injectTo placement; defaults to `"head"`
|
|
61
|
+
* @property {boolean=} voidTag force a void element (no closing tag); inferred from the tag name when omitted
|
|
62
|
+
*/
|
|
63
|
+
/** @typedef {{ outputName: string, html: string }} HtmlInjectTagsContext */
|
|
64
|
+
/**
|
|
65
|
+
* A `<script>`/`<link>`/`<style>`/`<meta>` tag already present in an emitted
|
|
66
|
+
* page, exposed for in-place mutation by `transformTags`. Mutate `attrs`, set
|
|
67
|
+
* `remove`, or change `injectTo` to move it; don't reorder the array itself.
|
|
68
|
+
* @typedef {object} HtmlMutableTag
|
|
69
|
+
* @property {string} tag the (lowercased) tag name
|
|
70
|
+
* @property {Record<string, string | boolean | undefined>} attrs mutable attributes; a string value renders `name="value"`, `true` a bare attribute, `false`/`undefined`/deleting the key drops it
|
|
71
|
+
* @property {("head" | "body" | "head-prepend" | "body-prepend")=} injectTo the tag's current region (`"head"`/`"body"`); set a different value to move it there (`*-prepend` to the region's start)
|
|
72
|
+
* @property {boolean=} remove set true to delete the whole element
|
|
73
|
+
*/
|
|
74
|
+
/** @typedef {{ outputName: string, html: string }} HtmlTransformTagsContext */
|
|
75
|
+
/**
|
|
76
|
+
* @typedef {object} HtmlCompilationHooks
|
|
77
|
+
* @property {AsyncSeriesWaterfallHook<[HtmlTagDescriptor[], HtmlInjectTagsContext]>} injectTags called with the list of extra tags to inject into each page (initially empty) plus the current HTML; push `HtmlTagDescriptor`s and return the list — webpack serializes and places them by `injectTo`. A structured alternative to the string-level `transformHtml` for adding tags; runs before CSP so injected inline tags are hashed
|
|
78
|
+
* @property {AsyncSeriesHook<[HtmlMutableTag[], HtmlTransformTagsContext]>} transformTags called with the page's `<script>`/`<link>`/`<style>`/`<meta>` tags (webpack's own and any injected) as mutable descriptors; mutate `attrs` (add a `nonce`/`data-*`, switch `defer`↔`async`, …), set `remove: true`, or change `injectTo` to move a tag between `<head>` and `<body>`, and webpack rewrites the changed tags. Add new tags with `injectTags` instead
|
|
79
|
+
* @property {AsyncSeriesWaterfallHook<[string, HtmlTransformHtmlContext]>} transformHtml called with each emitted page's final HTML (all sentinels resolved) just before it is written; return the (possibly transformed) HTML — e.g. to minify, inject a CSP meta, or rewrite tags
|
|
80
|
+
* @property {AsyncSeriesHook<[HtmlEmittedContext]>} htmlEmitted called once each page's HTML asset has been finalized — a post-emit notification (nothing to return)
|
|
81
|
+
*/
|
|
34
82
|
|
|
35
83
|
const PLUGIN_NAME = "HtmlModulesPlugin";
|
|
36
84
|
|
|
85
|
+
// Built-in default favicon (the webpack logo). Referenced as a `file:` request
|
|
86
|
+
// so `<link rel="icon">` in the synthetic wrapper flows through the normal
|
|
87
|
+
// asset pipeline — emitted hashed, with the correct publicPath, no direct IO.
|
|
88
|
+
// Resolved from `__filename` (a sibling of this module) — no `path` needed.
|
|
89
|
+
const DEFAULT_FAVICON = new URL("./favicon.svg", pathToFileURL(__filename))
|
|
90
|
+
.href;
|
|
91
|
+
|
|
92
|
+
// mime-db is heavy — only load it when a favicon actually needs a `type`.
|
|
93
|
+
const getMimeTypes = memoize(() => require("../util/mimeTypes"));
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Resolves `output.html.favicon` for one page to `[rel, icon]` pairs.
|
|
97
|
+
* `false`/absent → none; `true` → the webpack logo; a string → one `icon`
|
|
98
|
+
* link; an object maps each `rel` to an icon (a path string, an attributes
|
|
99
|
+
* object, or an array of those for several icons under one `rel`); a function
|
|
100
|
+
* receives the page name and returns any of those. Each icon is normalized to
|
|
101
|
+
* an object so `faviconLinkTag` reads one shape.
|
|
102
|
+
* @param {FaviconOption} favicon favicon option
|
|
103
|
+
* @param {string} name page/entry name
|
|
104
|
+
* @returns {[string, FaviconIcon][]} rel/icon pairs, in order
|
|
105
|
+
*/
|
|
106
|
+
const resolveFaviconLinks = (favicon, name) => {
|
|
107
|
+
const value = typeof favicon === "function" ? favicon(name) : favicon;
|
|
108
|
+
if (value === true) return [["icon", { href: DEFAULT_FAVICON }]];
|
|
109
|
+
if (typeof value === "string") return [["icon", { href: value }]];
|
|
110
|
+
if (value && typeof value === "object") {
|
|
111
|
+
/** @type {[string, FaviconIcon][]} */
|
|
112
|
+
const pairs = [];
|
|
113
|
+
for (const [rel, icon] of Object.entries(value)) {
|
|
114
|
+
for (const one of Array.isArray(icon) ? icon : [icon]) {
|
|
115
|
+
pairs.push([rel, typeof one === "string" ? { href: one } : one]);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return pairs;
|
|
119
|
+
}
|
|
120
|
+
return [];
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* @param {string} rel link relation
|
|
125
|
+
* @param {FaviconIcon} icon icon `href` plus optional link attributes
|
|
126
|
+
* @returns {string} a `<link rel=… [attrs…] href=…>` tag; `type` defaults to the file format
|
|
127
|
+
*/
|
|
128
|
+
const faviconLinkTag = (rel, icon) => {
|
|
129
|
+
const type = icon.type || getMimeTypes().lookup(icon.href) || "";
|
|
130
|
+
const attrs = [
|
|
131
|
+
`rel="${escapeAttribute(rel)}"`,
|
|
132
|
+
icon.sizes && `sizes="${escapeAttribute(icon.sizes)}"`,
|
|
133
|
+
type && `type="${escapeAttribute(type)}"`,
|
|
134
|
+
icon.media && `media="${escapeAttribute(icon.media)}"`,
|
|
135
|
+
icon.color && `color="${escapeAttribute(icon.color)}"`,
|
|
136
|
+
icon.crossorigin && `crossorigin="${escapeAttribute(icon.crossorigin)}"`,
|
|
137
|
+
`href="${escapeAttribute(icon.href)}"`
|
|
138
|
+
];
|
|
139
|
+
return `<link ${attrs.filter(Boolean).join(" ")}>`;
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Resolves `output.html.manifest` for one page to a `<link rel="manifest">`
|
|
144
|
+
* tag, or `""` when unset. A string is a path to an existing `.webmanifest`
|
|
145
|
+
* file; an object is serialized to a base64 `data:application/manifest+json`
|
|
146
|
+
* URL (routed to `asset/webmanifest` by a default rule, so its relative icon
|
|
147
|
+
* `src`s are hashed like any request); a function receives the page name.
|
|
148
|
+
* Base64 (not percent-encoding) is required because this tag is embedded in
|
|
149
|
+
* the outer `data:text/html` wrapper, which is itself percent-decoded — a
|
|
150
|
+
* percent-encoded inner URL would be corrupted by that decode.
|
|
151
|
+
* @param {ManifestOption} manifest manifest option
|
|
152
|
+
* @param {string} name page/entry name
|
|
153
|
+
* @returns {string} the `<link>` tag, or ""
|
|
154
|
+
*/
|
|
155
|
+
const manifestLinkTag = (manifest, name) => {
|
|
156
|
+
const value = typeof manifest === "function" ? manifest(name) : manifest;
|
|
157
|
+
if (!value) return "";
|
|
158
|
+
const href =
|
|
159
|
+
typeof value === "string"
|
|
160
|
+
? value
|
|
161
|
+
: `data:application/manifest+json;base64,${Buffer.from(
|
|
162
|
+
JSON.stringify(value),
|
|
163
|
+
"utf8"
|
|
164
|
+
).toString("base64")}`;
|
|
165
|
+
return `<link rel="manifest" href="${escapeAttribute(href)}">`;
|
|
166
|
+
};
|
|
167
|
+
|
|
37
168
|
// `\.html`/`\.css` request matchers for the synthetic `output.html` wrapper.
|
|
38
169
|
const HTML_REQUEST_RE = /\.html(\?|$)/i;
|
|
39
170
|
const CSS_REQUEST_RE = /\.css(\?|$)/i;
|
|
@@ -118,6 +249,24 @@ class HtmlModulesPlugin {
|
|
|
118
249
|
);
|
|
119
250
|
}
|
|
120
251
|
|
|
252
|
+
/**
|
|
253
|
+
* Filename template for an extracted HTML page: `output.htmlFilename` for
|
|
254
|
+
* initial chunks, `output.htmlChunkFilename` otherwise — the HTML counterpart
|
|
255
|
+
* of `CssModulesPlugin.getChunkFilenameTemplate`.
|
|
256
|
+
* @param {import("../Chunk")} chunk chunk
|
|
257
|
+
* @param {import("../../declarations/WebpackOptions").Output} outputOptions output options
|
|
258
|
+
* @returns {import("../Chunk").ChunkFilenameTemplate} used filename template
|
|
259
|
+
*/
|
|
260
|
+
static getChunkFilenameTemplate(chunk, outputOptions) {
|
|
261
|
+
return chunk.canBeInitial()
|
|
262
|
+
? /** @type {import("../Chunk").ChunkFilenameTemplate} */ (
|
|
263
|
+
outputOptions.htmlFilename
|
|
264
|
+
)
|
|
265
|
+
: /** @type {import("../Chunk").ChunkFilenameTemplate} */ (
|
|
266
|
+
outputOptions.htmlChunkFilename
|
|
267
|
+
);
|
|
268
|
+
}
|
|
269
|
+
|
|
121
270
|
/**
|
|
122
271
|
* Applies the plugin by registering its hooks on the compiler.
|
|
123
272
|
* @param {Compiler} compiler the compiler instance
|
|
@@ -179,9 +328,37 @@ class HtmlModulesPlugin {
|
|
|
179
328
|
scripts.push(`<script${scriptAttr} src="${r}"></script>`);
|
|
180
329
|
}
|
|
181
330
|
}
|
|
182
|
-
|
|
183
|
-
""
|
|
184
|
-
|
|
331
|
+
const htmlObj =
|
|
332
|
+
typeof html === "object"
|
|
333
|
+
? html
|
|
334
|
+
: typeof htmlOption === "object"
|
|
335
|
+
? htmlOption
|
|
336
|
+
: {};
|
|
337
|
+
const headTags = HtmlParser.buildHeadTags(htmlObj);
|
|
338
|
+
// Injected into webpack-generated pages only; each icon's href is a
|
|
339
|
+
// normal request the parser turns into a hashed asset.
|
|
340
|
+
const favicon =
|
|
341
|
+
typeof html === "object" && html.favicon !== undefined
|
|
342
|
+
? html.favicon
|
|
343
|
+
: false;
|
|
344
|
+
const faviconTag = resolveFaviconLinks(favicon, name)
|
|
345
|
+
.map(([rel, icon]) => faviconLinkTag(rel, icon))
|
|
346
|
+
.join("");
|
|
347
|
+
const manifest =
|
|
348
|
+
typeof html === "object" && html.manifest !== undefined
|
|
349
|
+
? html.manifest
|
|
350
|
+
: false;
|
|
351
|
+
const manifestTag = manifestLinkTag(manifest, name);
|
|
352
|
+
const inject = htmlObj.inject;
|
|
353
|
+
const scriptsInHead =
|
|
354
|
+
inject === "head" || (inject !== "body" && output.module);
|
|
355
|
+
const scriptsFirst = output.module || scriptAttr === " defer";
|
|
356
|
+
const headScripts = scriptsInHead ? scripts.join("") : "";
|
|
357
|
+
return `data:text/html,<!doctype html><html><head>${faviconTag}${manifestTag}${headTags}${
|
|
358
|
+
scriptsFirst
|
|
359
|
+
? headScripts + links.join("")
|
|
360
|
+
: links.join("") + headScripts
|
|
361
|
+
}</head><body>${scriptsInHead ? "" : scripts.join("")}</body></html>`;
|
|
185
362
|
}
|
|
186
363
|
);
|
|
187
364
|
|
|
@@ -199,35 +376,46 @@ class HtmlModulesPlugin {
|
|
|
199
376
|
// can never be imported as a runtime leader by a later script —
|
|
200
377
|
// that's what keeps the "preload but don't execute" contract of
|
|
201
378
|
// `<link rel="modulepreload">` intact.
|
|
202
|
-
|
|
203
|
-
|
|
379
|
+
// Per-compilation state: the HTML modules seen during make (so
|
|
380
|
+
// `finishMake` creates their entries without scanning the whole module
|
|
381
|
+
// graph) and the stylesheet entry names (read in `afterChunks`).
|
|
382
|
+
/** @type {WeakMap<import("../Compilation"), { htmlModules: Set<import("../Module")>, stylesheetEntries: Set<string>, htmlAssetNames: Set<string> }>} */
|
|
383
|
+
const compilationState = new WeakMap();
|
|
384
|
+
/**
|
|
385
|
+
* @param {import("../Compilation")} compilation compilation
|
|
386
|
+
* @returns {{ htmlModules: Set<import("../Module")>, stylesheetEntries: Set<string>, htmlAssetNames: Set<string> }} per-compilation state
|
|
387
|
+
*/
|
|
388
|
+
const getState = (compilation) => {
|
|
389
|
+
let state = compilationState.get(compilation);
|
|
390
|
+
if (state === undefined) {
|
|
391
|
+
state = {
|
|
392
|
+
htmlModules: new Set(),
|
|
393
|
+
stylesheetEntries: new Set(),
|
|
394
|
+
htmlAssetNames: new Set()
|
|
395
|
+
};
|
|
396
|
+
compilationState.set(compilation, state);
|
|
397
|
+
}
|
|
398
|
+
return state;
|
|
399
|
+
};
|
|
204
400
|
compiler.hooks.finishMake.tapAsync(PLUGIN_NAME, (compilation, callback) => {
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
401
|
+
const { htmlModules, stylesheetEntries } = getState(compilation);
|
|
402
|
+
|
|
403
|
+
// Collect the entries an HTML module asks for. Only the script chains
|
|
404
|
+
// (`script`, `script-module`) share a runtime via a leader-only
|
|
405
|
+
// `dependOn`; `modulepreload`, `stylesheet` and `html` links are all
|
|
406
|
+
// independent entries (a CSS or page entry must not chain into a JS
|
|
407
|
+
// leader, or the chunk would mix unrelated outputs).
|
|
408
|
+
/** @type {(module: import("../Module")) => { context: string, request: string, name: string, dependOn: string[] | undefined }[]} */
|
|
409
|
+
const collectEntrySpecs = (module) => {
|
|
410
|
+
const { htmlEntries } = /** @type {HtmlModuleBuildInfo} */ (
|
|
214
411
|
module.buildInfo
|
|
215
412
|
);
|
|
216
|
-
|
|
217
|
-
if (!htmlEntryScripts) continue;
|
|
218
|
-
|
|
413
|
+
if (!htmlEntries) return [];
|
|
219
414
|
const context = /** @type {string} */ (module.context);
|
|
220
|
-
|
|
221
|
-
for (const [groupKind, group] of Object.entries(
|
|
222
|
-
// Only the script chains (`script`, `script-module`) need a
|
|
223
|
-
// shared runtime via leader-only `dependOn` — the others
|
|
224
|
-
// either preload without executing (`modulepreload`) or
|
|
225
|
-
// produce CSS chunks (`stylesheet`) which have no runtime
|
|
226
|
-
// to share. CSS entries must NOT chain into a JS leader
|
|
227
|
-
// either, because the resulting chunk would mix a CSS
|
|
228
|
-
// stylesheet with a JS runtime.
|
|
415
|
+
const specs = [];
|
|
416
|
+
for (const [groupKind, group] of Object.entries(htmlEntries)) {
|
|
229
417
|
const isChainGroup =
|
|
230
|
-
groupKind
|
|
418
|
+
groupKind === "script" || groupKind === "script-module";
|
|
231
419
|
/** @type {string | undefined} */
|
|
232
420
|
let leaderName;
|
|
233
421
|
for (const entry of group) {
|
|
@@ -238,48 +426,220 @@ class HtmlModulesPlugin {
|
|
|
238
426
|
if (isChainGroup && leaderName === undefined) {
|
|
239
427
|
leaderName = entry.entryName;
|
|
240
428
|
}
|
|
241
|
-
|
|
429
|
+
// Stylesheet entries and `as="style"` preload/prefetch entries emit
|
|
430
|
+
// a CSS chunk, so they need the CSS filename template below.
|
|
431
|
+
if (groupKind === "stylesheet" || entry.css) {
|
|
242
432
|
stylesheetEntries.add(entry.entryName);
|
|
243
433
|
}
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
}),
|
|
251
|
-
{
|
|
252
|
-
name: entry.entryName,
|
|
253
|
-
// Each script src / modulepreload entry gets its own
|
|
254
|
-
// filename derived from the synthetic entry name so it
|
|
255
|
-
// doesn't collide with the user's `output.filename`.
|
|
256
|
-
// For CSS entries the JS `filename` is irrelevant (no
|
|
257
|
-
// `.js` is emitted) — the CSS file's name is set on the
|
|
258
|
-
// chunk via `cssFilenameTemplate` in `afterChunks` below.
|
|
259
|
-
filename:
|
|
260
|
-
compilation.outputOptions.chunkFilename || "[name].js",
|
|
261
|
-
dependOn
|
|
262
|
-
},
|
|
263
|
-
(err) => {
|
|
264
|
-
if (err) reject(err);
|
|
265
|
-
else resolve();
|
|
266
|
-
}
|
|
267
|
-
);
|
|
268
|
-
})
|
|
269
|
-
);
|
|
434
|
+
specs.push({
|
|
435
|
+
context,
|
|
436
|
+
request: entry.request,
|
|
437
|
+
name: entry.entryName,
|
|
438
|
+
dependOn
|
|
439
|
+
});
|
|
270
440
|
}
|
|
271
441
|
}
|
|
272
|
-
|
|
442
|
+
return specs;
|
|
443
|
+
};
|
|
444
|
+
|
|
445
|
+
// Push one collected reference as a compilation entry; resolves with
|
|
446
|
+
// the built entry module.
|
|
447
|
+
/** @type {(spec: { context: string, request: string, name: string, dependOn: string[] | undefined }) => Promise<import("../Module") | null>} */
|
|
448
|
+
const addEntry = (spec) =>
|
|
449
|
+
new Promise((resolve, reject) => {
|
|
450
|
+
compilation.addEntry(
|
|
451
|
+
spec.context,
|
|
452
|
+
EntryPlugin.createDependency(spec.request, { name: spec.name }),
|
|
453
|
+
{
|
|
454
|
+
name: spec.name,
|
|
455
|
+
// Each entry gets its own filename from the synthetic entry name so
|
|
456
|
+
// it doesn't collide with `output.filename`. CSS entries set their
|
|
457
|
+
// `.css` name via `cssFilenameTemplate` below; `html` page entries
|
|
458
|
+
// emit their file via `renderManifest`.
|
|
459
|
+
filename: compilation.outputOptions.chunkFilename || "[name].js",
|
|
460
|
+
dependOn: spec.dependOn
|
|
461
|
+
},
|
|
462
|
+
(err, entryModule) =>
|
|
463
|
+
err ? reject(err) : resolve(entryModule || null)
|
|
464
|
+
);
|
|
465
|
+
});
|
|
273
466
|
|
|
274
|
-
|
|
467
|
+
// Create one HTML module's entries. A `type: "html"` link is itself an
|
|
468
|
+
// HTML entry, so after it builds we recurse into it — the same handling
|
|
469
|
+
// every HTML entry already gets. `processed` guards diamonds and cycles.
|
|
470
|
+
/** @type {WeakSet<import("../Module")>} */
|
|
471
|
+
const processed = new WeakSet();
|
|
472
|
+
/** @type {(module: import("../Module")) => Promise<void>} */
|
|
473
|
+
const processEntry = async (module) => {
|
|
474
|
+
if (processed.has(module)) return;
|
|
475
|
+
processed.add(module);
|
|
476
|
+
await Promise.all(
|
|
477
|
+
collectEntrySpecs(module).map(async (spec) => {
|
|
478
|
+
const entryModule = await addEntry(spec);
|
|
479
|
+
if (entryModule && entryModule.type === HTML_MODULE_TYPE) {
|
|
480
|
+
await processEntry(entryModule);
|
|
481
|
+
}
|
|
482
|
+
})
|
|
483
|
+
);
|
|
484
|
+
};
|
|
485
|
+
// Seed with the HTML modules seen during make (config entries /
|
|
486
|
+
// imports, fresh or cache-restored); linked pages are reached by
|
|
487
|
+
// recursion above.
|
|
488
|
+
Promise.all([...htmlModules].map(processEntry)).then(
|
|
275
489
|
() => callback(),
|
|
276
|
-
|
|
490
|
+
callback
|
|
277
491
|
);
|
|
278
492
|
});
|
|
279
493
|
|
|
494
|
+
const integrity =
|
|
495
|
+
typeof htmlOption === "object" ? htmlOption.integrity : undefined;
|
|
496
|
+
const integrityOn =
|
|
497
|
+
integrity === true ||
|
|
498
|
+
typeof integrity === "function" ||
|
|
499
|
+
(Array.isArray(integrity) && integrity.length > 0);
|
|
500
|
+
// `output.html.csp` injects a `<meta http-equiv="Content-Security-Policy">`
|
|
501
|
+
// once the page's inline content is final (in `processAssets` below).
|
|
502
|
+
const csp = typeof htmlOption === "object" ? htmlOption.csp : undefined;
|
|
503
|
+
// `"auto"` marks implicit enablement (no user rule for HTML files) — see
|
|
504
|
+
// `applyExperimentsDefaults`. Only then loaders win over the built-in type
|
|
505
|
+
// (e.g. html-webpack-plugin's template loader in its child compiler).
|
|
506
|
+
// TODO webpack 6: html defaults to `true`, drop this implicit-only fallback.
|
|
507
|
+
const implicitlyEnabled = compiler.options.experiments.html === "auto";
|
|
280
508
|
compiler.hooks.compilation.tap(
|
|
281
509
|
PLUGIN_NAME,
|
|
282
510
|
(compilation, { normalModuleFactory }) => {
|
|
511
|
+
if (implicitlyEnabled) {
|
|
512
|
+
implicitTypeLoaderFallback(
|
|
513
|
+
normalModuleFactory,
|
|
514
|
+
PLUGIN_NAME,
|
|
515
|
+
/\.html$/i,
|
|
516
|
+
HTML_MODULE_TYPE
|
|
517
|
+
);
|
|
518
|
+
}
|
|
519
|
+
const { htmlModules } = getState(compilation);
|
|
520
|
+
// Record HTML modules as they appear — freshly built
|
|
521
|
+
// (`succeedModule`) or restored from cache (`stillValidModule`) —
|
|
522
|
+
// so `finishMake` creates their entries without scanning every
|
|
523
|
+
// module in the graph. Tracking only records; entries are still
|
|
524
|
+
// added (and awaited) in `finishMake`.
|
|
525
|
+
const trackHtmlModule = (/** @type {import("../Module")} */ module) => {
|
|
526
|
+
if (module.type === HTML_MODULE_TYPE) htmlModules.add(module);
|
|
527
|
+
};
|
|
528
|
+
compilation.hooks.succeedModule.tap(PLUGIN_NAME, trackHtmlModule);
|
|
529
|
+
compilation.hooks.stillValidModule.tap(PLUGIN_NAME, trackHtmlModule);
|
|
530
|
+
|
|
531
|
+
// Resolve integrity and inline sentinels after `RealContentHashPlugin`
|
|
532
|
+
// so final bytes are in place. Only HTML pages carry these sentinels —
|
|
533
|
+
// never JS chunks that embed an HTML string.
|
|
534
|
+
// SRI only takes effect on a cross-origin subresource fetched
|
|
535
|
+
// with CORS; without `output.crossOriginLoading` the browser
|
|
536
|
+
// silently ignores `integrity` on cross-origin loads. Warn once
|
|
537
|
+
// so a CDN deployment doesn't ship no-op integrity attributes.
|
|
538
|
+
if (integrityOn && !compilation.outputOptions.crossOriginLoading) {
|
|
539
|
+
compilation.warnings.push(
|
|
540
|
+
new WebpackError(
|
|
541
|
+
'output.html.integrity is set but output.crossOriginLoading is not. Browsers ignore Subresource Integrity on cross-origin requests made without CORS; set output.crossOriginLoading (e.g. "anonymous") if any asset is served from a different origin.'
|
|
542
|
+
)
|
|
543
|
+
);
|
|
544
|
+
}
|
|
545
|
+
compilation.hooks.processAssets.tapPromise(
|
|
546
|
+
{
|
|
547
|
+
name: PLUGIN_NAME,
|
|
548
|
+
stage: Compilation.PROCESS_ASSETS_STAGE_OPTIMIZE_HASH + 1
|
|
549
|
+
},
|
|
550
|
+
async (assets) => {
|
|
551
|
+
const { htmlAssetNames } = getState(compilation);
|
|
552
|
+
const hooks = HtmlModulesPlugin.getCompilationHooks(compilation);
|
|
553
|
+
/** @type {Set<string>} */
|
|
554
|
+
const inlinedFiles = new Set();
|
|
555
|
+
for (const name of Object.keys(assets)) {
|
|
556
|
+
if (!htmlAssetNames.has(name)) continue;
|
|
557
|
+
const content = assets[name].source();
|
|
558
|
+
if (typeof content !== "string") continue;
|
|
559
|
+
let resolved = content;
|
|
560
|
+
if (
|
|
561
|
+
integrityOn &&
|
|
562
|
+
resolved.includes("__WEBPACK_HTML_INTEGRITY__")
|
|
563
|
+
) {
|
|
564
|
+
resolved = HtmlGenerator.resolveChunkIntegritySentinels(
|
|
565
|
+
resolved,
|
|
566
|
+
compilation,
|
|
567
|
+
/** @type {import("./HtmlGenerator").HtmlIntegrity} */ (
|
|
568
|
+
integrity
|
|
569
|
+
)
|
|
570
|
+
);
|
|
571
|
+
}
|
|
572
|
+
if (resolved.includes("__WEBPACK_HTML_INLINE__")) {
|
|
573
|
+
resolved = HtmlGenerator.resolveChunkInlineSentinels(
|
|
574
|
+
resolved,
|
|
575
|
+
compilation,
|
|
576
|
+
name,
|
|
577
|
+
inlinedFiles
|
|
578
|
+
);
|
|
579
|
+
}
|
|
580
|
+
// `injectTags` (add tags), `transformTags` (mutate/move/remove the
|
|
581
|
+
// page's own and injected tags), and CSP all run over a single
|
|
582
|
+
// parse: collect the tags/anchors once, let the hooks act, then
|
|
583
|
+
// render every edit — placement, attribute rewrites, moves,
|
|
584
|
+
// removals, CSP hashes/nonce/meta — in one pass. Injected inline
|
|
585
|
+
// `<script>`/`<style>` are hashed like the page's own.
|
|
586
|
+
const injected = await hooks.injectTags.promise([], {
|
|
587
|
+
outputName: name,
|
|
588
|
+
html: resolved
|
|
589
|
+
});
|
|
590
|
+
const transformTags = hooks.transformTags.taps.length > 0;
|
|
591
|
+
if (injected.length > 0 || transformTags || csp) {
|
|
592
|
+
const model = HtmlGenerator.collectHtml(resolved);
|
|
593
|
+
HtmlGenerator.addInjectedTags(model, injected);
|
|
594
|
+
if (transformTags) {
|
|
595
|
+
await hooks.transformTags.promise(model.tags, {
|
|
596
|
+
outputName: name,
|
|
597
|
+
html: resolved
|
|
598
|
+
});
|
|
599
|
+
}
|
|
600
|
+
resolved = HtmlGenerator.renderHtml(resolved, model, csp);
|
|
601
|
+
}
|
|
602
|
+
// Final, fully-resolved HTML — let plugins transform it (minify,
|
|
603
|
+
// inject a CSP meta, rewrite tags) before it is written.
|
|
604
|
+
resolved = await hooks.transformHtml.promise(resolved, {
|
|
605
|
+
outputName: name
|
|
606
|
+
});
|
|
607
|
+
if (resolved !== content) {
|
|
608
|
+
compilation.updateAsset(name, new RawSource(resolved));
|
|
609
|
+
}
|
|
610
|
+
await hooks.htmlEmitted.promise({ outputName: name });
|
|
611
|
+
}
|
|
612
|
+
// An inlined chunk file is dead weight once nothing else references
|
|
613
|
+
// it by URL (another page's tag, or the runtime's async chunk map);
|
|
614
|
+
// a content-hashed filename makes a substring hit a real reference.
|
|
615
|
+
// Materialize each other asset's source once (it can be expensive to
|
|
616
|
+
// render) and test all inlined files against it, rather than
|
|
617
|
+
// re-materializing every asset per inlined file.
|
|
618
|
+
if (inlinedFiles.size > 0) {
|
|
619
|
+
/** @type {Set<string>} */
|
|
620
|
+
const stillReferenced = new Set();
|
|
621
|
+
for (const name of Object.keys(compilation.assets)) {
|
|
622
|
+
if (
|
|
623
|
+
inlinedFiles.has(name) ||
|
|
624
|
+
stillReferenced.size === inlinedFiles.size
|
|
625
|
+
) {
|
|
626
|
+
continue;
|
|
627
|
+
}
|
|
628
|
+
const source = compilation.assets[name].source();
|
|
629
|
+
if (typeof source !== "string") continue;
|
|
630
|
+
for (const file of inlinedFiles) {
|
|
631
|
+
if (!stillReferenced.has(file) && source.includes(file)) {
|
|
632
|
+
stillReferenced.add(file);
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
for (const file of inlinedFiles) {
|
|
637
|
+
if (!stillReferenced.has(file)) compilation.deleteAsset(file);
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
);
|
|
642
|
+
|
|
283
643
|
// CSS entries created by `<link rel="stylesheet">` in HTML need
|
|
284
644
|
// their `.css` filename set via `chunk.cssFilenameTemplate`
|
|
285
645
|
// (the field `CssModulesPlugin.getChunkFilenameTemplate` reads).
|
|
@@ -289,9 +649,8 @@ class HtmlModulesPlugin {
|
|
|
289
649
|
// entry emits to a distinct file derived from `output.cssFilename`
|
|
290
650
|
// (or `output.cssChunkFilename` for non-initial CSS chunks).
|
|
291
651
|
compilation.hooks.afterChunks.tap(PLUGIN_NAME, () => {
|
|
292
|
-
const stylesheetEntries =
|
|
293
|
-
|
|
294
|
-
if (!stylesheetEntries || stylesheetEntries.size === 0) return;
|
|
652
|
+
const { stylesheetEntries } = getState(compilation);
|
|
653
|
+
if (stylesheetEntries.size === 0) return;
|
|
295
654
|
for (const entryName of stylesheetEntries) {
|
|
296
655
|
const entrypoint = compilation.entrypoints.get(entryName);
|
|
297
656
|
if (!entrypoint) continue;
|
|
@@ -451,13 +810,6 @@ class HtmlModulesPlugin {
|
|
|
451
810
|
// template's `[contenthash]` placeholder works; the
|
|
452
811
|
// compilation hash is also forwarded so `[fullhash]` /
|
|
453
812
|
// `[hash]` work in user-supplied templates.
|
|
454
|
-
const {
|
|
455
|
-
getUndoPath,
|
|
456
|
-
makePathsRelative
|
|
457
|
-
} = require("../util/identifier");
|
|
458
|
-
const {
|
|
459
|
-
PUBLIC_PATH_AUTO: autoPlaceholder
|
|
460
|
-
} = require("../util/publicPathPlaceholder");
|
|
461
813
|
|
|
462
814
|
// Sentinel-resolved content and its content hash depend only on
|
|
463
815
|
// the HTML-type module source — not on the chunk or output
|
|
@@ -469,9 +821,124 @@ class HtmlModulesPlugin {
|
|
|
469
821
|
/** @type {WeakMap<import("webpack-sources").Source, { resolvedContent: string, contentHash: string }>} */
|
|
470
822
|
const resolvedSentinelHashCache = new WeakMap();
|
|
471
823
|
|
|
824
|
+
// Compute a linked/entry HTML page's emitted filename + chunk-URL-resolved
|
|
825
|
+
// content + content hash for one (module, chunk). Shared by the emit loop
|
|
826
|
+
// below and the page-link resolver. The content hash covers only the
|
|
827
|
+
// chunk-URL-resolved source — links to *other* pages stay sentinels here,
|
|
828
|
+
// so a page's filename doesn't depend on the filenames of pages it links to.
|
|
829
|
+
const computePageEmit = (
|
|
830
|
+
/** @type {NormalModule} */ module,
|
|
831
|
+
/** @type {import("../Chunk")} */ chunk
|
|
832
|
+
) => {
|
|
833
|
+
const { chunkGraph, outputOptions } = compilation;
|
|
834
|
+
const codeGenResult =
|
|
835
|
+
/** @type {import("../CodeGenerationResults")} */ (
|
|
836
|
+
compilation.codeGenerationResults
|
|
837
|
+
).get(module, chunk.runtime);
|
|
838
|
+
const placeholderSource = codeGenResult.sources.get(HTML_TYPE);
|
|
839
|
+
if (!placeholderSource) return undefined;
|
|
840
|
+
let cached = resolvedSentinelHashCache.get(placeholderSource);
|
|
841
|
+
if (cached === undefined) {
|
|
842
|
+
// Resolve chunk-URL sentinels *before* hashing so the HTML's
|
|
843
|
+
// `[contenthash]` invalidates with the referenced chunks'
|
|
844
|
+
// filenames. Inlined chunks have no URL, so tag their inline
|
|
845
|
+
// sentinels with the chunk content hash — that keeps the emitted
|
|
846
|
+
// bytes (and RealContentHashPlugin's later recompute)
|
|
847
|
+
// content-dependent, so the page hash tracks inlined content too.
|
|
848
|
+
// Asset URL sentinels (from `HtmlEntryDependency` resource-hint
|
|
849
|
+
// tags) resolve here too — deferred from template.apply so it
|
|
850
|
+
// doesn't race with asset-module codegen.
|
|
851
|
+
const resolvedContent = HtmlGenerator.resolveAssetUrlSentinels(
|
|
852
|
+
HtmlGenerator.embedInlineChunkHashes(
|
|
853
|
+
HtmlGenerator.resolveChunkUrlSentinels(
|
|
854
|
+
/** @type {string} */ (placeholderSource.source()),
|
|
855
|
+
compilation
|
|
856
|
+
),
|
|
857
|
+
compilation
|
|
858
|
+
),
|
|
859
|
+
compilation
|
|
860
|
+
);
|
|
861
|
+
cached = {
|
|
862
|
+
resolvedContent,
|
|
863
|
+
contentHash: HtmlModulesPlugin.computeContentHash(
|
|
864
|
+
resolvedContent,
|
|
865
|
+
outputOptions
|
|
866
|
+
)
|
|
867
|
+
};
|
|
868
|
+
resolvedSentinelHashCache.set(placeholderSource, cached);
|
|
869
|
+
}
|
|
870
|
+
const resource = module.getResource() || module.resource;
|
|
871
|
+
// Synthetic `output.html` entries are `data:text/html` modules with no
|
|
872
|
+
// real basename — name the file after the entry instead.
|
|
873
|
+
const sourceFilename =
|
|
874
|
+
resource.startsWith("data:text/html") && chunk.name
|
|
875
|
+
? chunk.name
|
|
876
|
+
: makePathsRelative(
|
|
877
|
+
compiler.context,
|
|
878
|
+
/** @type {string} */ (resource),
|
|
879
|
+
compiler.root
|
|
880
|
+
).replace(/^\.\//, "");
|
|
881
|
+
const filenameTemplate = HtmlModulesPlugin.getChunkFilenameTemplate(
|
|
882
|
+
chunk,
|
|
883
|
+
outputOptions
|
|
884
|
+
);
|
|
885
|
+
const { path: filename, info } = compilation.getPathWithInfo(
|
|
886
|
+
/** @type {import("../TemplatedPathPlugin").TemplatePath} */
|
|
887
|
+
(filenameTemplate),
|
|
888
|
+
{
|
|
889
|
+
module,
|
|
890
|
+
runtime: chunk.runtime,
|
|
891
|
+
chunkGraph,
|
|
892
|
+
contentHash: cached.contentHash,
|
|
893
|
+
contentHashType: HTML_TYPE,
|
|
894
|
+
filename: sourceFilename,
|
|
895
|
+
hash: compilation.hash
|
|
896
|
+
}
|
|
897
|
+
);
|
|
898
|
+
return {
|
|
899
|
+
resolvedContent: cached.resolvedContent,
|
|
900
|
+
contentHash: cached.contentHash,
|
|
901
|
+
filename,
|
|
902
|
+
info
|
|
903
|
+
};
|
|
904
|
+
};
|
|
905
|
+
|
|
906
|
+
// Emitted filename of a linked `type: "html"` page, keyed by its entry
|
|
907
|
+
// name. Looked up from the page's own entry chunk so it's independent of
|
|
908
|
+
// chunk render order; cached for the compilation.
|
|
909
|
+
/** @type {Map<string, string>} */
|
|
910
|
+
const htmlPageFilenameCache = new Map();
|
|
911
|
+
const computePageFilenameByEntry = (
|
|
912
|
+
/** @type {string} */ entryName
|
|
913
|
+
) => {
|
|
914
|
+
const cached = htmlPageFilenameCache.get(entryName);
|
|
915
|
+
if (cached !== undefined) return cached;
|
|
916
|
+
let filename = "data:,";
|
|
917
|
+
const entrypoint = compilation.entrypoints.get(entryName);
|
|
918
|
+
const chunk = entrypoint && entrypoint.getEntrypointChunk();
|
|
919
|
+
if (chunk) {
|
|
920
|
+
const modules =
|
|
921
|
+
compilation.chunkGraph.getOrderedChunkModulesIterableBySourceType(
|
|
922
|
+
chunk,
|
|
923
|
+
HTML_TYPE,
|
|
924
|
+
compareModulesByFullName(compilation.compiler)
|
|
925
|
+
);
|
|
926
|
+
const module = modules && modules[Symbol.iterator]().next().value;
|
|
927
|
+
if (module) {
|
|
928
|
+
const emit = computePageEmit(
|
|
929
|
+
/** @type {NormalModule} */ (module),
|
|
930
|
+
chunk
|
|
931
|
+
);
|
|
932
|
+
if (emit) filename = emit.filename;
|
|
933
|
+
}
|
|
934
|
+
}
|
|
935
|
+
htmlPageFilenameCache.set(entryName, filename);
|
|
936
|
+
return filename;
|
|
937
|
+
};
|
|
938
|
+
|
|
472
939
|
compilation.hooks.renderManifest.tap(
|
|
473
940
|
PLUGIN_NAME,
|
|
474
|
-
(result, { chunk
|
|
941
|
+
(result, { chunk }) => {
|
|
475
942
|
// HMR's `HotUpdateChunk`s flow through the same hook
|
|
476
943
|
// but aren't real output chunks — extracting `.html`
|
|
477
944
|
// for them would create stray hot-update HTML files.
|
|
@@ -488,88 +955,26 @@ class HtmlModulesPlugin {
|
|
|
488
955
|
const outputOptions = compilation.outputOptions;
|
|
489
956
|
for (const module of modules) {
|
|
490
957
|
const normalModule = /** @type {NormalModule} */ (module);
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
if (!placeholderSource) continue;
|
|
497
|
-
|
|
498
|
-
// `<iframe srcdoc>` modules expose `html` only so
|
|
499
|
-
// HtmlInlineHtmlDependency can write the processed markup back
|
|
500
|
-
// into the host attribute — they are never standalone pages.
|
|
501
|
-
// `generator.extract: "inline"` marks them so no `.html` file is
|
|
502
|
-
// emitted; without this they'd collide on the `data:text/html`
|
|
503
|
-
// → chunk-name path below (every srcdoc module in a chunk would
|
|
958
|
+
// `<iframe srcdoc>` modules expose `html` only so HtmlInlineHtmlDependency
|
|
959
|
+
// can write the processed markup back into the host attribute — they are
|
|
960
|
+
// never standalone pages. `generator.extract: "inline"` marks them so no
|
|
961
|
+
// `.html` file is emitted; without this they'd collide on the
|
|
962
|
+
// `data:text/html` → chunk-name path (every srcdoc module in a chunk would
|
|
504
963
|
// emit `<chunk>.html`). An O(1) flag check, not a scan.
|
|
505
964
|
const generatorOptions = normalModule.generatorOptions;
|
|
506
965
|
if (generatorOptions && generatorOptions.extract === "inline") {
|
|
507
966
|
continue;
|
|
508
967
|
}
|
|
509
968
|
|
|
510
|
-
const
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
const resource =
|
|
515
|
-
normalModule.getResource() || normalModule.resource;
|
|
516
|
-
// Synthetic `output.html` entries are `data:text/html` modules
|
|
517
|
-
// with no real basename — name the file after the entry instead.
|
|
518
|
-
const sourceFilename =
|
|
519
|
-
resource.startsWith("data:text/html") && chunk.name
|
|
520
|
-
? chunk.name
|
|
521
|
-
: makePathsRelative(
|
|
522
|
-
compiler.context,
|
|
523
|
-
/** @type {string} */ (resource),
|
|
524
|
-
compiler.root
|
|
525
|
-
).replace(/^\.\//, "");
|
|
526
|
-
|
|
527
|
-
let cached = resolvedSentinelHashCache.get(placeholderSource);
|
|
528
|
-
if (cached === undefined) {
|
|
529
|
-
const placeholderContent = /** @type {string} */ (
|
|
530
|
-
placeholderSource.source()
|
|
531
|
-
);
|
|
532
|
-
// Resolve sentinels *before* hashing so the HTML's `[contenthash]`
|
|
533
|
-
// invalidates with the referenced chunks' filenames.
|
|
534
|
-
const resolvedContent = HtmlGenerator.resolveChunkUrlSentinels(
|
|
535
|
-
placeholderContent,
|
|
536
|
-
compilation
|
|
537
|
-
);
|
|
538
|
-
cached = {
|
|
539
|
-
resolvedContent,
|
|
540
|
-
contentHash: HtmlModulesPlugin.computeContentHash(
|
|
541
|
-
resolvedContent,
|
|
542
|
-
outputOptions
|
|
543
|
-
)
|
|
544
|
-
};
|
|
545
|
-
resolvedSentinelHashCache.set(placeholderSource, cached);
|
|
546
|
-
}
|
|
547
|
-
const { resolvedContent, contentHash } = cached;
|
|
548
|
-
|
|
549
|
-
const { path: filename, info } = compilation.getPathWithInfo(
|
|
550
|
-
/** @type {import("../TemplatedPathPlugin").TemplatePath} */
|
|
551
|
-
(filenameTemplate),
|
|
552
|
-
{
|
|
553
|
-
module,
|
|
554
|
-
runtime: chunk.runtime,
|
|
555
|
-
chunkGraph,
|
|
556
|
-
contentHash,
|
|
557
|
-
contentHashType: HTML_TYPE,
|
|
558
|
-
filename: sourceFilename,
|
|
559
|
-
hash: compilationHash
|
|
560
|
-
}
|
|
561
|
-
);
|
|
969
|
+
const emit = computePageEmit(normalModule, chunk);
|
|
970
|
+
if (!emit) continue;
|
|
971
|
+
const { resolvedContent, contentHash, filename, info } = emit;
|
|
562
972
|
|
|
563
|
-
// Resolve any remaining `[webpack/auto]` placeholders to
|
|
564
|
-
//
|
|
565
|
-
//
|
|
566
|
-
// a subdirectory
|
|
567
|
-
//
|
|
568
|
-
// `main.js` root-relative, so the browser would resolve
|
|
569
|
-
// them under the HTML's directory instead of the
|
|
570
|
-
// `output.path` root.
|
|
571
|
-
// A relative `<base href>` prepends `../`s so the base can't
|
|
572
|
-
// misdirect the rewritten URLs (see `HtmlParser`).
|
|
973
|
+
// Resolve any remaining `[webpack/auto]` placeholders to an undo
|
|
974
|
+
// path computed from the emitted HTML's location, so asset/chunk
|
|
975
|
+
// URLs stay relative to `output.path` even when the page emits into
|
|
976
|
+
// a subdirectory. A relative `<base href>` prepends `../`s so the
|
|
977
|
+
// base can't misdirect the rewritten URLs (see `HtmlParser`).
|
|
573
978
|
const basePrefix =
|
|
574
979
|
/** @type {HtmlModuleBuildInfo} */ (normalModule.buildInfo)
|
|
575
980
|
.baseUrlPrefix || "";
|
|
@@ -580,20 +985,22 @@ class HtmlModulesPlugin {
|
|
|
580
985
|
/** @type {string} */ (outputOptions.path),
|
|
581
986
|
false
|
|
582
987
|
);
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
988
|
+
// Resolve linked-page (`type: "html"`) sentinels to each page's
|
|
989
|
+
// emitted filename before the undo path, so the href is relative to
|
|
990
|
+
// this page.
|
|
991
|
+
const finalContent = HtmlGenerator.resolveHtmlPageUrlSentinels(
|
|
992
|
+
resolvedContent,
|
|
993
|
+
computePageFilenameByEntry
|
|
994
|
+
)
|
|
995
|
+
.split(autoPlaceholder)
|
|
996
|
+
.join(undoPath);
|
|
586
997
|
const finalSource = new RawSource(finalContent);
|
|
587
|
-
// The same HTML module can land in multiple chunks
|
|
588
|
-
//
|
|
589
|
-
//
|
|
590
|
-
//
|
|
591
|
-
//
|
|
592
|
-
//
|
|
593
|
-
// the post-undo-path content in the hash, so the
|
|
594
|
-
// asset cache can't reuse one variant's bytes at
|
|
595
|
-
// another variant's URL. Unchanged content reuses
|
|
596
|
-
// the memoized hash instead of re-digesting.
|
|
998
|
+
// The same HTML module can land in multiple chunks with different
|
|
999
|
+
// `htmlFilename`/`htmlChunkFilename` shapes → different `undoPath`s and
|
|
1000
|
+
// final content for the same module id. Include the emitted filename in
|
|
1001
|
+
// the asset cache key and the post-undo-path content in the hash so the
|
|
1002
|
+
// asset cache can't reuse one variant's bytes at another variant's URL.
|
|
1003
|
+
// Unchanged content reuses the memoized hash instead of re-digesting.
|
|
597
1004
|
const finalContentHash =
|
|
598
1005
|
finalContent === resolvedContent
|
|
599
1006
|
? contentHash
|
|
@@ -602,6 +1009,9 @@ class HtmlModulesPlugin {
|
|
|
602
1009
|
outputOptions
|
|
603
1010
|
);
|
|
604
1011
|
|
|
1012
|
+
// Track HTML pages for the integrity sentinel pass (never JS chunks).
|
|
1013
|
+
getState(compilation).htmlAssetNames.add(filename);
|
|
1014
|
+
|
|
605
1015
|
result.push({
|
|
606
1016
|
render: () => finalSource,
|
|
607
1017
|
filename,
|
|
@@ -619,20 +1029,39 @@ class HtmlModulesPlugin {
|
|
|
619
1029
|
|
|
620
1030
|
// Resolve sentinels at JS chunk render time so later passes
|
|
621
1031
|
// (SourceMapDevToolPlugin, size optimizers, RealContentHash) see resolved bytes.
|
|
622
|
-
const JavascriptModulesPlugin = require("../javascript/JavascriptModulesPlugin");
|
|
623
|
-
|
|
624
1032
|
const jsHooks =
|
|
625
1033
|
JavascriptModulesPlugin.getCompilationHooks(compilation);
|
|
626
1034
|
jsHooks.render.tap(PLUGIN_NAME, (source, renderContext) => {
|
|
1035
|
+
// No HTML modules ⇒ no sentinels; skip materializing the JS source.
|
|
1036
|
+
if (htmlModules.size === 0) return source;
|
|
627
1037
|
const raw = source.source();
|
|
628
1038
|
if (typeof raw !== "string") return source;
|
|
629
|
-
if (
|
|
630
|
-
|
|
631
|
-
raw
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
.
|
|
635
|
-
|
|
1039
|
+
if (
|
|
1040
|
+
!raw.includes("__WEBPACK_HTML_CHUNK_URL__") &&
|
|
1041
|
+
!raw.includes("__WEBPACK_HTML_PAGE_URL__") &&
|
|
1042
|
+
!raw.includes("__WEBPACK_HTML_INTEGRITY__") &&
|
|
1043
|
+
!raw.includes("__WEBPACK_HTML_INLINE__") &&
|
|
1044
|
+
!raw.includes("__WEBPACK_HTML_ASSET_URL__")
|
|
1045
|
+
) {
|
|
1046
|
+
return source;
|
|
1047
|
+
}
|
|
1048
|
+
// Strip integrity and inline sentinels (not resolve): a JS chunk
|
|
1049
|
+
// can't hold real SRI hashes for its own not-yet-final bytes, and
|
|
1050
|
+
// inline content belongs only in the final .html, never in JS bundles.
|
|
1051
|
+
const resolved = HtmlGenerator.stripChunkIntegritySentinels(
|
|
1052
|
+
HtmlGenerator.resolveAssetUrlSentinels(
|
|
1053
|
+
HtmlGenerator.resolveHtmlPageUrlSentinels(
|
|
1054
|
+
HtmlGenerator.resolveChunkUrlSentinels(raw, compilation),
|
|
1055
|
+
computePageFilenameByEntry
|
|
1056
|
+
),
|
|
1057
|
+
compilation
|
|
1058
|
+
)
|
|
1059
|
+
.split(autoPlaceholder)
|
|
1060
|
+
.join("")
|
|
1061
|
+
).replace(
|
|
1062
|
+
/__WEBPACK_HTML_INLINE__[0-9a-f]+__[a-z]+(?:__[0-9a-f]+)?__END__/g,
|
|
1063
|
+
""
|
|
1064
|
+
);
|
|
636
1065
|
if (resolved === raw) return source;
|
|
637
1066
|
const chunkId = String(renderContext.chunk.id);
|
|
638
1067
|
const prior = sentinelResolvedSourceCache.get(chunkId);
|
|
@@ -664,4 +1093,19 @@ class HtmlModulesPlugin {
|
|
|
664
1093
|
}
|
|
665
1094
|
}
|
|
666
1095
|
|
|
1096
|
+
/**
|
|
1097
|
+
* Per-compilation hooks for the experimental HTML support.
|
|
1098
|
+
* @param {Compilation} compilation the compilation
|
|
1099
|
+
* @returns {HtmlCompilationHooks} the hooks
|
|
1100
|
+
*/
|
|
1101
|
+
HtmlModulesPlugin.getCompilationHooks = createHooksRegistry(
|
|
1102
|
+
() =>
|
|
1103
|
+
/** @type {HtmlCompilationHooks} */ ({
|
|
1104
|
+
injectTags: new AsyncSeriesWaterfallHook(["tags", "context"]),
|
|
1105
|
+
transformTags: new AsyncSeriesHook(["tags", "context"]),
|
|
1106
|
+
transformHtml: new AsyncSeriesWaterfallHook(["html", "context"]),
|
|
1107
|
+
htmlEmitted: new AsyncSeriesHook(["context"])
|
|
1108
|
+
})
|
|
1109
|
+
);
|
|
1110
|
+
|
|
667
1111
|
module.exports = HtmlModulesPlugin;
|