Adding option to set fetchPriority on script tags

This commit is contained in:
Andrew Boktor 2023-04-14 15:30:42 -07:00 committed by alexander.akait
parent 5e3c4d0ddf
commit f68ab55320
22 changed files with 183 additions and 54 deletions

View File

@ -81,6 +81,7 @@
"eval",
"Ewald",
"exitance",
"fetchpriority",
"filebase",
"fileoverview",
"filepath",
@ -104,8 +105,8 @@
"hotupdatechunk",
"ident",
"idents",
"IIFE's",
"IIFE",
"IIFE's",
"informations",
"instanceof",
"inversed",
@ -274,8 +275,8 @@
"webassembly",
"webassemblyjs",
"webmake",
"webpack's",
"webpack",
"webpack's",
"Xarray",
"Xexports",
"Xfactory",

View File

@ -3005,6 +3005,10 @@ export interface JavascriptParserOptions {
* Enable/disable parsing "import { createRequire } from "module"" and evaluating createRequire().
*/
createRequire?: boolean | string;
/**
* Specifies global fetchPriority for dynamic import.
*/
dynamicImportFetchPriority?: ("low" | "high" | "auto") | boolean;
/**
* Specifies global mode for dynamic import.
*/

View File

@ -28,6 +28,7 @@ const {
* @typedef {Object} RawChunkGroupOptions
* @property {number=} preloadOrder
* @property {number=} prefetchOrder
* @property {string=} fetchPriority
*/
/** @typedef {RawChunkGroupOptions & { name?: string }} ChunkGroupOptions */

View File

@ -936,14 +936,17 @@ class RuntimeTemplate {
message,
chunkName: block.chunkName
});
const fetchPriority = JSON.stringify(chunkGroup.options.fetchPriority);
if (chunks.length === 1) {
const chunkId = JSON.stringify(chunks[0].id);
runtimeRequirements.add(RuntimeGlobals.ensureChunk);
return `${RuntimeGlobals.ensureChunk}(${comment}${chunkId})`;
return `${RuntimeGlobals.ensureChunk}(${comment}${chunkId}, ${fetchPriority})`;
} else if (chunks.length > 0) {
runtimeRequirements.add(RuntimeGlobals.ensureChunk);
const requireChunkId = chunk =>
`${RuntimeGlobals.ensureChunk}(${JSON.stringify(chunk.id)})`;
`${RuntimeGlobals.ensureChunk}(${JSON.stringify(
chunk.id
)}, ${fetchPriority})`;
return `Promise.all(${comment.trim()}[${chunks
.map(requireChunkId)
.join(", ")}])`;

View File

@ -47,10 +47,22 @@ class ImportParserPlugin {
/** @type {RawChunkGroupOptions} */
const groupOptions = {};
const { dynamicImportPreload, dynamicImportPrefetch } = this.options;
const {
dynamicImportPreload,
dynamicImportPrefetch,
dynamicImportFetchPriority
} = this.options;
if (dynamicImportPreload !== undefined && dynamicImportPreload !== false)
groupOptions.preloadOrder =
dynamicImportPreload === true ? 0 : dynamicImportPreload;
if (
dynamicImportFetchPriority !== undefined &&
dynamicImportFetchPriority !== false
)
groupOptions.fetchPriority =
dynamicImportFetchPriority === true
? "auto"
: dynamicImportFetchPriority;
if (
dynamicImportPrefetch !== undefined &&
dynamicImportPrefetch !== false
@ -141,6 +153,23 @@ class ImportParserPlugin {
);
}
}
if (importOptions.webpackFetchPriority !== undefined) {
if (
typeof importOptions.webpackFetchPriority !== "string" ||
!["high", "low", "auto"].includes(
importOptions.webpackFetchPriority
)
) {
parser.state.module.addWarning(
new UnsupportedFeatureWarning(
`\`webpackFetchPriority\` expected "low", "high" or "auto", but received: ${importOptions.webpackFetchPriority}.`,
expr.loc
)
);
} else {
groupOptions.fetchPriority = importOptions.webpackFetchPriority;
}
}
if (importOptions.webpackInclude !== undefined) {
if (
!importOptions.webpackInclude ||

View File

@ -30,11 +30,14 @@ class EnsureChunkRuntimeModule extends RuntimeModule {
"// This file contains only the entry chunk.",
"// The chunk loading function for additional chunks",
`${RuntimeGlobals.ensureChunk} = ${runtimeTemplate.basicFunction(
"chunkId",
"chunkId, fetchPriority",
[
`return Promise.all(Object.keys(${handlers}).reduce(${runtimeTemplate.basicFunction(
"promises, key",
[`${handlers}[key](chunkId, promises);`, "return promises;"]
[
`${handlers}[key](chunkId, promises, fetchPriority);`,
"return promises;"
]
)}, []));`
]
)};`

View File

@ -81,6 +81,9 @@ class LoadScriptRuntimeModule extends HelperRuntimeModule {
uniqueName
? 'script.setAttribute("data-webpack", dataWebpackPrefix + key);'
: "",
"if(fetchPriority) {",
Template.indent('script.setAttribute("fetchpriority", fetchPriority);'),
"}",
`script.src = ${
this._withCreateScriptUrl
? `${RuntimeGlobals.createScriptUrl}(url)`
@ -105,53 +108,56 @@ class LoadScriptRuntimeModule extends HelperRuntimeModule {
? `var dataWebpackPrefix = ${JSON.stringify(uniqueName + ":")};`
: "// data-webpack is not used as build has no uniqueName",
"// loadScript function to load a script via script tag",
`${fn} = ${runtimeTemplate.basicFunction("url, done, key, chunkId", [
"if(inProgress[url]) { inProgress[url].push(done); return; }",
"var script, needAttach;",
"if(key !== undefined) {",
Template.indent([
'var scripts = document.getElementsByTagName("script");',
"for(var i = 0; i < scripts.length; i++) {",
`${fn} = ${runtimeTemplate.basicFunction(
"url, done, key, chunkId, fetchPriority",
[
"if(inProgress[url]) { inProgress[url].push(done); return; }",
"var script, needAttach;",
"if(key !== undefined) {",
Template.indent([
"var s = scripts[i];",
`if(s.getAttribute("src") == url${
uniqueName
? ' || s.getAttribute("data-webpack") == dataWebpackPrefix + key'
: ""
}) { script = s; break; }`
'var scripts = document.getElementsByTagName("script");',
"for(var i = 0; i < scripts.length; i++) {",
Template.indent([
"var s = scripts[i];",
`if(s.getAttribute("src") == url${
uniqueName
? ' || s.getAttribute("data-webpack") == dataWebpackPrefix + key'
: ""
}) { script = s; break; }`
]),
"}"
]),
"}"
]),
"}",
"if(!script) {",
Template.indent([
"needAttach = true;",
createScript.call(code, this.chunk)
]),
"}",
"inProgress[url] = [done];",
"var onScriptComplete = " +
runtimeTemplate.basicFunction(
"prev, event",
Template.asString([
"// avoid mem leaks in IE.",
"script.onerror = script.onload = null;",
"clearTimeout(timeout);",
"var doneFns = inProgress[url];",
"delete inProgress[url];",
"script.parentNode && script.parentNode.removeChild(script);",
`doneFns && doneFns.forEach(${runtimeTemplate.returningFunction(
"fn(event)",
"fn"
)});`,
"if(prev) return prev(event);"
])
),
`var timeout = setTimeout(onScriptComplete.bind(null, undefined, { type: 'timeout', target: script }), ${loadTimeout});`,
"script.onerror = onScriptComplete.bind(null, script.onerror);",
"script.onload = onScriptComplete.bind(null, script.onload);",
"needAttach && document.head.appendChild(script);"
])};`
"}",
"if(!script) {",
Template.indent([
"needAttach = true;",
createScript.call(code, this.chunk)
]),
"}",
"inProgress[url] = [done];",
"var onScriptComplete = " +
runtimeTemplate.basicFunction(
"prev, event",
Template.asString([
"// avoid mem leaks in IE.",
"script.onerror = script.onload = null;",
"clearTimeout(timeout);",
"var doneFns = inProgress[url];",
"delete inProgress[url];",
"script.parentNode && script.parentNode.removeChild(script);",
`doneFns && doneFns.forEach(${runtimeTemplate.returningFunction(
"fn(event)",
"fn"
)});`,
"if(prev) return prev(event);"
])
),
`var timeout = setTimeout(onScriptComplete.bind(null, undefined, { type: 'timeout', target: script }), ${loadTimeout});`,
"script.onerror = onScriptComplete.bind(null, script.onerror);",
"script.onload = onScriptComplete.bind(null, script.onload);",
"needAttach && document.head.appendChild(script);"
]
)};`
]);
}
}

View File

@ -138,7 +138,7 @@ class JsonpChunkLoadingRuntimeModule extends RuntimeModule {
withLoading
? Template.asString([
`${fn}.j = ${runtimeTemplate.basicFunction(
"chunkId, promises",
"chunkId, promises, fetchPriority",
hasJsMatcher !== false
? Template.indent([
"// JSONP chunk loading for javascript",
@ -190,7 +190,7 @@ class JsonpChunkLoadingRuntimeModule extends RuntimeModule {
"}"
]
)};`,
`${RuntimeGlobals.loadScript}(url, loadingEnded, "chunk-" + chunkId, chunkId);`
`${RuntimeGlobals.loadScript}(url, loadingEnded, "chunk-" + chunkId, chunkId, fetchPriority);`
]),
hasJsMatcher === true
? "}"

View File

@ -1632,6 +1632,17 @@
}
]
},
"dynamicImportFetchPriority": {
"description": "Specifies global fetchPriority for dynamic import.",
"anyOf": [
{
"enum": ["low", "high", "auto"]
},
{
"type": "boolean"
}
]
},
"dynamicImportMode": {
"description": "Specifies global mode for dynamic import.",
"enum": ["eager", "weak", "lazy", "lazy-once"]

View File

@ -0,0 +1 @@
export default "a";

View File

@ -0,0 +1 @@
export default "b";

View File

@ -0,0 +1 @@
export default "c";

View File

@ -180,6 +180,13 @@ if (process.env.NODE_ENV === "production") {
}
);
});
it("should be able to load with webpackFetchPriorty high, low and auto", function () {
return Promise.all([
import(/* webpackFetchPriority: "high"*/ "./dir14/a"),
import(/* webpackFetchPriority: "low"*/ "./dir14/b"),
import(/* webpackFetchPriority: "auto"*/ "./dir14/c"),
])
})
}
function testChunkLoading(load, expectedSyncInitial, expectedSyncRequested) {

View File

@ -0,0 +1 @@
export default "a";

View File

@ -0,0 +1,3 @@
import * as shared from './shared';
console.log(shared);
export default "b";

View File

@ -0,0 +1,3 @@
import * as shared from './shared';
console.log(shared);
export default "b";

View File

@ -0,0 +1 @@
export default "c";

View File

@ -0,0 +1 @@
export default "d";

View File

@ -0,0 +1,31 @@
it("should set fetchPriority", () => {
// Single Chunk
import(/* webpackFetchPriority: "high" */ "./a");
expect(document.head._children).toHaveLength(1);
const script1 = document.head._children[0];
expect(script1._attributes.fetchpriority).toBe("high");
// Multiple Chunks
import(/* webpackFetchPriority: "high" */ "./b");
import(/* webpackFetchPriority: "high" */ "./b2");
expect(document.head._children).toHaveLength(4);
const script2 = document.head._children[1];
const script3 = document.head._children[2];
const script4 = document.head._children[3];
expect(script2._attributes.fetchpriority).toBe("high");
expect(script3._attributes.fetchpriority).toBe("high");
expect(script4._attributes.fetchpriority).toBe("high");
// Single Chunk, low
import(/* webpackFetchPriority: "low" */ "./c");
expect(document.head._children).toHaveLength(5);
const script5 = document.head._children[4];
expect(script5._attributes.fetchpriority).toBe("low");
// Single Chunk, auto
import(/* webpackFetchPriority: "auto" */ "./d");
expect(document.head._children).toHaveLength(6);
const script6 = document.head._children[5];
expect(script6._attributes.fetchpriority).toBe("auto");
})

View File

@ -0,0 +1 @@
export default "shared";

View File

@ -0,0 +1,14 @@
/** @type {import("../../../../").Configuration} */
module.exports = {
target: "web",
output: {
chunkFilename: "[name].js",
crossOriginLoading: "anonymous"
},
optimization: {
minimize: false,
splitChunks: {
minSize: 1
}
}
};

6
types.d.ts vendored
View File

@ -6013,6 +6013,11 @@ declare interface JavascriptParserOptions {
*/
createRequire?: string | boolean;
/**
* Specifies global fetchPriority for dynamic import.
*/
dynamicImportFetchPriority?: boolean | "auto" | "low" | "high";
/**
* Specifies global mode for dynamic import.
*/
@ -9900,6 +9905,7 @@ declare interface ProvidesObject {
declare interface RawChunkGroupOptions {
preloadOrder?: number;
prefetchOrder?: number;
fetchPriority?: string;
}
type RawLoaderDefinition<
OptionsType = {},