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

feat: added fetchPriority #17249

Merged
merged 26 commits into from Jun 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
5 changes: 3 additions & 2 deletions cspell.json
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 @@ -274,8 +275,8 @@
"webassembly",
"webassemblyjs",
"webmake",
"webpack's",
"webpack",
"webpack's",
"Xarray",
"Xexports",
"Xfactory",
Expand Down
4 changes: 4 additions & 0 deletions declarations/WebpackOptions.d.ts
Expand Up @@ -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" | false;
/**
* Specifies global mode for dynamic import.
*/
Expand Down
1 change: 1 addition & 0 deletions lib/ChunkGroup.js
Expand Up @@ -28,6 +28,7 @@ const {
* @typedef {Object} RawChunkGroupOptions
* @property {number=} preloadOrder
* @property {number=} prefetchOrder
* @property {("low" | "high" | "auto")=} fetchPriority
*/

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

/**
* a flag when a chunk has a fetch priority
*/
exports.hasFetchPriority = "has fetch priority";

/**
* the chunk name of the chunk with the runtime
*/
Expand Down
3 changes: 2 additions & 1 deletion lib/RuntimePlugin.js
Expand Up @@ -377,9 +377,10 @@ class RuntimePlugin {
if (withCreateScriptUrl) {
set.add(RuntimeGlobals.createScriptUrl);
}
const withFetchPriority = set.has(RuntimeGlobals.hasFetchPriority);
compilation.addRuntimeModule(
chunk,
new LoadScriptRuntimeModule(withCreateScriptUrl)
new LoadScriptRuntimeModule(withCreateScriptUrl, withFetchPriority)
);
return true;
});
Expand Down
22 changes: 20 additions & 2 deletions lib/RuntimeTemplate.js
Expand Up @@ -939,11 +939,29 @@ class RuntimeTemplate {
if (chunks.length === 1) {
const chunkId = JSON.stringify(chunks[0].id);
runtimeRequirements.add(RuntimeGlobals.ensureChunk);
return `${RuntimeGlobals.ensureChunk}(${comment}${chunkId})`;

const fetchPriority = chunkGroup.options.fetchPriority;

if (fetchPriority) {
runtimeRequirements.add(RuntimeGlobals.hasFetchPriority);
}

return `${RuntimeGlobals.ensureChunk}(${comment}${chunkId}${
fetchPriority ? `, ${JSON.stringify(fetchPriority)}` : ""
})`;
} else if (chunks.length > 0) {
runtimeRequirements.add(RuntimeGlobals.ensureChunk);

const fetchPriority = chunkGroup.options.fetchPriority;

if (fetchPriority) {
runtimeRequirements.add(RuntimeGlobals.hasFetchPriority);
}

const requireChunkId = chunk =>
`${RuntimeGlobals.ensureChunk}(${JSON.stringify(chunk.id)})`;
`${RuntimeGlobals.ensureChunk}(${JSON.stringify(chunk.id)}${
fetchPriority ? `, ${JSON.stringify(fetchPriority)}` : ""
})`;
return `Promise.all(${comment.trim()}[${chunks
.map(requireChunkId)
.join(", ")}])`;
Expand Down
1 change: 1 addition & 0 deletions lib/config/defaults.js
Expand Up @@ -533,6 +533,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
19 changes: 19 additions & 0 deletions lib/dependencies/ImportMetaContextDependencyParserPlugin.js
Expand Up @@ -219,6 +219,25 @@ module.exports = class ImportMetaContextDependencyParserPlugin {
}
break;
}
case "fetchPriority": {
const expr = parser.evaluateExpression(
/** @type {Expression} */ (prop.value)
);
if (
expr.isString() &&
["high", "low", "auto"].includes(expr.string)
) {
groupOptions.fetchPriority =
/** @type {RawChunkGroupOptions["fetchPriority"]} */ (
expr.string
);
} else {
errors.push(
createPropertyParseError(prop, '"high"|"low"|"auto"')
);
}
break;
}
case "recursive": {
const recursiveExpr = parser.evaluateExpression(
/** @type {Expression} */ (prop.value)
Expand Down
26 changes: 25 additions & 1 deletion lib/dependencies/ImportParserPlugin.js
Expand Up @@ -47,7 +47,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 @@ -57,6 +61,11 @@ class ImportParserPlugin {
)
groupOptions.prefetchOrder =
dynamicImportPrefetch === true ? 0 : dynamicImportPrefetch;
if (
dynamicImportFetchPriority !== undefined &&
dynamicImportFetchPriority !== false
)
groupOptions.fetchPriority = dynamicImportFetchPriority;

const { options: importOptions, errors: commentErrors } =
parser.parseCommentOptions(expr.range);
Expand Down Expand Up @@ -141,6 +150,21 @@ class ImportParserPlugin {
);
}
}
if (importOptions.webpackFetchPriority !== undefined) {
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
4 changes: 3 additions & 1 deletion lib/prefetch/ChunkPrefetchPreloadPlugin.js
Expand Up @@ -11,6 +11,8 @@ const ChunkPrefetchStartupRuntimeModule = require("./ChunkPrefetchStartupRuntime
const ChunkPrefetchTriggerRuntimeModule = require("./ChunkPrefetchTriggerRuntimeModule");
const ChunkPreloadTriggerRuntimeModule = require("./ChunkPreloadTriggerRuntimeModule");

/** @typedef {import("../Chunk")} Chunk */
/** @typedef {import("../ChunkGroup").RawChunkGroupOptions} RawChunkGroupOptions */
/** @typedef {import("../Compiler")} Compiler */

class ChunkPrefetchPreloadPlugin {
Expand Down Expand Up @@ -43,7 +45,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
12 changes: 10 additions & 2 deletions lib/runtime/EnsureChunkRuntimeModule.js
Expand Up @@ -24,17 +24,25 @@ class EnsureChunkRuntimeModule extends RuntimeModule {
const { runtimeTemplate } = this.compilation;
// Check if there are non initial chunks which need to be imported using require-ensure
if (this.runtimeRequirements.has(RuntimeGlobals.ensureChunkHandlers)) {
const withFetchPriority = this.runtimeRequirements.has(
RuntimeGlobals.hasFetchPriority
);
const handlers = RuntimeGlobals.ensureChunkHandlers;
return Template.asString([
`${handlers} = {};`,
"// This file contains only the entry chunk.",
"// The chunk loading function for additional chunks",
`${RuntimeGlobals.ensureChunk} = ${runtimeTemplate.basicFunction(
"chunkId",
`chunkId${withFetchPriority ? ", fetchPriority" : ""}`,
[
`return Promise.all(Object.keys(${handlers}).reduce(${runtimeTemplate.basicFunction(
"promises, key",
[`${handlers}[key](chunkId, promises);`, "return promises;"]
[
`${handlers}[key](chunkId, promises${
withFetchPriority ? ", fetchPriority" : ""
});`,
"return promises;"
]
)}, []));`
]
)};`
Expand Down
108 changes: 62 additions & 46 deletions lib/runtime/LoadScriptRuntimeModule.js
Expand Up @@ -44,10 +44,12 @@ class LoadScriptRuntimeModule extends HelperRuntimeModule {

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

/**
Expand Down Expand Up @@ -81,6 +83,15 @@ class LoadScriptRuntimeModule extends HelperRuntimeModule {
uniqueName
? 'script.setAttribute("data-webpack", dataWebpackPrefix + key);'
: "",
this._withFetchPriority
? 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._withFetchPriority ? ", 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
11 changes: 9 additions & 2 deletions lib/web/JsonpChunkLoadingRuntimeModule.js
Expand Up @@ -108,6 +108,9 @@ class JsonpChunkLoadingRuntimeModule extends RuntimeModule {
const withPreload = this._runtimeRequirements.has(
RuntimeGlobals.preloadChunkHandlers
);
const withFetchPriority = this._runtimeRequirements.has(
RuntimeGlobals.hasFetchPriority
);
const chunkLoadingGlobalExpr = `${globalObject}[${JSON.stringify(
chunkLoadingGlobal
)}]`;
Expand Down Expand Up @@ -138,7 +141,7 @@ class JsonpChunkLoadingRuntimeModule extends RuntimeModule {
withLoading
? Template.asString([
`${fn}.j = ${runtimeTemplate.basicFunction(
"chunkId, promises",
`chunkId, promises${withFetchPriority ? ", fetchPriority" : ""}`,
hasJsMatcher !== false
? Template.indent([
"// JSONP chunk loading for javascript",
Expand Down Expand Up @@ -190,7 +193,11 @@ class JsonpChunkLoadingRuntimeModule extends RuntimeModule {
"}"
]
)};`,
`${RuntimeGlobals.loadScript}(url, loadingEnded, "chunk-" + chunkId, chunkId);`
`${
RuntimeGlobals.loadScript
}(url, loadingEnded, "chunk-" + chunkId, chunkId${
withFetchPriority ? ", fetchPriority" : ""
});`
]),
hasJsMatcher === true
? "}"
Expand Down