Skip to content
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 25 additions & 4 deletions crates/rspack_plugin_runtime/src/chunk_prefetch_preload.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use rspack_core::{
ChunkGroupOrderKey, ChunkUkey, Compilation, CompilationAdditionalTreeRuntimeRequirements, Plugin,
RuntimeGlobals,
ChunkGroupOrderKey, ChunkUkey, Compilation, CompilationAdditionalChunkRuntimeRequirements,
CompilationAdditionalTreeRuntimeRequirements, Plugin, RuntimeGlobals,
};
use rspack_error::Result;
use rspack_hook::{plugin, plugin_hook};
Expand All @@ -14,14 +14,22 @@ use crate::runtime_module::{
#[derive(Debug, Default)]
pub struct ChunkPrefetchPreloadPlugin;

#[plugin_hook(CompilationAdditionalTreeRuntimeRequirements for ChunkPrefetchPreloadPlugin)]
async fn additional_tree_runtime_requirements(
#[plugin_hook(CompilationAdditionalChunkRuntimeRequirements for ChunkPrefetchPreloadPlugin)]
async fn additional_chunk_runtime_requirements(
&self,
compilation: &mut Compilation,
chunk_ukey: &ChunkUkey,
runtime_requirements: &mut RuntimeGlobals,
) -> Result<()> {
let chunk = compilation.chunk_by_ukey.expect_get(chunk_ukey);
if compilation
.chunk_graph
.get_number_of_entry_modules(chunk_ukey)
== 0
{
return Ok(());
}

if let Some(startup_child_chunks) =
chunk.get_children_of_type_in_order(&ChunkGroupOrderKey::Prefetch, compilation, false)
{
Expand All @@ -33,7 +41,16 @@ async fn additional_tree_runtime_requirements(
Box::new(ChunkPrefetchStartupRuntimeModule::new(startup_child_chunks)),
)?
}
Ok(())
}

#[plugin_hook(CompilationAdditionalTreeRuntimeRequirements for ChunkPrefetchPreloadPlugin)]
async fn additional_tree_runtime_requirements(
&self,
compilation: &mut Compilation,
chunk_ukey: &ChunkUkey,
runtime_requirements: &mut RuntimeGlobals,
) -> Result<()> {
let chunk = compilation.chunk_by_ukey.expect_get(chunk_ukey);
let chunk_filter = |_: &ChunkUkey, __: &Compilation| true;
let mut chunk_map = chunk.get_child_ids_by_orders_map(false, compilation, &chunk_filter);
Expand Down Expand Up @@ -67,6 +84,10 @@ impl Plugin for ChunkPrefetchPreloadPlugin {
.compilation_hooks
.additional_tree_runtime_requirements
.tap(additional_tree_runtime_requirements::new(self));
ctx
.compilation_hooks
.additional_chunk_runtime_requirements
.tap(additional_chunk_runtime_requirements::new(self));
Ok(())
}
}
Original file line number Diff line number Diff line change
@@ -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");
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export default function() {
import(/* webpackPrefetch: true, webpackChunkName: "chunk1-a" */ "./chunk1-a");
import(/* webpackPreload: true, webpackChunkName: "chunk1-b" */ "./chunk1-b");
}
Original file line number Diff line number Diff line change
@@ -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"
);

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.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);
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
const fs = require("fs");
const path = require("path");

/** @type {import("@rspack/core").Configuration} */
module.exports = {
target: "web",
output: {
filename: "[name].js",
chunkFilename: "[name].js",
crossOriginLoading: "anonymous"
},
performance: {
hints: false
},
optimization: {
minimize: false,
runtimeChunk: {
name: entrypoint => `runtime~${entrypoint.name}`,
}
},
plugins: [{
apply(compiler) {
compiler.hooks.done.tap("DonePlugin", () => {
const output = compiler.options.output.path;
const runtime = fs.readFileSync(path.join(output, "runtime~main.js"), "utf-8");
expect(runtime).not.toContain("webpack/runtime/chunk_prefetch_startup");
const main = fs.readFileSync(path.join(output, "main.js"), "utf-8");
expect(main).toContain("webpack/runtime/chunk_prefetch_startup");
});
}
}]
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
module.exports = {
findBundle() {
return ["runtime~main.js", "main.js"];
}
};
Loading