Skip to content

Commit

Permalink
feat: support chunksSortMode option to HtmlRspackPlugin (#8585)
Browse files Browse the repository at this point in the history
* feat: support chunksSortMode option to HtmlRspackPlugin

* chore: improve code
  • Loading branch information
inottn authored Dec 2, 2024
1 parent ee8979a commit 9c46272
Show file tree
Hide file tree
Showing 10 changed files with 170 additions and 94 deletions.
1 change: 1 addition & 0 deletions crates/node_binding/binding.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1469,6 +1469,7 @@ export interface RawHtmlRspackPluginOptions {
/** entry_chunk_name (only entry chunks are supported) */
chunks?: Array<string>
excludeChunks?: Array<string>
chunksSortMode: "auto" | "manual"
sri?: "sha256" | "sha384" | "sha512"
minify?: boolean
title?: string
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use std::str::FromStr;
use napi::bindgen_prelude::Either3;
use napi_derive::napi;
use rspack_napi::threadsafe_function::ThreadsafeFunction;
use rspack_plugin_html::config::HtmlChunkSortMode;
use rspack_plugin_html::config::HtmlInject;
use rspack_plugin_html::config::HtmlRspackPluginBaseOptions;
use rspack_plugin_html::config::HtmlRspackPluginOptions;
Expand All @@ -17,6 +18,7 @@ pub type RawHtmlScriptLoading = String;
pub type RawHtmlInject = String;
pub type RawHtmlSriHashFunction = String;
pub type RawHtmlFilename = Vec<String>;
type RawChunkSortMode = String;

type RawTemplateRenderFn = ThreadsafeFunction<String, String>;

Expand Down Expand Up @@ -48,6 +50,9 @@ pub struct RawHtmlRspackPluginOptions {
/// entry_chunk_name (only entry chunks are supported)
pub chunks: Option<Vec<String>>,
pub exclude_chunks: Option<Vec<String>>,
#[napi(ts_type = "\"auto\" | \"manual\"")]
pub chunks_sort_mode: RawChunkSortMode,

#[napi(ts_type = "\"sha256\" | \"sha384\" | \"sha512\"")]
pub sri: Option<RawHtmlSriHashFunction>,
pub minify: Option<bool>,
Expand All @@ -65,6 +70,9 @@ impl From<RawHtmlRspackPluginOptions> for HtmlRspackPluginOptions {
let script_loading =
HtmlScriptLoading::from_str(&value.script_loading).expect("Invalid script_loading value");

let chunks_sort_mode =
HtmlChunkSortMode::from_str(&value.chunks_sort_mode).expect("Invalid chunks_sort_mode value");

let sri = value.sri.as_ref().map(|s| {
HtmlSriHashFunction::from_str(s).unwrap_or_else(|_| panic!("Invalid sri value: {s}"))
});
Expand Down Expand Up @@ -105,6 +113,7 @@ impl From<RawHtmlRspackPluginOptions> for HtmlRspackPluginOptions {
script_loading,
chunks: value.chunks,
exclude_chunks: value.exclude_chunks,
chunks_sort_mode,
sri,
minify: value.minify,
title: value.title,
Expand Down
42 changes: 28 additions & 14 deletions crates/rspack_plugin_html/src/asset.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ use serde::{Deserialize, Serialize};
use sugar_path::SugarPath;

use crate::{
config::{HtmlInject, HtmlRspackPluginOptions, HtmlScriptLoading},
config::{HtmlChunkSortMode, HtmlInject, HtmlRspackPluginOptions, HtmlScriptLoading},
sri::{add_sri, create_digest_from_asset},
tag::HtmlPluginTag,
};
Expand All @@ -49,19 +49,33 @@ impl HtmlPluginAssets {
let mut asset_map = HashMap::new();
assets.public_path = public_path.to_string();

let included_assets = compilation
.entrypoints
.keys()
.filter(|&entry_name| {
let mut included = true;
if let Some(included_chunks) = &config.chunks {
included = included_chunks.iter().any(|c| c.eq(entry_name));
}
if let Some(exclude_chunks) = &config.exclude_chunks {
included = included && !exclude_chunks.iter().any(|c| c.eq(entry_name));
}
included
})
let sorted_entry_names: Vec<&String> =
if matches!(config.chunks_sort_mode, HtmlChunkSortMode::Manual)
&& let Some(chunks) = &config.chunks
{
chunks
.iter()
.filter(|&name| compilation.entrypoints.contains_key(name))
.collect()
} else {
compilation
.entrypoints
.keys()
.filter(|&entry_name| {
let mut included = true;
if let Some(included_chunks) = &config.chunks {
included = included_chunks.iter().any(|c| c.eq(entry_name));
}
if let Some(exclude_chunks) = &config.exclude_chunks {
included = included && !exclude_chunks.iter().any(|c| c.eq(entry_name));
}
included
})
.collect()
};

let included_assets = sorted_entry_names
.iter()
.map(|entry_name| compilation.entrypoint_by_name(entry_name))
.flat_map(|entry| entry.get_files(&compilation.chunk_by_ukey))
.filter_map(|asset_name| {
Expand Down
31 changes: 31 additions & 0 deletions crates/rspack_plugin_html/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,31 @@ impl std::fmt::Debug for TemplateRenderFn {
}
}

#[derive(Serialize, Debug, Clone, Copy, Default)]
#[serde(rename_all = "snake_case")]
pub enum HtmlChunkSortMode {
#[default]
Auto,
Manual,
// TODO: support function
}

impl FromStr for HtmlChunkSortMode {
type Err = anyhow::Error;

fn from_str(s: &str) -> Result<Self, Self::Err> {
if s.eq("auto") {
Ok(HtmlChunkSortMode::Auto)
} else if s.eq("manual") {
Ok(HtmlChunkSortMode::Manual)
} else {
Err(anyhow::Error::msg(
"chunksSortMode in html config only support 'auto' or 'manual'",
))
}
}
}

#[derive(Serialize, Debug)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct HtmlRspackPluginOptions {
Expand All @@ -139,6 +164,7 @@ pub struct HtmlRspackPluginOptions {
/// entry_chunk_name (only entry chunks are supported)
pub chunks: Option<Vec<String>>,
pub exclude_chunks: Option<Vec<String>>,
pub chunks_sort_mode: HtmlChunkSortMode,

/// hash func that used in subsource integrity
/// sha384, sha256 or sha512
Expand All @@ -164,6 +190,10 @@ fn default_inject() -> HtmlInject {
HtmlInject::Head
}

fn default_chunks_sort_mode() -> HtmlChunkSortMode {
HtmlChunkSortMode::Auto
}

impl Default for HtmlRspackPluginOptions {
fn default() -> HtmlRspackPluginOptions {
HtmlRspackPluginOptions {
Expand All @@ -177,6 +207,7 @@ impl Default for HtmlRspackPluginOptions {
script_loading: default_script_loading(),
chunks: None,
exclude_chunks: None,
chunks_sort_mode: default_chunks_sort_mode(),
sri: None,
minify: None,
title: None,
Expand Down
1 change: 1 addition & 0 deletions crates/rspack_plugin_html/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#![feature(box_patterns)]
#![feature(let_chains)]

pub mod asset;
pub mod config;
Expand Down
1 change: 1 addition & 0 deletions packages/rspack/etc/core.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -2296,6 +2296,7 @@ export type HtmlRspackPluginOptions = {
scriptLoading?: "blocking" | "defer" | "module" | "systemjs-module";
chunks?: string[];
excludeChunks?: string[];
chunksSortMode?: "auto" | "manual";
sri?: "sha256" | "sha384" | "sha512";
minify?: boolean;
favicon?: string;
Expand Down
6 changes: 6 additions & 0 deletions packages/rspack/src/builtin-plugin/HtmlRspackPlugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,9 @@ export type HtmlRspackPluginOptions = {
/** Allows you to skip some chunks. */
excludeChunks?: string[];

/** Allows to control how chunks should be sorted before they are included to the HTML. */
chunksSortMode?: "auto" | "manual";

/** The sri hash algorithm, disabled by default. */
sri?: "sha256" | "sha384" | "sha512";

Expand Down Expand Up @@ -164,6 +167,7 @@ const htmlRspackPluginOptions = z.strictObject({
.optional(),
chunks: z.string().array().optional(),
excludeChunks: z.string().array().optional(),
chunksSortMode: z.enum(["auto", "manual"]).optional(),
sri: z.enum(["sha256", "sha384", "sha512"]).optional(),
minify: z.boolean().optional(),
title: z.string().optional(),
Expand Down Expand Up @@ -205,6 +209,7 @@ const HtmlRspackPluginImpl = create(
? "false"
: configInject;
const base = typeof c.base === "string" ? { href: c.base } : c.base;
const chunksSortMode = c.chunksSortMode ?? "auto";

let compilation: Compilation | null = null;
this.hooks.compilation.tap("HtmlRspackPlugin", compilationInstance => {
Expand Down Expand Up @@ -346,6 +351,7 @@ const HtmlRspackPluginImpl = create(
publicPath: c.publicPath,
chunks: c.chunks,
excludeChunks: c.excludeChunks,
chunksSortMode,
sri: c.sri,
minify: c.minify,
meta,
Expand Down
158 changes: 78 additions & 80 deletions tests/plugin-test/html-plugin/basic.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2934,43 +2934,42 @@ describe("HtmlWebpackPlugin", () => {
);
});

// TODO: support `chunksSortMode`
// it("should sort the chunks in auto mode", (done) => {
// testHtmlPlugin(
// {
// mode: "production",
// entry: {
// util: path.join(__dirname, "fixtures/util.js"),
// index: path.join(__dirname, "fixtures/index.js"),
// },
// output: {
// path: OUTPUT_DIR,
// filename: "[name]_bundle.js",
// },
// optimization: {
// splitChunks: {
// cacheGroups: {
// commons: {
// chunks: "initial",
// name: "common",
// enforce: true,
// },
// },
// },
// },
// plugins: [
// new HtmlWebpackPlugin({
// chunksSortMode: "auto",
// }),
// ],
// },
// [
// /(<script defer src="common_bundle.js">.+<script defer src="util_bundle.js">.+<script defer src="index_bundle.js">)|(<script defer src="common_bundle.js">.+<script defer src="index_bundle.js">.+<script defer src="util_bundle.js">)/,
// ],
// null,
// done,
// );
// });
it("should sort the chunks in auto mode", (done) => {
testHtmlPlugin(
{
mode: "production",
entry: {
util: path.join(__dirname, "fixtures/util.js"),
index: path.join(__dirname, "fixtures/index.js"),
},
output: {
path: OUTPUT_DIR,
filename: "[name]_bundle.js",
},
optimization: {
splitChunks: {
cacheGroups: {
commons: {
chunks: "initial",
name: "common",
enforce: true,
},
},
},
},
plugins: [
new HtmlWebpackPlugin({
chunksSortMode: "auto",
}),
],
},
[
/(<script defer src="common_bundle.js">.+<script defer src="util_bundle.js">.+<script defer src="index_bundle.js">)|(<script defer src="common_bundle.js">.+<script defer src="index_bundle.js">.+<script defer src="util_bundle.js">)/,
],
null,
done,
);
});

// TODO: support `chunksSortMode`
// it("should sort the chunks in custom (reverse alphabetical) order", (done) => {
Expand Down Expand Up @@ -3008,49 +3007,48 @@ describe("HtmlWebpackPlugin", () => {
// );
// });

// TODO: support `chunksSortMode`
// it("should sort manually by the chunks", (done) => {
// testHtmlPlugin(
// {
// mode: "production",
// entry: {
// b: path.join(__dirname, "fixtures/util.js"),
// a: path.join(__dirname, "fixtures/theme.js"),
// d: path.join(__dirname, "fixtures/util.js"),
// c: path.join(__dirname, "fixtures/theme.js"),
// },
// output: {
// path: OUTPUT_DIR,
// filename: "[name]_bundle.js",
// },
// module: {
// rules: [{ test: /\.css$/, loader: "css-loader" }],
// },
// optimization: {
// splitChunks: {
// cacheGroups: {
// commons: {
// chunks: "initial",
// name: "common",
// enforce: true,
// },
// },
// },
// },
// plugins: [
// new HtmlWebpackPlugin({
// chunksSortMode: "manual",
// chunks: ["common", "a", "b", "c"],
// }),
// ],
// },
// [
// /<script defer src="common_bundle.js">.+<script defer src="a_bundle.js">.+<script defer src="b_bundle.js">.+<script defer src="c_bundle.js">/,
// ],
// null,
// done,
// );
// });
it("should sort manually by the chunks", (done) => {
testHtmlPlugin(
{
mode: "production",
entry: {
b: path.join(__dirname, "fixtures/util.js"),
a: path.join(__dirname, "fixtures/theme.js"),
d: path.join(__dirname, "fixtures/util.js"),
c: path.join(__dirname, "fixtures/theme.js"),
},
output: {
path: OUTPUT_DIR,
filename: "[name]_bundle.js",
},
module: {
rules: [{ test: /\.css$/, loader: "css-loader" }],
},
optimization: {
splitChunks: {
cacheGroups: {
commons: {
chunks: "initial",
name: "common",
enforce: true,
},
},
},
},
plugins: [
new HtmlWebpackPlugin({
chunksSortMode: "manual",
chunks: ["common", "a", "b", "c"],
}),
],
},
[
/<script defer src="common_bundle.js">.+<script defer src="a_bundle.js">.+<script defer src="b_bundle.js">.+<script defer src="c_bundle.js">/,
],
null,
done,
);
});

it("should add the webpack compilation object as a property of the templateParam object", (done) => {
testHtmlPlugin(
Expand Down
Loading

2 comments on commit 9c46272

@rspack-bot
Copy link

Choose a reason for hiding this comment

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

📝 Benchmark detail: Open

Name Base (2024-12-02 5a967f7) Current Change
10000_big_production-mode_disable-minimize + exec 42.8 s ± 1.97 s 42.3 s ± 648 ms -1.08 %
10000_development-mode + exec 1.8 s ± 45 ms 1.8 s ± 27 ms -0.12 %
10000_development-mode_hmr + exec 648 ms ± 5.5 ms 646 ms ± 5.1 ms -0.36 %
10000_production-mode + exec 2.39 s ± 30 ms 2.39 s ± 20 ms -0.20 %
arco-pro_development-mode + exec 1.73 s ± 82 ms 1.76 s ± 66 ms +1.74 %
arco-pro_development-mode_hmr + exec 426 ms ± 2.5 ms 425 ms ± 1.7 ms -0.08 %
arco-pro_production-mode + exec 3.13 s ± 60 ms 3.12 s ± 83 ms -0.24 %
arco-pro_production-mode_generate-package-json-webpack-plugin + exec 3.16 s ± 77 ms 3.19 s ± 62 ms +1.05 %
threejs_development-mode_10x + exec 1.62 s ± 15 ms 1.62 s ± 11 ms -0.06 %
threejs_development-mode_10x_hmr + exec 810 ms ± 8.1 ms 811 ms ± 13 ms +0.15 %
threejs_production-mode_10x + exec 4.94 s ± 37 ms 4.93 s ± 38 ms -0.30 %
10000_big_production-mode_disable-minimize + rss memory 13399 MiB ± 459 MiB 13454 MiB ± 257 MiB +0.41 %
10000_development-mode + rss memory 775 MiB ± 13.2 MiB 788 MiB ± 29.8 MiB +1.70 %
10000_development-mode_hmr + rss memory 2010 MiB ± 398 MiB 2149 MiB ± 191 MiB +6.92 %
10000_production-mode + rss memory 671 MiB ± 27.6 MiB 671 MiB ± 36.8 MiB +0.03 %
arco-pro_development-mode + rss memory 741 MiB ± 47.6 MiB 723 MiB ± 35.2 MiB -2.37 %
arco-pro_development-mode_hmr + rss memory 922 MiB ± 152 MiB 934 MiB ± 104 MiB +1.30 %
arco-pro_production-mode + rss memory 878 MiB ± 26.9 MiB 848 MiB ± 59.5 MiB -3.45 %
arco-pro_production-mode_generate-package-json-webpack-plugin + rss memory 871 MiB ± 38.5 MiB 887 MiB ± 70.5 MiB +1.75 %
threejs_development-mode_10x + rss memory 830 MiB ± 31.6 MiB 823 MiB ± 37.3 MiB -0.80 %
threejs_development-mode_10x_hmr + rss memory 2115 MiB ± 194 MiB 2142 MiB ± 355 MiB +1.24 %
threejs_production-mode_10x + rss memory 1037 MiB ± 68.4 MiB 1078 MiB ± 90.8 MiB +3.97 %

@rspack-bot
Copy link

Choose a reason for hiding this comment

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

📝 Ran ecosystem CI: Open

suite result
modernjs ✅ success
_selftest ✅ success
rsdoctor ✅ success
rspress ✅ success
rslib ❌ failure
rsbuild ✅ success
examples ✅ success
devserver ✅ success
nuxt ✅ success

Please sign in to comment.