Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fetch priority #16991

Closed
wants to merge 14 commits into from
5 changes: 3 additions & 2 deletions cspell.json
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@
"eval",
"Ewald",
"exitance",
"fetchpriority",
"filebase",
"fileoverview",
"filepath",
Expand All @@ -104,8 +105,8 @@
"hotupdatechunk",
"ident",
"idents",
"IIFE's",
"IIFE",
"IIFE's",
"informations",
"instanceof",
"inversed",
Expand Down Expand Up @@ -273,8 +274,8 @@
"webassembly",
"webassemblyjs",
"webmake",
"webpack's",
"webpack",
"webpack's",
"Xarray",
"Xexports",
"Xfactory",
Expand Down
4 changes: 4 additions & 0 deletions declarations/WebpackOptions.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2970,6 +2970,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;
TheLarkInn marked this conversation as resolved.
Show resolved Hide resolved
/**
* Specifies global mode for dynamic import.
*/
Expand Down
1 change: 1 addition & 0 deletions lib/ChunkGroup.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ const {
* @typedef {Object} RawChunkGroupOptions
* @property {number=} preloadOrder
* @property {number=} prefetchOrder
* @property {string=} fetchPriority
*/

/** @typedef {RawChunkGroupOptions & { name?: string }} ChunkGroupOptions */
Expand Down
6 changes: 6 additions & 0 deletions lib/RuntimeGlobals.js
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,12 @@ exports.createScriptUrl = "__webpack_require__.tu";
*/
exports.getTrustedTypesPolicy = "__webpack_require__.tt";

/**
* function to return webpack's Trusted Types policy
* Arguments: () => TrustedTypePolicy
*/
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inaccurate comment.

exports.getFetchPriority = "__webpack_require__.fp";

/**
* the chunk name of the chunk with the runtime
*/
Expand Down
6 changes: 5 additions & 1 deletion lib/RuntimePlugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -373,9 +373,13 @@ class RuntimePlugin {
if (withCreateScriptUrl) {
set.add(RuntimeGlobals.createScriptUrl);
}
const hasGetFetchPriority = set.has(RuntimeGlobals.getFetchPriority);
compilation.addRuntimeModule(
chunk,
new LoadScriptRuntimeModule(withCreateScriptUrl)
new LoadScriptRuntimeModule(
withCreateScriptUrl,
hasGetFetchPriority
)
);
return true;
});
Expand Down
1 change: 1 addition & 0 deletions lib/config/defaults.js
Original file line number Diff line number Diff line change
Expand Up @@ -483,6 +483,7 @@ const applyJavascriptParserOptionsDefaults = (
D(parserOptions, "dynamicImportMode", "lazy");
D(parserOptions, "dynamicImportPrefetch", false);
D(parserOptions, "dynamicImportPreload", false);
D(parserOptions, "dynamicImportFetchPriority", false);
D(parserOptions, "createRequire", isNode);
if (futureDefaults) D(parserOptions, "exportsPresence", "error");
};
Expand Down
21 changes: 21 additions & 0 deletions lib/dependencies/ImportMetaContextDependencyParserPlugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,27 @@ module.exports = class ImportMetaContextDependencyParserPlugin {
}
break;
}
case "fetchPriority": {
const expr = parser.evaluateExpression(
/** @type {ExpressionNode} */ (prop.value)
);
if (expr.isBoolean()) {
groupOptions.fetchPriority = "auto";
Copy link
Contributor

@Zlatkovsky Zlatkovsky May 2, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@aboktor , what is the purpose of allowing a boolean at all, when setting it (and regardless of the value) will result in "auto"? I would think just keeping it to the three enums -- "high", "low", "auto" -- would keep it both simpler and more understandable

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe @alexander-akait added that code. I do see that pattern followed throughout webpack though.

} else if (
expr.isString() &&
["high", "low", "auto"].includes(expr.string)
) {
groupOptions.fetchPriority = expr.string;
} else {
errors.push(
createPropertyParseError(
prop,
'boolean|"high"|"low"|"auto"'
)
);
}
break;
}
case "recursive": {
const recursiveExpr = parser.evaluateExpression(
/** @type {ExpressionNode} */ (prop.value)
Expand Down
31 changes: 30 additions & 1 deletion lib/dependencies/ImportParserPlugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,11 @@ 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;
Expand All @@ -52,6 +56,14 @@ class ImportParserPlugin {
)
groupOptions.prefetchOrder =
dynamicImportPrefetch === true ? 0 : dynamicImportPrefetch;
if (
dynamicImportFetchPriority !== undefined &&
dynamicImportFetchPriority !== false
)
groupOptions.fetchPriority =
dynamicImportFetchPriority === true
? "auto"
: dynamicImportFetchPriority;

const { options: importOptions, errors: commentErrors } =
parser.parseCommentOptions(expr.range);
Expand Down Expand Up @@ -136,6 +148,23 @@ class ImportParserPlugin {
);
}
}
if (importOptions.webpackFetchPriority !== undefined) {
if (importOptions.webpackFetchPriority === true) {
groupOptions.fetchPriority = "auto";
} else if (
typeof importOptions.webpackFetchPriority === "string" &&
["high", "low", "auto"].includes(importOptions.webpackFetchPriority)
) {
groupOptions.fetchPriority = importOptions.webpackFetchPriority;
} else {
parser.state.module.addWarning(
new UnsupportedFeatureWarning(
`\`webpackFetchPriority\` expected true or "low", "high" or "auto", but received: ${importOptions.webpackFetchPriority}.`,
expr.loc
)
);
}
}
if (importOptions.webpackInclude !== undefined) {
if (
!importOptions.webpackInclude ||
Expand Down
41 changes: 41 additions & 0 deletions lib/prefetch/ChunkFetchPriorityFunctionRuntimeModule.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
MIT License http://www.opensource.org/licenses/mit-license.php
*/

"use strict";

const RuntimeGlobals = require("../RuntimeGlobals");
const RuntimeModule = require("../RuntimeModule");
const Template = require("../Template");

/** @typedef {import("../RuntimeTemplate")} RuntimeTemplate */

class ChunkFetchPriorityFunctionRuntimeModule extends RuntimeModule {
constructor(chunksFetchPriorityMap) {
super(`chunk fetch priority function`);
this.chunksFetchPriorityMap = chunksFetchPriorityMap;
}

/**
* @returns {string} runtime code
*/
generate() {
const { chunksFetchPriorityMap } = this;
const { runtimeTemplate } = this.compilation;
const fn = RuntimeGlobals.getFetchPriority;

return Template.asString([
`${fn} = ${runtimeTemplate.returningFunction(
`{${Object.keys(chunksFetchPriorityMap).map(
id =>
`${JSON.stringify(id)}:${JSON.stringify(
chunksFetchPriorityMap[id]
)}`
)}}[chunkId]`,
"chunkId"
)};`
]);
}
}

module.exports = ChunkFetchPriorityFunctionRuntimeModule;
32 changes: 31 additions & 1 deletion lib/prefetch/ChunkPrefetchPreloadPlugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"use strict";

const RuntimeGlobals = require("../RuntimeGlobals");
const ChunkFetchPriorityFunctionRuntimeModule = require("./ChunkFetchPriorityFunctionRuntimeModule");
const ChunkPrefetchFunctionRuntimeModule = require("./ChunkPrefetchFunctionRuntimeModule");
const ChunkPrefetchStartupRuntimeModule = require("./ChunkPrefetchStartupRuntimeModule");
const ChunkPrefetchTriggerRuntimeModule = require("./ChunkPrefetchTriggerRuntimeModule");
Expand Down Expand Up @@ -43,7 +44,7 @@ class ChunkPrefetchPreloadPlugin {
compilation.hooks.additionalTreeRuntimeRequirements.tap(
"ChunkPrefetchPreloadPlugin",
(chunk, set, { chunkGraph }) => {
const chunkMap = chunk.getChildIdsByOrdersMap(chunkGraph, false);
const chunkMap = chunk.getChildIdsByOrdersMap(chunkGraph);

if (chunkMap.prefetch) {
set.add(RuntimeGlobals.prefetchChunk);
Expand Down Expand Up @@ -87,6 +88,35 @@ class ChunkPrefetchPreloadPlugin {
);
set.add(RuntimeGlobals.preloadChunkHandlers);
});
compilation.hooks.additionalChunkRuntimeRequirements.tap(
"ChunkPrefetchPreloadPlugin",
(chunk, set, { chunkGraph }) => {
if (chunkGraph.getNumberOfEntryModules(chunk) === 0) return;

const chunksFetchPriorityMap = {};

for (const item of chunk.getAllAsyncChunks()) {
for (const group of item.groupsIterable) {
const fetchPriority = group.options["fetchPriority"];
if (fetchPriority === undefined) continue;

group.chunks.forEach(c => {
chunksFetchPriorityMap[c.id] = fetchPriority;
});
}
}

if (Object.keys(chunksFetchPriorityMap).length === 0) return;

compilation.addRuntimeModule(
chunk,
new ChunkFetchPriorityFunctionRuntimeModule(
chunksFetchPriorityMap
)
);
set.add(RuntimeGlobals.getFetchPriority);
}
);
}
);
}
Expand Down
108 changes: 62 additions & 46 deletions lib/runtime/LoadScriptRuntimeModule.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,12 @@ class LoadScriptRuntimeModule extends HelperRuntimeModule {

/**
* @param {boolean=} withCreateScriptUrl use create script url for trusted types
* @param {boolean=} hasGetFetchPriority use `fetchPriority` attribute
*/
constructor(withCreateScriptUrl) {
constructor(withCreateScriptUrl, hasGetFetchPriority) {
super("load script");
this._withCreateScriptUrl = withCreateScriptUrl;
this._hasGetFetchPriority = hasGetFetchPriority;
}

/**
Expand Down Expand Up @@ -81,6 +83,15 @@ class LoadScriptRuntimeModule extends HelperRuntimeModule {
uniqueName
? 'script.setAttribute("data-webpack", dataWebpackPrefix + key);'
: "",
this._hasGetFetchPriority
? Template.asString([
"if(fetchPriority) {",
Template.indent(
'script.setAttribute("fetchpriority", fetchPriority);'
),
"}"
])
: "",
`script.src = ${
this._withCreateScriptUrl
? `${RuntimeGlobals.createScriptUrl}(url)`
Expand All @@ -105,53 +116,58 @@ 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${
this._hasGetFetchPriority ? ", fetchPriority" : ""
}`,
[
"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++) {",
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([
"var s = scripts[i];",
`if(s.getAttribute("src") == url${
uniqueName
? ' || s.getAttribute("data-webpack") == dataWebpackPrefix + key'
: ""
}) { script = s; break; }`
"needAttach = true;",
createScript.call(code, this.chunk)
]),
"}"
]),
"}",
"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);"
])};`
"}",
"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);"
]
)};`
]);
}
}
Expand Down