/*
MIT License http://www.opensource.org/licenses/mit-license.php
*/
"use strict";
const RuntimeGlobals = require("../RuntimeGlobals");
const RuntimeModule = require("../RuntimeModule");
const Template = require("../Template");
const CssUrlDependency = require("../dependencies/CssUrlDependency");
const HtmlSourceDependency = require("../dependencies/HtmlSourceDependency");
const URLDependency = require("../dependencies/URLDependency");
const ResourceHintRuntimeModule = require("./ResourceHintRuntimeModule");
/** @typedef {import("../Chunk")} Chunk */
/** @typedef {import("../ChunkGraph")} ChunkGraph */
/** @typedef {import("../Compilation")} Compilation */
/** @typedef {import("../Module")} Module */
/** @typedef {import("../ModuleGraph")} ModuleGraph */
/**
* Fires `` / `` for every asset
* referenced from this chunk via `new URL(..., import.meta.url)` carrying
* `webpackPrefetch` / `webpackPreload`, at chunk startup. Runs before any
* user module evaluates, so by the time user code reaches the
* corresponding `new URL(...)` (or hands the URL to `
`, `fetch`,
* `new Worker`, etc.), the browser already has the response in flight.
*/
class StartupAssetHintRuntimeModule extends RuntimeModule {
constructor() {
super("startup asset hints", RuntimeModule.STAGE_TRIGGER);
}
/**
* Generates runtime code for this runtime module.
* @returns {string | null} runtime code
*/
generate() {
const compilation = /** @type {Compilation} */ (this.compilation);
const chunkGraph = /** @type {ChunkGraph} */ (this.chunkGraph);
const moduleGraph = /** @type {ModuleGraph} */ (compilation.moduleGraph);
const runtimeTemplate = compilation.runtimeTemplate;
const chunk = /** @type {Chunk} */ (this.chunk);
const runtimeRequirements =
/** @type {Set} */
(chunkGraph.getTreeRuntimeRequirements(chunk));
// Dedupe per asset module: if the same asset is referenced twice
// (e.g. CSS rule says `prefetch`, a JS magic comment says
// `preload`), pick `preload` over `prefetch` and keep the most
// recently seen explicit attribute overrides. Avoids emitting a
// duplicate `` and ordering-dependent winners.
/**
* @typedef {object} AssetHintEntry
* @property {import("../Module")} assetModule
* @property {string} request
* @property {boolean} preload
* @property {("low" | "high" | "auto" | undefined)} fetchPriority
* @property {(string | undefined)} as
* @property {(string | undefined)} type
* @property {(string | undefined)} media
*/
// Per-asset dedupe vs the HTML-head emission by `HtmlEntryDependency`:
// if the asset is reachable from an HTML entrypoint's chunks, its
// `` lands in that HTML `` already — skip the runtime
// hint for that asset so the DOM doesn't carry two tags for one URL.
// Set is computed once per compilation from the module graph (stable
// across cache reads), never from a mutable cross-module marker.
const ResourceHintPlugin = require("./ResourceHintPlugin");
// `output.resourceHints: "none"` is a hard off switch — the plugin's
// runtime-requirements hook already skips adding this module then, so no
// `"none"` guard is needed here.
const { isHtmlHinted } =
ResourceHintPlugin.getCompilationResolver(compilation);
/** @type {Map} */
const perAsset = new Map();
for (const module of chunkGraph.getChunkModulesIterable(chunk)) {
const deps =
/** @type {{ dependencies?: import("../Dependency")[] }} */
(module).dependencies;
if (!deps) continue;
for (const dep of deps) {
if (
!(dep instanceof URLDependency) &&
!(dep instanceof CssUrlDependency) &&
!(dep instanceof HtmlSourceDependency)
) {
continue;
}
if (!dep.prefetch && !dep.preload) continue;
const assetModule = moduleGraph.getModule(dep);
if (!assetModule) continue;
if (isHtmlHinted(assetModule)) continue;
const key = assetModule.identifier();
const urlDep =
/** @type {URLDependency & Partial & Partial} */
(dep);
const existing = perAsset.get(key);
if (existing) {
if (dep.preload) existing.preload = true;
if (dep.fetchPriority) existing.fetchPriority = dep.fetchPriority;
if (urlDep.asAttribute) existing.as = urlDep.asAttribute;
if (urlDep.typeAttribute) existing.type = urlDep.typeAttribute;
if (urlDep.mediaAttribute) existing.media = urlDep.mediaAttribute;
} else {
perAsset.set(key, {
assetModule: /** @type {Module} */ (assetModule),
request: dep.request,
preload: Boolean(dep.preload),
fetchPriority: dep.fetchPriority,
as: urlDep.asAttribute,
type: urlDep.typeAttribute,
media: urlDep.mediaAttribute
});
}
}
}
const lines = [];
for (const entry of perAsset.values()) {
const fn = entry.preload
? RuntimeGlobals.preloadAsset
: RuntimeGlobals.prefetchAsset;
// For CSS-only assets (referenced from `url(...)` but never
// from JS) the module produces no JS source, so
// `__webpack_require__(id)` would throw at runtime. Use the
// emitted filename + publicPath directly in that case; asset
// modules referenced from JS stay on the existing `moduleRaw`
// path which yields the same URL.
const hasJsSourceType = entry.assetModule
.getSourceTypes()
.has("javascript");
let hrefExpr;
if (hasJsSourceType) {
hrefExpr = runtimeTemplate.moduleRaw({
chunkGraph,
module: entry.assetModule,
request: entry.request,
runtimeRequirements,
weak: false
});
} else {
const buildInfo =
/** @type {{ filename?: string }} */
(entry.assetModule.buildInfo);
if (!buildInfo || !buildInfo.filename) continue;
runtimeRequirements.add(RuntimeGlobals.publicPath);
hrefExpr = `${RuntimeGlobals.publicPath} + ${JSON.stringify(buildInfo.filename)}`;
}
const as =
entry.as || ResourceHintRuntimeModule.guessAsAttribute(entry.request);
lines.push(
`${fn}(${hrefExpr}, ${JSON.stringify(as)}, ${
entry.type ? JSON.stringify(entry.type) : "undefined"
}, ${entry.media ? JSON.stringify(entry.media) : "undefined"}, ${
entry.fetchPriority
? JSON.stringify(entry.fetchPriority)
: "undefined"
});`
);
}
if (lines.length === 0) return null;
// `__webpack_nonce__` set inside the entry module is too late — the
// entry hasn't run yet. Read the nonce off the `