diff --git a/crates/node_binding/binding.d.ts b/crates/node_binding/binding.d.ts index 81c8b31ceed1..d2711d063ee8 100644 --- a/crates/node_binding/binding.d.ts +++ b/crates/node_binding/binding.d.ts @@ -147,6 +147,7 @@ export const enum BuiltinPluginName { ContainerPlugin = 'ContainerPlugin', ContainerReferencePlugin = 'ContainerReferencePlugin', ModuleFederationRuntimePlugin = 'ModuleFederationRuntimePlugin', + ProvideSharedPlugin = 'ProvideSharedPlugin', HttpExternalsRspackPlugin = 'HttpExternalsRspackPlugin', CopyRspackPlugin = 'CopyRspackPlugin', HtmlRspackPlugin = 'HtmlRspackPlugin', @@ -1009,6 +1010,14 @@ export interface RawProgressPluginOptions { profile: boolean } +export interface RawProvideOptions { + key: string + shareKey: string + shareScope: string + version?: string | false | undefined + eager: boolean +} + export interface RawReactOptions { runtime?: "automatic" | "classic" importSource?: 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 f1ed6b9934c4..50a0232d4c90 100644 --- a/crates/rspack_binding_options/src/options/raw_builtins/mod.rs +++ b/crates/rspack_binding_options/src/options/raw_builtins/mod.rs @@ -16,6 +16,7 @@ use rspack_core::{ mf::{ container_plugin::ContainerPlugin, container_reference_plugin::ContainerReferencePlugin, module_federation_runtime_plugin::ModuleFederationRuntimePlugin, + provide_shared_plugin::ProvideSharedPlugin, }, BoxPlugin, Define, DefinePlugin, PluginExt, Provide, ProvidePlugin, }; @@ -42,7 +43,7 @@ use rspack_plugin_swc_js_minimizer::SwcJsMinimizerRspackPlugin; use rspack_plugin_wasm::enable_wasm_loading_plugin; use rspack_plugin_web_worker_template::web_worker_template_plugin; -use self::raw_mf::RawContainerReferencePluginOptions; +use self::raw_mf::{RawContainerReferencePluginOptions, RawProvideOptions}; pub use self::{ raw_banner::RawBannerPluginOptions, raw_copy::RawCopyRspackPluginOptions, raw_html::RawHtmlRspackPluginOptions, raw_limit_chunk_count::RawLimitChunkCountPluginOptions, @@ -81,6 +82,7 @@ pub enum BuiltinPluginName { ContainerPlugin, ContainerReferencePlugin, ModuleFederationRuntimePlugin, + ProvideSharedPlugin, // rspack specific plugins HttpExternalsRspackPlugin, @@ -214,6 +216,14 @@ impl RawOptionsApply for BuiltinPlugin { BuiltinPluginName::ModuleFederationRuntimePlugin => { plugins.push(ModuleFederationRuntimePlugin::default().boxed()) } + BuiltinPluginName::ProvideSharedPlugin => { + let mut provides: Vec<_> = downcast_into::>(self.options)? + .into_iter() + .map(Into::into) + .collect(); + provides.sort_unstable_by_key(|(k, _)| k.to_string()); + plugins.push(ProvideSharedPlugin::new(provides).boxed()) + } // rspack specific plugins BuiltinPluginName::HttpExternalsRspackPlugin => { diff --git a/crates/rspack_binding_options/src/options/raw_builtins/raw_mf.rs b/crates/rspack_binding_options/src/options/raw_builtins/raw_mf.rs index da4b72ed654e..97aa03c17ff4 100644 --- a/crates/rspack_binding_options/src/options/raw_builtins/raw_mf.rs +++ b/crates/rspack_binding_options/src/options/raw_builtins/raw_mf.rs @@ -1,7 +1,9 @@ +use napi::Either; use napi_derive::napi; use rspack_core::mf::{ container_plugin::{ContainerPluginOptions, ExposeOptions}, container_reference_plugin::{ContainerReferencePluginOptions, RemoteOptions}, + provide_shared_plugin::{ProvideOptions, ProvideVersion}, }; use crate::RawLibraryOptions; @@ -87,3 +89,41 @@ impl From for (String, RemoteOptions) { ) } } + +#[derive(Debug)] +#[napi(object)] +pub struct RawProvideOptions { + pub key: String, + pub share_key: String, + pub share_scope: String, + #[napi(ts_type = "string | false | undefined")] + pub version: Option, + pub eager: bool, +} + +impl From for (String, ProvideOptions) { + fn from(value: RawProvideOptions) -> Self { + ( + value.key, + ProvideOptions { + share_key: value.share_key, + share_scope: value.share_scope, + version: value.version.map(|v| RawProvideVersionWrapper(v).into()), + eager: value.eager, + }, + ) + } +} + +pub type RawProvideVersion = Either; + +struct RawProvideVersionWrapper(RawProvideVersion); + +impl From for ProvideVersion { + fn from(value: RawProvideVersionWrapper) -> Self { + match value.0 { + Either::A(s) => ProvideVersion::Version(s), + Either::B(_) => ProvideVersion::False, + } + } +} diff --git a/crates/rspack_core/src/build_chunk_graph/code_splitter.rs b/crates/rspack_core/src/build_chunk_graph/code_splitter.rs index 65ad727baa56..333cd0d09548 100644 --- a/crates/rspack_core/src/build_chunk_graph/code_splitter.rs +++ b/crates/rspack_core/src/build_chunk_graph/code_splitter.rs @@ -1,4 +1,5 @@ use anyhow::anyhow; +use itertools::Itertools; use rspack_error::{internal_error, Result}; use rustc_hash::{FxHashMap as HashMap, FxHashSet as HashSet}; @@ -145,6 +146,33 @@ impl<'me> CodeSplitter<'me> { entrypoint.ukey, ); } + + let global_included_modules = compilation + .global_entry + .include_dependencies + .iter() + .filter_map(|dep| { + compilation + .module_graph + .module_identifier_by_dependency_id(dep) + }) + .copied() + .sorted_unstable(); + let included_modules = entry_data + .include_dependencies + .iter() + .filter_map(|dep| { + compilation + .module_graph + .module_identifier_by_dependency_id(dep) + }) + .copied() + .sorted_unstable(); + let included_modules = global_included_modules.chain(included_modules); + input_entrypoints_and_modules + .entry(entrypoint.ukey) + .or_default() + .extend(included_modules); } let mut runtime_chunks = HashSet::default(); diff --git a/crates/rspack_core/src/compiler/compilation.rs b/crates/rspack_core/src/compiler/compilation.rs index c403518c79ac..6572dd33293a 100644 --- a/crates/rspack_core/src/compiler/compilation.rs +++ b/crates/rspack_core/src/compiler/compilation.rs @@ -182,6 +182,7 @@ impl Compilation { } else { let data = EntryData { dependencies: vec![entry_id], + include_dependencies: vec![], options, }; self.entries.insert(name, data); @@ -191,6 +192,30 @@ impl Compilation { } } + pub async fn add_include(&mut self, entry: BoxDependency, options: EntryOptions) -> Result<()> { + let entry_id = *entry.id(); + self.module_graph.add_dependency(entry); + if let Some(name) = options.name.clone() { + if let Some(data) = self.entries.get_mut(&name) { + data.include_dependencies.push(entry_id); + } else { + let data = EntryData { + dependencies: vec![], + include_dependencies: vec![entry_id], + options, + }; + self.entries.insert(name, data); + } + } else { + self.global_entry.include_dependencies.push(entry_id); + } + self + .update_module_graph(vec![MakeParam::ForceBuildDeps(HashSet::from_iter([( + entry_id, None, + )]))]) + .await + } + pub fn update_asset( &mut self, filename: &str, diff --git a/crates/rspack_core/src/compiler/queue.rs b/crates/rspack_core/src/compiler/queue.rs index d5f371496886..fccb894151b0 100644 --- a/crates/rspack_core/src/compiler/queue.rs +++ b/crates/rspack_core/src/compiler/queue.rs @@ -110,6 +110,17 @@ impl WorkerTask for FactorizeTask { .await? .split_into_parts() } + DependencyType::ProvideSharedModule => { + let factory = crate::mf::provide_shared_module_factory::ProvideSharedModuleFactory; + factory + .create(ModuleFactoryCreateData { + resolve_options: self.resolve_options, + context, + dependency, + }) + .await? + .split_into_parts() + } _ => { assert!(dependency.as_context_dependency().is_none()); let factory = NormalModuleFactory::new( diff --git a/crates/rspack_core/src/dependency/dependency_type.rs b/crates/rspack_core/src/dependency/dependency_type.rs index 90b9c5ba2842..3d408157e447 100644 --- a/crates/rspack_core/src/dependency/dependency_type.rs +++ b/crates/rspack_core/src/dependency/dependency_type.rs @@ -68,6 +68,10 @@ pub enum DependencyType { ContainerEntry, /// remote to external, RemoteToExternal, + /// provide shared module + ProvideSharedModule, + /// provide module for shared + ProvideModuleForShared, Custom(Box), // TODO it will increase large layout size } @@ -110,6 +114,8 @@ impl DependencyType { DependencyType::ContainerExposed => Cow::Borrowed("container exposed"), DependencyType::ContainerEntry => Cow::Borrowed("container entry"), DependencyType::RemoteToExternal => Cow::Borrowed("remote to external"), + DependencyType::ProvideSharedModule => Cow::Borrowed("provide shared module"), + DependencyType::ProvideModuleForShared => Cow::Borrowed("provide module for shared"), } } } diff --git a/crates/rspack_core/src/dependency/runtime_template.rs b/crates/rspack_core/src/dependency/runtime_template.rs index 6e3a4ff7f944..d9ee7b116a58 100644 --- a/crates/rspack_core/src/dependency/runtime_template.rs +++ b/crates/rspack_core/src/dependency/runtime_template.rs @@ -1,9 +1,10 @@ use swc_core::ecma::atoms::JsWord; use crate::{ - get_import_var, property_access, AsyncDependenciesBlockIdentifier, Compilation, DependencyId, - ExportsType, FakeNamespaceObjectMode, InitFragmentExt, InitFragmentKey, InitFragmentStage, - ModuleGraph, ModuleIdentifier, NormalInitFragment, RuntimeGlobals, TemplateContext, + get_import_var, property_access, AsyncDependenciesBlockIdentifier, Compilation, + DependenciesBlock, DependencyId, ExportsType, FakeNamespaceObjectMode, InitFragmentExt, + InitFragmentKey, InitFragmentStage, ModuleGraph, ModuleIdentifier, NormalInitFragment, + RuntimeGlobals, TemplateContext, }; pub fn export_from_import( @@ -364,3 +365,42 @@ pub fn returning_function(return_value: &str, args: &str) -> String { pub fn basic_function(args: &str, body: &str) -> String { format!("function({args}) {{\n{body}\n}}") } + +pub fn sync_module_factory( + dep: &DependencyId, + request: &str, + compilation: &Compilation, + runtime_requirements: &mut RuntimeGlobals, +) -> String { + let factory = returning_function( + &module_raw(compilation, runtime_requirements, dep, request, false), + "", + ); + returning_function(&factory, "") +} + +pub fn async_module_factory( + block_id: &AsyncDependenciesBlockIdentifier, + request: &str, + compilation: &Compilation, + runtime_requirements: &mut RuntimeGlobals, +) -> String { + let block = compilation + .module_graph + .block_by_id(block_id) + .expect("should have block"); + let dep = block.get_dependencies()[0]; + let ensure_chunk = block_promise(Some(block_id), runtime_requirements, compilation); + let factory = returning_function( + &module_raw(compilation, runtime_requirements, &dep, request, false), + "", + ); + returning_function( + &if ensure_chunk.starts_with("Promise.resolve(") { + factory + } else { + format!("{ensure_chunk}.then({})", returning_function(&factory, "")) + }, + "", + ) +} diff --git a/crates/rspack_core/src/lib.rs b/crates/rspack_core/src/lib.rs index 4de85f20ff40..346d5ff8de40 100644 --- a/crates/rspack_core/src/lib.rs +++ b/crates/rspack_core/src/lib.rs @@ -138,6 +138,7 @@ pub enum ModuleType { Asset, Runtime, Remote, + Provide, } impl ModuleType { @@ -223,6 +224,7 @@ impl ModuleType { ModuleType::AssetInline => "asset/inline", ModuleType::Runtime => "runtime", ModuleType::Remote => "remote-module", + ModuleType::Provide => "provide-module", } } } diff --git a/crates/rspack_core/src/mf/container/remote_module.rs b/crates/rspack_core/src/mf/container/remote_module.rs index 43c3b7164f1b..6fb0715805d5 100644 --- a/crates/rspack_core/src/mf/container/remote_module.rs +++ b/crates/rspack_core/src/mf/container/remote_module.rs @@ -9,7 +9,7 @@ use rspack_sources::{RawSource, Source, SourceExt}; use super::remote_to_external_dependency::RemoteToExternalDependency; use crate::{ - mf::share_runtime_module::{CodeGenerationDataShareInit, DataInit, ShareInitData}, + mf::share_runtime_module::{CodeGenerationDataShareInit, ShareInitData}, AsyncDependenciesBlockIdentifier, BoxDependency, BuildContext, BuildInfo, BuildResult, CodeGenerationResult, Compilation, Context, DependenciesBlock, DependencyId, LibIdentOptions, Module, ModuleIdentifier, ModuleType, RuntimeSpec, SourceType, @@ -160,7 +160,10 @@ impl Module for RemoteModule { items: vec![ShareInitData { share_scope: self.share_scope.clone(), init_stage: 20, - init: DataInit::ExternalModuleId(id.map(|id| id.to_string())), + init: format!( + "initExternal({});", + serde_json::to_string(&id).expect("module_id should able to json to_string") + ), }], }); Ok(codegen) diff --git a/crates/rspack_core/src/mf/sharing/mod.rs b/crates/rspack_core/src/mf/sharing/mod.rs index fd36b7720385..44f8ad87112b 100644 --- a/crates/rspack_core/src/mf/sharing/mod.rs +++ b/crates/rspack_core/src/mf/sharing/mod.rs @@ -1 +1,6 @@ +pub mod provide_for_shared_dependency; +pub mod provide_shared_dependency; +pub mod provide_shared_module; +pub mod provide_shared_module_factory; +pub mod provide_shared_plugin; pub mod share_runtime_module; diff --git a/crates/rspack_core/src/mf/sharing/provide_for_shared_dependency.rs b/crates/rspack_core/src/mf/sharing/provide_for_shared_dependency.rs new file mode 100644 index 000000000000..2c0f63d10b94 --- /dev/null +++ b/crates/rspack_core/src/mf/sharing/provide_for_shared_dependency.rs @@ -0,0 +1,46 @@ +use crate::{ + AsContextDependency, AsDependencyTemplate, Dependency, DependencyCategory, DependencyId, + DependencyType, ModuleDependency, +}; + +#[derive(Debug, Clone)] +pub struct ProvideForSharedDependency { + id: DependencyId, + request: String, +} + +impl ProvideForSharedDependency { + pub fn new(request: String) -> Self { + Self { + id: DependencyId::new(), + request, + } + } +} + +impl Dependency for ProvideForSharedDependency { + fn dependency_debug_name(&self) -> &'static str { + "ProvideForSharedDependency" + } + + fn id(&self) -> &DependencyId { + &self.id + } + + fn dependency_type(&self) -> &DependencyType { + &DependencyType::ProvideModuleForShared + } + + fn category(&self) -> &DependencyCategory { + &DependencyCategory::Esm + } +} + +impl ModuleDependency for ProvideForSharedDependency { + fn request(&self) -> &str { + &self.request + } +} + +impl AsContextDependency for ProvideForSharedDependency {} +impl AsDependencyTemplate for ProvideForSharedDependency {} diff --git a/crates/rspack_core/src/mf/sharing/provide_shared_dependency.rs b/crates/rspack_core/src/mf/sharing/provide_shared_dependency.rs new file mode 100644 index 000000000000..4d8e651504f3 --- /dev/null +++ b/crates/rspack_core/src/mf/sharing/provide_shared_dependency.rs @@ -0,0 +1,75 @@ +use super::provide_shared_plugin::ProvideVersion; +use crate::{ + AsContextDependency, AsDependencyTemplate, Dependency, DependencyCategory, DependencyId, + DependencyType, ModuleDependency, +}; + +#[derive(Debug, Clone)] +pub struct ProvideSharedDependency { + id: DependencyId, + request: String, + pub share_scope: String, + pub name: String, + pub version: ProvideVersion, + pub eager: bool, + resource_identifier: String, +} + +impl ProvideSharedDependency { + pub fn new( + share_scope: String, + name: String, + version: ProvideVersion, + request: String, + eager: bool, + ) -> Self { + let resource_identifier = format!( + "provide module ({}) {} as {} @ {} {}", + &share_scope, + &request, + &name, + &version, + eager.then_some("eager").unwrap_or_default() + ); + Self { + id: DependencyId::new(), + request, + share_scope, + name, + version, + eager, + resource_identifier, + } + } +} + +impl Dependency for ProvideSharedDependency { + fn dependency_debug_name(&self) -> &'static str { + "ProvideSharedDependency" + } + + fn id(&self) -> &DependencyId { + &self.id + } + + fn dependency_type(&self) -> &DependencyType { + &DependencyType::ProvideSharedModule + } + + fn category(&self) -> &DependencyCategory { + &DependencyCategory::Esm + } + + fn resource_identifier(&self) -> Option<&str> { + Some(&self.resource_identifier) + } +} + +impl ModuleDependency for ProvideSharedDependency { + fn request(&self) -> &str { + &self.request + } +} + +impl AsContextDependency for ProvideSharedDependency {} +impl AsDependencyTemplate for ProvideSharedDependency {} diff --git a/crates/rspack_core/src/mf/sharing/provide_shared_module.rs b/crates/rspack_core/src/mf/sharing/provide_shared_module.rs new file mode 100644 index 000000000000..9ad7ffee39e0 --- /dev/null +++ b/crates/rspack_core/src/mf/sharing/provide_shared_module.rs @@ -0,0 +1,206 @@ +use std::{borrow::Cow, hash::Hash}; + +use async_trait::async_trait; +use rspack_error::{IntoTWithDiagnosticArray, Result, TWithDiagnosticArray}; +use rspack_hash::RspackHash; +use rspack_identifier::{Identifiable, Identifier}; +use rspack_sources::Source; + +use super::{ + provide_for_shared_dependency::ProvideForSharedDependency, + provide_shared_plugin::ProvideVersion, + share_runtime_module::{CodeGenerationDataShareInit, ShareInitData}, +}; +use crate::{ + async_module_factory, sync_module_factory, AsyncDependenciesBlock, + AsyncDependenciesBlockIdentifier, BoxDependency, BuildContext, BuildInfo, BuildResult, + CodeGenerationResult, Compilation, Context, DependenciesBlock, DependencyId, LibIdentOptions, + Module, ModuleIdentifier, ModuleType, RuntimeGlobals, RuntimeSpec, SourceType, +}; + +#[derive(Debug)] +pub struct ProvideSharedModule { + blocks: Vec, + dependencies: Vec, + identifier: ModuleIdentifier, + lib_ident: String, + readable_identifier: String, + name: String, + share_scope: String, + version: ProvideVersion, + request: String, + eager: bool, +} + +impl ProvideSharedModule { + pub fn new( + share_scope: String, + name: String, + version: ProvideVersion, + request: String, + eager: bool, + ) -> Self { + Self { + blocks: Vec::new(), + dependencies: Vec::new(), + identifier: ModuleIdentifier::from(format!( + "provide shared module ({}) {}@{} = {}", + &share_scope, &name, &version, &request + )), + lib_ident: format!("webpack/sharing/provide/{}/{}", &share_scope, &name), + readable_identifier: format!( + "provide shared module ({}) {}@{} = {}", + &share_scope, &name, &version, &request + ), + name, + share_scope, + version, + request, + eager, + } + } +} + +impl Identifiable for ProvideSharedModule { + fn identifier(&self) -> Identifier { + self.identifier + } +} + +impl DependenciesBlock for ProvideSharedModule { + fn add_block_id(&mut self, block: AsyncDependenciesBlockIdentifier) { + self.blocks.push(block) + } + + fn get_blocks(&self) -> &[AsyncDependenciesBlockIdentifier] { + &self.blocks + } + + fn add_dependency_id(&mut self, dependency: DependencyId) { + self.dependencies.push(dependency) + } + + fn get_dependencies(&self) -> &[DependencyId] { + &self.dependencies + } +} + +#[async_trait] +impl Module for ProvideSharedModule { + fn size(&self, _source_type: &SourceType) -> f64 { + 42.0 + } + + fn module_type(&self) -> &ModuleType { + &ModuleType::Provide + } + + fn source_types(&self) -> &[SourceType] { + &[SourceType::ShareInit] + } + + fn original_source(&self) -> Option<&dyn Source> { + None + } + + fn readable_identifier(&self, _context: &Context) -> Cow { + self.readable_identifier.as_str().into() + } + + fn lib_ident(&self, _options: LibIdentOptions) -> Option> { + Some(self.lib_ident.as_str().into()) + } + + async fn build( + &mut self, + build_context: BuildContext<'_>, + ) -> Result> { + let mut hasher = RspackHash::from(&build_context.compiler_options.output); + self.update_hash(&mut hasher); + let hash = hasher.digest(&build_context.compiler_options.output.hash_digest); + + let mut blocks = vec![]; + let mut dependencies = vec![]; + let dep = Box::new(ProvideForSharedDependency::new(self.request.clone())); + if self.eager { + dependencies.push(dep as BoxDependency); + } else { + let mut block = AsyncDependenciesBlock::new(self.identifier, ""); + block.add_dependency(dep); + blocks.push(block); + } + + Ok( + BuildResult { + build_info: BuildInfo { + hash: Some(hash), + strict: true, + ..Default::default() + }, + build_meta: Default::default(), + dependencies, + blocks, + ..Default::default() + } + .with_empty_diagnostic(), + ) + } + + #[allow(clippy::unwrap_in_result)] + fn code_generation( + &self, + compilation: &Compilation, + _runtime: Option<&RuntimeSpec>, + ) -> Result { + let mut code_generation_result = CodeGenerationResult::default(); + code_generation_result + .runtime_requirements + .insert(RuntimeGlobals::INITIALIZE_SHARING); + let init = format!( + "register({}, {}, {}{})", + serde_json::to_string(&self.name).expect("ProvideSharedModule name should able to json to_string"), + serde_json::to_string(&self.version.to_string()).expect("ProvideVersion::Version should able to json to_string in ProvideSharedModule::code_generation"), + if self.eager { + sync_module_factory( + &self.get_dependencies()[0], + &self.request, + compilation, + &mut code_generation_result.runtime_requirements, + ) + } else { + async_module_factory( + &self.get_blocks()[0], + &self.request, + compilation, + &mut code_generation_result.runtime_requirements, + ) + }, + if self.eager { ", 1" } else { "" }, + ); + code_generation_result + .data + .insert(CodeGenerationDataShareInit { + items: vec![ShareInitData { + share_scope: self.share_scope.clone(), + init_stage: 10, + init, + }], + }); + Ok(code_generation_result) + } +} + +impl Hash for ProvideSharedModule { + fn hash(&self, state: &mut H) { + "__rspack_internal__ProvideSharedModule".hash(state); + self.identifier().hash(state); + } +} + +impl PartialEq for ProvideSharedModule { + fn eq(&self, other: &Self) -> bool { + self.identifier() == other.identifier() + } +} + +impl Eq for ProvideSharedModule {} diff --git a/crates/rspack_core/src/mf/sharing/provide_shared_module_factory.rs b/crates/rspack_core/src/mf/sharing/provide_shared_module_factory.rs new file mode 100644 index 000000000000..d5ff76a60554 --- /dev/null +++ b/crates/rspack_core/src/mf/sharing/provide_shared_module_factory.rs @@ -0,0 +1,38 @@ +// TODO: move to rspack_plugin_mf + +use async_trait::async_trait; +use rspack_error::{internal_error, IntoTWithDiagnosticArray, Result, TWithDiagnosticArray}; + +use super::{ + provide_shared_dependency::ProvideSharedDependency, provide_shared_module::ProvideSharedModule, +}; +use crate::{ModuleDependency, ModuleFactory, ModuleFactoryCreateData, ModuleFactoryResult}; + +pub struct ProvideSharedModuleFactory; + +#[async_trait] +impl ModuleFactory for ProvideSharedModuleFactory { + async fn create( + mut self, + data: ModuleFactoryCreateData, + ) -> Result> { + let dep = data + .dependency + .downcast_ref::() + .ok_or_else(|| { + internal_error!( + "dependency of ProvideSharedModuleFactory should be ProvideSharedDependency" + ) + })?; + Ok( + ModuleFactoryResult::new(Box::new(ProvideSharedModule::new( + dep.share_scope.clone(), + dep.name.clone(), + dep.version.clone(), + dep.request().to_owned(), + dep.eager, + ))) + .with_empty_diagnostic(), + ) + } +} diff --git a/crates/rspack_core/src/mf/sharing/provide_shared_plugin.rs b/crates/rspack_core/src/mf/sharing/provide_shared_plugin.rs new file mode 100644 index 000000000000..6bc7339c8d17 --- /dev/null +++ b/crates/rspack_core/src/mf/sharing/provide_shared_plugin.rs @@ -0,0 +1,226 @@ +use std::fmt; + +use async_trait::async_trait; +use once_cell::sync::Lazy; +use regex::Regex; +use rspack_error::{Error, InternalError, Result, Severity}; +use rspack_loader_runner::ResourceData; +use rustc_hash::FxHashMap; +use tokio::sync::RwLock; + +use super::provide_shared_dependency::ProvideSharedDependency; +use crate::{ + BoxModule, Compilation, CompilationArgs, EntryOptions, NormalModuleCreateData, Plugin, + PluginCompilationHookOutput, PluginContext, PluginNormalModuleFactoryModuleHookOutput, +}; + +static RELATIVE_REQUEST: Lazy = + Lazy::new(|| Regex::new(r"^(\/|[A-Za-z]:\\|\\\\|\.\.?(\/|$))").expect("Invalid regex")); +static ABSOLUTE_REQUEST: Lazy = + Lazy::new(|| Regex::new(r"^(\/|[A-Za-z]:\\|\\\\)").expect("Invalid regex")); + +#[derive(Debug, Clone)] +pub struct ProvideOptions { + pub share_key: String, + pub share_scope: String, + pub version: Option, + pub eager: bool, +} + +#[derive(Debug, Clone)] +pub struct VersionedProvideOptions { + pub share_key: String, + pub share_scope: String, + pub version: ProvideVersion, + pub eager: bool, +} + +impl ProvideOptions { + fn to_versioned(&self) -> VersionedProvideOptions { + VersionedProvideOptions { + share_key: self.share_key.clone(), + share_scope: self.share_scope.clone(), + version: self.version.clone().unwrap_or_default(), + eager: self.eager, + } + } +} + +#[derive(Debug, Default, Clone)] +pub enum ProvideVersion { + Version(String), + #[default] + False, +} + +impl fmt::Display for ProvideVersion { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + ProvideVersion::Version(v) => write!(f, "{}", v), + ProvideVersion::False => write!(f, "0"), + } + } +} + +#[derive(Debug)] +pub struct ProvideSharedPlugin { + provides: Vec<(String, ProvideOptions)>, + resolved_provide_map: RwLock>, + match_provides: RwLock>, + prefix_match_provides: RwLock>, +} + +impl ProvideSharedPlugin { + pub fn new(provides: Vec<(String, ProvideOptions)>) -> Self { + Self { + provides, + resolved_provide_map: Default::default(), + match_provides: Default::default(), + prefix_match_provides: Default::default(), + } + } + + #[allow(clippy::too_many_arguments)] + pub async fn provide_shared_module( + &self, + key: &str, + share_key: &str, + share_scope: &str, + version: Option<&ProvideVersion>, + eager: bool, + resource: &str, + resource_data: &ResourceData, + ) -> Result<()> { + let error_header = "No version specified and unable to automatically determine one."; + if let Some(version) = version { + self.resolved_provide_map.write().await.insert( + resource.to_string(), + VersionedProvideOptions { + share_key: share_key.to_string(), + share_scope: share_scope.to_string(), + version: version.to_owned(), + eager, + }, + ); + } else if let Some(description) = &resource_data.resource_description { + if let Some(description) = description.json().as_object() + && let Some(version) = description.get("version") + && let Some(version) = version.as_str() + { + self.resolved_provide_map.write().await.insert( + resource.to_string(), + VersionedProvideOptions { + share_key: share_key.to_string(), + share_scope: share_scope.to_string(), + version: ProvideVersion::Version(version.to_string()), + eager, + }, + ); + } else { + return Err(Error::InternalError(InternalError::new(format!("{error_header} No version in description file (usually package.json). Add version to description file {}, or manually specify version in shared config. shared module {key} -> {resource}", description.path().display()), Severity::Warn))); + } + } else { + return Err(Error::InternalError(InternalError::new(format!("{error_header} No description file (usually package.json) found. Add description file with name and version, or manually specify version in shared config. shared module {key} -> {resource}"), Severity::Warn))); + } + Ok(()) + } +} + +#[async_trait] +impl Plugin for ProvideSharedPlugin { + fn name(&self) -> &'static str { + "rspack.ProvideSharedPlugin" + } + + async fn compilation(&self, _args: CompilationArgs<'_>) -> PluginCompilationHookOutput { + let mut resolved_provide_map = self.resolved_provide_map.write().await; + let mut match_provides = self.match_provides.write().await; + let mut prefix_match_provides = self.prefix_match_provides.write().await; + for (request, config) in &self.provides { + if RELATIVE_REQUEST.is_match(request) || ABSOLUTE_REQUEST.is_match(request) { + resolved_provide_map.insert(request.to_string(), config.to_versioned()); + } else if request.ends_with('/') { + prefix_match_provides.insert(request.to_string(), config.clone()); + } else { + match_provides.insert(request.to_string(), config.clone()); + } + } + Ok(()) + } + + async fn normal_module_factory_module( + &self, + _ctx: PluginContext, + module: BoxModule, + args: &NormalModuleCreateData, + ) -> PluginNormalModuleFactoryModuleHookOutput { + let resource = &args.resource_resolve_data.resource; + let resource_data = &args.resource_resolve_data; + if self + .resolved_provide_map + .read() + .await + .contains_key(resource) + { + return Ok(module); + } + let request = args.resolve_data_request; + { + let match_provides = self.match_provides.read().await; + if let Some(config) = match_provides.get(request) { + self + .provide_shared_module( + request, + &config.share_key, + &config.share_scope, + config.version.as_ref(), + config.eager, + resource, + resource_data, + ) + .await?; + } + } + for (prefix, config) in self.prefix_match_provides.read().await.iter() { + if request.starts_with(prefix) { + let remainder = &request[prefix.len()..]; + self + .provide_shared_module( + request, + &(config.share_key.to_string() + remainder), + &config.share_scope, + config.version.as_ref(), + config.eager, + resource, + resource_data, + ) + .await?; + } + } + Ok(module) + } + + async fn finish_make(&self, compilation: &mut Compilation) -> Result<()> { + for (resource, config) in self.resolved_provide_map.read().await.iter() { + compilation + .add_include( + Box::new(ProvideSharedDependency::new( + config.share_scope.to_string(), + config.share_key.to_string(), + config.version.clone(), + resource.to_string(), + config.eager, + )), + EntryOptions { + name: None, + ..Default::default() + }, + ) + .await?; + } + self.resolved_provide_map.write().await.clear(); + self.match_provides.write().await.clear(); + self.prefix_match_provides.write().await.clear(); + Ok(()) + } +} diff --git a/crates/rspack_core/src/mf/sharing/share_runtime_module.rs b/crates/rspack_core/src/mf/sharing/share_runtime_module.rs index 5149351d4c25..0f5e9d453215 100644 --- a/crates/rspack_core/src/mf/sharing/share_runtime_module.rs +++ b/crates/rspack_core/src/mf/sharing/share_runtime_module.rs @@ -33,10 +33,8 @@ impl RuntimeModule for ShareRuntimeModule { .chunk .expect("should have chunk in ::generate"); let chunk = compilation.chunk_by_ukey.expect_get(&chunk_ukey); - let mut init_per_scope: FxHashMap< - String, - LinkedHashMap>, - > = FxHashMap::default(); + let mut init_per_scope: FxHashMap>> = + FxHashMap::default(); for c in chunk.get_all_referenced_chunks(&compilation.chunk_group_by_ukey) { let chunk = compilation.chunk_by_ukey.expect_get(&c); let modules = compilation @@ -73,15 +71,7 @@ impl RuntimeModule for ShareRuntimeModule { let stages = stages .into_iter() .sorted_unstable_by_key(|(stage, _)| *stage) - .flat_map(|(_, inits)| { - inits.into_iter().filter_map(|init| match init { - DataInit::ExternalModuleId(Some(id)) => Some(format!( - "initExternal({});", - serde_json::to_string(&id).expect("module_id should able to json to_string") - )), - _ => None, - }) - }) + .flat_map(|(_, inits)| inits) .collect::>() .join("\n"); format!( @@ -105,10 +95,11 @@ var initPerScope = function(name, register, initExternal) {{ {init_per_scope_body} }} }}; -{initialize_sharing} = function(name, initScope) {{ return {initialize_sharing_fn}({{ name: name, initScope: initScope, initPerScope: initPerScope, initTokens: initTokens, initPromises: initPromises }}); }}; +{initialize_sharing} = function(name, initScope) {{ return {initialize_sharing_fn}({{ name: name, initScope: initScope, initPerScope: initPerScope, uniqueName: {unique_name}, initTokens: initTokens, initPromises: initPromises }}); }}; "#, share_scope_map = RuntimeGlobals::SHARE_SCOPE_MAP, init_per_scope_body = init_per_scope_body, + unique_name = serde_json::to_string(&compilation.options.output.unique_name).expect("uniqueName should able to json to_string"), initialize_sharing = RuntimeGlobals::INITIALIZE_SHARING, initialize_sharing_fn = "__webpack_require__.MF.initializeSharing" )) @@ -131,12 +122,7 @@ pub struct CodeGenerationDataShareInit { pub struct ShareInitData { pub share_scope: String, pub init_stage: DataInitStage, - pub init: DataInit, + pub init: String, } pub type DataInitStage = i8; - -#[derive(Debug, Clone, Hash, PartialEq, Eq)] -pub enum DataInit { - ExternalModuleId(Option), -} diff --git a/crates/rspack_core/src/normal_module_factory.rs b/crates/rspack_core/src/normal_module_factory.rs index 2121ea94223f..45b55b0875ca 100644 --- a/crates/rspack_core/src/normal_module_factory.rs +++ b/crates/rspack_core/src/normal_module_factory.rs @@ -632,11 +632,14 @@ impl NormalModuleFactory { self.context.module_type = Some(resolved_module_type); + let create_data = NormalModuleCreateData { + dependency_type: data.dependency.dependency_type().clone(), + resolve_data_request: dependency.request(), + resource_resolve_data: resource_data.clone(), + }; let module = if let Some(module) = self .plugin_driver - .create_module(NormalModuleCreateData { - dependency_type: data.dependency.dependency_type().clone(), - }) + .normal_module_factory_create_module(&create_data) .await? { module @@ -659,6 +662,11 @@ impl NormalModuleFactory { Box::new(normal_module) }; + let module = self + .plugin_driver + .normal_module_factory_module(module, &create_data) + .await?; + Ok(Some( ModuleFactoryResult::new(module) .file_dependency(file_dependency) diff --git a/crates/rspack_core/src/options/entry.rs b/crates/rspack_core/src/options/entry.rs index f1e22f5822ab..a04660f8ad4f 100644 --- a/crates/rspack_core/src/options/entry.rs +++ b/crates/rspack_core/src/options/entry.rs @@ -20,5 +20,6 @@ pub struct EntryDescription { #[derive(Debug, Default)] pub struct EntryData { pub dependencies: Vec, + pub include_dependencies: Vec, pub options: EntryOptions, } diff --git a/crates/rspack_core/src/plugin/api.rs b/crates/rspack_core/src/plugin/api.rs index 629be930a149..d4669907cfd6 100644 --- a/crates/rspack_core/src/plugin/api.rs +++ b/crates/rspack_core/src/plugin/api.rs @@ -25,7 +25,8 @@ pub type PluginBuildEndHookOutput = Result<()>; pub type PluginProcessAssetsHookOutput = Result<()>; pub type PluginReadResourceOutput = Result>; pub type PluginFactorizeHookOutput = Result>; -pub type PluginModuleHookOutput = Result>; +pub type PluginNormalModuleFactoryCreateModuleHookOutput = Result>; +pub type PluginNormalModuleFactoryModuleHookOutput = Result; pub type PluginNormalModuleFactoryResolveForSchemeOutput = Result<(ResourceData, bool)>; pub type PluginNormalModuleFactoryBeforeResolveOutput = Result>; pub type PluginNormalModuleFactoryAfterResolveOutput = Result>; @@ -127,14 +128,23 @@ pub trait Plugin: Debug + Send + Sync { Ok(None) } - async fn create_module( + async fn normal_module_factory_create_module( &self, _ctx: PluginContext, _args: &NormalModuleCreateData, - ) -> PluginModuleHookOutput { + ) -> PluginNormalModuleFactoryCreateModuleHookOutput { Ok(None) } + async fn normal_module_factory_module( + &self, + _ctx: PluginContext, + module: BoxModule, + _args: &NormalModuleCreateData, + ) -> PluginNormalModuleFactoryModuleHookOutput { + Ok(module) + } + async fn normal_module_factory_resolve_for_scheme( &self, _ctx: PluginContext, diff --git a/crates/rspack_core/src/plugin/args.rs b/crates/rspack_core/src/plugin/args.rs index 559a103dbe59..e7275c14dd71 100644 --- a/crates/rspack_core/src/plugin/args.rs +++ b/crates/rspack_core/src/plugin/args.rs @@ -2,6 +2,7 @@ use std::fmt::Debug; use std::path::{Path, PathBuf}; use rspack_hash::RspackHash; +use rspack_loader_runner::ResourceData; use rspack_sources::BoxSource; use rustc_hash::FxHashSet as HashSet; @@ -82,8 +83,10 @@ pub struct FactorizeArgs<'me> { } #[derive(Debug, Clone)] -pub struct NormalModuleCreateData { +pub struct NormalModuleCreateData<'a> { pub dependency_type: DependencyType, + pub resolve_data_request: &'a str, + pub resource_resolve_data: ResourceData, } #[derive(Debug, Clone)] diff --git a/crates/rspack_core/src/plugin/plugin_driver.rs b/crates/rspack_core/src/plugin/plugin_driver.rs index b4f9a7528bc0..0d7b1f4713f2 100644 --- a/crates/rspack_core/src/plugin/plugin_driver.rs +++ b/crates/rspack_core/src/plugin/plugin_driver.rs @@ -10,7 +10,7 @@ use tracing::instrument; use crate::{ AdditionalChunkRuntimeRequirementsArgs, AdditionalModuleRequirementsArgs, ApplyContext, - AssetEmittedArgs, BoxLoader, BoxedParserAndGeneratorBuilder, Chunk, ChunkAssetArgs, + AssetEmittedArgs, BoxLoader, BoxModule, BoxedParserAndGeneratorBuilder, Chunk, ChunkAssetArgs, ChunkContentHash, ChunkHashArgs, CodeGenerationResults, Compilation, CompilationArgs, CompilerOptions, Content, ContentHashArgs, DoneArgs, FactorizeArgs, JsChunkHashArgs, MakeParam, Module, ModuleIdentifier, ModuleType, NormalModule, NormalModuleAfterResolveArgs, @@ -18,13 +18,13 @@ use crate::{ OptimizeChunksArgs, Plugin, PluginAdditionalChunkRuntimeRequirementsOutput, PluginAdditionalModuleRequirementsOutput, PluginBuildEndHookOutput, PluginChunkHashHookOutput, PluginCompilationHookOutput, PluginContext, PluginFactorizeHookOutput, - PluginJsChunkHashHookOutput, PluginMakeHookOutput, PluginModuleHookOutput, - PluginNormalModuleFactoryAfterResolveOutput, PluginNormalModuleFactoryBeforeResolveOutput, - PluginProcessAssetsOutput, PluginRenderChunkHookOutput, PluginRenderHookOutput, - PluginRenderManifestHookOutput, PluginRenderModuleContentOutput, PluginRenderStartupHookOutput, - PluginThisCompilationHookOutput, ProcessAssetsArgs, RenderArgs, RenderChunkArgs, - RenderManifestArgs, RenderModuleContentArgs, RenderStartupArgs, Resolver, ResolverFactory, Stats, - ThisCompilationArgs, + PluginJsChunkHashHookOutput, PluginMakeHookOutput, PluginNormalModuleFactoryAfterResolveOutput, + PluginNormalModuleFactoryBeforeResolveOutput, PluginNormalModuleFactoryCreateModuleHookOutput, + PluginNormalModuleFactoryModuleHookOutput, PluginProcessAssetsOutput, + PluginRenderChunkHookOutput, PluginRenderHookOutput, PluginRenderManifestHookOutput, + PluginRenderModuleContentOutput, PluginRenderStartupHookOutput, PluginThisCompilationHookOutput, + ProcessAssetsArgs, RenderArgs, RenderChunkArgs, RenderManifestArgs, RenderModuleContentArgs, + RenderStartupArgs, Resolver, ResolverFactory, Stats, ThisCompilationArgs, }; pub struct PluginDriver { @@ -322,16 +322,39 @@ impl PluginDriver { Ok(None) } - pub async fn create_module(&self, args: NormalModuleCreateData) -> PluginModuleHookOutput { + pub async fn normal_module_factory_create_module( + &self, + args: &NormalModuleCreateData<'_>, + ) -> PluginNormalModuleFactoryCreateModuleHookOutput { for plugin in &self.plugins { - tracing::trace!("running render runtime:{}", plugin.name()); - if let Some(module) = plugin.create_module(PluginContext::new(), &args).await? { + tracing::trace!( + "running normal_module_factory_create_module:{}", + plugin.name() + ); + if let Some(module) = plugin + .normal_module_factory_create_module(PluginContext::new(), args) + .await? + { return Ok(Some(module)); } } Ok(None) } + pub async fn normal_module_factory_module( + &self, + mut module: BoxModule, + args: &NormalModuleCreateData<'_>, + ) -> PluginNormalModuleFactoryModuleHookOutput { + for plugin in &self.plugins { + tracing::trace!("running normal_module_factory_module:{}", plugin.name()); + module = plugin + .normal_module_factory_module(PluginContext::new(), module, args) + .await?; + } + Ok(module) + } + pub async fn before_resolve( &self, args: &mut NormalModuleBeforeResolveArgs, diff --git a/crates/rspack_plugin_javascript/src/visitors/dependency/api_scanner.rs b/crates/rspack_plugin_javascript/src/visitors/dependency/api_scanner.rs index d08f4d868503..3be7a34ff28d 100644 --- a/crates/rspack_plugin_javascript/src/visitors/dependency/api_scanner.rs +++ b/crates/rspack_plugin_javascript/src/visitors/dependency/api_scanner.rs @@ -12,6 +12,7 @@ use swc_core::{ use super::expr_matcher; use crate::dependency::ModuleArgumentDependency; + pub const WEBPACK_HASH: &str = "__webpack_hash__"; pub const WEBPACK_PUBLIC_PATH: &str = "__webpack_public_path__"; pub const WEBPACK_MODULES: &str = "__webpack_modules__"; @@ -21,6 +22,8 @@ pub const WEBPACK_CHUNK_LOAD: &str = "__webpack_chunk_load__"; pub const WEBPACK_BASE_URI: &str = "__webpack_base_uri__"; pub const NON_WEBPACK_REQUIRE: &str = "__non_webpack_require__"; pub const SYSTEM_CONTEXT: &str = "__system_context__"; +pub const WEBPACK_SHARE_SCOPES: &str = "__webpack_share_scopes__"; +pub const WEBPACK_INIT_SHARING: &str = "__webpack_init_sharing__"; pub struct ApiScanner<'a> { pub unresolved_ctxt: SyntaxContext, @@ -182,6 +185,26 @@ impl Visit for ApiScanner<'_> { RuntimeGlobals::SYSTEM_CONTEXT.name().into(), Some(RuntimeGlobals::SYSTEM_CONTEXT), ))), + WEBPACK_SHARE_SCOPES => { + self + .presentational_dependencies + .push(Box::new(ConstDependency::new( + ident.span.real_lo(), + ident.span.real_hi(), + RuntimeGlobals::SHARE_SCOPE_MAP.name().into(), + Some(RuntimeGlobals::SHARE_SCOPE_MAP), + ))) + } + WEBPACK_INIT_SHARING => { + self + .presentational_dependencies + .push(Box::new(ConstDependency::new( + ident.span.real_lo(), + ident.span.real_hi(), + RuntimeGlobals::INITIALIZE_SHARING.name().into(), + Some(RuntimeGlobals::INITIALIZE_SHARING), + ))) + } _ => {} } } diff --git a/crates/rspack_plugin_runtime/src/lazy_compilation.rs b/crates/rspack_plugin_runtime/src/lazy_compilation.rs index 448848a95f19..0ff04a89dc08 100644 --- a/crates/rspack_plugin_runtime/src/lazy_compilation.rs +++ b/crates/rspack_plugin_runtime/src/lazy_compilation.rs @@ -5,8 +5,8 @@ use async_trait::async_trait; use rspack_core::{ rspack_sources::{RawSource, Source, SourceExt}, AsyncDependenciesBlockIdentifier, Compilation, DependenciesBlock, DependencyId, Module, - ModuleType, NormalModuleCreateData, Plugin, PluginContext, PluginModuleHookOutput, - RuntimeGlobals, RuntimeSpec, SourceType, + ModuleType, NormalModuleCreateData, Plugin, PluginContext, + PluginNormalModuleFactoryCreateModuleHookOutput, RuntimeGlobals, RuntimeSpec, SourceType, }; use rspack_core::{CodeGenerationResult, Context, ModuleIdentifier}; use rspack_error::Result; @@ -115,11 +115,11 @@ impl Plugin for LazyCompilationPlugin { "LazyCompilationPlugin" } - async fn create_module( + async fn normal_module_factory_create_module( &self, _ctx: PluginContext, _args: &NormalModuleCreateData, - ) -> PluginModuleHookOutput { + ) -> PluginNormalModuleFactoryCreateModuleHookOutput { // if args.indentfiler.contains("rspack-dev-client") // || args.lazy_visit_modules.contains(args.indentfiler.as_str()) // { diff --git a/crates/rspack_plugin_runtime/src/runtime_module/readfile_chunk_loading.rs b/crates/rspack_plugin_runtime/src/runtime_module/readfile_chunk_loading.rs index 2f848a5cd9b8..1fcfc97a324e 100644 --- a/crates/rspack_plugin_runtime/src/runtime_module/readfile_chunk_loading.rs +++ b/crates/rspack_plugin_runtime/src/runtime_module/readfile_chunk_loading.rs @@ -97,7 +97,7 @@ impl RuntimeModule for ReadFileChunkLoadingRuntimeModule { .runtime_requirements .contains(RuntimeGlobals::ON_CHUNKS_LOADED); - if with_loading { + if with_loading || with_external_install_chunk { source.add(RawSource::from( include_str!("runtime/readfile_chunk_loading.js").replace( "$withOnChunkLoad$", diff --git a/crates/rspack_plugin_runtime/src/runtime_plugin.rs b/crates/rspack_plugin_runtime/src/runtime_plugin.rs index 5ee996b4bdd3..f8547012f8a0 100644 --- a/crates/rspack_plugin_runtime/src/runtime_plugin.rs +++ b/crates/rspack_plugin_runtime/src/runtime_plugin.rs @@ -81,8 +81,14 @@ static TREE_DEPENDENCIES: Lazy)>> = Laz RuntimeGlobals::DEFINE_PROPERTY_GETTERS, vec![RuntimeGlobals::HAS_OWN_PROPERTY], ), - // (RuntimeGlobals::INITIALIZE_SHARING, [RuntimeGlobals::SHARE_SCOPE_MAP]), - // (RuntimeGlobals::SHARE_SCOPE_MAP, [RuntimeGlobals::HAS_OWN_PROPERTY]), + ( + RuntimeGlobals::INITIALIZE_SHARING, + vec![RuntimeGlobals::SHARE_SCOPE_MAP], + ), + ( + RuntimeGlobals::SHARE_SCOPE_MAP, + vec![RuntimeGlobals::HAS_OWN_PROPERTY], + ), ( RuntimeGlobals::HARMONY_MODULE_DECORATOR, vec![RuntimeGlobals::MODULE, RuntimeGlobals::REQUIRE_SCOPE], diff --git a/packages/rspack/src/builtin-plugin/base.ts b/packages/rspack/src/builtin-plugin/base.ts index 2e82a4cc19d7..c3f1f26a7f2e 100644 --- a/packages/rspack/src/builtin-plugin/base.ts +++ b/packages/rspack/src/builtin-plugin/base.ts @@ -30,7 +30,8 @@ export enum BuiltinPluginName { OldSplitChunksPlugin = "OldSplitChunksPlugin", ContainerPlugin = "ContainerPlugin", ContainerReferencePlugin = "ContainerReferencePlugin", - ModuleFederationRuntimePlugin = "ModuleFederationRuntimePlugin" + ModuleFederationRuntimePlugin = "ModuleFederationRuntimePlugin", + ProvideSharedPlugin = "ProvideSharedPlugin" } export abstract class RspackBuiltinPlugin implements RspackPluginInstance { diff --git a/packages/rspack/src/container/ContainerReferencePlugin.ts b/packages/rspack/src/container/ContainerReferencePlugin.ts index c9c23d3301bc..2f2b11aa853e 100644 --- a/packages/rspack/src/container/ContainerReferencePlugin.ts +++ b/packages/rspack/src/container/ContainerReferencePlugin.ts @@ -30,12 +30,8 @@ export class ContainerReferencePlugin extends RspackBuiltinPlugin { name = BuiltinPluginName.ContainerReferencePlugin; _options: RawContainerReferencePluginOptions; _remotes; - _mfRuntimePlugin: [boolean, ModuleFederationRuntimePlugin]; - constructor( - options: ContainerReferencePluginOptions, - mfRuntimePlugin?: ModuleFederationRuntimePlugin - ) { + constructor(options: ContainerReferencePluginOptions) { super(); this._remotes = parseOptions( options.remotes, @@ -54,10 +50,6 @@ export class ContainerReferencePlugin extends RspackBuiltinPlugin { remoteType: options.remoteType, remotes: this._remotes.map(([key, r]) => ({ key, ...r })) }; - this._mfRuntimePlugin = [ - !isNil(mfRuntimePlugin), - mfRuntimePlugin ?? new ModuleFederationRuntimePlugin() - ]; } raw(compiler: Compiler): BuiltinPlugin { @@ -74,14 +66,14 @@ export class ContainerReferencePlugin extends RspackBuiltinPlugin { } } new ExternalsPlugin(remoteType, remoteExternals).apply(compiler); - const [injected, mfRuntimePlugin] = this._mfRuntimePlugin; - mfRuntimePlugin.addPlugin( + ModuleFederationRuntimePlugin.addPlugin( + compiler, require.resolve("../sharing/initializeSharing.js") ); - mfRuntimePlugin.addPlugin(require.resolve("./remotesLoading.js")); - if (!injected) { - mfRuntimePlugin.apply(compiler); - } + ModuleFederationRuntimePlugin.addPlugin( + compiler, + require.resolve("./remotesLoading.js") + ); return { name: this.name as any, diff --git a/packages/rspack/src/container/ModuleFederationPlugin.ts b/packages/rspack/src/container/ModuleFederationPlugin.ts index 88fbd8820c2d..dde18df04692 100644 --- a/packages/rspack/src/container/ModuleFederationPlugin.ts +++ b/packages/rspack/src/container/ModuleFederationPlugin.ts @@ -5,6 +5,7 @@ import { ExternalsType, externalsType } from "../config"; +import { SharePlugin, Shared } from "../sharing/SharePlugin"; import { isValidate } from "../util/validate"; import { ContainerPlugin, Exposes } from "./ContainerPlugin"; import { ContainerReferencePlugin, Remotes } from "./ContainerReferencePlugin"; @@ -19,7 +20,7 @@ export interface ModuleFederationPluginOptions { remotes?: Remotes; runtime?: EntryRuntime; shareScope?: string; - // shared?: Shared; + shared?: Shared; } export class ModuleFederationPlugin { @@ -39,8 +40,6 @@ export class ModuleFederationPlugin { ) { compiler.options.output.enabledLibraryTypes!.push(library.type); } - const mfRuntimePlugin = new ModuleFederationRuntimePlugin(); - mfRuntimePlugin.apply(compiler); compiler.hooks.afterPlugins.tap("ModuleFederationPlugin", () => { if ( options.exposes && @@ -63,21 +62,18 @@ export class ModuleFederationPlugin { ? options.remotes.length > 0 : Object.keys(options.remotes).length > 0) ) { - new ContainerReferencePlugin( - { - remoteType, - shareScope: options.shareScope, - remotes: options.remotes - }, - mfRuntimePlugin - ).apply(compiler); + new ContainerReferencePlugin({ + remoteType, + shareScope: options.shareScope, + remotes: options.remotes + }).apply(compiler); + } + if (options.shared) { + new SharePlugin({ + shared: options.shared, + shareScope: options.shareScope + }).apply(compiler); } - // if (options.shared) { - // new SharePlugin({ - // shared: options.shared, - // shareScope: options.shareScope - // }).apply(compiler); - // } }); } } diff --git a/packages/rspack/src/container/ModuleFederationRuntimePlugin.ts b/packages/rspack/src/container/ModuleFederationRuntimePlugin.ts index 440bffdc23ac..5232322d86ee 100644 --- a/packages/rspack/src/container/ModuleFederationRuntimePlugin.ts +++ b/packages/rspack/src/container/ModuleFederationRuntimePlugin.ts @@ -7,26 +7,33 @@ const ModuleFederationRuntimePlugin2 = create( () => undefined ); -export class ModuleFederationRuntimePlugin { - plugins: string[] = []; +const compilerToPlugins = new WeakMap(); +export class ModuleFederationRuntimePlugin { apply(compiler: Compiler) { // TODO: a hack to make sure this runtime is added after ContainerReferencePlugin // remove afterPlugin once we make rust side runtime_requirements_in_tree "tapable" compiler.hooks.afterPlugins.tap( { name: ModuleFederationRuntimePlugin.name, stage: 10 }, () => { - // TODO: move to rust side so don't depend on dataUrl - const entry = this.plugins.map(p => `import "${p}";`).join("\n"); - new EntryPlugin(compiler.context, `data:text/javascript,${entry}`, { - name: undefined - }).apply(compiler); + const plugins = compilerToPlugins.get(compiler); + if (plugins) { + // TODO: move to rust side so don't depend on dataUrl? + const entry = plugins.map(p => `import "${p}";`).join("\n"); + new EntryPlugin(compiler.context, `data:text/javascript,${entry}`, { + name: undefined + }).apply(compiler); + } new ModuleFederationRuntimePlugin2().apply(compiler); } ); } - addPlugin(dep: string) { - this.plugins.push(dep); + static addPlugin(compiler: Compiler, plugin: string) { + let plugins = compilerToPlugins.get(compiler); + if (!plugins) { + compilerToPlugins.set(compiler, (plugins = [])); + } + plugins.push(plugin); } } diff --git a/packages/rspack/src/exports.ts b/packages/rspack/src/exports.ts index 5baa498ba6bd..aee45b640fb6 100644 --- a/packages/rspack/src/exports.ts +++ b/packages/rspack/src/exports.ts @@ -145,6 +145,27 @@ export const container = { ModuleFederationPlugin }; +import { ProvideSharedPlugin } from "./sharing/ProvideSharedPlugin"; +import { SharePlugin } from "./sharing/SharePlugin"; +export type { + ProvideSharedPluginOptions, + Provides, + ProvidesConfig, + ProvidesItem, + ProvidesObject +} from "./sharing/ProvideSharedPlugin"; +export type { + SharePluginOptions, + Shared, + SharedConfig, + SharedItem, + SharedObject +} from "./sharing/SharePlugin"; +export const sharing = { + ProvideSharedPlugin, + SharePlugin +}; + ///// Rspack Postfixed Internal Plugins ///// export { HtmlRspackPlugin } from "./builtin-plugin"; export type { HtmlRspackPluginOptions } from "./builtin-plugin"; diff --git a/packages/rspack/src/rspackOptionsApply.ts b/packages/rspack/src/rspackOptionsApply.ts index 7300e2e71ede..f61153bff3b7 100644 --- a/packages/rspack/src/rspackOptionsApply.ts +++ b/packages/rspack/src/rspackOptionsApply.ts @@ -174,6 +174,8 @@ export class RspackOptionsApply { ); compiler.hooks.entryOption.call(options.context, options.entry); + new ModuleFederationRuntimePlugin().apply(compiler); + const { minimize, minimizer } = options.optimization; if (minimize && minimizer) { for (const item of minimizer) { diff --git a/packages/rspack/src/sharing/ProvideSharedPlugin.ts b/packages/rspack/src/sharing/ProvideSharedPlugin.ts new file mode 100644 index 000000000000..1c4177f44f31 --- /dev/null +++ b/packages/rspack/src/sharing/ProvideSharedPlugin.ts @@ -0,0 +1,65 @@ +import { BuiltinPlugin, RawProvideOptions } from "@rspack/binding"; +import { + BuiltinPluginName, + RspackBuiltinPlugin, + create +} from "../builtin-plugin/base"; +import { parseOptions } from "../container/options"; +import { Compiler } from "../Compiler"; +import { ModuleFederationRuntimePlugin } from "../container/ModuleFederationRuntimePlugin"; + +export type ProvideSharedPluginOptions = { + provides: Provides; + shareScope?: string; +}; +export type Provides = (ProvidesItem | ProvidesObject)[] | ProvidesObject; +export type ProvidesItem = string; +export type ProvidesObject = { + [k: string]: ProvidesConfig | ProvidesItem; +}; +export type ProvidesConfig = { + eager?: boolean; + shareKey: string; + shareScope?: string; + version?: false | string; +}; + +export class ProvideSharedPlugin extends RspackBuiltinPlugin { + name = BuiltinPluginName.ProvideSharedPlugin; + _options: RawProvideOptions[]; + + constructor(options: ProvideSharedPluginOptions) { + super(); + this._options = parseOptions( + options.provides, + item => { + if (Array.isArray(item)) + throw new Error("Unexpected array of provides"); + const result = { + shareKey: item, + version: undefined, + shareScope: options.shareScope || "default", + eager: false + }; + return result; + }, + item => ({ + shareKey: item.shareKey, + version: item.version, + shareScope: item.shareScope || options.shareScope || "default", + eager: !!item.eager + }) + ).map(([key, v]) => ({ key, ...v })); + } + + raw(compiler: Compiler): BuiltinPlugin { + ModuleFederationRuntimePlugin.addPlugin( + compiler, + require.resolve("../sharing/initializeSharing.js") + ); + return { + name: this.name as any, + options: this._options + }; + } +} diff --git a/packages/rspack/src/sharing/SharePlugin.ts b/packages/rspack/src/sharing/SharePlugin.ts new file mode 100644 index 000000000000..1b7c594d8e46 --- /dev/null +++ b/packages/rspack/src/sharing/SharePlugin.ts @@ -0,0 +1,88 @@ +import { Compiler } from "../Compiler"; +import { parseOptions } from "../container/options"; +import { ProvideSharedPlugin } from "./ProvideSharedPlugin"; +import { isRequiredVersion } from "./utils"; + +export type SharePluginOptions = { + shareScope?: string; + shared: Shared; +}; +export type Shared = (SharedItem | SharedObject)[] | SharedObject; +export type SharedItem = string; +export type SharedObject = { + [k: string]: SharedConfig | SharedItem; +}; +export type SharedConfig = { + eager?: boolean; + import?: false | SharedItem; + packageName?: string; + requiredVersion?: false | string; + shareKey?: string; + shareScope?: string; + singleton?: boolean; + strictVersion?: boolean; + version?: false | string; +}; + +export class SharePlugin { + _shareScope; + _consumes; + _provides; + + constructor(options: SharePluginOptions) { + const sharedOptions = parseOptions( + options.shared, + (item, key) => { + if (typeof item !== "string") + throw new Error("Unexpected array in shared"); + const config: SharedConfig = + item === key || !isRequiredVersion(item) + ? { + import: item + } + : { + import: key, + requiredVersion: item + }; + return config; + }, + item => item + ); + const consumes = sharedOptions.map(([key, options]) => ({ + [key]: { + import: options.import, + shareKey: options.shareKey || key, + shareScope: options.shareScope, + requiredVersion: options.requiredVersion, + strictVersion: options.strictVersion, + singleton: options.singleton, + packageName: options.packageName, + eager: options.eager + } + })); + const provides = sharedOptions + .filter(([, options]) => options.import !== false) + .map(([key, options]) => ({ + [options.import || key]: { + shareKey: options.shareKey || key, + shareScope: options.shareScope, + version: options.version, + eager: options.eager + } + })); + this._shareScope = options.shareScope; + this._consumes = consumes; + this._provides = provides; + } + + apply(compiler: Compiler) { + // new ConsumeSharedPlugin({ + // shareScope: this._shareScope, + // consumes: this._consumes + // }).apply(compiler); + new ProvideSharedPlugin({ + shareScope: this._shareScope, + provides: this._provides + }).apply(compiler); + } +} diff --git a/packages/rspack/src/sharing/initializeSharing.js b/packages/rspack/src/sharing/initializeSharing.js index 94031a09a5cb..3ed1de47ba4a 100644 --- a/packages/rspack/src/sharing/initializeSharing.js +++ b/packages/rspack/src/sharing/initializeSharing.js @@ -23,7 +23,7 @@ if (__webpack_require__.MF) { var warn = function (msg) { if (typeof console !== "undefined" && console.warn) console.warn(msg); }; - var uniqueName = "app"; + var uniqueName = data.uniqueName; var register = function (name, version, factory, eager) { var versions = (scope[name] = scope[name] || {}); var activeVersion = versions[version]; diff --git a/packages/rspack/src/sharing/utils.ts b/packages/rspack/src/sharing/utils.ts new file mode 100644 index 000000000000..3a89dbd573f3 --- /dev/null +++ b/packages/rspack/src/sharing/utils.ts @@ -0,0 +1,5 @@ +const VERSION_PATTERN_REGEXP = /^([\d^=v<>~]|[*xX]$)/; + +export function isRequiredVersion(str: string) { + return VERSION_PATTERN_REGEXP.test(str); +} diff --git a/webpack-test/configCases/sharing/provide-eager-module/node_modules/common/index.js b/webpack-test/configCases/sharing/provide-eager-module/node_modules/common/index.js new file mode 100644 index 000000000000..888cae37af95 --- /dev/null +++ b/webpack-test/configCases/sharing/provide-eager-module/node_modules/common/index.js @@ -0,0 +1 @@ +module.exports = 42; diff --git a/webpack-test/configCases/sharing/provide-eager-module/node_modules/common/package.json b/webpack-test/configCases/sharing/provide-eager-module/node_modules/common/package.json new file mode 100644 index 000000000000..1587a669681c --- /dev/null +++ b/webpack-test/configCases/sharing/provide-eager-module/node_modules/common/package.json @@ -0,0 +1,3 @@ +{ + "version": "1.0.0" +} diff --git a/webpack-test/configCases/sharing/provide-eager-module/node_modules/uncommon/index.js b/webpack-test/configCases/sharing/provide-eager-module/node_modules/uncommon/index.js new file mode 100644 index 000000000000..888cae37af95 --- /dev/null +++ b/webpack-test/configCases/sharing/provide-eager-module/node_modules/uncommon/index.js @@ -0,0 +1 @@ +module.exports = 42; diff --git a/webpack-test/configCases/sharing/provide-eager-module/node_modules/uncommon/package.json b/webpack-test/configCases/sharing/provide-eager-module/node_modules/uncommon/package.json new file mode 100644 index 000000000000..4928ba5355f3 --- /dev/null +++ b/webpack-test/configCases/sharing/provide-eager-module/node_modules/uncommon/package.json @@ -0,0 +1,3 @@ +{ + "version": "2.0.0" +} diff --git a/webpack-test/configCases/sharing/provide-eager-module/test.filter.js b/webpack-test/configCases/sharing/provide-eager-module/test.filter.js deleted file mode 100644 index 3be456dcd23c..000000000000 --- a/webpack-test/configCases/sharing/provide-eager-module/test.filter.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = () => {return false} \ No newline at end of file diff --git a/webpack-test/configCases/sharing/provide-eager-module/webpack.config.js b/webpack-test/configCases/sharing/provide-eager-module/webpack.config.js index 53a3963af52b..718bd2357506 100644 --- a/webpack-test/configCases/sharing/provide-eager-module/webpack.config.js +++ b/webpack-test/configCases/sharing/provide-eager-module/webpack.config.js @@ -1,5 +1,5 @@ // eslint-disable-next-line node/no-unpublished-require -const { ProvideSharedPlugin } = require("../../../../").sharing; +const { ProvideSharedPlugin } = require("../../../../packages/rspack").sharing; /** @type {import("../../../../types").Configuration} */ module.exports = { diff --git a/webpack-test/configCases/sharing/provide-module/node_modules/package/index.js b/webpack-test/configCases/sharing/provide-module/node_modules/package/index.js new file mode 100644 index 000000000000..7c1dac1c3021 --- /dev/null +++ b/webpack-test/configCases/sharing/provide-module/node_modules/package/index.js @@ -0,0 +1 @@ +module.exports = "package"; diff --git a/webpack-test/configCases/sharing/provide-module/node_modules/package/package.json b/webpack-test/configCases/sharing/provide-module/node_modules/package/package.json new file mode 100644 index 000000000000..1587a669681c --- /dev/null +++ b/webpack-test/configCases/sharing/provide-module/node_modules/package/package.json @@ -0,0 +1,3 @@ +{ + "version": "1.0.0" +} diff --git a/webpack-test/configCases/sharing/provide-module/test.filter.js b/webpack-test/configCases/sharing/provide-module/test.filter.js deleted file mode 100644 index 3be456dcd23c..000000000000 --- a/webpack-test/configCases/sharing/provide-module/test.filter.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = () => {return false} \ No newline at end of file diff --git a/webpack-test/configCases/sharing/provide-module/webpack.config.js b/webpack-test/configCases/sharing/provide-module/webpack.config.js index 8aff0afa0c8f..f2934e718f88 100644 --- a/webpack-test/configCases/sharing/provide-module/webpack.config.js +++ b/webpack-test/configCases/sharing/provide-module/webpack.config.js @@ -1,5 +1,5 @@ // eslint-disable-next-line node/no-unpublished-require -const { ProvideSharedPlugin } = require("../../../../").sharing; +const { ProvideSharedPlugin } = require("../../../../packages/rspack").sharing; /** @type {import("@rspack/core").Configuration} */ module.exports = { diff --git a/webpack-test/configCases/sharing/provide-multiple-versions/node_modules/my-module/index.js b/webpack-test/configCases/sharing/provide-multiple-versions/node_modules/my-module/index.js new file mode 100644 index 000000000000..33dcca8255bf --- /dev/null +++ b/webpack-test/configCases/sharing/provide-multiple-versions/node_modules/my-module/index.js @@ -0,0 +1 @@ +export * from "shared"; diff --git a/webpack-test/configCases/sharing/provide-multiple-versions/node_modules/my-module/node_modules/shared/index.js b/webpack-test/configCases/sharing/provide-multiple-versions/node_modules/my-module/node_modules/shared/index.js new file mode 100644 index 000000000000..fa434c11d85c --- /dev/null +++ b/webpack-test/configCases/sharing/provide-multiple-versions/node_modules/my-module/node_modules/shared/index.js @@ -0,0 +1 @@ +export * from "./package.json"; diff --git a/webpack-test/configCases/sharing/provide-multiple-versions/node_modules/my-module/node_modules/shared/package.json b/webpack-test/configCases/sharing/provide-multiple-versions/node_modules/my-module/node_modules/shared/package.json new file mode 100644 index 000000000000..8836d69c11f4 --- /dev/null +++ b/webpack-test/configCases/sharing/provide-multiple-versions/node_modules/my-module/node_modules/shared/package.json @@ -0,0 +1,4 @@ +{ + "name": "shared", + "version": "2.0.0" +} diff --git a/webpack-test/configCases/sharing/provide-multiple-versions/node_modules/shared/index.js b/webpack-test/configCases/sharing/provide-multiple-versions/node_modules/shared/index.js new file mode 100644 index 000000000000..fa434c11d85c --- /dev/null +++ b/webpack-test/configCases/sharing/provide-multiple-versions/node_modules/shared/index.js @@ -0,0 +1 @@ +export * from "./package.json"; diff --git a/webpack-test/configCases/sharing/provide-multiple-versions/node_modules/shared/package.json b/webpack-test/configCases/sharing/provide-multiple-versions/node_modules/shared/package.json new file mode 100644 index 000000000000..65b99b009284 --- /dev/null +++ b/webpack-test/configCases/sharing/provide-multiple-versions/node_modules/shared/package.json @@ -0,0 +1,4 @@ +{ + "name": "shared", + "version": "1.0.0" +} diff --git a/webpack-test/configCases/sharing/provide-multiple-versions/node_modules/unused-module/node_modules/shared/index.js b/webpack-test/configCases/sharing/provide-multiple-versions/node_modules/unused-module/node_modules/shared/index.js new file mode 100644 index 000000000000..fa434c11d85c --- /dev/null +++ b/webpack-test/configCases/sharing/provide-multiple-versions/node_modules/unused-module/node_modules/shared/index.js @@ -0,0 +1 @@ +export * from "./package.json"; diff --git a/webpack-test/configCases/sharing/provide-multiple-versions/node_modules/unused-module/node_modules/shared/package.json b/webpack-test/configCases/sharing/provide-multiple-versions/node_modules/unused-module/node_modules/shared/package.json new file mode 100644 index 000000000000..87cb039c937a --- /dev/null +++ b/webpack-test/configCases/sharing/provide-multiple-versions/node_modules/unused-module/node_modules/shared/package.json @@ -0,0 +1,4 @@ +{ + "name": "shared", + "version": "3.0.0" +} diff --git a/webpack-test/configCases/sharing/provide-multiple-versions/webpack.config.js b/webpack-test/configCases/sharing/provide-multiple-versions/webpack.config.js index 390df10a2948..4bd1ce2b944a 100644 --- a/webpack-test/configCases/sharing/provide-multiple-versions/webpack.config.js +++ b/webpack-test/configCases/sharing/provide-multiple-versions/webpack.config.js @@ -1,5 +1,5 @@ // eslint-disable-next-line node/no-unpublished-require -const { ProvideSharedPlugin } = require("../../../../").sharing; +const { ProvideSharedPlugin } = require("../../../../packages/rspack").sharing; /** @type {import("@rspack/core").Configuration} */ module.exports = { diff --git a/webpack-test/configCases/sharing/provide-shared-with-runtime-chunk/node_modules/x/index.js b/webpack-test/configCases/sharing/provide-shared-with-runtime-chunk/node_modules/x/index.js new file mode 100644 index 000000000000..888cae37af95 --- /dev/null +++ b/webpack-test/configCases/sharing/provide-shared-with-runtime-chunk/node_modules/x/index.js @@ -0,0 +1 @@ +module.exports = 42; diff --git a/webpack-test/configCases/sharing/provide-shared-with-runtime-chunk/node_modules/x/package.json b/webpack-test/configCases/sharing/provide-shared-with-runtime-chunk/node_modules/x/package.json new file mode 100644 index 000000000000..1587a669681c --- /dev/null +++ b/webpack-test/configCases/sharing/provide-shared-with-runtime-chunk/node_modules/x/package.json @@ -0,0 +1,3 @@ +{ + "version": "1.0.0" +} diff --git a/webpack-test/configCases/sharing/provide-shared-with-runtime-chunk/test.filter.js b/webpack-test/configCases/sharing/provide-shared-with-runtime-chunk/test.filter.js deleted file mode 100644 index 3be456dcd23c..000000000000 --- a/webpack-test/configCases/sharing/provide-shared-with-runtime-chunk/test.filter.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = () => {return false} \ No newline at end of file diff --git a/webpack-test/configCases/sharing/provide-shared-with-runtime-chunk/webpack.config.js b/webpack-test/configCases/sharing/provide-shared-with-runtime-chunk/webpack.config.js index 113227928c94..8795ca93a05e 100644 --- a/webpack-test/configCases/sharing/provide-shared-with-runtime-chunk/webpack.config.js +++ b/webpack-test/configCases/sharing/provide-shared-with-runtime-chunk/webpack.config.js @@ -1,4 +1,4 @@ -const { ProvideSharedPlugin } = require("../../../../").sharing; +const { ProvideSharedPlugin } = require("../../../../packages/rspack").sharing; /** @type {import("@rspack/core").Configuration} */ module.exports = { output: {