mirror of https://github.com/webpack/webpack.git
feat: implement URL-based prefetch/preload support with runtime modules
This commit is contained in:
parent
4cab6a07d5
commit
5d233d5389
|
@ -90,6 +90,7 @@
|
|||
"externref",
|
||||
"fetchpriority",
|
||||
"filebase",
|
||||
"flac",
|
||||
"fileoverview",
|
||||
"filepath",
|
||||
"finalizer",
|
||||
|
|
|
@ -315,6 +315,7 @@ module.exports.onChunksLoaded = "__webpack_require__.O";
|
|||
/**
|
||||
* the chunk prefetch function
|
||||
*/
|
||||
module.exports.prefetchAsset = "__webpack_require__.PA";
|
||||
module.exports.prefetchChunk = "__webpack_require__.E";
|
||||
|
||||
/**
|
||||
|
@ -325,6 +326,7 @@ module.exports.prefetchChunkHandlers = "__webpack_require__.F";
|
|||
/**
|
||||
* the chunk preload function
|
||||
*/
|
||||
module.exports.preloadAsset = "__webpack_require__.LA";
|
||||
module.exports.preloadChunk = "__webpack_require__.G";
|
||||
|
||||
/**
|
||||
|
@ -332,6 +334,14 @@ module.exports.preloadChunk = "__webpack_require__.G";
|
|||
*/
|
||||
module.exports.preloadChunkHandlers = "__webpack_require__.H";
|
||||
|
||||
/**
|
||||
* the asset prefetch function
|
||||
*/
|
||||
|
||||
/**
|
||||
* the asset preload function
|
||||
*/
|
||||
|
||||
/**
|
||||
* the bundle public path
|
||||
*/
|
||||
|
|
|
@ -63,6 +63,7 @@ const JavascriptModulesPlugin = require("./javascript/JavascriptModulesPlugin");
|
|||
const JsonModulesPlugin = require("./json/JsonModulesPlugin");
|
||||
|
||||
const ChunkPrefetchPreloadPlugin = require("./prefetch/ChunkPrefetchPreloadPlugin");
|
||||
const AssetPrefetchPreloadPlugin = require("./runtime/AssetPrefetchPreloadPlugin");
|
||||
|
||||
const DataUriPlugin = require("./schemes/DataUriPlugin");
|
||||
const FileUriPlugin = require("./schemes/FileUriPlugin");
|
||||
|
@ -223,6 +224,7 @@ class WebpackOptionsApply extends OptionsApply {
|
|||
}
|
||||
|
||||
new ChunkPrefetchPreloadPlugin().apply(compiler);
|
||||
new AssetPrefetchPreloadPlugin().apply(compiler);
|
||||
|
||||
if (typeof options.output.chunkFormat === "string") {
|
||||
switch (options.output.chunkFormat) {
|
||||
|
|
|
@ -132,7 +132,101 @@ URLDependency.Template = class URLDependencyTemplate extends (
|
|||
|
||||
runtimeRequirements.add(RuntimeGlobals.require);
|
||||
|
||||
// Check if we need to add prefetch/preload runtime
|
||||
const needsPrefetch = dep.prefetch !== undefined && dep.prefetch !== false;
|
||||
const needsPreload = dep.preload !== undefined && dep.preload !== false;
|
||||
|
||||
if (needsPrefetch || needsPreload) {
|
||||
// Get the module to determine asset type
|
||||
const module = moduleGraph.getModule(dep);
|
||||
let asType = "";
|
||||
|
||||
if (module) {
|
||||
const request = module.request || "";
|
||||
// Determine the 'as' attribute based on file extension
|
||||
// Reference: MDN rel=preload documentation (https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Attributes/rel/preload)
|
||||
// Valid 'as' values: fetch, font, image, script, style, track
|
||||
// Note: audio/video are in spec but not supported by major browsers as of 2025
|
||||
if (/\.(png|jpe?g|gif|svg|webp|avif|bmp|ico|tiff?)$/i.test(request)) {
|
||||
asType = "image";
|
||||
} else if (/\.(woff2?|ttf|otf|eot)$/i.test(request)) {
|
||||
asType = "font";
|
||||
} else if (/\.(js|mjs|jsx|ts|tsx)$/i.test(request)) {
|
||||
asType = "script";
|
||||
} else if (/\.css$/i.test(request)) {
|
||||
asType = "style";
|
||||
} else if (/\.vtt$/i.test(request)) {
|
||||
asType = "track"; // WebVTT files for video subtitles/captions
|
||||
} else if (
|
||||
/\.(mp4|webm|ogg|mp3|wav|flac|aac|m4a|avi|mov|wmv|mkv)$/i.test(
|
||||
request
|
||||
)
|
||||
) {
|
||||
// Audio/video files: use 'fetch' as fallback since as='audio'/'video' not supported
|
||||
// Reference: https://github.com/mdn/browser-compat-data/issues/9577
|
||||
asType = "fetch";
|
||||
} else if (/\.(json|xml|txt|csv|pdf|doc|docx|wasm)$/i.test(request)) {
|
||||
asType = "fetch"; // Data files, documents, WebAssembly
|
||||
} else {
|
||||
asType = "fetch"; // Generic fetch for unknown types
|
||||
}
|
||||
}
|
||||
|
||||
// Generate the module expression (just the module id)
|
||||
const moduleExpr = runtimeTemplate.moduleRaw({
|
||||
chunkGraph,
|
||||
module: moduleGraph.getModule(dep),
|
||||
request: dep.request,
|
||||
runtimeRequirements,
|
||||
weak: false
|
||||
});
|
||||
|
||||
// Build the prefetch/preload code
|
||||
const hintCode = [];
|
||||
const fetchPriority = dep.fetchPriority
|
||||
? `"${dep.fetchPriority}"`
|
||||
: "undefined";
|
||||
|
||||
if (needsPrefetch && !needsPreload) {
|
||||
// Only prefetch
|
||||
runtimeRequirements.add(RuntimeGlobals.prefetchAsset);
|
||||
hintCode.push(
|
||||
`${RuntimeGlobals.prefetchAsset}(url, "${asType}", ${fetchPriority});`
|
||||
);
|
||||
} else if (needsPreload) {
|
||||
// Preload (takes precedence over prefetch)
|
||||
runtimeRequirements.add(RuntimeGlobals.preloadAsset);
|
||||
hintCode.push(
|
||||
`${RuntimeGlobals.preloadAsset}(url, "${asType}", ${fetchPriority});`
|
||||
);
|
||||
}
|
||||
|
||||
// Wrap in IIFE to execute hint code and return URL
|
||||
if (dep.relative) {
|
||||
runtimeRequirements.add(RuntimeGlobals.relativeUrl);
|
||||
source.replace(
|
||||
dep.outerRange[0],
|
||||
dep.outerRange[1] - 1,
|
||||
`/* asset import */ (function() {
|
||||
var url = new ${RuntimeGlobals.relativeUrl}(${moduleExpr});
|
||||
${hintCode.join("\n")}
|
||||
return url;
|
||||
})()`
|
||||
);
|
||||
} else {
|
||||
runtimeRequirements.add(RuntimeGlobals.baseURI);
|
||||
source.replace(
|
||||
dep.range[0],
|
||||
dep.range[1] - 1,
|
||||
`/* asset import */ (function() {
|
||||
var url = new URL(${moduleExpr}, ${RuntimeGlobals.baseURI});
|
||||
${hintCode.join("\n")}
|
||||
return url;
|
||||
})(), ${RuntimeGlobals.baseURI}`
|
||||
);
|
||||
}
|
||||
} else if (dep.relative) {
|
||||
// No prefetch/preload - use original code
|
||||
runtimeRequirements.add(RuntimeGlobals.relativeUrl);
|
||||
source.replace(
|
||||
dep.outerRange[0],
|
||||
|
|
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
MIT License http://www.opensource.org/licenses/mit-license.php
|
||||
Author Tobias Koppers @sokra
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
const RuntimeGlobals = require("../RuntimeGlobals");
|
||||
const AssetPrefetchPreloadRuntimeModule = require("./AssetPrefetchPreloadRuntimeModule");
|
||||
|
||||
/** @typedef {import("../Compiler")} Compiler */
|
||||
|
||||
const PLUGIN_NAME = "AssetPrefetchPreloadPlugin";
|
||||
|
||||
class AssetPrefetchPreloadPlugin {
|
||||
/**
|
||||
* @param {Compiler} compiler the compiler
|
||||
* @returns {void}
|
||||
*/
|
||||
apply(compiler) {
|
||||
compiler.hooks.compilation.tap(PLUGIN_NAME, compilation => {
|
||||
// Add runtime module for asset prefetch
|
||||
compilation.hooks.runtimeRequirementInTree
|
||||
.for(RuntimeGlobals.prefetchAsset)
|
||||
.tap(PLUGIN_NAME, (chunk, set) => {
|
||||
compilation.addRuntimeModule(
|
||||
chunk,
|
||||
new AssetPrefetchPreloadRuntimeModule("prefetch")
|
||||
);
|
||||
});
|
||||
|
||||
// Add runtime module for asset preload
|
||||
compilation.hooks.runtimeRequirementInTree
|
||||
.for(RuntimeGlobals.preloadAsset)
|
||||
.tap(PLUGIN_NAME, (chunk, set) => {
|
||||
compilation.addRuntimeModule(
|
||||
chunk,
|
||||
new AssetPrefetchPreloadRuntimeModule("preload")
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = AssetPrefetchPreloadPlugin;
|
|
@ -0,0 +1,59 @@
|
|||
/*
|
||||
MIT License http://www.opensource.org/licenses/mit-license.php
|
||||
Author Tobias Koppers @sokra
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
const RuntimeGlobals = require("../RuntimeGlobals");
|
||||
const RuntimeModule = require("../RuntimeModule");
|
||||
const Template = require("../Template");
|
||||
|
||||
/** @typedef {import("../Compilation")} Compilation */
|
||||
|
||||
class AssetPrefetchPreloadRuntimeModule extends RuntimeModule {
|
||||
/**
|
||||
* @param {string} type "prefetch" or "preload"
|
||||
*/
|
||||
constructor(type) {
|
||||
super(`asset ${type}`);
|
||||
this._type = type;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {string | null} runtime code
|
||||
*/
|
||||
generate() {
|
||||
const { compilation } = this;
|
||||
const { runtimeTemplate } = compilation;
|
||||
const fn =
|
||||
this._type === "prefetch"
|
||||
? RuntimeGlobals.prefetchAsset
|
||||
: RuntimeGlobals.preloadAsset;
|
||||
|
||||
return Template.asString([
|
||||
`${fn} = ${runtimeTemplate.basicFunction("url, as, fetchPriority", [
|
||||
"var link = document.createElement('link');",
|
||||
this._type === "prefetch"
|
||||
? "link.rel = 'prefetch';"
|
||||
: "link.rel = 'preload';",
|
||||
"if(as) link.as = as;",
|
||||
"link.href = url;",
|
||||
"if(fetchPriority) link.fetchPriority = fetchPriority;",
|
||||
// Add nonce if needed
|
||||
compilation.outputOptions.crossOriginLoading
|
||||
? Template.asString([
|
||||
"if(__webpack_require__.nc) {",
|
||||
Template.indent(
|
||||
"link.setAttribute('nonce', __webpack_require__.nc);"
|
||||
),
|
||||
"}"
|
||||
])
|
||||
: "",
|
||||
"document.head.appendChild(link);"
|
||||
])};`
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = AssetPrefetchPreloadRuntimeModule;
|
Loading…
Reference in New Issue