/*
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;