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
|
@@ -14,22 +14,154 @@ const {
|
|
|
14
14
|
} = require("../ModuleSourceTypeConstants");
|
|
15
15
|
const RuntimeGlobals = require("../RuntimeGlobals");
|
|
16
16
|
const Template = require("../Template");
|
|
17
|
+
const WebpackError = require("../errors/WebpackError");
|
|
18
|
+
const { getUndoPath } = require("../util/identifier");
|
|
17
19
|
const memoize = require("../util/memoize");
|
|
18
20
|
const { PUBLIC_PATH_AUTO } = require("../util/publicPathPlaceholder");
|
|
21
|
+
const { NodeType, SourceProcessor, escapeAttribute } = require("./syntax");
|
|
19
22
|
|
|
20
23
|
// Lazy so loading the HTML generator doesn't pull in the whole CSS pipeline.
|
|
21
24
|
const getCssModulesPlugin = memoize(() => require("../css/CssModulesPlugin"));
|
|
25
|
+
// The CSS tokenizer, used to locate `url(...)` when rebasing inlined stylesheets.
|
|
26
|
+
const getCssSyntax = memoize(() => require("../css/syntax"));
|
|
27
|
+
// node's crypto — real SHA hashes for Content-Security-Policy source lists
|
|
28
|
+
// (the wasm hashes are xxhash, unusable for CSP), loaded only when CSP is on.
|
|
29
|
+
const getCreateNodeHash = memoize(() => require("crypto").createHash);
|
|
30
|
+
|
|
31
|
+
// HTML void elements — serialized without a closing tag when a tag descriptor
|
|
32
|
+
// (`output.html` `injectTags`) doesn't say otherwise.
|
|
33
|
+
const VOID_TAGS = new Set([
|
|
34
|
+
"area",
|
|
35
|
+
"base",
|
|
36
|
+
"br",
|
|
37
|
+
"col",
|
|
38
|
+
"embed",
|
|
39
|
+
"hr",
|
|
40
|
+
"img",
|
|
41
|
+
"input",
|
|
42
|
+
"link",
|
|
43
|
+
"meta",
|
|
44
|
+
"param",
|
|
45
|
+
"source",
|
|
46
|
+
"track",
|
|
47
|
+
"wbr"
|
|
48
|
+
]);
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Serializes an attribute map: `true` → bare attribute, `false`/`undefined`/
|
|
52
|
+
* `null` → omitted, else a quoted (escaped) value.
|
|
53
|
+
* @param {Record<string, string | boolean | undefined> | undefined} attrs attributes
|
|
54
|
+
* @returns {string} the leading-space-prefixed attribute string
|
|
55
|
+
*/
|
|
56
|
+
const serializeAttrs = (attrs) => {
|
|
57
|
+
let out = "";
|
|
58
|
+
if (attrs) {
|
|
59
|
+
for (const name of Object.keys(attrs)) {
|
|
60
|
+
const value = attrs[name];
|
|
61
|
+
if (value === false || value === undefined || value === null) continue;
|
|
62
|
+
out +=
|
|
63
|
+
value === true
|
|
64
|
+
? ` ${name}`
|
|
65
|
+
: ` ${name}="${escapeAttribute(`${value}`)}"`;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return out;
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Serializes one tag descriptor. `attrs` values: `true` → bare attribute,
|
|
73
|
+
* `false`/`undefined` → omitted, else a quoted (escaped) value.
|
|
74
|
+
* @param {HtmlTagDescriptor} tag the descriptor
|
|
75
|
+
* @returns {string} the tag's HTML
|
|
76
|
+
*/
|
|
77
|
+
const serializeTag = (tag) => {
|
|
78
|
+
const attrs = serializeAttrs(tag.attrs);
|
|
79
|
+
const isVoid =
|
|
80
|
+
tag.voidTag !== undefined
|
|
81
|
+
? tag.voidTag
|
|
82
|
+
: VOID_TAGS.has(tag.tag.toLowerCase());
|
|
83
|
+
return isVoid
|
|
84
|
+
? `<${tag.tag}${attrs}>`
|
|
85
|
+
: `<${tag.tag}${attrs}>${tag.children || ""}</${tag.tag}>`;
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Canonical string of an attribute map (present keys, sorted) — used to detect
|
|
90
|
+
* whether an `transformTags` plugin changed a tag, so unchanged tags stay untouched.
|
|
91
|
+
* `false`/`undefined`/`null` count as absent.
|
|
92
|
+
* @param {Record<string, string | boolean | undefined>} attrs attributes
|
|
93
|
+
* @returns {string} a comparable snapshot
|
|
94
|
+
*/
|
|
95
|
+
const snapshotAttrs = (attrs) =>
|
|
96
|
+
JSON.stringify(
|
|
97
|
+
Object.keys(attrs)
|
|
98
|
+
.filter((name) => {
|
|
99
|
+
const value = attrs[name];
|
|
100
|
+
return value !== false && value !== undefined && value !== null;
|
|
101
|
+
})
|
|
102
|
+
.sort()
|
|
103
|
+
.map((name) => [name, attrs[name]])
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Whether an attribute renders (present and not `false`/`undefined`/`null`).
|
|
108
|
+
* @param {Record<string, string | boolean | undefined>} attrs attributes
|
|
109
|
+
* @param {string} name attribute name
|
|
110
|
+
* @returns {boolean} true if it renders
|
|
111
|
+
*/
|
|
112
|
+
const attrPresent = (attrs, name) => {
|
|
113
|
+
const value = attrs[name];
|
|
114
|
+
return value !== undefined && value !== false && value !== null;
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Whether a `<meta>`'s attributes declare a Content-Security-Policy (case-
|
|
119
|
+
* insensitive `http-equiv`), used to detect an author-declared CSP.
|
|
120
|
+
* @param {Record<string, string | boolean | undefined>} attrs attributes
|
|
121
|
+
* @returns {boolean} true if it is a CSP meta
|
|
122
|
+
*/
|
|
123
|
+
const metaIsCsp = (attrs) => {
|
|
124
|
+
for (const name of Object.keys(attrs)) {
|
|
125
|
+
const value = attrs[name];
|
|
126
|
+
if (
|
|
127
|
+
name.toLowerCase() === "http-equiv" &&
|
|
128
|
+
typeof value === "string" &&
|
|
129
|
+
value.trim().toLowerCase() === "content-security-policy"
|
|
130
|
+
) {
|
|
131
|
+
return true;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
return false;
|
|
135
|
+
};
|
|
22
136
|
|
|
23
137
|
/** @typedef {import("webpack-sources").Source} Source */
|
|
24
138
|
/** @typedef {import("../../declarations/WebpackOptions").HtmlGeneratorOptions} HtmlGeneratorOptions */
|
|
139
|
+
/** @typedef {import("../../declarations/WebpackOptions").OutputHtmlOptions["csp"]} CspOption */
|
|
140
|
+
/** @typedef {import("./HtmlModulesPlugin").HtmlTagDescriptor} HtmlTagDescriptor */
|
|
141
|
+
/** @typedef {import("./HtmlModulesPlugin").HtmlMutableTag} HtmlMutableTag */
|
|
142
|
+
/** @typedef {"head" | "body"} HtmlTagLocation */
|
|
143
|
+
/**
|
|
144
|
+
* @typedef {object} HtmlTagSpan
|
|
145
|
+
* @property {number} start opening `<` offset
|
|
146
|
+
* @property {number} openEnd offset just after the opening `>`
|
|
147
|
+
* @property {number} end offset just after the whole element
|
|
148
|
+
* @property {HtmlTagLocation} location the `<head>`/`<body>` region it sits in
|
|
149
|
+
* @property {number} nameEnd offset after the tag name (nonce insertion point), `-1` for non-`<script>`/`<style>`
|
|
150
|
+
* @property {number} contentEnd raw-text body end for `<script>`/`<style>`, `-1` otherwise
|
|
151
|
+
* @property {boolean} selfClosing whether the element is self-closing
|
|
152
|
+
*/
|
|
153
|
+
/** @typedef {{ headOpen: number, headEnd: number, bodyOpen: number, bodyEnd: number }} HtmlAnchors */
|
|
154
|
+
/** @typedef {{ tags: HtmlMutableTag[], spans: (HtmlTagSpan | null)[], originals: (string | null)[], descriptors: (HtmlTagDescriptor | null)[], anchors: HtmlAnchors }} HtmlModel */
|
|
25
155
|
/** @typedef {import("../Chunk")} Chunk */
|
|
26
156
|
/** @typedef {import("../Compilation")} Compilation */
|
|
157
|
+
/** @typedef {boolean | string[] | ((asset: { chunk: Chunk, filename: string }) => string[] | false)} HtmlIntegrity */
|
|
27
158
|
/** @typedef {import("../Compilation").DependencyConstructor} DependencyConstructor */
|
|
28
159
|
/** @typedef {import("../CodeGenerationResults")} CodeGenerationResults */
|
|
29
160
|
/** @typedef {import("../Dependency")} Dependency */
|
|
30
161
|
/** @typedef {import("../DependencyTemplate").DependencyTemplateContext} DependencyTemplateContext */
|
|
31
162
|
/** @typedef {import("../Generator").GenerateContext} GenerateContext */
|
|
32
163
|
/** @typedef {import("../Generator").UpdateHashContext} UpdateHashContext */
|
|
164
|
+
/** @typedef {import("../Module")} Module */
|
|
33
165
|
/** @typedef {import("../Module").SourceType} SourceType */
|
|
34
166
|
/** @typedef {import("../Module").SourceTypes} SourceTypes */
|
|
35
167
|
/** @typedef {import("../Module").ConcatenationBailoutReasonContext} ConcatenationBailoutReasonContext */
|
|
@@ -52,12 +184,138 @@ const JAVASCRIPT_AND_HTML_TYPES = new Set([JAVASCRIPT_TYPE, HTML_TYPE]);
|
|
|
52
184
|
/** @type {WeakMap<Compilation, Map<string, Chunk>>} */
|
|
53
185
|
const chunksByIdCache = new WeakMap();
|
|
54
186
|
|
|
187
|
+
/**
|
|
188
|
+
* `String(chunk.id)` → chunk lookup, memoized per compilation and shared by the
|
|
189
|
+
* URL and integrity sentinel resolvers.
|
|
190
|
+
* @param {Compilation} compilation compilation
|
|
191
|
+
* @returns {Map<string, Chunk>} chunks keyed by stringified id
|
|
192
|
+
*/
|
|
193
|
+
const getChunksById = (compilation) => {
|
|
194
|
+
let chunksById = chunksByIdCache.get(compilation);
|
|
195
|
+
if (chunksById === undefined) {
|
|
196
|
+
chunksById = new Map();
|
|
197
|
+
for (const chunk of compilation.chunks) {
|
|
198
|
+
chunksById.set(String(chunk.id), chunk);
|
|
199
|
+
}
|
|
200
|
+
chunksByIdCache.set(compilation, chunksById);
|
|
201
|
+
}
|
|
202
|
+
return chunksById;
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
/** @type {WeakMap<Compilation, Map<string, Module>>} */
|
|
206
|
+
const modulesByIdentifierCache = new WeakMap();
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* `module.identifier()` → module lookup, memoized per compilation so the asset
|
|
210
|
+
* URL sentinel resolver doesn't re-scan the whole module graph per HTML module.
|
|
211
|
+
* @param {Compilation} compilation compilation
|
|
212
|
+
* @returns {Map<string, Module>} modules keyed by identifier
|
|
213
|
+
*/
|
|
214
|
+
const getModulesByIdentifier = (compilation) => {
|
|
215
|
+
let modulesByIdentifier = modulesByIdentifierCache.get(compilation);
|
|
216
|
+
if (modulesByIdentifier === undefined) {
|
|
217
|
+
modulesByIdentifier = new Map();
|
|
218
|
+
for (const module of compilation.modules) {
|
|
219
|
+
modulesByIdentifier.set(module.identifier(), module);
|
|
220
|
+
}
|
|
221
|
+
modulesByIdentifierCache.set(compilation, modulesByIdentifier);
|
|
222
|
+
}
|
|
223
|
+
return modulesByIdentifier;
|
|
224
|
+
};
|
|
225
|
+
|
|
55
226
|
// Hoisted so `resolveChunkUrlSentinels` (per chunk × module) doesn't allocate a
|
|
56
227
|
// fresh `RegExp` each call. `String#replace` resets `lastIndex`, so sharing the
|
|
57
228
|
// global-flag instance is safe under these synchronous, non-reentrant calls.
|
|
58
229
|
const CHUNK_URL_SENTINEL_REGEXP =
|
|
59
230
|
/__WEBPACK_HTML_CHUNK_URL__([0-9a-f]+)__([a-z]+)__END__/g;
|
|
60
231
|
|
|
232
|
+
// Sentinel for a linked HTML page (a tag mapped to the `html` source type);
|
|
233
|
+
// resolved to the page's emitted filename once it has been built. Keyed by the
|
|
234
|
+
// linked module's id so it shares the `<iframe srcdoc>` nested-module mechanism.
|
|
235
|
+
const HTML_PAGE_URL_SENTINEL_REGEXP =
|
|
236
|
+
/__WEBPACK_HTML_PAGE_URL__([0-9a-f]+)__END__/g;
|
|
237
|
+
|
|
238
|
+
// Sentinel for a URL-referenced asset (font/image/…) emitted into a resource-hint
|
|
239
|
+
// `<link>` tag by `HtmlEntryDependency`; resolved to the asset's real emitted
|
|
240
|
+
// URL from its codegen data during `HtmlModulesPlugin` render. Deferred because
|
|
241
|
+
// HtmlEntry deps run *while* the HTML module is being codegen'd — the asset
|
|
242
|
+
// module's own codegen might not have completed yet. Keyed by the asset
|
|
243
|
+
// module's identifier, hex-encoded so any character in it is safe in a URL.
|
|
244
|
+
const ASSET_URL_SENTINEL_REGEXP =
|
|
245
|
+
/__WEBPACK_HTML_ASSET_URL__([0-9a-f]+)__END__/g;
|
|
246
|
+
|
|
247
|
+
// Matches the whole ` integrity="<sentinel>"` so the attribute can be dropped
|
|
248
|
+
// entirely when the per-asset `integrity` function returns `false`.
|
|
249
|
+
const INTEGRITY_SENTINEL_REGEXP =
|
|
250
|
+
/ integrity="__WEBPACK_HTML_INTEGRITY__([0-9a-f]+)__([a-z]+)__END__"/g;
|
|
251
|
+
|
|
252
|
+
// Same attribute, but tolerant of the `\"` escaping the surrounding quotes get
|
|
253
|
+
// when the HTML lives inside a JS string literal — used only to strip it there.
|
|
254
|
+
const INTEGRITY_SENTINEL_STRIP_REGEXP =
|
|
255
|
+
/ integrity=\\?"__WEBPACK_HTML_INTEGRITY__[0-9a-f]+__[a-z]+__END__\\?"/g;
|
|
256
|
+
|
|
257
|
+
// The trailing `(?:__<hash>)?` is the inlined chunk's content hash, embedded by
|
|
258
|
+
// `embedInlineChunkHashes` so the host HTML's bytes (and its `[contenthash]`,
|
|
259
|
+
// incl. RealContentHashPlugin's recompute) change with the inlined content; the
|
|
260
|
+
// resolver ignores it.
|
|
261
|
+
const INLINE_SENTINEL_REGEXP =
|
|
262
|
+
/__WEBPACK_HTML_INLINE__([0-9a-f]+)__([a-z]+)(?:__[0-9a-f]+)?__END__/g;
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Rewrite relative `url(...)` references in a CSS chunk so they still resolve
|
|
266
|
+
* after the styles are inlined into the HTML document (which may sit in a
|
|
267
|
+
* different directory). webpack emits every url as `undoPath(cssFile) + <asset
|
|
268
|
+
* path from the output root>`, so swapping the CSS file's undo prefix for the
|
|
269
|
+
* HTML file's re-bases them — no path math. Uses the CSS tokenizer to find url
|
|
270
|
+
* tokens exactly (never a `url(` inside a comment or string); webpack emits
|
|
271
|
+
* unquoted url()s (special characters `\`-escaped), so only that shape occurs.
|
|
272
|
+
* @param {string} css the CSS content
|
|
273
|
+
* @param {string} fromFile the CSS file's output name
|
|
274
|
+
* @param {string} toFile the HTML file's output name
|
|
275
|
+
* @param {string} outputPath the compilation output path
|
|
276
|
+
* @returns {string} the CSS with rebased urls
|
|
277
|
+
*/
|
|
278
|
+
const rebaseCssUrls = (css, fromFile, toFile, outputPath) => {
|
|
279
|
+
const fromUndo = getUndoPath(fromFile, outputPath, false);
|
|
280
|
+
const toUndo = getUndoPath(toFile, outputPath, false);
|
|
281
|
+
if (fromUndo === toUndo) return css;
|
|
282
|
+
const { readToken, TT_URL } = getCssSyntax();
|
|
283
|
+
const token = {
|
|
284
|
+
type: 0,
|
|
285
|
+
start: 0,
|
|
286
|
+
end: 0,
|
|
287
|
+
isId: false,
|
|
288
|
+
contentStart: 0,
|
|
289
|
+
contentEnd: 0,
|
|
290
|
+
unitStart: 0
|
|
291
|
+
};
|
|
292
|
+
let pos = 0;
|
|
293
|
+
let last = 0;
|
|
294
|
+
let out = "";
|
|
295
|
+
while (readToken(css, pos, token) !== undefined) {
|
|
296
|
+
pos = token.end;
|
|
297
|
+
if (token.type !== TT_URL) continue;
|
|
298
|
+
const url = css.slice(token.contentStart, token.contentEnd);
|
|
299
|
+
// Rebase only the undo-path-relative urls; a root-relative (`/`), fragment
|
|
300
|
+
// (`#`) or scheme (`data:`, …, a `:` before any `/`) url is absolute.
|
|
301
|
+
const colon = url.indexOf(":");
|
|
302
|
+
const slash = url.indexOf("/");
|
|
303
|
+
if (
|
|
304
|
+
!url.startsWith(fromUndo) ||
|
|
305
|
+
url[0] === "/" ||
|
|
306
|
+
url[0] === "#" ||
|
|
307
|
+
(colon !== -1 && (slash === -1 || colon < slash))
|
|
308
|
+
) {
|
|
309
|
+
continue;
|
|
310
|
+
}
|
|
311
|
+
out += `${css.slice(last, token.contentStart)}${toUndo}${url.slice(
|
|
312
|
+
fromUndo.length
|
|
313
|
+
)}`;
|
|
314
|
+
last = token.contentEnd;
|
|
315
|
+
}
|
|
316
|
+
return last === 0 ? css : out + css.slice(last);
|
|
317
|
+
};
|
|
318
|
+
|
|
61
319
|
class HtmlGenerator extends Generator {
|
|
62
320
|
/**
|
|
63
321
|
* Emit a sentinel for a chunk URL that can't be resolved at code-gen time
|
|
@@ -83,20 +341,16 @@ class HtmlGenerator extends Generator {
|
|
|
83
341
|
static resolveChunkUrlSentinels(content, compilation) {
|
|
84
342
|
if (!content.includes("__WEBPACK_HTML_CHUNK_URL__")) return content;
|
|
85
343
|
const outputOptions = compilation.outputOptions;
|
|
86
|
-
|
|
87
|
-
if (chunksById === undefined) {
|
|
88
|
-
chunksById = new Map();
|
|
89
|
-
for (const chunk of compilation.chunks) {
|
|
90
|
-
chunksById.set(String(chunk.id), chunk);
|
|
91
|
-
}
|
|
92
|
-
chunksByIdCache.set(compilation, chunksById);
|
|
93
|
-
}
|
|
344
|
+
const chunksById = getChunksById(compilation);
|
|
94
345
|
return content.replace(
|
|
95
346
|
CHUNK_URL_SENTINEL_REGEXP,
|
|
96
347
|
(_, hexId, contentHashType) => {
|
|
97
348
|
const chunkId = Buffer.from(hexId, "hex").toString("utf8");
|
|
98
349
|
const chunk = chunksById.get(chunkId);
|
|
99
350
|
if (!chunk) return "data:,";
|
|
351
|
+
// Reuse each pipeline's own filename-template resolver so the URL
|
|
352
|
+
// matches exactly what that plugin emits (worker/hot-update chunk
|
|
353
|
+
// handling included), mirroring the CSS branch.
|
|
100
354
|
let filenameTemplate;
|
|
101
355
|
if (contentHashType === "css") {
|
|
102
356
|
filenameTemplate = getCssModulesPlugin().getChunkFilenameTemplate(
|
|
@@ -104,11 +358,12 @@ class HtmlGenerator extends Generator {
|
|
|
104
358
|
outputOptions
|
|
105
359
|
);
|
|
106
360
|
} else {
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
361
|
+
const JavascriptModulesPlugin = require("../javascript/JavascriptModulesPlugin");
|
|
362
|
+
|
|
363
|
+
filenameTemplate = JavascriptModulesPlugin.getChunkFilenameTemplate(
|
|
364
|
+
chunk,
|
|
365
|
+
outputOptions
|
|
366
|
+
);
|
|
112
367
|
}
|
|
113
368
|
const filename = compilation.getPath(
|
|
114
369
|
/** @type {import("../TemplatedPathPlugin").TemplatePath} */
|
|
@@ -123,6 +378,640 @@ class HtmlGenerator extends Generator {
|
|
|
123
378
|
);
|
|
124
379
|
}
|
|
125
380
|
|
|
381
|
+
/**
|
|
382
|
+
* Emit a sentinel for a linked HTML page's URL (a tag the user mapped to
|
|
383
|
+
* the `html` source `type`). The page's emitted filename isn't known at
|
|
384
|
+
* code-gen time; `resolveHtmlPageUrlSentinels` swaps it for
|
|
385
|
+
* `${PUBLIC_PATH_AUTO}<htmlFilename>` once the page has been built.
|
|
386
|
+
* @param {string | number} moduleId id of the linked HTML module
|
|
387
|
+
* @returns {string} sentinel
|
|
388
|
+
*/
|
|
389
|
+
static makeHtmlPageUrlSentinel(moduleId) {
|
|
390
|
+
const hexId = Buffer.from(String(moduleId), "utf8").toString("hex");
|
|
391
|
+
return `__WEBPACK_HTML_PAGE_URL__${hexId}__END__`;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
/**
|
|
395
|
+
* Replace every `makeHtmlPageUrlSentinel` sentinel in `content` with
|
|
396
|
+
* `${PUBLIC_PATH_AUTO}<htmlFilename>`, resolving each linked page's emitted
|
|
397
|
+
* filename through `getFilename`.
|
|
398
|
+
* @param {string} content content
|
|
399
|
+
* @param {(moduleId: string) => string} getFilename maps a linked module id to its emitted html filename
|
|
400
|
+
* @returns {string} resolved content
|
|
401
|
+
*/
|
|
402
|
+
static resolveHtmlPageUrlSentinels(content, getFilename) {
|
|
403
|
+
if (!content.includes("__WEBPACK_HTML_PAGE_URL__")) return content;
|
|
404
|
+
return content.replace(
|
|
405
|
+
HTML_PAGE_URL_SENTINEL_REGEXP,
|
|
406
|
+
(_, hexId) =>
|
|
407
|
+
`${PUBLIC_PATH_AUTO}${getFilename(
|
|
408
|
+
Buffer.from(hexId, "hex").toString("utf8")
|
|
409
|
+
)}`
|
|
410
|
+
);
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
/**
|
|
414
|
+
* Emit a sentinel for a URL-referenced asset module. Resolved by
|
|
415
|
+
* `resolveAssetUrlSentinels` from the asset module's own codegen data
|
|
416
|
+
* (`data.get("url")`), which isn't populated when `HtmlEntryDependency`
|
|
417
|
+
* runs — the asset module's own codegen may still be pending.
|
|
418
|
+
* @param {string} moduleIdentifier the referenced asset module's `identifier()`
|
|
419
|
+
* @returns {string} sentinel
|
|
420
|
+
*/
|
|
421
|
+
static makeAssetUrlSentinel(moduleIdentifier) {
|
|
422
|
+
const hex = Buffer.from(moduleIdentifier, "utf8").toString("hex");
|
|
423
|
+
return `__WEBPACK_HTML_ASSET_URL__${hex}__END__`;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
/**
|
|
427
|
+
* Replace every `makeAssetUrlSentinel` in `content` with the asset module's
|
|
428
|
+
* emitted URL, read from its codegen data. Unresolved sentinels (module
|
|
429
|
+
* absent, no codegen result yet, or no URL channel — e.g. an inlined
|
|
430
|
+
* data-URI) map to `data:,` so the surrounding `<link>` degrades cleanly
|
|
431
|
+
* instead of shipping the raw placeholder.
|
|
432
|
+
* @param {string} content content
|
|
433
|
+
* @param {Compilation} compilation compilation
|
|
434
|
+
* @returns {string} resolved content
|
|
435
|
+
*/
|
|
436
|
+
static resolveAssetUrlSentinels(content, compilation) {
|
|
437
|
+
if (!content.includes("__WEBPACK_HTML_ASSET_URL__")) return content;
|
|
438
|
+
const byIdentifier = getModulesByIdentifier(compilation);
|
|
439
|
+
const codeGenerationResults =
|
|
440
|
+
/** @type {import("../CodeGenerationResults")} */
|
|
441
|
+
(compilation.codeGenerationResults);
|
|
442
|
+
return content.replace(ASSET_URL_SENTINEL_REGEXP, (_, hex) => {
|
|
443
|
+
const id = Buffer.from(hex, "hex").toString("utf8");
|
|
444
|
+
const module = byIdentifier.get(id);
|
|
445
|
+
if (!module) return "data:,";
|
|
446
|
+
if (!codeGenerationResults.has(module, undefined)) return "data:,";
|
|
447
|
+
const codeGen = codeGenerationResults.get(module, undefined);
|
|
448
|
+
const data = codeGen.data;
|
|
449
|
+
if (!data) return "data:,";
|
|
450
|
+
const url =
|
|
451
|
+
/** @type {Record<string, string | undefined> | undefined} */
|
|
452
|
+
(data.get("url"));
|
|
453
|
+
// Prefer the `asset-url` channel — it's the actual URL string
|
|
454
|
+
// (publicPath + filename). Also handled: assets reached only from JS
|
|
455
|
+
// have no `asset-url` channel (their only URL is the JS expression
|
|
456
|
+
// `__webpack_require__.p + "…"`); for those we reconstruct the URL
|
|
457
|
+
// from the emitted `filename` + `PUBLIC_PATH_AUTO` so the same
|
|
458
|
+
// undo-path substitution runs at asset render.
|
|
459
|
+
if (url && url["asset-url"]) return url["asset-url"];
|
|
460
|
+
const filename = /** @type {string | undefined} */ (data.get("filename"));
|
|
461
|
+
if (filename) return `${PUBLIC_PATH_AUTO}${filename}`;
|
|
462
|
+
return "data:,";
|
|
463
|
+
});
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
/**
|
|
467
|
+
* Emit a sentinel for a chunk's SRI hash. Resolved late (after
|
|
468
|
+
* `RealContentHashPlugin`) by `resolveChunkIntegritySentinels`, since the
|
|
469
|
+
* hash must cover the chunk's final emitted bytes.
|
|
470
|
+
* @param {Chunk} chunk chunk
|
|
471
|
+
* @param {"javascript" | "css"} contentHashType which chunk asset to hash
|
|
472
|
+
* @returns {string} sentinel
|
|
473
|
+
*/
|
|
474
|
+
static makeChunkIntegritySentinel(chunk, contentHashType) {
|
|
475
|
+
const hexId = Buffer.from(String(chunk.id), "utf8").toString("hex");
|
|
476
|
+
return `__WEBPACK_HTML_INTEGRITY__${hexId}__${contentHashType}__END__`;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
/**
|
|
480
|
+
* Drop every ` integrity="<sentinel>"` from `content`. Used on the HTML
|
|
481
|
+
* string embedded in a JS chunk (e.g. `<iframe srcdoc>`), where a real SRI
|
|
482
|
+
* hash can't be produced — the chunk's own bytes aren't final at render time
|
|
483
|
+
* — and resolving it later would mutate the chunk after its content hash was
|
|
484
|
+
* computed. Only real HTML output assets keep the sentinel for late resolution.
|
|
485
|
+
* @param {string} content content
|
|
486
|
+
* @returns {string} content with integrity sentinels removed
|
|
487
|
+
*/
|
|
488
|
+
static stripChunkIntegritySentinels(content) {
|
|
489
|
+
if (!content.includes("__WEBPACK_HTML_INTEGRITY__")) return content;
|
|
490
|
+
return content.replace(INTEGRITY_SENTINEL_STRIP_REGEXP, "");
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
/**
|
|
494
|
+
* Replace every ` integrity="<sentinel>"` with the chunk's real SRI hashes,
|
|
495
|
+
* or drop the attribute when `integrity` (a function) returns `false` for it.
|
|
496
|
+
* @param {string} content content
|
|
497
|
+
* @param {Compilation} compilation compilation
|
|
498
|
+
* @param {HtmlIntegrity} integrity the `output.html.integrity` option
|
|
499
|
+
* @returns {string} resolved content
|
|
500
|
+
*/
|
|
501
|
+
static resolveChunkIntegritySentinels(content, compilation, integrity) {
|
|
502
|
+
if (!content.includes("__WEBPACK_HTML_INTEGRITY__")) return content;
|
|
503
|
+
|
|
504
|
+
const crypto = require("crypto");
|
|
505
|
+
|
|
506
|
+
// Report each bad hash algorithm once, not once per referencing tag.
|
|
507
|
+
const reportedBadAlgorithms = new Set();
|
|
508
|
+
|
|
509
|
+
const chunksById = getChunksById(compilation);
|
|
510
|
+
return content.replace(
|
|
511
|
+
INTEGRITY_SENTINEL_REGEXP,
|
|
512
|
+
(_, hexId, contentHashType) => {
|
|
513
|
+
const chunkId = Buffer.from(hexId, "hex").toString("utf8");
|
|
514
|
+
const chunk = chunksById.get(chunkId);
|
|
515
|
+
if (!chunk) return "";
|
|
516
|
+
// `chunk.files` holds the real emitted names (after
|
|
517
|
+
// `RealContentHashPlugin`), so hash those bytes — not a name
|
|
518
|
+
// re-derived from the template, which still has the pre-hash value.
|
|
519
|
+
let filename;
|
|
520
|
+
for (const file of chunk.files) {
|
|
521
|
+
if ((contentHashType === "css") === file.endsWith(".css")) {
|
|
522
|
+
filename = file;
|
|
523
|
+
break;
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
if (!filename) return "";
|
|
527
|
+
const asset = compilation.getAsset(filename);
|
|
528
|
+
if (!asset) return "";
|
|
529
|
+
const algorithms =
|
|
530
|
+
typeof integrity === "function"
|
|
531
|
+
? integrity({ chunk, filename })
|
|
532
|
+
: integrity === true
|
|
533
|
+
? ["sha384"]
|
|
534
|
+
: integrity;
|
|
535
|
+
if (!algorithms || algorithms.length === 0) return "";
|
|
536
|
+
const buffer = asset.source.buffer();
|
|
537
|
+
const parts = [];
|
|
538
|
+
for (const algorithm of algorithms) {
|
|
539
|
+
// `crypto.createHash` throws on an unknown algorithm name — turn
|
|
540
|
+
// that into a webpack error and skip the algorithm instead of
|
|
541
|
+
// crashing the whole compilation out of `processAssets`.
|
|
542
|
+
try {
|
|
543
|
+
parts.push(
|
|
544
|
+
`${algorithm}-${crypto
|
|
545
|
+
.createHash(algorithm)
|
|
546
|
+
.update(buffer)
|
|
547
|
+
.digest("base64")}`
|
|
548
|
+
);
|
|
549
|
+
} catch (_err) {
|
|
550
|
+
if (!reportedBadAlgorithms.has(algorithm)) {
|
|
551
|
+
reportedBadAlgorithms.add(algorithm);
|
|
552
|
+
compilation.errors.push(
|
|
553
|
+
new WebpackError(
|
|
554
|
+
`output.html.integrity: unsupported hash algorithm ${JSON.stringify(
|
|
555
|
+
algorithm
|
|
556
|
+
)}`
|
|
557
|
+
)
|
|
558
|
+
);
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
if (parts.length === 0) return "";
|
|
563
|
+
return ` integrity="${parts.join(" ")}"`;
|
|
564
|
+
}
|
|
565
|
+
);
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
/**
|
|
569
|
+
* @param {Chunk} chunk chunk
|
|
570
|
+
* @param {"javascript" | "css"} contentHashType which chunk asset to inline
|
|
571
|
+
* @returns {string} sentinel
|
|
572
|
+
*/
|
|
573
|
+
static makeChunkInlineSentinel(chunk, contentHashType) {
|
|
574
|
+
const hexId = Buffer.from(String(chunk.id), "utf8").toString("hex");
|
|
575
|
+
return `__WEBPACK_HTML_INLINE__${hexId}__${contentHashType}__END__`;
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
/**
|
|
579
|
+
* Replace every inline sentinel with the chunk's actual source content.
|
|
580
|
+
* Must run after `RealContentHashPlugin` so the final emitted filenames are
|
|
581
|
+
* known (we look up the asset by filename from `chunk.files`).
|
|
582
|
+
* @param {string} content content
|
|
583
|
+
* @param {Compilation} compilation compilation
|
|
584
|
+
* @param {string} htmlFilename output name of the HTML file being resolved (to rebase CSS urls)
|
|
585
|
+
* @param {Set<string>=} inlinedFiles collects every chunk file inlined here, so the caller can drop now-unreferenced assets
|
|
586
|
+
* @returns {string} resolved content
|
|
587
|
+
*/
|
|
588
|
+
static resolveChunkInlineSentinels(
|
|
589
|
+
content,
|
|
590
|
+
compilation,
|
|
591
|
+
htmlFilename,
|
|
592
|
+
inlinedFiles
|
|
593
|
+
) {
|
|
594
|
+
if (!content.includes("__WEBPACK_HTML_INLINE__")) return content;
|
|
595
|
+
const chunksById = getChunksById(compilation);
|
|
596
|
+
const outputPath = /** @type {string} */ (compilation.outputOptions.path);
|
|
597
|
+
return content.replace(
|
|
598
|
+
INLINE_SENTINEL_REGEXP,
|
|
599
|
+
(_, hexId, contentHashType) => {
|
|
600
|
+
const chunkId = Buffer.from(hexId, "hex").toString("utf8");
|
|
601
|
+
const chunk = chunksById.get(chunkId);
|
|
602
|
+
if (!chunk) return "";
|
|
603
|
+
const isCss = contentHashType === "css";
|
|
604
|
+
let out = "";
|
|
605
|
+
for (const file of chunk.files) {
|
|
606
|
+
if (isCss !== file.endsWith(".css")) continue;
|
|
607
|
+
const asset = compilation.getAsset(file);
|
|
608
|
+
if (!asset) continue;
|
|
609
|
+
let raw = String(asset.source.source());
|
|
610
|
+
if (isCss) {
|
|
611
|
+
// Styles move into the document, so relative `url(...)` must be
|
|
612
|
+
// rebased; then escape `</style` so it can't close the block early.
|
|
613
|
+
raw = rebaseCssUrls(raw, file, htmlFilename, outputPath).replace(
|
|
614
|
+
/<\/(style)/gi,
|
|
615
|
+
"<\\/$1"
|
|
616
|
+
);
|
|
617
|
+
} else {
|
|
618
|
+
// Escape `</script` (early close) and `<!--` (enters the script-data
|
|
619
|
+
// escaped state, where a later `<script` hides the real close); both
|
|
620
|
+
// only occur inside a string/comment/regex, where `<\` is inert.
|
|
621
|
+
raw = raw
|
|
622
|
+
.replace(/<\/(script)/gi, "<\\/$1")
|
|
623
|
+
.replace(/<!--/g, "<\\!--");
|
|
624
|
+
}
|
|
625
|
+
out += raw;
|
|
626
|
+
if (inlinedFiles) inlinedFiles.add(file);
|
|
627
|
+
}
|
|
628
|
+
return out;
|
|
629
|
+
}
|
|
630
|
+
);
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
/**
|
|
634
|
+
* Parses the final HTML once and collects everything the `output.html`
|
|
635
|
+
* post-processing stages need: every `<script>`/`<link>`/`<style>`/`<meta>` as
|
|
636
|
+
* a mutable descriptor (for `transformTags`) paired with its source span, an
|
|
637
|
+
* attribute snapshot (to detect changes), and — for `<script>`/`<style>` — the
|
|
638
|
+
* raw-text body range and nonce insertion point (for CSP); plus the
|
|
639
|
+
* `<head>`/`<body>` open/content-end anchors (for `injectTags`). One parse feeds
|
|
640
|
+
* `injectTags` + `transformTags` + CSP, which `renderHtml` then applies in a
|
|
641
|
+
* single pass. Uses the pipeline's own parser (no regex).
|
|
642
|
+
* @param {string} content the final HTML (inline content already resolved)
|
|
643
|
+
* @returns {HtmlModel} the collected model
|
|
644
|
+
*/
|
|
645
|
+
static collectHtml(content) {
|
|
646
|
+
/** @type {HtmlMutableTag[]} */
|
|
647
|
+
const tags = [];
|
|
648
|
+
/** @type {(HtmlTagSpan | null)[]} */
|
|
649
|
+
const spans = [];
|
|
650
|
+
/** @type {(string | null)[]} */
|
|
651
|
+
const originals = [];
|
|
652
|
+
/** @type {(HtmlTagDescriptor | null)[]} */
|
|
653
|
+
const descriptors = [];
|
|
654
|
+
let headOpen = -1;
|
|
655
|
+
let headEnd = -1;
|
|
656
|
+
let bodyOpen = -1;
|
|
657
|
+
let bodyEnd = -1;
|
|
658
|
+
let headDepth = 0;
|
|
659
|
+
let bodyDepth = 0;
|
|
660
|
+
// `skip.text` populates `contentEnd` (raw-text body end) without
|
|
661
|
+
// materializing text nodes; `</script` stays escaped, so CSP hashes match
|
|
662
|
+
// the exact bytes the browser sees.
|
|
663
|
+
new SourceProcessor()
|
|
664
|
+
.use({
|
|
665
|
+
[NodeType.Element]: {
|
|
666
|
+
enter: (path) => {
|
|
667
|
+
const name = path.tagName();
|
|
668
|
+
if (name === "head") {
|
|
669
|
+
if (headOpen < 0) headOpen = headEnd = path.tagEnd();
|
|
670
|
+
headDepth++;
|
|
671
|
+
return;
|
|
672
|
+
}
|
|
673
|
+
if (name === "body") {
|
|
674
|
+
if (bodyOpen < 0) bodyOpen = bodyEnd = path.tagEnd();
|
|
675
|
+
bodyDepth++;
|
|
676
|
+
return;
|
|
677
|
+
}
|
|
678
|
+
const end = path.end();
|
|
679
|
+
if (headDepth > 0 && end > headEnd) headEnd = end;
|
|
680
|
+
if (bodyDepth > 0 && end > bodyEnd) bodyEnd = end;
|
|
681
|
+
if (
|
|
682
|
+
name !== "script" &&
|
|
683
|
+
name !== "link" &&
|
|
684
|
+
name !== "style" &&
|
|
685
|
+
name !== "meta"
|
|
686
|
+
) {
|
|
687
|
+
return;
|
|
688
|
+
}
|
|
689
|
+
/** @type {Record<string, string | boolean | undefined>} */
|
|
690
|
+
const attrs = {};
|
|
691
|
+
const count = path.attributeCount();
|
|
692
|
+
for (let i = 0; i < count; i++) {
|
|
693
|
+
const attr = path.attributeAt(i);
|
|
694
|
+
attrs[path.attributeName(attr)] =
|
|
695
|
+
path.attributeValueStart(attr) === -1
|
|
696
|
+
? true
|
|
697
|
+
: path.attributeValue(attr);
|
|
698
|
+
}
|
|
699
|
+
const isRawText = name === "script" || name === "style";
|
|
700
|
+
tags.push({
|
|
701
|
+
tag: name,
|
|
702
|
+
attrs,
|
|
703
|
+
injectTo: bodyDepth > 0 ? "body" : "head"
|
|
704
|
+
});
|
|
705
|
+
spans.push({
|
|
706
|
+
start: path.start(),
|
|
707
|
+
openEnd: path.tagEnd(),
|
|
708
|
+
end,
|
|
709
|
+
location: bodyDepth > 0 ? "body" : "head",
|
|
710
|
+
nameEnd: isRawText ? path.nameEnd() : -1,
|
|
711
|
+
contentEnd: isRawText ? path.contentEnd() : -1,
|
|
712
|
+
selfClosing: path.selfClosing()
|
|
713
|
+
});
|
|
714
|
+
originals.push(snapshotAttrs(attrs));
|
|
715
|
+
descriptors.push(null);
|
|
716
|
+
},
|
|
717
|
+
exit: (path) => {
|
|
718
|
+
const name = path.tagName();
|
|
719
|
+
if (name === "head") {
|
|
720
|
+
if (headDepth > 0) headDepth--;
|
|
721
|
+
} else if (name === "body" && bodyDepth > 0) {
|
|
722
|
+
bodyDepth--;
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
})
|
|
727
|
+
.process(content, { skip: { text: true, doctype: true } });
|
|
728
|
+
return {
|
|
729
|
+
tags,
|
|
730
|
+
spans,
|
|
731
|
+
originals,
|
|
732
|
+
descriptors,
|
|
733
|
+
anchors: { headOpen, headEnd, bodyOpen, bodyEnd }
|
|
734
|
+
};
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
/**
|
|
738
|
+
* Appends `injectTags` descriptors to the model as spanless tags (pure inserts
|
|
739
|
+
* placed by `injectTo`), so `transformTags` sees them alongside the page's own
|
|
740
|
+
* tags and CSP hashes any injected inline `<script>`/`<style>`. `attrs` is
|
|
741
|
+
* copied so a `transformTags` mutation never corrupts the caller's descriptor.
|
|
742
|
+
* @param {HtmlModel} model the value from `collectHtml`
|
|
743
|
+
* @param {HtmlTagDescriptor[]} injected the `injectTags` descriptors
|
|
744
|
+
* @returns {void}
|
|
745
|
+
*/
|
|
746
|
+
static addInjectedTags(model, injected) {
|
|
747
|
+
for (const descriptor of injected) {
|
|
748
|
+
model.tags.push({
|
|
749
|
+
tag: descriptor.tag,
|
|
750
|
+
attrs: descriptor.attrs ? { ...descriptor.attrs } : {},
|
|
751
|
+
injectTo: descriptor.injectTo || "head"
|
|
752
|
+
});
|
|
753
|
+
model.spans.push(null);
|
|
754
|
+
model.originals.push(null);
|
|
755
|
+
model.descriptors.push(descriptor);
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
/**
|
|
760
|
+
* Renders the collected (and hook-mutated) model back into HTML in a single
|
|
761
|
+
* pass: injected tags are placed by `injectTo`; an existing tag is left
|
|
762
|
+
* byte-for-byte unless it was removed, moved (`<head>`↔`<body>`, honoring
|
|
763
|
+
* `*-prepend`), or had its `attrs` changed; and — when `csp` is set and the
|
|
764
|
+
* page declares no CSP — a strict `<meta http-equiv="Content-Security-Policy">`
|
|
765
|
+
* is injected with a hash of every inline `<script>`/`<style>` (page's own and
|
|
766
|
+
* injected) plus an optional placeholder `nonce` on each. Edits run
|
|
767
|
+
* right-to-left; at a shared offset a delete/replace runs before an insert.
|
|
768
|
+
* @param {string} content the collected HTML (inline content already resolved)
|
|
769
|
+
* @param {HtmlModel} model the value from `collectHtml` after the hooks
|
|
770
|
+
* @param {CspOption} csp the `output.html.csp` option
|
|
771
|
+
* @returns {string} the rendered HTML
|
|
772
|
+
*/
|
|
773
|
+
static renderHtml(content, model, csp) {
|
|
774
|
+
const { tags, spans, originals, descriptors, anchors } = model;
|
|
775
|
+
// CSP setup: an author-declared policy (page's own or injected) wins.
|
|
776
|
+
const options = csp ? (csp === true ? {} : csp) : undefined;
|
|
777
|
+
const nonce = options && options.nonce;
|
|
778
|
+
let hasCsp = false;
|
|
779
|
+
if (options) {
|
|
780
|
+
for (let i = 0; i < tags.length; i++) {
|
|
781
|
+
const tag = tags[i];
|
|
782
|
+
if (!tag.remove && tag.tag === "meta" && metaIsCsp(tag.attrs)) {
|
|
783
|
+
hasCsp = true;
|
|
784
|
+
break;
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
const addCsp = Boolean(options) && !hasCsp;
|
|
789
|
+
/** @type {Set<string>} */
|
|
790
|
+
const scriptSrc = new Set();
|
|
791
|
+
/** @type {Set<string>} */
|
|
792
|
+
const styleSrc = new Set();
|
|
793
|
+
let hashOf;
|
|
794
|
+
if (addCsp) {
|
|
795
|
+
const hashFunction =
|
|
796
|
+
/** @type {Exclude<CspOption, boolean | undefined>} */ (options)
|
|
797
|
+
.hashFunction || "sha256";
|
|
798
|
+
const createHash = getCreateNodeHash();
|
|
799
|
+
hashOf = (/** @type {string} */ body) =>
|
|
800
|
+
`'${hashFunction}-${createHash(hashFunction)
|
|
801
|
+
.update(body, "utf8")
|
|
802
|
+
.digest("base64")}'`;
|
|
803
|
+
}
|
|
804
|
+
// Insertions accumulated per offset (placement order preserved); range edits
|
|
805
|
+
// replace/delete an existing tag's span; region groups collect placements so
|
|
806
|
+
// `*-prepend` precedes the plain name at a shared (empty-region) anchor.
|
|
807
|
+
/** @type {Map<number, string>} */
|
|
808
|
+
const inserts = new Map();
|
|
809
|
+
/** @type {(pos: number, text: string) => void} */
|
|
810
|
+
const at = (pos, text) => {
|
|
811
|
+
if (text) inserts.set(pos, (inserts.get(pos) || "") + text);
|
|
812
|
+
};
|
|
813
|
+
// Placements accumulate per region so `*-prepend` always precedes the plain
|
|
814
|
+
// name at a shared offset (an empty `<head>`/`<body>`); emitted at the anchors
|
|
815
|
+
// after the loop.
|
|
816
|
+
/** @type {Record<string, string>} */
|
|
817
|
+
const groups = {
|
|
818
|
+
"head-prepend": "",
|
|
819
|
+
head: "",
|
|
820
|
+
"body-prepend": "",
|
|
821
|
+
body: ""
|
|
822
|
+
};
|
|
823
|
+
/** @type {(region: string | undefined, text: string) => void} */
|
|
824
|
+
const place = (region, text) => {
|
|
825
|
+
if (text) {
|
|
826
|
+
groups[region !== undefined && region in groups ? region : "head"] +=
|
|
827
|
+
text;
|
|
828
|
+
}
|
|
829
|
+
};
|
|
830
|
+
/** @type {{ start: number, end: number, text: string, insert: boolean }[]} */
|
|
831
|
+
const edits = [];
|
|
832
|
+
for (let i = 0; i < tags.length; i++) {
|
|
833
|
+
const tag = tags[i];
|
|
834
|
+
const span = spans[i];
|
|
835
|
+
const isRawText = tag.tag === "script" || tag.tag === "style";
|
|
836
|
+
const needsNonce =
|
|
837
|
+
addCsp &&
|
|
838
|
+
Boolean(nonce) &&
|
|
839
|
+
isRawText &&
|
|
840
|
+
!attrPresent(tag.attrs, "nonce");
|
|
841
|
+
// Hash an inline `<script>`/`<style>` (page's own or injected). External
|
|
842
|
+
// `<script src>` is covered by `'self'`; a self-closing tag has no body.
|
|
843
|
+
if (addCsp && isRawText && !tag.remove) {
|
|
844
|
+
const isScript = tag.tag === "script";
|
|
845
|
+
let body = "";
|
|
846
|
+
if (span) {
|
|
847
|
+
if (
|
|
848
|
+
!span.selfClosing &&
|
|
849
|
+
!(isScript && attrPresent(tag.attrs, "src"))
|
|
850
|
+
) {
|
|
851
|
+
body = content.slice(span.openEnd, span.contentEnd);
|
|
852
|
+
}
|
|
853
|
+
} else if (!(isScript && attrPresent(tag.attrs, "src"))) {
|
|
854
|
+
body =
|
|
855
|
+
/** @type {HtmlTagDescriptor} */ (descriptors[i]).children || "";
|
|
856
|
+
}
|
|
857
|
+
if (body !== "") {
|
|
858
|
+
(isScript ? scriptSrc : styleSrc).add(
|
|
859
|
+
/** @type {(body: string) => string} */ (hashOf)(body)
|
|
860
|
+
);
|
|
861
|
+
}
|
|
862
|
+
}
|
|
863
|
+
if (!span) {
|
|
864
|
+
// Injected tag: serialize (with a nonce when CSP asks) and place it.
|
|
865
|
+
if (tag.remove) continue;
|
|
866
|
+
const descriptor = /** @type {HtmlTagDescriptor} */ (descriptors[i]);
|
|
867
|
+
const attrs = needsNonce
|
|
868
|
+
? { nonce: /** @type {string} */ (nonce), ...tag.attrs }
|
|
869
|
+
: tag.attrs;
|
|
870
|
+
place(
|
|
871
|
+
tag.injectTo || "head",
|
|
872
|
+
serializeTag({
|
|
873
|
+
tag: tag.tag,
|
|
874
|
+
attrs,
|
|
875
|
+
children: descriptor.children,
|
|
876
|
+
voidTag: descriptor.voidTag
|
|
877
|
+
})
|
|
878
|
+
);
|
|
879
|
+
continue;
|
|
880
|
+
}
|
|
881
|
+
// Existing tag.
|
|
882
|
+
if (tag.remove) {
|
|
883
|
+
edits.push({
|
|
884
|
+
start: span.start,
|
|
885
|
+
end: span.end,
|
|
886
|
+
text: "",
|
|
887
|
+
insert: false
|
|
888
|
+
});
|
|
889
|
+
continue;
|
|
890
|
+
}
|
|
891
|
+
const attrsChanged = snapshotAttrs(tag.attrs) !== originals[i];
|
|
892
|
+
const moved =
|
|
893
|
+
tag.injectTo !== undefined && tag.injectTo !== span.location;
|
|
894
|
+
const nonceStr = needsNonce
|
|
895
|
+
? ` nonce="${escapeAttribute(/** @type {string} */ (nonce))}"`
|
|
896
|
+
: "";
|
|
897
|
+
if (moved) {
|
|
898
|
+
const opening = `<${tag.tag}${nonceStr}${serializeAttrs(tag.attrs)}>`;
|
|
899
|
+
edits.push({
|
|
900
|
+
start: span.start,
|
|
901
|
+
end: span.end,
|
|
902
|
+
text: "",
|
|
903
|
+
insert: false
|
|
904
|
+
});
|
|
905
|
+
place(tag.injectTo, opening + content.slice(span.openEnd, span.end));
|
|
906
|
+
} else if (attrsChanged) {
|
|
907
|
+
// Rewrite the opening tag (a nonce, if any, lands first).
|
|
908
|
+
edits.push({
|
|
909
|
+
start: span.start,
|
|
910
|
+
end: span.openEnd,
|
|
911
|
+
text: `<${tag.tag}${nonceStr}${serializeAttrs(tag.attrs)}>`,
|
|
912
|
+
insert: false
|
|
913
|
+
});
|
|
914
|
+
} else if (nonceStr) {
|
|
915
|
+
// Nonce-only change: insert right after the tag name, rest verbatim.
|
|
916
|
+
edits.push({
|
|
917
|
+
start: span.nameEnd,
|
|
918
|
+
end: span.nameEnd,
|
|
919
|
+
text: nonceStr,
|
|
920
|
+
insert: true
|
|
921
|
+
});
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
if (addCsp) {
|
|
925
|
+
if (nonce) {
|
|
926
|
+
scriptSrc.add(`'nonce-${nonce}'`);
|
|
927
|
+
styleSrc.add(`'nonce-${nonce}'`);
|
|
928
|
+
}
|
|
929
|
+
// Strict baseline; a user `policy` directive replaces one, hashes/nonce append.
|
|
930
|
+
/** @type {Map<string, Set<string>>} */
|
|
931
|
+
const directives = new Map([
|
|
932
|
+
["script-src", new Set(["'self'"])],
|
|
933
|
+
["style-src", new Set(["'self'"])],
|
|
934
|
+
["object-src", new Set(["'none'"])],
|
|
935
|
+
["base-uri", new Set(["'self'"])]
|
|
936
|
+
]);
|
|
937
|
+
const policyOption =
|
|
938
|
+
/** @type {Exclude<CspOption, boolean | undefined>} */ (options).policy;
|
|
939
|
+
if (policyOption) {
|
|
940
|
+
for (const [name, value] of Object.entries(policyOption)) {
|
|
941
|
+
directives.set(name, new Set(Array.isArray(value) ? value : [value]));
|
|
942
|
+
}
|
|
943
|
+
}
|
|
944
|
+
/** @type {(name: string, extra: Set<string>) => void} */
|
|
945
|
+
const append = (name, extra) => {
|
|
946
|
+
let set = directives.get(name);
|
|
947
|
+
if (!set) directives.set(name, (set = new Set()));
|
|
948
|
+
for (const value of extra) set.add(value);
|
|
949
|
+
};
|
|
950
|
+
append("script-src", scriptSrc);
|
|
951
|
+
append("style-src", styleSrc);
|
|
952
|
+
const policy = [...directives]
|
|
953
|
+
.filter(([, values]) => values.size > 0)
|
|
954
|
+
.map(([name, values]) => `${name} ${[...values].join(" ")}`)
|
|
955
|
+
.join("; ");
|
|
956
|
+
const meta = `<meta http-equiv="Content-Security-Policy" content="${escapeAttribute(
|
|
957
|
+
policy
|
|
958
|
+
)}">`;
|
|
959
|
+
// First at the head anchor, so it precedes any head-prepend tag.
|
|
960
|
+
at(anchors.headOpen, meta);
|
|
961
|
+
}
|
|
962
|
+
// Emit region placements at the `<head>`/`<body>` anchors (after any CSP
|
|
963
|
+
// meta, prepend before the plain name). The parser always synthesizes an
|
|
964
|
+
// implied `<head>`/`<body>`, so both anchors are always present.
|
|
965
|
+
at(anchors.headOpen, groups["head-prepend"]);
|
|
966
|
+
at(anchors.headEnd, groups.head);
|
|
967
|
+
at(anchors.bodyOpen, groups["body-prepend"]);
|
|
968
|
+
at(anchors.bodyEnd, groups.body);
|
|
969
|
+
for (const [pos, text] of inserts) {
|
|
970
|
+
edits.push({ start: pos, end: pos, text, insert: true });
|
|
971
|
+
}
|
|
972
|
+
if (edits.length === 0) return content;
|
|
973
|
+
edits.sort(
|
|
974
|
+
(a, b) => b.start - a.start || Number(a.insert) - Number(b.insert)
|
|
975
|
+
);
|
|
976
|
+
let result = content;
|
|
977
|
+
for (const edit of edits) {
|
|
978
|
+
result = result.slice(0, edit.start) + edit.text + result.slice(edit.end);
|
|
979
|
+
}
|
|
980
|
+
return result;
|
|
981
|
+
}
|
|
982
|
+
|
|
983
|
+
/**
|
|
984
|
+
* Embeds each inlined chunk's content hash into its inline sentinel
|
|
985
|
+
* (`…__<type>__END__` → `…__<type>__<hash>__END__`). The sentinel otherwise
|
|
986
|
+
* carries only the stable chunk id, so the host HTML's bytes wouldn't change
|
|
987
|
+
* when the inlined content changes — the resolved chunk URL that normally
|
|
988
|
+
* busts the page `[contenthash]` is gone once inlined. Embedding the hash
|
|
989
|
+
* makes the emitted bytes content-dependent, so both the initial hash and
|
|
990
|
+
* RealContentHashPlugin's recompute pick up inlined-content changes. The
|
|
991
|
+
* resolver (`resolveChunkInlineSentinels`) ignores this segment.
|
|
992
|
+
* @param {string} content content with chunk-url sentinels already resolved
|
|
993
|
+
* @param {Compilation} compilation compilation
|
|
994
|
+
* @returns {string} content with content-hash-tagged inline sentinels
|
|
995
|
+
*/
|
|
996
|
+
static embedInlineChunkHashes(content, compilation) {
|
|
997
|
+
if (!content.includes("__WEBPACK_HTML_INLINE__")) return content;
|
|
998
|
+
const chunksById = getChunksById(compilation);
|
|
999
|
+
return content.replace(
|
|
1000
|
+
INLINE_SENTINEL_REGEXP,
|
|
1001
|
+
(match, hexId, contentHashType) => {
|
|
1002
|
+
const chunkId = Buffer.from(hexId, "hex").toString("utf8");
|
|
1003
|
+
const chunk = chunksById.get(chunkId);
|
|
1004
|
+
if (!chunk) return match;
|
|
1005
|
+
const hash =
|
|
1006
|
+
(chunk.contentHash && chunk.contentHash[contentHashType]) ||
|
|
1007
|
+
chunk.renderedHash ||
|
|
1008
|
+
chunk.hash;
|
|
1009
|
+
if (!hash) return match;
|
|
1010
|
+
return `__WEBPACK_HTML_INLINE__${hexId}__${contentHashType}__${hash}__END__`;
|
|
1011
|
+
}
|
|
1012
|
+
);
|
|
1013
|
+
}
|
|
1014
|
+
|
|
126
1015
|
/**
|
|
127
1016
|
* Creates an instance of HtmlGenerator.
|
|
128
1017
|
* @param {HtmlGeneratorOptions=} options generator options
|
|
@@ -165,7 +1054,9 @@ class HtmlGenerator extends Generator {
|
|
|
165
1054
|
/**
|
|
166
1055
|
* Whether this HTML module is reached as a compilation entry. Entry
|
|
167
1056
|
* modules have at least one incoming connection without an
|
|
168
|
-
* `originModule` (the EntryDependency added by `compilation.addEntry`)
|
|
1057
|
+
* `originModule` (the EntryDependency added by `compilation.addEntry`) —
|
|
1058
|
+
* this covers both HTML-as-entry-point and a linked `type: "html"` page
|
|
1059
|
+
* (added as its own entry by HtmlModulesPlugin).
|
|
169
1060
|
* @param {NormalModule} module module
|
|
170
1061
|
* @returns {boolean} true when the module is an entry
|
|
171
1062
|
*/
|
|
@@ -181,7 +1072,8 @@ class HtmlGenerator extends Generator {
|
|
|
181
1072
|
* Whether to emit the extracted `.html` file for this module.
|
|
182
1073
|
* `options.extract === true` always extracts; `false` and `"inline"` never;
|
|
183
1074
|
* when the option is left unspecified, extraction defaults to on for HTML
|
|
184
|
-
* modules used as compilation entries — that's the HTML-as-entry-point case
|
|
1075
|
+
* modules used as compilation entries — that's the HTML-as-entry-point case
|
|
1076
|
+
* and a linked `type: "html"` page (which is added as its own entry).
|
|
185
1077
|
* @param {NormalModule} module module
|
|
186
1078
|
* @returns {boolean} true when the `.html` file should be emitted
|
|
187
1079
|
*/
|