feat: add webpackPreloadType support for new URL() syntax

This commit is contained in:
Ryuya 2025-07-26 20:48:35 -07:00
parent fb8bd910f0
commit 9532eea79c
9 changed files with 55 additions and 15 deletions

View File

@ -40,9 +40,8 @@ const getIgnoredRawDataUrlModule = memoize(
* @returns {string} The 'as' attribute value * @returns {string} The 'as' attribute value
*/ */
const getAssetType = (request) => { const getAssetType = (request) => {
// Reference: MDN rel=preload documentation (https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Attributes/rel/preload) // Reference: MDN rel=preload documentation (https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Attributes/rel/preload#what_types_of_content_can_be_preloaded)
// Valid 'as' values: fetch, font, image, script, style, track // 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)) { if (/\.(png|jpe?g|gif|svg|webp|avif|bmp|ico|tiff?)$/i.test(request)) {
return "image"; return "image";
} else if (/\.(woff2?|ttf|otf|eot)$/i.test(request)) { } else if (/\.(woff2?|ttf|otf|eot)$/i.test(request)) {
@ -59,8 +58,6 @@ const getAssetType = (request) => {
// Audio/video files: use 'fetch' as fallback since as='audio'/'video' not supported // Audio/video files: use 'fetch' as fallback since as='audio'/'video' not supported
// Reference: https://github.com/mdn/browser-compat-data/issues/9577 // Reference: https://github.com/mdn/browser-compat-data/issues/9577
return "fetch"; return "fetch";
} else if (/\.(json|xml|txt|csv|pdf|doc|docx|wasm)$/i.test(request)) {
return "fetch"; // Data files, documents, WebAssembly
} }
return "fetch"; // Generic fetch for unknown types return "fetch"; // Generic fetch for unknown types
}; };
@ -121,6 +118,7 @@ class URLDependency extends ModuleDependency {
write(this.preload); write(this.preload);
write(this.fetchPriority); write(this.fetchPriority);
write(this.preloadAs); write(this.preloadAs);
write(this.preloadType);
super.serialize(context); super.serialize(context);
} }
@ -136,6 +134,7 @@ class URLDependency extends ModuleDependency {
this.preload = read(); this.preload = read();
this.fetchPriority = read(); this.fetchPriority = read();
this.preloadAs = read(); this.preloadAs = read();
this.preloadType = read();
super.deserialize(context); super.deserialize(context);
} }
} }
@ -206,18 +205,21 @@ URLDependency.Template = class URLDependencyTemplate extends (
const fetchPriority = validFetchPriority const fetchPriority = validFetchPriority
? `"${validFetchPriority}"` ? `"${validFetchPriority}"`
: "undefined"; : "undefined";
const preloadType = dep.preloadType
? `"${dep.preloadType}"`
: "undefined";
if (needsPrefetch && !needsPreload) { if (needsPrefetch && !needsPreload) {
// Only prefetch // Only prefetch
runtimeRequirements.add(RuntimeGlobals.prefetchAsset); runtimeRequirements.add(RuntimeGlobals.prefetchAsset);
hintCode.push( hintCode.push(
`${RuntimeGlobals.prefetchAsset}(url, "${asType}", ${fetchPriority});` `${RuntimeGlobals.prefetchAsset}(url, "${asType}", ${fetchPriority}, ${preloadType});`
); );
} else if (needsPreload) { } else if (needsPreload) {
// Preload (takes precedence over prefetch) // Preload (takes precedence over prefetch)
runtimeRequirements.add(RuntimeGlobals.preloadAsset); runtimeRequirements.add(RuntimeGlobals.preloadAsset);
hintCode.push( hintCode.push(
`${RuntimeGlobals.preloadAsset}(url, "${asType}", ${fetchPriority});` `${RuntimeGlobals.preloadAsset}(url, "${asType}", ${fetchPriority}, ${preloadType});`
); );
} }

View File

@ -33,12 +33,13 @@ class AssetPrefetchPreloadRuntimeModule extends RuntimeModule {
: RuntimeGlobals.preloadAsset; : RuntimeGlobals.preloadAsset;
return Template.asString([ return Template.asString([
`${fn} = ${runtimeTemplate.basicFunction("url, as, fetchPriority", [ `${fn} = ${runtimeTemplate.basicFunction("url, as, fetchPriority, type", [
"var link = document.createElement('link');", "var link = document.createElement('link');",
this._type === "prefetch" this._type === "prefetch"
? "link.rel = 'prefetch';" ? "link.rel = 'prefetch';"
: "link.rel = 'preload';", : "link.rel = 'preload';",
"if(as) link.as = as;", "if(as) link.as = as;",
"if(type) link.type = type;",
"link.href = url;", "link.href = url;",
"if(fetchPriority) {", "if(fetchPriority) {",
Template.indent([ Template.indent([

View File

@ -242,11 +242,25 @@ class URLParserPlugin {
); );
} }
// Validate webpackPreloadType
if (
importOptions.webpackPreloadType !== undefined &&
typeof importOptions.webpackPreloadType !== "string"
) {
parser.state.module.addWarning(
new UnsupportedFeatureWarning(
`\`webpackPreloadType\` expected a string, but received: ${importOptions.webpackPreloadType}.`,
/** @type {DependencyLocation} */ (expr.loc)
)
);
}
// Store hints on the dependency for later use // Store hints on the dependency for later use
dep.prefetch = importOptions.webpackPrefetch; dep.prefetch = importOptions.webpackPrefetch;
dep.preload = importOptions.webpackPreload; dep.preload = importOptions.webpackPreload;
dep.fetchPriority = importOptions.webpackFetchPriority; dep.fetchPriority = importOptions.webpackFetchPriority;
dep.preloadAs = importOptions.webpackPreloadAs; dep.preloadAs = importOptions.webpackPreloadAs;
dep.preloadType = importOptions.webpackPreloadType;
} }
// Add dependency directly // Add dependency directly

View File

@ -0,0 +1,3 @@
body {
background-color: #f0f0f0;
}

View File

@ -0,0 +1,4 @@
.typed-element {
color: #333;
font-size: 16px;
}

View File

@ -5,7 +5,7 @@
// Invalid fetchPriority value - should generate warning // Invalid fetchPriority value - should generate warning
const invalidPriorityUrl = new URL(/* webpackPrefetch: true */ /* webpackFetchPriority: "invalid" */ "./assets/images/priority-invalid.png", import.meta.url); const invalidPriorityUrl = new URL(/* webpackPrefetch: true */ /* webpackFetchPriority: "invalid" */ "./assets/images/priority-invalid.png", import.meta.url);
// Both prefetch and preload specified - no warning anymore (preload takes precedence) // Invalid webpackPreloadType value - should generate warning
const bothHintsUrl = new URL(/* webpackPrefetch: true */ /* webpackPreload: true */ /* webpackFetchPriority: "high" */ "./assets/images/both-hints.png", import.meta.url); const invalidTypeUrl = new URL(/* webpackPreload: true */ /* webpackPreloadType: 123 */ "./assets/styles/invalid-type.css", import.meta.url);
export default {}; export default {};

View File

@ -86,9 +86,8 @@ it("should handle multiple URLs with different priorities", () => {
}); });
it("should prefer preload over prefetch when both are specified", () => { it("should prefer preload over prefetch when both are specified", () => {
// Note: The warning for both hints is tested in generate-warnings.js // When both prefetch and preload are specified, preload takes precedence
// Here we just verify that preload takes precedence const bothUrl = new URL(/* webpackPrefetch: true */ /* webpackPreload: true */ /* webpackFetchPriority: "high" */ "./assets/images/both-hints.png", import.meta.url);
const bothUrl = new URL(/* webpackPreload: true */ /* webpackFetchPriority: "high" */ "./assets/images/test.png", import.meta.url);
expect(document.head._children).toHaveLength(1); expect(document.head._children).toHaveLength(1);
const link1 = document.head._children[0]; const link1 = document.head._children[0];
@ -97,6 +96,19 @@ it("should prefer preload over prefetch when both are specified", () => {
expect(link1._attributes.fetchpriority).toBe("high"); expect(link1._attributes.fetchpriority).toBe("high");
}); });
it("should handle webpackPreloadType for CSS files", () => {
// Test preload with custom type
const cssUrl = new URL(/* webpackPreload: true */ /* webpackPreloadType: "text/css" */ "./assets/styles/typed.css", import.meta.url);
expect(document.head._children).toHaveLength(1);
const link1 = document.head._children[0];
expect(link1._type).toBe("link");
expect(link1.rel).toBe("preload");
expect(link1.as).toBe("style");
expect(link1.type).toBe("text/css");
expect(link1.href.toString()).toMatch(/typed\.css$/);
});
it("should handle different asset types correctly", () => { it("should handle different asset types correctly", () => {
// Image // Image
const imageUrl = new URL(/* webpackPrefetch: true */ /* webpackFetchPriority: "high" */ "./assets/images/test.png", import.meta.url); const imageUrl = new URL(/* webpackPrefetch: true */ /* webpackFetchPriority: "high" */ "./assets/images/test.png", import.meta.url);

View File

@ -60,10 +60,11 @@ module.exports = {
moduleScope(scope) { moduleScope(scope) {
// Inject runtime globals that would normally be provided by webpack // Inject runtime globals that would normally be provided by webpack
scope.__webpack_require__ = { scope.__webpack_require__ = {
PA(url, as, fetchPriority) { PA(url, as, fetchPriority, type) {
const link = global.document.createElement("link"); const link = global.document.createElement("link");
link.rel = "prefetch"; link.rel = "prefetch";
if (as) link.as = as; if (as) link.as = as;
if (type) link.type = type;
link.href = url; link.href = url;
if (fetchPriority) { if (fetchPriority) {
link.fetchPriority = fetchPriority; link.fetchPriority = fetchPriority;
@ -71,10 +72,11 @@ module.exports = {
} }
global.document.head.appendChild(link); global.document.head.appendChild(link);
}, },
LA(url, as, fetchPriority) { LA(url, as, fetchPriority, type) {
const link = global.document.createElement("link"); const link = global.document.createElement("link");
link.rel = "preload"; link.rel = "preload";
if (as) link.as = as; if (as) link.as = as;
if (type) link.type = type;
link.href = url; link.href = url;
if (fetchPriority) { if (fetchPriority) {
link.fetchPriority = fetchPriority; link.fetchPriority = fetchPriority;

View File

@ -4,5 +4,7 @@ module.exports = [
// Invalid fetchPriority value warning // Invalid fetchPriority value warning
[ [
/`webpackFetchPriority` expected "low", "high" or "auto", but received: invalid\./ /`webpackFetchPriority` expected "low", "high" or "auto", but received: invalid\./
] ],
// Invalid webpackPreloadType value warning
[/`webpackPreloadType` expected a string, but received: 123\./]
]; ];