diff --git a/crates/node_binding/binding.d.ts b/crates/node_binding/binding.d.ts index fca0c1f7b1c..b1bf342c1a0 100644 --- a/crates/node_binding/binding.d.ts +++ b/crates/node_binding/binding.d.ts @@ -131,6 +131,17 @@ export class JsContextModuleFactoryAfterResolveData { set recursive(recursive: boolean) } +export class JsContextModuleFactoryBeforeResolveData { + get context(): string + set context(context: string) + get request(): string + set request(request: string) + get regExp(): RawRegex | undefined + set regExp(rawRegExp: RawRegex | undefined) + get recursive(): boolean + set recursive(recursive: boolean) +} + export class JsEntries { clear(): void get size(): number @@ -475,13 +486,6 @@ export interface JsCompatSource { map?: string } -export interface JsContextModuleFactoryBeforeResolveData { - context: string - request: string - regExp?: RawRegex - recursive: boolean -} - export interface JsCreateData { request: string userRequest: string diff --git a/crates/node_binding/src/plugins/interceptor.rs b/crates/node_binding/src/plugins/interceptor.rs index f70d2b2c5de..a5b8dc81a32 100644 --- a/crates/node_binding/src/plugins/interceptor.rs +++ b/crates/node_binding/src/plugins/interceptor.rs @@ -16,7 +16,7 @@ use rspack_binding_values::{ JsAlterAssetTagGroupsData, JsAlterAssetTagsData, JsAssetEmittedArgs, JsBeforeAssetTagGenerationData, JsBeforeEmitData, JsBeforeResolveArgs, JsBeforeResolveOutput, JsChunk, JsChunkAssetArgs, JsCompilationWrapper, JsContextModuleFactoryAfterResolveDataWrapper, - JsContextModuleFactoryAfterResolveResult, JsContextModuleFactoryBeforeResolveData, + JsContextModuleFactoryAfterResolveResult, JsContextModuleFactoryBeforeResolveDataWrapper, JsContextModuleFactoryBeforeResolveResult, JsCreateData, JsExecuteModuleArg, JsFactorizeArgs, JsFactorizeOutput, JsModule, JsNormalModuleFactoryCreateModuleArgs, JsResolveArgs, JsResolveForSchemeArgs, JsResolveForSchemeOutput, JsResolveOutput, JsRuntimeGlobals, @@ -24,13 +24,13 @@ use rspack_binding_values::{ }; use rspack_collections::IdentifierSet; use rspack_core::{ - parse_resource, AfterResolveResult, AssetEmittedInfo, BeforeResolveData, BeforeResolveResult, - BoxModule, Chunk, ChunkUkey, CodeGenerationResults, Compilation, - CompilationAdditionalTreeRuntimeRequirements, CompilationAdditionalTreeRuntimeRequirementsHook, - CompilationAfterOptimizeModules, CompilationAfterOptimizeModulesHook, - CompilationAfterProcessAssets, CompilationAfterProcessAssetsHook, CompilationAfterSeal, - CompilationAfterSealHook, CompilationBuildModule, CompilationBuildModuleHook, - CompilationChunkAsset, CompilationChunkAssetHook, CompilationChunkHash, CompilationChunkHashHook, + parse_resource, AfterResolveResult, AssetEmittedInfo, BeforeResolveResult, BoxModule, Chunk, + ChunkUkey, CodeGenerationResults, Compilation, CompilationAdditionalTreeRuntimeRequirements, + CompilationAdditionalTreeRuntimeRequirementsHook, CompilationAfterOptimizeModules, + CompilationAfterOptimizeModulesHook, CompilationAfterProcessAssets, + CompilationAfterProcessAssetsHook, CompilationAfterSeal, CompilationAfterSealHook, + CompilationBuildModule, CompilationBuildModuleHook, CompilationChunkAsset, + CompilationChunkAssetHook, CompilationChunkHash, CompilationChunkHashHook, CompilationExecuteModule, CompilationExecuteModuleHook, CompilationFinishModules, CompilationFinishModulesHook, CompilationOptimizeChunkModules, CompilationOptimizeChunkModulesHook, CompilationOptimizeModules, CompilationOptimizeModulesHook, @@ -1507,34 +1507,14 @@ impl ContextModuleFactoryBeforeResolve for ContextModuleFactoryBeforeResolveTap async fn run(&self, result: BeforeResolveResult) -> rspack_error::Result { let js_result = match result { BeforeResolveResult::Ignored => JsContextModuleFactoryBeforeResolveResult::A(false), - BeforeResolveResult::Data(d) => { - let reg_exp = d.reg_exp.map(|js_regex| js_regex.into()); - JsContextModuleFactoryBeforeResolveResult::B(JsContextModuleFactoryBeforeResolveData { - context: d.context, - request: d.request, - reg_exp, - recursive: d.recursive, - }) - } + BeforeResolveResult::Data(data) => JsContextModuleFactoryBeforeResolveResult::B( + JsContextModuleFactoryBeforeResolveDataWrapper::new(data), + ), }; match self.function.call_with_promise(js_result).await { Ok(js_result) => match js_result { napi::bindgen_prelude::Either::A(_) => Ok(BeforeResolveResult::Ignored), - napi::bindgen_prelude::Either::B(d) => { - let reg_exp = match d.reg_exp { - Some(js_regex) => Some(js_regex.try_into()?), - None => None, - }; - let data = BeforeResolveData { - context: d.context, - request: d.request, - reg_exp, - recursive: d.recursive, - // TODO: fix - critical: true, - }; - Ok(BeforeResolveResult::Data(Box::new(data))) - } + napi::bindgen_prelude::Either::B(js_data) => Ok(BeforeResolveResult::Data(js_data.take())), }, Err(err) => Err(err), } diff --git a/crates/rspack_binding_values/src/context_module_factory.rs b/crates/rspack_binding_values/src/context_module_factory.rs index e1f97a3d202..a8e52868970 100644 --- a/crates/rspack_binding_values/src/context_module_factory.rs +++ b/crates/rspack_binding_values/src/context_module_factory.rs @@ -2,20 +2,113 @@ use napi::bindgen_prelude::{ ClassInstance, Either, FromNapiValue, ToNapiValue, TypeName, ValidateNapiValue, }; use napi_derive::napi; -use rspack_core::AfterResolveData; +use rspack_core::{AfterResolveData, BeforeResolveData}; use crate::RawRegex; -#[napi(object)] -pub struct JsContextModuleFactoryBeforeResolveData { - pub context: String, - pub request: String, - pub reg_exp: Option, - pub recursive: bool, +#[napi] +pub struct JsContextModuleFactoryBeforeResolveData(Box); + +#[napi] +impl JsContextModuleFactoryBeforeResolveData { + #[napi(getter)] + pub fn context(&self) -> &str { + &self.0.context + } + + #[napi(setter)] + pub fn set_context(&mut self, context: String) { + self.0.context = context; + } + + #[napi(getter)] + pub fn request(&self) -> &str { + &self.0.request + } + + #[napi(setter)] + pub fn set_request(&mut self, request: String) { + self.0.request = request; + } + + #[napi(getter)] + pub fn reg_exp(&self) -> Either { + match &self.0.reg_exp { + Some(r) => Either::A(r.clone().into()), + None => Either::B(()), + } + } + + #[napi(setter)] + pub fn set_reg_exp(&mut self, raw_reg_exp: Either) { + self.0.reg_exp = match raw_reg_exp { + Either::A(raw_reg_exp) => match raw_reg_exp.try_into() { + Ok(reg_exp) => Some(reg_exp), + Err(_) => None, + }, + Either::B(_) => None, + }; + } + + #[napi(getter)] + pub fn recursive(&self) -> bool { + self.0.recursive + } + + #[napi(setter)] + pub fn set_recursive(&mut self, recursive: bool) { + self.0.recursive = recursive; + } +} + +pub struct JsContextModuleFactoryBeforeResolveDataWrapper(Box); + +impl JsContextModuleFactoryBeforeResolveDataWrapper { + pub fn new(data: Box) -> Self { + Self(data) + } + + pub fn take(self) -> Box { + self.0 + } +} + +impl FromNapiValue for JsContextModuleFactoryBeforeResolveDataWrapper { + unsafe fn from_napi_value( + env: napi::sys::napi_env, + napi_val: napi::sys::napi_value, + ) -> napi::Result { + let instance = + as FromNapiValue>::from_napi_value( + env, napi_val, + )?; + Ok(Self(instance.0.clone())) + } } +impl ToNapiValue for JsContextModuleFactoryBeforeResolveDataWrapper { + unsafe fn to_napi_value( + env: napi::sys::napi_env, + val: Self, + ) -> napi::Result { + let js_val = JsContextModuleFactoryBeforeResolveData(val.0); + ToNapiValue::to_napi_value(env, js_val) + } +} + +impl TypeName for JsContextModuleFactoryBeforeResolveDataWrapper { + fn type_name() -> &'static str { + "JsContextModuleFactoryBeforeResolveData" + } + fn value_type() -> napi::ValueType { + napi::ValueType::Object + } +} + +impl ValidateNapiValue for JsContextModuleFactoryBeforeResolveDataWrapper {} + pub type JsContextModuleFactoryBeforeResolveResult = - Either; + Either; #[napi] pub struct JsContextModuleFactoryAfterResolveData(Box); @@ -103,9 +196,7 @@ impl FromNapiValue for JsContextModuleFactoryAfterResolveDataWrapper { as FromNapiValue>::from_napi_value( env, napi_val, )?; - Ok(JsContextModuleFactoryAfterResolveDataWrapper( - instance.0.clone(), - )) + Ok(Self(instance.0.clone())) } } diff --git a/crates/rspack_core/src/context_module_factory.rs b/crates/rspack_core/src/context_module_factory.rs index b74f82a83b0..a17ffe9f18b 100644 --- a/crates/rspack_core/src/context_module_factory.rs +++ b/crates/rspack_core/src/context_module_factory.rs @@ -6,6 +6,7 @@ use rspack_error::{error, miette::IntoDiagnostic, Result}; use rspack_hook::define_hook; use rspack_paths::{AssertUtf8, Utf8Path, Utf8PathBuf}; use rspack_regex::RspackRegex; +use swc_core::common::util::take::Take; use tracing::instrument; use crate::{ @@ -22,14 +23,14 @@ pub enum BeforeResolveResult { Data(Box), } -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct BeforeResolveData { // context_info // resolve_options pub context: String, pub request: String, // assertions - // dependencies + pub dependencies: Vec, // dependency_type // file_dependencies // missing_dependencies @@ -38,10 +39,6 @@ pub struct BeforeResolveData { // cacheable pub recursive: bool, pub reg_exp: Option, - // FIX: This field is used by ContextReplacementPlugin to ignore errors collected during the build phase of Context modules. - // In Webpack, the ContextModuleFactory's beforeResolve hook directly traverses dependencies in context and modifies the ContextDependency's critical field. - // Since Rspack currently has difficulty passing the dependencies field, an additional field is used to indicate whether to ignore the collected errors. - pub critical: bool, } #[derive(Clone)] @@ -55,7 +52,7 @@ pub enum AfterResolveResult { pub struct AfterResolveData { pub resource: Utf8PathBuf, pub context: String, - // dependencies + pub dependencies: Vec, // layer // resolve_options // file_dependencies: HashSet, @@ -74,11 +71,6 @@ pub struct AfterResolveData { // type_prefix: String, // category: String, // referenced_exports - - // FIX: This field is used by ContextReplacementPlugin to ignore errors collected during the build phase of Context modules. - // In Webpack, the ContextModuleFactory's beforeResolve hook directly traverses dependencies in context and modifies the ContextDependency's critical field. - // Since Rspack currently has difficulty passing the dependencies field, an additional field is used to indicate whether to ignore the collected errors. - pub critical: bool, #[derivative(Debug = "ignore")] pub resolve_dependencies: ResolveContextModuleDependencies, } @@ -110,12 +102,8 @@ impl ModuleFactory for ContextModuleFactory { BeforeResolveResult::Data(before_resolve_result) => { let (factorize_result, context_module_options) = self.resolve(data, before_resolve_result).await?; - if let Some(context_module_options) = context_module_options { - if let Some(factorize_result) = self - .after_resolve(context_module_options, &mut data.dependencies) - .await? - { + if let Some(factorize_result) = self.after_resolve(data, context_module_options).await? { return Ok(factorize_result); } } @@ -180,7 +168,7 @@ impl ContextModuleFactory { request: dependency.request().to_string(), recursive: dependency_options.recursive, reg_exp: dependency_options.reg_exp.clone(), - critical: true, + dependencies: data.dependencies.clone(), }; match self @@ -191,10 +179,9 @@ impl ContextModuleFactory { .await? { BeforeResolveResult::Ignored => Ok(BeforeResolveResult::Ignored), - BeforeResolveResult::Data(result) => { - if !result.critical { - *dependency.critical_mut() = None; - } + BeforeResolveResult::Data(mut result) => { + // The dependencies can be modified in the before resolve hook + data.dependencies = result.dependencies.take(); Ok(BeforeResolveResult::Data(result)) } } @@ -347,17 +334,17 @@ impl ContextModuleFactory { async fn after_resolve( &self, + data: &mut ModuleFactoryCreateData, mut context_module_options: ContextModuleOptions, - dependencies: &mut [BoxDependency], ) -> Result> { let context_options = &context_module_options.context_options; let after_resolve_data = AfterResolveData { resource: context_module_options.resource.clone(), context: context_options.context.clone(), + dependencies: data.dependencies.clone(), request: context_options.request.clone(), reg_exp: context_options.reg_exp.clone(), recursive: context_options.recursive, - critical: true, resolve_dependencies: self.resolve_dependencies.clone(), }; @@ -369,21 +356,19 @@ impl ContextModuleFactory { .await? { AfterResolveResult::Ignored => Ok(Some(ModuleFactoryResult::default())), - AfterResolveResult::Data(result) => { - context_module_options.resource = result.resource; - context_module_options.context_options.context = result.context; - context_module_options.context_options.reg_exp = result.reg_exp; - context_module_options.context_options.recursive = result.recursive; - - let dependency = dependencies[0] - .as_context_dependency_mut() - .expect("should be context dependency"); - if !result.critical { - *dependency.critical_mut() = None; - } - - let module = - ContextModule::new(result.resolve_dependencies, context_module_options.clone()); + AfterResolveResult::Data(mut after_resolve_data) => { + // The dependencies can be modified in the after resolve hook + data.dependencies = after_resolve_data.dependencies.take(); + + context_module_options.resource = after_resolve_data.resource; + context_module_options.context_options.context = after_resolve_data.context; + context_module_options.context_options.reg_exp = after_resolve_data.reg_exp; + context_module_options.context_options.recursive = after_resolve_data.recursive; + + let module = ContextModule::new( + after_resolve_data.resolve_dependencies, + context_module_options.clone(), + ); Ok(Some(ModuleFactoryResult::new_with_module(Box::new(module)))) } diff --git a/crates/rspack_plugin_context_replacement/src/lib.rs b/crates/rspack_plugin_context_replacement/src/lib.rs index 88403ac490a..8e781765f2f 100644 --- a/crates/rspack_plugin_context_replacement/src/lib.rs +++ b/crates/rspack_plugin_context_replacement/src/lib.rs @@ -58,12 +58,13 @@ async fn cmf_before_resolve(&self, mut result: BeforeResolveResult) -> Result Result { const data = bindingData - ? ({ - resource: bindingData.resource, - regExp: bindingData.regExp - ? new RegExp( - bindingData.regExp.source, - bindingData.regExp.flags - ) - : undefined, - request: bindingData.request, - context: bindingData.context, - recursive: bindingData.recursive, - // TODO: Dependencies are not fully supported yet; this is a placeholder to prevent errors in moment-locales-webpack-plugin. - dependencies: [] - } satisfies ContextModuleFactoryAfterResolveResult) + ? ContextModuleFactoryAfterResolveData.__from_binding( + bindingData + ) : false; const ret = await queried.promise(data); const result = ret - ? ({ - resource: ret.resource, - context: ret.context, - request: ret.request, - regExp: ret.regExp - ? { - source: ret.regExp.source, - flags: ret.regExp.flags - } - : undefined, - recursive: ret.recursive - } satisfies binding.JsContextModuleFactoryAfterResolveData) + ? ContextModuleFactoryAfterResolveData.__to_binding(ret) : false; return result; } diff --git a/packages/rspack/src/Module.ts b/packages/rspack/src/Module.ts index 80726af1f66..a83e3dbf2ec 100644 --- a/packages/rspack/src/Module.ts +++ b/packages/rspack/src/Module.ts @@ -1,5 +1,6 @@ import type { JsCodegenerationResult, + JsContextModuleFactoryAfterResolveData, JsCreateData, JsFactoryMeta, JsModule, @@ -9,6 +10,7 @@ import type { Source } from "webpack-sources"; import type { Compilation } from "./Compilation"; import { DependenciesBlock } from "./DependenciesBlock"; +import type { Dependency } from "./Dependency"; import { JsSource } from "./util/source"; export type ResourceData = { @@ -41,16 +43,81 @@ export type ContextModuleFactoryBeforeResolveResult = request?: string; }; +export class ContextModuleFactoryAfterResolveData { + #inner: JsContextModuleFactoryAfterResolveData; + + static __from_binding(binding: JsContextModuleFactoryAfterResolveData) { + return new ContextModuleFactoryAfterResolveData(binding); + } + + static __to_binding(data: ContextModuleFactoryAfterResolveData) { + return data.#inner; + } + + constructor(data: JsContextModuleFactoryAfterResolveData) { + this.#inner = data; + } + + get resource(): string { + return this.#inner.resource; + } + + set resource(val: string) { + this.#inner.resource = val; + } + + get context(): string { + return this.#inner.context; + } + + set context(val: string) { + this.#inner.context = val; + } + + get request(): string { + return this.#inner.request; + } + + set request(val: string) { + this.#inner.request = val; + } + + get regExp(): RegExp | undefined { + if (!this.#inner.regExp) { + return undefined; + } + const { source, flags } = this.#inner.regExp; + return new RegExp(source, flags); + } + + set regExp(val: RegExp | undefined) { + if (!val) { + this.#inner.regExp = undefined; + return; + } + this.#inner.regExp = { + source: val.source, + flags: val.flags + }; + } + + get recursive(): boolean { + return this.#inner.recursive; + } + + set recursive(val: boolean) { + this.#inner.recursive = val; + } + + get dependencies(): Dependency[] { + // TODO: Dependencies are not fully supported yet; this is a placeholder to prevent errors in moment-locales-webpack-plugin. + return []; + } +} + export type ContextModuleFactoryAfterResolveResult = | false - | { - resource: string; - context: string; - request: string; - regExp?: RegExp; - recursive: boolean; - dependencies: Array; - }; + | ContextModuleFactoryAfterResolveData; export class Module { #inner: JsModule | ModuleDTO;