Skip to content

Commit

Permalink
Merge pull request #17143 from webpack/issue-16838
Browse files Browse the repository at this point in the history
fix: use rel modulepreload for esm modules
  • Loading branch information
TheLarkInn committed May 8, 2023
2 parents 7fc5abf + 8dbe8ed commit 513842c
Show file tree
Hide file tree
Showing 9 changed files with 215 additions and 3 deletions.
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
__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
}
};

0 comments on commit 513842c

Please sign in to comment.