webpack 5.108.2 → 5.108.4

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.
@@ -35,14 +35,13 @@ const {
35
35
  webpackCommentRegExp
36
36
  } = require("../util/magicComment");
37
37
  const {
38
+ NS_HTML,
38
39
  NS_SVG,
39
40
  NodeType,
40
41
  SVG_TAG_ADJUST,
41
42
  SourceProcessor,
42
- buildHtmlAst,
43
43
  decodeHtmlEntities,
44
44
  decodeHtmlEntitiesWithMap,
45
- findAttr,
46
45
  parseSrcset
47
46
  } = require("./syntax");
48
47
 
@@ -113,6 +112,10 @@ const FUNC_IRI_URL_REGEXP = /url\(/i;
113
112
  // formatting/text markup (`<p>hi</p>`) rewrites to itself, so skip it.
114
113
  const SRCDOC_ASSET_REGEXP = /[=]|url\(|@import/i;
115
114
 
115
+ // A URL carrying its own scheme (`https:`, `data:`, …) ignores the document
116
+ // base per the URL spec, so `<base href>` never rewrites it.
117
+ const ABSOLUTE_URL_SCHEME_REGEXP = /^[a-zA-Z][a-zA-Z\d+\-.]*:/;
118
+
116
119
  // CSP/fetch attributes copied verbatim onto a synthesized sibling `<link>` /
117
120
  // `<script>` (`HtmlEntryDependency`). Fixed output order, independent of
118
121
  // source order.
@@ -120,24 +123,28 @@ const COPYABLE_SIBLING_ATTRS = ["nonce", "crossorigin", "referrerpolicy"];
120
123
 
121
124
  const CC_QUOTATION = '"'.charCodeAt(0);
122
125
  const CC_APOSTROPHE = "'".charCodeAt(0);
126
+ const CC_SLASH = "/".charCodeAt(0);
123
127
 
124
128
  /**
125
129
  * Byte-exact source span of an attribute including the single leading
126
130
  * whitespace (` name`, ` name=value`, ` name="value"`), mirroring the
127
131
  * tokenizer's end-of-attribute rule (`valueEnd + 1` past the closing quote).
132
+ * @param {import("./syntax").HtmlPath} path walk path (used only for attribute-ref reads)
128
133
  * @param {string} source HTML source
129
- * @param {import("./syntax").HtmlAttribute} attr attribute
134
+ * @param {import("./syntax").HtmlAttributeRef} attr attribute ref
130
135
  * @returns {string} the attribute's source slice
131
136
  */
132
- const attrSourceSpan = (source, attr) => {
137
+ const attrSourceSpan = (path, source, attr) => {
138
+ const valueStart = path.attributeValueStart(attr);
139
+ const valueEnd = path.attributeValueEnd(attr);
133
140
  const end =
134
- attr.valueStart === -1
135
- ? attr.nameEnd
136
- : source.charCodeAt(attr.valueStart - 1) === CC_QUOTATION ||
137
- source.charCodeAt(attr.valueStart - 1) === CC_APOSTROPHE
138
- ? attr.valueEnd + 1
139
- : attr.valueEnd;
140
- return source.slice(attr.nameStart - 1, end);
141
+ valueStart === -1
142
+ ? path.attributeNameEnd(attr)
143
+ : source.charCodeAt(valueStart - 1) === CC_QUOTATION ||
144
+ source.charCodeAt(valueStart - 1) === CC_APOSTROPHE
145
+ ? valueEnd + 1
146
+ : valueEnd;
147
+ return source.slice(path.attributeNameStart(attr) - 1, end);
141
148
  };
142
149
 
143
150
  // eslint-disable-next-line no-control-regex
@@ -909,6 +916,23 @@ class HtmlParser extends Parser {
909
916
 
910
917
  let nextEntryIndex = 0;
911
918
 
919
+ // `<base href>` resolves the relative URLs that follow it. A relative base
920
+ // (`./assets/`) rewrites them into a subdirectory — still bundled; a
921
+ // root-relative or absolute base (`/`, `https://cdn/`) points them outside
922
+ // the build, so those URLs are left untouched. Resolved in the walk below
923
+ // from the first `<base href>` seen (`documentBase` undefined until then).
924
+ /** @type {string | undefined} */
925
+ let documentBase;
926
+ /** @type {string | undefined} */
927
+ let baseDir;
928
+ let baseIsExternal = false;
929
+ // Prepended to the emitted URLs' auto-public-path undo path so the base
930
+ // doesn't misdirect them: the browser resolves rewritten (relative)
931
+ // output URLs against the base dir, so one `../` per base segment cancels
932
+ // it. Undefined for absolute publicPath (no undo path is emitted).
933
+ /** @type {string | undefined} */
934
+ let baseUrlPrefix;
935
+
912
936
  /**
913
937
  * Tracks the `webpackIgnore` value from the most recent comment that
914
938
  * appears before the next tag. Reset whenever a tag is emitted or a
@@ -920,41 +944,39 @@ class HtmlParser extends Parser {
920
944
  const magicCommentContext = this.magicCommentContext;
921
945
 
922
946
  /**
923
- * @param {import("./syntax").HtmlAttribute | undefined} typeAttr type attribute
947
+ * @param {import("./syntax").HtmlPath} path walk path (used only for attribute-ref reads)
948
+ * @param {import("./syntax").HtmlAttributeRef} typeAttr type attribute ref (0 = none)
924
949
  * @param {number} nameEnd end offset of the tag name
925
950
  * @param {string} type type of the script
926
951
  * @param {string} input source string
927
952
  */
928
- const reconcileScriptTypeAttr = (typeAttr, nameEnd, type, input) => {
953
+ const reconcileScriptTypeAttr = (path, typeAttr, nameEnd, type, input) => {
954
+ const valueStart =
955
+ typeAttr !== 0 ? path.attributeValueStart(typeAttr) : -1;
956
+ const valueEnd = typeAttr !== 0 ? path.attributeValueEnd(typeAttr) : -1;
929
957
  if (outputModule && type === "script") {
930
958
  // Chunk is an ES module; upgrade the tag.
931
- if (typeAttr && typeAttr.valueStart !== -1) {
959
+ if (typeAttr !== 0 && valueStart !== -1) {
932
960
  module.addPresentationalDependency(
933
- new ConstDependency("module", [
934
- typeAttr.valueStart,
935
- typeAttr.valueEnd
936
- ])
961
+ new ConstDependency("module", [valueStart, valueEnd])
937
962
  );
938
963
  } else {
939
964
  module.addPresentationalDependency(
940
965
  new ConstDependency(' type="module"', nameEnd)
941
966
  );
942
967
  }
943
- } else if (!outputModule && type === "script-module" && typeAttr) {
968
+ } else if (!outputModule && type === "script-module" && typeAttr !== 0) {
944
969
  // Chunk is a classic IIFE; drop `type="module"` so the
945
970
  // browser doesn't load it under module semantics.
946
971
  let attrEnd;
947
- if (typeAttr.valueStart === -1) {
948
- attrEnd = typeAttr.nameEnd;
949
- } else if (
950
- input[typeAttr.valueEnd] === '"' ||
951
- input[typeAttr.valueEnd] === "'"
952
- ) {
953
- attrEnd = typeAttr.valueEnd + 1;
972
+ if (valueStart === -1) {
973
+ attrEnd = path.attributeNameEnd(typeAttr);
974
+ } else if (input[valueEnd] === '"' || input[valueEnd] === "'") {
975
+ attrEnd = valueEnd + 1;
954
976
  } else {
955
- attrEnd = typeAttr.valueEnd;
977
+ attrEnd = valueEnd;
956
978
  }
957
- let attrStart = typeAttr.nameStart;
979
+ let attrStart = path.attributeNameStart(typeAttr);
958
980
  if (
959
981
  attrStart > 0 &&
960
982
  isASCIIWhitespace(input.charCodeAt(attrStart - 1))
@@ -986,29 +1008,102 @@ class HtmlParser extends Parser {
986
1008
  dep.setLoc(s.line, s.column, e.line, e.column);
987
1009
  };
988
1010
 
1011
+ /**
1012
+ * Classifies the first `<base href>` seen in the walk, setting
1013
+ * `documentBase`/`baseDir`/`baseIsExternal`/`baseUrlPrefix` for the URLs
1014
+ * that follow it.
1015
+ * @param {import("./syntax").HtmlPath} path walk path (for attribute reads)
1016
+ * @param {import("./syntax").HtmlAttributeRef} hrefAttr the base's href attribute
1017
+ */
1018
+ const resolveDocumentBase = (path, hrefAttr) => {
1019
+ documentBase = decodeHtmlEntities(
1020
+ path.attributeValue(hrefAttr),
1021
+ true
1022
+ ).trim();
1023
+ if (!documentBase) return;
1024
+ if (
1025
+ ABSOLUTE_URL_SCHEME_REGEXP.test(documentBase) ||
1026
+ documentBase.charCodeAt(0) === CC_SLASH
1027
+ ) {
1028
+ baseIsExternal = true;
1029
+ return;
1030
+ }
1031
+ // Normalize the base path into its descending directory segments,
1032
+ // resolving `.`/`..` like the URL spec. The final segment is the
1033
+ // referenced file (dropped) unless the href ends with `/` or a
1034
+ // dot-segment; `up` counts `..`s that climb above the document dir.
1035
+ const parts = documentBase.split("/");
1036
+ /** @type {string[]} */
1037
+ const dirs = [];
1038
+ let up = 0;
1039
+ for (let i = 0; i < parts.length; i++) {
1040
+ const part = parts[i];
1041
+ if (part === "" || part === ".") continue;
1042
+ if (part === "..") {
1043
+ if (dirs.length > 0) dirs.pop();
1044
+ else up++;
1045
+ continue;
1046
+ }
1047
+ // A trailing plain segment (no following `/`) is the file, not a dir.
1048
+ if (i === parts.length - 1) continue;
1049
+ dirs.push(part);
1050
+ }
1051
+ baseDir = `${"../".repeat(up)}${dirs.length > 0 ? `${dirs.join("/")}/` : ""}`;
1052
+ // A base above the document dir can't be cancelled with `../` (the
1053
+ // document's own dir name is unknown) — leave the output URLs
1054
+ // un-prefixed; they still resolve under an absolute publicPath.
1055
+ if (up === 0 && dirs.length > 0) {
1056
+ baseUrlPrefix = "../".repeat(dirs.length);
1057
+ }
1058
+ };
1059
+
1060
+ // Lazy decoded-attribute map for the current element — parse-scoped
1061
+ // to avoid a closure and memo slot per element.
1062
+ /** @type {import("./syntax").HtmlPath | undefined} */
1063
+ let attrMapPath;
1064
+ let attrMapCount = 0;
1065
+ /** @type {Map<string, string> | undefined} */
1066
+ let currentAttributesMap;
1067
+ const getAttributesMap = () => {
1068
+ if (currentAttributesMap) return currentAttributesMap;
1069
+ currentAttributesMap = new Map();
1070
+ const path = /** @type {import("./syntax").HtmlPath} */ (attrMapPath);
1071
+ for (let i = 0; i < attrMapCount; i++) {
1072
+ const attr = path.attributeAt(i);
1073
+ // Decoded values — filters and type resolvers compare what
1074
+ // the browser sees (e.g. `rel="&#105;con"` means `icon`)
1075
+ currentAttributesMap.set(
1076
+ path.attributeName(attr),
1077
+ decodeHtmlEntities(path.attributeValue(attr), true)
1078
+ );
1079
+ }
1080
+ return currentAttributesMap;
1081
+ };
1082
+
989
1083
  // TODO implement full HTML parser (WASM)
990
1084
  // The walker descends into children (and `<template>` content) itself;
991
1085
  // the Element `exit` clears a pending `webpackIgnore` once an element's
992
1086
  // children are done (the old `walkChildren` behaviour).
993
1087
  new SourceProcessor()
994
1088
  .use({
995
- [NodeType.Comment]: (n) => {
996
- const node = /** @type {import("./syntax").HtmlComment} */ (n);
1089
+ [NodeType.Comment]: (path) => {
1090
+ const start = path.start();
1091
+ const end = path.end();
997
1092
  // Only proper `<!-- ... -->` comments carry magic comments.
998
1093
  if (
999
- node.end - node.start < 7 ||
1000
- source.charCodeAt(node.start) !== 0x3c ||
1001
- source.charCodeAt(node.start + 1) !== 0x21 ||
1002
- source.charCodeAt(node.start + 2) !== 0x2d ||
1003
- source.charCodeAt(node.start + 3) !== 0x2d ||
1004
- source.charCodeAt(node.end - 1) !== 0x3e ||
1005
- source.charCodeAt(node.end - 2) !== 0x2d ||
1006
- source.charCodeAt(node.end - 3) !== 0x2d
1094
+ end - start < 7 ||
1095
+ source.charCodeAt(start) !== 0x3c ||
1096
+ source.charCodeAt(start + 1) !== 0x21 ||
1097
+ source.charCodeAt(start + 2) !== 0x2d ||
1098
+ source.charCodeAt(start + 3) !== 0x2d ||
1099
+ source.charCodeAt(end - 1) !== 0x3e ||
1100
+ source.charCodeAt(end - 2) !== 0x2d ||
1101
+ source.charCodeAt(end - 3) !== 0x2d
1007
1102
  ) {
1008
1103
  pendingWebpackIgnore = undefined;
1009
1104
  return;
1010
1105
  }
1011
- const value = node.data;
1106
+ const value = path.data();
1012
1107
  if (!webpackCommentRegExp.test(value)) {
1013
1108
  pendingWebpackIgnore = undefined;
1014
1109
  return;
@@ -1018,8 +1113,8 @@ class HtmlParser extends Parser {
1018
1113
  try {
1019
1114
  options = parseMagicComment(value, magicCommentContext);
1020
1115
  } catch (err) {
1021
- const { line: sl, column: sc } = locConverter.get(node.start);
1022
- const { line: el, column: ec } = locConverter.get(node.end);
1116
+ const { line: sl, column: sc } = locConverter.get(start);
1117
+ const { line: el, column: ec } = locConverter.get(end);
1023
1118
  module.addWarning(
1024
1119
  new CommentCompilationWarning(
1025
1120
  `Compilation error while processing magic comment(-s): /*${value}*/: ${
@@ -1039,8 +1134,8 @@ class HtmlParser extends Parser {
1039
1134
  return;
1040
1135
  }
1041
1136
  if (typeof options.webpackIgnore !== "boolean") {
1042
- const { line: sl, column: sc } = locConverter.get(node.start);
1043
- const { line: el, column: ec } = locConverter.get(node.end);
1137
+ const { line: sl, column: sc } = locConverter.get(start);
1138
+ const { line: el, column: ec } = locConverter.get(end);
1044
1139
  module.addWarning(
1045
1140
  new UnsupportedFeatureWarning(
1046
1141
  `\`webpackIgnore\` expected a boolean, but received: ${options.webpackIgnore}.`,
@@ -1062,8 +1157,7 @@ class HtmlParser extends Parser {
1062
1157
  // Children (and `<template>` content) are walked by the
1063
1158
  // processor after `enter`; `exit` clears a pending
1064
1159
  // `webpackIgnore` once they're done.
1065
- enter: (n) => {
1066
- const node = /** @type {import("./syntax").HtmlElement} */ (n);
1160
+ enter: (path) => {
1067
1161
  const ignore = pendingWebpackIgnore === true;
1068
1162
  pendingWebpackIgnore = undefined;
1069
1163
 
@@ -1071,24 +1165,26 @@ class HtmlParser extends Parser {
1071
1165
  return;
1072
1166
  }
1073
1167
 
1074
- const elementName = node.tagName;
1075
- const attrs = node.attributes;
1076
-
1077
- /** @type {Map<string, string> | undefined} */
1078
- let attributesMap;
1079
- const getAttributesMap = () => {
1080
- if (attributesMap) return attributesMap;
1081
- attributesMap = new Map();
1082
- for (const attr of attrs) {
1083
- // Decoded values — filters and type resolvers compare what
1084
- // the browser sees (e.g. `rel="&#105;con"` means `icon`)
1085
- attributesMap.set(
1086
- attr.name,
1087
- decodeHtmlEntities(attr.value, true)
1088
- );
1089
- }
1090
- return attributesMap;
1091
- };
1168
+ const elementName = path.tagName();
1169
+ const attributeCount = path.attributeCount();
1170
+ const elementStart = path.start();
1171
+ const tagEnd = path.tagEnd();
1172
+ const nameEnd = path.nameEnd();
1173
+
1174
+ // The first `<base href>` freezes the base for later URLs.
1175
+ if (
1176
+ documentBase === undefined &&
1177
+ elementName === "base" &&
1178
+ path.namespace() === NS_HTML
1179
+ ) {
1180
+ const hrefAttr = path.findAttribute("href");
1181
+ if (hrefAttr !== 0) resolveDocumentBase(path, hrefAttr);
1182
+ }
1183
+
1184
+ // Rebind the parse-scoped lazy attribute map to this element.
1185
+ attrMapPath = path;
1186
+ attrMapCount = attributeCount;
1187
+ currentAttributesMap = undefined;
1092
1188
 
1093
1189
  // Each matched attribute is a source; the element body (inline
1094
1190
  // `<style>`/`<script>`) is one more — content rather than an attribute.
@@ -1097,10 +1193,10 @@ class HtmlParser extends Parser {
1097
1193
  this.sourcesByTag[elementName] || this.sourcesByTag[ANY_TAG];
1098
1194
  const bodyItem = CONTENT_SOURCES[elementName];
1099
1195
  // Iterate the attributes, then one extra step for the element
1100
- // body (`i === attrs.length`); `attrs[i]` is only read on the
1101
- // attribute steps, so the access stays in bounds.
1102
- for (let i = 0; i < attrs.length + 1; i++) {
1103
- const content = i === attrs.length;
1196
+ // body (`i === attributeCount`); the attribute ref is only read on the
1197
+ // attribute steps, so it stays in bounds.
1198
+ for (let i = 0; i < attributeCount + 1; i++) {
1199
+ const content = i === attributeCount;
1104
1200
  /** @type {SourceType} */
1105
1201
  let type;
1106
1202
  /** @type {string} */
@@ -1109,23 +1205,18 @@ class HtmlParser extends Parser {
1109
1205
  let start;
1110
1206
  /** @type {number} */
1111
1207
  let end;
1112
- /** @type {import("./syntax").HtmlAttribute | undefined} */
1113
- let attr;
1208
+ /** @type {import("./syntax").HtmlAttributeRef} */
1209
+ let attr = 0;
1114
1210
  if (content) {
1115
1211
  if (!bodyItem) continue;
1116
1212
  if (bodyItem.filter && !bodyItem.filter(getAttributesMap())) {
1117
1213
  continue;
1118
1214
  }
1119
- /** @type {number | undefined} */
1120
- let bodyStart;
1121
- let bodyEnd = node.tagEnd;
1122
- for (const child of node.children) {
1123
- if (child.type === NodeType.Text) {
1124
- if (bodyStart === undefined) bodyStart = child.start;
1125
- bodyEnd = child.end;
1126
- }
1127
- }
1128
- if (bodyStart === undefined) continue;
1215
+ // `skip.text` drops the body `Text` node; the raw content
1216
+ // span is [`tagEnd`, `contentEnd`] on the element.
1217
+ const bodyStart = tagEnd;
1218
+ const bodyEnd = path.contentEnd();
1219
+ if (bodyEnd <= bodyStart) continue;
1129
1220
  value = source.slice(bodyStart, bodyEnd);
1130
1221
  if (value.trim() === "") continue;
1131
1222
  start = bodyStart;
@@ -1135,20 +1226,20 @@ class HtmlParser extends Parser {
1135
1226
  ? bodyItem.type(getAttributesMap(), css)
1136
1227
  : bodyItem.type;
1137
1228
  } else {
1138
- attr = attrs[i];
1139
- const item = sources[attr.name];
1229
+ attr = path.attributeAt(i);
1230
+ const item = sources[path.attributeName(attr)];
1140
1231
  if (!item) continue;
1141
1232
  if (
1142
1233
  item.namespace !== undefined &&
1143
- node.namespace !== item.namespace
1234
+ path.namespace() !== item.namespace
1144
1235
  ) {
1145
1236
  continue;
1146
1237
  }
1147
1238
  // Adoption-agency clones carry no offsets; skip blank values.
1148
- if (attr.valueStart === undefined || attr.valueStart === -1) {
1239
+ if (path.attributeValueStart(attr) === -1) {
1149
1240
  continue;
1150
1241
  }
1151
- value = attr.value;
1242
+ value = path.attributeValue(attr);
1152
1243
  if (!value || !/\S/.test(value)) continue;
1153
1244
  const filter = item.filter;
1154
1245
  if (filter) {
@@ -1159,8 +1250,8 @@ class HtmlParser extends Parser {
1159
1250
  : value;
1160
1251
  if (!filter(getAttributesMap(), decoded)) continue;
1161
1252
  }
1162
- start = attr.valueStart;
1163
- end = attr.valueEnd;
1253
+ start = path.attributeValueStart(attr);
1254
+ end = path.attributeValueEnd(attr);
1164
1255
  type =
1165
1256
  typeof item.type === "function"
1166
1257
  ? item.type(getAttributesMap(), css)
@@ -1225,7 +1316,7 @@ class HtmlParser extends Parser {
1225
1316
  const entryName = `__html_${moduleHash}_${nextEntryIndex++}`;
1226
1317
  const dep = new HtmlInlineScriptDependency(
1227
1318
  request,
1228
- node.nameEnd,
1319
+ nameEnd,
1229
1320
  [start, end],
1230
1321
  entryName,
1231
1322
  scriptType === "script-module" ? "esm" : "commonjs"
@@ -1233,8 +1324,9 @@ class HtmlParser extends Parser {
1233
1324
  setLoc(dep, start, end);
1234
1325
  module.addPresentationalDependency(dep);
1235
1326
  reconcileScriptTypeAttr(
1236
- findAttr(attrs, "type"),
1237
- node.nameEnd,
1327
+ path,
1328
+ path.findAttribute("type"),
1329
+ nameEnd,
1238
1330
  scriptType,
1239
1331
  source
1240
1332
  );
@@ -1258,9 +1350,6 @@ class HtmlParser extends Parser {
1258
1350
  // Parse the value into one or more URLs (decoding character
1259
1351
  // references, mapping spans back to raw offsets), then emit a plain
1260
1352
  // asset reference or an entry chunk per URL.
1261
- const a = /** @type {import("./syntax").HtmlAttribute} */ (
1262
- attr
1263
- );
1264
1353
  const parse =
1265
1354
  type === "srcset"
1266
1355
  ? parseSrcset
@@ -1287,9 +1376,9 @@ class HtmlParser extends Parser {
1287
1376
  new ModuleDependencyError(
1288
1377
  module,
1289
1378
  new WebpackError(
1290
- `Bad value for attribute "${
1291
- a.name
1292
- }" on element "${elementName}": ${
1379
+ `Bad value for attribute "${path.attributeName(
1380
+ attr
1381
+ )}" on element "${elementName}": ${
1293
1382
  /** @type {Error} */ (err).message
1294
1383
  }`
1295
1384
  ),
@@ -1305,6 +1394,18 @@ class HtmlParser extends Parser {
1305
1394
  for (const [url, us, ue] of parsed) {
1306
1395
  // Internal `url(#id)` / fragment-only refs aren't assets.
1307
1396
  if (!url || url.startsWith("#")) continue;
1397
+ // Resolve the request against `<base href>`. Only relative
1398
+ // URLs are affected (scheme / `//` / `/` URLs ignore the
1399
+ // base); an external base drops them from the build.
1400
+ let request = url;
1401
+ if (
1402
+ documentBase &&
1403
+ url.charCodeAt(0) !== CC_SLASH &&
1404
+ !ABSOLUTE_URL_SCHEME_REGEXP.test(url)
1405
+ ) {
1406
+ if (baseIsExternal) continue;
1407
+ request = baseDir + url;
1408
+ }
1308
1409
  const s = start + (map ? map[us] : us);
1309
1410
  const e = start + (map ? map[ue] : ue);
1310
1411
  // `src`/`srcset`/`css-url`/`msapplication-task` are plain assets;
@@ -1345,11 +1446,12 @@ class HtmlParser extends Parser {
1345
1446
  // CSP/fetch attributes the template copies onto siblings.
1346
1447
  let copyableAttrsText = "";
1347
1448
  let hasOwnCrossOrigin = false;
1348
- if (node.start >= 0) {
1449
+ if (elementStart >= 0) {
1349
1450
  for (const copyableName of COPYABLE_SIBLING_ATTRS) {
1350
- const copyableAttr = findAttr(attrs, copyableName);
1351
- if (copyableAttr) {
1451
+ const copyableAttr = path.findAttribute(copyableName);
1452
+ if (copyableAttr !== 0) {
1352
1453
  copyableAttrsText += attrSourceSpan(
1454
+ path,
1353
1455
  source,
1354
1456
  copyableAttr
1355
1457
  );
@@ -1363,7 +1465,7 @@ class HtmlParser extends Parser {
1363
1465
  firstScriptStart === -1 &&
1364
1466
  (type === "script" || type === "script-module")
1365
1467
  ) {
1366
- firstScriptStart = node.start;
1468
+ firstScriptStart = elementStart;
1367
1469
  }
1368
1470
  // Sibling-clone attribute edits, captured from the parsed
1369
1471
  // attributes (offsets relative to the tag) so the template
@@ -1374,10 +1476,13 @@ class HtmlParser extends Parser {
1374
1476
  let integrityRange = null;
1375
1477
  /** @type {Range | null} */
1376
1478
  let typeValueRange = null;
1377
- if (node.start >= 0 && elementName === nativeTag) {
1378
- const integrityAttr = findAttr(attrs, "integrity");
1379
- if (integrityAttr && integrityAttr.nameStart >= 0) {
1380
- let rs = integrityAttr.nameStart;
1479
+ if (elementStart >= 0 && elementName === nativeTag) {
1480
+ const integrityAttr = path.findAttribute("integrity");
1481
+ if (
1482
+ integrityAttr !== 0 &&
1483
+ path.attributeNameStart(integrityAttr) >= 0
1484
+ ) {
1485
+ let rs = path.attributeNameStart(integrityAttr);
1381
1486
  while (
1382
1487
  rs > 0 &&
1383
1488
  isASCIIWhitespace(source.charCodeAt(rs - 1))
@@ -1385,40 +1490,46 @@ class HtmlParser extends Parser {
1385
1490
  rs--;
1386
1491
  }
1387
1492
  let re;
1388
- if (integrityAttr.valueStart === -1) {
1389
- re = integrityAttr.nameEnd;
1493
+ const ivs = path.attributeValueStart(integrityAttr);
1494
+ if (ivs === -1) {
1495
+ re = path.attributeNameEnd(integrityAttr);
1390
1496
  } else {
1391
- const q = source.charCodeAt(
1392
- integrityAttr.valueStart - 1
1393
- );
1497
+ const q = source.charCodeAt(ivs - 1);
1498
+ const ive = path.attributeValueEnd(integrityAttr);
1394
1499
  re =
1395
1500
  q === CC_QUOTATION || q === CC_APOSTROPHE
1396
- ? integrityAttr.valueEnd + 1
1397
- : integrityAttr.valueEnd;
1501
+ ? ive + 1
1502
+ : ive;
1398
1503
  }
1399
- integrityRange = [rs - node.start, re - node.start];
1504
+ integrityRange = [
1505
+ rs - elementStart,
1506
+ re - elementStart
1507
+ ];
1400
1508
  }
1401
1509
  if (elementKind === "script-module") {
1402
- const typeAttr = findAttr(attrs, "type");
1403
- if (typeAttr && typeAttr.valueStart >= 0) {
1510
+ const typeAttr = path.findAttribute("type");
1511
+ if (
1512
+ typeAttr !== 0 &&
1513
+ path.attributeValueStart(typeAttr) >= 0
1514
+ ) {
1404
1515
  typeValueRange = [
1405
- typeAttr.valueStart - node.start,
1406
- typeAttr.valueEnd - node.start
1516
+ path.attributeValueStart(typeAttr) - elementStart,
1517
+ path.attributeValueEnd(typeAttr) - elementStart
1407
1518
  ];
1408
1519
  }
1409
1520
  }
1410
1521
  }
1411
1522
  const dep = new HtmlEntryDependency(
1412
- url,
1523
+ request,
1413
1524
  [s, e],
1414
1525
  entryName,
1415
1526
  entryCategory,
1416
1527
  elementKind,
1417
- node.start,
1418
- node.tagEnd,
1528
+ elementStart,
1529
+ tagEnd,
1419
1530
  elementName === nativeTag,
1420
1531
  copyableAttrsText,
1421
- node.nameEnd,
1532
+ nameEnd,
1422
1533
  hasOwnCrossOrigin,
1423
1534
  firstScriptStart,
1424
1535
  integrityRange,
@@ -1431,8 +1542,9 @@ class HtmlParser extends Parser {
1431
1542
  (type === "script" || type === "script-module")
1432
1543
  ) {
1433
1544
  reconcileScriptTypeAttr(
1434
- findAttr(attrs, "type"),
1435
- node.nameEnd,
1545
+ path,
1546
+ path.findAttribute("type"),
1547
+ nameEnd,
1436
1548
  type,
1437
1549
  source
1438
1550
  );
@@ -1444,9 +1556,9 @@ class HtmlParser extends Parser {
1444
1556
  : type === "stylesheet"
1445
1557
  ? stylesheetEntries
1446
1558
  : modulePreloadEntries
1447
- ).push({ request: url, entryName, type });
1559
+ ).push({ request, entryName, type });
1448
1560
  } else {
1449
- const dep = new HtmlSourceDependency(url, [s, e]);
1561
+ const dep = new HtmlSourceDependency(request, [s, e]);
1450
1562
  setLoc(dep, s, e);
1451
1563
  module.addDependency(dep);
1452
1564
  module.addCodeGenerationDependency(dep);
@@ -1462,10 +1574,14 @@ class HtmlParser extends Parser {
1462
1574
  }
1463
1575
  }
1464
1576
  })
1465
- .process(source, { ast: buildHtmlAst(source) });
1577
+ // Skip AST output this walk never reads: `text` (script/style bodies are
1578
+ // read by offset via `contentEnd`) and `doctype`; keep `comments` for
1579
+ // magic comments (`webpackIgnore`).
1580
+ .process(source, { skip: { text: true, doctype: true } });
1466
1581
 
1467
1582
  const buildInfo = /** @type {HtmlModuleBuildInfo} */ (module.buildInfo);
1468
1583
  buildInfo.strict = true;
1584
+ if (baseUrlPrefix !== undefined) buildInfo.baseUrlPrefix = baseUrlPrefix;
1469
1585
  // Hand off the collected entries to HtmlModulesPlugin; it creates the
1470
1586
  // real compilation entries during the finishMake hook. The `script`
1471
1587
  // and `script-module` groups are chained via a leader-only dependOn