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

fix: use rel modulepreload for esm modules #17143

Merged
merged 2 commits into from May 8, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
8 changes: 5 additions & 3 deletions lib/web/JsonpChunkLoadingRuntimeModule.js
Expand Up @@ -250,7 +250,7 @@ class JsonpChunkLoadingRuntimeModule extends RuntimeModule {
linkPreload.call(
Template.asString([
"var link = document.createElement('link');",
scriptType
scriptType && scriptType !== "module"
? `link.type = ${JSON.stringify(scriptType)};`
: "",
"link.charset = 'utf-8';",
Expand All @@ -259,8 +259,10 @@ class JsonpChunkLoadingRuntimeModule extends RuntimeModule {
`link.setAttribute("nonce", ${RuntimeGlobals.scriptNonce});`
),
"}",
'link.rel = "preload";',
'link.as = "script";',
scriptType === "module"
? 'link.rel = "modulepreload";'
: 'link.rel = "preload";',
scriptType === "module" ? "" : 'link.as = "script";',
`link.href = ${RuntimeGlobals.publicPath} + ${RuntimeGlobals.getChunkScriptFilename}(chunkId);`,
crossOriginLoading
? crossOriginLoading === "use-credentials"
Expand Down
Empty file.
Empty file.
Empty file.
5 changes: 5 additions & 0 deletions test/configCases/web/prefetch-preload-module/chunk1.js
@@ -0,0 +1,5 @@
export default function() {
import(/* webpackPrefetch: true, webpackChunkName: "chunk1-a" */ "./chunk1-a");
import(/* webpackPreload: true, webpackChunkName: "chunk1-b" */ "./chunk1-b");
import(/* webpackPrefetch: 10, webpackChunkName: "chunk1-c" */ "./chunk1-c");
}
4 changes: 4 additions & 0 deletions test/configCases/web/prefetch-preload-module/chunk2.js
@@ -0,0 +1,4 @@
export default function() {
import(/* webpackPrefetch: true, webpackChunkName: "chunk1-a" */ "./chunk1-a");
import(/* webpackPreload: true, webpackChunkName: "chunk1-b" */ "./chunk1-b");
}
90 changes: 90 additions & 0 deletions test/configCases/web/prefetch-preload-module/index.js
@@ -0,0 +1,90 @@
// This config need to be set on initial evaluation to be effective
Copy link
Member Author

Choose a reason for hiding this comment

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

Just copy/paste test for non ESM prefetch/preload and change rel on valid value, also there is good test for ESM modules (we don't have many them 馃槃 )

__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(1);

// Test prefetch from entry chunk
link = document.head._children[0];
expect(link._type).toBe("link");
expect(link.rel).toBe("prefetch");
expect(link.href).toBe("https://example.com/public/path/chunk1.js");

const promise = import(
/* webpackChunkName: "chunk1", webpackPrefetch: true */ "./chunk1"
);

expect(document.head._children).toHaveLength(3);

// Test normal script loading
script = document.head._children[1];
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[2];
expect(link._type).toBe("link");
expect(link.rel).toBe("preload");
expect(link.as).toBe("script");
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");

// Run the script
__non_webpack_require__("./chunk1.js");

script.onload();

return promise.then(() => {
expect(document.head._children).toHaveLength(4);

// Test prefetching for chunk1-c and chunk1-a in this order
link = document.head._children[2];
expect(link._type).toBe("link");
expect(link.rel).toBe("prefetch");
expect(link.href).toBe("https://example.com/public/path/chunk1-c.js");
expect(link.crossOrigin).toBe("anonymous");

link = document.head._children[3];
expect(link._type).toBe("link");
expect(link.rel).toBe("prefetch");
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"
);

// Loading chunk1 again should not trigger prefetch/preload
expect(document.head._children).toHaveLength(4);

const promise3 = import(/* webpackChunkName: "chunk2" */ "./chunk2");

expect(document.head._children).toHaveLength(5);

// Test normal script loading
script = document.head._children[4];
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
__non_webpack_require__("./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(4);
});
});
});
89 changes: 89 additions & 0 deletions test/configCases/web/prefetch-preload-module/index.mjs
@@ -0,0 +1,89 @@
// 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(1);

// Test prefetch from entry chunk
link = document.head._children[0];
expect(link._type).toBe("link");
expect(link.rel).toBe("prefetch");
expect(link.href).toBe("https://example.com/public/path/chunk1.js");

const promise = import(
/* webpackChunkName: "chunk1", webpackPrefetch: true */ "./chunk1.js"
);

expect(document.head._children).toHaveLength(3);

// Test normal script loading
script = document.head._children[1];
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[2];
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");

// Run the script
__non_webpack_require__("./chunk1.js");

script.onload();

return promise.then(() => {
expect(document.head._children).toHaveLength(4);

// Test prefetching for chunk1-c and chunk1-a in this order
link = document.head._children[2];
expect(link._type).toBe("link");
expect(link.rel).toBe("prefetch");
expect(link.href).toBe("https://example.com/public/path/chunk1-c.js");
expect(link.crossOrigin).toBe("anonymous");

link = document.head._children[3];
expect(link._type).toBe("link");
expect(link.rel).toBe("prefetch");
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(4);

const promise3 = import(/* webpackChunkName: "chunk2" */ "./chunk2.js");

expect(document.head._children).toHaveLength(5);

// Test normal script loading
script = document.head._children[4];
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
__non_webpack_require__("./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(4);
});
});
});
22 changes: 22 additions & 0 deletions test/configCases/web/prefetch-preload-module/webpack.config.js
@@ -0,0 +1,22 @@
/** @type {import("../../../../").Configuration} */
module.exports = {
entry: "./index.mjs",
experiments: {
outputModule: true
},
name: "esm",
target: "web",
output: {
publicPath: "",
module: true,
filename: "bundle0.js",
chunkFilename: "[name].js",
crossOriginLoading: "anonymous"
},
performance: {
hints: false
},
optimization: {
minimize: false
}
};