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
package/lib/html/HtmlParser.js
CHANGED
|
@@ -5,15 +5,6 @@
|
|
|
5
5
|
"use strict";
|
|
6
6
|
|
|
7
7
|
const Parser = require("../Parser");
|
|
8
|
-
const {
|
|
9
|
-
TT_EOF,
|
|
10
|
-
TT_FUNCTION,
|
|
11
|
-
TT_STRING,
|
|
12
|
-
TT_URL,
|
|
13
|
-
TT_WHITESPACE,
|
|
14
|
-
TokenStream,
|
|
15
|
-
equalsLowerCase
|
|
16
|
-
} = require("../css/syntax");
|
|
17
8
|
const ConstDependency = require("../dependencies/ConstDependency");
|
|
18
9
|
const HtmlEntryDependency = require("../dependencies/HtmlEntryDependency");
|
|
19
10
|
const HtmlInlineHtmlDependency = require("../dependencies/HtmlInlineHtmlDependency");
|
|
@@ -25,6 +16,8 @@ const CommentCompilationWarning = require("../errors/CommentCompilationWarning")
|
|
|
25
16
|
const ModuleDependencyError = require("../errors/ModuleDependencyError");
|
|
26
17
|
const UnsupportedFeatureWarning = require("../errors/UnsupportedFeatureWarning");
|
|
27
18
|
const WebpackError = require("../errors/WebpackError");
|
|
19
|
+
const ResourceHintPlugin = require("../prefetch/ResourceHintPlugin");
|
|
20
|
+
const parseResourceHintOptions = require("../prefetch/parseResourceHintOptions");
|
|
28
21
|
const LazySet = require("../util/LazySet");
|
|
29
22
|
const LocConverter = require("../util/LocConverter");
|
|
30
23
|
const createHash = require("../util/createHash");
|
|
@@ -35,14 +28,18 @@ const {
|
|
|
35
28
|
webpackCommentRegExp
|
|
36
29
|
} = require("../util/magicComment");
|
|
37
30
|
const {
|
|
31
|
+
NS_HTML,
|
|
38
32
|
NS_SVG,
|
|
39
33
|
NodeType,
|
|
40
34
|
SVG_TAG_ADJUST,
|
|
41
35
|
SourceProcessor,
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
36
|
+
decodeEntities,
|
|
37
|
+
escapeAttribute,
|
|
38
|
+
escapeText,
|
|
39
|
+
isAsciiWhitespace,
|
|
40
|
+
parseCssUrls,
|
|
41
|
+
parseMsapplicationTask,
|
|
42
|
+
parseSrc,
|
|
46
43
|
parseSrcset
|
|
47
44
|
} = require("./syntax");
|
|
48
45
|
|
|
@@ -70,31 +67,6 @@ const {
|
|
|
70
67
|
*/
|
|
71
68
|
/** @typedef {(source: string, context: HtmlTemplateContext) => string} HtmlTemplateFunction */
|
|
72
69
|
|
|
73
|
-
const HORIZONTAL_TAB = "\u0009".charCodeAt(0);
|
|
74
|
-
const NEWLINE = "\u000A".charCodeAt(0);
|
|
75
|
-
const FORM_FEED = "\u000C".charCodeAt(0);
|
|
76
|
-
const CARRIAGE_RETURN = "\u000D".charCodeAt(0);
|
|
77
|
-
const SPACE = "\u0020".charCodeAt(0);
|
|
78
|
-
|
|
79
|
-
/**
|
|
80
|
-
* @param {number} char char
|
|
81
|
-
* @returns {boolean} true when ASCII whitespace, otherwise false
|
|
82
|
-
*/
|
|
83
|
-
function isASCIIWhitespace(char) {
|
|
84
|
-
return (
|
|
85
|
-
// Horizontal tab
|
|
86
|
-
char === HORIZONTAL_TAB ||
|
|
87
|
-
// New line
|
|
88
|
-
char === NEWLINE ||
|
|
89
|
-
// Form feed
|
|
90
|
-
char === FORM_FEED ||
|
|
91
|
-
// Carriage return
|
|
92
|
-
char === CARRIAGE_RETURN ||
|
|
93
|
-
// Space
|
|
94
|
-
char === SPACE
|
|
95
|
-
);
|
|
96
|
-
}
|
|
97
|
-
|
|
98
70
|
/** @typedef {import("./syntax").ParsedSource} ParsedSource */
|
|
99
71
|
|
|
100
72
|
// Cheap pre-filter for a `style="..."` attribute: only route it through the
|
|
@@ -113,6 +85,67 @@ const FUNC_IRI_URL_REGEXP = /url\(/i;
|
|
|
113
85
|
// formatting/text markup (`<p>hi</p>`) rewrites to itself, so skip it.
|
|
114
86
|
const SRCDOC_ASSET_REGEXP = /[=]|url\(|@import/i;
|
|
115
87
|
|
|
88
|
+
// A URL carrying its own scheme (`https:`, `data:`, …) ignores the document
|
|
89
|
+
// base per the URL spec, so `<base href>` never rewrites it.
|
|
90
|
+
const ABSOLUTE_URL_SCHEME_REGEXP = /^[a-zA-Z][a-zA-Z\d+\-.]*:/;
|
|
91
|
+
|
|
92
|
+
// a `charset=` declaration inside an `http-equiv="content-type"` content value
|
|
93
|
+
const CHARSET_DECLARATION_REGEXP = /charset\s*=/i;
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* @param {string} name meta name
|
|
97
|
+
* @param {string} content meta content
|
|
98
|
+
* @returns {string} meta tag
|
|
99
|
+
*/
|
|
100
|
+
const metaTag = (name, content) => {
|
|
101
|
+
// og: uses property=; all others (including twitter:) use name=
|
|
102
|
+
const attr = name.startsWith("og:")
|
|
103
|
+
? `property="${escapeAttribute(name)}"`
|
|
104
|
+
: `name="${escapeAttribute(name)}"`;
|
|
105
|
+
return `<meta ${attr} content="${escapeAttribute(content)}">`;
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* @param {string | { href: string, target?: string }} base base option
|
|
110
|
+
* @returns {string} base tag
|
|
111
|
+
*/
|
|
112
|
+
const baseTag = (base) => {
|
|
113
|
+
const href = typeof base === "string" ? base : base.href;
|
|
114
|
+
const targetAttr =
|
|
115
|
+
typeof base === "object" && base.target
|
|
116
|
+
? ` target="${escapeAttribute(base.target)}"`
|
|
117
|
+
: "";
|
|
118
|
+
return `<base href="${escapeAttribute(href)}"${targetAttr}>`;
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Serializes every head tag an `output.html` options object asks for, in
|
|
123
|
+
* spec order: charset, base, meta tags, title. Used where a page is created
|
|
124
|
+
* from scratch (HtmlModulesPlugin's synthetic entry wrapper); authored pages
|
|
125
|
+
* instead merge the options against their existing tags during `parse` —
|
|
126
|
+
* existing tags win.
|
|
127
|
+
* @param {import("../../declarations/WebpackOptions").OutputHtmlOptions} opts html options
|
|
128
|
+
* @returns {string} head tags string
|
|
129
|
+
*/
|
|
130
|
+
const buildHeadTags = (opts) => {
|
|
131
|
+
let out = "";
|
|
132
|
+
const meta = opts.meta;
|
|
133
|
+
if (meta && meta.charset) {
|
|
134
|
+
out += `<meta charset="${escapeAttribute(meta.charset)}">`;
|
|
135
|
+
}
|
|
136
|
+
if (opts.base) out += baseTag(opts.base);
|
|
137
|
+
if (meta) {
|
|
138
|
+
for (const [name, content] of Object.entries(meta)) {
|
|
139
|
+
if (name === "charset") continue;
|
|
140
|
+
out += metaTag(name, content);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
if (opts.title) {
|
|
144
|
+
out += `<title>${escapeText(opts.title)}</title>`;
|
|
145
|
+
}
|
|
146
|
+
return out;
|
|
147
|
+
};
|
|
148
|
+
|
|
116
149
|
// CSP/fetch attributes copied verbatim onto a synthesized sibling `<link>` /
|
|
117
150
|
// `<script>` (`HtmlEntryDependency`). Fixed output order, independent of
|
|
118
151
|
// source order.
|
|
@@ -120,144 +153,58 @@ const COPYABLE_SIBLING_ATTRS = ["nonce", "crossorigin", "referrerpolicy"];
|
|
|
120
153
|
|
|
121
154
|
const CC_QUOTATION = '"'.charCodeAt(0);
|
|
122
155
|
const CC_APOSTROPHE = "'".charCodeAt(0);
|
|
156
|
+
const CC_SLASH = "/".charCodeAt(0);
|
|
123
157
|
|
|
124
158
|
/**
|
|
125
159
|
* Byte-exact source span of an attribute including the single leading
|
|
126
160
|
* whitespace (` name`, ` name=value`, ` name="value"`), mirroring the
|
|
127
161
|
* tokenizer's end-of-attribute rule (`valueEnd + 1` past the closing quote).
|
|
162
|
+
* @param {import("./syntax").HtmlPath} path walk path (used only for attribute-ref reads)
|
|
128
163
|
* @param {string} source HTML source
|
|
129
|
-
* @param {import("./syntax").
|
|
164
|
+
* @param {import("./syntax").HtmlAttributeRef} attr attribute ref
|
|
130
165
|
* @returns {string} the attribute's source slice
|
|
131
166
|
*/
|
|
132
|
-
const attrSourceSpan = (source, attr) => {
|
|
167
|
+
const attrSourceSpan = (path, source, attr) => {
|
|
168
|
+
const valueStart = path.attributeValueStart(attr);
|
|
169
|
+
const valueEnd = path.attributeValueEnd(attr);
|
|
133
170
|
const end =
|
|
134
|
-
|
|
135
|
-
? attr
|
|
136
|
-
: source.charCodeAt(
|
|
137
|
-
source.charCodeAt(
|
|
138
|
-
?
|
|
139
|
-
:
|
|
140
|
-
return source.slice(attr
|
|
171
|
+
valueStart === -1
|
|
172
|
+
? path.attributeNameEnd(attr)
|
|
173
|
+
: source.charCodeAt(valueStart - 1) === CC_QUOTATION ||
|
|
174
|
+
source.charCodeAt(valueStart - 1) === CC_APOSTROPHE
|
|
175
|
+
? valueEnd + 1
|
|
176
|
+
: valueEnd;
|
|
177
|
+
return source.slice(path.attributeNameStart(attr) - 1, end);
|
|
141
178
|
};
|
|
142
179
|
|
|
143
|
-
// eslint-disable-next-line no-control-regex
|
|
144
|
-
const IGNORE_CHARS_REGEXP = /[\u0000-\u001F\u007F-\u009F\u00A0]/g;
|
|
145
|
-
|
|
146
180
|
/**
|
|
147
|
-
*
|
|
148
|
-
*
|
|
181
|
+
* Source span of a tag's `integrity` attribute (offsets relative to
|
|
182
|
+
* `elementStart`, leading whitespace and quotes included), so the template can
|
|
183
|
+
* drop the content-specific author value without re-parsing. Null when absent.
|
|
184
|
+
* @param {import("./syntax").HtmlPath} path walk path (for attribute reads)
|
|
185
|
+
* @param {string} source HTML source
|
|
186
|
+
* @param {number} elementStart offset of the tag's opening `<`
|
|
187
|
+
* @returns {Range | null} the span, or null when there is no `integrity`
|
|
149
188
|
*/
|
|
150
|
-
const
|
|
151
|
-
const
|
|
152
|
-
if (
|
|
153
|
-
|
|
154
|
-
let start = 0;
|
|
155
|
-
let end = len;
|
|
156
|
-
|
|
157
|
-
while (start < end) {
|
|
158
|
-
const code = input.charCodeAt(start);
|
|
159
|
-
if (code > 32 && code !== 160) break;
|
|
160
|
-
start++;
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
if (start === end) throw new Error("Must be non-empty");
|
|
164
|
-
|
|
165
|
-
while (end > start) {
|
|
166
|
-
const code = input.charCodeAt(end - 1);
|
|
167
|
-
if (code > 32 && code !== 160) break;
|
|
168
|
-
end--;
|
|
189
|
+
const captureIntegrityRange = (path, source, elementStart) => {
|
|
190
|
+
const integrityAttr = path.findAttribute("integrity");
|
|
191
|
+
if (integrityAttr === 0 || path.attributeNameStart(integrityAttr) < 0) {
|
|
192
|
+
return null;
|
|
169
193
|
}
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
if (IGNORE_CHARS_REGEXP.test(value)) {
|
|
174
|
-
value = value.replace(IGNORE_CHARS_REGEXP, "");
|
|
175
|
-
if (value.length === 0) throw new Error("Must be non-empty");
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
return [[value, start, end]];
|
|
179
|
-
};
|
|
180
|
-
|
|
181
|
-
/**
|
|
182
|
-
* Extracts the `icon-uri` value of an `msapplication-task` meta content
|
|
183
|
-
* (`name=…;action-uri=…;icon-uri=…`) — the other parts are page URLs, not assets.
|
|
184
|
-
* @param {string} input input
|
|
185
|
-
* @returns {ParsedSource[]} parsed icon-uri
|
|
186
|
-
*/
|
|
187
|
-
const parseMsapplicationTask = (input) => {
|
|
188
|
-
const len = input.length;
|
|
189
|
-
let pos = 0;
|
|
190
|
-
while (pos < len) {
|
|
191
|
-
let sep = input.indexOf(";", pos);
|
|
192
|
-
if (sep === -1) sep = len;
|
|
193
|
-
const eq = input.indexOf("=", pos);
|
|
194
|
-
if (eq !== -1 && eq < sep) {
|
|
195
|
-
const key = input.slice(pos, eq).trim().toLowerCase();
|
|
196
|
-
if (key === "icon-uri") {
|
|
197
|
-
let start = eq + 1;
|
|
198
|
-
let end = sep;
|
|
199
|
-
while (start < end && isASCIIWhitespace(input.charCodeAt(start))) {
|
|
200
|
-
start++;
|
|
201
|
-
}
|
|
202
|
-
while (end > start && isASCIIWhitespace(input.charCodeAt(end - 1))) {
|
|
203
|
-
end--;
|
|
204
|
-
}
|
|
205
|
-
if (start === end) return [];
|
|
206
|
-
return [[input.slice(start, end), start, end]];
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
pos = sep + 1;
|
|
194
|
+
let rs = path.attributeNameStart(integrityAttr);
|
|
195
|
+
while (rs > 0 && isAsciiWhitespace(source.charCodeAt(rs - 1))) {
|
|
196
|
+
rs--;
|
|
210
197
|
}
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
* `url("path")` rewrites the inner string span (quotes preserved).
|
|
220
|
-
* @param {string} input attribute value (a CSS component-value list)
|
|
221
|
-
* @returns {ParsedSource[]} url references
|
|
222
|
-
*/
|
|
223
|
-
const parseCssUrls = (input) => {
|
|
224
|
-
const ts = new TokenStream(input);
|
|
225
|
-
/** @type {ParsedSource[]} */
|
|
226
|
-
const result = [];
|
|
227
|
-
for (;;) {
|
|
228
|
-
const t = ts.consume();
|
|
229
|
-
if (t.type === TT_EOF) break;
|
|
230
|
-
if (t.type === TT_URL) {
|
|
231
|
-
if (t.contentEnd > t.contentStart) {
|
|
232
|
-
result.push([
|
|
233
|
-
input.slice(t.contentStart, t.contentEnd),
|
|
234
|
-
t.contentStart,
|
|
235
|
-
t.contentEnd
|
|
236
|
-
]);
|
|
237
|
-
}
|
|
238
|
-
} else if (
|
|
239
|
-
t.type === TT_FUNCTION &&
|
|
240
|
-
equalsLowerCase(input.slice(t.start, t.end - 1), "url")
|
|
241
|
-
) {
|
|
242
|
-
let s = ts.consume();
|
|
243
|
-
while (s.type === TT_WHITESPACE) s = ts.consume();
|
|
244
|
-
if (s.type === TT_STRING) {
|
|
245
|
-
const quote = input.charCodeAt(s.start);
|
|
246
|
-
const innerStart = s.start + 1;
|
|
247
|
-
// Drop the closing quote, unless the string is unterminated at EOF.
|
|
248
|
-
const innerEnd =
|
|
249
|
-
input.charCodeAt(s.end - 1) === quote ? s.end - 1 : s.end;
|
|
250
|
-
if (innerEnd > innerStart) {
|
|
251
|
-
result.push([
|
|
252
|
-
input.slice(innerStart, innerEnd),
|
|
253
|
-
innerStart,
|
|
254
|
-
innerEnd
|
|
255
|
-
]);
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
}
|
|
198
|
+
let re;
|
|
199
|
+
const ivs = path.attributeValueStart(integrityAttr);
|
|
200
|
+
if (ivs === -1) {
|
|
201
|
+
re = path.attributeNameEnd(integrityAttr);
|
|
202
|
+
} else {
|
|
203
|
+
const q = source.charCodeAt(ivs - 1);
|
|
204
|
+
const ive = path.attributeValueEnd(integrityAttr);
|
|
205
|
+
re = q === CC_QUOTATION || q === CC_APOSTROPHE ? ive + 1 : ive;
|
|
259
206
|
}
|
|
260
|
-
return
|
|
207
|
+
return [rs - elementStart, re - elementStart];
|
|
261
208
|
};
|
|
262
209
|
|
|
263
210
|
/**
|
|
@@ -283,6 +230,8 @@ const META = new Map([
|
|
|
283
230
|
"msapplication-task",
|
|
284
231
|
"twitter:image",
|
|
285
232
|
"twitter:image:src",
|
|
233
|
+
// Twitter player card: URL to the raw video/media stream
|
|
234
|
+
"twitter:player:stream",
|
|
286
235
|
// Legacy preview-image hint
|
|
287
236
|
"thumbnail"
|
|
288
237
|
])
|
|
@@ -338,9 +287,7 @@ const ALLOWED_LINK_RELS = new Set([
|
|
|
338
287
|
"apple-touch-icon",
|
|
339
288
|
"apple-touch-icon-precomposed",
|
|
340
289
|
"apple-touch-startup-image",
|
|
341
|
-
//
|
|
342
|
-
// `icons`/`screenshots`/`shortcuts[].icons` URLs aren't parsed — needs a
|
|
343
|
-
// dedicated webmanifest module type (parser + generator), not a table entry.
|
|
290
|
+
// Routed to the `asset/webmanifest` module type, which parses its icon URLs.
|
|
344
291
|
"manifest",
|
|
345
292
|
"prefetch",
|
|
346
293
|
"preload",
|
|
@@ -451,14 +398,17 @@ const isLinkStylesheet = (attributes) => {
|
|
|
451
398
|
return rel.trim().toLowerCase().split(/\s+/).includes("stylesheet");
|
|
452
399
|
};
|
|
453
400
|
|
|
454
|
-
/** @typedef {"src" | "srcset" | "css-url" | "msapplication-task" | "script" | "script-module" | "modulepreload" | "stylesheet" | "stylesheet-style" | "stylesheet-style-attribute" | "srcdoc"} SourceType */
|
|
401
|
+
/** @typedef {"src" | "srcset" | "css-url" | "msapplication-task" | "script" | "script-module" | "modulepreload" | "stylesheet" | "html" | "preload" | "prefetch" | "stylesheet-style" | "stylesheet-style-attribute" | "srcdoc"} SourceType */
|
|
455
402
|
/** Entry types: a `type` whose value is loaded as its own compilation entry chunk. */
|
|
456
|
-
/** @typedef {"script" | "script-module" | "modulepreload" | "stylesheet"} EntrySourceType */
|
|
403
|
+
/** @typedef {"script" | "script-module" | "modulepreload" | "stylesheet" | "html" | "preload" | "prefetch"} EntrySourceType */
|
|
457
404
|
/** @typedef {SourceType | ((attrs: Map<string, string>, css: boolean) => SourceType)} SourceTypeOrResolver */
|
|
458
405
|
/** @typedef {(attributes: Map<string, string>, value: string) => boolean} SourceFilter */
|
|
459
406
|
/** @typedef {{ tag?: string, attribute: string, type: SourceType, filter?: SourceFilter }} SourceEntry */
|
|
460
407
|
/** A `type` plus optional gates: `filter` (the decoded attribute map + the decoded value; return false to skip — covers both cross-attribute checks and cheap value checks) and `namespace` (restrict to an element namespace, e.g. SVG). */
|
|
461
408
|
/** @typedef {{ type: SourceTypeOrResolver, filter?: SourceFilter, namespace?: number }} SourceItem */
|
|
409
|
+
/** A tag→attribute lookup; a `undefined` value marks a built-in source disabled via `sources` (`type: false`). */
|
|
410
|
+
/** @typedef {Record<string, SourceItem | undefined>} SourceBucket */
|
|
411
|
+
/** @typedef {Record<string, SourceBucket>} SourceTable */
|
|
462
412
|
/** A source whose value is the element's text content (a `<style>`/`<script>` body) rather than an attribute. */
|
|
463
413
|
/** @typedef {{ type: SourceTypeOrResolver, filter?: (attributes: Map<string, string>) => boolean }} ContentSourceItem */
|
|
464
414
|
|
|
@@ -494,6 +444,24 @@ const LINK_HREF = {
|
|
|
494
444
|
type: (attrs, css) => {
|
|
495
445
|
if (isLinkModulePreload(attrs)) return "modulepreload";
|
|
496
446
|
if (css && isLinkStylesheet(attrs)) return "stylesheet";
|
|
447
|
+
// `rel="preload"/"prefetch"` with `as="script"` (or `as="style"` when
|
|
448
|
+
// `experiments.css` is on) becomes a bundled, non-executing entry whose
|
|
449
|
+
// `href` is rewritten to the built chunk; other `as` targets (image,
|
|
450
|
+
// font, fetch, …) stay plain asset URLs.
|
|
451
|
+
const rel = getAttributeValue(attrs, "rel");
|
|
452
|
+
if (rel) {
|
|
453
|
+
const rels = rel.trim().toLowerCase().split(/\s+/);
|
|
454
|
+
const kind = rels.includes("preload")
|
|
455
|
+
? "preload"
|
|
456
|
+
: rels.includes("prefetch")
|
|
457
|
+
? "prefetch"
|
|
458
|
+
: undefined;
|
|
459
|
+
if (kind) {
|
|
460
|
+
const as = getAttributeValue(attrs, "as");
|
|
461
|
+
const asLower = as ? as.trim().toLowerCase() : "";
|
|
462
|
+
if (asLower === "script" || (css && asLower === "style")) return kind;
|
|
463
|
+
}
|
|
464
|
+
}
|
|
497
465
|
return "src";
|
|
498
466
|
},
|
|
499
467
|
filter: filterLinkUnion
|
|
@@ -581,15 +549,22 @@ const DEFAULT_SOURCES_BY_TAG = dict({
|
|
|
581
549
|
// `xlink:href` reference another element/resource; fragment-only `#id`
|
|
582
550
|
// values are left untouched, so only external `file.svg#id` is rewritten.
|
|
583
551
|
// `color-profile`'s `xlink:href` points at an external ICC profile file.
|
|
552
|
+
// `cursor`/`font-face-uri` point at an external image/font file; `altGlyph`/
|
|
553
|
+
// `glyphRef`/`tref` are legacy element references (removed in SVG 2).
|
|
554
|
+
altGlyph: dict({ "xlink:href": PLAIN_SRC, href: PLAIN_SRC }),
|
|
584
555
|
"color-profile": dict({ "xlink:href": PLAIN_SRC, href: PLAIN_SRC }),
|
|
556
|
+
cursor: dict({ "xlink:href": PLAIN_SRC, href: PLAIN_SRC }),
|
|
585
557
|
feImage: dict({ "xlink:href": PLAIN_SRC, href: PLAIN_SRC }),
|
|
586
558
|
filter: dict({ "xlink:href": PLAIN_SRC, href: PLAIN_SRC }),
|
|
559
|
+
"font-face-uri": dict({ "xlink:href": PLAIN_SRC, href: PLAIN_SRC }),
|
|
560
|
+
glyphRef: dict({ "xlink:href": PLAIN_SRC, href: PLAIN_SRC }),
|
|
587
561
|
image: dict({ "xlink:href": PLAIN_SRC, href: PLAIN_SRC }),
|
|
588
562
|
linearGradient: dict({ "xlink:href": PLAIN_SRC, href: PLAIN_SRC }),
|
|
589
563
|
mpath: dict({ "xlink:href": PLAIN_SRC, href: PLAIN_SRC }),
|
|
590
564
|
pattern: dict({ "xlink:href": PLAIN_SRC, href: PLAIN_SRC }),
|
|
591
565
|
radialGradient: dict({ "xlink:href": PLAIN_SRC, href: PLAIN_SRC }),
|
|
592
566
|
textPath: dict({ "xlink:href": PLAIN_SRC, href: PLAIN_SRC }),
|
|
567
|
+
tref: dict({ "xlink:href": PLAIN_SRC, href: PLAIN_SRC }),
|
|
593
568
|
use: dict({ "xlink:href": PLAIN_SRC, href: PLAIN_SRC })
|
|
594
569
|
// CSS `url(...)` references in SVG presentation attributes (fill, stroke,
|
|
595
570
|
// clip-path, …) are handled separately during the walk (`parseCssUrls`).
|
|
@@ -650,9 +625,9 @@ const ANY_TAG = "";
|
|
|
650
625
|
* Folds the any-tag sources into every per-tag bucket (tag-specific entries
|
|
651
626
|
* win) and stores them under `ANY_TAG`, so the whole source model is one
|
|
652
627
|
* object and the walk's per-attribute lookup is a single property read.
|
|
653
|
-
* @param {
|
|
654
|
-
* @param {
|
|
655
|
-
* @returns {
|
|
628
|
+
* @param {SourceTable} byTag per-tag sources
|
|
629
|
+
* @param {SourceBucket} any any-tag sources
|
|
630
|
+
* @returns {SourceTable} folded table
|
|
656
631
|
*/
|
|
657
632
|
const foldAnySources = (byTag, any) => {
|
|
658
633
|
for (const tag of Object.keys(byTag)) {
|
|
@@ -665,12 +640,12 @@ const foldAnySources = (byTag, any) => {
|
|
|
665
640
|
// Default table (per-tag defaults + any-tag sources folded in) and the
|
|
666
641
|
// `sources: false` table (only the always-on `style=""` attribute). Both are
|
|
667
642
|
// precomputed so the common cases reference them with zero per-parser work.
|
|
668
|
-
/** @type {
|
|
643
|
+
/** @type {SourceTable} */
|
|
669
644
|
const DEFAULT_SOURCES_FOLDED = foldAnySources(
|
|
670
645
|
dict(DEFAULT_SOURCES_BY_TAG),
|
|
671
646
|
DEFAULT_ANY_SOURCES
|
|
672
647
|
);
|
|
673
|
-
/** @type {
|
|
648
|
+
/** @type {SourceTable} */
|
|
674
649
|
const DISABLED_SOURCES_FOLDED = foldAnySources(dict(), ALWAYS_ANY_SOURCES);
|
|
675
650
|
|
|
676
651
|
/**
|
|
@@ -727,9 +702,21 @@ class HtmlParser extends Parser {
|
|
|
727
702
|
// the module source before it is stored and parsed.
|
|
728
703
|
/** @type {HtmlTemplateFunction | undefined} */
|
|
729
704
|
this.template = options.template;
|
|
705
|
+
/** @type {import("../../declarations/WebpackOptions").UrlHintRule[] | undefined} */
|
|
706
|
+
this._urlHints = options.urlHints;
|
|
707
|
+
// `as` selects the parse mode — the HTML analog of the CSS parser's `as`.
|
|
708
|
+
// `"document"` (default) parses a full page; any other value is the
|
|
709
|
+
// context element whose inner HTML the source is parsed as (a fragment),
|
|
710
|
+
// so context-sensitive tags (`<tr>`, `<td>`, `<option>`, …) in a partial
|
|
711
|
+
// are kept, not dropped. `"template"` is the neutral generic fragment.
|
|
712
|
+
/** @type {string | undefined} */
|
|
713
|
+
this.fragmentContext =
|
|
714
|
+
options.as === undefined || options.as === "document"
|
|
715
|
+
? undefined
|
|
716
|
+
: options.as;
|
|
730
717
|
// One source model: a per-tag table that also holds the any-tag sources
|
|
731
718
|
// under `ANY_TAG`. The common cases reference precomputed tables.
|
|
732
|
-
/** @type {
|
|
719
|
+
/** @type {SourceTable} */
|
|
733
720
|
this.sourcesByTag = DEFAULT_SOURCES_FOLDED;
|
|
734
721
|
|
|
735
722
|
const sources = options.sources;
|
|
@@ -748,19 +735,25 @@ class HtmlParser extends Parser {
|
|
|
748
735
|
// User entries override regardless of position. `dict()` keeps every
|
|
749
736
|
// table null-prototype (see its doc), and per-tag writes rebuild the
|
|
750
737
|
// bucket so the aliased default buckets stay intact.
|
|
751
|
-
/** @type {
|
|
738
|
+
/** @type {SourceTable} */
|
|
752
739
|
const byTag = sources.includes("...")
|
|
753
740
|
? dict(DEFAULT_SOURCES_BY_TAG)
|
|
754
741
|
: dict();
|
|
755
|
-
/** @type {
|
|
742
|
+
/** @type {SourceBucket} */
|
|
756
743
|
const any = dict(DEFAULT_ANY_SOURCES);
|
|
757
744
|
for (const entry of sources) {
|
|
758
745
|
if (entry === "...") continue;
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
746
|
+
// `type: false` disables a built-in source: writing `undefined` over the
|
|
747
|
+
// attribute makes the walk's `sources[name]` lookup skip it (see below).
|
|
748
|
+
/** @type {SourceItem | undefined} */
|
|
749
|
+
const item =
|
|
750
|
+
entry.type === false
|
|
751
|
+
? undefined
|
|
752
|
+
: {
|
|
753
|
+
type: entry.type,
|
|
754
|
+
filter:
|
|
755
|
+
typeof entry.filter === "function" ? entry.filter : undefined
|
|
756
|
+
};
|
|
764
757
|
const attr = entry.attribute.toLowerCase();
|
|
765
758
|
if (entry.tag === undefined) {
|
|
766
759
|
any[attr] = item;
|
|
@@ -775,7 +768,7 @@ class HtmlParser extends Parser {
|
|
|
775
768
|
}
|
|
776
769
|
}
|
|
777
770
|
}
|
|
778
|
-
/** @type {
|
|
771
|
+
/** @type {SourceTable} */
|
|
779
772
|
this.sourcesByTag = foldAnySources(byTag, any);
|
|
780
773
|
}
|
|
781
774
|
|
|
@@ -864,6 +857,7 @@ class HtmlParser extends Parser {
|
|
|
864
857
|
const { hashFunction, module: outputModule } = compilation.outputOptions;
|
|
865
858
|
const context = compilation.compiler.context;
|
|
866
859
|
const css = Boolean(compilation.options.experiments.css);
|
|
860
|
+
const urlHints = this._urlHints;
|
|
867
861
|
|
|
868
862
|
// Stable, per-HTML-module prefix used when generating entry names for
|
|
869
863
|
// script src / modulepreload references so they don't collide across
|
|
@@ -881,6 +875,43 @@ class HtmlParser extends Parser {
|
|
|
881
875
|
.digest("hex")
|
|
882
876
|
.slice(0, 8);
|
|
883
877
|
|
|
878
|
+
// `output.html` head injection: facts are collected during the walk and
|
|
879
|
+
// become presentational dependencies afterwards — no later asset pass has
|
|
880
|
+
// to re-parse the page. `data:` documents are skipped: synthetic entry
|
|
881
|
+
// wrappers already carry the tags and iframe srcdoc pages must stay
|
|
882
|
+
// untouched.
|
|
883
|
+
const htmlOutputOptions =
|
|
884
|
+
compilation.options && compilation.options.output
|
|
885
|
+
? compilation.options.output.html
|
|
886
|
+
: undefined;
|
|
887
|
+
const headOptions =
|
|
888
|
+
typeof htmlOutputOptions === "object" &&
|
|
889
|
+
(htmlOutputOptions.title !== undefined ||
|
|
890
|
+
htmlOutputOptions.meta !== undefined ||
|
|
891
|
+
htmlOutputOptions.base !== undefined) &&
|
|
892
|
+
!resource.startsWith("data:")
|
|
893
|
+
? htmlOutputOptions
|
|
894
|
+
: undefined;
|
|
895
|
+
let doctypeEnd = -1;
|
|
896
|
+
let htmlTagEnd = -1;
|
|
897
|
+
// insertion offsets just inside the (possibly implicit) head
|
|
898
|
+
let headStart = 0;
|
|
899
|
+
let headLastEnd = 0;
|
|
900
|
+
let headSeen = false;
|
|
901
|
+
let inHead = false;
|
|
902
|
+
// `<template>` contents are inert
|
|
903
|
+
let headTemplateDepth = 0;
|
|
904
|
+
// Depth of open `<template>` elements anywhere in the tree. Template
|
|
905
|
+
// content is an inert, separate fragment per spec, so a `<base>` inside
|
|
906
|
+
// it must NOT set the document base (`headTemplateDepth` only covers
|
|
907
|
+
// templates seen while in `<head>`, so it can't guard body templates).
|
|
908
|
+
let templateDepth = 0;
|
|
909
|
+
let headHasTitle = false;
|
|
910
|
+
let headHasBase = false;
|
|
911
|
+
let headCharsetEnd = -1;
|
|
912
|
+
/** @type {Set<string> | undefined} */
|
|
913
|
+
let headMetaNames;
|
|
914
|
+
|
|
884
915
|
// Script src / modulepreload references are collected per-type
|
|
885
916
|
// during the walk; HtmlModulesPlugin later turns them into real
|
|
886
917
|
// entries. `script` and `script-module` entries are chained via a
|
|
@@ -889,26 +920,57 @@ class HtmlParser extends Parser {
|
|
|
889
920
|
// must preload without running, so they can never become a runtime
|
|
890
921
|
// leader that other entries would import.
|
|
891
922
|
/**
|
|
892
|
-
* @typedef {object}
|
|
923
|
+
* @typedef {object} HtmlEntryInfo
|
|
893
924
|
* @property {string} request
|
|
894
925
|
* @property {string} entryName
|
|
895
|
-
* @property {"script" | "script-module" | "modulepreload" | "stylesheet"} type
|
|
926
|
+
* @property {"script" | "script-module" | "modulepreload" | "stylesheet" | "html" | "preload" | "prefetch"} type
|
|
927
|
+
* @property {boolean=} css entry emits a CSS chunk (`<link rel="preload" as="style">`), so it needs the CSS filename template
|
|
896
928
|
*/
|
|
897
|
-
/** @type {
|
|
929
|
+
/** @type {HtmlEntryInfo[]} */
|
|
898
930
|
const scriptEntries = [];
|
|
899
|
-
/** @type {
|
|
931
|
+
/** @type {HtmlEntryInfo[]} */
|
|
900
932
|
const scriptModuleEntries = [];
|
|
901
|
-
/** @type {
|
|
933
|
+
/** @type {HtmlEntryInfo[]} */
|
|
902
934
|
const modulePreloadEntries = [];
|
|
903
|
-
/** @type {
|
|
935
|
+
/** @type {HtmlEntryInfo[]} */
|
|
904
936
|
const stylesheetEntries = [];
|
|
905
|
-
|
|
906
|
-
//
|
|
907
|
-
|
|
908
|
-
|
|
937
|
+
// `type: "html"` links — each referenced `.html` becomes its own page
|
|
938
|
+
// entry (`<a href="page.html">`), independent (no dependOn).
|
|
939
|
+
/** @type {HtmlEntryInfo[]} */
|
|
940
|
+
const htmlLinkEntries = [];
|
|
941
|
+
// `<link rel="preload"/"prefetch">` of a bundled resource: each becomes an
|
|
942
|
+
// independent, non-executing entry whose `href` is rewritten to the built
|
|
943
|
+
// chunk's URL (JS for `as="script"`, CSS for `as="style"`).
|
|
944
|
+
/** @type {HtmlEntryInfo[]} */
|
|
945
|
+
const preloadEntries = [];
|
|
946
|
+
/** @type {HtmlEntryInfo[]} */
|
|
947
|
+
const prefetchEntries = [];
|
|
948
|
+
|
|
949
|
+
// Offset of the first classic blocking script tag; injected stylesheet
|
|
950
|
+
// `<link>`s stay ahead of it. `defer`/`async`/module scripts don't
|
|
951
|
+
// anchor — they execute after parsing (and after pending stylesheets),
|
|
952
|
+
// so CSS may follow their tags (the order Vite emits).
|
|
953
|
+
let firstBlockingScriptStart = -1;
|
|
909
954
|
|
|
910
955
|
let nextEntryIndex = 0;
|
|
911
956
|
|
|
957
|
+
// `<base href>` resolves the relative URLs that follow it. A relative base
|
|
958
|
+
// (`./assets/`) rewrites them into a subdirectory — still bundled; a
|
|
959
|
+
// root-relative or absolute base (`/`, `https://cdn/`) points them outside
|
|
960
|
+
// the build, so those URLs are left untouched. Resolved in the walk below
|
|
961
|
+
// from the first `<base href>` seen (`documentBase` undefined until then).
|
|
962
|
+
/** @type {string | undefined} */
|
|
963
|
+
let documentBase;
|
|
964
|
+
/** @type {string | undefined} */
|
|
965
|
+
let baseDir;
|
|
966
|
+
let baseIsExternal = false;
|
|
967
|
+
// Prepended to the emitted URLs' auto-public-path undo path so the base
|
|
968
|
+
// doesn't misdirect them: the browser resolves rewritten (relative)
|
|
969
|
+
// output URLs against the base dir, so one `../` per base segment cancels
|
|
970
|
+
// it. Undefined for absolute publicPath (no undo path is emitted).
|
|
971
|
+
/** @type {string | undefined} */
|
|
972
|
+
let baseUrlPrefix;
|
|
973
|
+
|
|
912
974
|
/**
|
|
913
975
|
* Tracks the `webpackIgnore` value from the most recent comment that
|
|
914
976
|
* appears before the next tag. Reset whenever a tag is emitted or a
|
|
@@ -916,48 +978,54 @@ class HtmlParser extends Parser {
|
|
|
916
978
|
* @type {boolean | undefined}
|
|
917
979
|
*/
|
|
918
980
|
let pendingWebpackIgnore;
|
|
981
|
+
/** @type {boolean | undefined} */
|
|
982
|
+
let pendingWebpackInline;
|
|
983
|
+
/**
|
|
984
|
+
* Tracks the resource-hint values from the most recent `<!-- webpackPrefetch: … -->`
|
|
985
|
+
* (etc.) comment so the next asset URL attribute picks them up.
|
|
986
|
+
* @type {import("../prefetch/parseResourceHintOptions").ResourceHintOptions | undefined}
|
|
987
|
+
*/
|
|
988
|
+
let pendingHints;
|
|
919
989
|
|
|
920
990
|
const magicCommentContext = this.magicCommentContext;
|
|
921
991
|
|
|
922
992
|
/**
|
|
923
|
-
* @param {import("./syntax").
|
|
993
|
+
* @param {import("./syntax").HtmlPath} path walk path (used only for attribute-ref reads)
|
|
994
|
+
* @param {import("./syntax").HtmlAttributeRef} typeAttr type attribute ref (0 = none)
|
|
924
995
|
* @param {number} nameEnd end offset of the tag name
|
|
925
996
|
* @param {string} type type of the script
|
|
926
997
|
* @param {string} input source string
|
|
927
998
|
*/
|
|
928
|
-
const reconcileScriptTypeAttr = (typeAttr, nameEnd, type, input) => {
|
|
999
|
+
const reconcileScriptTypeAttr = (path, typeAttr, nameEnd, type, input) => {
|
|
1000
|
+
const valueStart =
|
|
1001
|
+
typeAttr !== 0 ? path.attributeValueStart(typeAttr) : -1;
|
|
1002
|
+
const valueEnd = typeAttr !== 0 ? path.attributeValueEnd(typeAttr) : -1;
|
|
929
1003
|
if (outputModule && type === "script") {
|
|
930
1004
|
// Chunk is an ES module; upgrade the tag.
|
|
931
|
-
if (typeAttr &&
|
|
1005
|
+
if (typeAttr !== 0 && valueStart !== -1) {
|
|
932
1006
|
module.addPresentationalDependency(
|
|
933
|
-
new ConstDependency("module", [
|
|
934
|
-
typeAttr.valueStart,
|
|
935
|
-
typeAttr.valueEnd
|
|
936
|
-
])
|
|
1007
|
+
new ConstDependency("module", [valueStart, valueEnd])
|
|
937
1008
|
);
|
|
938
1009
|
} else {
|
|
939
1010
|
module.addPresentationalDependency(
|
|
940
1011
|
new ConstDependency(' type="module"', nameEnd)
|
|
941
1012
|
);
|
|
942
1013
|
}
|
|
943
|
-
} else if (!outputModule && type === "script-module" && typeAttr) {
|
|
1014
|
+
} else if (!outputModule && type === "script-module" && typeAttr !== 0) {
|
|
944
1015
|
// Chunk is a classic IIFE; drop `type="module"` so the
|
|
945
1016
|
// browser doesn't load it under module semantics.
|
|
946
1017
|
let attrEnd;
|
|
947
|
-
if (
|
|
948
|
-
attrEnd = typeAttr
|
|
949
|
-
} else if (
|
|
950
|
-
|
|
951
|
-
input[typeAttr.valueEnd] === "'"
|
|
952
|
-
) {
|
|
953
|
-
attrEnd = typeAttr.valueEnd + 1;
|
|
1018
|
+
if (valueStart === -1) {
|
|
1019
|
+
attrEnd = path.attributeNameEnd(typeAttr);
|
|
1020
|
+
} else if (input[valueEnd] === '"' || input[valueEnd] === "'") {
|
|
1021
|
+
attrEnd = valueEnd + 1;
|
|
954
1022
|
} else {
|
|
955
|
-
attrEnd =
|
|
1023
|
+
attrEnd = valueEnd;
|
|
956
1024
|
}
|
|
957
|
-
let attrStart = typeAttr
|
|
1025
|
+
let attrStart = path.attributeNameStart(typeAttr);
|
|
958
1026
|
if (
|
|
959
1027
|
attrStart > 0 &&
|
|
960
|
-
|
|
1028
|
+
isAsciiWhitespace(input.charCodeAt(attrStart - 1))
|
|
961
1029
|
) {
|
|
962
1030
|
attrStart -= 1;
|
|
963
1031
|
}
|
|
@@ -986,31 +1054,109 @@ class HtmlParser extends Parser {
|
|
|
986
1054
|
dep.setLoc(s.line, s.column, e.line, e.column);
|
|
987
1055
|
};
|
|
988
1056
|
|
|
1057
|
+
/**
|
|
1058
|
+
* Classifies the first `<base href>` seen in the walk, setting
|
|
1059
|
+
* `documentBase`/`baseDir`/`baseIsExternal`/`baseUrlPrefix` for the URLs
|
|
1060
|
+
* that follow it.
|
|
1061
|
+
* @param {import("./syntax").HtmlPath} path walk path (for attribute reads)
|
|
1062
|
+
* @param {import("./syntax").HtmlAttributeRef} hrefAttr the base's href attribute
|
|
1063
|
+
*/
|
|
1064
|
+
const resolveDocumentBase = (path, hrefAttr) => {
|
|
1065
|
+
documentBase = decodeEntities(path.attributeValue(hrefAttr), true).trim();
|
|
1066
|
+
if (!documentBase) return;
|
|
1067
|
+
if (
|
|
1068
|
+
ABSOLUTE_URL_SCHEME_REGEXP.test(documentBase) ||
|
|
1069
|
+
documentBase.charCodeAt(0) === CC_SLASH
|
|
1070
|
+
) {
|
|
1071
|
+
baseIsExternal = true;
|
|
1072
|
+
return;
|
|
1073
|
+
}
|
|
1074
|
+
// Normalize the base path into its descending directory segments,
|
|
1075
|
+
// resolving `.`/`..` like the URL spec. The final segment is the
|
|
1076
|
+
// referenced file (dropped) unless the href ends with `/` or a
|
|
1077
|
+
// dot-segment; `up` counts `..`s that climb above the document dir.
|
|
1078
|
+
const parts = documentBase.split("/");
|
|
1079
|
+
/** @type {string[]} */
|
|
1080
|
+
const dirs = [];
|
|
1081
|
+
let up = 0;
|
|
1082
|
+
for (let i = 0; i < parts.length; i++) {
|
|
1083
|
+
const part = parts[i];
|
|
1084
|
+
if (part === "" || part === ".") continue;
|
|
1085
|
+
if (part === "..") {
|
|
1086
|
+
if (dirs.length > 0) dirs.pop();
|
|
1087
|
+
else up++;
|
|
1088
|
+
continue;
|
|
1089
|
+
}
|
|
1090
|
+
// A trailing plain segment (no following `/`) is the file, not a dir.
|
|
1091
|
+
if (i === parts.length - 1) continue;
|
|
1092
|
+
dirs.push(part);
|
|
1093
|
+
}
|
|
1094
|
+
baseDir = `${"../".repeat(up)}${dirs.length > 0 ? `${dirs.join("/")}/` : ""}`;
|
|
1095
|
+
// A base above the document dir can't be cancelled with `../` (the
|
|
1096
|
+
// document's own dir name is unknown) — leave the output URLs
|
|
1097
|
+
// un-prefixed; they still resolve under an absolute publicPath.
|
|
1098
|
+
if (up === 0 && dirs.length > 0) {
|
|
1099
|
+
baseUrlPrefix = "../".repeat(dirs.length);
|
|
1100
|
+
}
|
|
1101
|
+
};
|
|
1102
|
+
|
|
1103
|
+
// Lazy decoded-attribute map for the current element — parse-scoped
|
|
1104
|
+
// to avoid a closure and memo slot per element.
|
|
1105
|
+
/** @type {import("./syntax").HtmlPath | undefined} */
|
|
1106
|
+
let attrMapPath;
|
|
1107
|
+
let attrMapCount = 0;
|
|
1108
|
+
/** @type {Map<string, string> | undefined} */
|
|
1109
|
+
let currentAttributesMap;
|
|
1110
|
+
const getAttributesMap = () => {
|
|
1111
|
+
if (currentAttributesMap) return currentAttributesMap;
|
|
1112
|
+
currentAttributesMap = new Map();
|
|
1113
|
+
const path = /** @type {import("./syntax").HtmlPath} */ (attrMapPath);
|
|
1114
|
+
for (let i = 0; i < attrMapCount; i++) {
|
|
1115
|
+
const attr = path.attributeAt(i);
|
|
1116
|
+
// Decoded values — filters and type resolvers compare what
|
|
1117
|
+
// the browser sees (e.g. `rel="icon"` means `icon`)
|
|
1118
|
+
currentAttributesMap.set(
|
|
1119
|
+
path.attributeName(attr),
|
|
1120
|
+
decodeEntities(path.attributeValue(attr), true)
|
|
1121
|
+
);
|
|
1122
|
+
}
|
|
1123
|
+
return currentAttributesMap;
|
|
1124
|
+
};
|
|
1125
|
+
|
|
989
1126
|
// TODO implement full HTML parser (WASM)
|
|
990
1127
|
// The walker descends into children (and `<template>` content) itself;
|
|
991
1128
|
// the Element `exit` clears a pending `webpackIgnore` once an element's
|
|
992
1129
|
// children are done (the old `walkChildren` behaviour).
|
|
993
1130
|
new SourceProcessor()
|
|
994
1131
|
.use({
|
|
995
|
-
[NodeType.Comment]: (
|
|
996
|
-
const
|
|
1132
|
+
[NodeType.Comment]: (path) => {
|
|
1133
|
+
const start = path.start();
|
|
1134
|
+
const end = path.end();
|
|
1135
|
+
// comments count toward the head-end insertion anchor
|
|
1136
|
+
if (inHead && headTemplateDepth === 0 && end > headLastEnd) {
|
|
1137
|
+
headLastEnd = end;
|
|
1138
|
+
}
|
|
997
1139
|
// Only proper `<!-- ... -->` comments carry magic comments.
|
|
998
1140
|
if (
|
|
999
|
-
|
|
1000
|
-
source.charCodeAt(
|
|
1001
|
-
source.charCodeAt(
|
|
1002
|
-
source.charCodeAt(
|
|
1003
|
-
source.charCodeAt(
|
|
1004
|
-
source.charCodeAt(
|
|
1005
|
-
source.charCodeAt(
|
|
1006
|
-
source.charCodeAt(
|
|
1141
|
+
end - start < 7 ||
|
|
1142
|
+
source.charCodeAt(start) !== 0x3c ||
|
|
1143
|
+
source.charCodeAt(start + 1) !== 0x21 ||
|
|
1144
|
+
source.charCodeAt(start + 2) !== 0x2d ||
|
|
1145
|
+
source.charCodeAt(start + 3) !== 0x2d ||
|
|
1146
|
+
source.charCodeAt(end - 1) !== 0x3e ||
|
|
1147
|
+
source.charCodeAt(end - 2) !== 0x2d ||
|
|
1148
|
+
source.charCodeAt(end - 3) !== 0x2d
|
|
1007
1149
|
) {
|
|
1008
1150
|
pendingWebpackIgnore = undefined;
|
|
1151
|
+
pendingWebpackInline = undefined;
|
|
1152
|
+
pendingHints = undefined;
|
|
1009
1153
|
return;
|
|
1010
1154
|
}
|
|
1011
|
-
const value =
|
|
1155
|
+
const value = path.data();
|
|
1012
1156
|
if (!webpackCommentRegExp.test(value)) {
|
|
1013
1157
|
pendingWebpackIgnore = undefined;
|
|
1158
|
+
pendingWebpackInline = undefined;
|
|
1159
|
+
pendingHints = undefined;
|
|
1014
1160
|
return;
|
|
1015
1161
|
}
|
|
1016
1162
|
/** @type {Record<string, EXPECTED_ANY>} */
|
|
@@ -1018,8 +1164,8 @@ class HtmlParser extends Parser {
|
|
|
1018
1164
|
try {
|
|
1019
1165
|
options = parseMagicComment(value, magicCommentContext);
|
|
1020
1166
|
} catch (err) {
|
|
1021
|
-
const { line: sl, column: sc } = locConverter.get(
|
|
1022
|
-
const { line: el, column: ec } = locConverter.get(
|
|
1167
|
+
const { line: sl, column: sc } = locConverter.get(start);
|
|
1168
|
+
const { line: el, column: ec } = locConverter.get(end);
|
|
1023
1169
|
module.addWarning(
|
|
1024
1170
|
new CommentCompilationWarning(
|
|
1025
1171
|
`Compilation error while processing magic comment(-s): /*${value}*/: ${
|
|
@@ -1032,15 +1178,28 @@ class HtmlParser extends Parser {
|
|
|
1032
1178
|
)
|
|
1033
1179
|
);
|
|
1034
1180
|
pendingWebpackIgnore = undefined;
|
|
1181
|
+
pendingWebpackInline = undefined;
|
|
1182
|
+
pendingHints = undefined;
|
|
1035
1183
|
return;
|
|
1036
1184
|
}
|
|
1037
|
-
if (
|
|
1185
|
+
if (
|
|
1186
|
+
options.webpackIgnore === undefined &&
|
|
1187
|
+
options.webpackInline === undefined &&
|
|
1188
|
+
options.webpackPrefetch === undefined &&
|
|
1189
|
+
options.webpackPreload === undefined &&
|
|
1190
|
+
options.webpackFetchPriority === undefined
|
|
1191
|
+
) {
|
|
1038
1192
|
pendingWebpackIgnore = undefined;
|
|
1193
|
+
pendingWebpackInline = undefined;
|
|
1194
|
+
pendingHints = undefined;
|
|
1039
1195
|
return;
|
|
1040
1196
|
}
|
|
1041
|
-
if (
|
|
1042
|
-
|
|
1043
|
-
|
|
1197
|
+
if (
|
|
1198
|
+
options.webpackIgnore !== undefined &&
|
|
1199
|
+
typeof options.webpackIgnore !== "boolean"
|
|
1200
|
+
) {
|
|
1201
|
+
const { line: sl, column: sc } = locConverter.get(start);
|
|
1202
|
+
const { line: el, column: ec } = locConverter.get(end);
|
|
1044
1203
|
module.addWarning(
|
|
1045
1204
|
new UnsupportedFeatureWarning(
|
|
1046
1205
|
`\`webpackIgnore\` expected a boolean, but received: ${options.webpackIgnore}.`,
|
|
@@ -1051,44 +1210,203 @@ class HtmlParser extends Parser {
|
|
|
1051
1210
|
)
|
|
1052
1211
|
);
|
|
1053
1212
|
pendingWebpackIgnore = undefined;
|
|
1213
|
+
pendingWebpackInline = undefined;
|
|
1214
|
+
pendingHints = undefined;
|
|
1215
|
+
return;
|
|
1216
|
+
}
|
|
1217
|
+
if (
|
|
1218
|
+
options.webpackInline !== undefined &&
|
|
1219
|
+
typeof options.webpackInline !== "boolean"
|
|
1220
|
+
) {
|
|
1221
|
+
const { line: sl, column: sc } = locConverter.get(start);
|
|
1222
|
+
const { line: el, column: ec } = locConverter.get(end);
|
|
1223
|
+
module.addWarning(
|
|
1224
|
+
new UnsupportedFeatureWarning(
|
|
1225
|
+
`\`webpackInline\` expected a boolean, but received: ${options.webpackInline}.`,
|
|
1226
|
+
{
|
|
1227
|
+
start: { line: sl, column: sc },
|
|
1228
|
+
end: { line: el, column: ec }
|
|
1229
|
+
}
|
|
1230
|
+
)
|
|
1231
|
+
);
|
|
1232
|
+
pendingWebpackIgnore = undefined;
|
|
1233
|
+
pendingWebpackInline = undefined;
|
|
1234
|
+
pendingHints = undefined;
|
|
1054
1235
|
return;
|
|
1055
1236
|
}
|
|
1056
1237
|
pendingWebpackIgnore = options.webpackIgnore;
|
|
1238
|
+
pendingWebpackInline = options.webpackInline;
|
|
1239
|
+
if (
|
|
1240
|
+
options.webpackPrefetch !== undefined ||
|
|
1241
|
+
options.webpackPreload !== undefined ||
|
|
1242
|
+
options.webpackFetchPriority !== undefined
|
|
1243
|
+
) {
|
|
1244
|
+
const s = locConverter.get(start);
|
|
1245
|
+
const e = locConverter.get(end);
|
|
1246
|
+
const parsedHints = parseResourceHintOptions(options, module, {
|
|
1247
|
+
start: { line: s.line, column: s.column },
|
|
1248
|
+
end: { line: e.line, column: e.column }
|
|
1249
|
+
});
|
|
1250
|
+
pendingHints =
|
|
1251
|
+
parsedHints.prefetch !== undefined ||
|
|
1252
|
+
parsedHints.preload !== undefined ||
|
|
1253
|
+
parsedHints.fetchPriority !== undefined
|
|
1254
|
+
? parsedHints
|
|
1255
|
+
: undefined;
|
|
1256
|
+
} else {
|
|
1257
|
+
pendingHints = undefined;
|
|
1258
|
+
}
|
|
1057
1259
|
},
|
|
1058
|
-
[NodeType.Doctype]: () => {
|
|
1260
|
+
[NodeType.Doctype]: (path) => {
|
|
1261
|
+
// only visited when head injection is active (see `skip` below)
|
|
1262
|
+
if (doctypeEnd === -1) doctypeEnd = path.end();
|
|
1059
1263
|
pendingWebpackIgnore = undefined;
|
|
1264
|
+
pendingWebpackInline = undefined;
|
|
1265
|
+
pendingHints = undefined;
|
|
1060
1266
|
},
|
|
1061
1267
|
[NodeType.Element]: {
|
|
1062
1268
|
// Children (and `<template>` content) are walked by the
|
|
1063
1269
|
// processor after `enter`; `exit` clears a pending
|
|
1064
1270
|
// `webpackIgnore` once they're done.
|
|
1065
|
-
enter: (
|
|
1066
|
-
const node = /** @type {import("./syntax").HtmlElement} */ (n);
|
|
1271
|
+
enter: (path) => {
|
|
1067
1272
|
const ignore = pendingWebpackIgnore === true;
|
|
1273
|
+
const forceInline = pendingWebpackInline === true;
|
|
1068
1274
|
pendingWebpackIgnore = undefined;
|
|
1275
|
+
pendingWebpackInline = undefined;
|
|
1276
|
+
|
|
1277
|
+
// `<head>` anchors are collected for every page — they place
|
|
1278
|
+
// `inject: "head"` siblings and resource hints; the
|
|
1279
|
+
// title/meta/base facts only when configured. Structural, so it
|
|
1280
|
+
// runs even for `webpackIgnore`d elements.
|
|
1281
|
+
{
|
|
1282
|
+
const name = path.tagName();
|
|
1283
|
+
if (name === "template" && path.namespace() === NS_HTML) {
|
|
1284
|
+
templateDepth++;
|
|
1285
|
+
}
|
|
1286
|
+
if (headTemplateDepth > 0) {
|
|
1287
|
+
if (name === "template" && path.namespace() === NS_HTML) {
|
|
1288
|
+
headTemplateDepth++;
|
|
1289
|
+
}
|
|
1290
|
+
} else if (inHead) {
|
|
1291
|
+
if (headOptions !== undefined && path.namespace() === NS_HTML) {
|
|
1292
|
+
switch (name) {
|
|
1293
|
+
case "title":
|
|
1294
|
+
headHasTitle = true;
|
|
1295
|
+
break;
|
|
1296
|
+
case "base":
|
|
1297
|
+
headHasBase = true;
|
|
1298
|
+
break;
|
|
1299
|
+
case "meta": {
|
|
1300
|
+
if (path.findAttribute("charset") !== 0) {
|
|
1301
|
+
headCharsetEnd = path.tagEnd();
|
|
1302
|
+
} else {
|
|
1303
|
+
const httpEquiv = path.findAttribute("http-equiv");
|
|
1304
|
+
const contentAttr =
|
|
1305
|
+
httpEquiv !== 0 &&
|
|
1306
|
+
decodeEntities(
|
|
1307
|
+
path.attributeValue(httpEquiv),
|
|
1308
|
+
true
|
|
1309
|
+
).toLowerCase() === "content-type"
|
|
1310
|
+
? path.findAttribute("content")
|
|
1311
|
+
: 0;
|
|
1312
|
+
if (
|
|
1313
|
+
contentAttr !== 0 &&
|
|
1314
|
+
CHARSET_DECLARATION_REGEXP.test(
|
|
1315
|
+
decodeEntities(
|
|
1316
|
+
path.attributeValue(contentAttr),
|
|
1317
|
+
true
|
|
1318
|
+
)
|
|
1319
|
+
)
|
|
1320
|
+
) {
|
|
1321
|
+
headCharsetEnd = path.tagEnd();
|
|
1322
|
+
}
|
|
1323
|
+
}
|
|
1324
|
+
const nameAttr = path.findAttribute("name");
|
|
1325
|
+
const propertyAttr = path.findAttribute("property");
|
|
1326
|
+
if (nameAttr !== 0 || propertyAttr !== 0) {
|
|
1327
|
+
if (headMetaNames === undefined) {
|
|
1328
|
+
headMetaNames = new Set();
|
|
1329
|
+
}
|
|
1330
|
+
if (nameAttr !== 0) {
|
|
1331
|
+
headMetaNames.add(
|
|
1332
|
+
decodeEntities(
|
|
1333
|
+
path.attributeValue(nameAttr),
|
|
1334
|
+
true
|
|
1335
|
+
).toLowerCase()
|
|
1336
|
+
);
|
|
1337
|
+
}
|
|
1338
|
+
if (propertyAttr !== 0) {
|
|
1339
|
+
headMetaNames.add(
|
|
1340
|
+
decodeEntities(
|
|
1341
|
+
path.attributeValue(propertyAttr),
|
|
1342
|
+
true
|
|
1343
|
+
).toLowerCase()
|
|
1344
|
+
);
|
|
1345
|
+
}
|
|
1346
|
+
}
|
|
1347
|
+
break;
|
|
1348
|
+
}
|
|
1349
|
+
case "template":
|
|
1350
|
+
headTemplateDepth++;
|
|
1351
|
+
break;
|
|
1352
|
+
}
|
|
1353
|
+
} else if (
|
|
1354
|
+
name === "template" &&
|
|
1355
|
+
path.namespace() === NS_HTML
|
|
1356
|
+
) {
|
|
1357
|
+
headTemplateDepth++;
|
|
1358
|
+
}
|
|
1359
|
+
if (path.end() > headLastEnd) headLastEnd = path.end();
|
|
1360
|
+
} else if (!headSeen && path.namespace() === NS_HTML) {
|
|
1361
|
+
if (name === "html") {
|
|
1362
|
+
htmlTagEnd = path.tagEnd();
|
|
1363
|
+
} else if (name === "head") {
|
|
1364
|
+
headSeen = true;
|
|
1365
|
+
inHead = true;
|
|
1366
|
+
// explicit head has real offsets; an implicit one (head
|
|
1367
|
+
// tags are optional in HTML5) anchors after `<html>`,
|
|
1368
|
+
// the doctype, or the document start
|
|
1369
|
+
const tagEnd = path.tagEnd();
|
|
1370
|
+
headStart =
|
|
1371
|
+
tagEnd > 0
|
|
1372
|
+
? tagEnd
|
|
1373
|
+
: htmlTagEnd > 0
|
|
1374
|
+
? htmlTagEnd
|
|
1375
|
+
: doctypeEnd > 0
|
|
1376
|
+
? doctypeEnd
|
|
1377
|
+
: 0;
|
|
1378
|
+
headLastEnd = headStart;
|
|
1379
|
+
}
|
|
1380
|
+
}
|
|
1381
|
+
}
|
|
1069
1382
|
|
|
1070
1383
|
if (ignore) {
|
|
1071
1384
|
return;
|
|
1072
1385
|
}
|
|
1073
1386
|
|
|
1074
|
-
const elementName =
|
|
1075
|
-
const
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
}
|
|
1387
|
+
const elementName = path.tagName();
|
|
1388
|
+
const attributeCount = path.attributeCount();
|
|
1389
|
+
const elementStart = path.start();
|
|
1390
|
+
const tagEnd = path.tagEnd();
|
|
1391
|
+
const nameEnd = path.nameEnd();
|
|
1392
|
+
|
|
1393
|
+
// The first `<base href>` freezes the base for later URLs — but
|
|
1394
|
+
// only one in the real document tree, not inside an inert
|
|
1395
|
+
// `<template>` (whose contents are a separate fragment).
|
|
1396
|
+
if (
|
|
1397
|
+
documentBase === undefined &&
|
|
1398
|
+
templateDepth === 0 &&
|
|
1399
|
+
elementName === "base" &&
|
|
1400
|
+
path.namespace() === NS_HTML
|
|
1401
|
+
) {
|
|
1402
|
+
const hrefAttr = path.findAttribute("href");
|
|
1403
|
+
if (hrefAttr !== 0) resolveDocumentBase(path, hrefAttr);
|
|
1404
|
+
}
|
|
1405
|
+
|
|
1406
|
+
// Rebind the parse-scoped lazy attribute map to this element.
|
|
1407
|
+
attrMapPath = path;
|
|
1408
|
+
attrMapCount = attributeCount;
|
|
1409
|
+
currentAttributesMap = undefined;
|
|
1092
1410
|
|
|
1093
1411
|
// Each matched attribute is a source; the element body (inline
|
|
1094
1412
|
// `<style>`/`<script>`) is one more — content rather than an attribute.
|
|
@@ -1097,10 +1415,10 @@ class HtmlParser extends Parser {
|
|
|
1097
1415
|
this.sourcesByTag[elementName] || this.sourcesByTag[ANY_TAG];
|
|
1098
1416
|
const bodyItem = CONTENT_SOURCES[elementName];
|
|
1099
1417
|
// Iterate the attributes, then one extra step for the element
|
|
1100
|
-
// body (`i ===
|
|
1101
|
-
// attribute steps, so
|
|
1102
|
-
for (let i = 0; i <
|
|
1103
|
-
const content = i ===
|
|
1418
|
+
// body (`i === attributeCount`); the attribute ref is only read on the
|
|
1419
|
+
// attribute steps, so it stays in bounds.
|
|
1420
|
+
for (let i = 0; i < attributeCount + 1; i++) {
|
|
1421
|
+
const content = i === attributeCount;
|
|
1104
1422
|
/** @type {SourceType} */
|
|
1105
1423
|
let type;
|
|
1106
1424
|
/** @type {string} */
|
|
@@ -1109,23 +1427,18 @@ class HtmlParser extends Parser {
|
|
|
1109
1427
|
let start;
|
|
1110
1428
|
/** @type {number} */
|
|
1111
1429
|
let end;
|
|
1112
|
-
/** @type {import("./syntax").
|
|
1113
|
-
let attr;
|
|
1430
|
+
/** @type {import("./syntax").HtmlAttributeRef} */
|
|
1431
|
+
let attr = 0;
|
|
1114
1432
|
if (content) {
|
|
1115
1433
|
if (!bodyItem) continue;
|
|
1116
1434
|
if (bodyItem.filter && !bodyItem.filter(getAttributesMap())) {
|
|
1117
1435
|
continue;
|
|
1118
1436
|
}
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
if (bodyStart === undefined) bodyStart = child.start;
|
|
1125
|
-
bodyEnd = child.end;
|
|
1126
|
-
}
|
|
1127
|
-
}
|
|
1128
|
-
if (bodyStart === undefined) continue;
|
|
1437
|
+
// `skip.text` drops the body `Text` node; the raw content
|
|
1438
|
+
// span is [`tagEnd`, `contentEnd`] on the element.
|
|
1439
|
+
const bodyStart = tagEnd;
|
|
1440
|
+
const bodyEnd = path.contentEnd();
|
|
1441
|
+
if (bodyEnd <= bodyStart) continue;
|
|
1129
1442
|
value = source.slice(bodyStart, bodyEnd);
|
|
1130
1443
|
if (value.trim() === "") continue;
|
|
1131
1444
|
start = bodyStart;
|
|
@@ -1135,32 +1448,32 @@ class HtmlParser extends Parser {
|
|
|
1135
1448
|
? bodyItem.type(getAttributesMap(), css)
|
|
1136
1449
|
: bodyItem.type;
|
|
1137
1450
|
} else {
|
|
1138
|
-
attr =
|
|
1139
|
-
const item = sources[attr
|
|
1451
|
+
attr = path.attributeAt(i);
|
|
1452
|
+
const item = sources[path.attributeName(attr)];
|
|
1140
1453
|
if (!item) continue;
|
|
1141
1454
|
if (
|
|
1142
1455
|
item.namespace !== undefined &&
|
|
1143
|
-
|
|
1456
|
+
path.namespace() !== item.namespace
|
|
1144
1457
|
) {
|
|
1145
1458
|
continue;
|
|
1146
1459
|
}
|
|
1147
1460
|
// Adoption-agency clones carry no offsets; skip blank values.
|
|
1148
|
-
if (
|
|
1461
|
+
if (path.attributeValueStart(attr) === -1) {
|
|
1149
1462
|
continue;
|
|
1150
1463
|
}
|
|
1151
|
-
value = attr
|
|
1464
|
+
value = path.attributeValue(attr);
|
|
1152
1465
|
if (!value || !/\S/.test(value)) continue;
|
|
1153
1466
|
const filter = item.filter;
|
|
1154
1467
|
if (filter) {
|
|
1155
1468
|
// The filter also sees the decoded value (the browser tokenizes it
|
|
1156
1469
|
// decoded, e.g. `url(`), so a value-only check needn't re-read it.
|
|
1157
1470
|
const decoded = value.includes("&")
|
|
1158
|
-
?
|
|
1471
|
+
? decodeEntities(value, true)
|
|
1159
1472
|
: value;
|
|
1160
1473
|
if (!filter(getAttributesMap(), decoded)) continue;
|
|
1161
1474
|
}
|
|
1162
|
-
start = attr
|
|
1163
|
-
end = attr
|
|
1475
|
+
start = path.attributeValueStart(attr);
|
|
1476
|
+
end = path.attributeValueEnd(attr);
|
|
1164
1477
|
type =
|
|
1165
1478
|
typeof item.type === "function"
|
|
1166
1479
|
? item.type(getAttributesMap(), css)
|
|
@@ -1175,9 +1488,7 @@ class HtmlParser extends Parser {
|
|
|
1175
1488
|
case "stylesheet-style":
|
|
1176
1489
|
case "stylesheet-style-attribute": {
|
|
1177
1490
|
if (!css) break;
|
|
1178
|
-
const cssText = content
|
|
1179
|
-
? value
|
|
1180
|
-
: decodeHtmlEntities(value, true);
|
|
1491
|
+
const cssText = content ? value : decodeEntities(value, true);
|
|
1181
1492
|
if (cssText.trim() === "") break;
|
|
1182
1493
|
const dep = new HtmlInlineStyleDependency(
|
|
1183
1494
|
dataUri("text/css", cssText),
|
|
@@ -1196,7 +1507,7 @@ class HtmlParser extends Parser {
|
|
|
1196
1507
|
// whose URL is `about:srcdoc`, so its base URL is inherited from
|
|
1197
1508
|
// this document — asset URLs resolve against this file's context.
|
|
1198
1509
|
case "srcdoc": {
|
|
1199
|
-
const htmlText =
|
|
1510
|
+
const htmlText = decodeEntities(value, true);
|
|
1200
1511
|
// Only spin up a nested module when there is markup that can
|
|
1201
1512
|
// reference an asset; text/formatting-only markup is identical
|
|
1202
1513
|
// after rewriting (see `SRCDOC_ASSET_REGEXP`).
|
|
@@ -1225,7 +1536,7 @@ class HtmlParser extends Parser {
|
|
|
1225
1536
|
const entryName = `__html_${moduleHash}_${nextEntryIndex++}`;
|
|
1226
1537
|
const dep = new HtmlInlineScriptDependency(
|
|
1227
1538
|
request,
|
|
1228
|
-
|
|
1539
|
+
nameEnd,
|
|
1229
1540
|
[start, end],
|
|
1230
1541
|
entryName,
|
|
1231
1542
|
scriptType === "script-module" ? "esm" : "commonjs"
|
|
@@ -1233,8 +1544,9 @@ class HtmlParser extends Parser {
|
|
|
1233
1544
|
setLoc(dep, start, end);
|
|
1234
1545
|
module.addPresentationalDependency(dep);
|
|
1235
1546
|
reconcileScriptTypeAttr(
|
|
1236
|
-
|
|
1237
|
-
|
|
1547
|
+
path,
|
|
1548
|
+
path.findAttribute("type"),
|
|
1549
|
+
nameEnd,
|
|
1238
1550
|
scriptType,
|
|
1239
1551
|
source
|
|
1240
1552
|
);
|
|
@@ -1254,13 +1566,13 @@ class HtmlParser extends Parser {
|
|
|
1254
1566
|
case "css-url":
|
|
1255
1567
|
case "msapplication-task":
|
|
1256
1568
|
case "modulepreload":
|
|
1257
|
-
case "stylesheet":
|
|
1569
|
+
case "stylesheet":
|
|
1570
|
+
case "html":
|
|
1571
|
+
case "preload":
|
|
1572
|
+
case "prefetch": {
|
|
1258
1573
|
// Parse the value into one or more URLs (decoding character
|
|
1259
1574
|
// references, mapping spans back to raw offsets), then emit a plain
|
|
1260
1575
|
// asset reference or an entry chunk per URL.
|
|
1261
|
-
const a = /** @type {import("./syntax").HtmlAttribute} */ (
|
|
1262
|
-
attr
|
|
1263
|
-
);
|
|
1264
1576
|
const parse =
|
|
1265
1577
|
type === "srcset"
|
|
1266
1578
|
? parseSrcset
|
|
@@ -1273,7 +1585,7 @@ class HtmlParser extends Parser {
|
|
|
1273
1585
|
/** @type {number[] | undefined} */
|
|
1274
1586
|
let map;
|
|
1275
1587
|
if (value.includes("&")) {
|
|
1276
|
-
({ text, map } =
|
|
1588
|
+
({ text, map } = decodeEntities(value, true, true));
|
|
1277
1589
|
if (!/\S/.test(text)) break;
|
|
1278
1590
|
}
|
|
1279
1591
|
/** @type {ParsedSource[] | undefined} */
|
|
@@ -1287,9 +1599,9 @@ class HtmlParser extends Parser {
|
|
|
1287
1599
|
new ModuleDependencyError(
|
|
1288
1600
|
module,
|
|
1289
1601
|
new WebpackError(
|
|
1290
|
-
`Bad value for attribute "${
|
|
1291
|
-
|
|
1292
|
-
}" on element "${elementName}": ${
|
|
1602
|
+
`Bad value for attribute "${path.attributeName(
|
|
1603
|
+
attr
|
|
1604
|
+
)}" on element "${elementName}": ${
|
|
1293
1605
|
/** @type {Error} */ (err).message
|
|
1294
1606
|
}`
|
|
1295
1607
|
),
|
|
@@ -1305,8 +1617,84 @@ class HtmlParser extends Parser {
|
|
|
1305
1617
|
for (const [url, us, ue] of parsed) {
|
|
1306
1618
|
// Internal `url(#id)` / fragment-only refs aren't assets.
|
|
1307
1619
|
if (!url || url.startsWith("#")) continue;
|
|
1620
|
+
// Resolve the request against `<base href>`. Only relative
|
|
1621
|
+
// URLs are affected (scheme / `//` / `/` URLs ignore the
|
|
1622
|
+
// base); an external base drops them from the build.
|
|
1623
|
+
let request = url;
|
|
1624
|
+
if (
|
|
1625
|
+
documentBase &&
|
|
1626
|
+
url.charCodeAt(0) !== CC_SLASH &&
|
|
1627
|
+
!ABSOLUTE_URL_SCHEME_REGEXP.test(url)
|
|
1628
|
+
) {
|
|
1629
|
+
if (baseIsExternal) continue;
|
|
1630
|
+
request = baseDir + url;
|
|
1631
|
+
}
|
|
1308
1632
|
const s = start + (map ? map[us] : us);
|
|
1309
1633
|
const e = start + (map ? map[ue] : ue);
|
|
1634
|
+
// `html` links the URL as its own emitted page: the same
|
|
1635
|
+
// `HtmlEntryDependency` as `<script src>` / `<link>`, but the `html`
|
|
1636
|
+
// kind rewrites to the page's filename and emits no sibling tags.
|
|
1637
|
+
// Recorded as an independent entry (no `dependOn`), like an entry point.
|
|
1638
|
+
if (type === "html") {
|
|
1639
|
+
const entryName = `__html_${moduleHash}_${nextEntryIndex++}`;
|
|
1640
|
+
const dep = new HtmlEntryDependency(
|
|
1641
|
+
request,
|
|
1642
|
+
[s, e],
|
|
1643
|
+
entryName,
|
|
1644
|
+
undefined,
|
|
1645
|
+
"html"
|
|
1646
|
+
);
|
|
1647
|
+
setLoc(dep, s, e);
|
|
1648
|
+
module.addPresentationalDependency(dep);
|
|
1649
|
+
htmlLinkEntries.push({ request, entryName, type });
|
|
1650
|
+
continue;
|
|
1651
|
+
}
|
|
1652
|
+
// `rel="preload"/"prefetch"` of a bundled resource: an
|
|
1653
|
+
// independent, non-executing entry whose `href` is rewritten to
|
|
1654
|
+
// the built chunk's URL (CSS for `as="style"`, JS otherwise). No
|
|
1655
|
+
// sibling tags — a resource hint references exactly one URL.
|
|
1656
|
+
if (type === "preload" || type === "prefetch") {
|
|
1657
|
+
const as = getAttributeValue(getAttributesMap(), "as");
|
|
1658
|
+
const asStyle = as
|
|
1659
|
+
? as.trim().toLowerCase() === "style"
|
|
1660
|
+
: false;
|
|
1661
|
+
const entryName = `__html_${moduleHash}_${nextEntryIndex++}`;
|
|
1662
|
+
// Tag metadata so the template can mirror `crossorigin`/
|
|
1663
|
+
// `integrity` onto a native `preload` `<link>` (integrity-
|
|
1664
|
+
// eligible per the SRI spec). `prefetch` gets none — the spec
|
|
1665
|
+
// doesn't list it — so its `integrity` is left uncaptured.
|
|
1666
|
+
const isNativeLink = elementName === "link";
|
|
1667
|
+
const dep = new HtmlEntryDependency(
|
|
1668
|
+
request,
|
|
1669
|
+
[s, e],
|
|
1670
|
+
entryName,
|
|
1671
|
+
asStyle ? "css-import" : undefined,
|
|
1672
|
+
type,
|
|
1673
|
+
elementStart,
|
|
1674
|
+
tagEnd,
|
|
1675
|
+
isNativeLink,
|
|
1676
|
+
"",
|
|
1677
|
+
nameEnd,
|
|
1678
|
+
isNativeLink && path.findAttribute("crossorigin") !== 0,
|
|
1679
|
+
-1,
|
|
1680
|
+
type === "preload" && isNativeLink
|
|
1681
|
+
? captureIntegrityRange(path, source, elementStart)
|
|
1682
|
+
: null,
|
|
1683
|
+
null
|
|
1684
|
+
);
|
|
1685
|
+
setLoc(dep, s, e);
|
|
1686
|
+
module.addPresentationalDependency(dep);
|
|
1687
|
+
(type === "preload"
|
|
1688
|
+
? preloadEntries
|
|
1689
|
+
: prefetchEntries
|
|
1690
|
+
).push({
|
|
1691
|
+
request,
|
|
1692
|
+
entryName,
|
|
1693
|
+
type,
|
|
1694
|
+
css: asStyle
|
|
1695
|
+
});
|
|
1696
|
+
continue;
|
|
1697
|
+
}
|
|
1310
1698
|
// `src`/`srcset`/`css-url`/`msapplication-task` are plain assets;
|
|
1311
1699
|
// the rest are narrowed to entry types here and share one
|
|
1312
1700
|
// `HtmlEntryDependency`, recorded for HtmlModulesPlugin.
|
|
@@ -1345,11 +1733,12 @@ class HtmlParser extends Parser {
|
|
|
1345
1733
|
// CSP/fetch attributes the template copies onto siblings.
|
|
1346
1734
|
let copyableAttrsText = "";
|
|
1347
1735
|
let hasOwnCrossOrigin = false;
|
|
1348
|
-
if (
|
|
1736
|
+
if (elementStart >= 0) {
|
|
1349
1737
|
for (const copyableName of COPYABLE_SIBLING_ATTRS) {
|
|
1350
|
-
const copyableAttr =
|
|
1351
|
-
if (copyableAttr) {
|
|
1738
|
+
const copyableAttr = path.findAttribute(copyableName);
|
|
1739
|
+
if (copyableAttr !== 0) {
|
|
1352
1740
|
copyableAttrsText += attrSourceSpan(
|
|
1741
|
+
path,
|
|
1353
1742
|
source,
|
|
1354
1743
|
copyableAttr
|
|
1355
1744
|
);
|
|
@@ -1359,70 +1748,68 @@ class HtmlParser extends Parser {
|
|
|
1359
1748
|
}
|
|
1360
1749
|
}
|
|
1361
1750
|
}
|
|
1751
|
+
// Blocking-ness of the tag as *emitted*:
|
|
1752
|
+
// `reconcileScriptTypeAttr` upgrades every script entry
|
|
1753
|
+
// to `type="module"` under `output.module` and strips an
|
|
1754
|
+
// authored `type="module"` otherwise — so only non-module
|
|
1755
|
+
// output without `defer`/`async` blocks. Non-native
|
|
1756
|
+
// elements anchor conservatively (their synthesized
|
|
1757
|
+
// siblings may be plain blocking `<script>`s).
|
|
1362
1758
|
if (
|
|
1363
|
-
|
|
1364
|
-
(type === "script" || type === "script-module")
|
|
1759
|
+
firstBlockingScriptStart === -1 &&
|
|
1760
|
+
(type === "script" || type === "script-module") &&
|
|
1761
|
+
!outputModule &&
|
|
1762
|
+
(elementName !== nativeTag ||
|
|
1763
|
+
(path.findAttribute("defer") === 0 &&
|
|
1764
|
+
path.findAttribute("async") === 0))
|
|
1365
1765
|
) {
|
|
1366
|
-
|
|
1766
|
+
firstBlockingScriptStart = elementStart;
|
|
1367
1767
|
}
|
|
1368
1768
|
// Sibling-clone attribute edits, captured from the parsed
|
|
1369
1769
|
// attributes (offsets relative to the tag) so the template
|
|
1370
1770
|
// needn't re-parse the tag text: `integrity` is dropped
|
|
1371
1771
|
// (content-specific) and `script-module` clones are forced
|
|
1372
1772
|
// to `type="module"` (its value range, else inserted).
|
|
1373
|
-
|
|
1374
|
-
|
|
1773
|
+
const integrityRange =
|
|
1774
|
+
elementStart >= 0 && elementName === nativeTag
|
|
1775
|
+
? captureIntegrityRange(path, source, elementStart)
|
|
1776
|
+
: null;
|
|
1375
1777
|
/** @type {Range | null} */
|
|
1376
1778
|
let typeValueRange = null;
|
|
1377
|
-
if (
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
const q = source.charCodeAt(
|
|
1392
|
-
integrityAttr.valueStart - 1
|
|
1393
|
-
);
|
|
1394
|
-
re =
|
|
1395
|
-
q === CC_QUOTATION || q === CC_APOSTROPHE
|
|
1396
|
-
? integrityAttr.valueEnd + 1
|
|
1397
|
-
: integrityAttr.valueEnd;
|
|
1398
|
-
}
|
|
1399
|
-
integrityRange = [rs - node.start, re - node.start];
|
|
1400
|
-
}
|
|
1401
|
-
if (elementKind === "script-module") {
|
|
1402
|
-
const typeAttr = findAttr(attrs, "type");
|
|
1403
|
-
if (typeAttr && typeAttr.valueStart >= 0) {
|
|
1404
|
-
typeValueRange = [
|
|
1405
|
-
typeAttr.valueStart - node.start,
|
|
1406
|
-
typeAttr.valueEnd - node.start
|
|
1407
|
-
];
|
|
1408
|
-
}
|
|
1779
|
+
if (
|
|
1780
|
+
elementStart >= 0 &&
|
|
1781
|
+
elementName === nativeTag &&
|
|
1782
|
+
elementKind === "script-module"
|
|
1783
|
+
) {
|
|
1784
|
+
const typeAttr = path.findAttribute("type");
|
|
1785
|
+
if (
|
|
1786
|
+
typeAttr !== 0 &&
|
|
1787
|
+
path.attributeValueStart(typeAttr) >= 0
|
|
1788
|
+
) {
|
|
1789
|
+
typeValueRange = [
|
|
1790
|
+
path.attributeValueStart(typeAttr) - elementStart,
|
|
1791
|
+
path.attributeValueEnd(typeAttr) - elementStart
|
|
1792
|
+
];
|
|
1409
1793
|
}
|
|
1410
1794
|
}
|
|
1411
1795
|
const dep = new HtmlEntryDependency(
|
|
1412
|
-
|
|
1796
|
+
request,
|
|
1413
1797
|
[s, e],
|
|
1414
1798
|
entryName,
|
|
1415
1799
|
entryCategory,
|
|
1416
1800
|
elementKind,
|
|
1417
|
-
|
|
1418
|
-
|
|
1801
|
+
elementStart,
|
|
1802
|
+
tagEnd,
|
|
1419
1803
|
elementName === nativeTag,
|
|
1420
1804
|
copyableAttrsText,
|
|
1421
|
-
|
|
1805
|
+
nameEnd,
|
|
1422
1806
|
hasOwnCrossOrigin,
|
|
1423
|
-
|
|
1807
|
+
firstBlockingScriptStart,
|
|
1424
1808
|
integrityRange,
|
|
1425
|
-
typeValueRange
|
|
1809
|
+
typeValueRange,
|
|
1810
|
+
forceInline,
|
|
1811
|
+
headSeen && headStart > 0 ? headStart : -1,
|
|
1812
|
+
headSeen && headLastEnd > 0 ? headLastEnd : -1
|
|
1426
1813
|
);
|
|
1427
1814
|
setLoc(dep, s, e);
|
|
1428
1815
|
module.addPresentationalDependency(dep);
|
|
@@ -1431,8 +1818,9 @@ class HtmlParser extends Parser {
|
|
|
1431
1818
|
(type === "script" || type === "script-module")
|
|
1432
1819
|
) {
|
|
1433
1820
|
reconcileScriptTypeAttr(
|
|
1434
|
-
|
|
1435
|
-
|
|
1821
|
+
path,
|
|
1822
|
+
path.findAttribute("type"),
|
|
1823
|
+
nameEnd,
|
|
1436
1824
|
type,
|
|
1437
1825
|
source
|
|
1438
1826
|
);
|
|
@@ -1444,9 +1832,19 @@ class HtmlParser extends Parser {
|
|
|
1444
1832
|
: type === "stylesheet"
|
|
1445
1833
|
? stylesheetEntries
|
|
1446
1834
|
: modulePreloadEntries
|
|
1447
|
-
).push({ request
|
|
1835
|
+
).push({ request, entryName, type });
|
|
1448
1836
|
} else {
|
|
1449
|
-
const dep = new HtmlSourceDependency(
|
|
1837
|
+
const dep = new HtmlSourceDependency(request, [s, e]);
|
|
1838
|
+
ResourceHintPlugin.applyDefaults(
|
|
1839
|
+
dep,
|
|
1840
|
+
ResourceHintPlugin.matchUrlHints(urlHints, request)
|
|
1841
|
+
);
|
|
1842
|
+
// `<!-- webpackPrefetch: true -->` (etc.) immediately
|
|
1843
|
+
// before this tag wins over the project-wide default.
|
|
1844
|
+
if (pendingHints) {
|
|
1845
|
+
ResourceHintPlugin.applyParsedHints(dep, pendingHints);
|
|
1846
|
+
pendingHints = undefined;
|
|
1847
|
+
}
|
|
1450
1848
|
setLoc(dep, s, e);
|
|
1451
1849
|
module.addDependency(dep);
|
|
1452
1850
|
module.addCodeGenerationDependency(dep);
|
|
@@ -1457,32 +1855,104 @@ class HtmlParser extends Parser {
|
|
|
1457
1855
|
}
|
|
1458
1856
|
}
|
|
1459
1857
|
},
|
|
1460
|
-
exit: () => {
|
|
1858
|
+
exit: (path) => {
|
|
1859
|
+
if (path.namespace() === NS_HTML) {
|
|
1860
|
+
const name = path.tagName();
|
|
1861
|
+
if (name === "template") {
|
|
1862
|
+
if (templateDepth > 0) templateDepth--;
|
|
1863
|
+
if (headTemplateDepth > 0) headTemplateDepth--;
|
|
1864
|
+
} else if (name === "head") {
|
|
1865
|
+
inHead = false;
|
|
1866
|
+
}
|
|
1867
|
+
}
|
|
1461
1868
|
pendingWebpackIgnore = undefined;
|
|
1869
|
+
pendingWebpackInline = undefined;
|
|
1870
|
+
pendingHints = undefined;
|
|
1462
1871
|
}
|
|
1463
1872
|
}
|
|
1464
1873
|
})
|
|
1465
|
-
|
|
1874
|
+
// Skip AST output this walk never reads: `text` (script/style bodies are
|
|
1875
|
+
// read by offset via `contentEnd`) and `doctype` — unless head injection
|
|
1876
|
+
// needs the doctype as an insertion anchor; keep `comments` for
|
|
1877
|
+
// magic comments (`webpackIgnore`).
|
|
1878
|
+
.process(source, {
|
|
1879
|
+
fragmentContext: this.fragmentContext,
|
|
1880
|
+
skip: { text: true, doctype: headOptions === undefined }
|
|
1881
|
+
});
|
|
1882
|
+
|
|
1883
|
+
// `output.html` head injection: existing tags win, injected values are
|
|
1884
|
+
// escaped, insertions at the same offset are merged to keep their order.
|
|
1885
|
+
if (headOptions !== undefined && headSeen) {
|
|
1886
|
+
const { title, meta, base } = headOptions;
|
|
1887
|
+
let startTags = "";
|
|
1888
|
+
if (meta && meta.charset && headCharsetEnd === -1) {
|
|
1889
|
+
startTags += `<meta charset="${escapeAttribute(meta.charset)}">`;
|
|
1890
|
+
}
|
|
1891
|
+
// <base> must follow the charset declaration per spec
|
|
1892
|
+
let baseAfterCharset = "";
|
|
1893
|
+
if (base && !headHasBase) {
|
|
1894
|
+
if (headCharsetEnd !== -1) baseAfterCharset = baseTag(base);
|
|
1895
|
+
else startTags += baseTag(base);
|
|
1896
|
+
}
|
|
1897
|
+
let endTags = "";
|
|
1898
|
+
if (meta) {
|
|
1899
|
+
for (const [name, value] of Object.entries(meta)) {
|
|
1900
|
+
if (
|
|
1901
|
+
name === "charset" ||
|
|
1902
|
+
(headMetaNames !== undefined &&
|
|
1903
|
+
headMetaNames.has(name.toLowerCase()))
|
|
1904
|
+
) {
|
|
1905
|
+
continue;
|
|
1906
|
+
}
|
|
1907
|
+
endTags += metaTag(name, value);
|
|
1908
|
+
}
|
|
1909
|
+
}
|
|
1910
|
+
if (title && !headHasTitle) {
|
|
1911
|
+
endTags += `<title>${escapeText(title)}</title>`;
|
|
1912
|
+
}
|
|
1913
|
+
/** @type {[number, string][]} */
|
|
1914
|
+
const inserts = [];
|
|
1915
|
+
if (startTags) inserts.push([headStart, startTags]);
|
|
1916
|
+
if (baseAfterCharset) inserts.push([headCharsetEnd, baseAfterCharset]);
|
|
1917
|
+
if (endTags) inserts.push([headLastEnd, endTags]);
|
|
1918
|
+
inserts.sort((a, b) => a[0] - b[0]);
|
|
1919
|
+
for (let i = 0; i < inserts.length; i++) {
|
|
1920
|
+
const at = inserts[i][0];
|
|
1921
|
+
let tags = inserts[i][1];
|
|
1922
|
+
while (i + 1 < inserts.length && inserts[i + 1][0] === at) {
|
|
1923
|
+
tags += inserts[++i][1];
|
|
1924
|
+
}
|
|
1925
|
+
module.addPresentationalDependency(new ConstDependency(tags, at));
|
|
1926
|
+
}
|
|
1927
|
+
}
|
|
1466
1928
|
|
|
1467
1929
|
const buildInfo = /** @type {HtmlModuleBuildInfo} */ (module.buildInfo);
|
|
1468
1930
|
buildInfo.strict = true;
|
|
1931
|
+
if (baseUrlPrefix !== undefined) buildInfo.baseUrlPrefix = baseUrlPrefix;
|
|
1469
1932
|
// Hand off the collected entries to HtmlModulesPlugin; it creates the
|
|
1470
1933
|
// real compilation entries during the finishMake hook. The `script`
|
|
1471
1934
|
// and `script-module` groups are chained via a leader-only dependOn
|
|
1472
1935
|
// so they share a runtime; `modulepreload` and `stylesheet` entries
|
|
1473
1936
|
// are emitted as independent entries since `<link rel=modulepreload>`
|
|
1474
|
-
// must preload without running and stylesheets have no runtime.
|
|
1937
|
+
// must preload without running and stylesheets have no runtime. `html`
|
|
1938
|
+
// links are independent page entries too.
|
|
1475
1939
|
if (
|
|
1476
1940
|
scriptEntries.length > 0 ||
|
|
1477
1941
|
scriptModuleEntries.length > 0 ||
|
|
1478
1942
|
modulePreloadEntries.length > 0 ||
|
|
1479
|
-
stylesheetEntries.length > 0
|
|
1943
|
+
stylesheetEntries.length > 0 ||
|
|
1944
|
+
htmlLinkEntries.length > 0 ||
|
|
1945
|
+
preloadEntries.length > 0 ||
|
|
1946
|
+
prefetchEntries.length > 0
|
|
1480
1947
|
) {
|
|
1481
|
-
buildInfo.
|
|
1948
|
+
buildInfo.htmlEntries = {
|
|
1482
1949
|
script: scriptEntries,
|
|
1483
1950
|
"script-module": scriptModuleEntries,
|
|
1484
1951
|
modulepreload: modulePreloadEntries,
|
|
1485
|
-
stylesheet: stylesheetEntries
|
|
1952
|
+
stylesheet: stylesheetEntries,
|
|
1953
|
+
html: htmlLinkEntries,
|
|
1954
|
+
preload: preloadEntries,
|
|
1955
|
+
prefetch: prefetchEntries
|
|
1486
1956
|
};
|
|
1487
1957
|
}
|
|
1488
1958
|
|
|
@@ -1495,4 +1965,6 @@ class HtmlParser extends Parser {
|
|
|
1495
1965
|
}
|
|
1496
1966
|
}
|
|
1497
1967
|
|
|
1968
|
+
HtmlParser.buildHeadTags = buildHeadTags;
|
|
1969
|
+
|
|
1498
1970
|
module.exports = HtmlParser;
|