/* MIT License http://www.opensource.org/licenses/mit-license.php */ "use strict"; const { CSS_IMPORT_TYPE, CSS_TYPE, JAVASCRIPT_TYPE } = require("../ModuleSourceTypeConstants"); const ResourceHintPlugin = require("../prefetch/ResourceHintPlugin"); const ResourceHintRuntimeModule = require("../prefetch/ResourceHintRuntimeModule"); const makeSerializable = require("../util/makeSerializable"); const memoize = require("../util/memoize"); const ModuleDependency = require("./ModuleDependency"); // only the template needs it, and that runs only for a real html module const getHtmlGenerator = memoize(() => require("../html/HtmlGenerator")); /** @import { ReplaceSource } from "webpack-sources" */ /** @import Chunk from "../Chunk" */ /** @import ChunkGraph from "../ChunkGraph" */ /** @import Dependency from "../Dependency" */ /** @import { DependencyTemplateContext } from "../DependencyTemplate" */ /** @import Entrypoint from "../Entrypoint" */ /** @import { Range } from "../javascript/JavascriptParser" */ /** @typedef {"script" | "script-module" | "modulepreload" | "stylesheet" | "html" | "preload" | "prefetch"} HtmlEntryElementKind */ /** @typedef {import("../serialization/ObjectMiddleware").ObjectDeserializerContext<[string, string, HtmlEntryElementKind, number, number, boolean, string, number, boolean, number, (Range | null), (Range | null), boolean, number, number]>} ObjectDeserializerContext */ /** @typedef {import("../serialization/ObjectMiddleware").ObjectSerializerContext<[string, string, HtmlEntryElementKind, number, number, boolean, string, number, boolean, number, (Range | null), (Range | null), boolean, number, number]>} ObjectSerializerContext */ class HtmlEntryDependency extends ModuleDependency { /** * Creates an instance of HtmlEntryDependency. * @param {string} request request * @param {Range} range range of the attribute value in the source * @param {string} entryName name of the entry this script src is bundled into * @param {string=} category dependency category used for resolving and grouping * @param {HtmlEntryElementKind=} elementKind shape of the originating HTML element; used when expanding sibling tags for split/runtime chunks * @param {number=} tagStart position of the opening `<` of the originating tag in the source; sibling tags emitted for additional entry chunks are inserted right before this * @param {number=} tagOpenEnd position of the character immediately after the opening tag's `>` in the source; combined with `tagStart` this lets the template clone the original opening tag verbatim (preserving attributes like `nonce`, `crossorigin`, `referrerpolicy`, `defer`, `async`) when generating sibling tags * @param {boolean=} tagIsNative whether the originating element is the native tag for `elementKind` (`` (as `cloneTagWithUrl` does for a real ``, * so synthesize a real script element instead — copying only the CSP/fetch * attributes (`extra`, captured at parse time), since the custom element's * other attributes have no defined meaning on a ``; }; /** * Clone the original `` for script tags) */ const cloneTagWithUrl = ( originalTag, srcStartInTag, srcEndInTag, newUrl, elementKind, crossOrigin, tagNameEndInTag, integrityRange, typeValueRange, integrity ) => { const isLink = elementKind === "modulepreload" || elementKind === "stylesheet"; /** @type {{ start: number, end: number, text: string }[]} */ const edits = [{ start: srcStartInTag, end: srcEndInTag, text: newUrl }]; // Drop the author's `integrity` — content-specific, wrong for another // chunk's file. A correct per-chunk one is re-inserted below from // `output.html.integrity`. if (integrityRange) { edits.push({ start: integrityRange[0], end: integrityRange[1], text: "" }); } // Inserted right after the tag name: `crossorigin` from // `output.crossOriginLoading`, then a forced `type="module"` when the tag // has no `type` value to rewrite in place. let afterName = crossOrigin; if (elementKind === "script-module") { if (typeValueRange) { edits.push({ start: typeValueRange[0], end: typeValueRange[1], text: "module" }); } else { afterName += ' type="module"'; } } // Per-chunk `integrity` sentinel goes in alongside `crossorigin`/`type`. afterName += integrity; if (afterName) { edits.push({ start: tagNameEndInTag, end: tagNameEndInTag, text: afterName }); } edits.sort((a, b) => b.start - a.start); let body = originalTag; for (const edit of edits) { body = body.slice(0, edit.start) + edit.text + body.slice(edit.end); } // `` is a void element — no closing tag. ``. return isLink ? body : `${body}`; }; // ES5 `` polyfill (Safari <17, Firefox <115, …): // on browsers without native support, fetch each modulepreload target with the // tag's own credentials/integrity so the pending module import reuses it. The // `.ep` guard makes it idempotent, and the observer catches any later-inserted // links. Emitted only when the target environment lacks native support. const MODULEPRELOAD_POLYFILL = [ "(function(){", 'var s=document.createElement("link").relList;', 'if(s&&s.supports&&s.supports("modulepreload"))return;', "function f(e){", "if(e.ep)return;e.ep=1;", "var o={};", "if(e.integrity)o.integrity=e.integrity;", "if(e.referrerPolicy)o.referrerPolicy=e.referrerPolicy;", 'o.credentials=e.crossOrigin==="use-credentials"?"include":e.crossOrigin==="anonymous"?"omit":"same-origin";', "fetch(e.href,o)}", "var l=document.querySelectorAll('link[rel=\"modulepreload\"]');", "for(var i=0;i` per rendered page. Keyed by the `ReplaceSource` of a // single HTML code-generation pass (shared by all of a page's entry tags, fresh // per pass), so multi-``; } const url = getHtmlGenerator().makeChunkUrlSentinel(chunk, kind); const integrityAttr = integrityOn ? ` integrity="${getHtmlGenerator().makeChunkIntegritySentinel(chunk, kind)}"` : ""; if (kind === "css") { // A CSS chunk is always loaded via ``. // Clone only when the entry tag is itself a native `` (so attributes like `media` carry over); // a ``); } // Insert right after the `` open tag (parser-derived) so the // hints are the document's first fetches; fall back to the first // blocking script (else the entry tag) when the page has no // written `` (e.g. an HTML fragment). const hintAt = dep.headOpenEnd >= 0 ? dep.headOpenEnd : dep.cssAnchor === -1 ? dep.tagStart : dep.cssAnchor; source.insert(hintAt, finalHints.join("")); } } } }; makeSerializable( HtmlEntryDependency, "webpack/lib/dependencies/HtmlEntryDependency" ); module.exports = HtmlEntryDependency;