/* MIT License http://www.opensource.org/licenses/mit-license.php */ "use strict"; const { RawSource, ReplaceSource } = require("webpack-sources"); const ConcatenationScope = require("../ConcatenationScope"); const Generator = require("../Generator"); const { HTML_TYPE, JAVASCRIPT_TYPE, JAVASCRIPT_TYPES } = require("../ModuleSourceTypeConstants"); const RuntimeGlobals = require("../RuntimeGlobals"); const Template = require("../Template"); const CssModulesPlugin = require("../css/CssModulesPlugin"); const WebpackError = require("../errors/WebpackError"); const { getUndoPath } = require("../util/identifier"); const memoize = require("../util/memoize"); const { PUBLIC_PATH_AUTO } = require("../util/publicPathPlaceholder"); const { VOID } = require("./data"); const getCreateNodeHash = memoize(() => require("crypto").createHash); const getCssSyntax = memoize(() => require("../css/syntax")); const getHtmlSyntax = memoize(() => require("./syntax")); // Lazy: the dependency templates need this module's sentinel helpers on every // build, but the HTML tokenizer is only reached once HTML is actually emitted. // Lazy so loading the HTML generator doesn't pull in the whole CSS pipeline. // The CSS tokenizer, used to locate `url(...)` when rebasing inlined stylesheets. // node's crypto — real SHA hashes for Content-Security-Policy source lists // (the wasm hashes are xxhash, unusable for CSP), loaded only when CSP is on. // HTML void elements — serialized without a closing tag when a tag descriptor // (`output.html` `injectTags`) doesn't say otherwise. /** * Serializes an attribute map: `true` → bare attribute, `false`/`undefined`/ * `null` → omitted, else a quoted (escaped) value. * @param {Record | undefined} attrs attributes * @returns {string} the leading-space-prefixed attribute string */ const serializeAttrs = (attrs) => { let out = ""; if (attrs) { const { escapeAttribute } = getHtmlSyntax(); for (const name of Object.keys(attrs)) { const value = attrs[name]; if (value === false || value === undefined || value === null) continue; out += value === true ? ` ${name}` : ` ${name}="${escapeAttribute(`${value}`)}"`; } } return out; }; /** * Serializes one tag descriptor. `attrs` values: `true` → bare attribute, * `false`/`undefined` → omitted, else a quoted (escaped) value. * @param {HtmlTagDescriptor} tag the descriptor * @returns {string} the tag's HTML */ const serializeTag = (tag) => { const attrs = serializeAttrs(tag.attrs); const isVoid = tag.voidTag !== undefined ? tag.voidTag : VOID.has(tag.tag.toLowerCase()); return isVoid ? `<${tag.tag}${attrs}>` : `<${tag.tag}${attrs}>${tag.children || ""}`; }; /** * Canonical string of an attribute map (present keys, sorted) — used to detect * whether an `transformTags` plugin changed a tag, so unchanged tags stay untouched. * `false`/`undefined`/`null` count as absent. * @param {Record} attrs attributes * @returns {string} a comparable snapshot */ const snapshotAttrs = (attrs) => JSON.stringify( Object.keys(attrs) .filter((name) => { const value = attrs[name]; return value !== false && value !== undefined && value !== null; }) .sort() .map((name) => [name, attrs[name]]) ); /** * Whether an attribute renders (present and not `false`/`undefined`/`null`). * @param {Record} attrs attributes * @param {string} name attribute name * @returns {boolean} true if it renders */ const attrPresent = (attrs, name) => { const value = attrs[name]; return value !== undefined && value !== false && value !== null; }; /** * Whether a ``'s attributes declare a Content-Security-Policy (case- * insensitive `http-equiv`), used to detect an author-declared CSP. * @param {Record} attrs attributes * @returns {boolean} true if it is a CSP meta */ const metaIsCsp = (attrs) => { for (const name of Object.keys(attrs)) { const value = attrs[name]; if ( name.toLowerCase() === "http-equiv" && typeof value === "string" && value.trim().toLowerCase() === "content-security-policy" ) { return true; } } return false; }; /** @import { Source } from "webpack-sources" */ /** @import { HtmlGeneratorOptions } from "../../declarations/WebpackOptions" */ /** @typedef {import("../../declarations/WebpackOptions").OutputHtmlOptions["csp"]} CspOption */ /** @import { HtmlTagDescriptor, HtmlMutableTag } from "./HtmlModulesPlugin" */ /** @typedef {"head" | "body"} HtmlTagLocation */ /** * @typedef {object} HtmlTagSpan * @property {number} start opening `<` offset * @property {number} openEnd offset just after the opening `>` * @property {number} end offset just after the whole element * @property {HtmlTagLocation} location the ``/`` region it sits in * @property {number} nameEnd offset after the tag name (nonce insertion point), `-1` for non-`