webpack
Version:
Packs ECMAScript/CommonJs/AMD modules for the browser. Allows you to split your codebase into multiple bundles, which can be loaded on demand. Supports loaders to preprocess files, i.e. json, jsx, es7, css, less, ... and your custom stuff.
1,237 lines • 50.1 kB
JavaScript
/*
MIT License http://www.opensource.org/licenses/mit-license.php
*/
"use strict";
const {
CSS_IMPORT_TYPE,
CSS_TYPE,
HTML_TYPE,
JAVASCRIPT_TYPE
} = require("../../module/ModuleSourceTypeConstants");
const ResourceHintPlugin = require("../../prefetch/ResourceHintPlugin");
const ResourceHintRuntimeModule = require("../../prefetch/ResourceHintRuntimeModule");
const makeSerializable = require("../../util/makeSerializable");
const memoize = require("../../util/memoize");
const ModuleDependency = require("../core/ModuleDependency");
// only the template needs it, and that runs only for a real html module
const getHtmlGenerator = memoize(() => require("../../html/HtmlGenerator"));
// only read when a page carries stylesheets, and it drags the css subsystem in
const getCssModulesPlugin = memoize(() =>
require("../../css/CssModulesPlugin")
);
/** @import { ReplaceSource } from "webpack-sources" */
/** @import Chunk from "../../graph/Chunk" */
/** @import ChunkGraph from "../../graph/ChunkGraph" */
/** @import Dependency, { DependencyKinds } from "../../graph/Dependency" */
/** @import { DependencyTemplateContext } from "../../template/DependencyTemplate" */
/** @import Entrypoint from "../../graph/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` (`<script>` / `<link>`); decided at parse time from the tag name so the template needn't re-parse the source text. A custom element mapped to a `script`/`stylesheet` source `type` is non-native and gets a freshly synthesized sibling tag instead of a verbatim clone
* @param {string=} copyableAttrsText the originating tag's `nonce`/`crossorigin`/`referrerpolicy` attribute source spans (leading-space-prefixed, in that fixed order), captured at parse time so synthesized sibling `<link>`/`<script>` tags carry the same CSP/fetch policy without the template re-parsing the tag text; empty when none are present
* @param {number=} tagNameEnd position right after the originating tag's name (e.g. after `<script`), captured at parse time so the template can insert a `crossorigin` attribute without re-scanning the tag text for the name boundary
* @param {boolean=} hasOwnCrossOrigin whether the originating tag already carries a `crossorigin` attribute; when true the author's value wins and `output.crossOriginLoading` is not applied
* @param {number=} cssAnchor source offset of the first classic blocking `<script>` tag (not `defer`/`async`/module — those execute after parsing and after pending stylesheets); injected stylesheet `<link>`s are kept ahead of it. -1 when the document has no blocking script, letting CSS follow `defer`/module script tags (the order Vite emits)
* @param {Range | null=} integrityRange source span of the originating tag's `integrity` attribute (offsets relative to `tagStart`, leading whitespace and quotes included), captured at parse time so cloned sibling tags can drop it without re-parsing; null when absent
* @param {Range | null=} typeValueRange source span of the originating tag's `type` attribute value (offsets relative to `tagStart`), captured at parse time so `script-module` sibling clones can force `type="module"` in place; null when there is no value to rewrite (a `type="module"` is then inserted instead)
* @param {boolean=} forceInline `<!-- webpackInline: true -->` was set before this tag; takes precedence over `output.html.inline`
* @param {number=} headOpenEnd position just inside the (possibly implicit) `<head>` — after its opening tag, else after `<html>` or the doctype — captured from the parse so the template needn't re-scan the source (a text scan would match markup inside comments); resource hints are inserted here. -1 when unknown
* @param {number=} headAnchor position right after the last node inside `<head>` (the same anchor `output.html` title/meta injection uses); `inject: "head"` sibling tags are inserted here. -1 when the document has no usable head (insertion then falls back to the entry tag)
*/
constructor(
request,
range,
entryName,
category,
elementKind,
tagStart,
tagOpenEnd,
tagIsNative,
copyableAttrsText,
tagNameEnd,
hasOwnCrossOrigin,
cssAnchor,
integrityRange,
typeValueRange,
forceInline,
headOpenEnd,
headAnchor
) {
super(request);
this.range = range;
/** @type {string} */
this.entryName = entryName;
/** @type {string} */
this._category = category || "commonjs";
/** @type {HtmlEntryElementKind} */
this.elementKind = elementKind || "script";
/** @type {number} */
this.tagStart = tagStart === undefined ? -1 : tagStart;
/** @type {number} */
this.tagOpenEnd = tagOpenEnd === undefined ? -1 : tagOpenEnd;
/** @type {boolean} */
this.tagIsNative = tagIsNative !== false;
/** @type {string} */
this.copyableAttrsText = copyableAttrsText || "";
/** @type {number} */
this.tagNameEnd = tagNameEnd === undefined ? -1 : tagNameEnd;
/** @type {boolean} */
this.hasOwnCrossOrigin = hasOwnCrossOrigin === true;
/** @type {number} */
this.cssAnchor = cssAnchor === undefined ? -1 : cssAnchor;
/** @type {Range | null} */
this.integrityRange = integrityRange || null;
/** @type {Range | null} */
this.typeValueRange = typeValueRange || null;
/** @type {boolean} */
this.forceInline = forceInline === true;
/** @type {number} */
this.headOpenEnd = headOpenEnd === undefined ? -1 : headOpenEnd;
/** @type {number} */
this.headAnchor = headAnchor === undefined ? -1 : headAnchor;
}
get type() {
return "html entry";
}
get category() {
return this._category;
}
/**
* Serializes this instance into the provided serializer context.
* @param {ObjectSerializerContext} context context
*/
serialize(context) {
context
.write(this.entryName)
.write(this._category)
.write(this.elementKind)
.write(this.tagStart)
.write(this.tagOpenEnd)
.write(this.tagIsNative)
.write(this.copyableAttrsText)
.write(this.tagNameEnd)
.write(this.hasOwnCrossOrigin)
.write(this.cssAnchor)
.write(this.integrityRange)
.write(this.typeValueRange)
.write(this.forceInline)
.write(this.headOpenEnd)
.write(this.headAnchor);
super.serialize(context);
}
/**
* Restores this instance from the provided deserializer context.
* @param {ObjectDeserializerContext} context context
*/
deserialize(context) {
this.entryName = context.read();
const c1 = context.rest;
this._category = c1.read();
const c2 = c1.rest;
this.elementKind = c2.read();
const c3 = c2.rest;
this.tagStart = c3.read();
const c4 = c3.rest;
this.tagOpenEnd = c4.read();
const c5 = c4.rest;
this.tagIsNative = c5.read();
const c6 = c5.rest;
this.copyableAttrsText = c6.read();
const c7 = c6.rest;
this.tagNameEnd = c7.read();
const c8 = c7.rest;
this.hasOwnCrossOrigin = c8.read();
const c9 = c8.rest;
this.cssAnchor = c9.read();
const c10 = c9.rest;
this.integrityRange = c10.read();
const c11 = c10.rest;
this.typeValueRange = c11.read();
const c12 = c11.rest;
this.forceInline = c12.read();
const c13 = c12.rest;
this.headOpenEnd = c13.read();
const c14 = c13.rest;
this.headAnchor = c14.read();
super.deserialize(c14.rest);
}
}
/**
* @param {Entrypoint} entrypoint entrypoint
* @returns {Chunk[]} every chunk this entrypoint needs in load order: the
* runtime chunk first (when `optimization.runtimeChunk` splits it off), then
* any intermediate chunks (e.g. from `optimization.splitChunks`), and finally
* the entry chunk itself. The entry chunk is always returned last so callers
* can identify it as the tag whose `src`/`href` attribute is being rewritten
* in place. Chunks that are already loaded by an ancestor (`dependOn`) entry's
* own script tag — i.e. the parent entrypoint's entry chunk *and* its runtime
* chunk — are skipped, otherwise they would be loaded twice when the same
* HTML contains tags for both the leader and the dependant entries.
*/
const getEntrypointChunksInLoadOrder = (entrypoint) => {
const entryChunk = entrypoint.getEntrypointChunk();
const runtimeChunk = entrypoint.getRuntimeChunk();
/** @type {Set<Chunk>} */
const chunksLoadedByAncestorTags = new Set();
/** @type {Set<import("../../graph/ChunkGroup")>} */
const visitedGroups = new Set();
/**
* Collects the chunks an ancestor entry's own tags already load.
* @param {import("../../graph/ChunkGroup")} group chunk group to walk up from
* @returns {void}
*/
const walk = (group) => {
if (visitedGroups.has(group)) return;
visitedGroups.add(group);
for (const parent of group.parentsIterable) {
if (
typeof (/** @type {Entrypoint} */ (parent).getEntrypointChunk) ===
"function"
) {
const parentEntry =
/** @type {Entrypoint} */
(parent).getEntrypointChunk();
if (parentEntry) chunksLoadedByAncestorTags.add(parentEntry);
const parentRuntime =
/** @type {Entrypoint} */
(parent).getRuntimeChunk();
if (parentRuntime) chunksLoadedByAncestorTags.add(parentRuntime);
}
walk(parent);
}
};
walk(entrypoint);
/** @type {Chunk[]} */
const ordered = [];
/** @type {Set<Chunk>} */
const seen = new Set();
/**
* Appends a chunk once, skipping the ones an ancestor entry already loads.
* @param {Chunk | null | undefined} chunk chunk to append
* @returns {void}
*/
const push = (chunk) => {
if (!chunk || seen.has(chunk) || chunk === entryChunk) return;
if (chunksLoadedByAncestorTags.has(chunk)) return;
seen.add(chunk);
ordered.push(chunk);
};
if (runtimeChunk !== entryChunk) {
push(runtimeChunk);
}
for (const chunk of entrypoint.chunks) {
push(chunk);
}
ordered.push(entryChunk);
return ordered;
};
/**
* Whether webpack will emit a `.js` file for this chunk that must be
* loaded with a `<script>` tag. Covers three independent reasons a
* chunk needs JS output: it owns one or more JS-source-type modules;
* it has entry modules whose source types include JavaScript (entry
* modules don't show up in `getChunkModulesIterableBySourceType` until
* they're connected as regular modules — this is why
* `JavascriptModulesPlugin#_chunkHasJs` checks them separately); or it
* is a runtime chunk — `chunk.hasRuntime()` — which produces a `.js`
* file holding the webpack runtime, but its `RuntimeModule`s live in
* a separate `runtimeModules` set and are *not* surfaced via
* `getChunkModulesIterableBySourceType`. Missing the runtime case
* would cause a `runtimeChunk`-split chunk to fall out of the
* `<script>` list and re-emerge after the chunks that depend on it,
* producing `__webpack_require__ is not defined` at load time.
* @param {Chunk} chunk chunk
* @param {ChunkGraph} chunkGraph chunk graph
* @returns {boolean} true if the chunk emits a `.js` file
*/
const chunkHasJs = (chunk, chunkGraph) => {
if (chunk.hasRuntime()) return true;
if (chunkGraph.getNumberOfEntryModules(chunk) > 0) {
for (const module of chunkGraph.getChunkEntryModulesIterable(chunk)) {
if (chunkGraph.getModuleSourceTypes(module).has(JAVASCRIPT_TYPE)) {
return true;
}
}
}
return Boolean(
chunkGraph.getChunkModulesIterableBySourceType(chunk, JAVASCRIPT_TYPE)
);
};
/**
* Whether webpack will emit a `.css` file for this chunk that must be
* loaded with a `<link rel="stylesheet">` tag. Matches
* `CssModulesPlugin.chunkHasCss` exactly — both regular CSS modules
* and pure `@import` placeholder modules count, since the latter
* still contribute a `.css` asset to the chunk.
* @param {Chunk} chunk chunk
* @param {ChunkGraph} chunkGraph chunk graph
* @returns {boolean} true if the chunk emits a `.css` file
*/
const chunkHasCss = (chunk, chunkGraph) =>
Boolean(chunkGraph.getChunkModulesIterableBySourceType(chunk, CSS_TYPE)) ||
Boolean(
chunkGraph.getChunkModulesIterableBySourceType(chunk, CSS_IMPORT_TYPE)
);
/**
* Build a fresh `<link rel="stylesheet" href="…">` for a CSS chunk that
* was pulled in by a `<script src>` entry — the originating tag was a
* `<script>`, but the chunk is CSS so cloning the script tag verbatim
* would produce nonsense (`<script src="…\.css">`). `extra` carries the
* `nonce`/`crossorigin`/`referrerpolicy` source spans captured from the
* original element at parse time so the same CSP and fetch policy applies;
* `defer`/`async`/`type` have no meaning on `<link>` and are dropped.
* @param {string} extra the copyable CSP/fetch attribute source text (leading-space-prefixed, or empty)
* @param {string} href URL for the stylesheet
* @param {string} crossOrigin ` crossorigin="…"` to append from `output.crossOriginLoading`, or `""`
* @param {string} integrity ` integrity="<sentinel>"` to append when `output.html.integrity` is on, or `""`
* @returns {string} the sibling `<link>` tag's HTML
*/
const buildStylesheetLink = (extra, href, crossOrigin, integrity) => {
const safeHref = href.replace(/"/g, """);
return `<link rel="stylesheet" href="${safeHref}"${extra}${crossOrigin}${integrity}>`;
};
/**
* Build a fresh `<script src="…">` for a JS chunk pulled in by a custom
* (non-`<script>`) element that the user mapped to `type: "script"` /
* `"script-module"`. Cloning the custom element verbatim and appending
* `</script>` (as `cloneTagWithUrl` does for a real `<script>`) would emit
* mismatched, non-executing markup such as `<my-script src="…"></script>`,
* 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 `<script>`.
* @param {string} extra the copyable CSP/fetch attribute source text (leading-space-prefixed, or empty)
* @param {string} src URL for the script
* @param {boolean} isModule whether to emit `type="module"`
* @param {string} crossOrigin ` crossorigin="…"` to append from `output.crossOriginLoading`, or `""`
* @param {string} integrity ` integrity="<sentinel>"` to append when `output.html.integrity` is on, or `""`
* @returns {string} the sibling `<script>` tag's HTML
*/
const buildScriptTag = (extra, src, isModule, crossOrigin, integrity) => {
const safeSrc = src.replace(/"/g, """);
return `<script${isModule ? ' type="module"' : ""} src="${safeSrc}"${extra}${crossOrigin}${integrity}></script>`;
};
/**
* Clone the original `<script>`/`<link>` opening tag with its `src`/`href`
* value swapped for a different chunk URL. Reusing the source text verbatim
* preserves attributes such as `nonce`, `crossorigin`, `referrerpolicy`,
* `defer`, and `async` so the sibling tags load with the same semantics as
* the entry tag that's already there. `integrity` is dropped because it's
* content-specific. When the original tag was upgraded to a module script
* (either by the author or by the `output.module` auto-upgrade in
* `HtmlParser`), the sibling is forced to `type="module"` regardless of what
* the source originally said. The `integrity`/`type` spans come from the
* parser (`HtmlParser` captured them off the AST); edits are applied
* right-to-left so earlier offsets stay valid — no re-parsing of the tag.
* @param {string} originalTag the opening tag's source text including `>`
* @param {number} srcStartInTag offset of the src/href value start within `originalTag`
* @param {number} srcEndInTag offset of the src/href value end within `originalTag`
* @param {string} newUrl URL to put into the cloned tag's src/href slot
* @param {HtmlEntryElementKind} elementKind shape of the originating tag
* @param {string} crossOrigin ` crossorigin="…"` to insert from `output.crossOriginLoading`, or `""`
* @param {number} tagNameEndInTag offset right after the tag name within `originalTag`, where `crossOrigin`/`integrity` are inserted
* @param {Range | null} integrityRange `integrity` attribute span within `originalTag` to drop, or null
* @param {Range | null} typeValueRange `type` value span within `originalTag` to overwrite with `module`, or null to insert `type="module"`
* @param {string} integrity ` integrity="<sentinel>"` to insert when `output.html.integrity` is on, or `""`
* @returns {string} the sibling tag's HTML (including a closing `</script>` 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);
}
// `<link>` is a void element — no closing tag. `<script>` needs `</script>`.
return isLink ? body : `${body}</script>`;
};
// WHY: an ES5 `<link rel="modulepreload">` polyfill (Safari <17, Firefox <115,
// …). On a browser without native support, each modulepreload target is fetched
// with the tag's own credentials and integrity, so the pending module import
// reuses it. The `.ep` guard makes that idempotent and the observer catches any
// later-inserted link. Emitted only where 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<l.length;i++)f(l[i]);",
"new MutationObserver(function(m){",
"for(var j=0;j<m.length;j++)",
"for(var a=m[j].addedNodes,k=0;k<a.length;k++)",
'if(a[k].tagName==="LINK"&&a[k].rel==="modulepreload")f(a[k])',
"}).observe(document,{childList:true,subtree:true})",
"})();"
].join("");
// One polyfill `<script>` 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-`<script>` pages get exactly one and the cache stays correct.
/** @type {WeakSet<ReplaceSource>} */
const polyfilledSources = new WeakSet();
/** @typedef {"preload" | "prefetch" | "modulepreload" | "preconnect" | "dns-prefetch"} HtmlResourceHintRel */
/**
* A custom resource-hint `<link>` (`output.html.resourceHints`). Exactly one of
* `href` / `chunk` / `entry` names the target; `chunk`/`entry` URLs (hash and
* public path) are resolved by webpack, `href` is used verbatim.
* @typedef {object} HtmlResourceHint
* @property {HtmlResourceHintRel} rel hint relationship
* @property {string=} href literal URL (external, `preconnect`, or an already-hashed asset)
* @property {string=} chunk chunk name to hint
* @property {string=} entry entrypoint name to hint (expands to one hint per initial chunk)
* @property {string=} as the `as` attribute; defaults to `script` for chunk/entry refs
* @property {string=} type the `type` attribute (MIME)
* @property {(boolean | "anonymous" | "use-credentials")=} crossorigin CORS mode (`true` → `anonymous`)
* @property {string=} media the `media` attribute
* @property {boolean=} integrity SRI for a chunk/entry ref; follows `output.html.integrity` by default, `false` opts out
* @property {("low" | "high" | "auto")=} fetchPriority the `fetchpriority` attribute (allowed on `preload`/`modulepreload`/`prefetch`)
*/
/**
* @typedef {object} HtmlResourceHintContext
* @property {string} entryName name of the entry this page loads
* @property {Entrypoint} entrypoint the entrypoint
* @property {"html" | "js"} hostType `"html"` when the entry has an extracted HTML page; `"js"` when a JS-only entry is being consulted (SSR framework case)
* @property {import("../../Compilation")} compilation the compilation
* @property {{ rel: "modulepreload" | "preload" | "prefetch", chunk: string, hostChunks: string[] }[]} defaultHints the auto initial-graph hints (`chunk` / `hostChunks` name the referencing chunk — Vite's `hostId`)
*/
/**
* @param {string} value attribute value
* @returns {string} value with `"` escaped so it can't break out of the attribute
*/
const escapeAttr = (value) => String(value).replace(/"/g, """);
/**
* @param {boolean | "anonymous" | "use-credentials" | undefined} value crossorigin option
* @returns {string | undefined} ` crossorigin="…"`, or undefined when unset/false
*/
const crossOriginAttr = (value) => {
if (value === undefined || value === false) return undefined;
return ` crossorigin="${value === true ? "anonymous" : value}"`;
};
/**
* Build one resource-hint `<link>` tag from a resolved href and attributes.
* @param {HtmlResourceHintRel} rel the `rel`
* @param {{ href: string, as?: string, type?: string, crossorigin?: string, integrity?: string, media?: string, fetchpriority?: string }} attrs attributes (already href-resolved; `crossorigin`/`integrity` are full ` name="…"` spans)
* @returns {string} the `<link>` tag
*/
const buildLinkTag = (rel, attrs) =>
`<link rel="${rel}"${attrs.as ? ` as="${escapeAttr(attrs.as)}"` : ""}${
attrs.type ? ` type="${escapeAttr(attrs.type)}"` : ""
} href="${escapeAttr(attrs.href)}"${attrs.crossorigin || ""}${
attrs.integrity || ""
}${attrs.media ? ` media="${escapeAttr(attrs.media)}"` : ""}${
attrs.fetchpriority
? ` fetchpriority="${escapeAttr(attrs.fetchpriority)}"`
: ""
}>`;
/**
* The chunks a hint means when it names a page: a page loads no JavaScript of
* its own, so the hint follows the entries the parser extracted from it.
* @param {import("../../Compilation")} compilation compilation
* @param {Chunk} chunk chunk a hint resolved to
* @returns {Chunk[]} the chunks to hint at
*/
const resolvePageChunk = (compilation, chunk) => {
const { chunkGraph } = compilation;
const modules = chunkGraph.getChunkModulesIterableBySourceType(
chunk,
HTML_TYPE
);
// A page with a JavaScript side of its own (an `output.library` entry) is
// hinted too — `chunkHasJs` can't say so, it holds for any runtime chunk.
const javascript = chunkGraph.getChunkModulesIterableBySourceType(
chunk,
JAVASCRIPT_TYPE
);
/** @type {Chunk[]} */
const chunks = javascript === undefined ? [] : [chunk];
// A chunk carrying no page stands for itself; a page stands for the entries
// extracted from it, so one that extracted nothing hints nothing.
if (modules === undefined) return chunks.length === 0 ? [chunk] : chunks;
for (const module of modules) {
const { htmlEntries } =
/** @type {import("../../html/HtmlModule").HtmlModuleBuildInfo} */ (
module.buildInfo
) || {};
if (!htmlEntries) continue;
for (const group of Object.values(htmlEntries)) {
for (const { entryName } of group) {
const entrypoint = compilation.entrypoints.get(entryName);
const entryChunk = entrypoint && entrypoint.getEntrypointChunk();
if (entryChunk) chunks.push(entryChunk);
}
}
}
return chunks;
};
/**
* Resolve one `output.html.resourceHints` descriptor to zero or more `<link>`
* tags. A `chunk`/`entry` reference becomes the emitted chunk URL (a sentinel
* resolved with the final hash and public path), an `href` is used verbatim, and
* `preconnect`/`dns-prefetch` use only `href`. Unknown chunk/entry names are skipped.
* @param {HtmlResourceHint} desc the descriptor
* @param {{ compilation: import("../../Compilation"), chunkGraph: ChunkGraph, chunkByName: Map<string, Chunk>, outputCrossOrigin: string, integrityOn: boolean }} ctx resolution context
* @returns {string[]} the resolved tags
*/
const resolveResourceHint = (desc, ctx) => {
const rel = desc.rel;
if (rel === "preconnect" || rel === "dns-prefetch") {
if (!desc.href) return [];
return [
buildLinkTag(rel, {
href: desc.href,
crossorigin: crossOriginAttr(desc.crossorigin),
media: desc.media
})
];
}
if (desc.href) {
return [
buildLinkTag(rel, {
href: desc.href,
as: desc.as,
type: desc.type,
crossorigin: crossOriginAttr(desc.crossorigin),
media: desc.media,
fetchpriority: desc.fetchPriority
})
];
}
/** @type {Chunk[]} */
let chunks;
if (desc.entry) {
const entrypoint = ctx.compilation.entrypoints.get(desc.entry);
if (!entrypoint) return [];
chunks = getEntrypointChunksInLoadOrder(entrypoint);
} else if (desc.chunk) {
const chunk = ctx.chunkByName.get(desc.chunk);
if (!chunk) return [];
chunks = [chunk];
} else {
return [];
}
/** @type {Chunk[]} */
const hinted = [];
for (const chunk of chunks) {
for (const resolved of resolvePageChunk(ctx.compilation, chunk)) {
hinted.push(resolved);
}
}
chunks = hinted;
const kind = desc.as === "style" ? "css" : "javascript";
const as = desc.as || (rel === "modulepreload" ? undefined : "script");
// A hint targeting another chunk must match how that chunk is fetched, so
// inherit `output.crossOriginLoading` unless the descriptor overrides it.
const crossorigin =
desc.crossorigin !== undefined
? crossOriginAttr(desc.crossorigin)
: ctx.outputCrossOrigin || undefined;
/** @type {string[]} */
const tags = [];
for (const chunk of chunks) {
const has =
kind === "css"
? chunkHasCss(chunk, ctx.chunkGraph)
: chunkHasJs(chunk, ctx.chunkGraph);
if (!has) continue;
tags.push(
buildLinkTag(rel, {
href: getHtmlGenerator().makeChunkUrlSentinel(chunk, kind),
as,
type: desc.type,
crossorigin,
// SRI follows the global `output.html.integrity` policy (like the
// auto hints); `integrity: false` opts a single hint out. Only
// `preload`/`modulepreload` are SRI-eligible — `prefetch` is not.
integrity:
(rel === "preload" || rel === "modulepreload") &&
desc.integrity !== false &&
ctx.integrityOn
? ` integrity="${getHtmlGenerator().makeChunkIntegritySentinel(chunk, kind)}"`
: undefined,
media: desc.media,
fetchpriority: desc.fetchPriority
})
);
}
return tags;
};
HtmlEntryDependency.Template = class HtmlEntryDependencyTemplate extends (
ModuleDependency.Template
) {
/**
* Applies the plugin by registering its hooks on the compiler.
* @param {Dependency} dependency the dependency for which the template should be applied
* @param {ReplaceSource} source the current replace source which can be modified
* @param {DependencyTemplateContext} templateContext the context object
* @returns {void}
*/
apply(dependency, source, templateContext) {
const { runtimeTemplate } = templateContext;
const dep = /** @type {HtmlEntryDependency} */ (dependency);
const compilation = runtimeTemplate.compilation;
// A `type: "html"` link points at another emitted page rather than a chunk,
// so rewrite the attribute to that page's filename and emit no siblings:
// a page link loads only itself.
if (dep.elementKind === "html") {
source.replace(
dep.range[0],
dep.range[1] - 1,
getHtmlGenerator().makeHtmlPageUrlSentinel(dep.entryName)
);
return;
}
const { chunkGraph } = compilation;
const { crossOriginLoading } = compilation.outputOptions;
const entrypoint = compilation.entrypoints.get(dep.entryName);
if (!entrypoint) {
source.replace(dep.range[0], dep.range[1] - 1, "data:,");
return;
}
const orderedChunks = getEntrypointChunksInLoadOrder(entrypoint);
const entryChunk = orderedChunks[orderedChunks.length - 1];
// Mirrors `output.crossOriginLoading` onto every injected tag, as webpack's
// runtime does for chunk-loading scripts. Empty when the option is off or
// the originating tag set `crossorigin` itself, since the author wins.
const crossOrigin =
crossOriginLoading && !dep.hasOwnCrossOrigin
? ` crossorigin="${crossOriginLoading}"`
: "";
// Per-chunk SRI emits an `integrity` sentinel resolved once the chunks'
// final bytes exist. One module can back pages with different `integrity`
// options, so every page emits them and strips its own on emit.
const htmlOption = compilation.outputOptions.html;
const integrityOn = getHtmlGenerator().hasIntegritySentinels(compilation);
const inject =
typeof htmlOption === "object" ? htmlOption.inject : undefined;
/**
* Mirror `crossorigin`/`integrity` onto a native entry tag, inserted right
* after the tag name so they sit alongside any `type="module"` the parser
* injected. The author's content-specific `integrity` is dropped first (as
* clones do) so the tag never carries two.
* @param {Chunk} chunk chunk whose SRI hash the tag references
* @param {"javascript" | "css"} contentHashType which chunk asset to hash
* @returns {void}
*/
const applyEntryTagAttrs = (chunk, contentHashType) => {
if (!dep.tagIsNative || dep.tagNameEnd < 0) return;
if (crossOrigin) source.insert(dep.tagNameEnd, crossOrigin);
if (integrityOn) {
if (dep.integrityRange) {
source.replace(
dep.tagStart + dep.integrityRange[0],
dep.tagStart + dep.integrityRange[1] - 1,
""
);
}
source.insert(
dep.tagNameEnd,
` integrity="${getHtmlGenerator().makeChunkIntegritySentinel(
chunk,
contentHashType
)}"`
);
}
};
// `rel="preload"/"prefetch"` is a resource hint: rewrite the `href` to the
// built chunk's URL (CSS for `as="style"`, JS otherwise) and stop — no
// sibling tags and no execution.
if (dep.elementKind === "preload" || dep.elementKind === "prefetch") {
const contentHashType =
dep.category === "css-import" ? "css" : "javascript";
source.replace(
dep.range[0],
dep.range[1] - 1,
getHtmlGenerator().makeChunkUrlSentinel(entryChunk, contentHashType)
);
// `preload` is integrity-eligible per the SRI spec, so mirror
// `crossorigin`/`integrity` onto it (SRI requires CORS). `prefetch` is a
// navigation cache hint the spec doesn't cover — leave it a bare href.
if (dep.elementKind === "preload") {
applyEntryTagAttrs(entryChunk, contentHashType);
}
return;
}
const isStylesheet = dep.elementKind === "stylesheet";
// Inlined scripts are `type="module"` only for ESM output; classic-IIFE
// chunks (the default) must stay a plain `<script>`.
const scriptTypeAttr =
compilation.outputOptions.module === true ? ' type="module"' : "";
const entryContentHashType = isStylesheet ? "css" : "javascript";
/**
* @param {Chunk} chunk chunk to test
* @param {"javascript" | "css"} contentType which asset of the chunk is being emitted
* @returns {boolean} true when this chunk's content should be inlined
*/
const isChunkInlined = (chunk, contentType) => {
// A `<link rel="modulepreload">` is a void fetch hint — inlining it would
// emit an unclosed, executing `<script>`.
if (dep.elementKind === "modulepreload") return false;
if (!getHtmlGenerator().canInlineChunk(compilation, chunk)) {
return false;
}
if (dep.forceInline) return true;
return getHtmlGenerator().isChunkInlined(htmlOption, chunk, contentType);
};
// Only inline when the HTML module is emitted as a standalone page (extract mode).
// If it's exported as a JS string, sentinels would end up embedded in the JS chunk.
const isExtractedHtml = chunkGraph
.getModuleSourceTypes(templateContext.module)
.has("html");
const entryInlined =
isExtractedHtml &&
dep.tagStart >= 0 &&
dep.tagOpenEnd > dep.tagStart &&
isChunkInlined(entryChunk, entryContentHashType);
if (entryInlined) {
const inlineSentinel = getHtmlGenerator().makeChunkInlineSentinel(
entryChunk,
entryContentHashType
);
if (isStylesheet) {
source.replace(
dep.tagStart,
dep.tagOpenEnd - 1,
`<style>${inlineSentinel}</style>`
);
} else {
source.replace(
dep.tagStart,
dep.tagOpenEnd - 1,
`<script${scriptTypeAttr}>${inlineSentinel}`
);
}
} else {
source.replace(
dep.range[0],
dep.range[1] - 1,
getHtmlGenerator().makeChunkUrlSentinel(
entryChunk,
entryContentHashType
)
);
}
if (dep.tagStart < 0 || dep.tagOpenEnd <= dep.tagStart) {
return;
}
const originalContent = /** @type {string} */ (source.original().source());
const originalTag = originalContent.slice(dep.tagStart, dep.tagOpenEnd);
const srcStartInTag = dep.range[0] - dep.tagStart;
const srcEndInTag = dep.range[1] - dep.tagStart;
const tagIsNative = dep.tagIsNative;
const tagNameEndInTag = dep.tagNameEnd - dep.tagStart;
// Mirror `crossorigin`/`integrity` onto the non-inlined entry tag
// (siblings get them via the builders below).
if (!entryInlined) applyEntryTagAttrs(entryChunk, entryContentHashType);
// `inject: false` suppresses sibling-chunk tags only; resource hints
// below still apply (the way to reference the suppressed chunks).
if (inject !== false) {
/**
* @param {Chunk} chunk chunk to emit a sibling tag for
* @param {"javascript" | "css"} kind content type slice of the chunk to emit
* @returns {string} a single sibling tag's HTML
*/
const buildSibling = (chunk, kind) => {
if (isChunkInlined(chunk, kind)) {
const sentinel = getHtmlGenerator().makeChunkInlineSentinel(
chunk,
kind
);
return kind === "css"
? `<style>${sentinel}</style>`
: `<script${scriptTypeAttr}>${sentinel}</script>`;
}
const url = getHtmlGenerator().makeChunkUrlSentinel(chunk, kind);
const integrityAttr = integrityOn
? ` integrity="${getHtmlGenerator().makeChunkIntegritySentinel(chunk, kind)}"`
: "";
if (kind === "css") {
// A CSS chunk always loads through `<link rel="stylesheet">`. Clone
// only when the entry tag is itself one, so `media` carries over;
// anything else gets a freshly synthesized `<link>`.
if (isStylesheet && tagIsNative) {
return cloneTagWithUrl(
originalTag,
srcStartInTag,
srcEndInTag,
url,
dep.elementKind,
crossOrigin,
tagNameEndInTag,
dep.integrityRange,
dep.typeValueRange,
integrityAttr
);
}
return buildStylesheetLink(
dep.copyableAttrsText,
url,
crossOrigin,
integrityAttr
);
}
// A JS chunk is loaded via `<script>`. Clone a native `<script>`
// verbatim; synthesize a real `<script>` for custom elements that
// were mapped to `type: script`/`script-module`.
if (tagIsNative) {
return cloneTagWithUrl(
originalTag,
srcStartInTag,
srcEndInTag,
url,
dep.elementKind,
crossOrigin,
tagNameEndInTag,
dep.integrityRange,
dep.typeValueRange,
integrityAttr
);
}
return buildScriptTag(
dep.copyableAttrsText,
url,
dep.elementKind === "script-module",
crossOrigin,
integrityAttr
);
};
const siblings = [];
// Parser-derived anchor (a source text scan would match `</head>` inside
// comments or raw text).
const headClose = inject === "head" ? dep.headAnchor : -1;
// With the entry tag already inside `<head>`, inserting at `headClose`
// would place siblings after it and reverse runtime-before-entry order
// for deferred scripts. Insert before the entry tag instead.
const headAt =
headClose < 0
? -1
: dep.tagStart < headClose
? dep.tagStart
: headClose;
let cssHtml = "";
let cssAt = -1;
if (isStylesheet) {
// WHY: a `<link rel="stylesheet">` entry is CSS, but a shared or runtime
// JS-only chunk — `optimization.runtimeChunk`, say — can still sit in the
// entrypoint, so the `<link>` is cloned only for siblings that actually
// produce a `.css` asset, mirroring the JS branch below. Otherwise it
// points at a `.css` file that does not exist: a 404, or an empty
// `<style>` when inlined. Attributes such as `media` still carry over.
for (let i = 0; i < orderedChunks.length - 1; i++) {
if (chunkHasCss(orderedChunks[i], chunkGraph)) {
siblings.push(buildSibling(orderedChunks[i], "css"));
}
}
} else {
// WHY: splitChunks inserts each new chunk before the entry chunk, so
// `entrypoint.chunks` hands back split CSS siblings in reverse of the
// order they were processed — the html-webpack-plugin#1838 and
// mini-css-extract#959 symptom. The order is re-derived from the
// entrypoint's module post-order index, which follows the dependency
// walk and so the source's import order.
/** @type {Chunk[]} */
const cssChunks = [];
/** @type {Chunk[]} */
const jsChunks = [];
for (let i = 0; i < orderedChunks.length - 1; i++) {
const chunk = orderedChunks[i];
const hasCss = chunkHasCss(chunk, chunkGraph);
const hasJs = chunkHasJs(chunk, chunkGraph);
if (hasCss) {
cssChunks.push(chunk);
}
if (hasJs) jsChunks.push(chunk);
}
// When the entry chunk carries CSS itself, fold it into the same
// ordering so its `<link>` lands in the right cascade position
// relative to the sibling CSS chunks.
if (chunkHasCss(entryChunk, chunkGraph)) {
cssChunks.push(entryChunk);
}
const cssChunkOrder = getCssModulesPlugin().getCssChunksInCascadeOrder(
cssChunks,
entrypoint,
chunkGraph
);
// Stylesheets land in `<head>`. A classic blocking script pins them
// ahead of itself, so it never awaits later CSS; with only `defer` and
// module scripts they go last, which starts the script fetches earlier.
cssAt =
dep.headAnchor >= 0
? dep.cssAnchor >= 0 && dep.cssAnchor < dep.headAnchor
? dep.cssAnchor
: dep.headAnchor
: dep.cssAnchor >= 0
? dep.cssAnchor
: dep.tagStart;
cssHtml = cssChunkOrder
.map((chunk) => buildSibling(chunk, "css"))
.join("");
for (const chunk of jsChunks) {
siblings.push(buildSibling(chunk, "javascript"));
}
}
// Same-offset inserts keep call order: CSS goes in before the
// sibling scripts only when a blocking script demands CSS-first.
if (cssHtml !== "" && dep.cssAnchor >= 0) source.insert(cssAt, cssHtml);
if (siblings.length > 0) {
source.insert(headAt >= 0 ? headAt : dep.tagStart, siblings.join(""));
}
if (cssHtml !== "" && dep.cssAnchor < 0) source.insert(cssAt, cssHtml);
}
// WHY: resource hints are opt-in, the initial chunks already loading
// through parallel `<script>` tags, so there is no default waterfall to
// prevent. `output.resourceHints` requests them: `true` preloads the entry's
// initial dependency chunks (runtime, vendor, split), while an array or a
// function adds custom ones and a function may spread the auto
// `defaultHints`. Extracted pages only — a JS-string HTML export would embed
// the sentinels in the JS chunk. A URL-referenced asset carrying
// `webpackPrefetch` / `webpackPreload`, by magic comment or a
// `parser.<type>.urlHints` rule, emits here too.
const resourceHintResolver =
ResourceHintPlugin.getCompilationResolver(compilation);
const resourceHints = resourceHintResolver.hints;
if (
isExtractedHtml &&
(dep.elementKind === "script" || dep.elementKind === "script-module")
) {
const isModuleOutput = compilation.outputOptions.module === true;
/** @type {string[]} */
const hints = [];
// === preconnect channel — cross-origin publicPath (resourceHints.preconnect) ===
const rhOptions = compilation.outputOptions.resourceHints;
if (rhOptions && rhOptions.preconnect) {
const pp = compilation.outputOptions.publicPath;
const originMatch =
typeof pp === "string" ? /^(https?:)?\/\/[^/?#]+/i.exec(pp) : null;
if (originMatch) {
hints.push(
`<link rel="preconnect" href="${escapeAttr(originMatch[0])}"${crossOrigin}>`
);
}
}
// === chunks channel — the entry's own initial dependency graph ===
if (resourceHints !== undefined && resourceHints !== false) {
// WHY: descriptors for the auto initial-graph preload, carrying their
// exact tag and built only when needed — `true` or `"prefetch"` emits
// them, and a function may spread them. The entry chunk is skipped, being
// the `<script src>` already. ESM uses `modulepreload`, which fetches and
// parses; classic output `preload as="script"`; and `"prefetch"` opts into
// `<link rel="prefetch">`, an idle-time fetch with no `as` and no
// `integrity`, which the spec does not list. An async `import()` chunk is
// not covered here: that is
// `module.parser.javascript.dynamicImport*`, which routes through
// webpack's on-demand chunk-load runtime.
const prefetchMode = resourceHints === "prefetch";
const preloadMode = resourceHints === true;
/** @type {"modulepreload" | "preload" | "prefetch"} */
const rel = prefetchMode
? "prefetch"
: isModuleOutput
? "modulepreload"
: "preload";
/** @type {{ rel: "modulepreload" | "preload" | "prefetch", chunk: string, hostChunks: string[], _tag: string }[]} */
const defaultHints = [];
if (
preloadMode ||
prefetchMode ||
typeof resourceHints === "function"
) {
for (const chunk of orderedChunks) {
if (
chunk === entryChunk ||
isChunkInlined(chunk, "javascript") ||
!chunkHasJs(chunk, chunkGraph)
) {
continue;
}
const href = getHtmlGenerator().makeChunkUrlSentinel(
chunk,
"javascript"
);
const integrityAttr =
integrityOn && !prefetchMode
? ` integrity="${getHtmlGenerator().makeChunkIntegritySentinel(
chunk,
"javascript"
)}"`
: "";
// Mirror the entry tag's `nonce`, `crossorigin` and `referrerpolicy`,
// so the request matches the sibling `<script>`'s and the browser
// reuses it rather than fetching twice.
const attrs = `${dep.copyableAttrsText}${crossOrigin}${integrityAttr}`;
const tag = prefetchMode
? `<link rel="prefetch" href="${href}"${attrs}>`
: isModuleOutput
? `<link rel="modulepreload" href="${href}"${attrs}>`
: `<link rel="preload" as="script" href="${href}"${attrs}>`;
const chunkName = chunk.name || String(chunk.id);
defaultHints.push({
rel,
chunk: chunkName,
hostChunks: [chunkName],
_tag: tag
});
}
}
if (preloadMode || prefetchMode) {
for (const d of defaultHints) {
hints.push(d._tag);
}
} else {
// Array (custom only) or function (returns the final list, may spread
// `defaultHints`). A returned default hint keeps its prebuilt tag; any
// other descriptor is resolved through the URL/SRI pipeline.
let descriptors = resourceHints;
if (typeof resourceHints === "function") {
// The page's own entry name (its named entry chunk), not the synthetic
// `<script>` entry — that's what users key on.
let entryName = dep.entryName;
for (const c of chunkGraph.getModuleChunks(
templateContext.module
)) {
if (c.name) {
entryName = c.name;
break;
}
}
descriptors = resourceHints({
entryName,
entrypoint,
hostType: "html",
compilation,
defaultHints
});
}
if (Array.isArray(descriptors) && descriptors.length > 0) {
/** @type {Map<string, Chunk>} */
const chunkByName = new Map();
for (const c of compilation.chunks) {
if (c.name) chunkByName.set(c.name, c);
}
const outputCrossOrigin = crossOriginLoading
? ` crossorigin="${crossOriginLoading}"`
: "";
const ctx = {
compilation,
chunkGraph,
chunkByName,
outputCrossOrigin,
integrityOn
};
for (const desc of descriptors) {
if (!desc || typeof desc !== "object") continue;
const prebuilt = /** @type {{ _tag?: string }} */ (desc)._tag;
if (prebuilt) {
hints.push(prebuilt);
} else if (desc.rel) {
for (const tag of resolveResourceHint(desc, ctx)) {
hints.push(tag);
}
}
}
}
}
}
// === assets channel — URL-referenced assets carrying `prefetch`/`preload` ===
// The plugin already walked the entrypoint once and cached this entry's
// asset deps, so consume that list rather than repeat it. URLs stay
// sentinels here: the asset module may not be codegen'd yet.
const outputCrossOriginAttr = crossOriginLoading
? ` crossorigin="${crossOriginLoading}"`
: "";
for (const { module, dep: md } of resourceHintResolver.getHtmlHinted(
dep.entryName
)) {
const rel = md.preload ? "preload" : "prefetch";
const asAttr =
md.asAttribute ||
ResourceHintRuntimeModule.guessAsAttribute(
/** @type {import("../url/URLDependency")} */ (md).request || ""
);
const typeAttr = md.typeAttribute;
const mediaAttr = md.mediaAttribute;
const fetchPriority = md.fetchPriority;
const href = getHtmlGenerator().makeAssetUrlSentinel(
module.identifier()
);
const attrs =
`${asAttr ? ` as="${escapeAttr(asAttr)}"` : ""}` +
`${typeAttr ? ` type="${escapeAttr(typeAttr)}"` : ""}` +
` href="${escapeAttr(href)}"${
outputCrossOriginAttr
}${mediaAttr ? ` media="${escapeAttr(mediaAttr)}"` : ""}` +
`${fetchPriority ? ` fetchpriority="${escapeAttr(fetchPriority)}"` : ""}`;
hints.push(`<link rel="${rel}"${attrs}>`);
}
if (hints.length > 0) {
// De-duplicate (a custom hint may repeat an auto one), preserving order.
/** @type {Set<string>} */
const seen = new Set();
/** @type {string[]} */
const finalHints = [];
for (const h of hints) {
if (!seen.has(h)) {
seen.add(h);
finalHints.push(h);
}
}
// Without native `modulepreload` the hints warm no cache, so append the
// polyfill once per page. It defaults from the target's support, and
// `false` opts out, which a strict CSP needs.
if (
rhOptions &&
rhOptions.modulePreloadPolyfill &&
!polyfilledSources.has(source) &&
finalHints.some((h) => h.startsWith('<link rel="modulepreload"'))
) {
polyfilledSources.add(source);
finalHints.push(`<script>${MODULEPRELOAD_POLYFILL}</script>`);
}
// Insert just after the `<head>` open tag, so the hints are the first
// fetches. A page with no written `<head>` falls back to the first
// blocking script, else the entry tag.
const hintAt =
dep.headOpenEnd >= 0
? dep.headOpenEnd
: dep.cssAnchor === -1
? dep.tagStart
: dep.cssAnchor;
source.insert(hintAt, finalHints.join(""));
}
}
}
};
makeSerializable(HtmlEntryDependency, [
"webpack/lib/dependencies/html/HtmlEntryDependency",
// TODO in the next major release: remove, they restore pre-move cache packs
"webpack/lib/dependencies/HtmlEntryDependency"
]);
/** @type {Set<keyof DependencyKinds>} */
HtmlEntryDependency.KINDS = new Set([...ModuleDependency.KINDS, "html-entry"]);
module.exports = HtmlEntryDependency;