source: frontend/node_modules/webpack/lib/html/HtmlParser.js

Last change on this file was 9af201e, checked in by MBK <marija.karapandzova@…>, 11 days ago

Fix frontend appearance

  • Property mode set to 100644
File size: 44.0 KB
Line 
1/*
2 MIT License http://www.opensource.org/licenses/mit-license.php
3*/
4
5"use strict";
6
7const vm = require("vm");
8const Parser = require("../Parser");
9const ConstDependency = require("../dependencies/ConstDependency");
10const HtmlInlineScriptDependency = require("../dependencies/HtmlInlineScriptDependency");
11const HtmlInlineStyleDependency = require("../dependencies/HtmlInlineStyleDependency");
12const HtmlScriptSrcDependency = require("../dependencies/HtmlScriptSrcDependency");
13const HtmlSourceDependency = require("../dependencies/HtmlSourceDependency");
14const StaticExportsDependency = require("../dependencies/StaticExportsDependency");
15const CommentCompilationWarning = require("../errors/CommentCompilationWarning");
16const ModuleDependencyError = require("../errors/ModuleDependencyError");
17const UnsupportedFeatureWarning = require("../errors/UnsupportedFeatureWarning");
18const WebpackError = require("../errors/WebpackError");
19const LocConverter = require("../util/LocConverter");
20const createHash = require("../util/createHash");
21const { contextify } = require("../util/identifier");
22const {
23 createMagicCommentContext,
24 webpackCommentRegExp
25} = require("../util/magicComment");
26const walkHtmlTokens = require("./walkHtmlTokens");
27
28/** @typedef {import("../Module").BuildInfo} BuildInfo */
29/** @typedef {import("../Module").BuildMeta} BuildMeta */
30/** @typedef {import("../Parser").ParserState} ParserState */
31/** @typedef {import("../Parser").PreparsedAst} PreparsedAst */
32
33const HORIZONTAL_TAB = "\u0009".charCodeAt(0);
34const NEWLINE = "\u000A".charCodeAt(0);
35const FORM_FEED = "\u000C".charCodeAt(0);
36const CARRIAGE_RETURN = "\u000D".charCodeAt(0);
37const SPACE = "\u0020".charCodeAt(0);
38const COMMA = ",".charCodeAt(0);
39const LEFT_PARENTHESIS = "(".charCodeAt(0);
40const RIGHT_PARENTHESIS = ")".charCodeAt(0);
41const SMALL_LETTER_W = "w".charCodeAt(0);
42const SMALL_LETTER_X = "x".charCodeAt(0);
43const SMALL_LETTER_H = "h".charCodeAt(0);
44
45/**
46 * @param {number} char char
47 * @returns {boolean} true when ASCII whitespace, otherwise false
48 */
49function isASCIIWhitespace(char) {
50 return (
51 // Horizontal tab
52 char === HORIZONTAL_TAB ||
53 // New line
54 char === NEWLINE ||
55 // Form feed
56 char === FORM_FEED ||
57 // Carriage return
58 char === CARRIAGE_RETURN ||
59 // Space
60 char === SPACE
61 );
62}
63
64/** @typedef {[string, number, number]} ParsedSource */
65
66// eslint-disable-next-line no-control-regex
67const IGNORE_CHARS_REGEXP = /[\u0000-\u001F\u007F-\u009F\u00A0]/g;
68
69/**
70 * @param {string} input input
71 * @returns {ParsedSource[]} parsed src
72 */
73const parseSrc = (input) => {
74 const len = input.length;
75 if (len === 0) throw new Error("Must be non-empty");
76
77 let start = 0;
78 let end = len;
79
80 while (start < end) {
81 const code = input.charCodeAt(start);
82 if (code > 32 && code !== 160) break;
83 start++;
84 }
85
86 if (start === end) throw new Error("Must be non-empty");
87
88 while (end > start) {
89 const code = input.charCodeAt(end - 1);
90 if (code > 32 && code !== 160) break;
91 end--;
92 }
93
94 let value = input.slice(start, end);
95
96 if (IGNORE_CHARS_REGEXP.test(value)) {
97 value = value.replace(IGNORE_CHARS_REGEXP, "");
98 if (value.length === 0) throw new Error("Must be non-empty");
99 }
100
101 return [[value, start, end]];
102};
103
104// HTML `<style>` content is rawtext: it ends at the first `</style>`
105// where the tag name is followed by whitespace, `>` or `/`. The
106// lookahead (rather than a consuming character class) keeps the match
107// from running past the first `>` into a later tag — the
108// `[^>]*` only consumes any (rarely-seen) end-tag attributes before the
109// closing `>` of the end tag itself.
110const STYLE_END_REGEXP = /<\/style(?=[\s/>])[^>]*>/gi;
111
112// (Don't use \s, to avoid matching non-breaking space)
113// eslint-disable-next-line no-control-regex
114const LEADING_SPACES_REGEXP = /^[ \t\n\r\u000C]+/;
115// eslint-disable-next-line no-control-regex
116const LEADING_COMMAS_OR_SPACES_REGEXP = /^[, \t\n\r\u000C]+/;
117// eslint-disable-next-line no-control-regex
118const LEADING_NOT_SPACES = /^[^ \t\n\r\u000C]+/;
119const TRAILING_COMMAS_REGEXP = /[,]+$/;
120const NON_NEGATIVE_INTEGER_REGEXP = /^\d+$/;
121// ( Positive or negative or unsigned integers or decimals, without or without exponents.
122// Must include at least one digit.
123// According to spec tests any decimal point must be followed by a digit.
124// No leading plus sign is allowed.)
125// https://html.spec.whatwg.org/multipage/infrastructure.html#valid-floating-point-number
126const FLOATING_POINT_REGEXP =
127 /^-?(?:[0-9]+|[0-9]*\.[0-9]+)(?:[eE][+-]?[0-9]+)?$/;
128
129/**
130 * @param {string} input input
131 * @returns {ParsedSource[]} parsed srcset
132 */
133const parseSrcset = (input) => {
134 // 1. Let input be the value passed to this algorithm.
135 const inputLength = input.length;
136
137 /** @type {string | undefined} */
138 let url;
139 /** @type {string[]} */
140 let descriptors;
141 /** @type {string} */
142 let currentDescriptor;
143 /** @type {string} */
144 let state;
145 /** @type {number} */
146 let charCode;
147 /** @type {number} */
148 let position = 0;
149 /** @type {number} */
150 let start;
151
152 /** @type {[string, number, number][]} */
153 const candidates = [];
154
155 /**
156 * @param {RegExp} regExp reg exp to collect characters
157 * @returns {string | undefined} characters
158 */
159 function collectCharacters(regExp) {
160 /** @type {string} */
161 let chars;
162 const match = regExp.exec(input.slice(Math.max(0, position)));
163
164 if (match) {
165 [chars] = match;
166 position += chars.length;
167
168 return chars;
169 }
170 }
171
172 /**
173 * @returns {void}
174 */
175 function parseDescriptors() {
176 // 9. Descriptor parser: Let error be no.
177 let pError = false;
178
179 // 10. Let width be absent.
180 // 11. Let density be absent.
181 // 12. Let future-compat-h be absent. (We're implementing it now as h)
182 /** @type {number | undefined} */
183 let width;
184 /** @type {number | undefined} */
185 let density;
186 /** @type {number | undefined} */
187 let height;
188 /** @type {string | undefined} */
189 let desc;
190
191 // 13. For each descriptor in descriptors, run the appropriate set of steps
192 // from the following list:
193 for (let i = 0; i < descriptors.length; i++) {
194 desc = descriptors[i];
195
196 const lastChar = desc[desc.length - 1].charCodeAt(0);
197 const value = desc.slice(0, Math.max(0, desc.length - 1));
198
199 // If the descriptor consists of a valid non-negative integer followed by
200 // a U+0077 LATIN SMALL LETTER W character
201 if (
202 NON_NEGATIVE_INTEGER_REGEXP.test(value) &&
203 lastChar === SMALL_LETTER_W
204 ) {
205 // If width and density are not both absent, then let error be yes.
206 if (width || density) {
207 pError = true;
208 }
209
210 const intVal = Number.parseInt(value, 10);
211
212 // Apply the rules for parsing non-negative integers to the descriptor.
213 // If the result is zero, let error be yes.
214 // Otherwise, let width be the result.
215 if (intVal === 0) {
216 pError = true;
217 } else {
218 width = intVal;
219 }
220 }
221 // If the descriptor consists of a valid floating-point number followed by
222 // a U+0078 LATIN SMALL LETTER X character
223 else if (
224 FLOATING_POINT_REGEXP.test(value) &&
225 lastChar === SMALL_LETTER_X
226 ) {
227 // If width, density and future-compat-h are not all absent, then let error
228 // be yes.
229 if (width || density || height) {
230 pError = true;
231 }
232
233 const floatVal = Number.parseFloat(value);
234
235 // Apply the rules for parsing floating-point number values to the descriptor.
236 // If the result is less than zero, let error be yes. Otherwise, let density
237 // be the result.
238 if (floatVal < 0) {
239 pError = true;
240 } else {
241 density = floatVal;
242 }
243 }
244 // If the descriptor consists of a valid non-negative integer followed by
245 // a U+0068 LATIN SMALL LETTER H character
246 else if (
247 NON_NEGATIVE_INTEGER_REGEXP.test(value) &&
248 lastChar === SMALL_LETTER_H
249 ) {
250 // If height and density are not both absent, then let error be yes.
251 if (height || density) {
252 pError = true;
253 }
254
255 const intVal = Number.parseInt(value, 10);
256
257 // Apply the rules for parsing non-negative integers to the descriptor.
258 // If the result is zero, let error be yes. Otherwise, let future-compat-h
259 // be the result.
260 if (intVal === 0) {
261 pError = true;
262 } else {
263 height = intVal;
264 }
265
266 // Anything else, Let error be yes.
267 } else {
268 pError = true;
269 }
270 }
271
272 // 15. If error is still no, then append a new image source to candidates whose
273 // URL is url, associated with a width width if not absent and a pixel
274 // density density if not absent. Otherwise, there is a parse error.
275 if (!pError) {
276 candidates.push([
277 /** @type {string} */ (url),
278 start,
279 start + /** @type {string} */ (url).length
280 ]);
281 } else {
282 throw new Error(
283 `Invalid srcset descriptor found in '${input}' at '${desc}'`
284 );
285 }
286 }
287
288 /**
289 * @returns {void}
290 */
291 function tokenize() {
292 // 8.1. Descriptor tokenizer: Skip whitespace
293 collectCharacters(LEADING_SPACES_REGEXP);
294
295 // 8.2. Let current descriptor be the empty string.
296 currentDescriptor = "";
297
298 // 8.3. Let state be in descriptor.
299 state = "in descriptor";
300
301 while (true) {
302 // 8.4. Let charCode be the character at position.
303 charCode = input.charCodeAt(position);
304
305 // Do the following depending on the value of state.
306 // For the purpose of this step, "EOF" is a special character representing
307 // that position is past the end of input.
308
309 // In descriptor
310 if (state === "in descriptor") {
311 // Do the following, depending on the value of charCode:
312
313 // Space character
314 // If current descriptor is not empty, append current descriptor to
315 // descriptors and let current descriptor be the empty string.
316 // Set state to after descriptor.
317 if (isASCIIWhitespace(charCode)) {
318 if (currentDescriptor) {
319 descriptors.push(currentDescriptor);
320 currentDescriptor = "";
321 state = "after descriptor";
322 }
323 }
324 // U+002C COMMA (,)
325 // Advance position to the next character in input. If current descriptor
326 // is not empty, append current descriptor to descriptors. Jump to the step
327 // labeled descriptor parser.
328 else if (charCode === COMMA) {
329 position += 1;
330
331 if (currentDescriptor) {
332 descriptors.push(currentDescriptor);
333 }
334
335 parseDescriptors();
336
337 return;
338 }
339 // U+0028 LEFT PARENTHESIS (()
340 // Append charCode to current descriptor. Set state to in parens.
341 else if (charCode === LEFT_PARENTHESIS) {
342 currentDescriptor += input.charAt(position);
343 state = "in parens";
344 }
345 // EOF
346 // If current descriptor is not empty, append current descriptor to
347 // descriptors. Jump to the step labeled descriptor parser.
348 else if (Number.isNaN(charCode)) {
349 if (currentDescriptor) {
350 descriptors.push(currentDescriptor);
351 }
352
353 parseDescriptors();
354
355 return;
356
357 // Anything else
358 // Append charCode to current descriptor.
359 } else {
360 currentDescriptor += input.charAt(position);
361 }
362 }
363 // In parens
364 else if (state === "in parens") {
365 // U+0029 RIGHT PARENTHESIS ())
366 // Append charCode to current descriptor. Set state to in descriptor.
367 if (charCode === RIGHT_PARENTHESIS) {
368 currentDescriptor += input.charAt(position);
369 state = "in descriptor";
370 }
371 // EOF
372 // Append current descriptor to descriptors. Jump to the step labeled
373 // descriptor parser.
374 else if (Number.isNaN(charCode)) {
375 descriptors.push(currentDescriptor);
376 parseDescriptors();
377 return;
378 }
379 // Anything else
380 // Append charCode to current descriptor.
381 else {
382 currentDescriptor += input.charAt(position);
383 }
384 }
385 // After descriptor
386 else if (state === "after descriptor") {
387 // Do the following, depending on the value of charCode:
388 if (isASCIIWhitespace(charCode)) {
389 // Space character: Stay in this state.
390 }
391 // EOF: Jump to the step labeled descriptor parser.
392 else if (Number.isNaN(charCode)) {
393 parseDescriptors();
394 return;
395 }
396 // Anything else
397 // Set state to in descriptor. Set position to the previous character in input.
398 else {
399 state = "in descriptor";
400 position -= 1;
401 }
402 }
403
404 // Advance position to the next character in input.
405 position += 1;
406 }
407 }
408
409 // 3. Let candidates be an initially empty source set.
410 // const candidates = []; // Moved to top
411
412 // 4. Splitting loop: Collect a sequence of characters that are space
413 // characters or U+002C COMMA characters. If any U+002C COMMA characters
414 // were collected, that is a parse error.
415
416 while (true) {
417 collectCharacters(LEADING_COMMAS_OR_SPACES_REGEXP);
418
419 // 5. If position is past the end of input, return candidates and abort these steps.
420 if (position >= inputLength) {
421 if (candidates.length === 0) {
422 throw new Error("Must contain one or more image candidate strings");
423 }
424
425 // (we're done, this is the sole return path)
426 return candidates;
427 }
428
429 // 6. Collect a sequence of characters that are not space characters,
430 // and let that be url.
431 start = position;
432 url = collectCharacters(LEADING_NOT_SPACES);
433
434 // 7. Let descriptors be a new empty list.
435 descriptors = [];
436
437 // 8. If url ends with a U+002C COMMA character (,), follow these sub steps:
438 // (1). Remove all trailing U+002C COMMA characters from url. If this removed
439 // more than one character, that is a parse error.
440 if (url && url.charCodeAt(url.length - 1) === COMMA) {
441 url = url.replace(TRAILING_COMMAS_REGEXP, "");
442
443 // (Jump ahead to step 9 to skip tokenization and just push the candidate).
444 parseDescriptors();
445 }
446 // Otherwise, follow these sub steps:
447 else {
448 tokenize();
449 }
450
451 // 16. Return to the step labeled splitting loop.
452 }
453};
454
455/**
456 * @param {Map<string, string>} attributes attributes
457 * @param {string} name name
458 * @returns {string | undefined} attribute value
459 */
460const getAttributeValue = (attributes, name) => attributes.get(name);
461
462/** @type {Map<string, Set<string>>} */
463const META = new Map([
464 [
465 "name",
466 new Set([
467 // msapplication-TileImage
468 "msapplication-tileimage",
469 "msapplication-square70x70logo",
470 "msapplication-square150x150logo",
471 "msapplication-wide310x150logo",
472 "msapplication-square310x310logo",
473 "msapplication-config",
474 // TODO Do we need to parser it?
475 // "msapplication-task",
476 "twitter:image"
477 ])
478 ],
479 [
480 "property",
481 new Set([
482 "og:image",
483 "og:image:url",
484 "og:image:secure_url",
485 "og:audio",
486 "og:audio:secure_url",
487 "og:video",
488 "og:video:secure_url",
489 "vk:image"
490 ])
491 ],
492 [
493 "itemprop",
494 new Set([
495 "image",
496 "logo",
497 "screenshot",
498 "thumbnailurl",
499 "contenturl",
500 "downloadurl",
501 "duringmedia",
502 "embedurl",
503 "installurl",
504 "layoutimage"
505 ])
506 ]
507]);
508
509/**
510 * @param {Map<string, string>} attributes attributes
511 * @returns {boolean} true when need to parse, otherwise false
512 */
513const filterLinkItemprop = (attributes) => {
514 const value = getAttributeValue(attributes, "itemprop");
515 if (!value) return false;
516 const allowedAttributes = META.get("itemprop");
517 if (!allowedAttributes) return false;
518
519 return allowedAttributes.has(value.trim().toLowerCase());
520};
521
522/**
523 * @param {Map<string, string>} attributes attributes
524 * @returns {boolean} true when need to parse, otherwise false
525 */
526const filterLinkHref = (attributes) => {
527 const rel = getAttributeValue(attributes, "rel");
528 if (!rel) return false;
529 const usedRels = rel.trim().toLowerCase().split(" ").filter(Boolean);
530 const allowedRels = [
531 "stylesheet",
532 "icon",
533 "mask-icon",
534 "apple-touch-icon",
535 "apple-touch-icon-precomposed",
536 "apple-touch-startup-image",
537 "manifest",
538 "prefetch",
539 "preload",
540 "modulepreload"
541 ];
542
543 return allowedRels.some((value) => usedRels.includes(value));
544};
545
546/**
547 * @param {Map<string, string>} attributes attributes
548 * @returns {boolean} true when need to parse, otherwise false
549 */
550const filterLinkUnion = (attributes) =>
551 filterLinkHref(attributes) || filterLinkItemprop(attributes);
552
553/**
554 * @param {Map<string, string>} attributes attributes
555 * @returns {boolean} true when need to parse, otherwise false
556 */
557const filterMetaContent = (attributes) => {
558 for (const item of META) {
559 const [key, allowedNames] = item;
560 const name = getAttributeValue(attributes, key);
561 if (!name) continue;
562
563 return allowedNames.has(name.trim().toLowerCase());
564 }
565
566 return false;
567};
568
569/**
570 * @param {Map<string, string>} attributes attributes
571 * @returns {boolean} true when the script element opts into ES module semantics
572 */
573const isModuleScript = (attributes) => {
574 const type = getAttributeValue(attributes, "type");
575 if (!type) return false;
576 return type.trim().toLowerCase() === "module";
577};
578
579// HTML `<script>` `type` values that the browser treats as executable
580// JavaScript. Anything outside this set (e.g. `application/ld+json`,
581// `importmap`, `application/wasm`) is a data block — webpack must not
582// try to bundle it as a JS entry; it should pass through as an asset URL.
583const JS_SCRIPT_TYPES = new Set([
584 "",
585 "module",
586 "text/javascript",
587 "application/javascript",
588 "text/ecmascript",
589 "application/ecmascript"
590]);
591
592/**
593 * @param {Map<string, string>} attributes attributes
594 * @returns {boolean} true when the script element's `type` is executable JS
595 */
596const isExecutableJsScript = (attributes) => {
597 const type = getAttributeValue(attributes, "type");
598 if (type === undefined) return true;
599 return JS_SCRIPT_TYPES.has(type.trim().toLowerCase());
600};
601
602/**
603 * @param {Map<string, string>} attributes attributes
604 * @returns {boolean} true when the link points at an ES module that should be bundled as an entry chunk
605 */
606const isLinkModulePreload = (attributes) => {
607 const rel = getAttributeValue(attributes, "rel");
608 if (!rel) return false;
609 return rel.trim().toLowerCase().split(/\s+/).includes("modulepreload");
610};
611
612/**
613 * @param {Map<string, string>} attributes attributes
614 * @returns {boolean} true when the link is a `<link rel="stylesheet">` that should be bundled as a CSS entry chunk
615 */
616const isLinkStylesheet = (attributes) => {
617 const rel = getAttributeValue(attributes, "rel");
618 if (!rel) return false;
619 return rel.trim().toLowerCase().split(/\s+/).includes("stylesheet");
620};
621
622/** @type {Map<string, Map<string, { parse: (input: string) => ParsedSource[] | undefined, filter?: (attributes: Map<string, string>) => boolean, entry?: boolean | ((attributes: Map<string, string>) => boolean), entryCategory?: string }>>} */
623const DEFAULT_SOURCES = new Map([
624 [
625 "audio",
626 new Map([
627 [
628 "src",
629 {
630 parse: parseSrc
631 }
632 ]
633 ])
634 ],
635 [
636 "embed",
637 new Map([
638 [
639 "src",
640 {
641 parse: parseSrc
642 }
643 ]
644 ])
645 ],
646 [
647 "img",
648 new Map([
649 [
650 "src",
651 {
652 parse: parseSrc
653 }
654 ],
655 [
656 "srcset",
657 {
658 parse: parseSrcset
659 }
660 ]
661 ])
662 ],
663 [
664 "input",
665 new Map([
666 [
667 "src",
668 {
669 parse: parseSrc
670 }
671 ]
672 ])
673 ],
674 [
675 "link",
676 new Map([
677 [
678 "href",
679 {
680 parse: parseSrc,
681 filter: filterLinkUnion,
682 entry: isLinkModulePreload,
683 entryCategory: "esm"
684 }
685 ],
686 [
687 "imagesrcset",
688 {
689 parse: parseSrcset,
690 filter: filterLinkHref
691 }
692 ]
693 ])
694 ],
695 [
696 "meta",
697 new Map([
698 [
699 "content",
700 {
701 parse: parseSrc,
702 filter: filterMetaContent
703 }
704 ]
705 ])
706 ],
707 [
708 "object",
709 new Map([
710 [
711 "data",
712 {
713 parse: parseSrc
714 }
715 ]
716 ])
717 ],
718 [
719 "script",
720 new Map([
721 [
722 "src",
723 {
724 parse: parseSrc,
725 // Only executable-JS scripts become entries. Non-JS
726 // `<script>` types (e.g. `application/ld+json`,
727 // `importmap`) fall through to HtmlSourceDependency so
728 // the browser keeps seeing them as data blocks, with
729 // the asset URL rewritten like any other resource.
730 entry: isExecutableJsScript
731 }
732 ]
733 ])
734 ],
735 [
736 "source",
737 new Map([
738 [
739 "src",
740 {
741 parse: parseSrc
742 }
743 ],
744 [
745 "srcset",
746 {
747 parse: parseSrcset
748 }
749 ]
750 ])
751 ],
752 [
753 "track",
754 new Map([
755 [
756 "src",
757 {
758 parse: parseSrc
759 }
760 ]
761 ])
762 ],
763 [
764 "video",
765 new Map([
766 [
767 "poster",
768 {
769 parse: parseSrc
770 }
771 ],
772 [
773 "src",
774 {
775 parse: parseSrc
776 }
777 ]
778 ])
779 ],
780 // SVG
781 [
782 "image",
783 new Map([
784 [
785 "xlink:href",
786 {
787 parse: parseSrc
788 }
789 ],
790 [
791 "href",
792 {
793 parse: parseSrc
794 }
795 ]
796 ])
797 ],
798 [
799 "use",
800 new Map([
801 [
802 "xlink:href",
803 {
804 parse: parseSrc
805 }
806 ],
807 [
808 "href",
809 {
810 parse: parseSrc
811 }
812 ]
813 ])
814 ]
815]);
816
817class HtmlParser extends Parser {
818 /**
819 * Creates an instance of HtmlParser.
820 * @param {(string | typeof import("../util/Hash"))=} hashFunction algorithm or constructor used by `output.hashFunction`; falls back to the default when omitted
821 * @param {string=} context compilation context used to contextify the HTML module's identifier when seeding the entry-name hash
822 * @param {boolean=} outputModule whether `output.module` is enabled; when true, classic `<script src>` tags get `type="module"` auto-inserted so the rewritten src can load the emitted ES module chunk
823 * @param {boolean=} css whether `experiments.css` is enabled; when true, inline `<style>` bodies are routed through the CSS pipeline as `data:text/css` modules
824 */
825 constructor(hashFunction, context, outputModule, css) {
826 super();
827 this.magicCommentContext = createMagicCommentContext();
828 this.hashFunction = hashFunction;
829 this.context = context;
830 this.outputModule = outputModule;
831 this.css = css;
832 }
833
834 /**
835 * Parses the provided source and updates the parser state.
836 * @param {string | Buffer | PreparsedAst} source the source to parse
837 * @param {ParserState} state the parser state
838 * @returns {ParserState} the parser state
839 */
840 parse(source, state) {
841 if (Buffer.isBuffer(source)) {
842 source = source.toString("utf8");
843 } else if (typeof source === "object") {
844 throw new Error("webpackAst is unexpected for the HtmlParser");
845 }
846 if (source[0] === "\uFEFF") {
847 source = source.slice(1);
848 }
849
850 const locConverter = new LocConverter(source);
851
852 const module = state.module;
853
854 // Stable, per-HTML-module prefix used when generating entry names for
855 // script src / modulepreload references so they don't collide across
856 // HTML modules in the same compilation. We hash the module's resource
857 // path (a plain absolute path) — going through `contextify` against
858 // the compilation root keeps the hash machine-stable for the same
859 // project layout. Note: `module.identifier()` returns `html|<path>`
860 // for HTML modules, which doesn't start with `/`, so contextify would
861 // leave it absolute. `module.resource` is the bare path.
862 /** @type {string} */
863 const resource =
864 /** @type {EXPECTED_ANY} */ (module).resource || module.identifier();
865 const moduleHash = createHash(this.hashFunction || "md4")
866 .update(this.context ? contextify(this.context, resource) : resource)
867 .digest("hex")
868 .slice(0, 8);
869
870 /** @typedef {{ nameStart: number, nameEnd: number, valueStart: number, valueEnd: number }} AttrToken */
871
872 /** @type {AttrToken[]} */
873 const pendingAttributes = [];
874
875 /**
876 * Reconciles the rewritten `<script>` tag's `type` attribute with the
877 * emitted chunk's actual format. Used by both the `<script src>` and
878 * inline `<script>` paths so the two stay in sync.
879 * @param {AttrToken | undefined} typeAttr existing `type` attribute, if any
880 * @param {number} nameEnd position right after `<script` (for inserts)
881 * @param {"classic" | "esm-script"} kind chunk kind decided by the parser
882 * @param {string} input full source string
883 * @returns {void}
884 */
885 const reconcileScriptTypeAttr = (typeAttr, nameEnd, kind, input) => {
886 if (this.outputModule && kind === "classic") {
887 // Chunk is an ES module; upgrade the tag.
888 if (typeAttr && typeAttr.valueStart !== -1) {
889 module.addPresentationalDependency(
890 new ConstDependency("module", [
891 typeAttr.valueStart,
892 typeAttr.valueEnd
893 ])
894 );
895 } else {
896 module.addPresentationalDependency(
897 new ConstDependency(' type="module"', nameEnd)
898 );
899 }
900 } else if (!this.outputModule && kind === "esm-script" && typeAttr) {
901 // Chunk is a classic IIFE; drop `type="module"` so the
902 // browser doesn't load it under module semantics.
903 let attrEnd;
904 if (typeAttr.valueStart === -1) {
905 attrEnd = typeAttr.nameEnd;
906 } else if (
907 input[typeAttr.valueEnd] === '"' ||
908 input[typeAttr.valueEnd] === "'"
909 ) {
910 attrEnd = typeAttr.valueEnd + 1;
911 } else {
912 attrEnd = typeAttr.valueEnd;
913 }
914 // Consume one leading whitespace char so we don't leave a
915 // double space between `<script` and the next attribute.
916 let attrStart = typeAttr.nameStart;
917 if (
918 attrStart > 0 &&
919 isASCIIWhitespace(input.charCodeAt(attrStart - 1))
920 ) {
921 attrStart -= 1;
922 }
923 module.addPresentationalDependency(
924 new ConstDependency("", [attrStart, attrEnd])
925 );
926 }
927 };
928
929 // Inline `<script>` body extraction is deferred to the matching
930 // `closeTag` event so the walker's script-data state machine
931 // (including its escaped/double-escaped sub-states) decides where
932 // the body ends. This is the spec-compliant way to find the close
933 // — a plain `</script>` regex would split too early inside
934 // `<!--<script>…</script>-->` patterns.
935 /** @type {null | { contentStart: number, attrs: Map<string, string>, typeAttr: AttrToken | undefined, nameEnd: number }} */
936 let pendingInlineScript = null;
937
938 // Script src / modulepreload references are collected per-category
939 // during the walk; HtmlModulesPlugin later turns them into real
940 // entries. Classic <script src> and <script type="module" src> are
941 // chained via a leader-only dependOn so they share a runtime.
942 // `<link rel="modulepreload">` entries are kept independent — they
943 // must preload without running, so they can never become a runtime
944 // leader that other entries would import.
945 /**
946 * @typedef {object} EntryScriptInfo
947 * @property {string} request
948 * @property {string} entryName
949 * @property {"classic" | "esm-script" | "modulepreload" | "stylesheet"} kind
950 */
951 /** @type {EntryScriptInfo[]} */
952 const classicEntries = [];
953 /** @type {EntryScriptInfo[]} */
954 const esmScriptEntries = [];
955 /** @type {EntryScriptInfo[]} */
956 const modulePreloadEntries = [];
957 /** @type {EntryScriptInfo[]} */
958 const stylesheetEntries = [];
959
960 let nextEntryIndex = 0;
961
962 /**
963 * Tracks the `webpackIgnore` value from the most recent comment that
964 * appears before the next tag. Reset whenever a tag is emitted or a
965 * comment without a `webpackIgnore` value is encountered.
966 * @type {boolean | undefined}
967 */
968 let pendingWebpackIgnore;
969
970 const magicCommentContext = this.magicCommentContext;
971
972 // TODO implement full HTML parser (WASM)
973 walkHtmlTokens(source, 0, {
974 comment: (input, start, end) => {
975 // Only proper `<!-- ... -->` comments carry magic comments.
976 // `walkHtmlTokens` also dispatches this callback for bogus
977 // comments such as `<!DOCTYPE …>` and `<?…>`, which must not
978 // be parsed as magic comments.
979 if (
980 end - start < 7 ||
981 input.charCodeAt(start) !== 0x3c /* < */ ||
982 input.charCodeAt(start + 1) !== 0x21 /* ! */ ||
983 input.charCodeAt(start + 2) !== 0x2d /* - */ ||
984 input.charCodeAt(start + 3) !== 0x2d /* - */ ||
985 input.charCodeAt(end - 1) !== 0x3e /* > */ ||
986 input.charCodeAt(end - 2) !== 0x2d /* - */ ||
987 input.charCodeAt(end - 3) !== 0x2d /* - */
988 ) {
989 pendingWebpackIgnore = undefined;
990 return end;
991 }
992 const contentStart = start + 4;
993 const contentEnd = end - 3;
994 const value = input.slice(contentStart, contentEnd);
995 if (!webpackCommentRegExp.test(value)) {
996 pendingWebpackIgnore = undefined;
997 return end;
998 }
999 /** @type {Record<string, EXPECTED_ANY>} */
1000 let options;
1001 try {
1002 options = vm.runInContext(
1003 `(function(){return {${value}};})()`,
1004 magicCommentContext
1005 );
1006 } catch (err) {
1007 const { line: sl, column: sc } = locConverter.get(start);
1008 const { line: el, column: ec } = locConverter.get(end);
1009 module.addWarning(
1010 new CommentCompilationWarning(
1011 `Compilation error while processing magic comment(-s): /*${value}*/: ${
1012 /** @type {Error} */ (err).message
1013 }`,
1014 {
1015 start: { line: sl, column: sc },
1016 end: { line: el, column: ec }
1017 }
1018 )
1019 );
1020 pendingWebpackIgnore = undefined;
1021 return end;
1022 }
1023 if (options.webpackIgnore === undefined) {
1024 pendingWebpackIgnore = undefined;
1025 return end;
1026 }
1027 if (typeof options.webpackIgnore !== "boolean") {
1028 const { line: sl, column: sc } = locConverter.get(start);
1029 const { line: el, column: ec } = locConverter.get(end);
1030 module.addWarning(
1031 new UnsupportedFeatureWarning(
1032 `\`webpackIgnore\` expected a boolean, but received: ${options.webpackIgnore}.`,
1033 {
1034 start: { line: sl, column: sc },
1035 end: { line: el, column: ec }
1036 }
1037 )
1038 );
1039 pendingWebpackIgnore = undefined;
1040 return end;
1041 }
1042 pendingWebpackIgnore = options.webpackIgnore;
1043 return end;
1044 },
1045 attribute: (
1046 input,
1047 nameStart,
1048 nameEnd,
1049 valueStart,
1050 valueEnd,
1051 quoteType
1052 ) => {
1053 pendingAttributes.push({ nameStart, nameEnd, valueStart, valueEnd });
1054 if (valueStart === -1) return nameEnd;
1055 return quoteType !== walkHtmlTokens.QUOTE_NONE
1056 ? valueEnd + 1
1057 : valueEnd;
1058 },
1059 closeTag: (input, start, end, nameStart, nameEnd) => {
1060 pendingWebpackIgnore = undefined;
1061 if (pendingInlineScript) {
1062 const elName = input.slice(nameStart, nameEnd).toLowerCase();
1063 if (elName === "script") {
1064 const ps = pendingInlineScript;
1065 pendingInlineScript = null;
1066 const contentStart = ps.contentStart;
1067 const contentEnd = start; // start of `</script>`
1068 const jsContent = input.slice(contentStart, contentEnd);
1069 if (jsContent.trim() === "") return end;
1070
1071 // Base64-encode the JS body so the data URI round-trips
1072 // arbitrary JavaScript text, including non-ASCII source
1073 // (`decodeDataURI` decodes non-base64 bodies as ASCII,
1074 // which would corrupt Unicode string literals or
1075 // identifiers).
1076 const request = `data:text/javascript;base64,${Buffer.from(
1077 jsContent,
1078 "utf8"
1079 ).toString("base64")}`;
1080
1081 const useEsmEntry = isModuleScript(ps.attrs);
1082 const entryName = `__html_${moduleHash}_${nextEntryIndex++}`;
1083 /** @type {"classic" | "esm-script"} */
1084 const kind = useEsmEntry ? "esm-script" : "classic";
1085 const { line: sl, column: sc } = locConverter.get(contentStart);
1086 const { line: el, column: ec } = locConverter.get(contentEnd);
1087 const dep = new HtmlInlineScriptDependency(
1088 request,
1089 ps.nameEnd,
1090 [contentStart, contentEnd],
1091 entryName,
1092 useEsmEntry ? "esm" : "commonjs"
1093 );
1094 dep.setLoc(sl, sc, el, ec);
1095 module.addPresentationalDependency(dep);
1096 reconcileScriptTypeAttr(ps.typeAttr, ps.nameEnd, kind, input);
1097 const collection =
1098 kind === "classic" ? classicEntries : esmScriptEntries;
1099 collection.push({ request, entryName, kind });
1100 }
1101 }
1102 return end;
1103 },
1104 openTag: (input, start, end, nameStart, nameEnd) => {
1105 const ignore = pendingWebpackIgnore === true;
1106 pendingWebpackIgnore = undefined;
1107 if (ignore) {
1108 // For `<script>` and `<style>` we don't emit a dependency,
1109 // but we must NOT advance past the close tag either: the
1110 // walker is already in script-data/rawtext state for
1111 // those tags and will consume the body and emit the
1112 // matching `closeTag` itself. Returning a position past
1113 // `</script>`/`</style>` would leave the walker stuck in
1114 // rawtext mode, swallowing later markup.
1115 pendingAttributes.length = 0;
1116 return end;
1117 }
1118 const elementName = input.slice(nameStart, nameEnd).toLowerCase();
1119
1120 // `<style>` is rawtext: capture the inline CSS and hand it to
1121 // the CSS pipeline as a virtual `data:text/css` module with
1122 // `exportType: "text"`. We use the regex only to discover the
1123 // `</style>` position so we can slice the body; the walker is
1124 // already in rawtext state for `<style>` and will emit the
1125 // matching `closeTag` event itself, so we must return `end`
1126 // (the position right after the opening tag's `>`) rather
1127 // than advancing past `</style>`. Only `<style>` tags whose
1128 // `type` attribute is absent, empty, or `text/css` are
1129 // processed; other types are left untouched.
1130 if (elementName === "style") {
1131 /** @type {string | undefined} */
1132 let typeValue;
1133 for (const attr of pendingAttributes) {
1134 const attrName = input
1135 .slice(attr.nameStart, attr.nameEnd)
1136 .toLowerCase();
1137 if (attrName === "type") {
1138 typeValue =
1139 attr.valueStart !== -1
1140 ? input.slice(attr.valueStart, attr.valueEnd)
1141 : "";
1142 break;
1143 }
1144 }
1145 pendingAttributes.length = 0;
1146
1147 STYLE_END_REGEXP.lastIndex = end;
1148 const closeMatch = STYLE_END_REGEXP.exec(input);
1149 if (!closeMatch) return end;
1150 const contentStart = end;
1151 const contentEnd = closeMatch.index;
1152
1153 const trimmedType =
1154 typeValue !== undefined ? typeValue.trim().toLowerCase() : "";
1155 if (
1156 typeValue !== undefined &&
1157 trimmedType !== "" &&
1158 trimmedType !== "text/css"
1159 ) {
1160 return end;
1161 }
1162
1163 // Inline-style processing requires the CSS pipeline; when
1164 // `experiments.css` is off, leave the body alone — the
1165 // walker is in rawtext mode for `<style>` and will skip
1166 // over the body to the matching `</style>` itself.
1167 if (!this.css) {
1168 return end;
1169 }
1170
1171 const cssContent = input.slice(contentStart, contentEnd);
1172 if (cssContent.trim() === "") {
1173 return end;
1174 }
1175
1176 // URL-encode the CSS body so the data URI parser's `(.*)$`
1177 // body group matches even when the source has newlines or
1178 // other characters that would otherwise break the regex.
1179 const request = `data:text/css,${encodeURIComponent(cssContent)}`;
1180
1181 const { line: sl, column: sc } = locConverter.get(contentStart);
1182 const { line: el, column: ec } = locConverter.get(contentEnd);
1183 const dep = new HtmlInlineStyleDependency(request, [
1184 contentStart,
1185 contentEnd
1186 ]);
1187 dep.setLoc(sl, sc, el, ec);
1188 module.addDependency(dep);
1189 module.addCodeGenerationDependency(dep);
1190 return end;
1191 }
1192
1193 const sources = DEFAULT_SOURCES.get(elementName);
1194
1195 if (!sources) {
1196 pendingAttributes.length = 0;
1197 return end;
1198 }
1199
1200 /** @type {Map<string, string> | undefined} */
1201 let attributesMap;
1202 const getAttributesMap = () => {
1203 if (attributesMap) return attributesMap;
1204 attributesMap = new Map();
1205 for (const attr of pendingAttributes) {
1206 const name = input
1207 .slice(attr.nameStart, attr.nameEnd)
1208 .toLowerCase();
1209 const value =
1210 attr.valueStart !== -1
1211 ? input.slice(attr.valueStart, attr.valueEnd)
1212 : "";
1213 attributesMap.set(name, value);
1214 }
1215 return attributesMap;
1216 };
1217
1218 for (const attr of pendingAttributes) {
1219 const attributeName = input
1220 .slice(attr.nameStart, attr.nameEnd)
1221 .toLowerCase();
1222 const sourceItem = sources.get(attributeName);
1223
1224 if (!sourceItem) continue;
1225
1226 // TODO(html-entities): We should ideally decode entities here using
1227 // `walkHtmlTokens.decodeHtmlEntities(input.slice(...))` so that URLs
1228 // like `image.png?a=1&amp;b=2` are correctly resolved as `&`.
1229 // However, doing so currently breaks `srcset` parsing tests (e.g. `errors.js`)
1230 // which explicitly expect whitespace entities like `&#x9;` to NOT be decoded
1231 // before the srcset parser runs. A follow-up PR should implement selective
1232 // decoding for specific URL attributes.
1233 const attributeValue =
1234 attr.valueStart !== -1
1235 ? input.slice(attr.valueStart, attr.valueEnd)
1236 : "";
1237
1238 if (!attributeValue) continue;
1239
1240 if (
1241 typeof sourceItem.filter === "function" &&
1242 !sourceItem.filter(getAttributesMap())
1243 ) {
1244 continue;
1245 }
1246
1247 /** @type {ParsedSource[] | undefined} */
1248 let parsedAttributeValue;
1249
1250 try {
1251 parsedAttributeValue = sourceItem.parse(attributeValue);
1252 } catch (err) {
1253 const { line: sl, column: sc } = locConverter.get(attr.valueStart);
1254 const { line: el, column: ec } = locConverter.get(attr.valueEnd);
1255
1256 module.addError(
1257 new ModuleDependencyError(
1258 module,
1259 new WebpackError(
1260 `Bad value for attribute "${attributeName}" on element "${elementName}": ${
1261 /** @type {Error} */ (err).message
1262 }`
1263 ),
1264 {
1265 start: { line: sl, column: sc },
1266 end: { line: el, column: ec }
1267 }
1268 )
1269 );
1270 }
1271
1272 if (!parsedAttributeValue) continue;
1273
1274 // `<link rel="stylesheet">` is upgraded to an entry only when
1275 // `experiments.css` is on — that's the mode where webpack can
1276 // bundle the CSS into its own chunk. Without it, the
1277 // stylesheet href stays a plain asset URL. Scope this to the
1278 // `href` attribute only: `<link>` also exposes
1279 // `imagesrcset` URLs which must continue to flow through
1280 // the regular asset rewriting path even on a stylesheet
1281 // link.
1282 const isStylesheetEntry =
1283 this.css &&
1284 elementName === "link" &&
1285 attributeName === "href" &&
1286 isLinkStylesheet(getAttributesMap());
1287 const isEntry =
1288 isStylesheetEntry ||
1289 sourceItem.entry === true ||
1290 (typeof sourceItem.entry === "function" &&
1291 sourceItem.entry(getAttributesMap()));
1292
1293 // `<script type="module" src>` and `<link rel="modulepreload">`
1294 // reference ES modules; everything else under `<script src>` is a
1295 // classic script. The category drives ESM vs CommonJS resolution
1296 // of the entry — the chunk format is controlled by the user via
1297 // `output.module` / `experiments.outputModule`.
1298 const useEsmEntry =
1299 (elementName === "script" && isModuleScript(getAttributesMap())) ||
1300 sourceItem.entryCategory === "esm";
1301
1302 for (const parsedSource of parsedAttributeValue) {
1303 const [value, innerStart, innerEnd] = parsedSource;
1304 if (value.startsWith("#")) continue;
1305 const sourceStart = attr.valueStart + innerStart;
1306 const sourceEnd = attr.valueStart + innerEnd;
1307 const { line: sl, column: sc } = locConverter.get(sourceStart);
1308 const { line: el, column: ec } = locConverter.get(sourceEnd);
1309 if (isEntry) {
1310 const entryName = `__html_${moduleHash}_${nextEntryIndex++}`;
1311 const isStylesheetLink =
1312 elementName === "link" && isLinkStylesheet(getAttributesMap());
1313 /** @type {"classic" | "esm-script" | "modulepreload" | "stylesheet"} */
1314 const kind =
1315 elementName === "link"
1316 ? isStylesheetLink
1317 ? "stylesheet"
1318 : "modulepreload"
1319 : useEsmEntry
1320 ? "esm-script"
1321 : "classic";
1322 // With `output.module` enabled, a classic `<script src>` is
1323 // upgraded in place to `<script type="module" src>` (see the
1324 // ConstDependency insertion below). Account for that in the
1325 // dependency's `elementKind` so sibling tags emitted by the
1326 // template for additional entry chunks (runtime / split chunks)
1327 // also use `type="module"`.
1328 const willBeModuleScript =
1329 kind === "esm-script" ||
1330 (this.outputModule &&
1331 kind === "classic" &&
1332 elementName === "script");
1333 /** @type {"script-classic" | "script-module" | "modulepreload" | "stylesheet"} */
1334 const elementKind =
1335 kind === "modulepreload"
1336 ? "modulepreload"
1337 : kind === "stylesheet"
1338 ? "stylesheet"
1339 : willBeModuleScript
1340 ? "script-module"
1341 : "script-classic";
1342 const dep = new HtmlScriptSrcDependency(
1343 value,
1344 [sourceStart, sourceEnd],
1345 entryName,
1346 // `<link rel="stylesheet">` is bundled as a CSS entry —
1347 // using a non-"url" category so the default `.css` rule
1348 // (which gives the resolved module the CSS module type)
1349 // wins over the `dependency: "url"` → asset rule.
1350 kind === "stylesheet"
1351 ? "css-import"
1352 : useEsmEntry
1353 ? "esm"
1354 : sourceItem.entryCategory,
1355 elementKind,
1356 start,
1357 end
1358 );
1359 dep.setLoc(sl, sc, el, ec);
1360 module.addPresentationalDependency(dep);
1361 // Reconcile the rewritten `<script>` tag's `type`
1362 // attribute with the chunk's actual format. See
1363 // `reconcileScriptTypeAttr` for the rules.
1364 if (
1365 elementName === "script" &&
1366 (kind === "classic" || kind === "esm-script")
1367 ) {
1368 /** @type {AttrToken | undefined} */
1369 let typeAttr;
1370 for (const a of pendingAttributes) {
1371 if (
1372 input.slice(a.nameStart, a.nameEnd).toLowerCase() === "type"
1373 ) {
1374 typeAttr = a;
1375 break;
1376 }
1377 }
1378 reconcileScriptTypeAttr(typeAttr, nameEnd, kind, input);
1379 }
1380 const collection =
1381 kind === "classic"
1382 ? classicEntries
1383 : kind === "esm-script"
1384 ? esmScriptEntries
1385 : kind === "stylesheet"
1386 ? stylesheetEntries
1387 : modulePreloadEntries;
1388 collection.push({ request: value, entryName, kind });
1389 } else {
1390 const dep = new HtmlSourceDependency(value, [
1391 sourceStart,
1392 sourceEnd
1393 ]);
1394 dep.setLoc(sl, sc, el, ec);
1395 module.addDependency(dep);
1396 module.addCodeGenerationDependency(dep);
1397 }
1398 }
1399 }
1400
1401 // `<script>` is rawtext (the "script data state" in the HTML
1402 // tokenizer): its body must never be reparsed as HTML,
1403 // regardless of whether the tag has a `src` attribute. The
1404 // walker is already in script-data state for `<script>` and
1405 // will emit the matching `closeTag` event itself, so we
1406 // return `end` and defer body extraction to the closeTag
1407 // callback (which gets the spec-correct `</script>` position
1408 // even in escaped/double-escaped script-data sub-states).
1409 // When the tag has no `src` and its body is non-empty, the
1410 // closeTag handler bundles the inline JS as its own entry —
1411 // the same pipeline that processes `<script src>` — by
1412 // issuing a `data:text/javascript;base64,...` virtual request
1413 // and adding a dependency that rewrites the tag to
1414 // `<script src="…">` at render time. Only `<script>` tags
1415 // whose `type` attribute is absent, empty, or a recognized
1416 // JS mimetype are processed as JS; other types (e.g.
1417 // `application/ld+json`, `importmap`) are left untouched.
1418 if (elementName === "script") {
1419 const attrs = getAttributesMap();
1420 // Use attribute presence, not value: a `<script src>` with
1421 // an empty or valueless `src` still ignores its inline
1422 // body in the browser, so we must not bundle the body.
1423 const hasSrc = attrs.has("src");
1424
1425 /** @type {AttrToken | undefined} */
1426 let typeAttr;
1427 for (const a of pendingAttributes) {
1428 if (input.slice(a.nameStart, a.nameEnd).toLowerCase() === "type") {
1429 typeAttr = a;
1430 break;
1431 }
1432 }
1433 pendingAttributes.length = 0;
1434
1435 if (hasSrc || !isExecutableJsScript(attrs)) {
1436 // `<script src>` body is ignored by the browser, and
1437 // non-JS `<script type>` (e.g. importmap, JSON-LD)
1438 // passes through unchanged. Either way the walker
1439 // consumes the body and emits the close tag itself.
1440 return end;
1441 }
1442
1443 pendingInlineScript = {
1444 contentStart: end,
1445 attrs,
1446 typeAttr,
1447 nameEnd
1448 };
1449 return end;
1450 }
1451
1452 pendingAttributes.length = 0;
1453 return end;
1454 }
1455 });
1456
1457 const buildInfo = /** @type {BuildInfo} */ (module.buildInfo);
1458 buildInfo.strict = true;
1459 // Hand off the collected entries to HtmlModulesPlugin; it creates the
1460 // real compilation entries during the finishMake hook. The classic
1461 // and esm-script groups are chained via a leader-only dependOn so
1462 // they share a runtime; modulepreload entries are emitted as
1463 // independent entries since `<link rel=modulepreload>` must preload
1464 // without running.
1465 if (
1466 classicEntries.length > 0 ||
1467 esmScriptEntries.length > 0 ||
1468 modulePreloadEntries.length > 0 ||
1469 stylesheetEntries.length > 0
1470 ) {
1471 /** @type {Record<string, EntryScriptInfo[]>} */
1472 (buildInfo.htmlEntryScripts) = {
1473 classic: classicEntries,
1474 "esm-script": esmScriptEntries,
1475 modulepreload: modulePreloadEntries,
1476 stylesheet: stylesheetEntries
1477 };
1478 }
1479
1480 const buildMeta = /** @type {BuildMeta} */ (state.module.buildMeta);
1481 buildMeta.exportsType = "default";
1482
1483 state.module.addDependency(new StaticExportsDependency(["default"], true));
1484
1485 return state;
1486 }
1487}
1488
1489module.exports = HtmlParser;
Note: See TracBrowser for help on using the repository browser.