mirror of https://github.com/webpack/webpack.git
Merge a8d39c0cdf
into 85bacbdc6e
This commit is contained in:
commit
3d7da1d3d5
|
@ -90,6 +90,7 @@
|
|||
"externref",
|
||||
"fetchpriority",
|
||||
"filebase",
|
||||
"flac",
|
||||
"fileoverview",
|
||||
"filepath",
|
||||
"finalizer",
|
||||
|
|
|
@ -312,6 +312,11 @@ module.exports.nodeModuleDecorator = "__webpack_require__.nmd";
|
|||
*/
|
||||
module.exports.onChunksLoaded = "__webpack_require__.O";
|
||||
|
||||
/**
|
||||
* the asset prefetch function
|
||||
*/
|
||||
module.exports.prefetchAsset = "__webpack_require__.PA";
|
||||
|
||||
/**
|
||||
* the chunk prefetch function
|
||||
*/
|
||||
|
@ -322,6 +327,11 @@ module.exports.prefetchChunk = "__webpack_require__.E";
|
|||
*/
|
||||
module.exports.prefetchChunkHandlers = "__webpack_require__.F";
|
||||
|
||||
/**
|
||||
* the asset preload function
|
||||
*/
|
||||
module.exports.preloadAsset = "__webpack_require__.LA";
|
||||
|
||||
/**
|
||||
* the chunk preload function
|
||||
*/
|
||||
|
|
|
@ -34,6 +34,8 @@ const WebpackIsIncludedPlugin = require("./WebpackIsIncludedPlugin");
|
|||
|
||||
const AssetModulesPlugin = require("./asset/AssetModulesPlugin");
|
||||
|
||||
const AssetResourcePrefetchPlugin = require("./asset/AssetResourcePrefetchPlugin");
|
||||
|
||||
const InferAsyncModulesPlugin = require("./async-modules/InferAsyncModulesPlugin");
|
||||
|
||||
const ResolverCachePlugin = require("./cache/ResolverCachePlugin");
|
||||
|
@ -200,6 +202,21 @@ class WebpackOptionsApply extends OptionsApply {
|
|||
|
||||
new ChunkPrefetchPreloadPlugin().apply(compiler);
|
||||
|
||||
// Apply AssetResourcePrefetchPlugin only for web targets or universal targets
|
||||
// Check if we're targeting web environment
|
||||
const externalsPresets = options.externalsPresets || {};
|
||||
const isTargetingWeb = Boolean(
|
||||
externalsPresets.web ||
|
||||
externalsPresets.webAsync ||
|
||||
externalsPresets.electronRenderer
|
||||
);
|
||||
|
||||
// Apply the plugin if we're targeting web environment
|
||||
// For universal targets (["web", "node"]), the runtime module will handle platform detection using isNeutralPlatform
|
||||
if (isTargetingWeb || !externalsPresets.node) {
|
||||
new AssetResourcePrefetchPlugin().apply(compiler);
|
||||
}
|
||||
|
||||
if (typeof options.output.chunkFormat === "string") {
|
||||
switch (options.output.chunkFormat) {
|
||||
case "array-push": {
|
||||
|
|
|
@ -0,0 +1,55 @@
|
|||
/*
|
||||
MIT License http://www.opensource.org/licenses/mit-license.php
|
||||
Author Tobias Koppers @sokra
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
const RuntimeGlobals = require("../RuntimeGlobals");
|
||||
const ResourcePrefetchRuntimeModule = require("../prefetch/ResourcePrefetchRuntimeModule");
|
||||
|
||||
/** @typedef {import("../Compiler")} Compiler */
|
||||
|
||||
const PLUGIN_NAME = "AssetResourcePrefetchPlugin";
|
||||
|
||||
class AssetResourcePrefetchPlugin {
|
||||
/**
|
||||
* @param {Compiler} compiler the compiler
|
||||
* @returns {void}
|
||||
*/
|
||||
apply(compiler) {
|
||||
compiler.hooks.compilation.tap(PLUGIN_NAME, (compilation) => {
|
||||
// prefetchAsset
|
||||
compilation.hooks.runtimeRequirementInTree
|
||||
.for(RuntimeGlobals.prefetchAsset)
|
||||
.tap(PLUGIN_NAME, (chunk, set) => {
|
||||
set.add(RuntimeGlobals.publicPath);
|
||||
set.add(RuntimeGlobals.require);
|
||||
set.add(RuntimeGlobals.baseURI);
|
||||
set.add(RuntimeGlobals.relativeUrl);
|
||||
compilation.addRuntimeModule(
|
||||
chunk,
|
||||
new ResourcePrefetchRuntimeModule("prefetch")
|
||||
);
|
||||
return true;
|
||||
});
|
||||
|
||||
// preloadAsset
|
||||
compilation.hooks.runtimeRequirementInTree
|
||||
.for(RuntimeGlobals.preloadAsset)
|
||||
.tap(PLUGIN_NAME, (chunk, set) => {
|
||||
set.add(RuntimeGlobals.publicPath);
|
||||
set.add(RuntimeGlobals.require);
|
||||
set.add(RuntimeGlobals.baseURI);
|
||||
set.add(RuntimeGlobals.relativeUrl);
|
||||
compilation.addRuntimeModule(
|
||||
chunk,
|
||||
new ResourcePrefetchRuntimeModule("preload")
|
||||
);
|
||||
return true;
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = AssetResourcePrefetchPlugin;
|
|
@ -5,6 +5,7 @@
|
|||
|
||||
"use strict";
|
||||
|
||||
const InitFragment = require("../InitFragment");
|
||||
const RuntimeGlobals = require("../RuntimeGlobals");
|
||||
const RawDataUrlModule = require("../asset/RawDataUrlModule");
|
||||
const {
|
||||
|
@ -43,6 +44,15 @@ class URLDependency extends ModuleDependency {
|
|||
this.relative = relative || false;
|
||||
/** @type {UsedByExports | undefined} */
|
||||
this.usedByExports = undefined;
|
||||
this.prefetch = undefined;
|
||||
this.preload = undefined;
|
||||
this.fetchPriority = undefined;
|
||||
/** @type {string|undefined} */
|
||||
this.preloadAs = undefined;
|
||||
/** @type {string|undefined} */
|
||||
this.preloadType = undefined;
|
||||
/** @type {string|undefined} */
|
||||
this.preloadMedia = undefined;
|
||||
}
|
||||
|
||||
get type() {
|
||||
|
@ -81,6 +91,12 @@ class URLDependency extends ModuleDependency {
|
|||
write(this.outerRange);
|
||||
write(this.relative);
|
||||
write(this.usedByExports);
|
||||
write(this.prefetch);
|
||||
write(this.preload);
|
||||
write(this.fetchPriority);
|
||||
write(this.preloadAs);
|
||||
write(this.preloadType);
|
||||
write(this.preloadMedia);
|
||||
super.serialize(context);
|
||||
}
|
||||
|
||||
|
@ -92,6 +108,12 @@ class URLDependency extends ModuleDependency {
|
|||
this.outerRange = read();
|
||||
this.relative = read();
|
||||
this.usedByExports = read();
|
||||
this.prefetch = read();
|
||||
this.preload = read();
|
||||
this.fetchPriority = read();
|
||||
this.preloadAs = read();
|
||||
this.preloadType = read();
|
||||
this.preloadMedia = read();
|
||||
super.deserialize(context);
|
||||
}
|
||||
}
|
||||
|
@ -99,6 +121,32 @@ class URLDependency extends ModuleDependency {
|
|||
URLDependency.Template = class URLDependencyTemplate extends (
|
||||
ModuleDependency.Template
|
||||
) {
|
||||
/**
|
||||
* Determines the 'as' attribute value for prefetch/preload based on file extension
|
||||
* https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Attributes/rel/preload#what_types_of_content_can_be_preloaded
|
||||
* @param {string} request module request string or filename
|
||||
* @returns {string} asset type for link element 'as' attribute
|
||||
*/
|
||||
static _getAssetType(request) {
|
||||
if (/\.(png|jpe?g|gif|svg|webp|avif|bmp|ico|tiff?)$/i.test(request)) {
|
||||
return "image";
|
||||
} else if (/\.(woff2?|ttf|otf|eot)$/i.test(request)) {
|
||||
return "font";
|
||||
} else if (/\.(js|mjs|jsx|ts|tsx)$/i.test(request)) {
|
||||
return "script";
|
||||
} else if (/\.css$/i.test(request)) {
|
||||
return "style";
|
||||
} else if (/\.vtt$/i.test(request)) {
|
||||
return "track";
|
||||
} else if (
|
||||
/\.(mp4|webm|ogg|mp3|wav|flac|aac|m4a|avi|mov|wmv|mkv)$/i.test(request)
|
||||
) {
|
||||
// Audio/video files use 'fetch' as browser support varies
|
||||
return "fetch";
|
||||
}
|
||||
return "fetch";
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Dependency} dependency the dependency for which the template should be applied
|
||||
* @param {ReplaceSource} source the current replace source which can be modified
|
||||
|
@ -111,9 +159,12 @@ URLDependency.Template = class URLDependencyTemplate extends (
|
|||
moduleGraph,
|
||||
runtimeRequirements,
|
||||
runtimeTemplate,
|
||||
runtime
|
||||
runtime,
|
||||
initFragments
|
||||
} = templateContext;
|
||||
const dep = /** @type {URLDependency} */ (dependency);
|
||||
|
||||
const module = moduleGraph.getModule(dep);
|
||||
const connection = moduleGraph.getConnection(dep);
|
||||
// Skip rendering depending when dependency is conditional
|
||||
if (connection && !connection.isTargetActive(runtime)) {
|
||||
|
@ -125,38 +176,87 @@ URLDependency.Template = class URLDependencyTemplate extends (
|
|||
return;
|
||||
}
|
||||
|
||||
runtimeRequirements.add(RuntimeGlobals.require);
|
||||
|
||||
// Standard URL generation
|
||||
if (dep.relative) {
|
||||
runtimeRequirements.add(RuntimeGlobals.relativeUrl);
|
||||
source.replace(
|
||||
dep.outerRange[0],
|
||||
dep.outerRange[1] - 1,
|
||||
`/* asset import */ new ${
|
||||
RuntimeGlobals.relativeUrl
|
||||
}(${runtimeTemplate.moduleRaw({
|
||||
`/* asset import */ new ${RuntimeGlobals.relativeUrl}(${runtimeTemplate.moduleRaw(
|
||||
{
|
||||
chunkGraph,
|
||||
module: moduleGraph.getModule(dep),
|
||||
module,
|
||||
request: dep.request,
|
||||
runtimeRequirements,
|
||||
weak: false
|
||||
})})`
|
||||
}
|
||||
)})`
|
||||
);
|
||||
} else {
|
||||
runtimeRequirements.add(RuntimeGlobals.baseURI);
|
||||
|
||||
source.replace(
|
||||
dep.range[0],
|
||||
dep.range[1] - 1,
|
||||
`/* asset import */ ${runtimeTemplate.moduleRaw({
|
||||
chunkGraph,
|
||||
module: moduleGraph.getModule(dep),
|
||||
module,
|
||||
request: dep.request,
|
||||
runtimeRequirements,
|
||||
weak: false
|
||||
})}, ${RuntimeGlobals.baseURI}`
|
||||
);
|
||||
}
|
||||
|
||||
// Prefetch/Preload via InitFragment
|
||||
if ((dep.prefetch || dep.preload) && module) {
|
||||
const request = dep.request;
|
||||
const detectedAssetType = URLDependencyTemplate._getAssetType(request);
|
||||
const id = chunkGraph.getModuleId(module);
|
||||
if (id !== null) {
|
||||
const moduleId = runtimeTemplate.moduleId({
|
||||
module,
|
||||
chunkGraph,
|
||||
request: dep.request,
|
||||
weak: false
|
||||
});
|
||||
|
||||
if (dep.preload) {
|
||||
runtimeRequirements.add(RuntimeGlobals.preloadAsset);
|
||||
const asArg = JSON.stringify(dep.preloadAs || detectedAssetType);
|
||||
const fetchPriorityArg = dep.fetchPriority
|
||||
? JSON.stringify(dep.fetchPriority)
|
||||
: "undefined";
|
||||
const typeArg = dep.preloadType
|
||||
? JSON.stringify(dep.preloadType)
|
||||
: "undefined";
|
||||
const mediaArg = dep.preloadMedia
|
||||
? JSON.stringify(dep.preloadMedia)
|
||||
: "undefined";
|
||||
initFragments.push(
|
||||
new InitFragment(
|
||||
`${RuntimeGlobals.preloadAsset}(${moduleId}, ${asArg}, ${fetchPriorityArg}, ${typeArg}, ${mediaArg}, ${dep.relative});\n`,
|
||||
InitFragment.STAGE_CONSTANTS,
|
||||
-10,
|
||||
`asset_preload_${moduleId}`
|
||||
)
|
||||
);
|
||||
} else if (dep.prefetch) {
|
||||
runtimeRequirements.add(RuntimeGlobals.prefetchAsset);
|
||||
const asArg = JSON.stringify(detectedAssetType);
|
||||
const fetchPriorityArg = dep.fetchPriority
|
||||
? JSON.stringify(dep.fetchPriority)
|
||||
: "undefined";
|
||||
initFragments.push(
|
||||
new InitFragment(
|
||||
`${RuntimeGlobals.prefetchAsset}(${moduleId}, ${asArg}, ${fetchPriorityArg}, undefined, undefined, ${dep.relative});\n`,
|
||||
InitFragment.STAGE_CONSTANTS,
|
||||
-5,
|
||||
`asset_prefetch_${moduleId}`
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -38,6 +38,7 @@ const WorkerDependency = require("./WorkerDependency");
|
|||
/** @typedef {import("../../declarations/WebpackOptions").WasmLoading} WasmLoading */
|
||||
/** @typedef {import("../../declarations/WebpackOptions").WorkerPublicPath} WorkerPublicPath */
|
||||
/** @typedef {import("../Compiler")} Compiler */
|
||||
/** @typedef {import("../ChunkGroup").RawChunkGroupOptions} RawChunkGroupOptions */
|
||||
/** @typedef {import("../Dependency").DependencyLocation} DependencyLocation */
|
||||
/** @typedef {import("../Entrypoint").EntryOptions} EntryOptions */
|
||||
/** @typedef {import("../NormalModule")} NormalModule */
|
||||
|
@ -223,9 +224,12 @@ class WorkerPlugin {
|
|||
}
|
||||
}
|
||||
const insertType = expr.properties.length > 0 ? "comma" : "single";
|
||||
const insertLocation = /** @type {Range} */ (
|
||||
const insertLocation =
|
||||
expr.properties.length > 0
|
||||
? /** @type {Range} */ (
|
||||
expr.properties[expr.properties.length - 1].range
|
||||
)[1];
|
||||
)[1]
|
||||
: /** @type {Range} */ (expr.range)[0] + 1;
|
||||
return {
|
||||
expressions,
|
||||
otherElements,
|
||||
|
@ -299,6 +303,10 @@ class WorkerPlugin {
|
|||
? /** @type {Range} */ (arg2.range)
|
||||
: /** @type {Range} */ (arg1.range)[1]
|
||||
};
|
||||
|
||||
/** @type {RawChunkGroupOptions} */
|
||||
const groupOptions = {};
|
||||
|
||||
const { options: importOptions, errors: commentErrors } =
|
||||
parser.parseCommentOptions(/** @type {Range} */ (expr.range));
|
||||
|
||||
|
@ -360,6 +368,60 @@ class WorkerPlugin {
|
|||
entryOptions.name = importOptions.webpackChunkName;
|
||||
}
|
||||
}
|
||||
|
||||
// Support webpackPrefetch (true | number)
|
||||
if (importOptions.webpackPrefetch !== undefined) {
|
||||
if (importOptions.webpackPrefetch === true) {
|
||||
groupOptions.prefetchOrder = 0;
|
||||
} else if (typeof importOptions.webpackPrefetch === "number") {
|
||||
groupOptions.prefetchOrder = importOptions.webpackPrefetch;
|
||||
} else {
|
||||
parser.state.module.addWarning(
|
||||
new UnsupportedFeatureWarning(
|
||||
`\`webpackPrefetch\` expected true or a number, but received: ${importOptions.webpackPrefetch}.`,
|
||||
/** @type {DependencyLocation} */ (expr.loc)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Support webpackPreload (true | number)
|
||||
if (importOptions.webpackPreload !== undefined) {
|
||||
if (importOptions.webpackPreload === true) {
|
||||
groupOptions.preloadOrder = 0;
|
||||
} else if (typeof importOptions.webpackPreload === "number") {
|
||||
groupOptions.preloadOrder = importOptions.webpackPreload;
|
||||
} else {
|
||||
parser.state.module.addWarning(
|
||||
new UnsupportedFeatureWarning(
|
||||
`\`webpackPreload\` expected true or a number, but received: ${importOptions.webpackPreload}.`,
|
||||
/** @type {DependencyLocation} */ (expr.loc)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Support webpackFetchPriority ("high" | "low" | "auto")
|
||||
if (importOptions.webpackFetchPriority !== undefined) {
|
||||
if (
|
||||
typeof importOptions.webpackFetchPriority === "string" &&
|
||||
["high", "low", "auto"].includes(
|
||||
importOptions.webpackFetchPriority
|
||||
)
|
||||
) {
|
||||
groupOptions.fetchPriority =
|
||||
/** @type {"auto" | "high" | "low"} */ (
|
||||
importOptions.webpackFetchPriority
|
||||
);
|
||||
} else {
|
||||
parser.state.module.addWarning(
|
||||
new UnsupportedFeatureWarning(
|
||||
`\`webpackFetchPriority\` expected "low", "high" or "auto", but received: ${importOptions.webpackFetchPriority}.`,
|
||||
/** @type {DependencyLocation} */ (expr.loc)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
|
@ -388,6 +450,7 @@ class WorkerPlugin {
|
|||
}
|
||||
|
||||
const block = new AsyncDependenciesBlock({
|
||||
...groupOptions,
|
||||
name: entryOptions.name,
|
||||
entryOptions: {
|
||||
chunkLoading: this._chunkLoading,
|
||||
|
|
|
@ -0,0 +1,105 @@
|
|||
/*
|
||||
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 ResourcePrefetchRuntimeModule extends RuntimeModule {
|
||||
/**
|
||||
* @param {string} type "prefetch" or "preload"
|
||||
*/
|
||||
constructor(type) {
|
||||
super(`asset ${type}`, RuntimeModule.STAGE_ATTACH);
|
||||
this._type = type;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {string | null} runtime code
|
||||
*/
|
||||
generate() {
|
||||
const { compilation } = this;
|
||||
if (!compilation) return null;
|
||||
|
||||
const { runtimeTemplate, outputOptions } = compilation;
|
||||
const fnName =
|
||||
this._type === "prefetch"
|
||||
? RuntimeGlobals.prefetchAsset
|
||||
: RuntimeGlobals.preloadAsset;
|
||||
|
||||
const crossOriginLoading = outputOptions.crossOriginLoading;
|
||||
const isNeutralPlatform = runtimeTemplate.isNeutralPlatform();
|
||||
|
||||
// For neutral platform (universal targets), generate code that checks for document at runtime
|
||||
const code = [
|
||||
"var url;",
|
||||
"if (relative) {",
|
||||
Template.indent([
|
||||
`url = new ${RuntimeGlobals.relativeUrl}(${RuntimeGlobals.require}(moduleId));`
|
||||
]),
|
||||
"} else {",
|
||||
Template.indent([
|
||||
`url = new URL(${RuntimeGlobals.require}(moduleId), ${RuntimeGlobals.baseURI});`
|
||||
]),
|
||||
"}",
|
||||
"",
|
||||
"var link = document.createElement('link');",
|
||||
`link.rel = '${this._type}';`,
|
||||
"if (as) link.as = as;",
|
||||
"link.href = url.href;",
|
||||
"",
|
||||
"if (fetchPriority) {",
|
||||
Template.indent([
|
||||
"link.fetchPriority = fetchPriority;",
|
||||
"link.setAttribute('fetchpriority', fetchPriority);"
|
||||
]),
|
||||
"}",
|
||||
"",
|
||||
"if (type) link.type = type;",
|
||||
"if (media) link.media = media;",
|
||||
"",
|
||||
crossOriginLoading
|
||||
? Template.asString([
|
||||
"if (link.href.indexOf(window.location.origin + '/') !== 0) {",
|
||||
Template.indent([
|
||||
`link.crossOrigin = ${JSON.stringify(crossOriginLoading)};`
|
||||
]),
|
||||
"}"
|
||||
])
|
||||
: "",
|
||||
"",
|
||||
"document.head.appendChild(link);"
|
||||
];
|
||||
|
||||
// For neutral platform, wrap the code to check for document availability
|
||||
if (isNeutralPlatform) {
|
||||
return Template.asString([
|
||||
`${fnName} = ${runtimeTemplate.basicFunction(
|
||||
"moduleId, as, fetchPriority, type, media, relative",
|
||||
[
|
||||
"// Only execute in browser environment",
|
||||
"if (typeof document !== 'undefined') {",
|
||||
Template.indent(code),
|
||||
"}"
|
||||
]
|
||||
)};`
|
||||
]);
|
||||
}
|
||||
|
||||
// For browser-only targets, generate code without the check
|
||||
return Template.asString([
|
||||
`${fnName} = ${runtimeTemplate.basicFunction(
|
||||
"moduleId, as, fetchPriority, type, media, relative",
|
||||
code
|
||||
)};`
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = ResourcePrefetchRuntimeModule;
|
|
@ -182,6 +182,91 @@ class URLParserPlugin {
|
|||
relative
|
||||
);
|
||||
dep.loc = /** @type {DependencyLocation} */ (expr.loc);
|
||||
// Parse magic comments with simplified rules
|
||||
if (importOptions) {
|
||||
// Accept only boolean true for webpackPrefetch
|
||||
if (importOptions.webpackPrefetch === true) {
|
||||
dep.prefetch = true;
|
||||
} else if (importOptions.webpackPrefetch !== undefined) {
|
||||
parser.state.module.addWarning(
|
||||
new UnsupportedFeatureWarning(
|
||||
`\`webpackPrefetch\` expected true, but received: ${importOptions.webpackPrefetch}.`,
|
||||
/** @type {DependencyLocation} */ (expr.loc)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// Accept only boolean true for webpackPreload
|
||||
if (importOptions.webpackPreload === true) {
|
||||
dep.preload = true;
|
||||
} else if (importOptions.webpackPreload !== undefined) {
|
||||
parser.state.module.addWarning(
|
||||
new UnsupportedFeatureWarning(
|
||||
`\`webpackPreload\` expected true, but received: ${importOptions.webpackPreload}.`,
|
||||
/** @type {DependencyLocation} */ (expr.loc)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// webpackFetchPriority: "high" | "low" | "auto"
|
||||
if (
|
||||
typeof importOptions.webpackFetchPriority === "string" &&
|
||||
["high", "low", "auto"].includes(importOptions.webpackFetchPriority)
|
||||
) {
|
||||
dep.fetchPriority = importOptions.webpackFetchPriority;
|
||||
} else if (importOptions.webpackFetchPriority !== undefined) {
|
||||
parser.state.module.addWarning(
|
||||
new UnsupportedFeatureWarning(
|
||||
`\`webpackFetchPriority\` expected "low", "high" or "auto", but received: ${importOptions.webpackFetchPriority}.`,
|
||||
/** @type {DependencyLocation} */ (expr.loc)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// webpackPreloadAs: allow override of the "as" attribute for preload
|
||||
if (importOptions.webpackPreloadAs !== undefined) {
|
||||
if (typeof importOptions.webpackPreloadAs === "string") {
|
||||
dep.preloadAs = importOptions.webpackPreloadAs;
|
||||
} else {
|
||||
parser.state.module.addWarning(
|
||||
new UnsupportedFeatureWarning(
|
||||
`\`webpackPreloadAs\` expected a string, but received: ${importOptions.webpackPreloadAs}.`,
|
||||
/** @type {DependencyLocation} */ (expr.loc)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// webpackPreloadType: set link.type when provided
|
||||
if (importOptions.webpackPreloadType !== undefined) {
|
||||
if (typeof importOptions.webpackPreloadType === "string") {
|
||||
dep.preloadType = importOptions.webpackPreloadType;
|
||||
} else {
|
||||
parser.state.module.addWarning(
|
||||
new UnsupportedFeatureWarning(
|
||||
`\`webpackPreloadType\` expected a string, but received: ${importOptions.webpackPreloadType}.`,
|
||||
/** @type {DependencyLocation} */ (expr.loc)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// webpackPreloadMedia: set link.media when provided
|
||||
if (importOptions.webpackPreloadMedia !== undefined) {
|
||||
if (typeof importOptions.webpackPreloadMedia === "string") {
|
||||
dep.preloadMedia = importOptions.webpackPreloadMedia;
|
||||
} else {
|
||||
parser.state.module.addWarning(
|
||||
new UnsupportedFeatureWarning(
|
||||
`\`webpackPreloadMedia\` expected a string, but received: ${importOptions.webpackPreloadMedia}.`,
|
||||
/** @type {DependencyLocation} */ (expr.loc)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Register the dependency
|
||||
parser.state.current.addDependency(dep);
|
||||
InnerGraph.onUsage(parser.state, (e) => (dep.usedByExports = e));
|
||||
return true;
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
|
|
@ -0,0 +1,3 @@
|
|||
body {
|
||||
background-color: #f0f0f0;
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
.typed-element {
|
||||
color: #333;
|
||||
font-size: 16px;
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
"use strict";
|
||||
|
||||
// This file is used to generate expected warnings during compilation
|
||||
|
||||
// Invalid fetchPriority value - should generate warning
|
||||
const invalidPriorityUrl = new URL(/* webpackPrefetch: true */ /* webpackFetchPriority: "invalid" */ "./assets/images/priority-invalid.png", import.meta.url);
|
||||
// Invalid preloadAs (non-string) - should generate warning
|
||||
const invalidPreloadAs = new URL(
|
||||
/* webpackPreload: true */
|
||||
/* webpackPreloadAs: 123 */
|
||||
"./assets/images/priority-invalid.png",
|
||||
import.meta.url
|
||||
);
|
||||
// Invalid preloadType (non-string) - should generate warning
|
||||
const invalidPreloadType = new URL(
|
||||
/* webpackPreload: true */
|
||||
/* webpackPreloadType: 123 */
|
||||
"./assets/images/priority-invalid.png",
|
||||
import.meta.url
|
||||
);
|
||||
|
||||
// Invalid preloadMedia (non-string) - should generate warning
|
||||
const invalidPreloadMedia = new URL(
|
||||
/* webpackPreload: true */
|
||||
/* webpackPreloadMedia: 456 */
|
||||
"./assets/images/priority-invalid.png",
|
||||
import.meta.url
|
||||
);
|
||||
|
||||
export default {};
|
|
@ -0,0 +1,178 @@
|
|||
"use strict";
|
||||
|
||||
function verifyLink(link, expectations) {
|
||||
expect(link._type).toBe("link");
|
||||
expect(link.rel).toBe(expectations.rel);
|
||||
|
||||
if (expectations.as) {
|
||||
expect(link.as).toBe(expectations.as);
|
||||
}
|
||||
|
||||
if (expectations.type !== undefined) {
|
||||
if (expectations.type) {
|
||||
expect(link.type).toBe(expectations.type);
|
||||
} else {
|
||||
expect(link.type).toBeUndefined();
|
||||
}
|
||||
}
|
||||
|
||||
if (expectations.media !== undefined) {
|
||||
if (expectations.media) {
|
||||
expect(link.media).toBe(expectations.media);
|
||||
} else {
|
||||
expect(link.media).toBeUndefined();
|
||||
}
|
||||
}
|
||||
|
||||
if (expectations.fetchPriority !== undefined) {
|
||||
if (expectations.fetchPriority) {
|
||||
expect(link._attributes.fetchpriority).toBe(expectations.fetchPriority);
|
||||
expect(link.fetchPriority).toBe(expectations.fetchPriority);
|
||||
} else {
|
||||
expect(link._attributes.fetchpriority).toBeUndefined();
|
||||
expect(link.fetchPriority).toBeUndefined();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (expectations.href) {
|
||||
expect(link.href.toString()).toMatch(expectations.href);
|
||||
}
|
||||
}
|
||||
|
||||
it("should generate all prefetch and preload links", () => {
|
||||
const urls = {
|
||||
prefetchHigh: new URL(
|
||||
/* webpackPrefetch: true */ /* webpackFetchPriority: "high" */
|
||||
"./assets/images/priority-high.png",
|
||||
import.meta.url
|
||||
),
|
||||
preloadLow: new URL(
|
||||
/* webpackPreload: true */ /* webpackFetchPriority: "low" */
|
||||
"./assets/styles/priority-low.css",
|
||||
import.meta.url
|
||||
),
|
||||
prefetchAuto: new URL(
|
||||
/* webpackPrefetch: true */ /* webpackFetchPriority: "auto" */
|
||||
"./priority-auto.js",
|
||||
import.meta.url
|
||||
),
|
||||
bothHints: new URL(
|
||||
/* webpackPrefetch: true */ /* webpackPreload: true */ /* webpackFetchPriority: "high" */
|
||||
"./assets/images/both-hints.png",
|
||||
import.meta.url
|
||||
),
|
||||
noPriority: new URL(
|
||||
/* webpackPrefetch: true */
|
||||
"./assets/images/test.png",
|
||||
import.meta.url
|
||||
),
|
||||
preloadFont: new URL(
|
||||
/* webpackPreload: true */
|
||||
"./assets/fonts/test.woff2",
|
||||
import.meta.url
|
||||
)
|
||||
};
|
||||
|
||||
const prefetchHighLink = document.head._children.find(
|
||||
link => link.href.includes("priority-high.png") && link.rel === "prefetch"
|
||||
);
|
||||
expect(prefetchHighLink).toBeTruthy();
|
||||
verifyLink(prefetchHighLink, {
|
||||
rel: "prefetch",
|
||||
as: "image",
|
||||
fetchPriority: "high",
|
||||
href: /priority-high\.png$/
|
||||
});
|
||||
|
||||
const preloadLowLink = document.head._children.find(
|
||||
link => link.href.includes("priority-low.css") && link.rel === "preload"
|
||||
);
|
||||
expect(preloadLowLink).toBeTruthy();
|
||||
verifyLink(preloadLowLink, {
|
||||
rel: "preload",
|
||||
as: "style",
|
||||
fetchPriority: "low",
|
||||
href: /priority-low\.css$/
|
||||
});
|
||||
|
||||
const prefetchAutoLink = document.head._children.find(
|
||||
link => link.href.includes("priority-auto.js") && link.rel === "prefetch"
|
||||
);
|
||||
expect(prefetchAutoLink).toBeTruthy();
|
||||
verifyLink(prefetchAutoLink, {
|
||||
rel: "prefetch",
|
||||
as: "script",
|
||||
fetchPriority: "auto"
|
||||
});
|
||||
|
||||
const bothHintsLink = document.head._children.find(
|
||||
link => link.href.includes("both-hints.png")
|
||||
);
|
||||
expect(bothHintsLink).toBeTruthy();
|
||||
expect(bothHintsLink.rel).toBe("preload");
|
||||
expect(bothHintsLink._attributes.fetchpriority).toBe("high");
|
||||
|
||||
const noPriorityLink = document.head._children.find(
|
||||
link => link.href.includes("test.png") && link.rel === "prefetch" &&
|
||||
!link._attributes.fetchpriority
|
||||
);
|
||||
expect(noPriorityLink).toBeTruthy();
|
||||
verifyLink(noPriorityLink, {
|
||||
rel: "prefetch",
|
||||
as: "image",
|
||||
fetchPriority: undefined
|
||||
});
|
||||
|
||||
const fontPreloadLink = document.head._children.find(
|
||||
link => link.href.includes("test.woff2") && link.rel === "preload"
|
||||
);
|
||||
expect(fontPreloadLink).toBeTruthy();
|
||||
verifyLink(fontPreloadLink, {
|
||||
rel: "preload",
|
||||
as: "font",
|
||||
href: /test\.woff2$/
|
||||
});
|
||||
});
|
||||
|
||||
it("should allow overriding as/type/media via magic comments", () => {
|
||||
const override = new URL(
|
||||
/* webpackPreload: true */
|
||||
/* webpackPreloadAs: "font" */
|
||||
/* webpackPreloadType: "font/woff2" */
|
||||
/* webpackPreloadMedia: "(max-width: 600px)" */
|
||||
"./assets/images/override.png",
|
||||
import.meta.url
|
||||
);
|
||||
|
||||
const link = document.head._children.find(
|
||||
l => l.href.includes("override.png") && l.rel === "preload"
|
||||
);
|
||||
expect(link).toBeTruthy();
|
||||
verifyLink(link, {
|
||||
rel: "preload",
|
||||
as: "font",
|
||||
type: "font/woff2",
|
||||
media: "(max-width: 600px)",
|
||||
href: /override\.png$/
|
||||
});
|
||||
});
|
||||
|
||||
it("should accept additional as tokens from Fetch Standard (e.g., sharedworker)", () => {
|
||||
const u = new URL(
|
||||
/* webpackPreload: true */
|
||||
/* webpackPreloadAs: "sharedworker" */
|
||||
"./priority-auto.js",
|
||||
import.meta.url
|
||||
);
|
||||
|
||||
const link = document.head._children.find(
|
||||
l => l.href.includes("priority-auto.js") && l.rel === "preload"
|
||||
);
|
||||
expect(link).toBeTruthy();
|
||||
verifyLink(link, {
|
||||
rel: "preload",
|
||||
as: "sharedworker",
|
||||
href: /priority-auto\.js$/
|
||||
});
|
||||
});
|
|
@ -0,0 +1,4 @@
|
|||
"use strict";
|
||||
|
||||
// Test asset file
|
||||
console.log("priority-auto.js loaded");
|
|
@ -0,0 +1,66 @@
|
|||
"use strict";
|
||||
|
||||
// Mock document.head structure for testing
|
||||
const mockCreateElement = (tagName) => {
|
||||
const element = {
|
||||
_type: tagName,
|
||||
_attributes: {},
|
||||
setAttribute(name, value) {
|
||||
this._attributes[name] = value;
|
||||
// Also set as property for fetchPriority
|
||||
if (name === "fetchpriority") {
|
||||
this.fetchPriority = value;
|
||||
}
|
||||
},
|
||||
getAttribute(name) {
|
||||
return this._attributes[name];
|
||||
}
|
||||
};
|
||||
|
||||
// Set properties based on tag type
|
||||
if (tagName === "link") {
|
||||
element.rel = "";
|
||||
element.as = "";
|
||||
element.href = "";
|
||||
element.type = undefined;
|
||||
element.media = undefined;
|
||||
element.fetchPriority = undefined;
|
||||
} else if (tagName === "script") {
|
||||
element.src = "";
|
||||
element.async = true;
|
||||
element.fetchPriority = undefined;
|
||||
}
|
||||
|
||||
return element;
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
beforeExecute: () => {
|
||||
// Mock document for browser environment
|
||||
global.document = {
|
||||
head: {
|
||||
_children: [],
|
||||
appendChild(element) {
|
||||
this._children.push(element);
|
||||
}
|
||||
},
|
||||
createElement: mockCreateElement
|
||||
};
|
||||
|
||||
// Mock window for import.meta.url
|
||||
global.window = {
|
||||
location: {
|
||||
href: "https://test.example.com/"
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
findBundle() {
|
||||
return ["main.js"];
|
||||
},
|
||||
|
||||
moduleScope(scope) {
|
||||
// Make document available in the module scope
|
||||
scope.document = global.document;
|
||||
}
|
||||
};
|
|
@ -0,0 +1,4 @@
|
|||
"use strict";
|
||||
|
||||
// Test JavaScript file
|
||||
console.log("test.js loaded");
|
|
@ -0,0 +1,14 @@
|
|||
"use strict";
|
||||
|
||||
module.exports = [
|
||||
// Invalid fetchPriority value warning
|
||||
[
|
||||
/`webpackFetchPriority` expected "low", "high" or "auto", but received: invalid\./
|
||||
],
|
||||
// Invalid preloadAs (non-string)
|
||||
[/`webpackPreloadAs` expected a string, but received: 123\./],
|
||||
// Invalid preloadType (non-string)
|
||||
[/`webpackPreloadType` expected a string, but received: 123\./],
|
||||
// Invalid preloadMedia (non-string)
|
||||
[/`webpackPreloadMedia` expected a string, but received: 456\./]
|
||||
];
|
|
@ -0,0 +1,24 @@
|
|||
"use strict";
|
||||
|
||||
/** @type {import("../../../../types").Configuration} */
|
||||
module.exports = {
|
||||
mode: "development",
|
||||
entry: {
|
||||
main: "./index.js",
|
||||
warnings: "./generate-warnings.js"
|
||||
},
|
||||
output: {
|
||||
filename: "[name].js",
|
||||
assetModuleFilename: "[name][ext]",
|
||||
publicPath: "/public/"
|
||||
},
|
||||
target: "web",
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.(png|jpg|css|woff2)$/,
|
||||
type: "asset/resource"
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
Binary file not shown.
After Width: | Height: | Size: 68 B |
|
@ -0,0 +1,49 @@
|
|||
// Test cases for new URL() prefetch/preload support
|
||||
|
||||
it("should prefetch an image asset", () => {
|
||||
const url = new URL(
|
||||
/* webpackPrefetch: true */
|
||||
"./prefetch-image.png",
|
||||
import.meta.url
|
||||
);
|
||||
expect(url.href).toMatch(/prefetch-image\.png$/);
|
||||
});
|
||||
|
||||
it("should preload an image asset", () => {
|
||||
const url = new URL(
|
||||
/* webpackPreload: true */
|
||||
"./preload-image.png",
|
||||
import.meta.url
|
||||
);
|
||||
expect(url.href).toMatch(/preload-image\.png$/);
|
||||
});
|
||||
|
||||
it("should preload with fetch priority", () => {
|
||||
const url = new URL(
|
||||
/* webpackPreload: true */
|
||||
/* webpackFetchPriority: "high" */
|
||||
"./priority-image.png",
|
||||
import.meta.url
|
||||
);
|
||||
expect(url.href).toMatch(/priority-image\.png$/);
|
||||
});
|
||||
|
||||
it("should handle invalid fetch priority", () => {
|
||||
const url2 = new URL(
|
||||
/* webpackPreload: true */
|
||||
/* webpackFetchPriority: "invalid" */
|
||||
"./invalid-priority-image.png",
|
||||
import.meta.url
|
||||
);
|
||||
expect(url2.href).toMatch(/invalid-priority-image\.png$/);
|
||||
});
|
||||
|
||||
it("should handle both prefetch and preload", () => {
|
||||
const url3 = new URL(
|
||||
/* webpackPrefetch: true */
|
||||
/* webpackPreload: true */
|
||||
"./both-hints-image.png",
|
||||
import.meta.url
|
||||
);
|
||||
expect(url3.href).toMatch(/both-hints-image\.png$/);
|
||||
});
|
Binary file not shown.
After Width: | Height: | Size: 68 B |
Binary file not shown.
After Width: | Height: | Size: 68 B |
Binary file not shown.
After Width: | Height: | Size: 68 B |
Binary file not shown.
After Width: | Height: | Size: 68 B |
|
@ -0,0 +1,7 @@
|
|||
"use strict";
|
||||
|
||||
module.exports = {
|
||||
findBundle() {
|
||||
return ["main.js"];
|
||||
}
|
||||
};
|
|
@ -0,0 +1,5 @@
|
|||
"use strict";
|
||||
|
||||
const supportsWorker = require("../../../helpers/supportsWorker");
|
||||
|
||||
module.exports = () => supportsWorker();
|
|
@ -0,0 +1,8 @@
|
|||
"use strict";
|
||||
|
||||
module.exports = [
|
||||
// Invalid fetch priority
|
||||
[
|
||||
/`webpackFetchPriority` expected "low", "high" or "auto", but received: invalid\./
|
||||
]
|
||||
];
|
|
@ -0,0 +1,18 @@
|
|||
"use strict";
|
||||
|
||||
/** @type {import("../../../../types").Configuration} */
|
||||
module.exports = {
|
||||
output: {
|
||||
filename: "[name].js",
|
||||
assetModuleFilename: "[name][ext]"
|
||||
},
|
||||
target: "web",
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.png$/,
|
||||
type: "asset/resource"
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
|
@ -18767,8 +18767,10 @@ declare namespace exports {
|
|||
export let moduleLoaded: "module.loaded";
|
||||
export let nodeModuleDecorator: "__webpack_require__.nmd";
|
||||
export let onChunksLoaded: "__webpack_require__.O";
|
||||
export let prefetchAsset: "__webpack_require__.PA";
|
||||
export let prefetchChunk: "__webpack_require__.E";
|
||||
export let prefetchChunkHandlers: "__webpack_require__.F";
|
||||
export let preloadAsset: "__webpack_require__.LA";
|
||||
export let preloadChunk: "__webpack_require__.G";
|
||||
export let preloadChunkHandlers: "__webpack_require__.H";
|
||||
export let publicPath: "__webpack_require__.p";
|
||||
|
|
Loading…
Reference in New Issue