diff --git a/crates/node_binding/binding.d.ts b/crates/node_binding/binding.d.ts index 486a0d5b6a0..115ec58cc0c 100644 --- a/crates/node_binding/binding.d.ts +++ b/crates/node_binding/binding.d.ts @@ -146,6 +146,7 @@ export class ModuleDto { get type(): string get layer(): string | undefined get blocks(): Array + size(ty?: string | undefined | null): number } export type ModuleDTO = ModuleDto @@ -351,6 +352,10 @@ export interface JsBuildTimeExecutionOption { baseUri?: string } +export interface JsCacheGroupTestCtx { + module: ModuleDTO +} + export interface JsChunk { __inner_ukey: number __inner_groups: Array @@ -987,10 +992,6 @@ export interface RawCacheGroupOptions { usedExports?: boolean } -export interface RawCacheGroupTestCtx { - module: JsModule -} - export interface RawCacheOptions { type: string maxGenerations: number diff --git a/crates/node_binding/src/lib.rs b/crates/node_binding/src/lib.rs index 599aca4615a..f78ba6ac59e 100644 --- a/crates/node_binding/src/lib.rs +++ b/crates/node_binding/src/lib.rs @@ -11,7 +11,7 @@ use std::sync::Mutex; use compiler::{Compiler, CompilerState, CompilerStateGuard}; use napi::bindgen_prelude::*; use rspack_binding_options::BuiltinPlugin; -use rspack_core::PluginExt; +use rspack_core::{Compilation, PluginExt}; use rspack_error::Diagnostic; use rspack_fs_node::{AsyncNodeWritableFileSystem, ThreadsafeNodeFS}; @@ -164,6 +164,9 @@ impl Rspack { // SAFETY: The mutable reference to `Compiler` is exclusive. It's guaranteed by the running state guard. Ok(unsafe { s.compiler.as_mut().get_unchecked_mut() }) })?; + + self.cleanup_last_compilation(&compiler.compilation); + // SAFETY: // 1. `Compiler` is pinned and stored on the heap. // 2. `JsReference` (NAPI internal mechanism) keeps `Compiler` alive until its instance getting garbage collected. @@ -172,6 +175,10 @@ impl Rspack { _guard, ) } + + fn cleanup_last_compilation(&self, compilation: &Compilation) { + JsCompilationWrapper::cleanup(compilation.id()); + } } fn concurrent_compiler_error() -> Error { diff --git a/crates/node_binding/src/plugins/interceptor.rs b/crates/node_binding/src/plugins/interceptor.rs index 76d92a35f0a..4c4044592b2 100644 --- a/crates/node_binding/src/plugins/interceptor.rs +++ b/crates/node_binding/src/plugins/interceptor.rs @@ -13,7 +13,7 @@ use napi::{ use rspack_binding_values::{ CompatSource, JsAdditionalTreeRuntimeRequirementsArg, JsAdditionalTreeRuntimeRequirementsResult, JsAfterResolveData, JsAfterResolveOutput, JsAssetEmittedArgs, JsBeforeResolveArgs, - JsBeforeResolveOutput, JsChunk, JsChunkAssetArgs, JsCompilation, + JsBeforeResolveOutput, JsChunk, JsChunkAssetArgs, JsCompilationWrapper, JsContextModuleFactoryAfterResolveData, JsContextModuleFactoryAfterResolveResult, JsContextModuleFactoryBeforeResolveData, JsContextModuleFactoryBeforeResolveResult, JsCreateData, JsExecuteModuleArg, JsFactorizeArgs, JsFactorizeOutput, JsModule, @@ -350,23 +350,23 @@ pub struct RegisterJsTaps { #[napi( ts_type = "(stages: Array) => Array<{ function: ((arg: JsCompilation) => void); stage: number; }>" )] - pub register_compiler_this_compilation_taps: RegisterFunction, + pub register_compiler_this_compilation_taps: RegisterFunction, #[napi( ts_type = "(stages: Array) => Array<{ function: ((arg: JsCompilation) => void); stage: number; }>" )] - pub register_compiler_compilation_taps: RegisterFunction, + pub register_compiler_compilation_taps: RegisterFunction, #[napi( ts_type = "(stages: Array) => Array<{ function: ((arg: JsCompilation) => Promise); stage: number; }>" )] - pub register_compiler_make_taps: RegisterFunction>, + pub register_compiler_make_taps: RegisterFunction>, #[napi( ts_type = "(stages: Array) => Array<{ function: ((arg: JsCompilation) => void); stage: number; }>" )] - pub register_compiler_finish_make_taps: RegisterFunction>, + pub register_compiler_finish_make_taps: RegisterFunction>, #[napi( ts_type = "(stages: Array) => Array<{ function: ((arg: JsCompilation) => boolean | undefined); stage: number; }>" )] - pub register_compiler_should_emit_taps: RegisterFunction>, + pub register_compiler_should_emit_taps: RegisterFunction>, #[napi( ts_type = "(stages: Array) => Array<{ function: (() => Promise); stage: number; }>" )] @@ -410,7 +410,7 @@ pub struct RegisterJsTaps { #[napi( ts_type = "(stages: Array) => Array<{ function: ((arg: JsCompilation) => Promise); stage: number; }>" )] - pub register_compilation_finish_modules_taps: RegisterFunction>, + pub register_compilation_finish_modules_taps: RegisterFunction>, #[napi( ts_type = "(stages: Array) => Array<{ function: (() => boolean | undefined); stage: number; }>" )] @@ -436,11 +436,11 @@ pub struct RegisterJsTaps { #[napi( ts_type = "(stages: Array) => Array<{ function: ((arg: JsCompilation) => Promise); stage: number; }>" )] - pub register_compilation_process_assets_taps: RegisterFunction>, + pub register_compilation_process_assets_taps: RegisterFunction>, #[napi( ts_type = "(stages: Array) => Array<{ function: ((arg: JsCompilation) => void); stage: number; }>" )] - pub register_compilation_after_process_assets_taps: RegisterFunction, + pub register_compilation_after_process_assets_taps: RegisterFunction, #[napi(ts_type = "(stages: Array) => Array<{ function: (() => void); stage: number; }>")] pub register_compilation_seal_taps: RegisterFunction<(), ()>, #[napi( @@ -500,7 +500,7 @@ pub struct RegisterJsTaps { /* Compiler Hooks */ define_register!( RegisterCompilerThisCompilationTaps, - tap = CompilerThisCompilationTap @ CompilerThisCompilationHook, + tap = CompilerThisCompilationTap @ CompilerThisCompilationHook, cache = false, sync = false, kind = RegisterJsTapKind::CompilerThisCompilation, @@ -508,7 +508,7 @@ define_register!( ); define_register!( RegisterCompilerCompilationTaps, - tap = CompilerCompilationTap @ CompilerCompilationHook, + tap = CompilerCompilationTap @ CompilerCompilationHook, cache = false, sync = false, kind = RegisterJsTapKind::CompilerCompilation, @@ -516,7 +516,7 @@ define_register!( ); define_register!( RegisterCompilerMakeTaps, - tap = CompilerMakeTap> @ CompilerMakeHook, + tap = CompilerMakeTap> @ CompilerMakeHook, cache = false, sync = false, kind = RegisterJsTapKind::CompilerMake, @@ -524,7 +524,7 @@ define_register!( ); define_register!( RegisterCompilerFinishMakeTaps, - tap = CompilerFinishMakeTap> @ CompilerFinishMakeHook, + tap = CompilerFinishMakeTap> @ CompilerFinishMakeHook, cache = false, sync = false, kind = RegisterJsTapKind::CompilerFinishMake, @@ -532,7 +532,7 @@ define_register!( ); define_register!( RegisterCompilerShouldEmitTaps, - tap = CompilerShouldEmitTap> @ CompilerShouldEmitHook, + tap = CompilerShouldEmitTap> @ CompilerShouldEmitHook, cache = false, sync = false, kind = RegisterJsTapKind::CompilerShouldEmit, @@ -598,7 +598,7 @@ define_register!( ); define_register!( RegisterCompilationFinishModulesTaps, - tap = CompilationFinishModulesTap> @ CompilationFinishModulesHook, + tap = CompilationFinishModulesTap> @ CompilationFinishModulesHook, cache = false, sync = false, kind = RegisterJsTapKind::CompilationFinishModules, @@ -670,7 +670,7 @@ define_register!( ); define_register!( RegisterCompilationProcessAssetsTaps, - tap = CompilationProcessAssetsTap> @ CompilationProcessAssetsHook, + tap = CompilationProcessAssetsTap> @ CompilationProcessAssetsHook, cache = false, sync = false, kind = RegisterJsTapKind::CompilationProcessAssets, @@ -678,7 +678,7 @@ define_register!( ); define_register!( RegisterCompilationAfterProcessAssetsTaps, - tap = CompilationAfterProcessAssetsTap @ CompilationAfterProcessAssetsHook, + tap = CompilationAfterProcessAssetsTap @ CompilationAfterProcessAssetsHook, cache = false, sync = false, kind = RegisterJsTapKind::CompilationAfterProcessAssets, @@ -786,11 +786,7 @@ impl CompilerThisCompilation for CompilerThisCompilationTap { compilation: &mut Compilation, _: &mut CompilationParams, ) -> rspack_error::Result<()> { - // SAFETY: - // 1. `Compiler` is stored on the heap and pinned in binding crate. - // 2. `Compilation` outlives `JsCompilation` and `Compiler` outlives `Compilation`. - // 3. `JsCompilation` was replaced everytime a new `Compilation` was created before getting accessed. - let compilation = unsafe { JsCompilation::from_compilation(compilation) }; + let compilation = JsCompilationWrapper::new(compilation); self.function.call_with_sync(compilation).await } @@ -806,11 +802,7 @@ impl CompilerCompilation for CompilerCompilationTap { compilation: &mut Compilation, _: &mut CompilationParams, ) -> rspack_error::Result<()> { - // SAFETY: - // 1. `Compiler` is stored on the heap and pinned in binding crate. - // 2. `Compilation` outlives `JsCompilation` and `Compiler` outlives `Compilation`. - // 3. `JsCompilation` was replaced everytime a new `Compilation` was created before getting accessed. - let compilation = unsafe { JsCompilation::from_compilation(compilation) }; + let compilation = JsCompilationWrapper::new(compilation); self.function.call_with_sync(compilation).await } @@ -822,12 +814,7 @@ impl CompilerCompilation for CompilerCompilationTap { #[async_trait] impl CompilerMake for CompilerMakeTap { async fn run(&self, compilation: &mut Compilation) -> rspack_error::Result<()> { - // SAFETY: - // 1. `Compiler` is stored on the heap and pinned in binding crate. - // 2. `Compilation` outlives `JsCompilation` and `Compiler` outlives `Compilation`. - // 3. `JsCompilation` was replaced everytime a new `Compilation` was created before getting accessed. - let compilation = unsafe { JsCompilation::from_compilation(compilation) }; - + let compilation = JsCompilationWrapper::new(compilation); self.function.call_with_promise(compilation).await } @@ -839,12 +826,7 @@ impl CompilerMake for CompilerMakeTap { #[async_trait] impl CompilerFinishMake for CompilerFinishMakeTap { async fn run(&self, compilation: &mut Compilation) -> rspack_error::Result<()> { - // SAFETY: - // 1. `Compiler` is stored on the heap and pinned in binding crate. - // 2. `Compilation` outlives `JsCompilation` and `Compiler` outlives `Compilation`. - // 3. `JsCompilation` was replaced everytime a new `Compilation` was created before getting accessed. - let compilation = unsafe { JsCompilation::from_compilation(compilation) }; - + let compilation = JsCompilationWrapper::new(compilation); self.function.call_with_promise(compilation).await } @@ -856,12 +838,7 @@ impl CompilerFinishMake for CompilerFinishMakeTap { #[async_trait] impl CompilerShouldEmit for CompilerShouldEmitTap { async fn run(&self, compilation: &mut Compilation) -> rspack_error::Result> { - // SAFETY: - // 1. `Compiler` is stored on the heap and pinned in binding crate. - // 2. `Compilation` outlives `JsCompilation` and `Compiler` outlives `Compilation`. - // 3. `JsCompilation` was replaced everytime a new `Compilation` was created before getting accessed. - let compilation = unsafe { JsCompilation::from_compilation(compilation) }; - + let compilation = JsCompilationWrapper::new(compilation); self.function.call_with_sync(compilation).await } @@ -982,12 +959,7 @@ impl CompilationExecuteModule for CompilationExecuteModuleTap { #[async_trait] impl CompilationFinishModules for CompilationFinishModulesTap { async fn run(&self, compilation: &mut Compilation) -> rspack_error::Result<()> { - // SAFETY: - // 1. `Compiler` is stored on the heap and pinned in binding crate. - // 2. `Compilation` outlives `JsCompilation` and `Compiler` outlives `Compilation`. - // 3. `JsCompilation` was replaced everytime a new `Compilation` was created before getting accessed. - let compilation = unsafe { JsCompilation::from_compilation(compilation) }; - + let compilation = JsCompilationWrapper::new(compilation); self.function.call_with_promise(compilation).await } @@ -1149,12 +1121,7 @@ impl CompilationChunkAsset for CompilationChunkAssetTap { #[async_trait] impl CompilationProcessAssets for CompilationProcessAssetsTap { async fn run(&self, compilation: &mut Compilation) -> rspack_error::Result<()> { - // SAFETY: - // 1. `Compiler` is stored on the heap and pinned in binding crate. - // 2. `Compilation` outlives `JsCompilation` and `Compiler` outlives `Compilation`. - // 3. `JsCompilation` was replaced everytime a new `Compilation` was created before getting accessed. - let compilation = unsafe { JsCompilation::from_compilation(compilation) }; - + let compilation = JsCompilationWrapper::new(compilation); self.function.call_with_promise(compilation).await } @@ -1166,12 +1133,7 @@ impl CompilationProcessAssets for CompilationProcessAssetsTap { #[async_trait] impl CompilationAfterProcessAssets for CompilationAfterProcessAssetsTap { async fn run(&self, compilation: &mut Compilation) -> rspack_error::Result<()> { - // SAFETY: - // 1. `Compiler` is stored on the heap and pinned in binding crate. - // 2. `Compilation` outlives `JsCompilation` and `Compiler` outlives `Compilation`. - // 3. `JsCompilation` was replaced everytime a new `Compilation` was created before getting accessed. - let compilation = unsafe { JsCompilation::from_compilation(compilation) }; - + let compilation = JsCompilationWrapper::new(compilation); self.function.call_with_sync(compilation).await } diff --git a/crates/rspack_binding_options/src/options/raw_split_chunks/raw_split_chunk_cache_group_test.rs b/crates/rspack_binding_options/src/options/raw_split_chunks/raw_split_chunk_cache_group_test.rs index b057f974cca..19e7d67572c 100644 --- a/crates/rspack_binding_options/src/options/raw_split_chunks/raw_split_chunk_cache_group_test.rs +++ b/crates/rspack_binding_options/src/options/raw_split_chunks/raw_split_chunk_cache_group_test.rs @@ -1,28 +1,35 @@ use std::sync::Arc; -use napi::bindgen_prelude::Either3; +use napi::bindgen_prelude::{Either3, FromNapiValue}; use napi_derive::napi; -use rspack_binding_values::{JsModule, ToJsModule}; +use rspack_binding_values::ModuleDTOWrapper; use rspack_napi::regexp::{JsRegExp, JsRegExpExt}; use rspack_napi::threadsafe_function::ThreadsafeFunction; use rspack_plugin_split_chunks::{CacheGroupTest, CacheGroupTestFnCtx}; use tokio::runtime::Handle; pub(super) type RawCacheGroupTest = - Either3>>; + Either3>>; -#[napi(object)] -pub struct RawCacheGroupTestCtx { - pub module: JsModule, +#[napi(object, object_from_js = false)] +pub struct JsCacheGroupTestCtx { + #[napi(ts_type = "ModuleDTO")] + pub module: ModuleDTOWrapper, } -impl<'a> From> for RawCacheGroupTestCtx { +impl FromNapiValue for JsCacheGroupTestCtx { + unsafe fn from_napi_value( + _env: napi::sys::napi_env, + _napi_val: napi::sys::napi_value, + ) -> napi::Result { + unreachable!() + } +} + +impl<'a> From> for JsCacheGroupTestCtx { fn from(value: CacheGroupTestFnCtx<'a>) -> Self { - RawCacheGroupTestCtx { - module: value - .module - .to_js_module() - .expect("should convert js module success"), + JsCacheGroupTestCtx { + module: ModuleDTOWrapper::new(value.module.identifier(), value.compilation), } } } diff --git a/crates/rspack_binding_values/src/compilation/mod.rs b/crates/rspack_binding_values/src/compilation/mod.rs index 6d097a398ab..6628d79d83c 100644 --- a/crates/rspack_binding_values/src/compilation/mod.rs +++ b/crates/rspack_binding_values/src/compilation/mod.rs @@ -1,6 +1,7 @@ mod dependency; mod entries; +use std::cell::RefCell; use std::collections::HashMap; use std::ops::Deref; use std::ops::DerefMut; @@ -15,46 +16,29 @@ use rspack_core::get_chunk_group_from_ukey; use rspack_core::rspack_sources::BoxSource; use rspack_core::rspack_sources::SourceExt; use rspack_core::AssetInfo; +use rspack_core::CompilationId; use rspack_core::ModuleIdentifier; use rspack_error::Diagnostic; use rspack_napi::napi::bindgen_prelude::*; use rspack_napi::NapiResultExt; +use rspack_napi::Ref; +use sys::napi_env; use super::module::ToJsModule; use super::{JsFilename, PathWithInfo}; use crate::utils::callbackify; use crate::JsStatsOptimizationBailout; use crate::LocalJsFilename; +use crate::ModuleDTOWrapper; use crate::{ chunk::JsChunk, CompatSource, JsAsset, JsAssetInfo, JsChunkGroup, JsCompatSource, JsPathData, - JsStats, ModuleDTO, ToJsCompatSource, + JsStats, ToJsCompatSource, }; use crate::{JsDiagnostic, JsRspackError}; #[napi] pub struct JsCompilation(pub(crate) &'static mut rspack_core::Compilation); -impl JsCompilation { - /// Convert Rust `Compilation` to `JsCompilation`. - /// - /// ## JS Interoperable - /// `JsCompilation` implements [napi::bindgen_prelude::ToNapiValue]. - /// It can be send to JavaScript. - /// - /// ## Safety - /// Safety is guaranteed by the following contracts: - /// 1. `Compiler` should not be moved. For example: store it on the heap. - /// 2. The pointer should be valid for the entire lifetime of `JsCompilation`. - /// 3. Caching old `Compilation` will result the program to undefined behavior and it's likely to crash. - pub unsafe fn from_compilation(inner: &mut rspack_core::Compilation) -> Self { - Self(unsafe { - std::mem::transmute::<&'_ mut rspack_core::Compilation, &'static mut rspack_core::Compilation>( - inner, - ) - }) - } -} - impl Deref for JsCompilation { type Target = rspack_core::Compilation; @@ -151,15 +135,15 @@ impl JsCompilation { .transpose() } - #[napi(getter)] - pub fn modules(&'static self) -> Vec { + #[napi(getter, ts_return_type = "Array")] + pub fn modules(&'static self) -> Vec { self .0 .get_module_graph() .modules() .keys() .cloned() - .map(|module_id| ModuleDTO::new(module_id, self.0)) + .map(|module_id| ModuleDTOWrapper::new(module_id, self.0)) .collect::>() } @@ -578,6 +562,84 @@ impl JsCompilation { } } +#[derive(Default)] +struct CompilationInstanceRefs(RefCell>); + +impl Drop for CompilationInstanceRefs { + fn drop(&mut self) { + // cleanup references to be executed in cases of panic or unexpected termination + let mut refs = self.0.borrow_mut(); + for (_, (mut r, env)) in refs.drain() { + let _ = r.unref(env); + } + } +} + +thread_local! { + static COMPILATION_INSTANCE_REFS: CompilationInstanceRefs = Default::default(); +} + +// The difference between JsCompilationWrapper and JsCompilation is: +// JsCompilationWrapper maintains a cache to ensure that the corresponding instance of the same Compilation is unique on the JS side. +// +// This means that when transferring a JsCompilation from Rust to JS, you must use JsCompilationWrapper instead. +pub struct JsCompilationWrapper(pub(crate) &'static mut rspack_core::Compilation); + +impl JsCompilationWrapper { + pub fn new(compilation: &mut rspack_core::Compilation) -> Self { + // SAFETY: + // 1. `Compiler` is stored on the heap and pinned in binding crate. + // 2. `Compilation` outlives `JsCompilation` and `Compiler` outlives `Compilation`. + // 3. `JsCompilation` was replaced everytime a new `Compilation` was created before getting accessed. + Self(unsafe { + std::mem::transmute::<&'_ mut rspack_core::Compilation, &'static mut rspack_core::Compilation>( + compilation, + ) + }) + } + + pub fn cleanup(compilation_id: CompilationId) { + COMPILATION_INSTANCE_REFS.with(|ref_cell| { + let mut refs = ref_cell.0.borrow_mut(); + if let Some((mut r, env)) = refs.remove(&compilation_id) { + let _ = r.unref(env); + } + }); + ModuleDTOWrapper::cleanup(compilation_id); + } +} + +impl ToNapiValue for JsCompilationWrapper { + unsafe fn to_napi_value(env: sys::napi_env, val: Self) -> Result { + COMPILATION_INSTANCE_REFS.with(|ref_cell| { + let mut env_wrapper = Env::from_raw(env); + let mut refs = ref_cell.0.borrow_mut(); + let compilation_id = val.0.id(); + let mut vacant = false; + let napi_value = match refs.entry(compilation_id) { + std::collections::hash_map::Entry::Occupied(entry) => { + let r = entry.get(); + ToNapiValue::to_napi_value(env, &r.0) + } + std::collections::hash_map::Entry::Vacant(entry) => { + vacant = true; + let instance = JsCompilation(val.0).into_instance(env_wrapper)?; + let napi_value = ToNapiValue::to_napi_value(env, instance)?; + let r = Ref::new(env, napi_value, 1)?; + let r = entry.insert((r, env)); + ToNapiValue::to_napi_value(env, &r.0) + } + }; + if vacant { + // cleanup references to be executed when the JS thread exits normally + let _ = env_wrapper + .add_env_cleanup_hook((), move |_| JsCompilationWrapper::cleanup(compilation_id)); + } + napi_value + }) + } +} + #[napi(object)] pub struct JsExecuteModuleResult { pub file_dependencies: Vec, diff --git a/crates/rspack_binding_values/src/module.rs b/crates/rspack_binding_values/src/module.rs index 96942016233..887927b6c30 100644 --- a/crates/rspack_binding_values/src/module.rs +++ b/crates/rspack_binding_values/src/module.rs @@ -1,9 +1,14 @@ +use std::cell::RefCell; + use napi_derive::napi; +use rspack_collections::Identifier; use rspack_core::{ - AsyncDependenciesBlock, AsyncDependenciesBlockIdentifier, Compilation, CompilerModuleContext, - DependenciesBlock, Module, ModuleGraph, ModuleIdentifier, + AsyncDependenciesBlock, AsyncDependenciesBlockIdentifier, Compilation, CompilationId, + CompilerModuleContext, DependenciesBlock, Module, ModuleGraph, ModuleIdentifier, SourceType, }; -use rspack_napi::napi::bindgen_prelude::*; +use rspack_napi::{napi::bindgen_prelude::*, Ref}; +use rustc_hash::FxHashMap as HashMap; +use sys::napi_env; use super::{JsCompatSource, ToJsCompatSource}; use crate::{DependencyDTO, JsChunk, JsCodegenerationResults}; @@ -209,6 +214,101 @@ impl ModuleDTO { .map(|block_id| DependenciesBlockDTO::new(block_id, self.compilation)) .collect::>() } + + #[napi] + pub fn size(&self, ty: Option) -> f64 { + let module = self.module(); + let ty = ty.map(|s| SourceType::from(s.as_str())); + module.size(ty.as_ref(), self.compilation) + } +} + +type ModuleInstanceRefs = HashMap; + +#[derive(Default)] +struct ModuleInstanceRefsByCompilationId(RefCell>); + +impl Drop for ModuleInstanceRefsByCompilationId { + fn drop(&mut self) { + let mut refs_by_compilation_id = self.0.borrow_mut(); + for (_, mut refs) in refs_by_compilation_id.drain() { + for (_, (mut r, env)) in refs.drain() { + let _ = r.unref(env); + } + } + } +} + +thread_local! { + static MODULE_INSTANCE_REFS: ModuleInstanceRefsByCompilationId = Default::default(); +} + +// The difference between ModuleDTOWrapper and ModuleDTO is: +// ModuleDTOWrapper maintains a cache to ensure that the corresponding instance of the same Module is unique on the JS side. +// +// This means that when transferring a ModuleDTO from Rust to JS, you must use ModuleDTOWrapper instead. +pub struct ModuleDTOWrapper { + pub module_id: ModuleIdentifier, + pub compilation: &'static Compilation, +} + +impl ModuleDTOWrapper { + pub fn new(module_id: ModuleIdentifier, compilation: &Compilation) -> Self { + // SAFETY: + // 1. `Compiler` is stored on the heap and pinned in binding crate. + // 2. `Compilation` outlives `JsCompilation` and `Compiler` outlives `Compilation`. + // 3. `JsCompilation` was replaced everytime a new `Compilation` was created before getting accessed. + let compilation = unsafe { + std::mem::transmute::<&rspack_core::Compilation, &'static rspack_core::Compilation>( + compilation, + ) + }; + Self { + module_id, + compilation, + } + } + + pub fn cleanup(compilation_id: CompilationId) { + MODULE_INSTANCE_REFS.with(|refs| { + let mut refs_by_compilation_id = refs.0.borrow_mut(); + if let Some(mut refs) = refs_by_compilation_id.remove(&compilation_id) { + for (_, (mut r, env)) in refs.drain() { + let _ = r.unref(env); + } + } + }); + } +} + +impl ToNapiValue for ModuleDTOWrapper { + unsafe fn to_napi_value(env: sys::napi_env, val: Self) -> Result { + MODULE_INSTANCE_REFS.with(|refs| { + let mut refs_by_compilation_id = refs.0.borrow_mut(); + let entry = refs_by_compilation_id.entry(val.compilation.id()); + let refs = match entry { + std::collections::hash_map::Entry::Occupied(entry) => entry.into_mut(), + std::collections::hash_map::Entry::Vacant(entry) => { + let refs = HashMap::default(); + entry.insert(refs) + } + }; + match refs.entry(val.module_id) { + std::collections::hash_map::Entry::Occupied(entry) => { + let r = entry.get(); + ToNapiValue::to_napi_value(env, &r.0) + } + std::collections::hash_map::Entry::Vacant(entry) => { + let instance = + ModuleDTO::new(val.module_id, val.compilation).into_instance(Env::from_raw(env))?; + let napi_value = ToNapiValue::to_napi_value(env, instance)?; + let r = Ref::new(env, napi_value, 1)?; + let r = entry.insert((r, env)); + ToNapiValue::to_napi_value(env, &r.0) + } + } + }) + } } #[derive(Default)] diff --git a/crates/rspack_binding_values/src/stats.rs b/crates/rspack_binding_values/src/stats.rs index 58b8bb47bed..8dcc2c3b2cc 100644 --- a/crates/rspack_binding_values/src/stats.rs +++ b/crates/rspack_binding_values/src/stats.rs @@ -14,8 +14,8 @@ use rspack_napi::{ }; use rustc_hash::FxHashMap as HashMap; -use super::{JsCompilation, ToJsCompatSource}; -use crate::identifier::JsIdentifier; +use super::ToJsCompatSource; +use crate::{identifier::JsIdentifier, JsCompilation}; thread_local! { static MODULE_DESCRIPTOR_REFS: RefCell> = Default::default(); diff --git a/crates/rspack_plugin_split_chunks/src/options/cache_group_test.rs b/crates/rspack_plugin_split_chunks/src/options/cache_group_test.rs index f105d1aac26..47ffeefb969 100644 --- a/crates/rspack_plugin_split_chunks/src/options/cache_group_test.rs +++ b/crates/rspack_plugin_split_chunks/src/options/cache_group_test.rs @@ -1,9 +1,10 @@ use std::sync::Arc; -use rspack_core::Module; +use rspack_core::{Compilation, Module}; use rspack_error::Result; pub struct CacheGroupTestFnCtx<'a> { + pub compilation: &'a Compilation, pub module: &'a dyn Module, } diff --git a/crates/rspack_plugin_split_chunks/src/plugin/module_group.rs b/crates/rspack_plugin_split_chunks/src/plugin/module_group.rs index d7ba5b5bea3..707c2b99f4d 100644 --- a/crates/rspack_plugin_split_chunks/src/plugin/module_group.rs +++ b/crates/rspack_plugin_split_chunks/src/plugin/module_group.rs @@ -334,7 +334,7 @@ impl SplitChunksPlugin { .name_for_condition() .map_or(false, |name| regexp.test(&name)), CacheGroupTest::Fn(f) => { - let ctx = CacheGroupTestFnCtx { module }; + let ctx = CacheGroupTestFnCtx { compilation, module }; f(ctx)?.unwrap_or_default() } CacheGroupTest::Enabled => true, diff --git a/packages/rspack-test-tools/tests/configCases/split-chunks/cache-group-test/index.js b/packages/rspack-test-tools/tests/configCases/split-chunks/cache-group-test/index.js new file mode 100644 index 00000000000..7e9668e7834 --- /dev/null +++ b/packages/rspack-test-tools/tests/configCases/split-chunks/cache-group-test/index.js @@ -0,0 +1 @@ +"foo" \ No newline at end of file diff --git a/packages/rspack-test-tools/tests/configCases/split-chunks/cache-group-test/rspack.config.js b/packages/rspack-test-tools/tests/configCases/split-chunks/cache-group-test/rspack.config.js new file mode 100644 index 00000000000..781158e3e90 --- /dev/null +++ b/packages/rspack-test-tools/tests/configCases/split-chunks/cache-group-test/rspack.config.js @@ -0,0 +1,21 @@ +/** @type {import("@rspack/core").Configuration} */ +module.exports = { + entry: "./index.js", + target: "node", + output: { + filename: "[name].js" + }, + optimization: { + splitChunks: { + cacheGroups: { + common: { + test(module) { + expect(module.size()).toBe(5); + return true; + } + } + } + } + } + +}; diff --git a/packages/rspack/etc/api.md b/packages/rspack/etc/api.md index a5b31dca14e..7d1b8c24263 100644 --- a/packages/rspack/etc/api.md +++ b/packages/rspack/etc/api.md @@ -6075,6 +6075,8 @@ export class Module { // (undocumented) resource?: Readonly; // (undocumented) + size(type?: string): number; + // (undocumented) type: string; // (undocumented) userRequest?: Readonly; diff --git a/packages/rspack/src/Module.ts b/packages/rspack/src/Module.ts index c987ea25a61..5c474a7c9f4 100644 --- a/packages/rspack/src/Module.ts +++ b/packages/rspack/src/Module.ts @@ -131,6 +131,13 @@ export class Module { } return []; } + + size(type?: string): number { + if ("size" in this.#inner) { + return this.#inner.size(type); + } + return 0; + } } export class CodeGenerationResult {