diff --git a/crates/node_binding/binding.d.ts b/crates/node_binding/binding.d.ts index 24e6d390ee00..69f398c6e770 100644 --- a/crates/node_binding/binding.d.ts +++ b/crates/node_binding/binding.d.ts @@ -315,6 +315,7 @@ export declare enum BuiltinPluginName { NaturalChunkIdsPlugin = 'NaturalChunkIdsPlugin', NamedChunkIdsPlugin = 'NamedChunkIdsPlugin', DeterministicChunkIdsPlugin = 'DeterministicChunkIdsPlugin', + OccurrenceChunkIdsPlugin = 'OccurrenceChunkIdsPlugin', RealContentHashPlugin = 'RealContentHashPlugin', RemoveEmptyChunksPlugin = 'RemoveEmptyChunksPlugin', EnsureChunkConditionsPlugin = 'EnsureChunkConditionsPlugin', @@ -1731,6 +1732,10 @@ export interface RawNonStandard { deepSelectorCombinator: boolean } +export interface RawOccurrenceChunkIdsPluginOptions { + prioritiseInitial?: boolean +} + export interface RawOptimizationOptions { removeAvailableModules: boolean sideEffects: string diff --git a/crates/rspack_binding_options/src/options/raw_builtins/mod.rs b/crates/rspack_binding_options/src/options/raw_builtins/mod.rs index 89c706df72fe..fc8c6a4a9ca3 100644 --- a/crates/rspack_binding_options/src/options/raw_builtins/mod.rs +++ b/crates/rspack_binding_options/src/options/raw_builtins/mod.rs @@ -4,6 +4,7 @@ mod raw_copy; mod raw_css_extract; mod raw_dll; mod raw_html; +mod raw_ids; mod raw_ignore; mod raw_lazy_compilation; mod raw_lightning_css_minimizer; @@ -17,13 +18,14 @@ mod raw_swc_js_minimizer; use napi::{bindgen_prelude::FromNapiValue, Env, JsUnknown}; use napi_derive::napi; use raw_dll::{RawDllReferenceAgencyPluginOptions, RawFlagAllModulesAsUsedPluginOptions}; +use raw_ids::RawOccurrenceChunkIdsPluginOptions; use raw_lightning_css_minimizer::RawLightningCssMinimizerRspackPluginOptions; use rspack_binding_values::entry::JsEntryPluginOptions; use rspack_core::{BoxPlugin, Plugin, PluginExt}; use rspack_error::Result; use rspack_ids::{ DeterministicChunkIdsPlugin, DeterministicModuleIdsPlugin, NamedChunkIdsPlugin, - NamedModuleIdsPlugin, NaturalChunkIdsPlugin, NaturalModuleIdsPlugin, + NamedModuleIdsPlugin, NaturalChunkIdsPlugin, NaturalModuleIdsPlugin, OccurrenceChunkIdsPlugin, }; use rspack_napi::NapiResultExt; use rspack_plugin_asset::AssetPlugin; @@ -149,6 +151,7 @@ pub enum BuiltinPluginName { NaturalChunkIdsPlugin, NamedChunkIdsPlugin, DeterministicChunkIdsPlugin, + OccurrenceChunkIdsPlugin, RealContentHashPlugin, RemoveEmptyChunksPlugin, EnsureChunkConditionsPlugin, @@ -370,6 +373,12 @@ impl BuiltinPlugin { BuiltinPluginName::DeterministicChunkIdsPlugin => { plugins.push(DeterministicChunkIdsPlugin::default().boxed()) } + BuiltinPluginName::OccurrenceChunkIdsPlugin => plugins.push( + OccurrenceChunkIdsPlugin::new( + downcast_into::(self.options)?.into(), + ) + .boxed(), + ), BuiltinPluginName::RealContentHashPlugin => { plugins.push(RealContentHashPlugin::default().boxed()) } diff --git a/crates/rspack_binding_options/src/options/raw_builtins/raw_ids.rs b/crates/rspack_binding_options/src/options/raw_builtins/raw_ids.rs new file mode 100644 index 000000000000..0846d8446970 --- /dev/null +++ b/crates/rspack_binding_options/src/options/raw_builtins/raw_ids.rs @@ -0,0 +1,16 @@ +use napi_derive::napi; +use rspack_ids::OccurrenceChunkIdsPluginOptions; + +#[derive(Debug)] +#[napi(object, object_to_js = false)] +pub struct RawOccurrenceChunkIdsPluginOptions { + pub prioritise_initial: Option, +} + +impl From for OccurrenceChunkIdsPluginOptions { + fn from(value: RawOccurrenceChunkIdsPluginOptions) -> Self { + Self { + prioritise_initial: value.prioritise_initial.unwrap_or_default(), + } + } +} diff --git a/crates/rspack_core/src/chunk.rs b/crates/rspack_core/src/chunk.rs index 365cacec8799..26621dc2455f 100644 --- a/crates/rspack_core/src/chunk.rs +++ b/crates/rspack_core/src/chunk.rs @@ -159,6 +159,10 @@ impl Chunk { self.groups.remove(chunk_group) } + pub fn get_number_of_groups(&self) -> usize { + self.groups.len() + } + pub fn runtime(&self) -> &RuntimeSpec { &self.runtime } diff --git a/crates/rspack_ids/src/lib.rs b/crates/rspack_ids/src/lib.rs index 6acb5816a74d..e139531a6177 100644 --- a/crates/rspack_ids/src/lib.rs +++ b/crates/rspack_ids/src/lib.rs @@ -14,3 +14,5 @@ mod natural_module_ids_plugin; pub use natural_module_ids_plugin::NaturalModuleIdsPlugin; mod natural_chunk_ids_plugin; pub use natural_chunk_ids_plugin::NaturalChunkIdsPlugin; +mod occurrence_chunk_ids_plugin; +pub use occurrence_chunk_ids_plugin::*; diff --git a/crates/rspack_ids/src/occurrence_chunk_ids_plugin.rs b/crates/rspack_ids/src/occurrence_chunk_ids_plugin.rs new file mode 100644 index 000000000000..131070489c40 --- /dev/null +++ b/crates/rspack_ids/src/occurrence_chunk_ids_plugin.rs @@ -0,0 +1,94 @@ +use std::collections::HashMap; + +use itertools::Itertools; +use rspack_collections::DatabaseItem; +use rspack_core::{ + ApplyContext, Chunk, CompilationChunkIds, CompilerOptions, Plugin, PluginContext, +}; +use rspack_error::Result; +use rspack_hook::{plugin, plugin_hook}; + +use crate::id_helpers::{assign_ascending_chunk_ids, compare_chunks_natural}; + +pub struct OccurrenceChunkIdsPluginOptions { + pub prioritise_initial: bool, +} + +#[plugin] +#[derive(Debug, Default)] +pub struct OccurrenceChunkIdsPlugin { + prioritise_initial: bool, +} + +impl OccurrenceChunkIdsPlugin { + pub fn new(option: OccurrenceChunkIdsPluginOptions) -> Self { + Self::new_inner(option.prioritise_initial) + } +} + +#[plugin_hook(CompilationChunkIds for OccurrenceChunkIdsPlugin)] +fn chunk_ids(&self, compilation: &mut rspack_core::Compilation) -> Result<()> { + let chunk_graph = &compilation.chunk_graph; + let module_graph = &compilation.get_module_graph(); + let chunk_group_by_ukey = &compilation.chunk_group_by_ukey; + let mut occurs_in_initial_chunks_map = HashMap::new(); + + for chunk in compilation.chunk_by_ukey.values() { + let mut occurs = 0; + for chunk_group_ukey in chunk.groups() { + if let Some(chunk_group) = chunk_group_by_ukey.get(chunk_group_ukey) { + for parent_ukey in &chunk_group.parents { + if let Some(parent) = chunk_group_by_ukey.get(parent_ukey) { + if parent.is_initial() { + occurs += 1; + } + } + } + } + } + occurs_in_initial_chunks_map.insert(chunk.ukey(), occurs); + } + + let chunks = compilation + .chunk_by_ukey + .values() + .map(|chunk| chunk as &Chunk) + .sorted_unstable_by(|a, b| { + if self.prioritise_initial { + let a_entry_occurs = occurs_in_initial_chunks_map.get(&a.ukey()).unwrap_or(&0); + let b_entry_occurs = occurs_in_initial_chunks_map.get(&b.ukey()).unwrap_or(&0); + if a_entry_occurs != b_entry_occurs { + return b_entry_occurs.cmp(a_entry_occurs); + } + } + + let a_occurs = a.get_number_of_groups(); + let b_occurs = b.get_number_of_groups(); + if a_occurs != b_occurs { + return b_occurs.cmp(&a_occurs); + } + + compare_chunks_natural(chunk_graph, module_graph, &compilation.module_ids, a, b) + }) + .map(|chunk| chunk.ukey()) + .collect::>(); + + assign_ascending_chunk_ids(&chunks, compilation); + + Ok(()) +} + +impl Plugin for OccurrenceChunkIdsPlugin { + fn name(&self) -> &'static str { + "rspack.OccurrenceChunkIdsPlugin" + } + + fn apply(&self, ctx: PluginContext<&mut ApplyContext>, _options: &CompilerOptions) -> Result<()> { + ctx + .context + .compilation_hooks + .chunk_ids + .tap(chunk_ids::new(self)); + Ok(()) + } +} diff --git a/packages/rspack/etc/core.api.md b/packages/rspack/etc/core.api.md index b669b0e4e9a3..f627a1e878b1 100644 --- a/packages/rspack/etc/core.api.md +++ b/packages/rspack/etc/core.api.md @@ -4084,7 +4084,7 @@ type Open = (file: PathLike, flags: undefined | string | number, callback: (arg0 // @public (undocumented) export type Optimization = { moduleIds?: "named" | "natural" | "deterministic"; - chunkIds?: "natural" | "named" | "deterministic"; + chunkIds?: "natural" | "named" | "deterministic" | "size" | "total-size"; minimize?: boolean; minimizer?: Array<"..." | Plugin_2>; mergeDuplicateChunks?: boolean; @@ -6918,7 +6918,7 @@ export const rspackOptions: z.ZodObject<{ snapshot: z.ZodOptional>; optimization: z.ZodOptional>; - chunkIds: z.ZodOptional>; + chunkIds: z.ZodOptional>; minimize: z.ZodOptional; minimizer: z.ZodOptional, z.ZodUnion<[z.ZodType, z.ZodUnion<[z.ZodLiteral, z.ZodLiteral<0>, z.ZodLiteral<"">, z.ZodNull, z.ZodUndefined]>]>]>, "many">>; mergeDuplicateChunks: z.ZodOptional; @@ -7182,7 +7182,7 @@ export const rspackOptions: z.ZodObject<{ automaticNameDelimiter?: string | undefined; } | undefined; moduleIds?: "named" | "natural" | "deterministic" | undefined; - chunkIds?: "named" | "natural" | "deterministic" | undefined; + chunkIds?: "size" | "named" | "natural" | "deterministic" | "total-size" | undefined; removeAvailableModules?: boolean | undefined; minimize?: boolean | undefined; minimizer?: (false | "" | 0 | "..." | t.RspackPluginInstance | t.WebpackPluginInstance | t.RspackPluginFunction | t.WebpackPluginFunction | null | undefined)[] | undefined; @@ -7248,7 +7248,7 @@ export const rspackOptions: z.ZodObject<{ automaticNameDelimiter?: string | undefined; } | undefined; moduleIds?: "named" | "natural" | "deterministic" | undefined; - chunkIds?: "named" | "natural" | "deterministic" | undefined; + chunkIds?: "size" | "named" | "natural" | "deterministic" | "total-size" | undefined; removeAvailableModules?: boolean | undefined; minimize?: boolean | undefined; minimizer?: (false | "" | 0 | "..." | t.RspackPluginInstance | t.WebpackPluginInstance | t.RspackPluginFunction | t.WebpackPluginFunction | null | undefined)[] | undefined; @@ -8684,7 +8684,7 @@ export const rspackOptions: z.ZodObject<{ automaticNameDelimiter?: string | undefined; } | undefined; moduleIds?: "named" | "natural" | "deterministic" | undefined; - chunkIds?: "named" | "natural" | "deterministic" | undefined; + chunkIds?: "size" | "named" | "natural" | "deterministic" | "total-size" | undefined; removeAvailableModules?: boolean | undefined; minimize?: boolean | undefined; minimizer?: (false | "" | 0 | "..." | t.RspackPluginInstance | t.WebpackPluginInstance | t.RspackPluginFunction | t.WebpackPluginFunction | null | undefined)[] | undefined; @@ -9286,7 +9286,7 @@ export const rspackOptions: z.ZodObject<{ automaticNameDelimiter?: string | undefined; } | undefined; moduleIds?: "named" | "natural" | "deterministic" | undefined; - chunkIds?: "named" | "natural" | "deterministic" | undefined; + chunkIds?: "size" | "named" | "natural" | "deterministic" | "total-size" | undefined; removeAvailableModules?: boolean | undefined; minimize?: boolean | undefined; minimizer?: (false | "" | 0 | "..." | t.RspackPluginInstance | t.WebpackPluginInstance | t.RspackPluginFunction | t.WebpackPluginFunction | null | undefined)[] | undefined; diff --git a/packages/rspack/src/builtin-plugin/OccurrenceChunkIdsPlugin.ts b/packages/rspack/src/builtin-plugin/OccurrenceChunkIdsPlugin.ts new file mode 100644 index 000000000000..ddbca6dab2e1 --- /dev/null +++ b/packages/rspack/src/builtin-plugin/OccurrenceChunkIdsPlugin.ts @@ -0,0 +1,12 @@ +import { + BuiltinPluginName, + type RawOccurrenceChunkIdsPluginOptions +} from "@rspack/binding"; + +import { create } from "./base"; + +export const OccurrenceChunkIdsPlugin = create( + BuiltinPluginName.OccurrenceChunkIdsPlugin, + (options?: RawOccurrenceChunkIdsPluginOptions) => ({ ...options }), + "compilation" +); diff --git a/packages/rspack/src/builtin-plugin/index.ts b/packages/rspack/src/builtin-plugin/index.ts index d77a6acbb7e9..df4fdf32a725 100644 --- a/packages/rspack/src/builtin-plugin/index.ts +++ b/packages/rspack/src/builtin-plugin/index.ts @@ -47,6 +47,7 @@ export * from "./NamedModuleIdsPlugin"; export * from "./NaturalChunkIdsPlugin"; export * from "./NaturalModuleIdsPlugin"; export * from "./NodeTargetPlugin"; +export * from "./OccurrenceChunkIdsPlugin"; export * from "./ProgressPlugin"; export * from "./ProvidePlugin"; export * from "./RealContentHashPlugin"; diff --git a/packages/rspack/src/config/types.ts b/packages/rspack/src/config/types.ts index cd94cb9d869f..629d18f05eb3 100644 --- a/packages/rspack/src/config/types.ts +++ b/packages/rspack/src/config/types.ts @@ -2208,7 +2208,7 @@ export type Optimization = { /** * Which algorithm to use when choosing chunk ids. */ - chunkIds?: "natural" | "named" | "deterministic"; + chunkIds?: "natural" | "named" | "deterministic" | "size" | "total-size"; /** * Whether to minimize the bundle. diff --git a/packages/rspack/src/config/zod.ts b/packages/rspack/src/config/zod.ts index 7471ad911711..d3f1befeab34 100644 --- a/packages/rspack/src/config/zod.ts +++ b/packages/rspack/src/config/zod.ts @@ -1244,7 +1244,9 @@ const optimizationSplitChunksOptions = z.strictObject({ const optimization = z.strictObject({ moduleIds: z.enum(["named", "natural", "deterministic"]).optional(), - chunkIds: z.enum(["natural", "named", "deterministic"]).optional(), + chunkIds: z + .enum(["natural", "named", "deterministic", "size", "total-size"]) + .optional(), minimize: z.boolean().optional(), minimizer: z.literal("...").or(plugin).array().optional(), mergeDuplicateChunks: z.boolean().optional(), diff --git a/packages/rspack/src/rspackOptionsApply.ts b/packages/rspack/src/rspackOptionsApply.ts index 4eb1887ba197..4dd476226fd3 100644 --- a/packages/rspack/src/rspackOptionsApply.ts +++ b/packages/rspack/src/rspackOptionsApply.ts @@ -56,6 +56,7 @@ import { NaturalModuleIdsPlugin, NoEmitOnErrorsPlugin, NodeTargetPlugin, + OccurrenceChunkIdsPlugin, RealContentHashPlugin, RemoveEmptyChunksPlugin, RuntimeChunkPlugin, @@ -337,6 +338,18 @@ export class RspackOptionsApply { new DeterministicChunkIdsPlugin().apply(compiler); break; } + case "size": { + new OccurrenceChunkIdsPlugin({ + prioritiseInitial: true + }).apply(compiler); + break; + } + case "total-size": { + new OccurrenceChunkIdsPlugin({ + prioritiseInitial: false + }).apply(compiler); + break; + } default: throw new Error(`chunkIds: ${chunkIds} is not implemented`); } diff --git a/tests/webpack-test/configCases/contenthash/include-chunk-id/test.filter.js b/tests/webpack-test/configCases/contenthash/include-chunk-id/test.filter.js deleted file mode 100644 index 2a9dadde2381..000000000000 --- a/tests/webpack-test/configCases/contenthash/include-chunk-id/test.filter.js +++ /dev/null @@ -1,2 +0,0 @@ -// blocked by chunkIds -module.exports = () => { return 'https://github.com/web-infra-dev/rspack/issues/8605' } diff --git a/website/components/PluginSupportStatusTable.tsx b/website/components/PluginSupportStatusTable.tsx index 418c906d67c2..aab985b55359 100644 --- a/website/components/PluginSupportStatusTable.tsx +++ b/website/components/PluginSupportStatusTable.tsx @@ -413,7 +413,7 @@ const pluginSupportStatusList: PluginSupportStatus[] = [ }, { name: 'OccurrenceChunkIdsPlugin', - status: SupportStatus.NotSupported, + status: SupportStatus.FullySupported, }, { name: 'OccurrenceModuleIdsPlugin', diff --git a/website/docs/en/config/optimization.mdx b/website/docs/en/config/optimization.mdx index 70c9e5bf5144..a565b7164bd4 100644 --- a/website/docs/en/config/optimization.mdx +++ b/website/docs/en/config/optimization.mdx @@ -54,8 +54,11 @@ The following string values are supported: | option | description | | ----------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | +| `'natural'` | Use numeric ids in order of usage. | | `'named'` | Readable ids for better debugging. | | `'deterministic'` | Short numeric ids which will not be changing between compilation. Good for long term caching. By default a minimum length of 3 digits is used. | +| `'size'` | Use numeric ids to make the initial download package smaller. | +| `'total-size'` | Use numeric ids to make the overall download package smaller. | ```js title=rspack.config.js module.exports = { diff --git a/website/docs/zh/config/optimization.mdx b/website/docs/zh/config/optimization.mdx index e324c43e042c..88dafa5d80f2 100644 --- a/website/docs/zh/config/optimization.mdx +++ b/website/docs/zh/config/optimization.mdx @@ -52,8 +52,11 @@ module.exports = { | 选项 | 描述 | | ----------------- | ---------------------------------------------------------------------------------------- | +| `'natural'` | 根据 Chunk 加载的顺序使用自增数字作为 Chunk id。 | | `'named'` | 使用有意义、方便调试的内容当作 Chunk id。 | | `'deterministic'` | 简短的数字 id,在多次编译的场景下,会尽量保持其稳定性。适合长期缓存。默认使用 3 位数字。 | +| `'size'` | 使用让初始下载包大小更小的数字 id。 | +| `'total-size'` | 使用让总下载包大小更小的数字 id。 | ```js title=rspack.config.js module.exports = {