feat: add preload options for enhanced asset loading

- Introduced `preloadAs`, `preloadType`, and `preloadMedia` properties to `URLDependency` for better control over asset preloading.
This commit is contained in:
Ryuya 2025-09-15 06:24:36 -07:00
parent 37632364c5
commit 3c30c24d8f
4 changed files with 104 additions and 13 deletions

View File

@ -231,6 +231,7 @@
"serializables",
"serializer",
"serializers",
"serviceworker",
"shama",
"skypack",
"snapshotting",

View File

@ -52,6 +52,12 @@ class URLDependency extends ModuleDependency {
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() {
@ -93,6 +99,9 @@ class URLDependency extends ModuleDependency {
write(this.prefetch);
write(this.preload);
write(this.fetchPriority);
write(this.preloadAs);
write(this.preloadType);
write(this.preloadMedia);
super.serialize(context);
}
@ -107,6 +116,9 @@ class URLDependency extends ModuleDependency {
this.prefetch = read();
this.preload = read();
this.fetchPriority = read();
this.preloadAs = read();
this.preloadType = read();
this.preloadMedia = read();
super.deserialize(context);
}
}
@ -203,7 +215,7 @@ URLDependency.Template = class URLDependencyTemplate extends (
// Prefetch/Preload via InitFragment
if ((dep.prefetch || dep.preload) && module) {
const request = dep.request;
const assetType = URLDependencyTemplate._getAssetType(request);
const detectedAssetType = URLDependencyTemplate._getAssetType(request);
const id = chunkGraph.getModuleId(module);
if (id !== null) {
const moduleId = runtimeTemplate.moduleId({
@ -215,13 +227,19 @@ URLDependency.Template = class URLDependencyTemplate extends (
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}, ${JSON.stringify(
assetType
)}${dep.fetchPriority ? `, ${JSON.stringify(dep.fetchPriority)}` : ""}, ${
dep.relative
});\n`,
`${RuntimeGlobals.preloadAsset}(${moduleId}, ${asArg}, ${fetchPriorityArg}, ${typeArg}, ${mediaArg}, ${dep.relative});\n`,
InitFragment.STAGE_CONSTANTS,
-10,
`asset_preload_${moduleId}`
@ -229,13 +247,13 @@ URLDependency.Template = class URLDependencyTemplate extends (
);
} 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}, ${JSON.stringify(
assetType
)}${dep.fetchPriority ? `, ${JSON.stringify(dep.fetchPriority)}` : ""}, ${
dep.relative
});\n`,
`${RuntimeGlobals.prefetchAsset}(${moduleId}, ${asArg}, ${fetchPriorityArg}, undefined, undefined, ${dep.relative});\n`,
InitFragment.STAGE_CONSTANTS,
-5,
`asset_prefetch_${moduleId}`

View File

@ -61,6 +61,9 @@ class ResourcePrefetchRuntimeModule extends RuntimeModule {
]),
"}",
"",
"if (type) link.type = type;",
"if (media) link.media = media;",
"",
crossOriginLoading
? Template.asString([
"if (link.href.indexOf(window.location.origin + '/') !== 0) {",
@ -78,7 +81,7 @@ class ResourcePrefetchRuntimeModule extends RuntimeModule {
if (isNeutralPlatform) {
return Template.asString([
`${fnName} = ${runtimeTemplate.basicFunction(
"moduleId, as, fetchPriority, relative",
"moduleId, as, fetchPriority, type, media, relative",
[
"// Only execute in browser environment",
"if (typeof document !== 'undefined') {",
@ -92,7 +95,7 @@ class ResourcePrefetchRuntimeModule extends RuntimeModule {
// For browser-only targets, generate code without the check
return Template.asString([
`${fnName} = ${runtimeTemplate.basicFunction(
"moduleId, as, fetchPriority, relative",
"moduleId, as, fetchPriority, type, media, relative",
code
)};`
]);

View File

@ -224,6 +224,75 @@ class URLParserPlugin {
)
);
}
// webpackPreloadAs: allow override of the "as" attribute for preload
// Fetch Standard: Request destinations (enumerates destinations used by `as`) https://fetch.spec.whatwg.org/#concept-request-destination
if (importOptions.webpackPreloadAs !== undefined) {
const allowedAs = [
// Per HTML LS "match-preload-type" + Fetch Standard request destinations
// See references above
"audio",
"audioworklet",
"document",
"embed",
"fetch",
"font",
"image",
"manifest",
"object",
"paintworklet",
"report",
"script",
"sharedworker",
"serviceworker",
"style",
"track",
"video",
"worker",
"xslt"
];
if (
typeof importOptions.webpackPreloadAs === "string" &&
allowedAs.includes(importOptions.webpackPreloadAs)
) {
dep.preloadAs = importOptions.webpackPreloadAs;
} else {
parser.state.module.addWarning(
new UnsupportedFeatureWarning(
`\`webpackPreloadAs\` expected one of ${JSON.stringify(allowedAs)}, 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