webpack 5.108.3 → 5.109.0
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/lib/APIPlugin.js +48 -29
- package/lib/CacheFacade.js +8 -0
- package/lib/ChunkGroup.js +1 -0
- package/lib/CompatibilityPlugin.js +31 -29
- package/lib/Compilation.js +50 -8
- package/lib/ConstPlugin.js +11 -11
- package/lib/ContextModule.js +239 -6
- package/lib/ContextModuleFactory.js +139 -14
- package/lib/DefinePlugin.js +396 -101
- package/lib/Entrypoint.js +1 -1
- package/lib/ExportsInfoApiPlugin.js +2 -2
- package/lib/ExternalModule.js +107 -5
- package/lib/ExternalModuleFactoryPlugin.js +20 -1
- package/lib/FileSystemInfo.js +130 -74
- package/lib/FlagDependencyExportsPlugin.js +55 -8
- package/lib/HotModuleReplacementPlugin.js +18 -15
- package/lib/LazyBarrel.js +30 -3
- 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 +169 -405
- 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 +33 -0
- package/lib/RuntimeTemplate.js +304 -16
- 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 +222 -32
- package/lib/config/normalization.js +26 -0
- package/lib/config/target.js +9 -1
- package/lib/css/CssGenerator.js +73 -73
- package/lib/css/CssLoadingRuntimeModule.js +5 -1
- package/lib/css/CssModulesPlugin.js +92 -53
- package/lib/css/CssParser.js +843 -302
- package/lib/css/syntax.js +1219 -483
- 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/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 +190 -87
- 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/HarmonyExportDependencyParserPlugin.js +7 -19
- package/lib/dependencies/HarmonyImportDependency.js +12 -1
- package/lib/dependencies/HarmonyImportDependencyParserPlugin.js +34 -107
- package/lib/dependencies/HarmonyImportGuard.js +192 -20
- 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 +87 -26
- 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 +32 -8
- package/lib/html/HtmlGenerator.js +940 -91
- package/lib/html/HtmlModule.js +3 -2
- package/lib/html/HtmlModulesPlugin.js +625 -170
- package/lib/html/HtmlParser.js +812 -340
- package/lib/html/favicon.svg +1 -0
- package/lib/html/syntax.js +2116 -834
- package/lib/ids/IdHelpers.js +1 -1
- 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 +711 -355
- package/lib/javascript/JavascriptParserHelpers.js +3 -6
- package/lib/javascript/syntax.js +4380 -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 +19 -0
- package/lib/node/ReadFileCompileWasmPlugin.js +14 -0
- package/lib/optimize/ConcatenatedModule.js +120 -16
- package/lib/optimize/FlagIncludedChunksPlugin.js +13 -3
- package/lib/optimize/InnerGraphPlugin.js +50 -30
- package/lib/optimize/ModuleConcatenationPlugin.js +69 -3
- package/lib/optimize/SideEffectsFlagPlugin.js +86 -43
- package/lib/prefetch/ChunkPrefetchPreloadPlugin.js +15 -0
- package/lib/prefetch/ChunkPreloadTriggerRuntimeModule.js +24 -13
- package/lib/prefetch/ResourceHintPlugin.js +663 -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/FileMiddleware.js +29 -3
- package/lib/stats/DefaultStatsFactoryPlugin.js +32 -3
- package/lib/stats/DefaultStatsPresetPlugin.js +2 -0
- package/lib/stats/DefaultStatsPrinterPlugin.js +12 -2
- package/lib/stats/StatsFactory.js +15 -8
- package/lib/url/URLParserPlugin.js +98 -19
- package/lib/util/AsyncQueue.js +122 -72
- package/lib/util/LocConverter.js +9 -8
- package/lib/util/SetHelpers.js +1 -1
- package/lib/util/SourceProcessor.js +41 -28
- 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 +14 -13
- 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 +1300 -325
|
@@ -10,6 +10,8 @@ const {
|
|
|
10
10
|
JAVASCRIPT_TYPE
|
|
11
11
|
} = require("../ModuleSourceTypeConstants");
|
|
12
12
|
const HtmlGenerator = require("../html/HtmlGenerator");
|
|
13
|
+
const ResourceHintPlugin = require("../prefetch/ResourceHintPlugin");
|
|
14
|
+
const ResourceHintRuntimeModule = require("../prefetch/ResourceHintRuntimeModule");
|
|
13
15
|
const makeSerializable = require("../util/makeSerializable");
|
|
14
16
|
const ModuleDependency = require("./ModuleDependency");
|
|
15
17
|
|
|
@@ -20,10 +22,10 @@ const ModuleDependency = require("./ModuleDependency");
|
|
|
20
22
|
/** @typedef {import("../DependencyTemplate").DependencyTemplateContext} DependencyTemplateContext */
|
|
21
23
|
/** @typedef {import("../Entrypoint")} Entrypoint */
|
|
22
24
|
/** @typedef {import("../javascript/JavascriptParser").Range} Range */
|
|
23
|
-
/** @typedef {"script" | "script-module" | "modulepreload" | "stylesheet"} HtmlEntryElementKind */
|
|
25
|
+
/** @typedef {"script" | "script-module" | "modulepreload" | "stylesheet" | "html" | "preload" | "prefetch"} HtmlEntryElementKind */
|
|
24
26
|
|
|
25
|
-
/** @typedef {import("../serialization/ObjectMiddleware").ObjectDeserializerContext<[string, string, HtmlEntryElementKind, number, number, boolean, string, number, boolean, number, (Range | null), (Range | null)]>} ObjectDeserializerContext */
|
|
26
|
-
/** @typedef {import("../serialization/ObjectMiddleware").ObjectSerializerContext<[string, string, HtmlEntryElementKind, number, number, boolean, string, number, boolean, number, (Range | null), (Range | null)]>} ObjectSerializerContext */
|
|
27
|
+
/** @typedef {import("../serialization/ObjectMiddleware").ObjectDeserializerContext<[string, string, HtmlEntryElementKind, number, number, boolean, string, number, boolean, number, (Range | null), (Range | null), boolean, number, number]>} ObjectDeserializerContext */
|
|
28
|
+
/** @typedef {import("../serialization/ObjectMiddleware").ObjectSerializerContext<[string, string, HtmlEntryElementKind, number, number, boolean, string, number, boolean, number, (Range | null), (Range | null), boolean, number, number]>} ObjectSerializerContext */
|
|
27
29
|
|
|
28
30
|
class HtmlEntryDependency extends ModuleDependency {
|
|
29
31
|
/**
|
|
@@ -39,9 +41,12 @@ class HtmlEntryDependency extends ModuleDependency {
|
|
|
39
41
|
* @param {string=} copyableAttrsText the originating tag's `nonce`/`crossorigin`/`referrerpolicy` attribute source spans (leading-space-prefixed, in that fixed order), captured at parse time so synthesized sibling `<link>`/`<script>` tags carry the same CSP/fetch policy without the template re-parsing the tag text; empty when none are present
|
|
40
42
|
* @param {number=} tagNameEnd position right after the originating tag's name (e.g. after `<script`), captured at parse time so the template can insert a `crossorigin` attribute without re-scanning the tag text for the name boundary
|
|
41
43
|
* @param {boolean=} hasOwnCrossOrigin whether the originating tag already carries a `crossorigin` attribute; when true the author's value wins and `output.crossOriginLoading` is not applied
|
|
42
|
-
* @param {number=} cssAnchor source offset of the first `<script>` tag
|
|
44
|
+
* @param {number=} cssAnchor source offset of the first classic blocking `<script>` tag (not `defer`/`async`/module — those execute after parsing and after pending stylesheets); injected stylesheet `<link>`s are kept ahead of it. -1 when the document has no blocking script, letting CSS follow `defer`/module script tags (the order Vite emits)
|
|
43
45
|
* @param {Range | null=} integrityRange source span of the originating tag's `integrity` attribute (offsets relative to `tagStart`, leading whitespace and quotes included), captured at parse time so cloned sibling tags can drop it without re-parsing; null when absent
|
|
44
46
|
* @param {Range | null=} typeValueRange source span of the originating tag's `type` attribute value (offsets relative to `tagStart`), captured at parse time so `script-module` sibling clones can force `type="module"` in place; null when there is no value to rewrite (a `type="module"` is then inserted instead)
|
|
47
|
+
* @param {boolean=} forceInline `<!-- webpackInline: true -->` was set before this tag; takes precedence over `output.html.inline`
|
|
48
|
+
* @param {number=} headOpenEnd position just inside the (possibly implicit) `<head>` — after its opening tag, else after `<html>` or the doctype — captured from the parse so the template needn't re-scan the source (a text scan would match markup inside comments); resource hints are inserted here. -1 when unknown
|
|
49
|
+
* @param {number=} headAnchor position right after the last node inside `<head>` (the same anchor `output.html` title/meta injection uses); `inject: "head"` sibling tags are inserted here. -1 when the document has no usable head (insertion then falls back to the entry tag)
|
|
45
50
|
*/
|
|
46
51
|
constructor(
|
|
47
52
|
request,
|
|
@@ -57,7 +62,10 @@ class HtmlEntryDependency extends ModuleDependency {
|
|
|
57
62
|
hasOwnCrossOrigin,
|
|
58
63
|
cssAnchor,
|
|
59
64
|
integrityRange,
|
|
60
|
-
typeValueRange
|
|
65
|
+
typeValueRange,
|
|
66
|
+
forceInline,
|
|
67
|
+
headOpenEnd,
|
|
68
|
+
headAnchor
|
|
61
69
|
) {
|
|
62
70
|
super(request);
|
|
63
71
|
this.range = range;
|
|
@@ -85,6 +93,12 @@ class HtmlEntryDependency extends ModuleDependency {
|
|
|
85
93
|
this.integrityRange = integrityRange || null;
|
|
86
94
|
/** @type {Range | null} */
|
|
87
95
|
this.typeValueRange = typeValueRange || null;
|
|
96
|
+
/** @type {boolean} */
|
|
97
|
+
this.forceInline = forceInline === true;
|
|
98
|
+
/** @type {number} */
|
|
99
|
+
this.headOpenEnd = headOpenEnd === undefined ? -1 : headOpenEnd;
|
|
100
|
+
/** @type {number} */
|
|
101
|
+
this.headAnchor = headAnchor === undefined ? -1 : headAnchor;
|
|
88
102
|
}
|
|
89
103
|
|
|
90
104
|
get type() {
|
|
@@ -112,7 +126,10 @@ class HtmlEntryDependency extends ModuleDependency {
|
|
|
112
126
|
.write(this.hasOwnCrossOrigin)
|
|
113
127
|
.write(this.cssAnchor)
|
|
114
128
|
.write(this.integrityRange)
|
|
115
|
-
.write(this.typeValueRange)
|
|
129
|
+
.write(this.typeValueRange)
|
|
130
|
+
.write(this.forceInline)
|
|
131
|
+
.write(this.headOpenEnd)
|
|
132
|
+
.write(this.headAnchor);
|
|
116
133
|
super.serialize(context);
|
|
117
134
|
}
|
|
118
135
|
|
|
@@ -144,7 +161,13 @@ class HtmlEntryDependency extends ModuleDependency {
|
|
|
144
161
|
this.integrityRange = c10.read();
|
|
145
162
|
const c11 = c10.rest;
|
|
146
163
|
this.typeValueRange = c11.read();
|
|
147
|
-
|
|
164
|
+
const c12 = c11.rest;
|
|
165
|
+
this.forceInline = c12.read();
|
|
166
|
+
const c13 = c12.rest;
|
|
167
|
+
this.headOpenEnd = c13.read();
|
|
168
|
+
const c14 = c13.rest;
|
|
169
|
+
this.headAnchor = c14.read();
|
|
170
|
+
super.deserialize(c14.rest);
|
|
148
171
|
}
|
|
149
172
|
}
|
|
150
173
|
|
|
@@ -278,6 +301,25 @@ const cssChunkSortKey = (chunk) =>
|
|
|
278
301
|
chunk.id === null || chunk.id === undefined ? "" : chunk.id
|
|
279
302
|
}`;
|
|
280
303
|
|
|
304
|
+
/**
|
|
305
|
+
* Sort comparator for the CSS sibling emission order. Direct subtraction
|
|
306
|
+
* would yield `NaN` when both indices are `Infinity` (the documented
|
|
307
|
+
* fallback for chunks whose CSS modules the entrypoint's walk never
|
|
308
|
+
* reaches), and `Array#sort` doesn't promise stable ordering on the legacy
|
|
309
|
+
* Node 10 targets this repo still supports — so the tie-breaker must always
|
|
310
|
+
* run when the indices match, including the `Infinity === Infinity` case.
|
|
311
|
+
* @param {{ index: number, key: string }} a first entry
|
|
312
|
+
* @param {{ index: number, key: string }} b second entry
|
|
313
|
+
* @returns {-1 | 0 | 1} sort order
|
|
314
|
+
*/
|
|
315
|
+
const compareCssChunkOrder = (a, b) => {
|
|
316
|
+
if (a.index < b.index) return -1;
|
|
317
|
+
if (a.index > b.index) return 1;
|
|
318
|
+
if (a.key < b.key) return -1;
|
|
319
|
+
if (a.key > b.key) return 1;
|
|
320
|
+
return 0;
|
|
321
|
+
};
|
|
322
|
+
|
|
281
323
|
const CSS_SOURCE_TYPES = [CSS_TYPE, CSS_IMPORT_TYPE];
|
|
282
324
|
|
|
283
325
|
/**
|
|
@@ -328,11 +370,12 @@ const firstCssModulePostOrderIndex = (chunk, entrypoint, chunkGraph) => {
|
|
|
328
370
|
* @param {string} extra the copyable CSP/fetch attribute source text (leading-space-prefixed, or empty)
|
|
329
371
|
* @param {string} href URL for the stylesheet
|
|
330
372
|
* @param {string} crossOrigin ` crossorigin="…"` to append from `output.crossOriginLoading`, or `""`
|
|
373
|
+
* @param {string} integrity ` integrity="<sentinel>"` to append when `output.html.integrity` is on, or `""`
|
|
331
374
|
* @returns {string} the sibling `<link>` tag's HTML
|
|
332
375
|
*/
|
|
333
|
-
const buildStylesheetLink = (extra, href, crossOrigin) => {
|
|
376
|
+
const buildStylesheetLink = (extra, href, crossOrigin, integrity) => {
|
|
334
377
|
const safeHref = href.replace(/"/g, """);
|
|
335
|
-
return `<link rel="stylesheet" href="${safeHref}"${extra}${crossOrigin}>`;
|
|
378
|
+
return `<link rel="stylesheet" href="${safeHref}"${extra}${crossOrigin}${integrity}>`;
|
|
336
379
|
};
|
|
337
380
|
|
|
338
381
|
/**
|
|
@@ -348,13 +391,12 @@ const buildStylesheetLink = (extra, href, crossOrigin) => {
|
|
|
348
391
|
* @param {string} src URL for the script
|
|
349
392
|
* @param {boolean} isModule whether to emit `type="module"`
|
|
350
393
|
* @param {string} crossOrigin ` crossorigin="…"` to append from `output.crossOriginLoading`, or `""`
|
|
394
|
+
* @param {string} integrity ` integrity="<sentinel>"` to append when `output.html.integrity` is on, or `""`
|
|
351
395
|
* @returns {string} the sibling `<script>` tag's HTML
|
|
352
396
|
*/
|
|
353
|
-
const buildScriptTag = (extra, src, isModule, crossOrigin) => {
|
|
397
|
+
const buildScriptTag = (extra, src, isModule, crossOrigin, integrity) => {
|
|
354
398
|
const safeSrc = src.replace(/"/g, """);
|
|
355
|
-
return `<script${
|
|
356
|
-
isModule ? ' type="module"' : ""
|
|
357
|
-
} src="${safeSrc}"${extra}${crossOrigin}></script>`;
|
|
399
|
+
return `<script${isModule ? ' type="module"' : ""} src="${safeSrc}"${extra}${crossOrigin}${integrity}></script>`;
|
|
358
400
|
};
|
|
359
401
|
|
|
360
402
|
/**
|
|
@@ -375,9 +417,10 @@ const buildScriptTag = (extra, src, isModule, crossOrigin) => {
|
|
|
375
417
|
* @param {string} newUrl URL to put into the cloned tag's src/href slot
|
|
376
418
|
* @param {HtmlEntryElementKind} elementKind shape of the originating tag
|
|
377
419
|
* @param {string} crossOrigin ` crossorigin="…"` to insert from `output.crossOriginLoading`, or `""`
|
|
378
|
-
* @param {number} tagNameEndInTag offset right after the tag name within `originalTag`, where `crossOrigin`
|
|
420
|
+
* @param {number} tagNameEndInTag offset right after the tag name within `originalTag`, where `crossOrigin`/`integrity` are inserted
|
|
379
421
|
* @param {Range | null} integrityRange `integrity` attribute span within `originalTag` to drop, or null
|
|
380
422
|
* @param {Range | null} typeValueRange `type` value span within `originalTag` to overwrite with `module`, or null to insert `type="module"`
|
|
423
|
+
* @param {string} integrity ` integrity="<sentinel>"` to insert when `output.html.integrity` is on, or `""`
|
|
381
424
|
* @returns {string} the sibling tag's HTML (including a closing `</script>` for script tags)
|
|
382
425
|
*/
|
|
383
426
|
const cloneTagWithUrl = (
|
|
@@ -389,7 +432,8 @@ const cloneTagWithUrl = (
|
|
|
389
432
|
crossOrigin,
|
|
390
433
|
tagNameEndInTag,
|
|
391
434
|
integrityRange,
|
|
392
|
-
typeValueRange
|
|
435
|
+
typeValueRange,
|
|
436
|
+
integrity
|
|
393
437
|
) => {
|
|
394
438
|
const isLink =
|
|
395
439
|
elementKind === "modulepreload" || elementKind === "stylesheet";
|
|
@@ -397,9 +441,9 @@ const cloneTagWithUrl = (
|
|
|
397
441
|
/** @type {{ start: number, end: number, text: string }[]} */
|
|
398
442
|
const edits = [{ start: srcStartInTag, end: srcEndInTag, text: newUrl }];
|
|
399
443
|
|
|
400
|
-
// `integrity`
|
|
401
|
-
//
|
|
402
|
-
//
|
|
444
|
+
// Drop the author's `integrity` — content-specific, wrong for another
|
|
445
|
+
// chunk's file. A correct per-chunk one is re-inserted below from
|
|
446
|
+
// `output.html.integrity`.
|
|
403
447
|
if (integrityRange) {
|
|
404
448
|
edits.push({ start: integrityRange[0], end: integrityRange[1], text: "" });
|
|
405
449
|
}
|
|
@@ -420,6 +464,8 @@ const cloneTagWithUrl = (
|
|
|
420
464
|
}
|
|
421
465
|
}
|
|
422
466
|
|
|
467
|
+
// Per-chunk `integrity` sentinel goes in alongside `crossorigin`/`type`.
|
|
468
|
+
afterName += integrity;
|
|
423
469
|
if (afterName) {
|
|
424
470
|
edits.push({
|
|
425
471
|
start: tagNameEndInTag,
|
|
@@ -438,6 +484,181 @@ const cloneTagWithUrl = (
|
|
|
438
484
|
return isLink ? body : `${body}</script>`;
|
|
439
485
|
};
|
|
440
486
|
|
|
487
|
+
// ES5 `<link rel="modulepreload">` polyfill (Safari <17, Firefox <115, …):
|
|
488
|
+
// on browsers without native support, fetch each modulepreload target with the
|
|
489
|
+
// tag's own credentials/integrity so the pending module import reuses it. The
|
|
490
|
+
// `.ep` guard makes it idempotent, and the observer catches any later-inserted
|
|
491
|
+
// links. Emitted only when the target environment lacks native support.
|
|
492
|
+
const MODULEPRELOAD_POLYFILL = [
|
|
493
|
+
"(function(){",
|
|
494
|
+
'var s=document.createElement("link").relList;',
|
|
495
|
+
'if(s&&s.supports&&s.supports("modulepreload"))return;',
|
|
496
|
+
"function f(e){",
|
|
497
|
+
"if(e.ep)return;e.ep=1;",
|
|
498
|
+
"var o={};",
|
|
499
|
+
"if(e.integrity)o.integrity=e.integrity;",
|
|
500
|
+
"if(e.referrerPolicy)o.referrerPolicy=e.referrerPolicy;",
|
|
501
|
+
'o.credentials=e.crossOrigin==="use-credentials"?"include":e.crossOrigin==="anonymous"?"omit":"same-origin";',
|
|
502
|
+
"fetch(e.href,o)}",
|
|
503
|
+
"var l=document.querySelectorAll('link[rel=\"modulepreload\"]');",
|
|
504
|
+
"for(var i=0;i<l.length;i++)f(l[i]);",
|
|
505
|
+
"new MutationObserver(function(m){",
|
|
506
|
+
"for(var j=0;j<m.length;j++)",
|
|
507
|
+
"for(var a=m[j].addedNodes,k=0;k<a.length;k++)",
|
|
508
|
+
'if(a[k].tagName==="LINK"&&a[k].rel==="modulepreload")f(a[k])',
|
|
509
|
+
"}).observe(document,{childList:true,subtree:true})",
|
|
510
|
+
"})();"
|
|
511
|
+
].join("");
|
|
512
|
+
|
|
513
|
+
// One polyfill `<script>` per rendered page. Keyed by the `ReplaceSource` of a
|
|
514
|
+
// single HTML code-generation pass (shared by all of a page's entry tags, fresh
|
|
515
|
+
// per pass), so multi-`<script>` pages get exactly one and the cache stays correct.
|
|
516
|
+
/** @type {WeakSet<ReplaceSource>} */
|
|
517
|
+
const polyfilledSources = new WeakSet();
|
|
518
|
+
|
|
519
|
+
/** @typedef {"preload" | "prefetch" | "modulepreload" | "preconnect" | "dns-prefetch"} HtmlResourceHintRel */
|
|
520
|
+
/**
|
|
521
|
+
* A custom resource-hint `<link>` (`output.html.resourceHints`). Exactly one of
|
|
522
|
+
* `href` / `chunk` / `entry` names the target; `chunk`/`entry` URLs (hash and
|
|
523
|
+
* public path) are resolved by webpack, `href` is used verbatim.
|
|
524
|
+
* @typedef {object} HtmlResourceHint
|
|
525
|
+
* @property {HtmlResourceHintRel} rel hint relationship
|
|
526
|
+
* @property {string=} href literal URL (external, `preconnect`, or an already-hashed asset)
|
|
527
|
+
* @property {string=} chunk chunk name to hint
|
|
528
|
+
* @property {string=} entry entrypoint name to hint (expands to one hint per initial chunk)
|
|
529
|
+
* @property {string=} as the `as` attribute; defaults to `script` for chunk/entry refs
|
|
530
|
+
* @property {string=} type the `type` attribute (MIME)
|
|
531
|
+
* @property {(boolean | "anonymous" | "use-credentials")=} crossorigin CORS mode (`true` → `anonymous`)
|
|
532
|
+
* @property {string=} media the `media` attribute
|
|
533
|
+
* @property {boolean=} integrity SRI for a chunk/entry ref; follows `output.html.integrity` by default, `false` opts out
|
|
534
|
+
* @property {("low" | "high" | "auto")=} fetchPriority the `fetchpriority` attribute (allowed on `preload`/`modulepreload`/`prefetch`)
|
|
535
|
+
*/
|
|
536
|
+
/**
|
|
537
|
+
* @typedef {object} HtmlResourceHintContext
|
|
538
|
+
* @property {string} entryName name of the entry this page loads
|
|
539
|
+
* @property {Entrypoint} entrypoint the entrypoint
|
|
540
|
+
* @property {"html" | "js"} hostType `"html"` when the entry has an extracted HTML page; `"js"` when a JS-only entry is being consulted (SSR framework case)
|
|
541
|
+
* @property {import("../Compilation")} compilation the compilation
|
|
542
|
+
* @property {{ rel: "modulepreload" | "preload" | "prefetch", chunk: string, hostChunks: string[] }[]} defaultHints the auto initial-graph hints (`chunk` / `hostChunks` name the referencing chunk — Vite's `hostId`)
|
|
543
|
+
*/
|
|
544
|
+
|
|
545
|
+
/**
|
|
546
|
+
* @param {string} value attribute value
|
|
547
|
+
* @returns {string} value with `"` escaped so it can't break out of the attribute
|
|
548
|
+
*/
|
|
549
|
+
const escapeAttr = (value) => String(value).replace(/"/g, """);
|
|
550
|
+
|
|
551
|
+
/**
|
|
552
|
+
* @param {boolean | "anonymous" | "use-credentials" | undefined} value crossorigin option
|
|
553
|
+
* @returns {string | undefined} ` crossorigin="…"`, or undefined when unset/false
|
|
554
|
+
*/
|
|
555
|
+
const crossOriginAttr = (value) => {
|
|
556
|
+
if (value === undefined || value === false) return undefined;
|
|
557
|
+
return ` crossorigin="${value === true ? "anonymous" : value}"`;
|
|
558
|
+
};
|
|
559
|
+
|
|
560
|
+
/**
|
|
561
|
+
* Build one resource-hint `<link>` tag from a resolved href and attributes.
|
|
562
|
+
* @param {HtmlResourceHintRel} rel the `rel`
|
|
563
|
+
* @param {{ href: string, as?: string, type?: string, crossorigin?: string, integrity?: string, media?: string, fetchpriority?: string }} attrs attributes (already href-resolved; `crossorigin`/`integrity` are full ` name="…"` spans)
|
|
564
|
+
* @returns {string} the `<link>` tag
|
|
565
|
+
*/
|
|
566
|
+
const buildLinkTag = (rel, attrs) =>
|
|
567
|
+
`<link rel="${rel}"${attrs.as ? ` as="${escapeAttr(attrs.as)}"` : ""}${
|
|
568
|
+
attrs.type ? ` type="${escapeAttr(attrs.type)}"` : ""
|
|
569
|
+
} href="${escapeAttr(attrs.href)}"${attrs.crossorigin || ""}${
|
|
570
|
+
attrs.integrity || ""
|
|
571
|
+
}${attrs.media ? ` media="${escapeAttr(attrs.media)}"` : ""}${
|
|
572
|
+
attrs.fetchpriority
|
|
573
|
+
? ` fetchpriority="${escapeAttr(attrs.fetchpriority)}"`
|
|
574
|
+
: ""
|
|
575
|
+
}>`;
|
|
576
|
+
|
|
577
|
+
/**
|
|
578
|
+
* Resolve one `output.html.resourceHints` descriptor to zero or more `<link>`
|
|
579
|
+
* tags. A `chunk`/`entry` reference becomes the emitted chunk URL (a sentinel
|
|
580
|
+
* resolved with the final hash and public path), an `href` is used verbatim, and
|
|
581
|
+
* `preconnect`/`dns-prefetch` use only `href`. Unknown chunk/entry names are skipped.
|
|
582
|
+
* @param {HtmlResourceHint} desc the descriptor
|
|
583
|
+
* @param {{ compilation: import("../Compilation"), chunkGraph: ChunkGraph, chunkByName: Map<string, Chunk>, outputCrossOrigin: string, integrityOn: boolean }} ctx resolution context
|
|
584
|
+
* @returns {string[]} the resolved tags
|
|
585
|
+
*/
|
|
586
|
+
const resolveResourceHint = (desc, ctx) => {
|
|
587
|
+
const rel = desc.rel;
|
|
588
|
+
if (rel === "preconnect" || rel === "dns-prefetch") {
|
|
589
|
+
if (!desc.href) return [];
|
|
590
|
+
return [
|
|
591
|
+
buildLinkTag(rel, {
|
|
592
|
+
href: desc.href,
|
|
593
|
+
crossorigin: crossOriginAttr(desc.crossorigin),
|
|
594
|
+
media: desc.media
|
|
595
|
+
})
|
|
596
|
+
];
|
|
597
|
+
}
|
|
598
|
+
if (desc.href) {
|
|
599
|
+
return [
|
|
600
|
+
buildLinkTag(rel, {
|
|
601
|
+
href: desc.href,
|
|
602
|
+
as: desc.as,
|
|
603
|
+
type: desc.type,
|
|
604
|
+
crossorigin: crossOriginAttr(desc.crossorigin),
|
|
605
|
+
media: desc.media,
|
|
606
|
+
fetchpriority: desc.fetchPriority
|
|
607
|
+
})
|
|
608
|
+
];
|
|
609
|
+
}
|
|
610
|
+
/** @type {Chunk[]} */
|
|
611
|
+
let chunks;
|
|
612
|
+
if (desc.entry) {
|
|
613
|
+
const entrypoint = ctx.compilation.entrypoints.get(desc.entry);
|
|
614
|
+
if (!entrypoint) return [];
|
|
615
|
+
chunks = getEntrypointChunksInLoadOrder(entrypoint);
|
|
616
|
+
} else if (desc.chunk) {
|
|
617
|
+
const chunk = ctx.chunkByName.get(desc.chunk);
|
|
618
|
+
if (!chunk) return [];
|
|
619
|
+
chunks = [chunk];
|
|
620
|
+
} else {
|
|
621
|
+
return [];
|
|
622
|
+
}
|
|
623
|
+
const kind = desc.as === "style" ? "css" : "javascript";
|
|
624
|
+
const as = desc.as || (rel === "modulepreload" ? undefined : "script");
|
|
625
|
+
// A hint targeting another chunk must match how that chunk is fetched, so
|
|
626
|
+
// inherit `output.crossOriginLoading` unless the descriptor overrides it.
|
|
627
|
+
const crossorigin =
|
|
628
|
+
desc.crossorigin !== undefined
|
|
629
|
+
? crossOriginAttr(desc.crossorigin)
|
|
630
|
+
: ctx.outputCrossOrigin || undefined;
|
|
631
|
+
/** @type {string[]} */
|
|
632
|
+
const tags = [];
|
|
633
|
+
for (const chunk of chunks) {
|
|
634
|
+
const has =
|
|
635
|
+
kind === "css"
|
|
636
|
+
? chunkHasCss(chunk, ctx.chunkGraph)
|
|
637
|
+
: chunkHasJs(chunk, ctx.chunkGraph);
|
|
638
|
+
if (!has) continue;
|
|
639
|
+
tags.push(
|
|
640
|
+
buildLinkTag(rel, {
|
|
641
|
+
href: HtmlGenerator.makeChunkUrlSentinel(chunk, kind),
|
|
642
|
+
as,
|
|
643
|
+
type: desc.type,
|
|
644
|
+
crossorigin,
|
|
645
|
+
// SRI follows the global `output.html.integrity` policy (like the
|
|
646
|
+
// auto hints); `integrity: false` opts a single hint out. Only
|
|
647
|
+
// `preload`/`modulepreload` are SRI-eligible — `prefetch` is not.
|
|
648
|
+
integrity:
|
|
649
|
+
(rel === "preload" || rel === "modulepreload") &&
|
|
650
|
+
desc.integrity !== false &&
|
|
651
|
+
ctx.integrityOn
|
|
652
|
+
? ` integrity="${HtmlGenerator.makeChunkIntegritySentinel(chunk, kind)}"`
|
|
653
|
+
: undefined,
|
|
654
|
+
media: desc.media,
|
|
655
|
+
fetchpriority: desc.fetchPriority
|
|
656
|
+
})
|
|
657
|
+
);
|
|
658
|
+
}
|
|
659
|
+
return tags;
|
|
660
|
+
};
|
|
661
|
+
|
|
441
662
|
HtmlEntryDependency.Template = class HtmlEntryDependencyTemplate extends (
|
|
442
663
|
ModuleDependency.Template
|
|
443
664
|
) {
|
|
@@ -452,6 +673,20 @@ HtmlEntryDependency.Template = class HtmlEntryDependencyTemplate extends (
|
|
|
452
673
|
const { runtimeTemplate } = templateContext;
|
|
453
674
|
const dep = /** @type {HtmlEntryDependency} */ (dependency);
|
|
454
675
|
const compilation = runtimeTemplate.compilation;
|
|
676
|
+
|
|
677
|
+
// A `type: "html"` link points at another emitted page, not a JS/CSS
|
|
678
|
+
// chunk: rewrite the attribute to that page's filename (resolved by
|
|
679
|
+
// HtmlModulesPlugin once it's built) and emit no sibling tags — a page
|
|
680
|
+
// link loads only itself.
|
|
681
|
+
if (dep.elementKind === "html") {
|
|
682
|
+
source.replace(
|
|
683
|
+
dep.range[0],
|
|
684
|
+
dep.range[1] - 1,
|
|
685
|
+
HtmlGenerator.makeHtmlPageUrlSentinel(dep.entryName)
|
|
686
|
+
);
|
|
687
|
+
return;
|
|
688
|
+
}
|
|
689
|
+
|
|
455
690
|
const { chunkGraph } = compilation;
|
|
456
691
|
const { crossOriginLoading } = compilation.outputOptions;
|
|
457
692
|
const entrypoint = /** @type {Entrypoint | undefined} */ (
|
|
@@ -465,74 +700,218 @@ HtmlEntryDependency.Template = class HtmlEntryDependencyTemplate extends (
|
|
|
465
700
|
|
|
466
701
|
const orderedChunks = getEntrypointChunksInLoadOrder(entrypoint);
|
|
467
702
|
const entryChunk = orderedChunks[orderedChunks.length - 1];
|
|
703
|
+
|
|
704
|
+
// `crossorigin` mirrors `output.crossOriginLoading` onto every injected
|
|
705
|
+
// tag. Empty when the option is off or the originating tag already set
|
|
706
|
+
// `crossorigin` (author value wins, flagged at parse time). Mirrors
|
|
707
|
+
// webpack's runtime, which sets `crossOrigin` on chunk-loading scripts,
|
|
708
|
+
// and matches Vite, which emits it on every injected script/stylesheet.
|
|
709
|
+
const crossOrigin =
|
|
710
|
+
crossOriginLoading && !dep.hasOwnCrossOrigin
|
|
711
|
+
? ` crossorigin="${crossOriginLoading}"`
|
|
712
|
+
: "";
|
|
713
|
+
|
|
714
|
+
// Per-chunk SRI: emit an `integrity` sentinel resolved late (after the
|
|
715
|
+
// chunks' final bytes exist) by `resolveChunkIntegritySentinels`. On when
|
|
716
|
+
// `output.html.integrity` is `true`, a non-empty array, or a function.
|
|
717
|
+
const htmlOption = compilation.outputOptions.html;
|
|
718
|
+
const integrity =
|
|
719
|
+
typeof htmlOption === "object" ? htmlOption.integrity : undefined;
|
|
720
|
+
const integrityOn =
|
|
721
|
+
integrity === true ||
|
|
722
|
+
typeof integrity === "function" ||
|
|
723
|
+
(Array.isArray(integrity) && integrity.length > 0);
|
|
724
|
+
const inject =
|
|
725
|
+
typeof htmlOption === "object" ? htmlOption.inject : undefined;
|
|
726
|
+
|
|
727
|
+
/**
|
|
728
|
+
* Mirror `crossorigin`/`integrity` onto a native entry tag, inserted right
|
|
729
|
+
* after the tag name so they sit alongside any `type="module"` the parser
|
|
730
|
+
* injected. The author's content-specific `integrity` is dropped first (as
|
|
731
|
+
* clones do) so the tag never carries two.
|
|
732
|
+
* @param {Chunk} chunk chunk whose SRI hash the tag references
|
|
733
|
+
* @param {"javascript" | "css"} contentHashType which chunk asset to hash
|
|
734
|
+
* @returns {void}
|
|
735
|
+
*/
|
|
736
|
+
const applyEntryTagAttrs = (chunk, contentHashType) => {
|
|
737
|
+
if (!dep.tagIsNative || dep.tagNameEnd < 0) return;
|
|
738
|
+
if (crossOrigin) source.insert(dep.tagNameEnd, crossOrigin);
|
|
739
|
+
if (integrityOn) {
|
|
740
|
+
if (dep.integrityRange) {
|
|
741
|
+
source.replace(
|
|
742
|
+
dep.tagStart + dep.integrityRange[0],
|
|
743
|
+
dep.tagStart + dep.integrityRange[1] - 1,
|
|
744
|
+
""
|
|
745
|
+
);
|
|
746
|
+
}
|
|
747
|
+
source.insert(
|
|
748
|
+
dep.tagNameEnd,
|
|
749
|
+
` integrity="${HtmlGenerator.makeChunkIntegritySentinel(
|
|
750
|
+
chunk,
|
|
751
|
+
contentHashType
|
|
752
|
+
)}"`
|
|
753
|
+
);
|
|
754
|
+
}
|
|
755
|
+
};
|
|
756
|
+
|
|
757
|
+
// `rel="preload"/"prefetch"` is a resource hint: rewrite the `href` to the
|
|
758
|
+
// built chunk's URL (CSS for `as="style"`, JS otherwise) and stop — no
|
|
759
|
+
// sibling tags and no execution.
|
|
760
|
+
if (dep.elementKind === "preload" || dep.elementKind === "prefetch") {
|
|
761
|
+
const contentHashType =
|
|
762
|
+
dep.category === "css-import" ? "css" : "javascript";
|
|
763
|
+
source.replace(
|
|
764
|
+
dep.range[0],
|
|
765
|
+
dep.range[1] - 1,
|
|
766
|
+
HtmlGenerator.makeChunkUrlSentinel(entryChunk, contentHashType)
|
|
767
|
+
);
|
|
768
|
+
// `preload` is integrity-eligible per the SRI spec, so mirror
|
|
769
|
+
// `crossorigin`/`integrity` onto it (SRI requires CORS). `prefetch` is a
|
|
770
|
+
// navigation cache hint the spec doesn't cover — leave it a bare href.
|
|
771
|
+
if (dep.elementKind === "preload") {
|
|
772
|
+
applyEntryTagAttrs(entryChunk, contentHashType);
|
|
773
|
+
}
|
|
774
|
+
return;
|
|
775
|
+
}
|
|
776
|
+
|
|
468
777
|
const isStylesheet = dep.elementKind === "stylesheet";
|
|
778
|
+
// Inlined scripts are `type="module"` only for ESM output; classic-IIFE
|
|
779
|
+
// chunks (the default) must stay a plain `<script>`.
|
|
780
|
+
const scriptTypeAttr =
|
|
781
|
+
compilation.outputOptions.module === true ? ' type="module"' : "";
|
|
469
782
|
|
|
470
|
-
// Rewrite src/href to a chunk-URL sentinel (resolved by renderManifest):
|
|
471
|
-
// `.css` for `<link rel="stylesheet">`, `.js` for everything else.
|
|
472
783
|
const entryContentHashType = isStylesheet ? "css" : "javascript";
|
|
473
|
-
const
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
);
|
|
477
|
-
|
|
784
|
+
const inlineOption =
|
|
785
|
+
typeof htmlOption === "object" ? htmlOption.inline : undefined;
|
|
786
|
+
/** @type {RegExp[] | null} */
|
|
787
|
+
const inlinePatterns = Array.isArray(inlineOption) ? inlineOption : null;
|
|
788
|
+
/**
|
|
789
|
+
* @param {Chunk} chunk chunk to test
|
|
790
|
+
* @param {"javascript" | "css"} contentType which asset of the chunk is being emitted
|
|
791
|
+
* @returns {boolean} true when this chunk's content should be inlined
|
|
792
|
+
*/
|
|
793
|
+
const isChunkInlined = (chunk, contentType) => {
|
|
794
|
+
// A `<link rel="modulepreload">` is a void fetch hint — inlining it would
|
|
795
|
+
// emit an unclosed, executing `<script>`.
|
|
796
|
+
if (dep.elementKind === "modulepreload") return false;
|
|
797
|
+
if (dep.forceInline) return true;
|
|
798
|
+
if (!inlineOption) return false;
|
|
799
|
+
if (inlineOption === true) return true;
|
|
800
|
+
// `"script"`/`"style"` inline by asset type, not chunk name — a chunk's
|
|
801
|
+
// JS and CSS share a name, so only the emitted `contentType` tells them
|
|
802
|
+
// apart.
|
|
803
|
+
if (inlineOption === "script") return contentType === "javascript";
|
|
804
|
+
if (inlineOption === "style") return contentType === "css";
|
|
805
|
+
const name = chunk.name || String(chunk.id);
|
|
806
|
+
return /** @type {RegExp[]} */ (inlinePatterns).some((re) =>
|
|
807
|
+
re.test(name)
|
|
808
|
+
);
|
|
809
|
+
};
|
|
810
|
+
|
|
811
|
+
// Only inline when the HTML module is emitted as a standalone page (extract mode).
|
|
812
|
+
// If it's exported as a JS string, sentinels would end up embedded in the JS chunk.
|
|
813
|
+
const isExtractedHtml = chunkGraph
|
|
814
|
+
.getModuleSourceTypes(
|
|
815
|
+
/** @type {import("../Module")} */ (templateContext.module)
|
|
816
|
+
)
|
|
817
|
+
.has("html");
|
|
818
|
+
const entryInlined =
|
|
819
|
+
isExtractedHtml &&
|
|
820
|
+
dep.tagStart >= 0 &&
|
|
821
|
+
dep.tagOpenEnd > dep.tagStart &&
|
|
822
|
+
isChunkInlined(entryChunk, entryContentHashType);
|
|
823
|
+
|
|
824
|
+
if (entryInlined) {
|
|
825
|
+
const inlineSentinel = HtmlGenerator.makeChunkInlineSentinel(
|
|
826
|
+
entryChunk,
|
|
827
|
+
entryContentHashType
|
|
828
|
+
);
|
|
829
|
+
if (isStylesheet) {
|
|
830
|
+
source.replace(
|
|
831
|
+
dep.tagStart,
|
|
832
|
+
dep.tagOpenEnd - 1,
|
|
833
|
+
`<style>${inlineSentinel}</style>`
|
|
834
|
+
);
|
|
835
|
+
} else {
|
|
836
|
+
source.replace(
|
|
837
|
+
dep.tagStart,
|
|
838
|
+
dep.tagOpenEnd - 1,
|
|
839
|
+
`<script${scriptTypeAttr}>${inlineSentinel}`
|
|
840
|
+
);
|
|
841
|
+
}
|
|
842
|
+
} else {
|
|
843
|
+
source.replace(
|
|
844
|
+
dep.range[0],
|
|
845
|
+
dep.range[1] - 1,
|
|
846
|
+
HtmlGenerator.makeChunkUrlSentinel(entryChunk, entryContentHashType)
|
|
847
|
+
);
|
|
848
|
+
}
|
|
478
849
|
|
|
479
850
|
if (dep.tagStart < 0 || dep.tagOpenEnd <= dep.tagStart) {
|
|
480
851
|
return;
|
|
481
852
|
}
|
|
482
853
|
|
|
483
|
-
// The browser must load every chunk the entry needs, not just the
|
|
484
|
-
// entry chunk. For `<script>` entries that's the JS for sibling
|
|
485
|
-
// chunks plus — critically — the CSS for any chunk that holds
|
|
486
|
-
// stylesheets imported transitively from the JS source. Previously
|
|
487
|
-
// every sibling was cloned as a `<script>` pointing at a `.js`
|
|
488
|
-
// filename, so CSS chunks ended up as `<script src="foo.css">`
|
|
489
|
-
// pointing at non-existent `.js` files (the bug in
|
|
490
|
-
// html-webpack-plugin#1838 / webpack/mini-css-extract-plugin#959,
|
|
491
|
-
// magnified here because the entry chunk's own CSS was emitted to
|
|
492
|
-
// disk but never linked from the HTML at all).
|
|
493
854
|
const originalContent = /** @type {string} */ (source.original().source());
|
|
494
855
|
const originalTag = originalContent.slice(dep.tagStart, dep.tagOpenEnd);
|
|
495
856
|
const srcStartInTag = dep.range[0] - dep.tagStart;
|
|
496
857
|
const srcEndInTag = dep.range[1] - dep.tagStart;
|
|
497
|
-
// Non-native originating elements (a custom element mapped to a
|
|
498
|
-
// `script`/`stylesheet` source `type`) can't be cloned verbatim — the
|
|
499
|
-
// clone would be invalid markup (e.g. `<my-script …></script>`), so a
|
|
500
|
-
// real native tag is synthesized instead. Decided at parse time from the
|
|
501
|
-
// tag name (`dep.tagIsNative`) rather than re-parsing the source text.
|
|
502
858
|
const tagIsNative = dep.tagIsNative;
|
|
503
|
-
|
|
504
|
-
// `crossorigin` to mirror `output.crossOriginLoading` onto every injected
|
|
505
|
-
// tag. Empty when the option is off or the originating tag already set
|
|
506
|
-
// `crossorigin` (author value wins, flagged at parse time). Mirrors
|
|
507
|
-
// webpack's runtime, which sets `crossOrigin` on chunk-loading scripts,
|
|
508
|
-
// and matches Vite, which emits it on every injected script/stylesheet.
|
|
509
|
-
const crossOrigin =
|
|
510
|
-
crossOriginLoading && !dep.hasOwnCrossOrigin
|
|
511
|
-
? ` crossorigin="${crossOriginLoading}"`
|
|
512
|
-
: "";
|
|
513
859
|
const tagNameEndInTag = dep.tagNameEnd - dep.tagStart;
|
|
514
860
|
|
|
515
|
-
// Mirror
|
|
516
|
-
//
|
|
517
|
-
|
|
518
|
-
if (tagIsNative && crossOrigin && dep.tagNameEnd >= 0) {
|
|
519
|
-
source.insert(dep.tagNameEnd, crossOrigin);
|
|
520
|
-
}
|
|
861
|
+
// Mirror `crossorigin`/`integrity` onto the non-inlined entry tag
|
|
862
|
+
// (siblings get them via the builders below).
|
|
863
|
+
if (!entryInlined) applyEntryTagAttrs(entryChunk, entryContentHashType);
|
|
521
864
|
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
865
|
+
// `inject: false` suppresses sibling-chunk tags only; resource hints
|
|
866
|
+
// below still apply (the way to reference the suppressed chunks).
|
|
867
|
+
if (inject !== false) {
|
|
868
|
+
/**
|
|
869
|
+
* @param {Chunk} chunk chunk to emit a sibling tag for
|
|
870
|
+
* @param {"javascript" | "css"} kind content type slice of the chunk to emit
|
|
871
|
+
* @returns {string} a single sibling tag's HTML
|
|
872
|
+
*/
|
|
873
|
+
const buildSibling = (chunk, kind) => {
|
|
874
|
+
if (isChunkInlined(chunk, kind)) {
|
|
875
|
+
const sentinel = HtmlGenerator.makeChunkInlineSentinel(chunk, kind);
|
|
876
|
+
return kind === "css"
|
|
877
|
+
? `<style>${sentinel}</style>`
|
|
878
|
+
: `<script${scriptTypeAttr}>${sentinel}</script>`;
|
|
879
|
+
}
|
|
880
|
+
const url = HtmlGenerator.makeChunkUrlSentinel(chunk, kind);
|
|
881
|
+
const integrityAttr = integrityOn
|
|
882
|
+
? ` integrity="${HtmlGenerator.makeChunkIntegritySentinel(chunk, kind)}"`
|
|
883
|
+
: "";
|
|
884
|
+
if (kind === "css") {
|
|
885
|
+
// A CSS chunk is always loaded via `<link rel="stylesheet">`.
|
|
886
|
+
// Clone only when the entry tag is itself a native `<link
|
|
887
|
+
// rel="stylesheet">` (so attributes like `media` carry over);
|
|
888
|
+
// a `<script>` entry that imported CSS, or a custom element,
|
|
889
|
+
// gets a freshly synthesized `<link>`.
|
|
890
|
+
if (isStylesheet && tagIsNative) {
|
|
891
|
+
return cloneTagWithUrl(
|
|
892
|
+
originalTag,
|
|
893
|
+
srcStartInTag,
|
|
894
|
+
srcEndInTag,
|
|
895
|
+
url,
|
|
896
|
+
dep.elementKind,
|
|
897
|
+
crossOrigin,
|
|
898
|
+
tagNameEndInTag,
|
|
899
|
+
dep.integrityRange,
|
|
900
|
+
dep.typeValueRange,
|
|
901
|
+
integrityAttr
|
|
902
|
+
);
|
|
903
|
+
}
|
|
904
|
+
return buildStylesheetLink(
|
|
905
|
+
dep.copyableAttrsText,
|
|
906
|
+
url,
|
|
907
|
+
crossOrigin,
|
|
908
|
+
integrityAttr
|
|
909
|
+
);
|
|
910
|
+
}
|
|
911
|
+
// A JS chunk is loaded via `<script>`. Clone a native `<script>`
|
|
912
|
+
// verbatim; synthesize a real `<script>` for custom elements that
|
|
913
|
+
// were mapped to `type: script`/`script-module`.
|
|
914
|
+
if (tagIsNative) {
|
|
536
915
|
return cloneTagWithUrl(
|
|
537
916
|
originalTag,
|
|
538
917
|
srcStartInTag,
|
|
@@ -542,122 +921,368 @@ HtmlEntryDependency.Template = class HtmlEntryDependencyTemplate extends (
|
|
|
542
921
|
crossOrigin,
|
|
543
922
|
tagNameEndInTag,
|
|
544
923
|
dep.integrityRange,
|
|
545
|
-
dep.typeValueRange
|
|
924
|
+
dep.typeValueRange,
|
|
925
|
+
integrityAttr
|
|
546
926
|
);
|
|
547
927
|
}
|
|
548
|
-
return
|
|
549
|
-
|
|
550
|
-
// A JS chunk is loaded via `<script>`. Clone a native `<script>`
|
|
551
|
-
// verbatim; synthesize a real `<script>` for custom elements that
|
|
552
|
-
// were mapped to `type: script`/`script-module`.
|
|
553
|
-
if (tagIsNative) {
|
|
554
|
-
return cloneTagWithUrl(
|
|
555
|
-
originalTag,
|
|
556
|
-
srcStartInTag,
|
|
557
|
-
srcEndInTag,
|
|
928
|
+
return buildScriptTag(
|
|
929
|
+
dep.copyableAttrsText,
|
|
558
930
|
url,
|
|
559
|
-
dep.elementKind,
|
|
931
|
+
dep.elementKind === "script-module",
|
|
560
932
|
crossOrigin,
|
|
561
|
-
|
|
562
|
-
dep.integrityRange,
|
|
563
|
-
dep.typeValueRange
|
|
933
|
+
integrityAttr
|
|
564
934
|
);
|
|
565
|
-
}
|
|
566
|
-
return buildScriptTag(
|
|
567
|
-
dep.copyableAttrsText,
|
|
568
|
-
url,
|
|
569
|
-
dep.elementKind === "script-module",
|
|
570
|
-
crossOrigin
|
|
571
|
-
);
|
|
572
|
-
};
|
|
935
|
+
};
|
|
573
936
|
|
|
574
|
-
|
|
937
|
+
const siblings = [];
|
|
938
|
+
// Parser-derived anchor (a source text scan would match `</head>` inside
|
|
939
|
+
// comments or raw text).
|
|
940
|
+
const headClose = inject === "head" ? dep.headAnchor : -1;
|
|
941
|
+
// When the entry tag is already inside <head> (tagStart < headClose),
|
|
942
|
+
// inserting siblings at headClose would place them after the entry,
|
|
943
|
+
// reversing runtime-before-entry execution order for deferred scripts.
|
|
944
|
+
// Insert before the entry tag instead so the load order is preserved.
|
|
945
|
+
const headAt =
|
|
946
|
+
headClose < 0
|
|
947
|
+
? -1
|
|
948
|
+
: dep.tagStart < headClose
|
|
949
|
+
? dep.tagStart
|
|
950
|
+
: headClose;
|
|
575
951
|
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
siblings
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
952
|
+
let cssHtml = "";
|
|
953
|
+
let cssAt = -1;
|
|
954
|
+
if (isStylesheet) {
|
|
955
|
+
// `<link rel="stylesheet">` entries are CSS, but a shared/runtime
|
|
956
|
+
// JS-only chunk (e.g. `optimization.runtimeChunk`) can still sit in
|
|
957
|
+
// the entrypoint — only clone the `<link>` for siblings that actually
|
|
958
|
+
// produce a `.css` asset (mirrors the JS branch below), otherwise the
|
|
959
|
+
// link points at a non-existent `.css` file (a 404, or an empty
|
|
960
|
+
// `<style>` when inlined). Attributes like `media` still carry over.
|
|
961
|
+
for (let i = 0; i < orderedChunks.length - 1; i++) {
|
|
962
|
+
if (chunkHasCss(orderedChunks[i], chunkGraph)) {
|
|
963
|
+
siblings.push(buildSibling(orderedChunks[i], "css"));
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
} else {
|
|
967
|
+
// CSS chunks are emitted before JS chunks so the cascade is set
|
|
968
|
+
// up before any script runs. Within CSS the order needs to match
|
|
969
|
+
// the source's import order — `entrypoint.chunks` alone doesn't
|
|
970
|
+
// give us that for arbitrary splitChunks layouts (splitChunks
|
|
971
|
+
// inserts each new chunk before the entry chunk via
|
|
972
|
+
// `insertChunk(_, before)`, so split CSS siblings end up in
|
|
973
|
+
// *reverse* of the order they were processed — exactly the
|
|
974
|
+
// html-webpack-plugin#1838 / mini-css-extract#959 symptom). We
|
|
975
|
+
// re-derive the order from the entrypoint's module post-order
|
|
976
|
+
// index, which mirrors the dependency walk and so reflects the
|
|
977
|
+
// import order.
|
|
978
|
+
/** @type {{ chunk: Chunk, index: number, key: string }[]} */
|
|
979
|
+
const cssChunkOrder = [];
|
|
980
|
+
/** @type {Chunk[]} */
|
|
981
|
+
const jsChunks = [];
|
|
982
|
+
for (let i = 0; i < orderedChunks.length - 1; i++) {
|
|
983
|
+
const chunk = orderedChunks[i];
|
|
984
|
+
const hasCss = chunkHasCss(chunk, chunkGraph);
|
|
985
|
+
const hasJs = chunkHasJs(chunk, chunkGraph);
|
|
986
|
+
if (hasCss) {
|
|
987
|
+
cssChunkOrder.push({
|
|
988
|
+
chunk,
|
|
989
|
+
index: firstCssModulePostOrderIndex(
|
|
990
|
+
chunk,
|
|
991
|
+
entrypoint,
|
|
992
|
+
chunkGraph
|
|
993
|
+
),
|
|
994
|
+
key: cssChunkSortKey(chunk)
|
|
995
|
+
});
|
|
996
|
+
}
|
|
997
|
+
// Anything that isn't CSS-only stays on the JS lane, in the
|
|
998
|
+
// `orderedChunks` order — that preserves the runtime-first /
|
|
999
|
+
// vendor-before-entry invariant of `getEntrypointChunksInLoadOrder`.
|
|
1000
|
+
// Chunks that produce no `.js` and no `.css` (e.g. wasm-only
|
|
1001
|
+
// or asset-only) still get a `<script>` clone here so we
|
|
1002
|
+
// keep prior behavior for users who relied on it.
|
|
1003
|
+
if (hasJs || !hasCss) jsChunks.push(chunk);
|
|
1004
|
+
}
|
|
1005
|
+
// If the entry chunk itself contains CSS (entry JS imports CSS
|
|
1006
|
+
// without splitChunks separating it), fold it into the same CSS
|
|
1007
|
+
// ordering so the entry-chunk `<link>` lands in the correct
|
|
1008
|
+
// cascade position relative to sibling CSS chunks.
|
|
1009
|
+
if (chunkHasCss(entryChunk, chunkGraph)) {
|
|
604
1010
|
cssChunkOrder.push({
|
|
605
|
-
chunk,
|
|
606
|
-
index: firstCssModulePostOrderIndex(
|
|
607
|
-
|
|
1011
|
+
chunk: entryChunk,
|
|
1012
|
+
index: firstCssModulePostOrderIndex(
|
|
1013
|
+
entryChunk,
|
|
1014
|
+
entrypoint,
|
|
1015
|
+
chunkGraph
|
|
1016
|
+
),
|
|
1017
|
+
key: cssChunkSortKey(entryChunk)
|
|
608
1018
|
});
|
|
609
1019
|
}
|
|
610
|
-
|
|
611
|
-
//
|
|
612
|
-
//
|
|
613
|
-
//
|
|
614
|
-
//
|
|
615
|
-
//
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
}
|
|
1020
|
+
cssChunkOrder.sort(compareCssChunkOrder);
|
|
1021
|
+
// Stylesheets always land in <head> when the page has one. A
|
|
1022
|
+
// classic blocking script pins them ahead of itself (`cssAnchor`)
|
|
1023
|
+
// so its execution never awaits later CSS; when every script is
|
|
1024
|
+
// `defer`/module they go to the *end* of head, after the script
|
|
1025
|
+
// tags — Vite's and html-webpack-plugin's order: the scripts
|
|
1026
|
+
// start fetching earlier yet still execute after parsing and
|
|
1027
|
+
// after pending stylesheets.
|
|
1028
|
+
cssAt =
|
|
1029
|
+
dep.headAnchor >= 0
|
|
1030
|
+
? dep.cssAnchor >= 0 && dep.cssAnchor < dep.headAnchor
|
|
1031
|
+
? dep.cssAnchor
|
|
1032
|
+
: dep.headAnchor
|
|
1033
|
+
: dep.cssAnchor >= 0
|
|
1034
|
+
? dep.cssAnchor
|
|
1035
|
+
: dep.tagStart;
|
|
1036
|
+
cssHtml = cssChunkOrder
|
|
1037
|
+
.map(({ chunk }) => buildSibling(chunk, "css"))
|
|
1038
|
+
.join("");
|
|
1039
|
+
for (const chunk of jsChunks) {
|
|
1040
|
+
siblings.push(buildSibling(chunk, "javascript"));
|
|
1041
|
+
}
|
|
632
1042
|
}
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
// tie-breaker must always run when the indices match,
|
|
640
|
-
// including the `Infinity === Infinity` case.
|
|
641
|
-
if (a.index < b.index) return -1;
|
|
642
|
-
if (a.index > b.index) return 1;
|
|
643
|
-
if (a.key < b.key) return -1;
|
|
644
|
-
if (a.key > b.key) return 1;
|
|
645
|
-
return 0;
|
|
646
|
-
});
|
|
647
|
-
// Insert CSS before the first script (not just before this tag) so a
|
|
648
|
-
// later entry's stylesheet still loads ahead of every script.
|
|
649
|
-
const cssAt = dep.cssAnchor === -1 ? dep.tagStart : dep.cssAnchor;
|
|
650
|
-
const cssLinks = cssChunkOrder.map(({ chunk }) =>
|
|
651
|
-
buildSibling(chunk, "css")
|
|
652
|
-
);
|
|
653
|
-
if (cssLinks.length > 0) source.insert(cssAt, cssLinks.join(""));
|
|
654
|
-
for (const chunk of jsChunks) {
|
|
655
|
-
siblings.push(buildSibling(chunk, "javascript"));
|
|
1043
|
+
|
|
1044
|
+
// Same-offset inserts keep call order: CSS goes in before the
|
|
1045
|
+
// sibling scripts only when a blocking script demands CSS-first.
|
|
1046
|
+
if (cssHtml !== "" && dep.cssAnchor >= 0) source.insert(cssAt, cssHtml);
|
|
1047
|
+
if (siblings.length > 0) {
|
|
1048
|
+
source.insert(headAt >= 0 ? headAt : dep.tagStart, siblings.join(""));
|
|
656
1049
|
}
|
|
1050
|
+
if (cssHtml !== "" && dep.cssAnchor < 0) source.insert(cssAt, cssHtml);
|
|
657
1051
|
}
|
|
658
1052
|
|
|
659
|
-
|
|
660
|
-
|
|
1053
|
+
// Resource hints are opt-in — webpack already loads the initial chunks
|
|
1054
|
+
// via parallel `<script>` tags, so there is no waterfall to prevent by
|
|
1055
|
+
// default. `output.resourceHints` requests them: `true` preloads the
|
|
1056
|
+
// entry's initial dependency chunks (runtime, vendor, split); an array
|
|
1057
|
+
// or function adds custom hints (a function may spread the auto
|
|
1058
|
+
// `defaultHints`). Only for extracted pages — a JS-string HTML export
|
|
1059
|
+
// would embed the sentinels in the JS chunk. URL-referenced assets
|
|
1060
|
+
// (fonts, images) carrying `webpackPrefetch` / `webpackPreload`
|
|
1061
|
+
// (magic-comment or `parser.<type>.urlHints` rule) also emit here.
|
|
1062
|
+
const resourceHintResolver =
|
|
1063
|
+
ResourceHintPlugin.getCompilationResolver(compilation);
|
|
1064
|
+
const resourceHints = resourceHintResolver.hints;
|
|
1065
|
+
if (
|
|
1066
|
+
isExtractedHtml &&
|
|
1067
|
+
(dep.elementKind === "script" || dep.elementKind === "script-module")
|
|
1068
|
+
) {
|
|
1069
|
+
const isModuleOutput = compilation.outputOptions.module === true;
|
|
1070
|
+
|
|
1071
|
+
/** @type {string[]} */
|
|
1072
|
+
const hints = [];
|
|
1073
|
+
|
|
1074
|
+
// === preconnect channel — cross-origin publicPath (resourceHints.preconnect) ===
|
|
1075
|
+
const rhOptions = compilation.outputOptions.resourceHints;
|
|
1076
|
+
if (rhOptions && rhOptions.preconnect) {
|
|
1077
|
+
const pp = compilation.outputOptions.publicPath;
|
|
1078
|
+
const originMatch =
|
|
1079
|
+
typeof pp === "string" ? /^(https?:)?\/\/[^/?#]+/i.exec(pp) : null;
|
|
1080
|
+
if (originMatch) {
|
|
1081
|
+
hints.push(
|
|
1082
|
+
`<link rel="preconnect" href="${escapeAttr(originMatch[0])}"${crossOrigin}>`
|
|
1083
|
+
);
|
|
1084
|
+
}
|
|
1085
|
+
}
|
|
1086
|
+
|
|
1087
|
+
// === chunks channel — the entry's own initial dependency graph ===
|
|
1088
|
+
if (resourceHints !== undefined && resourceHints !== false) {
|
|
1089
|
+
// The auto initial-graph preload as descriptors carrying their exact
|
|
1090
|
+
// tag, built only when needed (`true`/`"prefetch"` emits them, a
|
|
1091
|
+
// function may spread them). The entry chunk is skipped — it's
|
|
1092
|
+
// already the `<script src>`. ESM uses `modulepreload` (fetch +
|
|
1093
|
+
// parse), classic output `preload as="script"`, and `"prefetch"`
|
|
1094
|
+
// opts into `<link rel="prefetch">` (idle-time fetch, no `as` / no
|
|
1095
|
+
// `integrity` — the spec doesn't list them). Async `import()` chunks
|
|
1096
|
+
// aren't covered here; use `module.parser.javascript.dynamicImport*`
|
|
1097
|
+
// which routes through webpack's on-demand chunk-load runtime.
|
|
1098
|
+
const prefetchMode = resourceHints === "prefetch";
|
|
1099
|
+
const preloadMode = resourceHints === true;
|
|
1100
|
+
/** @type {"modulepreload" | "preload" | "prefetch"} */
|
|
1101
|
+
const rel = prefetchMode
|
|
1102
|
+
? "prefetch"
|
|
1103
|
+
: isModuleOutput
|
|
1104
|
+
? "modulepreload"
|
|
1105
|
+
: "preload";
|
|
1106
|
+
/** @type {{ rel: "modulepreload" | "preload" | "prefetch", chunk: string, hostChunks: string[], _tag: string }[]} */
|
|
1107
|
+
const defaultHints = [];
|
|
1108
|
+
if (
|
|
1109
|
+
preloadMode ||
|
|
1110
|
+
prefetchMode ||
|
|
1111
|
+
typeof resourceHints === "function"
|
|
1112
|
+
) {
|
|
1113
|
+
for (const chunk of orderedChunks) {
|
|
1114
|
+
if (
|
|
1115
|
+
chunk === entryChunk ||
|
|
1116
|
+
isChunkInlined(chunk, "javascript") ||
|
|
1117
|
+
!chunkHasJs(chunk, chunkGraph)
|
|
1118
|
+
) {
|
|
1119
|
+
continue;
|
|
1120
|
+
}
|
|
1121
|
+
const href = HtmlGenerator.makeChunkUrlSentinel(
|
|
1122
|
+
chunk,
|
|
1123
|
+
"javascript"
|
|
1124
|
+
);
|
|
1125
|
+
const integrityAttr =
|
|
1126
|
+
integrityOn && !prefetchMode
|
|
1127
|
+
? ` integrity="${HtmlGenerator.makeChunkIntegritySentinel(
|
|
1128
|
+
chunk,
|
|
1129
|
+
"javascript"
|
|
1130
|
+
)}"`
|
|
1131
|
+
: "";
|
|
1132
|
+
// Mirror the entry tag's `nonce`/`crossorigin`/`referrerpolicy`
|
|
1133
|
+
// (and `output.crossOriginLoading`) so the request matches the
|
|
1134
|
+
// sibling `<script>`'s and the browser reuses it instead of
|
|
1135
|
+
// fetching twice.
|
|
1136
|
+
const attrs = `${dep.copyableAttrsText}${crossOrigin}${integrityAttr}`;
|
|
1137
|
+
const tag = prefetchMode
|
|
1138
|
+
? `<link rel="prefetch" href="${href}"${attrs}>`
|
|
1139
|
+
: isModuleOutput
|
|
1140
|
+
? `<link rel="modulepreload" href="${href}"${attrs}>`
|
|
1141
|
+
: `<link rel="preload" as="script" href="${href}"${attrs}>`;
|
|
1142
|
+
const chunkName = chunk.name || String(chunk.id);
|
|
1143
|
+
defaultHints.push({
|
|
1144
|
+
rel,
|
|
1145
|
+
chunk: chunkName,
|
|
1146
|
+
hostChunks: [chunkName],
|
|
1147
|
+
_tag: tag
|
|
1148
|
+
});
|
|
1149
|
+
}
|
|
1150
|
+
}
|
|
1151
|
+
|
|
1152
|
+
if (preloadMode || prefetchMode) {
|
|
1153
|
+
for (const d of defaultHints) {
|
|
1154
|
+
hints.push(d._tag);
|
|
1155
|
+
}
|
|
1156
|
+
} else {
|
|
1157
|
+
// Array (custom only) or function (returns the final list, may spread
|
|
1158
|
+
// `defaultHints`). A returned default hint keeps its prebuilt tag; any
|
|
1159
|
+
// other descriptor is resolved through the URL/SRI pipeline.
|
|
1160
|
+
let descriptors = resourceHints;
|
|
1161
|
+
if (typeof resourceHints === "function") {
|
|
1162
|
+
// The page's own entry name (its named entry chunk), not the synthetic
|
|
1163
|
+
// `<script>` entry — that's what users key on.
|
|
1164
|
+
let entryName = dep.entryName;
|
|
1165
|
+
for (const c of chunkGraph.getModuleChunks(
|
|
1166
|
+
/** @type {import("../Module")} */ (templateContext.module)
|
|
1167
|
+
)) {
|
|
1168
|
+
if (c.name) {
|
|
1169
|
+
entryName = c.name;
|
|
1170
|
+
break;
|
|
1171
|
+
}
|
|
1172
|
+
}
|
|
1173
|
+
descriptors = resourceHints({
|
|
1174
|
+
entryName,
|
|
1175
|
+
entrypoint,
|
|
1176
|
+
hostType: "html",
|
|
1177
|
+
compilation,
|
|
1178
|
+
defaultHints
|
|
1179
|
+
});
|
|
1180
|
+
}
|
|
1181
|
+
if (Array.isArray(descriptors) && descriptors.length > 0) {
|
|
1182
|
+
/** @type {Map<string, Chunk>} */
|
|
1183
|
+
const chunkByName = new Map();
|
|
1184
|
+
for (const c of compilation.chunks) {
|
|
1185
|
+
if (c.name) chunkByName.set(c.name, c);
|
|
1186
|
+
}
|
|
1187
|
+
const outputCrossOrigin = crossOriginLoading
|
|
1188
|
+
? ` crossorigin="${crossOriginLoading}"`
|
|
1189
|
+
: "";
|
|
1190
|
+
const ctx = {
|
|
1191
|
+
compilation,
|
|
1192
|
+
chunkGraph,
|
|
1193
|
+
chunkByName,
|
|
1194
|
+
outputCrossOrigin,
|
|
1195
|
+
integrityOn
|
|
1196
|
+
};
|
|
1197
|
+
for (const desc of descriptors) {
|
|
1198
|
+
if (!desc || typeof desc !== "object") continue;
|
|
1199
|
+
const prebuilt = /** @type {{ _tag?: string }} */ (desc)._tag;
|
|
1200
|
+
if (prebuilt) {
|
|
1201
|
+
hints.push(prebuilt);
|
|
1202
|
+
} else if (desc.rel) {
|
|
1203
|
+
for (const tag of resolveResourceHint(desc, ctx)) {
|
|
1204
|
+
hints.push(tag);
|
|
1205
|
+
}
|
|
1206
|
+
}
|
|
1207
|
+
}
|
|
1208
|
+
}
|
|
1209
|
+
}
|
|
1210
|
+
}
|
|
1211
|
+
|
|
1212
|
+
// === assets channel — URL-referenced assets carrying `prefetch`/`preload` ===
|
|
1213
|
+
// The plugin has already scanned the entrypoint's chunks × modules
|
|
1214
|
+
// once and cached the URL/CSS/HTML asset deps for this entry name;
|
|
1215
|
+
// consume that list here so this template doesn't repeat the walk.
|
|
1216
|
+
// Each unique asset emits one `<link rel="prefetch|preload">` into
|
|
1217
|
+
// the HTML `<head>`; the JS chunk-startup runtime skips it (no
|
|
1218
|
+
// double `<link>` in DOM). URLs are sentinels resolved from asset
|
|
1219
|
+
// codegen data during `HtmlModulesPlugin` render — the asset
|
|
1220
|
+
// module might not have been codegen'd yet at this point.
|
|
1221
|
+
const outputCrossOriginAttr = crossOriginLoading
|
|
1222
|
+
? ` crossorigin="${crossOriginLoading}"`
|
|
1223
|
+
: "";
|
|
1224
|
+
for (const { module, dep: md } of resourceHintResolver.getHtmlHinted(
|
|
1225
|
+
dep.entryName
|
|
1226
|
+
)) {
|
|
1227
|
+
const rel = md.preload ? "preload" : "prefetch";
|
|
1228
|
+
const asAttr =
|
|
1229
|
+
md.asAttribute ||
|
|
1230
|
+
ResourceHintRuntimeModule.guessAsAttribute(
|
|
1231
|
+
/** @type {import("./URLDependency")} */ (md).request || ""
|
|
1232
|
+
);
|
|
1233
|
+
const typeAttr = md.typeAttribute;
|
|
1234
|
+
const mediaAttr = md.mediaAttribute;
|
|
1235
|
+
const fetchPriority = md.fetchPriority;
|
|
1236
|
+
const href = HtmlGenerator.makeAssetUrlSentinel(module.identifier());
|
|
1237
|
+
const attrs =
|
|
1238
|
+
`${asAttr ? ` as="${escapeAttr(asAttr)}"` : ""}` +
|
|
1239
|
+
`${typeAttr ? ` type="${escapeAttr(typeAttr)}"` : ""}` +
|
|
1240
|
+
` href="${escapeAttr(href)}"${
|
|
1241
|
+
outputCrossOriginAttr
|
|
1242
|
+
}${mediaAttr ? ` media="${escapeAttr(mediaAttr)}"` : ""}` +
|
|
1243
|
+
`${fetchPriority ? ` fetchpriority="${escapeAttr(fetchPriority)}"` : ""}`;
|
|
1244
|
+
hints.push(`<link rel="${rel}"${attrs}>`);
|
|
1245
|
+
}
|
|
1246
|
+
|
|
1247
|
+
if (hints.length > 0) {
|
|
1248
|
+
// De-duplicate (a custom hint may repeat an auto one), preserving order.
|
|
1249
|
+
/** @type {Set<string>} */
|
|
1250
|
+
const seen = new Set();
|
|
1251
|
+
/** @type {string[]} */
|
|
1252
|
+
const finalHints = [];
|
|
1253
|
+
for (const h of hints) {
|
|
1254
|
+
if (!seen.has(h)) {
|
|
1255
|
+
seen.add(h);
|
|
1256
|
+
finalHints.push(h);
|
|
1257
|
+
}
|
|
1258
|
+
}
|
|
1259
|
+
// When the target environment lacks native `modulepreload` (an
|
|
1260
|
+
// `output.environment` capability, like the JS-feature checks), append
|
|
1261
|
+
// a tiny polyfill once per page so the hints still warm the cache there.
|
|
1262
|
+
// `resourceHints.modulePreloadPolyfill` defaults from the target's
|
|
1263
|
+
// modulepreload support (`output.environment.modulePreload`); `false`
|
|
1264
|
+
// opts out (strict CSP).
|
|
1265
|
+
if (
|
|
1266
|
+
rhOptions &&
|
|
1267
|
+
rhOptions.modulePreloadPolyfill &&
|
|
1268
|
+
!polyfilledSources.has(source) &&
|
|
1269
|
+
finalHints.some((h) => h.startsWith('<link rel="modulepreload"'))
|
|
1270
|
+
) {
|
|
1271
|
+
polyfilledSources.add(source);
|
|
1272
|
+
finalHints.push(`<script>${MODULEPRELOAD_POLYFILL}</script>`);
|
|
1273
|
+
}
|
|
1274
|
+
// Insert right after the `<head>` open tag (parser-derived) so the
|
|
1275
|
+
// hints are the document's first fetches; fall back to the first
|
|
1276
|
+
// blocking script (else the entry tag) when the page has no
|
|
1277
|
+
// written `<head>` (e.g. an HTML fragment).
|
|
1278
|
+
const hintAt =
|
|
1279
|
+
dep.headOpenEnd >= 0
|
|
1280
|
+
? dep.headOpenEnd
|
|
1281
|
+
: dep.cssAnchor === -1
|
|
1282
|
+
? dep.tagStart
|
|
1283
|
+
: dep.cssAnchor;
|
|
1284
|
+
source.insert(hintAt, finalHints.join(""));
|
|
1285
|
+
}
|
|
661
1286
|
}
|
|
662
1287
|
}
|
|
663
1288
|
};
|