feat: support prefetch/preload for module chunk format

This commit is contained in:
Alexander Akait 2024-03-25 17:10:05 +03:00 committed by GitHub
commit e660ad8b75
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
31 changed files with 397 additions and 81 deletions

View File

@ -2292,6 +2292,10 @@ export interface Environment {
* The environment supports destructuring ('{ a, b } = obj').
*/
destructuring?: boolean;
/**
* The environment supports 'document'.
*/
document?: boolean;
/**
* The environment supports an async import() function to import EcmaScript modules.
*/

View File

@ -1142,6 +1142,11 @@ const applyOutputDefaults = (
output.module
)
);
F(
environment,
"document",
() => tp && optimistic(/** @type {boolean | undefined} */ (tp.document))
);
const { trustedTypes } = output;
if (trustedTypes) {

View File

@ -505,19 +505,17 @@ class CssModulesPlugin {
}
if (hasFailed) {
// There is a not resolve-able conflict with the selectedModule
if (compilation) {
// TODO print better warning
compilation.warnings.push(
new WebpackError(
`chunk ${chunk.name || chunk.id}\nConflicting order between ${
/** @type {Module} */
(hasFailed).readableIdentifier(compilation.requestShortener)
} and ${selectedModule.readableIdentifier(
compilation.requestShortener
)}`
)
);
}
// TODO print better warning
compilation.warnings.push(
new WebpackError(
`chunk ${chunk.name || chunk.id}\nConflicting order between ${
/** @type {Module} */
(hasFailed).readableIdentifier(compilation.requestShortener)
} and ${selectedModule.readableIdentifier(
compilation.requestShortener
)}`
)
);
selectedModule = /** @type {Module} */ (hasFailed);
}
// Insert the selected module into the final modules list

View File

@ -89,7 +89,7 @@ class ModuleChunkLoadingRuntimeModule extends RuntimeModule {
const chunk = /** @type {Chunk} */ (this.chunk);
const {
runtimeTemplate,
outputOptions: { importFunctionName }
outputOptions: { environment, importFunctionName, crossOriginLoading }
} = compilation;
const fn = RuntimeGlobals.ensureChunkHandlers;
const withBaseURI = this._runtimeRequirements.has(RuntimeGlobals.baseURI);
@ -105,6 +105,14 @@ class ModuleChunkLoadingRuntimeModule extends RuntimeModule {
const withHmr = this._runtimeRequirements.has(
RuntimeGlobals.hmrDownloadUpdateHandlers
);
const { linkPreload, linkPrefetch } =
ModuleChunkLoadingRuntimeModule.getCompilationHooks(compilation);
const withPrefetch =
environment.document &&
this._runtimeRequirements.has(RuntimeGlobals.prefetchChunkHandlers);
const withPreload =
environment.document &&
this._runtimeRequirements.has(RuntimeGlobals.preloadChunkHandlers);
const conditionMap = chunkGraph.getChunkConditionMap(chunk, chunkHasJs);
const hasJsMatcher = compileBooleanMatcher(conditionMap);
const initialChunkIds = getInitialChunkIds(chunk, chunkGraph, chunkHasJs);
@ -229,6 +237,86 @@ class ModuleChunkLoadingRuntimeModule extends RuntimeModule {
])
: "// no chunk on demand loading",
"",
withPrefetch && hasJsMatcher !== false
? `${
RuntimeGlobals.prefetchChunkHandlers
}.j = ${runtimeTemplate.basicFunction("chunkId", [
`if((!${
RuntimeGlobals.hasOwnProperty
}(installedChunks, chunkId) || installedChunks[chunkId] === undefined) && ${
hasJsMatcher === true ? "true" : hasJsMatcher("chunkId")
}) {`,
Template.indent([
"installedChunks[chunkId] = null;",
linkPrefetch.call(
Template.asString([
"var link = document.createElement('link');",
crossOriginLoading
? `link.crossOrigin = ${JSON.stringify(
crossOriginLoading
)};`
: "",
`if (${RuntimeGlobals.scriptNonce}) {`,
Template.indent(
`link.setAttribute("nonce", ${RuntimeGlobals.scriptNonce});`
),
"}",
'link.rel = "prefetch";',
'link.as = "script";',
`link.href = ${RuntimeGlobals.publicPath} + ${RuntimeGlobals.getChunkScriptFilename}(chunkId);`
]),
chunk
),
"document.head.appendChild(link);"
]),
"}"
])};`
: "// no prefetching",
"",
withPreload && hasJsMatcher !== false
? `${
RuntimeGlobals.preloadChunkHandlers
}.j = ${runtimeTemplate.basicFunction("chunkId", [
`if((!${
RuntimeGlobals.hasOwnProperty
}(installedChunks, chunkId) || installedChunks[chunkId] === undefined) && ${
hasJsMatcher === true ? "true" : hasJsMatcher("chunkId")
}) {`,
Template.indent([
"installedChunks[chunkId] = null;",
linkPreload.call(
Template.asString([
"var link = document.createElement('link');",
"link.charset = 'utf-8';",
`if (${RuntimeGlobals.scriptNonce}) {`,
Template.indent(
`link.setAttribute("nonce", ${RuntimeGlobals.scriptNonce});`
),
"}",
'link.rel = "modulepreload";',
`link.href = ${RuntimeGlobals.publicPath} + ${RuntimeGlobals.getChunkScriptFilename}(chunkId);`,
crossOriginLoading
? crossOriginLoading === "use-credentials"
? 'link.crossOrigin = "use-credentials";'
: Template.asString([
"if (link.href.indexOf(window.location.origin + '/') !== 0) {",
Template.indent(
`link.crossOrigin = ${JSON.stringify(
crossOriginLoading
)};`
),
"}"
])
: ""
]),
chunk
),
"document.head.appendChild(link);"
]),
"}"
])};`
: "// no preloaded",
"",
withExternalInstallChunk
? Template.asString([
`${RuntimeGlobals.externalInstallChunk} = installChunk;`

File diff suppressed because one or more lines are too long

View File

@ -865,6 +865,10 @@
"description": "The environment supports destructuring ('{ a, b } = obj').",
"type": "boolean"
},
"document": {
"description": "The environment supports 'document'.",
"type": "boolean"
},
"dynamicImport": {
"description": "The environment supports an async import() function to import EcmaScript modules.",
"type": "boolean"

View File

@ -123,6 +123,7 @@ describe("snapshots", () => {
"bigIntLiteral": undefined,
"const": true,
"destructuring": true,
"document": true,
"dynamicImport": undefined,
"dynamicImportInWorker": undefined,
"forOf": true,
@ -347,6 +348,7 @@ describe("snapshots", () => {
"bigIntLiteral": undefined,
"const": true,
"destructuring": true,
"document": true,
"dynamicImport": undefined,
"dynamicImportInWorker": undefined,
"forOf": true,
@ -1310,6 +1312,9 @@ describe("snapshots", () => {
- "web": true,
+ "web": false,
@@ ... @@
- "document": true,
+ "document": false,
@@ ... @@
- "target": "web",
+ "target": "node",
@@ ... @@
@ -1336,6 +1341,9 @@ describe("snapshots", () => {
- "fetch",
+ "async-node",
@@ ... @@
- "document": true,
+ "document": false,
@@ ... @@
- "globalObject": "self",
+ "globalObject": "global",
@@ ... @@
@ -1426,12 +1434,18 @@ describe("snapshots", () => {
- Expected
+ Received
@@ ... @@
- "document": true,
+ "document": false,
@@ ... @@
- "chunkLoading": "jsonp",
+ "chunkLoading": "import-scripts",
@@ ... @@
- "jsonp",
@@ ... @@
- "document": true,
+ "document": false,
@@ ... @@
+ "worker",
@@ ... @@
- "target": "web",
@ -1455,6 +1469,9 @@ describe("snapshots", () => {
- "web": true,
+ "web": false,
@@ ... @@
- "document": true,
+ "document": false,
@@ ... @@
- "target": "web",
+ "target": "electron-main",
@@ ... @@
@ -1481,6 +1498,9 @@ describe("snapshots", () => {
- "fetch",
+ "async-node",
@@ ... @@
- "document": true,
+ "document": false,
@@ ... @@
- "globalObject": "self",
+ "globalObject": "global",
@@ ... @@
@ -1582,6 +1602,9 @@ describe("snapshots", () => {
- "node": false,
+ "node": true,
@@ ... @@
- "document": true,
+ "document": false,
@@ ... @@
- "target": "web",
+ "target": "electron-preload",
@@ ... @@
@ -1608,6 +1631,9 @@ describe("snapshots", () => {
- "fetch",
+ "async-node",
@@ ... @@
- "document": true,
+ "document": false,
@@ ... @@
- "globalObject": "self",
+ "globalObject": "global",
@@ ... @@
@ -2035,6 +2061,12 @@ describe("snapshots", () => {
- "bigIntLiteral": undefined,
- "const": true,
- "destructuring": true,
+ "arrowFunction": false,
+ "asyncFunction": false,
+ "bigIntLiteral": false,
+ "const": false,
+ "destructuring": false,
@@ ... @@
- "dynamicImport": undefined,
- "dynamicImportInWorker": undefined,
- "forOf": true,
@ -2042,11 +2074,6 @@ describe("snapshots", () => {
- "module": undefined,
- "optionalChaining": true,
- "templateLiteral": true,
+ "arrowFunction": false,
+ "asyncFunction": false,
+ "bigIntLiteral": false,
+ "const": false,
+ "destructuring": false,
+ "dynamicImport": false,
+ "dynamicImportInWorker": false,
+ "forOf": false,
@ -2066,6 +2093,12 @@ describe("snapshots", () => {
- "bigIntLiteral": undefined,
- "const": true,
- "destructuring": true,
+ "arrowFunction": false,
+ "asyncFunction": false,
+ "bigIntLiteral": false,
+ "const": false,
+ "destructuring": false,
@@ ... @@
- "dynamicImport": undefined,
- "dynamicImportInWorker": undefined,
- "forOf": true,
@ -2073,11 +2106,6 @@ describe("snapshots", () => {
- "module": undefined,
- "optionalChaining": true,
- "templateLiteral": true,
+ "arrowFunction": false,
+ "asyncFunction": false,
+ "bigIntLiteral": false,
+ "const": false,
+ "destructuring": false,
+ "dynamicImport": false,
+ "dynamicImportInWorker": false,
+ "forOf": false,

View File

@ -6283,6 +6283,19 @@ Object {
"multiple": false,
"simpleType": "boolean",
},
"output-environment-document": Object {
"configs": Array [
Object {
"description": "The environment supports 'document'.",
"multiple": false,
"path": "output.environment.document",
"type": "boolean",
},
],
"description": "The environment supports 'document'.",
"multiple": false,
"simpleType": "boolean",
},
"output-environment-dynamic-import": Object {
"configs": Array [
Object {

View File

@ -5,10 +5,17 @@ __webpack_public_path__ = "https://example.com/public/path/";
it("should prefetch and preload child chunks on chunk load", () => {
let link, script;
expect(document.head._children).toHaveLength(1);
expect(document.head._children).toHaveLength(2);
// Test preload
link = document.head._children[0];
expect(link._type).toBe("link");
expect(link.rel).toBe("prefetch");
expect(link.as).toBe("script");
expect(link.href).toBe("https://example.com/public/path/chunk1.mjs");
// Test prefetch
link = document.head._children[0];
link = document.head._children[1];
expect(link._type).toBe("link");
expect(link.rel).toBe("prefetch");
expect(link.as).toBe("style");
@ -18,37 +25,54 @@ it("should prefetch and preload child chunks on chunk load", () => {
/* webpackChunkName: "chunk1", webpackPrefetch: true */ "./chunk1.mjs"
);
expect(document.head._children).toHaveLength(2);
expect(document.head._children).toHaveLength(4);
// Test normal script loading
link = document.head._children[1];
link = document.head._children[2];
expect(link._type).toBe("link");
expect(link.rel).toBe("preload");
expect(link.as).toBe("style");
expect(link.href).toBe("https://example.com/public/path/chunk1-a-css.css");
link = document.head._children[3];
expect(link._type).toBe("link");
expect(link.rel).toBe("modulepreload");
expect(link.href).toBe("https://example.com/public/path/chunk1-b.mjs");
return promise.then(() => {
expect(document.head._children).toHaveLength(2);
expect(document.head._children).toHaveLength(6);
link = document.head._children[4];
expect(link._type).toBe("link");
expect(link.rel).toBe("prefetch");
expect(link.as).toBe("script");
expect(link.href).toBe("https://example.com/public/path/chunk1-c.mjs");
link = document.head._children[5];
expect(link._type).toBe("link");
expect(link.rel).toBe("prefetch");
expect(link.as).toBe("script");
expect(link.href).toBe("https://example.com/public/path/chunk1-a.mjs");
const promise2 = import(
/* webpackChunkName: "chunk1", webpackPrefetch: true */ "./chunk1.mjs"
);
// Loading chunk1 again should not trigger prefetch/preload
expect(document.head._children).toHaveLength(2);
expect(document.head._children).toHaveLength(6);
const promise3 = import(/* webpackChunkName: "chunk2" */ "./chunk2.mjs");
expect(document.head._children).toHaveLength(2);
expect(document.head._children).toHaveLength(6);
return promise3.then(() => {
expect(document.head._children).toHaveLength(2);
expect(document.head._children).toHaveLength(6);
const promise4 = import(/* webpackChunkName: "chunk1-css" */ "./chunk1.css");
expect(document.head._children).toHaveLength(3);
expect(document.head._children).toHaveLength(7);
link = document.head._children[2];
link = document.head._children[6];
expect(link._type).toBe("link");
expect(link.rel).toBe("stylesheet");
expect(link.href).toBe("https://example.com/public/path/chunk1-css.css");
@ -56,9 +80,9 @@ it("should prefetch and preload child chunks on chunk load", () => {
const promise5 = import(/* webpackChunkName: "chunk2-css", webpackPrefetch: true */ "./chunk2.css");
expect(document.head._children).toHaveLength(4);
expect(document.head._children).toHaveLength(8);
link = document.head._children[3];
link = document.head._children[7];
expect(link._type).toBe("link");
expect(link.rel).toBe("stylesheet");
expect(link.href).toBe("https://example.com/public/path/chunk2-css.css");

View File

@ -13,6 +13,7 @@ module.exports = {
"bigIntLiteral": false,
"const": false,
"destructuring": false,
"document": true,
"dynamicImport": false,
"dynamicImportInWorker": false,
"forOf": false,

View File

@ -13,6 +13,7 @@ module.exports = {
"bigIntLiteral": false,
"const": false,
"destructuring": false,
"document": true,
"dynamicImport": false,
"dynamicImportInWorker": false,
"forOf": false,

View File

@ -11,6 +11,7 @@ module.exports = {
"bigIntLiteral": false,
"const": false,
"destructuring": false,
"document": true,
"dynamicImport": false,
"dynamicImportInWorker": false,
"forOf": false,

View File

@ -11,6 +11,7 @@ module.exports = {
"bigIntLiteral": true,
"const": true,
"destructuring": true,
"document": false,
"dynamicImport": true,
"dynamicImportInWorker": false,
"forOf": true,

View File

@ -0,0 +1,3 @@
a {
color: red;
}

View File

@ -0,0 +1,3 @@
a {
color: red;
}

View File

@ -0,0 +1,3 @@
a {
color: blue;
}

View File

@ -0,0 +1,125 @@
// This config need to be set on initial evaluation to be effective
__webpack_nonce__ = "nonce";
__webpack_public_path__ = "https://example.com/public/path/";
it("should prefetch and preload child chunks on chunk load", () => {
let link, script;
expect(document.head._children).toHaveLength(2);
// Test prefetch from entry chunk
link = document.head._children[0];
expect(link._type).toBe("link");
expect(link.rel).toBe("prefetch");
expect(link.as).toBe("script");
expect(link.href).toBe("https://example.com/public/path/chunk1.js");
link = document.head._children[1];
expect(link._type).toBe("link");
expect(link.rel).toBe("prefetch");
expect(link.as).toBe("style");
expect(link.href).toBe("https://example.com/public/path/chunk2-css.css");
const promise = import(
/* webpackChunkName: "chunk1", webpackPrefetch: true */ "./chunk1.js"
);
expect(document.head._children).toHaveLength(5);
// Test normal script loading
script = document.head._children[2];
expect(script._type).toBe("script");
expect(script.src).toBe("https://example.com/public/path/chunk1.js");
expect(script.getAttribute("nonce")).toBe("nonce");
expect(script.crossOrigin).toBe("anonymous");
expect(script.onload).toBeTypeOf("function");
// Test preload of chunk1-b
link = document.head._children[3];
expect(link._type).toBe("link");
expect(link.rel).toBe("modulepreload");
expect(link.href).toBe("https://example.com/public/path/chunk1-b.js");
expect(link.charset).toBe("utf-8");
expect(link.getAttribute("nonce")).toBe("nonce");
expect(link.crossOrigin).toBe("anonymous");
// Test preload of chunk1-a-css
link = document.head._children[4];
expect(link._type).toBe("link");
expect(link.rel).toBe("preload");
expect(link.as).toBe("style");
expect(link.href).toBe("https://example.com/public/path/chunk1-a-css.css");
// Run the script
import(/* webpackIgnore: true */ "./chunk1.js");
script.onload();
return promise.then(() => {
expect(document.head._children).toHaveLength(6);
// Test prefetching for chunk1-c and chunk1-a in this order
link = document.head._children[4];
expect(link._type).toBe("link");
expect(link.rel).toBe("prefetch");
expect(link.as).toBe("script");
expect(link.href).toBe("https://example.com/public/path/chunk1-c.js");
expect(link.crossOrigin).toBe("anonymous");
link = document.head._children[5];
expect(link._type).toBe("link");
expect(link.rel).toBe("prefetch");
expect(link.as).toBe("script");
expect(link.href).toBe("https://example.com/public/path/chunk1-a.js");
expect(link.crossOrigin).toBe("anonymous");
const promise2 = import(
/* webpackChunkName: "chunk1", webpackPrefetch: true */ "./chunk1.js"
);
// Loading chunk1 again should not trigger prefetch/preload
expect(document.head._children).toHaveLength(6);
const promise3 = import(/* webpackChunkName: "chunk2" */ "./chunk2.js");
expect(document.head._children).toHaveLength(7);
// Test normal script loading
script = document.head._children[6];
expect(script._type).toBe("script");
expect(script.src).toBe("https://example.com/public/path/chunk2.js");
expect(script.getAttribute("nonce")).toBe("nonce");
expect(script.crossOrigin).toBe("anonymous");
expect(script.onload).toBeTypeOf("function");
// Run the script
import(/* webpackIgnore: true */ "./chunk2.js");
script.onload();
return promise3.then(() => {
// Loading chunk2 again should not trigger prefetch/preload as it's already prefetch/preloaded
expect(document.head._children).toHaveLength(6);
const promise4 = import(/* webpackChunkName: "chunk1-css" */ "./chunk1.css");
expect(document.head._children).toHaveLength(7);
link = document.head._children[6];
expect(link._type).toBe("link");
expect(link.rel).toBe("stylesheet");
expect(link.href).toBe("https://example.com/public/path/chunk1-css.css");
expect(link.crossOrigin).toBe("anonymous");
const promise5 = import(/* webpackChunkName: "chunk2-css", webpackPrefetch: true */ "./chunk2.css");
expect(document.head._children).toHaveLength(8);
link = document.head._children[7];
expect(link._type).toBe("link");
expect(link.rel).toBe("stylesheet");
expect(link.href).toBe("https://example.com/public/path/chunk2-css.css");
expect(link.crossOrigin).toBe("anonymous");
});
});
});

View File

@ -0,0 +1,23 @@
/** @type {import("../../../../").Configuration} */
module.exports = {
entry: "./index.mjs",
experiments: {
outputModule: true,
css: true
},
name: "esm",
target: "web",
output: {
publicPath: "",
module: true,
filename: "bundle0.mjs",
chunkFilename: "[name].js",
crossOriginLoading: "anonymous"
},
performance: {
hints: false
},
optimization: {
minimize: false
}
};

View File

@ -0,0 +1,6 @@
export default function() {
import(/* webpackPrefetch: true, webpackChunkName: "chunk1-a" */ "./chunk1-a.mjs");
import(/* webpackPreload: true, webpackChunkName: "chunk1-b" */ "./chunk1-b.mjs");
import(/* webpackPreload: true, webpackChunkName: "chunk1-a-css" */ "./chunk1-a.css");
import(/* webpackPrefetch: 10, webpackChunkName: "chunk1-c" */ "./chunk1-c.mjs");
}

View File

@ -0,0 +1,4 @@
export default function() {
import(/* webpackPrefetch: true, webpackChunkName: "chunk1-a" */ "./chunk1-a.mjs");
import(/* webpackPreload: true, webpackChunkName: "chunk1-b" */ "./chunk1-b.mjs");
}

View File

@ -3,7 +3,7 @@ __webpack_nonce__ = "nonce";
__webpack_public_path__ = "https://example.com/public/path/";
it("should prefetch and preload child chunks on chunk load", () => {
let link, script;
let link;
expect(document.head._children).toHaveLength(2);
@ -12,7 +12,7 @@ it("should prefetch and preload child chunks on chunk load", () => {
expect(link._type).toBe("link");
expect(link.rel).toBe("prefetch");
expect(link.as).toBe("script");
expect(link.href).toBe("https://example.com/public/path/chunk1.js");
expect(link.href).toBe("https://example.com/public/path/chunk1.mjs");
link = document.head._children[1];
expect(link._type).toBe("link");
@ -21,40 +21,29 @@ it("should prefetch and preload child chunks on chunk load", () => {
expect(link.href).toBe("https://example.com/public/path/chunk2-css.css");
const promise = import(
/* webpackChunkName: "chunk1", webpackPrefetch: true */ "./chunk1.js"
/* webpackChunkName: "chunk1", webpackPrefetch: true */ "./chunk1.mjs"
);
expect(document.head._children).toHaveLength(5);
expect(document.head._children).toHaveLength(4);
// Test normal script loading
script = document.head._children[2];
expect(script._type).toBe("script");
expect(script.src).toBe("https://example.com/public/path/chunk1.js");
expect(script.getAttribute("nonce")).toBe("nonce");
expect(script.crossOrigin).toBe("anonymous");
expect(script.onload).toBeTypeOf("function");
link = document.head._children[2];
expect(link._type).toBe("link");
expect(link.rel).toBe("preload");
expect(link.as).toBe("style");
expect(link.href).toBe("https://example.com/public/path/chunk1-a-css.css");
expect(link.getAttribute("nonce")).toBe("nonce");
expect(link.crossOrigin).toBe("anonymous");
// Test preload of chunk1-b
link = document.head._children[3];
expect(link._type).toBe("link");
expect(link.rel).toBe("modulepreload");
expect(link.href).toBe("https://example.com/public/path/chunk1-b.js");
expect(link.href).toBe("https://example.com/public/path/chunk1-b.mjs");
expect(link.charset).toBe("utf-8");
expect(link.getAttribute("nonce")).toBe("nonce");
expect(link.crossOrigin).toBe("anonymous");
// Test preload of chunk1-a-css
link = document.head._children[4];
expect(link._type).toBe("link");
expect(link.rel).toBe("preload");
expect(link.as).toBe("style");
expect(link.href).toBe("https://example.com/public/path/chunk1-a-css.css");
// Run the script
import(/* webpackIgnore: true */ "./chunk1.js");
script.onload();
return promise.then(() => {
expect(document.head._children).toHaveLength(6);
@ -63,39 +52,25 @@ it("should prefetch and preload child chunks on chunk load", () => {
expect(link._type).toBe("link");
expect(link.rel).toBe("prefetch");
expect(link.as).toBe("script");
expect(link.href).toBe("https://example.com/public/path/chunk1-c.js");
expect(link.href).toBe("https://example.com/public/path/chunk1-c.mjs");
expect(link.crossOrigin).toBe("anonymous");
link = document.head._children[5];
expect(link._type).toBe("link");
expect(link.rel).toBe("prefetch");
expect(link.as).toBe("script");
expect(link.href).toBe("https://example.com/public/path/chunk1-a.js");
expect(link.href).toBe("https://example.com/public/path/chunk1-a.mjs");
expect(link.getAttribute("nonce")).toBe("nonce");
expect(link.crossOrigin).toBe("anonymous");
const promise2 = import(
/* webpackChunkName: "chunk1", webpackPrefetch: true */ "./chunk1.js"
/* webpackChunkName: "chunk1", webpackPrefetch: true */ "./chunk1.mjs"
);
// Loading chunk1 again should not trigger prefetch/preload
expect(document.head._children).toHaveLength(6);
const promise3 = import(/* webpackChunkName: "chunk2" */ "./chunk2.js");
const promise3 = import(/* webpackChunkName: "chunk2" */ "./chunk2.mjs");
expect(document.head._children).toHaveLength(7);
// Test normal script loading
script = document.head._children[6];
expect(script._type).toBe("script");
expect(script.src).toBe("https://example.com/public/path/chunk2.js");
expect(script.getAttribute("nonce")).toBe("nonce");
expect(script.crossOrigin).toBe("anonymous");
expect(script.onload).toBeTypeOf("function");
// Run the script
import(/* webpackIgnore: true */ "./chunk2.js");
script.onload();
expect(document.head._children).toHaveLength(6);
return promise3.then(() => {
// Loading chunk2 again should not trigger prefetch/preload as it's already prefetch/preloaded

View File

@ -11,7 +11,8 @@ module.exports = {
publicPath: "",
module: true,
filename: "bundle0.mjs",
chunkFilename: "[name].js",
chunkFilename: "[name].mjs",
chunkFormat: "module",
crossOriginLoading: "anonymous"
},
performance: {

5
types.d.ts vendored
View File

@ -3918,6 +3918,11 @@ declare interface Environment {
*/
destructuring?: boolean;
/**
* The environment supports 'document'.
*/
document?: boolean;
/**
* The environment supports an async import() function to import EcmaScript modules.
*/