Skip to content

Commit 0b8a3bb

Browse files
authored
fix: should inject prefetch startup in entry chunk instead of runtime chunk (#12233)
1 parent 9542b49 commit 0b8a3bb

File tree

9 files changed

+160
-4
lines changed

9 files changed

+160
-4
lines changed

crates/rspack_plugin_runtime/src/chunk_prefetch_preload.rs

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use rspack_core::{
2-
ChunkGroupOrderKey, ChunkUkey, Compilation, CompilationAdditionalTreeRuntimeRequirements, Plugin,
3-
RuntimeGlobals,
2+
ChunkGroupOrderKey, ChunkUkey, Compilation, CompilationAdditionalChunkRuntimeRequirements,
3+
CompilationAdditionalTreeRuntimeRequirements, Plugin, RuntimeGlobals,
44
};
55
use rspack_error::Result;
66
use rspack_hook::{plugin, plugin_hook};
@@ -14,14 +14,22 @@ use crate::runtime_module::{
1414
#[derive(Debug, Default)]
1515
pub struct ChunkPrefetchPreloadPlugin;
1616

17-
#[plugin_hook(CompilationAdditionalTreeRuntimeRequirements for ChunkPrefetchPreloadPlugin)]
18-
async fn additional_tree_runtime_requirements(
17+
#[plugin_hook(CompilationAdditionalChunkRuntimeRequirements for ChunkPrefetchPreloadPlugin)]
18+
async fn additional_chunk_runtime_requirements(
1919
&self,
2020
compilation: &mut Compilation,
2121
chunk_ukey: &ChunkUkey,
2222
runtime_requirements: &mut RuntimeGlobals,
2323
) -> Result<()> {
2424
let chunk = compilation.chunk_by_ukey.expect_get(chunk_ukey);
25+
if compilation
26+
.chunk_graph
27+
.get_number_of_entry_modules(chunk_ukey)
28+
== 0
29+
{
30+
return Ok(());
31+
}
32+
2533
if let Some(startup_child_chunks) =
2634
chunk.get_children_of_type_in_order(&ChunkGroupOrderKey::Prefetch, compilation, false)
2735
{
@@ -33,7 +41,16 @@ async fn additional_tree_runtime_requirements(
3341
Box::new(ChunkPrefetchStartupRuntimeModule::new(startup_child_chunks)),
3442
)?
3543
}
44+
Ok(())
45+
}
3646

47+
#[plugin_hook(CompilationAdditionalTreeRuntimeRequirements for ChunkPrefetchPreloadPlugin)]
48+
async fn additional_tree_runtime_requirements(
49+
&self,
50+
compilation: &mut Compilation,
51+
chunk_ukey: &ChunkUkey,
52+
runtime_requirements: &mut RuntimeGlobals,
53+
) -> Result<()> {
3754
let chunk = compilation.chunk_by_ukey.expect_get(chunk_ukey);
3855
let chunk_filter = |_: &ChunkUkey, __: &Compilation| true;
3956
let mut chunk_map = chunk.get_child_ids_by_orders_map(false, compilation, &chunk_filter);
@@ -67,6 +84,10 @@ impl Plugin for ChunkPrefetchPreloadPlugin {
6784
.compilation_hooks
6885
.additional_tree_runtime_requirements
6986
.tap(additional_tree_runtime_requirements::new(self));
87+
ctx
88+
.compilation_hooks
89+
.additional_chunk_runtime_requirements
90+
.tap(additional_chunk_runtime_requirements::new(self));
7091
Ok(())
7192
}
7293
}

tests/rspack-test/configCases/web/prefetch-preload-runtime-chunk/chunk1-a.js

Whitespace-only changes.

tests/rspack-test/configCases/web/prefetch-preload-runtime-chunk/chunk1-b.js

Whitespace-only changes.

tests/rspack-test/configCases/web/prefetch-preload-runtime-chunk/chunk1-c.js

Whitespace-only changes.
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
export default function() {
2+
import(/* webpackPrefetch: true, webpackChunkName: "chunk1-a" */ "./chunk1-a");
3+
import(/* webpackPreload: true, webpackChunkName: "chunk1-b" */ "./chunk1-b");
4+
import(/* webpackPrefetch: 10, webpackChunkName: "chunk1-c" */ "./chunk1-c");
5+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
export default function() {
2+
import(/* webpackPrefetch: true, webpackChunkName: "chunk1-a" */ "./chunk1-a");
3+
import(/* webpackPreload: true, webpackChunkName: "chunk1-b" */ "./chunk1-b");
4+
}
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
// This config need to be set on initial evaluation to be effective
2+
__webpack_nonce__ = "nonce";
3+
__webpack_public_path__ = "https://example.com/public/path/";
4+
5+
it("should prefetch and preload child chunks on chunk load", () => {
6+
let link, script;
7+
8+
expect(document.head._children).toHaveLength(1);
9+
10+
// Test prefetch from entry chunk
11+
link = document.head._children[0];
12+
expect(link._type).toBe("link");
13+
expect(link.rel).toBe("prefetch");
14+
expect(link.href).toBe("https://example.com/public/path/chunk1.js");
15+
16+
const promise = import(
17+
/* webpackChunkName: "chunk1", webpackPrefetch: true */ "./chunk1"
18+
);
19+
20+
expect(document.head._children).toHaveLength(3);
21+
22+
// Test normal script loading
23+
script = document.head._children[1];
24+
expect(script._type).toBe("script");
25+
expect(script.src).toBe("https://example.com/public/path/chunk1.js");
26+
expect(script.getAttribute("nonce")).toBe("nonce");
27+
expect(script.crossOrigin).toBe("anonymous");
28+
expect(script.onload).toBeTypeOf("function");
29+
30+
// Test preload of chunk1-b
31+
link = document.head._children[2];
32+
expect(link._type).toBe("link");
33+
expect(link.rel).toBe("preload");
34+
expect(link.as).toBe("script");
35+
expect(link.href).toBe("https://example.com/public/path/chunk1-b.js");
36+
expect(link.getAttribute("nonce")).toBe("nonce");
37+
expect(link.crossOrigin).toBe("anonymous");
38+
39+
// Run the script
40+
__non_webpack_require__("./chunk1.js");
41+
42+
script.onload();
43+
44+
return promise.then(() => {
45+
expect(document.head._children).toHaveLength(4);
46+
47+
// Test prefetching for chunk1-c and chunk1-a in this order
48+
link = document.head._children[2];
49+
expect(link._type).toBe("link");
50+
expect(link.rel).toBe("prefetch");
51+
expect(link.href).toBe("https://example.com/public/path/chunk1-c.js");
52+
expect(link.crossOrigin).toBe("anonymous");
53+
54+
link = document.head._children[3];
55+
expect(link._type).toBe("link");
56+
expect(link.rel).toBe("prefetch");
57+
expect(link.href).toBe("https://example.com/public/path/chunk1-a.js");
58+
expect(link.crossOrigin).toBe("anonymous");
59+
60+
const promise2 = import(
61+
/* webpackChunkName: "chunk1", webpackPrefetch: true */ "./chunk1"
62+
);
63+
64+
// Loading chunk1 again should not trigger prefetch/preload
65+
expect(document.head._children).toHaveLength(4);
66+
67+
const promise3 = import(/* webpackChunkName: "chunk2" */ "./chunk2");
68+
69+
expect(document.head._children).toHaveLength(5);
70+
71+
// Test normal script loading
72+
script = document.head._children[4];
73+
expect(script._type).toBe("script");
74+
expect(script.src).toBe("https://example.com/public/path/chunk2.js");
75+
expect(script.getAttribute("nonce")).toBe("nonce");
76+
expect(script.crossOrigin).toBe("anonymous");
77+
expect(script.onload).toBeTypeOf("function");
78+
79+
// Run the script
80+
__non_webpack_require__("./chunk2.js");
81+
82+
script.onload();
83+
84+
return promise3.then(() => {
85+
// Loading chunk2 again should not trigger prefetch/preload as it's already prefetch/preloaded
86+
expect(document.head._children).toHaveLength(4);
87+
});
88+
});
89+
});
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
const fs = require("fs");
2+
const path = require("path");
3+
4+
/** @type {import("@rspack/core").Configuration} */
5+
module.exports = {
6+
target: "web",
7+
output: {
8+
filename: "[name].js",
9+
chunkFilename: "[name].js",
10+
crossOriginLoading: "anonymous"
11+
},
12+
performance: {
13+
hints: false
14+
},
15+
optimization: {
16+
minimize: false,
17+
runtimeChunk: {
18+
name: entrypoint => `runtime~${entrypoint.name}`,
19+
}
20+
},
21+
plugins: [{
22+
apply(compiler) {
23+
compiler.hooks.done.tap("DonePlugin", () => {
24+
const output = compiler.options.output.path;
25+
const runtime = fs.readFileSync(path.join(output, "runtime~main.js"), "utf-8");
26+
expect(runtime).not.toContain("webpack/runtime/chunk_prefetch_startup");
27+
const main = fs.readFileSync(path.join(output, "main.js"), "utf-8");
28+
expect(main).toContain("webpack/runtime/chunk_prefetch_startup");
29+
});
30+
}
31+
}]
32+
};
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
module.exports = {
2+
findBundle() {
3+
return ["runtime~main.js", "main.js"];
4+
}
5+
};

0 commit comments

Comments
 (0)