| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915 |
- /*
- MIT License http://www.opensource.org/licenses/mit-license.php
- */
- "use strict";
- const Parser = require("../Parser");
- const ConstDependency = require("../dependencies/ConstDependency");
- const HtmlEntryDependency = require("../dependencies/HtmlEntryDependency");
- const HtmlInlineHtmlDependency = require("../dependencies/HtmlInlineHtmlDependency");
- const HtmlInlineScriptDependency = require("../dependencies/HtmlInlineScriptDependency");
- const HtmlInlineStyleDependency = require("../dependencies/HtmlInlineStyleDependency");
- const HtmlSourceDependency = require("../dependencies/HtmlSourceDependency");
- const StaticExportsDependency = require("../dependencies/StaticExportsDependency");
- const WebpackError = require("../errors/WebpackError");
- const ResourceHintPlugin = require("../prefetch/ResourceHintPlugin");
- const parseResourceHintOptions = require("../prefetch/parseResourceHintOptions");
- const LazySet = require("../util/LazySet");
- const LocConverter = require("../util/LocConverter");
- const createHash = require("../util/createHash");
- const { contextify } = require("../util/identifier");
- const {
- createMagicCommentContext,
- parseMagicComment,
- webpackCommentRegExp
- } = require("../util/magicComment");
- const memoize = require("../util/memoize");
- const { JAVASCRIPT_SCRIPT_TYPES } = require("./data");
- const {
- NS_HTML,
- NS_SVG,
- NodeType,
- SVG_TAG_ADJUST,
- SourceProcessor,
- baseTag,
- buildHeadTags,
- decodeEntities,
- escapeAttribute,
- escapeText,
- isAsciiWhitespace,
- metaTag,
- parseCssUrls,
- parseMsapplicationTask,
- parseSrc,
- parseSrcset
- } = require("./syntax");
- const getUnsupportedFeatureWarning = memoize(() =>
- require("../errors/UnsupportedFeatureWarning")
- );
- const getModuleDependencyError = memoize(() =>
- require("../errors/ModuleDependencyError")
- );
- const getCommentCompilationWarning = memoize(() =>
- require("../errors/CommentCompilationWarning")
- );
- /** @import { HtmlParserOptions } from "../../declarations/WebpackOptions" */
- /** @import { Range } from "../javascript/JavascriptParser" */
- /** @import Module, { BuildInfo, BuildMeta } from "../Module" */
- /** @import { FileSystemDependencies } from "../Compilation" */
- /** @import NormalModule from "../NormalModule" */
- /** @import { ParserState, PreparsedAst } from "../Parser" */
- /** @import { HtmlModuleBuildInfo } from "./HtmlModule" */
- /**
- * @typedef {object} HtmlTemplateContext
- * @property {Module} module the html module being transformed
- * @property {string} resource absolute path of the module's resource
- * @property {(dependency: string) => void} addDependency register a file (e.g. a template partial) as a build dependency so editing it triggers a rebuild
- * @property {(dependency: string) => void} addContextDependency register a directory as a build dependency
- * @property {(dependency: string) => void} addMissingDependency register a not-yet-existing path as a build dependency so creating it triggers a rebuild
- * @property {(dependency: string) => void} addBuildDependency register a build dependency (e.g. a template engine config) so changing it invalidates the cache
- * @property {(warning: Error | string) => void} emitWarning report a non-fatal warning on the module
- * @property {(error: Error | string) => void} emitError report an error on the module
- */
- /** @typedef {(source: string, context: HtmlTemplateContext) => string} HtmlTemplateFunction */
- /** @import { ParsedSource } from "./syntax" */
- // Cheap pre-filter for a `style="..."` attribute: only route it through the
- // CSS pipeline when it can hold a URL-bearing function (`url()` / `src()` /
- // `image()` / `image-set()`), otherwise the processed text equals the input.
- const STYLE_ATTR_URL_REGEXP = /url\(|src\(|image\(|image-set\(/i;
- // Cheap pre-filter for a `css-url` attribute value (an SVG presentation
- // attribute such as `fill`, `clip-path`, …): only tokenize values that can
- // hold a `url(...)` FuncIRI. Most are plain colors/keywords (`#fff`, `red`).
- const FUNC_IRI_URL_REGEXP = /url\(/i;
- // Cheap pre-filter for `<iframe srcdoc>` markup: only spin up a nested HTML
- // module when the document can reference an asset — via any attribute (`src=`,
- // `href=`, `style=`, …), a CSS `url(...)`, or a CSS `@import`. Pure
- // formatting/text markup (`<p>hi</p>`) rewrites to itself, so skip it.
- const SRCDOC_ASSET_REGEXP = /[=]|url\(|@import/i;
- // A URL carrying its own scheme (`https:`, `data:`, …) ignores the document
- // base per the URL spec, so `<base href>` never rewrites it.
- const ABSOLUTE_URL_SCHEME_REGEXP = /^[a-zA-Z][a-zA-Z\d+\-.]*:/;
- // a `charset=` declaration inside an `http-equiv="content-type"` content value
- const CHARSET_DECLARATION_REGEXP = /charset\s*=/i;
- // CSP/fetch attributes copied verbatim onto a synthesized sibling `<link>` /
- // `<script>` (`HtmlEntryDependency`). Fixed output order, independent of
- // source order.
- const COPYABLE_SIBLING_ATTRS = ["nonce", "crossorigin", "referrerpolicy"];
- const CC_QUOTATION = '"'.charCodeAt(0);
- const CC_APOSTROPHE = "'".charCodeAt(0);
- const CC_SLASH = "/".charCodeAt(0);
- /**
- * Byte-exact source span of an attribute including the single leading
- * whitespace (` name`, ` name=value`, ` name="value"`), mirroring the
- * tokenizer's end-of-attribute rule (`valueEnd + 1` past the closing quote).
- * @param {import("./syntax").HtmlPath} path walk path (used only for attribute-ref reads)
- * @param {string} source HTML source
- * @param {import("./syntax").HtmlAttributeRef} attr attribute ref
- * @returns {string} the attribute's source slice
- */
- const attrSourceSpan = (path, source, attr) => {
- const valueStart = path.attributeValueStart(attr);
- const valueEnd = path.attributeValueEnd(attr);
- const end =
- valueStart === -1
- ? path.attributeNameEnd(attr)
- : source.charCodeAt(valueStart - 1) === CC_QUOTATION ||
- source.charCodeAt(valueStart - 1) === CC_APOSTROPHE
- ? valueEnd + 1
- : valueEnd;
- return source.slice(path.attributeNameStart(attr) - 1, end);
- };
- /**
- * Source span of a tag's `integrity` attribute (offsets relative to
- * `elementStart`, leading whitespace and quotes included), so the template can
- * drop the content-specific author value without re-parsing. Null when absent.
- * @param {import("./syntax").HtmlPath} path walk path (for attribute reads)
- * @param {string} source HTML source
- * @param {number} elementStart offset of the tag's opening `<`
- * @returns {Range | null} the span, or null when there is no `integrity`
- */
- const captureIntegrityRange = (path, source, elementStart) => {
- const integrityAttr = path.findAttribute("integrity");
- if (integrityAttr === 0 || path.attributeNameStart(integrityAttr) < 0) {
- return null;
- }
- let rs = path.attributeNameStart(integrityAttr);
- while (rs > 0 && isAsciiWhitespace(source.charCodeAt(rs - 1))) {
- rs--;
- }
- let re;
- const ivs = path.attributeValueStart(integrityAttr);
- if (ivs === -1) {
- re = path.attributeNameEnd(integrityAttr);
- } else {
- const q = source.charCodeAt(ivs - 1);
- const ive = path.attributeValueEnd(integrityAttr);
- re = q === CC_QUOTATION || q === CC_APOSTROPHE ? ive + 1 : ive;
- }
- return [rs - elementStart, re - elementStart];
- };
- /**
- * @param {Map<string, string>} attributes attributes
- * @param {string} name name
- * @returns {string | undefined} attribute value
- */
- const getAttributeValue = (attributes, name) => attributes.get(name);
- /** @type {Map<string, Set<string>>} */
- const META = new Map([
- [
- "name",
- new Set([
- // msapplication-TileImage
- "msapplication-tileimage",
- "msapplication-square70x70logo",
- "msapplication-square150x150logo",
- "msapplication-wide310x150logo",
- "msapplication-square310x310logo",
- "msapplication-config",
- // Only the `icon-uri` part is an asset, see `parseMsapplicationTask`
- "msapplication-task",
- "twitter:image",
- "twitter:image:src",
- // Twitter player card: URL to the raw video/media stream
- "twitter:player:stream",
- // Legacy preview-image hint
- "thumbnail"
- ])
- ],
- [
- "property",
- new Set([
- "og:image",
- "og:image:url",
- "og:image:secure_url",
- "og:audio",
- "og:audio:secure_url",
- "og:video",
- "og:video:secure_url",
- "vk:image"
- ])
- ],
- [
- "itemprop",
- new Set([
- "image",
- "logo",
- "screenshot",
- "thumbnailurl",
- "contenturl",
- "downloadurl",
- "duringmedia",
- "embedurl",
- "installurl",
- "layoutimage"
- ])
- ]
- ]);
- /**
- * @param {Map<string, string>} attributes attributes
- * @returns {boolean} true when need to parse, otherwise false
- */
- const filterLinkItemprop = (attributes) => {
- const itemprop = getAttributeValue(attributes, "itemprop");
- if (!itemprop) return false;
- const allowedAttributes = META.get("itemprop");
- if (!allowedAttributes) return false;
- return allowedAttributes.has(itemprop.trim().toLowerCase());
- };
- // `<link rel>` values whose `href`/`imagesrcset` webpack treats as a reference.
- const ALLOWED_LINK_RELS = new Set([
- "stylesheet",
- "icon",
- "mask-icon",
- "apple-touch-icon",
- "apple-touch-icon-precomposed",
- "apple-touch-startup-image",
- // Routed to the `asset/webmanifest` module type, which parses its icon URLs.
- "manifest",
- "prefetch",
- "preload",
- "modulepreload",
- // Legacy preview-image hint (`<link rel="image_src" href>`)
- "image_src"
- ]);
- /**
- * @param {Map<string, string>} attributes attributes
- * @returns {boolean} true when need to parse, otherwise false
- */
- const filterLinkHref = (attributes) => {
- const rel = getAttributeValue(attributes, "rel");
- if (!rel) return false;
- const usedRels = rel.trim().toLowerCase().split(/\s+/);
- for (let i = 0; i < usedRels.length; i++) {
- if (ALLOWED_LINK_RELS.has(usedRels[i])) return true;
- }
- return false;
- };
- /**
- * @param {Map<string, string>} attributes attributes
- * @returns {boolean} true when need to parse, otherwise false
- */
- const filterLinkUnion = (attributes) =>
- filterLinkHref(attributes) || filterLinkItemprop(attributes);
- /**
- * @param {Map<string, string>} attributes attributes
- * @returns {boolean} true when need to parse, otherwise false
- */
- const filterMetaContent = (attributes) => {
- for (const item of META) {
- const [key, allowedNames] = item;
- const name = getAttributeValue(attributes, key);
- if (!name) continue;
- // Check every present attribute, not only the first one
- if (allowedNames.has(name.trim().toLowerCase())) return true;
- }
- return false;
- };
- /**
- * `<param value>` (obsolete `<object>`/`<applet>` child) is only a URL when
- * `valuetype="ref"`; otherwise it's an opaque string.
- * @param {Map<string, string>} attributes attributes
- * @returns {boolean} true when `value` is a URL reference
- */
- const filterParamRef = (attributes) => {
- const valuetype = getAttributeValue(attributes, "valuetype");
- if (!valuetype) return false;
- return valuetype.trim().toLowerCase() === "ref";
- };
- /**
- * @param {Map<string, string>} attributes attributes
- * @returns {boolean} true when the script element opts into ES module semantics
- */
- const isModuleScript = (attributes) => {
- const type = getAttributeValue(attributes, "type");
- if (!type) return false;
- return type.trim().toLowerCase() === "module";
- };
- // HTML `<script>` `type` values that the browser treats as executable
- /**
- * @param {Map<string, string>} attributes attributes
- * @returns {boolean} true when the script element's `type` is executable JS
- */
- const isExecutableJsScript = (attributes) => {
- const type = getAttributeValue(attributes, "type");
- if (type === undefined) return true;
- return JAVASCRIPT_SCRIPT_TYPES.has(type.trim().toLowerCase());
- };
- /**
- * @param {Map<string, string>} attributes attributes
- * @returns {boolean} true when the link points at an ES module that should be bundled as an entry chunk
- */
- const isLinkModulePreload = (attributes) => {
- const rel = getAttributeValue(attributes, "rel");
- if (!rel) return false;
- return rel.trim().toLowerCase().split(/\s+/).includes("modulepreload");
- };
- /**
- * @param {Map<string, string>} attributes attributes
- * @returns {boolean} true when the link is a `<link rel="stylesheet">` that should be bundled as a CSS entry chunk
- */
- const isLinkStylesheet = (attributes) => {
- const rel = getAttributeValue(attributes, "rel");
- if (!rel) return false;
- return rel.trim().toLowerCase().split(/\s+/).includes("stylesheet");
- };
- /** @typedef {"src" | "srcset" | "css-url" | "msapplication-task" | "script" | "script-module" | "modulepreload" | "stylesheet" | "html" | "preload" | "prefetch" | "stylesheet-style" | "stylesheet-style-attribute" | "srcdoc"} SourceType */
- /** Entry types: a `type` whose value is loaded as its own compilation entry chunk. */
- /** @typedef {"script" | "script-module" | "modulepreload" | "stylesheet" | "html" | "preload" | "prefetch"} EntrySourceType */
- /** @typedef {SourceType | ((attrs: Map<string, string>, css: boolean) => SourceType)} SourceTypeOrResolver */
- /** @typedef {(attributes: Map<string, string>, value: string) => boolean} SourceFilter */
- /** @typedef {{ tag?: string, attribute: string, type: SourceType, filter?: SourceFilter }} SourceEntry */
- /** 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). */
- /** @typedef {{ type: SourceTypeOrResolver, filter?: SourceFilter, namespace?: number }} SourceItem */
- /** A tag→attribute lookup; a `undefined` value marks a built-in source disabled via `sources` (`type: false`). */
- /** @typedef {Record<string, SourceItem | undefined>} SourceBucket */
- /** @typedef {Record<string, SourceBucket>} SourceTable */
- /** A source whose value is the element's text content (a `<style>`/`<script>` body) rather than an attribute. */
- /** @typedef {{ type: SourceTypeOrResolver, filter?: (attributes: Map<string, string>) => boolean }} ContentSourceItem */
- /**
- * Builds a null-prototype dictionary from the given property bags
- * (later bags win; `undefined` bags are skipped). A null prototype is
- * essential here: the tables are indexed by HTML tag and attribute
- * names, so a plain object would let names like `__proto__`,
- * `constructor`, or `toString` resolve to inherited values at lookup
- * time — producing bogus dependencies and letting `sources: false` be
- * bypassed.
- * @param {...(Record<string, EXPECTED_ANY> | undefined)} bags property bags
- * @returns {EXPECTED_ANY} null-prototype dictionary
- */
- const dict = (...bags) => Object.assign(Object.create(null), ...bags);
- // Shared `SourceItem` singletons used by the built-in defaults —
- // keeping one instance per kind stabilizes V8's hidden classes across
- // lookups in the walk. `srcset` is its own type; the walk picks the
- // `parseSrcset` parser for it and otherwise treats it like `src`.
- /** @type {SourceItem} */
- const PLAIN_SRC = { type: "src", filter: undefined };
- /** @type {SourceItem} */
- const PLAIN_SRCSET = { type: "srcset", filter: undefined };
- /**
- * `<link href>` is polymorphic: `rel="modulepreload"` → an ESM
- * preload entry, `rel="stylesheet"` (with `experiments.css`) → a CSS
- * entry, otherwise a plain asset URL.
- * @type {SourceItem}
- */
- const LINK_HREF = {
- type: (attrs, css) => {
- if (isLinkModulePreload(attrs)) return "modulepreload";
- if (css && isLinkStylesheet(attrs)) return "stylesheet";
- // `rel="preload"/"prefetch"` with `as="script"` (or `as="style"` when
- // `experiments.css` is on) becomes a bundled, non-executing entry whose
- // `href` is rewritten to the built chunk; other `as` targets (image,
- // font, fetch, …) stay plain asset URLs.
- const rel = getAttributeValue(attrs, "rel");
- if (rel) {
- const rels = rel.trim().toLowerCase().split(/\s+/);
- const kind = rels.includes("preload")
- ? "preload"
- : rels.includes("prefetch")
- ? "prefetch"
- : undefined;
- if (kind) {
- const as = getAttributeValue(attrs, "as");
- const asLower = as ? as.trim().toLowerCase() : "";
- if (asLower === "script" || (css && asLower === "style")) return kind;
- }
- }
- return "src";
- },
- filter: filterLinkUnion
- };
- /**
- * `<script src>`: non-JS types (e.g. `application/ld+json`,
- * `importmap`) stay plain asset URLs so the browser keeps seeing them
- * as data blocks; `type="module"` opts into the ESM entry chunk;
- * everything else in `JAVASCRIPT_SCRIPT_TYPES` is a classic script.
- * @type {SourceItem}
- */
- const SCRIPT_SRC = {
- type: (attrs) =>
- isExecutableJsScript(attrs)
- ? isModuleScript(attrs)
- ? "script-module"
- : "script"
- : "src",
- filter: undefined
- };
- /**
- * `<meta content>`: most referenced names hold a single URL (`src`); the
- * `msapplication-task` value is a `;`-delimited list whose `icon-uri` part
- * is the only asset, so it selects the `msapplication-task` parser.
- * @type {SourceItem}
- */
- const META_CONTENT = {
- type: (attrs) => {
- const name = attrs.get("name");
- return name !== undefined &&
- name.trim().toLowerCase() === "msapplication-task"
- ? "msapplication-task"
- : "src";
- },
- filter: filterMetaContent
- };
- // Built-in lookup table, written directly in its final resolved shape:
- // `DEFAULT_SOURCES_BY_TAG[tag][attribute] = item`. No module-load
- // loop, no separate array representation — every parser created with
- // the default `sources` config just references this table and pays
- // zero per-parser work.
- /** @type {Record<string, Record<string, SourceItem>>} */
- const DEFAULT_SOURCES_BY_TAG = dict({
- // Obsolete Java-applet element; `code`/`object` are single class/object URLs.
- applet: dict({ code: PLAIN_SRC, object: PLAIN_SRC }),
- audio: dict({ src: PLAIN_SRC }),
- // Deprecated presentational `background` attribute — an image URL.
- body: dict({ background: PLAIN_SRC }),
- embed: dict({ src: PLAIN_SRC }),
- // `srcdoc` is an entity-encoded HTML document parsed and rewritten as a
- // nested module — its handling lives in the generic source loop so the
- // `sources` option can re-target it (or other tags/attributes) freely.
- iframe: dict({ srcdoc: { type: "srcdoc", filter: undefined } }),
- img: dict({ src: PLAIN_SRC, srcset: PLAIN_SRCSET }),
- input: dict({ src: PLAIN_SRC }),
- link: dict({
- href: LINK_HREF,
- imagesrcset: { type: "srcset", filter: filterLinkHref }
- }),
- meta: dict({ content: META_CONTENT }),
- // MathML `<mglyph src>` references an image.
- mglyph: dict({ src: PLAIN_SRC }),
- // `classid` is a single object URI (`codebase`/`archive` are a base/list, skipped).
- object: dict({ data: PLAIN_SRC, classid: PLAIN_SRC }),
- // Obsolete `<param valuetype="ref" value="url">` child of `<object>`.
- param: dict({ value: { type: "src", filter: filterParamRef } }),
- // `href`/`xlink:href` reference the source of SVG `<script>` elements
- script: dict({
- src: SCRIPT_SRC,
- href: SCRIPT_SRC,
- "xlink:href": SCRIPT_SRC
- }),
- source: dict({ src: PLAIN_SRC, srcset: PLAIN_SRCSET }),
- // Deprecated presentational `background` attribute — an image URL.
- table: dict({ background: PLAIN_SRC }),
- td: dict({ background: PLAIN_SRC }),
- th: dict({ background: PLAIN_SRC }),
- track: dict({ src: PLAIN_SRC }),
- video: dict({ poster: PLAIN_SRC, src: PLAIN_SRC }),
- // SVG. Tag names match the tree builder's adjusted camelCase
- // (`feImage`/`textPath`/`linearGradient`/`radialGradient`). `href`/
- // `xlink:href` reference another element/resource; fragment-only `#id`
- // values are left untouched, so only external `file.svg#id` is rewritten.
- // `color-profile`'s `xlink:href` points at an external ICC profile file.
- // `cursor`/`font-face-uri` point at an external image/font file; `altGlyph`/
- // `glyphRef`/`tref` are legacy element references (removed in SVG 2).
- altGlyph: dict({ "xlink:href": PLAIN_SRC, href: PLAIN_SRC }),
- "color-profile": dict({ "xlink:href": PLAIN_SRC, href: PLAIN_SRC }),
- cursor: dict({ "xlink:href": PLAIN_SRC, href: PLAIN_SRC }),
- feImage: dict({ "xlink:href": PLAIN_SRC, href: PLAIN_SRC }),
- filter: dict({ "xlink:href": PLAIN_SRC, href: PLAIN_SRC }),
- "font-face-uri": dict({ "xlink:href": PLAIN_SRC, href: PLAIN_SRC }),
- glyphRef: dict({ "xlink:href": PLAIN_SRC, href: PLAIN_SRC }),
- image: dict({ "xlink:href": PLAIN_SRC, href: PLAIN_SRC }),
- linearGradient: dict({ "xlink:href": PLAIN_SRC, href: PLAIN_SRC }),
- mpath: dict({ "xlink:href": PLAIN_SRC, href: PLAIN_SRC }),
- pattern: dict({ "xlink:href": PLAIN_SRC, href: PLAIN_SRC }),
- radialGradient: dict({ "xlink:href": PLAIN_SRC, href: PLAIN_SRC }),
- textPath: dict({ "xlink:href": PLAIN_SRC, href: PLAIN_SRC }),
- tref: dict({ "xlink:href": PLAIN_SRC, href: PLAIN_SRC }),
- use: dict({ "xlink:href": PLAIN_SRC, href: PLAIN_SRC })
- // CSS `url(...)` references in SVG presentation attributes (fill, stroke,
- // clip-path, …) are handled separately during the walk (`parseCssUrls`).
- });
- // SVG presentation attributes (`fill`, `stroke`, …) carry CSS `url(...)`
- // FuncIRIs; `parseCssUrls` extracts external refs as assets (internal
- // `url(#id)` is left untouched). They apply to every SVG-namespace element, so
- // the item is `namespace`-gated rather than placed in a tag bucket. The filter
- // is a cheap pre-check skipping the common plain values (`red`, `none`, `#fff`).
- /** @type {SourceItem} */
- const SVG_CSS_URL = {
- type: "css-url",
- filter: (attributes, value) => FUNC_IRI_URL_REGEXP.test(value),
- namespace: NS_SVG
- };
- // The global `style=""` attribute (a CSS declaration list), available on every
- // tag and even with `sources: false`. Like every `stylesheet-style*` source it
- // only emits when `experiments.css` is on (checked where the dependency is
- // created); the filter is a cheap pre-check skipping declarations without a
- // `url()`/`src()`/… .
- /** @type {SourceItem} */
- const STYLE_ATTRIBUTE = {
- type: "stylesheet-style-attribute",
- filter: (attributes, value) => STYLE_ATTR_URL_REGEXP.test(value)
- };
- // Any-tag sources — applied to every element as a fallback and folded into
- // each tag bucket, so the walk resolves one object per element and reads one
- // property per attribute. `DEFAULT` (svg presentation + style) is active
- // unless `sources: false`; only the always-on style attribute survives `false`.
- /** @type {Record<string, SourceItem>} */
- const DEFAULT_ANY_SOURCES = dict({ style: STYLE_ATTRIBUTE });
- for (const name of [
- "fill",
- "stroke",
- "clip-path",
- "mask",
- "filter",
- "marker",
- "marker-start",
- "marker-mid",
- "marker-end",
- "cursor"
- ]) {
- DEFAULT_ANY_SOURCES[name] = SVG_CSS_URL;
- }
- /** @type {Record<string, SourceItem>} */
- const ALWAYS_ANY_SOURCES = dict({ style: STYLE_ATTRIBUTE });
- // Reserved key holding the any-tag sources inside the per-tag table — the
- // schema forbids an empty `tag`, so it never collides with a real tag. The
- // walk falls back to it for elements without their own bucket.
- const ANY_TAG = "";
- /**
- * Folds the any-tag sources into every per-tag bucket (tag-specific entries
- * win) and stores them under `ANY_TAG`, so the whole source model is one
- * object and the walk's per-attribute lookup is a single property read.
- * @param {SourceTable} byTag per-tag sources
- * @param {SourceBucket} any any-tag sources
- * @returns {SourceTable} folded table
- */
- const foldAnySources = (byTag, any) => {
- for (const tag of Object.keys(byTag)) {
- byTag[tag] = dict(any, byTag[tag]);
- }
- byTag[ANY_TAG] = any;
- return byTag;
- };
- // Default table (per-tag defaults + any-tag sources folded in) and the
- // `sources: false` table (only the always-on `style=""` attribute). Both are
- // precomputed so the common cases reference them with zero per-parser work.
- /** @type {SourceTable} */
- const DEFAULT_SOURCES_FOLDED = foldAnySources(
- dict(DEFAULT_SOURCES_BY_TAG),
- DEFAULT_ANY_SOURCES
- );
- /** @type {SourceTable} */
- const DISABLED_SOURCES_FOLDED = foldAnySources(dict(), ALWAYS_ANY_SOURCES);
- /**
- * Inline `<script>` body: classic JS unless `type="module"` opts into ESM.
- * @param {Map<string, string>} attrs attributes
- * @returns {SourceType} the entry type for the inline script
- */
- const scriptContentType = (attrs) =>
- isModuleScript(attrs) ? "script-module" : "script";
- /**
- * Inline `<script>` body: only an executable JS block with no external source
- * carries content webpack should bundle.
- * @param {Map<string, string>} attrs attributes
- * @returns {boolean} true when the inline body should be bundled
- */
- const scriptContentFilter = (attrs) =>
- !attrs.has("src") &&
- !attrs.has("href") &&
- !attrs.has("xlink:href") &&
- isExecutableJsScript(attrs);
- /**
- * `<style>` body is a stylesheet only when its `type` is empty or `text/css`.
- * @param {Map<string, string>} attrs attributes
- * @returns {boolean} true when the body is CSS
- */
- const styleContentFilter = (attrs) => {
- const type = attrs.get("type");
- if (type === undefined) return true;
- const t = type.trim().toLowerCase();
- return t === "" || t === "text/css";
- };
- // Element-body sources, independent of the `sources` option (even `sources:
- // false`): inline `<script>` bodies are always processed; the `<style>` body
- // only emits when `experiments.css` is on (checked where the dependency is
- // created, like every `stylesheet-style*` source).
- /** @type {Record<string, ContentSourceItem>} */
- const CONTENT_SOURCES = dict({
- script: { type: scriptContentType, filter: scriptContentFilter },
- style: { type: "stylesheet-style", filter: styleContentFilter }
- });
- class HtmlParser extends Parser {
- /**
- * Creates an instance of HtmlParser.
- * @param {HtmlParserOptions} options parser options (from `module.parser.html`; always passed by the `createParser` hook)
- */
- constructor(options) {
- super();
- this.magicCommentContext = createMagicCommentContext();
- // Read by HtmlModulesPlugin's `processResult` hook, which transforms
- // the module source before it is stored and parsed.
- /** @type {HtmlTemplateFunction | undefined} */
- this.template = options.template;
- /** @type {import("../../declarations/WebpackOptions").UrlHintRule[] | undefined} */
- this._urlHints = options.urlHints;
- // `as` selects the parse mode — the HTML analog of the CSS parser's `as`.
- // `"document"` (default) parses a full page; any other value is the
- // context element whose inner HTML the source is parsed as (a fragment),
- // so context-sensitive tags (`<tr>`, `<td>`, `<option>`, …) in a partial
- // are kept, not dropped. `"template"` is the neutral generic fragment.
- /** @type {string | undefined} */
- this.fragmentContext =
- options.as === undefined || options.as === "document"
- ? undefined
- : options.as;
- // One source model: a per-tag table that also holds the any-tag sources
- // under `ANY_TAG`. The common cases reference precomputed tables.
- /** @type {SourceTable} */
- this.sourcesByTag = DEFAULT_SOURCES_FOLDED;
- const sources = options.sources;
- if (sources === undefined || sources === true) return;
- if (sources === false) {
- // Only the always-on `style=""` attribute survives; nothing else is
- // extracted (svg presentation, `<script src>`, `<link>` entries, …).
- this.sourcesByTag = DISABLED_SOURCES_FOLDED;
- return;
- }
- // User array — `"..."` anywhere opts the per-tag defaults in as the
- // base; the built-in any-tag sources (svg presentation + style) are
- // always present. A user entry with no `tag` is an any-tag source (e.g.
- // `{ attribute: "data-style", type: "stylesheet-style-attribute" }`).
- // User entries override regardless of position. `dict()` keeps every
- // table null-prototype (see its doc), and per-tag writes rebuild the
- // bucket so the aliased default buckets stay intact.
- /** @type {SourceTable} */
- const byTag = sources.includes("...")
- ? dict(DEFAULT_SOURCES_BY_TAG)
- : dict();
- /** @type {SourceBucket} */
- const any = dict(DEFAULT_ANY_SOURCES);
- for (const entry of sources) {
- if (entry === "...") continue;
- // `type: false` disables a built-in source: writing `undefined` over the
- // attribute makes the walk's `sources[name]` lookup skip it (see below).
- /** @type {SourceItem | undefined} */
- const item =
- entry.type === false
- ? undefined
- : {
- type: entry.type,
- filter:
- typeof entry.filter === "function" ? entry.filter : undefined
- };
- const attr = entry.attribute.toLowerCase();
- if (entry.tag === undefined) {
- any[attr] = item;
- } else {
- const tag = entry.tag.toLowerCase();
- byTag[tag] = dict(byTag[tag], { [attr]: item });
- // The AST carries adjusted camelCase names for foreign-content
- // tags (e.g. `feImage`) — register the entry under both.
- const adjusted = SVG_TAG_ADJUST[tag];
- if (adjusted !== undefined) {
- byTag[adjusted] = dict(byTag[adjusted], { [attr]: item });
- }
- }
- }
- /** @type {SourceTable} */
- this.sourcesByTag = foldAnySources(byTag, any);
- }
- /**
- * Runs the `template` option over the source and returns the transformed
- * html. Called from HtmlModulesPlugin's `processResult`, where the return
- * value becomes the module's stored source so the parser (which records
- * dependency offsets against it) and the generator (which renders from
- * `module.originalSource()`) stay in agreement.
- * @param {string | Buffer} source the original source
- * @param {NormalModule} module the html module
- * @returns {string | Buffer} the transformed source
- */
- applyTemplate(source, module) {
- if (!this.template) return source;
- // `processResult` runs after `_doBuild` has initialized these
- // dependency sets, so they are always present here.
- const buildInfo = /** @type {BuildInfo} */ (module.buildInfo);
- const fileDependencies = /** @type {FileSystemDependencies} */ (
- buildInfo.fileDependencies
- );
- const contextDependencies = /** @type {FileSystemDependencies} */ (
- buildInfo.contextDependencies
- );
- const missingDependencies = /** @type {FileSystemDependencies} */ (
- buildInfo.missingDependencies
- );
- const transformed = this.template(
- typeof source === "string" ? source : source.toString("utf8"),
- {
- module,
- resource: module.resource,
- addDependency: (dependency) => {
- fileDependencies.add(dependency);
- },
- addContextDependency: (dependency) => {
- contextDependencies.add(dependency);
- },
- addMissingDependency: (dependency) => {
- missingDependencies.add(dependency);
- },
- addBuildDependency: (dependency) => {
- if (buildInfo.buildDependencies === undefined) {
- buildInfo.buildDependencies = new LazySet();
- }
- buildInfo.buildDependencies.add(dependency);
- },
- emitWarning: (warning) =>
- module.addWarning(
- warning instanceof Error ? warning : new WebpackError(warning)
- ),
- emitError: (error) =>
- module.addError(
- error instanceof Error ? error : new WebpackError(error)
- )
- }
- );
- if (typeof transformed !== "string") {
- throw new Error(
- "The `template` html parser option must return a string."
- );
- }
- return transformed;
- }
- /**
- * Parses the provided source and updates the parser state.
- * @param {string | Buffer | PreparsedAst} source the source to parse
- * @param {ParserState} state the parser state
- * @returns {ParserState} the parser state
- */
- parse(source, state) {
- if (Buffer.isBuffer(source)) {
- source = source.toString("utf8");
- } else if (typeof source === "object") {
- throw new Error("webpackAst is unexpected for the HtmlParser");
- }
- if (source[0] === "\uFEFF") {
- source = source.slice(1);
- }
- const locConverter = new LocConverter(source);
- const module = state.module;
- const compilation = state.compilation;
- const { hashFunction, module: outputModule } = compilation.outputOptions;
- const context = compilation.compiler.context;
- const css = Boolean(compilation.options.experiments.css);
- const urlHints = this._urlHints;
- // Stable, per-HTML-module prefix used when generating entry names for
- // script src / modulepreload references so they don't collide across
- // HTML modules in the same compilation. We hash the module's resource
- // path (a plain absolute path) — going through `contextify` against
- // the compilation root keeps the hash machine-stable for the same
- // project layout. Note: `module.identifier()` returns `html|<path>`
- // for HTML modules, which doesn't start with `/`, so contextify would
- // leave it absolute. `module.resource` is the bare path.
- /** @type {string} */
- const resource =
- /** @type {EXPECTED_ANY} */ (module).resource || module.identifier();
- const moduleHash = createHash(hashFunction || "md4")
- .update(context ? contextify(context, resource) : resource)
- .digest("hex")
- .slice(0, 8);
- // `output.html` head injection: facts are collected during the walk and
- // become presentational dependencies afterwards — no later asset pass has
- // to re-parse the page. `data:` documents are skipped: synthetic entry
- // wrappers already carry the tags and iframe srcdoc pages must stay
- // untouched.
- const htmlOutputOptions =
- compilation.options && compilation.options.output
- ? compilation.options.output.html
- : undefined;
- const headOptions =
- typeof htmlOutputOptions === "object" &&
- (htmlOutputOptions.title !== undefined ||
- htmlOutputOptions.meta !== undefined ||
- htmlOutputOptions.base !== undefined) &&
- !resource.startsWith("data:")
- ? htmlOutputOptions
- : undefined;
- let doctypeEnd = -1;
- let htmlTagEnd = -1;
- // insertion offsets just inside the (possibly implicit) head
- let headStart = 0;
- let headLastEnd = 0;
- let headSeen = false;
- let inHead = false;
- // `<template>` contents are inert
- let headTemplateDepth = 0;
- // Depth of open `<template>` elements anywhere in the tree. Template
- // content is an inert, separate fragment per spec, so a `<base>` inside
- // it must NOT set the document base (`headTemplateDepth` only covers
- // templates seen while in `<head>`, so it can't guard body templates).
- let templateDepth = 0;
- let headHasTitle = false;
- let headHasBase = false;
- let headCharsetEnd = -1;
- /** @type {Set<string> | undefined} */
- let headMetaNames;
- // Script src / modulepreload references are collected per-type
- // during the walk; HtmlModulesPlugin later turns them into real
- // entries. `script` and `script-module` entries are chained via a
- // leader-only dependOn so they share a runtime.
- // `<link rel="modulepreload">` entries are kept independent — they
- // must preload without running, so they can never become a runtime
- // leader that other entries would import.
- /**
- * @typedef {object} HtmlEntryInfo
- * @property {string} request
- * @property {string} entryName
- * @property {"script" | "script-module" | "modulepreload" | "stylesheet" | "html" | "preload" | "prefetch"} type
- * @property {boolean=} css entry emits a CSS chunk (`<link rel="preload" as="style">`), so it needs the CSS filename template
- */
- /** @type {HtmlEntryInfo[]} */
- const scriptEntries = [];
- /** @type {HtmlEntryInfo[]} */
- const scriptModuleEntries = [];
- /** @type {HtmlEntryInfo[]} */
- const modulePreloadEntries = [];
- /** @type {HtmlEntryInfo[]} */
- const stylesheetEntries = [];
- // `type: "html"` links — each referenced `.html` becomes its own page
- // entry (`<a href="page.html">`), independent (no dependOn).
- /** @type {HtmlEntryInfo[]} */
- const htmlLinkEntries = [];
- // `<link rel="preload"/"prefetch">` of a bundled resource: each becomes an
- // independent, non-executing entry whose `href` is rewritten to the built
- // chunk's URL (JS for `as="script"`, CSS for `as="style"`).
- /** @type {HtmlEntryInfo[]} */
- const preloadEntries = [];
- /** @type {HtmlEntryInfo[]} */
- const prefetchEntries = [];
- // Offset of the first classic blocking script tag; injected stylesheet
- // `<link>`s stay ahead of it. `defer`/`async`/module scripts don't
- // anchor — they execute after parsing (and after pending stylesheets),
- // so CSS may follow their tags (the order Vite emits).
- let firstBlockingScriptStart = -1;
- let nextEntryIndex = 0;
- // `<base href>` resolves the relative URLs that follow it. A relative base
- // (`./assets/`) rewrites them into a subdirectory — still bundled; a
- // root-relative or absolute base (`/`, `https://cdn/`) points them outside
- // the build, so those URLs are left untouched. Resolved in the walk below
- // from the first `<base href>` seen (`documentBase` undefined until then).
- /** @type {string | undefined} */
- let documentBase;
- /** @type {string | undefined} */
- let baseDir;
- let baseIsExternal = false;
- // Prepended to the emitted URLs' auto-public-path undo path so the base
- // doesn't misdirect them: the browser resolves rewritten (relative)
- // output URLs against the base dir, so one `../` per base segment cancels
- // it. Undefined for absolute publicPath (no undo path is emitted).
- /** @type {string | undefined} */
- let baseUrlPrefix;
- /**
- * Tracks the `webpackIgnore` value from the most recent comment that
- * appears before the next tag. Reset whenever a tag is emitted or a
- * comment without a `webpackIgnore` value is encountered.
- * @type {boolean | undefined}
- */
- let pendingWebpackIgnore;
- /** @type {boolean | undefined} */
- let pendingWebpackInline;
- /**
- * Tracks the resource-hint values from the most recent `<!-- webpackPrefetch: … -->`
- * (etc.) comment so the next asset URL attribute picks them up.
- * @type {import("../prefetch/parseResourceHintOptions").ResourceHintOptions | undefined}
- */
- let pendingHints;
- const magicCommentContext = this.magicCommentContext;
- /**
- * @param {import("./syntax").HtmlPath} path walk path (used only for attribute-ref reads)
- * @param {import("./syntax").HtmlAttributeRef} typeAttr type attribute ref (0 = none)
- * @param {number} nameEnd end offset of the tag name
- * @param {string} type type of the script
- * @param {string} input source string
- */
- const reconcileScriptTypeAttr = (path, typeAttr, nameEnd, type, input) => {
- const valueStart =
- typeAttr !== 0 ? path.attributeValueStart(typeAttr) : -1;
- const valueEnd = typeAttr !== 0 ? path.attributeValueEnd(typeAttr) : -1;
- if (outputModule && type === "script") {
- // Chunk is an ES module; upgrade the tag.
- if (typeAttr !== 0 && valueStart !== -1) {
- module.addPresentationalDependency(
- new ConstDependency("module", [valueStart, valueEnd])
- );
- } else {
- module.addPresentationalDependency(
- new ConstDependency(' type="module"', nameEnd)
- );
- }
- } else if (!outputModule && type === "script-module" && typeAttr !== 0) {
- // Chunk is a classic IIFE; drop `type="module"` so the
- // browser doesn't load it under module semantics.
- let attrEnd;
- if (valueStart === -1) {
- attrEnd = path.attributeNameEnd(typeAttr);
- } else if (input[valueEnd] === '"' || input[valueEnd] === "'") {
- attrEnd = valueEnd + 1;
- } else {
- attrEnd = valueEnd;
- }
- let attrStart = path.attributeNameStart(typeAttr);
- if (
- attrStart > 0 &&
- isAsciiWhitespace(input.charCodeAt(attrStart - 1))
- ) {
- attrStart -= 1;
- }
- module.addPresentationalDependency(
- new ConstDependency("", [attrStart, attrEnd])
- );
- }
- };
- /**
- * @param {string} mime mime type
- * @param {string} text inline text
- * @returns {string} a `data:` request for the text
- */
- const dataUri = (mime, text) =>
- `data:${mime};base64,${Buffer.from(text, "utf8").toString("base64")}`;
- /**
- * @param {ConstDependency | HtmlSourceDependency | HtmlEntryDependency | HtmlInlineStyleDependency | HtmlInlineScriptDependency | HtmlInlineHtmlDependency} dep dependency
- * @param {number} start raw start offset
- * @param {number} end raw end offset
- */
- const setLoc = (dep, start, end) => {
- const s = locConverter.get(start);
- const e = locConverter.get(end);
- dep.setLoc(s.line, s.column, e.line, e.column);
- };
- /**
- * Classifies the first `<base href>` seen in the walk, setting
- * `documentBase`/`baseDir`/`baseIsExternal`/`baseUrlPrefix` for the URLs
- * that follow it.
- * @param {import("./syntax").HtmlPath} path walk path (for attribute reads)
- * @param {import("./syntax").HtmlAttributeRef} hrefAttr the base's href attribute
- */
- const resolveDocumentBase = (path, hrefAttr) => {
- documentBase = decodeEntities(path.attributeValue(hrefAttr), true).trim();
- if (!documentBase) return;
- if (
- ABSOLUTE_URL_SCHEME_REGEXP.test(documentBase) ||
- documentBase.charCodeAt(0) === CC_SLASH
- ) {
- baseIsExternal = true;
- return;
- }
- // Normalize the base path into its descending directory segments,
- // resolving `.`/`..` like the URL spec. The final segment is the
- // referenced file (dropped) unless the href ends with `/` or a
- // dot-segment; `up` counts `..`s that climb above the document dir.
- const parts = documentBase.split("/");
- /** @type {string[]} */
- const dirs = [];
- let up = 0;
- for (let i = 0; i < parts.length; i++) {
- const part = parts[i];
- if (part === "" || part === ".") continue;
- if (part === "..") {
- if (dirs.length > 0) dirs.pop();
- else up++;
- continue;
- }
- // A trailing plain segment (no following `/`) is the file, not a dir.
- if (i === parts.length - 1) continue;
- dirs.push(part);
- }
- baseDir = `${"../".repeat(up)}${dirs.length > 0 ? `${dirs.join("/")}/` : ""}`;
- // A base above the document dir can't be cancelled with `../` (the
- // document's own dir name is unknown) — leave the output URLs
- // un-prefixed; they still resolve under an absolute publicPath.
- if (up === 0 && dirs.length > 0) {
- baseUrlPrefix = "../".repeat(dirs.length);
- }
- };
- // Lazy decoded-attribute map for the current element — parse-scoped
- // to avoid a closure and memo slot per element.
- /** @type {import("./syntax").HtmlPath | undefined} */
- let attrMapPath;
- let attrMapCount = 0;
- /** @type {Map<string, string> | undefined} */
- let currentAttributesMap;
- const getAttributesMap = () => {
- if (currentAttributesMap) return currentAttributesMap;
- currentAttributesMap = new Map();
- const path = /** @type {import("./syntax").HtmlPath} */ (attrMapPath);
- for (let i = 0; i < attrMapCount; i++) {
- const attr = path.attributeAt(i);
- // Decoded values — filters and type resolvers compare what
- // the browser sees (e.g. `rel="icon"` means `icon`)
- currentAttributesMap.set(
- path.attributeName(attr),
- decodeEntities(path.attributeValue(attr), true)
- );
- }
- return currentAttributesMap;
- };
- // TODO implement full HTML parser (WASM)
- // The walker descends into children (and `<template>` content) itself;
- // the Element `exit` clears a pending `webpackIgnore` once an element's
- // children are done (the old `walkChildren` behaviour).
- new SourceProcessor()
- .use({
- [NodeType.Comment]: (path) => {
- const start = path.start();
- const end = path.end();
- // comments count toward the head-end insertion anchor
- if (inHead && headTemplateDepth === 0 && end > headLastEnd) {
- headLastEnd = end;
- }
- // Only proper `<!-- ... -->` comments carry magic comments.
- if (
- end - start < 7 ||
- source.charCodeAt(start) !== 0x3c ||
- source.charCodeAt(start + 1) !== 0x21 ||
- source.charCodeAt(start + 2) !== 0x2d ||
- source.charCodeAt(start + 3) !== 0x2d ||
- source.charCodeAt(end - 1) !== 0x3e ||
- source.charCodeAt(end - 2) !== 0x2d ||
- source.charCodeAt(end - 3) !== 0x2d
- ) {
- pendingWebpackIgnore = undefined;
- pendingWebpackInline = undefined;
- pendingHints = undefined;
- return;
- }
- const value = path.data();
- if (!webpackCommentRegExp.test(value)) {
- pendingWebpackIgnore = undefined;
- pendingWebpackInline = undefined;
- pendingHints = undefined;
- return;
- }
- /** @type {Record<string, EXPECTED_ANY>} */
- let options;
- try {
- options = parseMagicComment(value, magicCommentContext);
- } catch (err) {
- const { line: sl, column: sc } = locConverter.get(start);
- const { line: el, column: ec } = locConverter.get(end);
- module.addWarning(
- new (getCommentCompilationWarning())(
- `Compilation error while processing magic comment(-s): /*${value}*/: ${
- /** @type {Error} */ (err).message
- }`,
- {
- start: { line: sl, column: sc },
- end: { line: el, column: ec }
- }
- )
- );
- pendingWebpackIgnore = undefined;
- pendingWebpackInline = undefined;
- pendingHints = undefined;
- return;
- }
- if (
- options.webpackIgnore === undefined &&
- options.webpackInline === undefined &&
- options.webpackPrefetch === undefined &&
- options.webpackPreload === undefined &&
- options.webpackFetchPriority === undefined
- ) {
- pendingWebpackIgnore = undefined;
- pendingWebpackInline = undefined;
- pendingHints = undefined;
- return;
- }
- if (
- options.webpackIgnore !== undefined &&
- typeof options.webpackIgnore !== "boolean"
- ) {
- const { line: sl, column: sc } = locConverter.get(start);
- const { line: el, column: ec } = locConverter.get(end);
- module.addWarning(
- new (getUnsupportedFeatureWarning())(
- `\`webpackIgnore\` expected a boolean, but received: ${options.webpackIgnore}.`,
- {
- start: { line: sl, column: sc },
- end: { line: el, column: ec }
- }
- )
- );
- pendingWebpackIgnore = undefined;
- pendingWebpackInline = undefined;
- pendingHints = undefined;
- return;
- }
- if (
- options.webpackInline !== undefined &&
- typeof options.webpackInline !== "boolean"
- ) {
- const { line: sl, column: sc } = locConverter.get(start);
- const { line: el, column: ec } = locConverter.get(end);
- module.addWarning(
- new (getUnsupportedFeatureWarning())(
- `\`webpackInline\` expected a boolean, but received: ${options.webpackInline}.`,
- {
- start: { line: sl, column: sc },
- end: { line: el, column: ec }
- }
- )
- );
- pendingWebpackIgnore = undefined;
- pendingWebpackInline = undefined;
- pendingHints = undefined;
- return;
- }
- pendingWebpackIgnore = options.webpackIgnore;
- pendingWebpackInline = options.webpackInline;
- if (
- options.webpackPrefetch !== undefined ||
- options.webpackPreload !== undefined ||
- options.webpackFetchPriority !== undefined
- ) {
- const s = locConverter.get(start);
- const e = locConverter.get(end);
- const parsedHints = parseResourceHintOptions(options, module, {
- start: { line: s.line, column: s.column },
- end: { line: e.line, column: e.column }
- });
- pendingHints =
- parsedHints.prefetch !== undefined ||
- parsedHints.preload !== undefined ||
- parsedHints.fetchPriority !== undefined
- ? parsedHints
- : undefined;
- } else {
- pendingHints = undefined;
- }
- },
- [NodeType.Doctype]: (path) => {
- // only visited when head injection is active (see `skip` below)
- if (doctypeEnd === -1) doctypeEnd = path.end();
- pendingWebpackIgnore = undefined;
- pendingWebpackInline = undefined;
- pendingHints = undefined;
- },
- [NodeType.Element]: {
- // Children (and `<template>` content) are walked by the
- // processor after `enter`; `exit` clears a pending
- // `webpackIgnore` once they're done.
- enter: (path) => {
- const ignore = pendingWebpackIgnore === true;
- const forceInline = pendingWebpackInline === true;
- pendingWebpackIgnore = undefined;
- pendingWebpackInline = undefined;
- // `<head>` anchors are collected for every page — they place
- // `inject: "head"` siblings and resource hints; the
- // title/meta/base facts only when configured. Structural, so it
- // runs even for `webpackIgnore`d elements.
- {
- const name = path.tagName();
- if (name === "template" && path.namespace() === NS_HTML) {
- templateDepth++;
- }
- if (headTemplateDepth > 0) {
- if (name === "template" && path.namespace() === NS_HTML) {
- headTemplateDepth++;
- }
- } else if (inHead) {
- if (headOptions !== undefined && path.namespace() === NS_HTML) {
- switch (name) {
- case "title":
- headHasTitle = true;
- break;
- case "base":
- headHasBase = true;
- break;
- case "meta": {
- if (path.findAttribute("charset") !== 0) {
- headCharsetEnd = path.tagEnd();
- } else {
- const httpEquiv = path.findAttribute("http-equiv");
- const contentAttr =
- httpEquiv !== 0 &&
- decodeEntities(
- path.attributeValue(httpEquiv),
- true
- ).toLowerCase() === "content-type"
- ? path.findAttribute("content")
- : 0;
- if (
- contentAttr !== 0 &&
- CHARSET_DECLARATION_REGEXP.test(
- decodeEntities(
- path.attributeValue(contentAttr),
- true
- )
- )
- ) {
- headCharsetEnd = path.tagEnd();
- }
- }
- const nameAttr = path.findAttribute("name");
- const propertyAttr = path.findAttribute("property");
- if (nameAttr !== 0 || propertyAttr !== 0) {
- if (headMetaNames === undefined) {
- headMetaNames = new Set();
- }
- if (nameAttr !== 0) {
- headMetaNames.add(
- decodeEntities(
- path.attributeValue(nameAttr),
- true
- ).toLowerCase()
- );
- }
- if (propertyAttr !== 0) {
- headMetaNames.add(
- decodeEntities(
- path.attributeValue(propertyAttr),
- true
- ).toLowerCase()
- );
- }
- }
- break;
- }
- case "template":
- headTemplateDepth++;
- break;
- }
- } else if (
- name === "template" &&
- path.namespace() === NS_HTML
- ) {
- headTemplateDepth++;
- }
- if (path.end() > headLastEnd) headLastEnd = path.end();
- } else if (!headSeen && path.namespace() === NS_HTML) {
- if (name === "html") {
- htmlTagEnd = path.tagEnd();
- } else if (name === "head") {
- headSeen = true;
- inHead = true;
- // explicit head has real offsets; an implicit one (head
- // tags are optional in HTML5) anchors after `<html>`,
- // the doctype, or the document start
- const tagEnd = path.tagEnd();
- headStart =
- tagEnd > 0
- ? tagEnd
- : htmlTagEnd > 0
- ? htmlTagEnd
- : doctypeEnd > 0
- ? doctypeEnd
- : 0;
- headLastEnd = headStart;
- }
- }
- }
- if (ignore) {
- return;
- }
- const elementName = path.tagName();
- const attributeCount = path.attributeCount();
- const elementStart = path.start();
- const tagEnd = path.tagEnd();
- const nameEnd = path.nameEnd();
- // The first `<base href>` freezes the base for later URLs — but
- // only one in the real document tree, not inside an inert
- // `<template>` (whose contents are a separate fragment).
- if (
- documentBase === undefined &&
- templateDepth === 0 &&
- elementName === "base" &&
- path.namespace() === NS_HTML
- ) {
- const hrefAttr = path.findAttribute("href");
- if (hrefAttr !== 0) resolveDocumentBase(path, hrefAttr);
- }
- // Rebind the parse-scoped lazy attribute map to this element.
- attrMapPath = path;
- attrMapCount = attributeCount;
- currentAttributesMap = undefined;
- // Each matched attribute is a source; the element body (inline
- // `<style>`/`<script>`) is one more — content rather than an attribute.
- // All are dispatched by the same `switch (type)`.
- const sources =
- this.sourcesByTag[elementName] || this.sourcesByTag[ANY_TAG];
- const bodyItem = CONTENT_SOURCES[elementName];
- // Iterate the attributes, then one extra step for the element
- // body (`i === attributeCount`); the attribute ref is only read on the
- // attribute steps, so it stays in bounds.
- for (let i = 0; i < attributeCount + 1; i++) {
- const content = i === attributeCount;
- /** @type {SourceType} */
- let type;
- /** @type {string} */
- let value;
- /** @type {number} */
- let start;
- /** @type {number} */
- let end;
- /** @type {import("./syntax").HtmlAttributeRef} */
- let attr = 0;
- if (content) {
- if (!bodyItem) continue;
- if (bodyItem.filter && !bodyItem.filter(getAttributesMap())) {
- continue;
- }
- // `skip.text` drops the body `Text` node; the raw content
- // span is [`tagEnd`, `contentEnd`] on the element.
- const bodyStart = tagEnd;
- const bodyEnd = path.contentEnd();
- if (bodyEnd <= bodyStart) continue;
- value = source.slice(bodyStart, bodyEnd);
- if (value.trim() === "") continue;
- start = bodyStart;
- end = bodyEnd;
- type =
- typeof bodyItem.type === "function"
- ? bodyItem.type(getAttributesMap(), css)
- : bodyItem.type;
- } else {
- attr = path.attributeAt(i);
- const item = sources[path.attributeName(attr)];
- if (!item) continue;
- if (
- item.namespace !== undefined &&
- path.namespace() !== item.namespace
- ) {
- continue;
- }
- // Adoption-agency clones carry no offsets; skip blank values.
- if (path.attributeValueStart(attr) === -1) {
- continue;
- }
- value = path.attributeValue(attr);
- if (!value || !/\S/.test(value)) continue;
- const filter = item.filter;
- if (filter) {
- // The filter also sees the decoded value (the browser tokenizes it
- // decoded, e.g. `url(`), so a value-only check needn't re-read it.
- const decoded = value.includes("&")
- ? decodeEntities(value, true)
- : value;
- if (!filter(getAttributesMap(), decoded)) continue;
- }
- start = path.attributeValueStart(attr);
- end = path.attributeValueEnd(attr);
- type =
- typeof item.type === "function"
- ? item.type(getAttributesMap(), css)
- : item.type;
- }
- // A resolved `type` selects the parse algorithm and the dependency
- // kind — the only dispatch in the parser.
- switch (type) {
- // Inline CSS — a `<style>` body (raw, a full stylesheet) or an
- // attribute value (decoded, re-escaped on write-back; `*-attribute`
- // is a declaration list). Needs `experiments.css`.
- case "stylesheet-style":
- case "stylesheet-style-attribute": {
- if (!css) break;
- const cssText = content ? value : decodeEntities(value, true);
- if (cssText.trim() === "") break;
- const dep = new HtmlInlineStyleDependency(
- dataUri("text/css", cssText),
- [start, end],
- content ? false : type === "stylesheet-style-attribute",
- !content
- );
- setLoc(dep, start, end);
- module.addDependency(dep);
- module.addCodeGenerationDependency(dep);
- break;
- }
- // `<iframe srcdoc>`: an entity-encoded HTML document. Feed the
- // decoded markup back through the HTML pipeline as a nested
- // `data:text/html` module. Per spec the value is a full document
- // whose URL is `about:srcdoc`, so its base URL is inherited from
- // this document — asset URLs resolve against this file's context.
- case "srcdoc": {
- const htmlText = decodeEntities(value, true);
- // Only spin up a nested module when there is markup that can
- // reference an asset; text/formatting-only markup is identical
- // after rewriting (see `SRCDOC_ASSET_REGEXP`).
- if (
- !htmlText.includes("<") ||
- !SRCDOC_ASSET_REGEXP.test(htmlText)
- ) {
- break;
- }
- const dep = new HtmlInlineHtmlDependency(
- dataUri("text/html", htmlText),
- [start, end]
- );
- setLoc(dep, start, end);
- module.addDependency(dep);
- module.addCodeGenerationDependency(dep);
- break;
- }
- // Inline `<script>` body — bundled as its own entry chunk.
- case "script":
- case "script-module":
- if (content) {
- const scriptType =
- /** @type {"script" | "script-module"} */ (type);
- const request = dataUri("text/javascript", value);
- const entryName = `__html_${moduleHash}_${nextEntryIndex++}`;
- const dep = new HtmlInlineScriptDependency(
- request,
- nameEnd,
- [start, end],
- entryName,
- scriptType === "script-module" ? "esm" : "commonjs"
- );
- setLoc(dep, start, end);
- module.addPresentationalDependency(dep);
- reconcileScriptTypeAttr(
- path,
- path.findAttribute("type"),
- nameEnd,
- scriptType,
- source
- );
- (scriptType === "script"
- ? scriptEntries
- : scriptModuleEntries
- ).push({
- request,
- entryName,
- type: scriptType
- });
- break;
- }
- // falls through — external `<script src>` joins the URL-bearing types
- case "src":
- case "srcset":
- case "css-url":
- case "msapplication-task":
- case "modulepreload":
- case "stylesheet":
- case "html":
- case "preload":
- case "prefetch": {
- // Parse the value into one or more URLs (decoding character
- // references, mapping spans back to raw offsets), then emit a plain
- // asset reference or an entry chunk per URL.
- const parse =
- type === "srcset"
- ? parseSrcset
- : type === "css-url"
- ? parseCssUrls
- : type === "msapplication-task"
- ? parseMsapplicationTask
- : parseSrc;
- let text = value;
- /** @type {number[] | undefined} */
- let map;
- if (value.includes("&")) {
- ({ text, map } = decodeEntities(value, true, true));
- if (!/\S/.test(text)) break;
- }
- /** @type {ParsedSource[] | undefined} */
- let parsed;
- try {
- parsed = parse(text);
- } catch (err) {
- const ds = locConverter.get(start);
- const de = locConverter.get(end);
- module.addError(
- new (getModuleDependencyError())(
- module,
- new WebpackError(
- `Bad value for attribute "${path.attributeName(
- attr
- )}" on element "${elementName}": ${
- /** @type {Error} */ (err).message
- }`
- ),
- {
- start: { line: ds.line, column: ds.column },
- end: { line: de.line, column: de.column }
- }
- )
- );
- break;
- }
- if (!parsed) break;
- for (const [url, us, ue] of parsed) {
- // Internal `url(#id)` / fragment-only refs aren't assets.
- if (!url || url.startsWith("#")) continue;
- // Resolve the request against `<base href>`. Only relative
- // URLs are affected (scheme / `//` / `/` URLs ignore the
- // base); an external base drops them from the build.
- let request = url;
- if (
- documentBase &&
- url.charCodeAt(0) !== CC_SLASH &&
- !ABSOLUTE_URL_SCHEME_REGEXP.test(url)
- ) {
- if (baseIsExternal) continue;
- request = baseDir + url;
- }
- const s = start + (map ? map[us] : us);
- const e = start + (map ? map[ue] : ue);
- // `html` links the URL as its own emitted page: the same
- // `HtmlEntryDependency` as `<script src>` / `<link>`, but the `html`
- // kind rewrites to the page's filename and emits no sibling tags.
- // Recorded as an independent entry (no `dependOn`), like an entry point.
- if (type === "html") {
- const entryName = `__html_${moduleHash}_${nextEntryIndex++}`;
- const dep = new HtmlEntryDependency(
- request,
- [s, e],
- entryName,
- undefined,
- "html"
- );
- setLoc(dep, s, e);
- module.addPresentationalDependency(dep);
- htmlLinkEntries.push({ request, entryName, type });
- continue;
- }
- // `rel="preload"/"prefetch"` of a bundled resource: an
- // independent, non-executing entry whose `href` is rewritten to
- // the built chunk's URL (CSS for `as="style"`, JS otherwise). No
- // sibling tags — a resource hint references exactly one URL.
- if (type === "preload" || type === "prefetch") {
- const as = getAttributeValue(getAttributesMap(), "as");
- const asStyle = as
- ? as.trim().toLowerCase() === "style"
- : false;
- const entryName = `__html_${moduleHash}_${nextEntryIndex++}`;
- // Tag metadata so the template can mirror `crossorigin`/
- // `integrity` onto a native `preload` `<link>` (integrity-
- // eligible per the SRI spec). `prefetch` gets none — the spec
- // doesn't list it — so its `integrity` is left uncaptured.
- const isNativeLink = elementName === "link";
- const dep = new HtmlEntryDependency(
- request,
- [s, e],
- entryName,
- asStyle ? "css-import" : undefined,
- type,
- elementStart,
- tagEnd,
- isNativeLink,
- "",
- nameEnd,
- isNativeLink && path.findAttribute("crossorigin") !== 0,
- -1,
- type === "preload" && isNativeLink
- ? captureIntegrityRange(path, source, elementStart)
- : null,
- null
- );
- setLoc(dep, s, e);
- module.addPresentationalDependency(dep);
- (type === "preload"
- ? preloadEntries
- : prefetchEntries
- ).push({
- request,
- entryName,
- type,
- css: asStyle
- });
- continue;
- }
- // `src`/`srcset`/`css-url`/`msapplication-task` are plain assets;
- // the rest are narrowed to entry types here and share one
- // `HtmlEntryDependency`, recorded for HtmlModulesPlugin.
- if (
- type === "script" ||
- type === "script-module" ||
- type === "modulepreload" ||
- type === "stylesheet"
- ) {
- const entryName = `__html_${moduleHash}_${nextEntryIndex++}`;
- const willBeModuleScript =
- type === "script-module" ||
- (outputModule && type === "script");
- /** @type {EntrySourceType} */
- const elementKind =
- type === "modulepreload"
- ? "modulepreload"
- : type === "stylesheet"
- ? "stylesheet"
- : willBeModuleScript
- ? "script-module"
- : "script";
- const entryCategory =
- type === "stylesheet"
- ? "css-import"
- : type === "script-module" || type === "modulepreload"
- ? "esm"
- : undefined;
- // Native = the loading element is the tag the template clones
- // verbatim (`<script>` / `<link>`); a custom element is not.
- const nativeTag =
- elementKind === "stylesheet" ||
- elementKind === "modulepreload"
- ? "link"
- : "script";
- // CSP/fetch attributes the template copies onto siblings.
- let copyableAttrsText = "";
- let hasOwnCrossOrigin = false;
- if (elementStart >= 0) {
- for (const copyableName of COPYABLE_SIBLING_ATTRS) {
- const copyableAttr = path.findAttribute(copyableName);
- if (copyableAttr !== 0) {
- copyableAttrsText += attrSourceSpan(
- path,
- source,
- copyableAttr
- );
- if (copyableName === "crossorigin") {
- hasOwnCrossOrigin = true;
- }
- }
- }
- }
- // Blocking-ness of the tag as *emitted*:
- // `reconcileScriptTypeAttr` upgrades every script entry
- // to `type="module"` under `output.module` and strips an
- // authored `type="module"` otherwise — so only non-module
- // output without `defer`/`async` blocks. Non-native
- // elements anchor conservatively (their synthesized
- // siblings may be plain blocking `<script>`s).
- if (
- firstBlockingScriptStart === -1 &&
- (type === "script" || type === "script-module") &&
- !outputModule &&
- (elementName !== nativeTag ||
- (path.findAttribute("defer") === 0 &&
- path.findAttribute("async") === 0))
- ) {
- firstBlockingScriptStart = elementStart;
- }
- // Sibling-clone attribute edits, captured from the parsed
- // attributes (offsets relative to the tag) so the template
- // needn't re-parse the tag text: `integrity` is dropped
- // (content-specific) and `script-module` clones are forced
- // to `type="module"` (its value range, else inserted).
- const integrityRange =
- elementStart >= 0 && elementName === nativeTag
- ? captureIntegrityRange(path, source, elementStart)
- : null;
- /** @type {Range | null} */
- let typeValueRange = null;
- if (
- elementStart >= 0 &&
- elementName === nativeTag &&
- elementKind === "script-module"
- ) {
- const typeAttr = path.findAttribute("type");
- if (
- typeAttr !== 0 &&
- path.attributeValueStart(typeAttr) >= 0
- ) {
- typeValueRange = [
- path.attributeValueStart(typeAttr) - elementStart,
- path.attributeValueEnd(typeAttr) - elementStart
- ];
- }
- }
- const dep = new HtmlEntryDependency(
- request,
- [s, e],
- entryName,
- entryCategory,
- elementKind,
- elementStart,
- tagEnd,
- elementName === nativeTag,
- copyableAttrsText,
- nameEnd,
- hasOwnCrossOrigin,
- firstBlockingScriptStart,
- integrityRange,
- typeValueRange,
- forceInline,
- headSeen && headStart > 0 ? headStart : -1,
- headSeen && headLastEnd > 0 ? headLastEnd : -1
- );
- setLoc(dep, s, e);
- module.addPresentationalDependency(dep);
- if (
- elementName === "script" &&
- (type === "script" || type === "script-module")
- ) {
- reconcileScriptTypeAttr(
- path,
- path.findAttribute("type"),
- nameEnd,
- type,
- source
- );
- }
- (type === "script"
- ? scriptEntries
- : type === "script-module"
- ? scriptModuleEntries
- : type === "stylesheet"
- ? stylesheetEntries
- : modulePreloadEntries
- ).push({ request, entryName, type });
- } else {
- const dep = new HtmlSourceDependency(request, [s, e]);
- ResourceHintPlugin.applyDefaults(
- dep,
- ResourceHintPlugin.matchUrlHints(urlHints, request)
- );
- // `<!-- webpackPrefetch: true -->` (etc.) immediately
- // before this tag wins over the project-wide default.
- if (pendingHints) {
- ResourceHintPlugin.applyParsedHints(dep, pendingHints);
- pendingHints = undefined;
- }
- setLoc(dep, s, e);
- module.addDependency(dep);
- module.addCodeGenerationDependency(dep);
- }
- }
- break;
- }
- }
- }
- },
- exit: (path) => {
- if (path.namespace() === NS_HTML) {
- const name = path.tagName();
- if (name === "template") {
- if (templateDepth > 0) templateDepth--;
- if (headTemplateDepth > 0) headTemplateDepth--;
- } else if (name === "head") {
- inHead = false;
- }
- }
- pendingWebpackIgnore = undefined;
- pendingWebpackInline = undefined;
- pendingHints = undefined;
- }
- }
- })
- // Skip AST output this walk never reads: `text` (script/style bodies are
- // read by offset via `contentEnd`) and `doctype` — unless head injection
- // needs the doctype as an insertion anchor; keep `comments` for
- // magic comments (`webpackIgnore`).
- .process(source, {
- fragmentContext: this.fragmentContext,
- skip: { text: true, doctype: headOptions === undefined }
- });
- // `output.html` head injection: existing tags win, injected values are
- // escaped, insertions at the same offset are merged to keep their order.
- if (headOptions !== undefined && headSeen) {
- const { title, meta, base } = headOptions;
- let startTags = "";
- if (meta && meta.charset && headCharsetEnd === -1) {
- startTags += `<meta charset="${escapeAttribute(meta.charset)}">`;
- }
- // <base> must follow the charset declaration per spec
- let baseAfterCharset = "";
- if (base && !headHasBase) {
- if (headCharsetEnd !== -1) baseAfterCharset = baseTag(base);
- else startTags += baseTag(base);
- }
- let endTags = "";
- if (meta) {
- for (const [name, value] of Object.entries(meta)) {
- if (
- name === "charset" ||
- (headMetaNames !== undefined &&
- headMetaNames.has(name.toLowerCase()))
- ) {
- continue;
- }
- endTags += metaTag(name, value);
- }
- }
- if (title && !headHasTitle) {
- endTags += `<title>${escapeText(title)}</title>`;
- }
- /** @type {[number, string][]} */
- const inserts = [];
- if (startTags) inserts.push([headStart, startTags]);
- if (baseAfterCharset) inserts.push([headCharsetEnd, baseAfterCharset]);
- if (endTags) inserts.push([headLastEnd, endTags]);
- inserts.sort((a, b) => a[0] - b[0]);
- for (let i = 0; i < inserts.length; i++) {
- const at = inserts[i][0];
- let tags = inserts[i][1];
- while (i + 1 < inserts.length && inserts[i + 1][0] === at) {
- tags += inserts[++i][1];
- }
- module.addPresentationalDependency(new ConstDependency(tags, at));
- }
- }
- const buildInfo = /** @type {HtmlModuleBuildInfo} */ (module.buildInfo);
- buildInfo.strict = true;
- if (baseUrlPrefix !== undefined) buildInfo.baseUrlPrefix = baseUrlPrefix;
- // Hand off the collected entries to HtmlModulesPlugin; it creates the
- // real compilation entries during the finishMake hook. The `script`
- // and `script-module` groups are chained via a leader-only dependOn
- // so they share a runtime; `modulepreload` and `stylesheet` entries
- // are emitted as independent entries since `<link rel=modulepreload>`
- // must preload without running and stylesheets have no runtime. `html`
- // links are independent page entries too.
- if (
- scriptEntries.length > 0 ||
- scriptModuleEntries.length > 0 ||
- modulePreloadEntries.length > 0 ||
- stylesheetEntries.length > 0 ||
- htmlLinkEntries.length > 0 ||
- preloadEntries.length > 0 ||
- prefetchEntries.length > 0
- ) {
- buildInfo.htmlEntries = {
- script: scriptEntries,
- "script-module": scriptModuleEntries,
- modulepreload: modulePreloadEntries,
- stylesheet: stylesheetEntries,
- html: htmlLinkEntries,
- preload: preloadEntries,
- prefetch: prefetchEntries
- };
- }
- const buildMeta = /** @type {BuildMeta} */ (state.module.buildMeta);
- buildMeta.exportsType = "default";
- state.module.addDependency(new StaticExportsDependency(["default"], true));
- return state;
- }
- }
- HtmlParser.buildHeadTags = buildHeadTags;
- module.exports = HtmlParser;
|