diff --git a/crates/node_binding/binding.d.ts b/crates/node_binding/binding.d.ts index 351b49e8059..2e407dac3fd 100644 --- a/crates/node_binding/binding.d.ts +++ b/crates/node_binding/binding.d.ts @@ -142,6 +142,7 @@ export const enum BuiltinPluginName { LimitChunkCountPlugin = 'LimitChunkCountPlugin', WebWorkerTemplatePlugin = 'WebWorkerTemplatePlugin', MergeDuplicateChunksPlugin = 'MergeDuplicateChunksPlugin', + ContainerPlugin = 'ContainerPlugin', HttpExternalsRspackPlugin = 'HttpExternalsRspackPlugin', CopyRspackPlugin = 'CopyRspackPlugin', HtmlRspackPlugin = 'HtmlRspackPlugin', @@ -611,6 +612,15 @@ export interface RawChunkOptionNameCtx { module: JsModule } +export interface RawContainerPluginOptions { + name: string + shareScope: string + library: RawLibraryOptions + runtime?: string + filename?: string + exposes: Array +} + export interface RawCopyGlobOptions { caseSensitiveMatch?: boolean dot?: boolean @@ -685,6 +695,12 @@ export interface RawExperiments { rspackFuture: RawRspackFuture } +export interface RawExposeOptions { + key: string + name?: string + import: Array +} + export interface RawExternalItemFnCtx { request: string context: 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 10fdafc8ac9..9fc73211680 100644 --- a/crates/rspack_binding_options/src/options/raw_builtins/mod.rs +++ b/crates/rspack_binding_options/src/options/raw_builtins/mod.rs @@ -2,6 +2,7 @@ mod raw_banner; mod raw_copy; mod raw_html; mod raw_limit_chunk_count; +mod raw_mf; mod raw_progress; mod raw_swc_js_minimizer; mod raw_to_be_deprecated; @@ -11,7 +12,9 @@ use napi::{ JsUnknown, }; use napi_derive::napi; -use rspack_core::{BoxPlugin, Define, DefinePlugin, PluginExt, Provide, ProvidePlugin}; +use rspack_core::{ + mf::ContainerPlugin, BoxPlugin, Define, DefinePlugin, PluginExt, Provide, ProvidePlugin, +}; use rspack_error::Result; use rspack_napi_shared::NapiResultExt; use rspack_plugin_banner::BannerPlugin; @@ -38,7 +41,7 @@ use rspack_plugin_web_worker_template::web_worker_template_plugin; pub use self::{ raw_banner::RawBannerPluginOptions, raw_copy::RawCopyRspackPluginOptions, raw_html::RawHtmlRspackPluginOptions, raw_limit_chunk_count::RawLimitChunkCountPluginOptions, - raw_progress::RawProgressPluginOptions, + raw_mf::RawContainerPluginOptions, raw_progress::RawProgressPluginOptions, raw_swc_js_minimizer::RawSwcJsMinimizerRspackPluginOptions, }; use crate::{ @@ -68,6 +71,7 @@ pub enum BuiltinPluginName { LimitChunkCountPlugin, WebWorkerTemplatePlugin, MergeDuplicateChunksPlugin, + ContainerPlugin, // rspack specific plugins HttpExternalsRspackPlugin, @@ -174,6 +178,12 @@ impl RawOptionsApply for BuiltinPlugin { BuiltinPluginName::MergeDuplicateChunksPlugin => { plugins.push(MergeDuplicateChunksPlugin.boxed()); } + BuiltinPluginName::ContainerPlugin => { + plugins.push( + ContainerPlugin::new(downcast_into::(self.options)?.into()) + .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 new file mode 100644 index 00000000000..2b3bd729e50 --- /dev/null +++ b/crates/rspack_binding_options/src/options/raw_builtins/raw_mf.rs @@ -0,0 +1,48 @@ +use napi_derive::napi; +use rspack_core::mf::{ContainerPluginOptions, ExposeOptions}; + +use crate::RawLibraryOptions; + +#[derive(Debug)] +#[napi(object)] +pub struct RawContainerPluginOptions { + pub name: String, + pub share_scope: String, + pub library: RawLibraryOptions, + pub runtime: Option, + pub filename: Option, + pub exposes: Vec, +} + +impl From for ContainerPluginOptions { + fn from(value: RawContainerPluginOptions) -> Self { + Self { + name: value.name, + share_scope: value.share_scope, + library: value.library.into(), + runtime: value.runtime, + filename: value.filename.map(|f| f.into()), + exposes: value.exposes.into_iter().map(|e| e.into()).collect(), + } + } +} + +#[derive(Debug, Clone)] +#[napi(object)] +pub struct RawExposeOptions { + pub key: String, + pub name: Option, + pub import: Vec, +} + +impl From for (String, ExposeOptions) { + fn from(value: RawExposeOptions) -> Self { + ( + value.key, + ExposeOptions { + name: value.name, + import: value.import, + }, + ) + } +} 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 58f30320d72..65ad727baa5 100644 --- a/crates/rspack_core/src/build_chunk_graph/code_splitter.rs +++ b/crates/rspack_core/src/build_chunk_graph/code_splitter.rs @@ -1,14 +1,13 @@ -use std::sync::Arc; - use anyhow::anyhow; use rspack_error::{internal_error, Result}; use rustc_hash::{FxHashMap as HashMap, FxHashSet as HashSet}; use super::remove_parent_modules::RemoveParentModulesContext; use crate::{ - AsyncDependenciesBlockIdentifier, BoxDependency, ChunkGroup, ChunkGroupInfo, ChunkGroupKind, - ChunkGroupOptions, ChunkGroupUkey, ChunkLoading, ChunkUkey, Compilation, DependenciesBlock, - GroupOptions, Logger, ModuleGraphConnection, ModuleIdentifier, RuntimeSpec, IS_NEW_TREESHAKING, + get_entry_runtime, AsyncDependenciesBlockIdentifier, BoxDependency, ChunkGroup, ChunkGroupInfo, + ChunkGroupKind, ChunkGroupOptions, ChunkGroupUkey, ChunkLoading, ChunkUkey, Compilation, + DependenciesBlock, GroupOptions, Logger, ModuleGraphConnection, ModuleIdentifier, RuntimeSpec, + IS_NEW_TREESHAKING, }; pub(super) struct CodeSplitter<'me> { @@ -95,9 +94,7 @@ impl<'me> CodeSplitter<'me> { let mut entrypoint = ChunkGroup::new( ChunkGroupKind::new_entrypoint(true, Box::new(options.clone())), ChunkGroupInfo { - runtime: HashSet::from_iter([Arc::from( - options.runtime.clone().unwrap_or_else(|| name.to_string()), - )]), + runtime: get_entry_runtime(name, options), chunk_loading: !matches!( options .chunk_loading diff --git a/crates/rspack_core/src/chunk_group.rs b/crates/rspack_core/src/chunk_group.rs index 89f23c834c9..dd61479f238 100644 --- a/crates/rspack_core/src/chunk_group.rs +++ b/crates/rspack_core/src/chunk_group.rs @@ -301,10 +301,12 @@ impl ChunkGroupKind { } } +pub type EntryRuntime = String; + #[derive(Debug, Default, Clone)] pub struct EntryOptions { pub name: Option, - pub runtime: Option, + pub runtime: Option, pub chunk_loading: Option, pub async_chunks: Option, pub public_path: Option, diff --git a/crates/rspack_core/src/compiler/compilation.rs b/crates/rspack_core/src/compiler/compilation.rs index b5dcdbf0e37..6a3793e4c8c 100644 --- a/crates/rspack_core/src/compiler/compilation.rs +++ b/crates/rspack_core/src/compiler/compilation.rs @@ -41,11 +41,12 @@ use crate::{ ChunkGraph, ChunkGroupByUkey, ChunkGroupUkey, ChunkHashArgs, ChunkKind, ChunkUkey, CleanQueue, CleanTask, CleanTaskResult, CodeGenerationResult, CodeGenerationResults, CompilationLogger, CompilationLogging, CompilerOptions, ContentHashArgs, ContextDependency, DependencyId, - DependencyParents, Entry, EntryData, EntryOptions, Entrypoint, FactorizeQueue, FactorizeTask, - FactorizeTaskResult, Filename, Logger, Module, ModuleGraph, ModuleIdentifier, ModuleProfile, - ModuleType, PathData, ProcessAssetsArgs, ProcessDependenciesQueue, ProcessDependenciesResult, - ProcessDependenciesTask, RenderManifestArgs, Resolve, ResolverFactory, RuntimeGlobals, - RuntimeModule, RuntimeSpec, SharedPluginDriver, SourceType, Stats, TaskResult, WorkerTask, + DependencyParents, DependencyType, Entry, EntryData, EntryOptions, Entrypoint, FactorizeQueue, + FactorizeTask, FactorizeTaskResult, Filename, Logger, Module, ModuleGraph, ModuleIdentifier, + ModuleProfile, ModuleType, PathData, ProcessAssetsArgs, ProcessDependenciesQueue, + ProcessDependenciesResult, ProcessDependenciesTask, RenderManifestArgs, Resolve, ResolverFactory, + RuntimeGlobals, RuntimeModule, RuntimeSpec, SharedPluginDriver, SourceType, Stats, TaskResult, + WorkerTask, }; use crate::{tree_shaking::visitor::OptimizeAnalyzeResult, Context}; @@ -172,19 +173,21 @@ impl Compilation { } } - pub fn add_entry(&mut self, entry: DependencyId, options: EntryOptions) { + pub fn add_entry(&mut self, entry: BoxDependency, options: EntryOptions) { + 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.dependencies.push(entry); + data.dependencies.push(entry_id); } else { let data = EntryData { - dependencies: vec![entry], + dependencies: vec![entry_id], options, }; self.entries.insert(name, data); } } else { - self.global_entry.dependencies.push(entry); + self.global_entry.dependencies.push(entry_id); } } @@ -939,11 +942,13 @@ impl Compilation { if self.options.builtins.tree_shaking.enable() { self.bailout_module_identifiers = self .module_graph - .modules() + .dependencies() .values() .par_bridge() - .filter_map(|module| { - if module.as_context_module().is_some() { + .filter_map(|dep| { + if dep.as_context_dependency().is_some() + && let Some(module) = self.module_graph.get_module(dep.id()) + { let mut values = vec![(module.identifier(), BailoutFlag::CONTEXT_MODULE)]; if let Some(dependencies) = self .module_graph @@ -960,6 +965,10 @@ impl Compilation { } Some(values) + } else if matches!(dep.dependency_type(), DependencyType::ContainerExposed) + && let Some(module) = self.module_graph.get_module(dep.id()) + { + Some(vec![(module.identifier(), BailoutFlag::CONTAINER_EXPOSED)]) } else { None } diff --git a/crates/rspack_core/src/compiler/queue.rs b/crates/rspack_core/src/compiler/queue.rs index ab48ea72f54..9c45c8a6f39 100644 --- a/crates/rspack_core/src/compiler/queue.rs +++ b/crates/rspack_core/src/compiler/queue.rs @@ -96,6 +96,17 @@ impl WorkerTask for FactorizeTask { .await? .split_into_parts() } + DependencyType::ContainerEntry => { + let factory = crate::mf::ContainerEntryModuleFactory; + 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/dependencies_block.rs b/crates/rspack_core/src/dependencies_block.rs index 2903f34cd73..dabc1b6ce9f 100644 --- a/crates/rspack_core/src/dependencies_block.rs +++ b/crates/rspack_core/src/dependencies_block.rs @@ -23,6 +23,17 @@ impl AsyncDependenciesBlockIdentifier { pub fn new(from: ModuleIdentifier, modifier: Ustr) -> Self { Self { from, modifier } } + + pub fn get<'a>(&self, compilation: &'a Compilation) -> Option<&'a AsyncDependenciesBlock> { + compilation.module_graph.block_by_id(self) + } + + pub fn expect_get<'a>(&self, compilation: &'a Compilation) -> &'a AsyncDependenciesBlock { + compilation + .module_graph + .block_by_id(self) + .expect("should have block") + } } #[derive(Debug, Clone)] diff --git a/crates/rspack_core/src/dependency/dependency_type.rs b/crates/rspack_core/src/dependency/dependency_type.rs index 692da09ae4e..21115f520e3 100644 --- a/crates/rspack_core/src/dependency/dependency_type.rs +++ b/crates/rspack_core/src/dependency/dependency_type.rs @@ -62,6 +62,10 @@ pub enum DependencyType { WasmExportImported, /// static exports StaticExports, + /// container exposed + ContainerExposed, + /// container entry, + ContainerEntry, Custom(Box), // TODO it will increase large layout size } @@ -101,6 +105,8 @@ impl DependencyType { DependencyType::ExportInfoApi => Cow::Borrowed("export info api"), // TODO: mode DependencyType::ImportMetaContext => Cow::Borrowed("import.meta context"), + DependencyType::ContainerExposed => Cow::Borrowed("container exposed"), + DependencyType::ContainerEntry => Cow::Borrowed("container entry"), } } } diff --git a/crates/rspack_core/src/dependency/runtime_template.rs b/crates/rspack_core/src/dependency/runtime_template.rs index 5cfbc9361bb..6a9aa06d67d 100644 --- a/crates/rspack_core/src/dependency/runtime_template.rs +++ b/crates/rspack_core/src/dependency/runtime_template.rs @@ -353,3 +353,11 @@ pub fn weak_error(request: &str) -> String { let msg = format!("Module is not available (weak dependency), request is {request}."); format!("var e = new Error('{msg}'); e.code = 'MODULE_NOT_FOUND'; throw e;") } + +pub fn returning_function(return_value: &str, args: &str) -> String { + format!("function({args}) {{ return {return_value}; }}") +} + +pub fn basic_function(args: &str, body: &str) -> String { + format!("function({args}) {{\n{body}\n}}") +} diff --git a/crates/rspack_core/src/lib.rs b/crates/rspack_core/src/lib.rs index 45e80c58bde..964c7d17c52 100644 --- a/crates/rspack_core/src/lib.rs +++ b/crates/rspack_core/src/lib.rs @@ -7,6 +7,7 @@ use std::sync::atomic::AtomicBool; use std::{fmt, sync::Arc}; mod dependencies_block; +pub mod mf; pub use dependencies_block::{ AsyncDependenciesBlock, AsyncDependenciesBlockIdentifier, DependenciesBlock, }; diff --git a/crates/rspack_core/src/mf/container/container_entry_dependency.rs b/crates/rspack_core/src/mf/container/container_entry_dependency.rs new file mode 100644 index 00000000000..c05e7796d6f --- /dev/null +++ b/crates/rspack_core/src/mf/container/container_entry_dependency.rs @@ -0,0 +1,64 @@ +use super::ExposeOptions; +use crate::{ + AsContextDependency, AsDependencyTemplate, Dependency, DependencyCategory, DependencyId, + DependencyType, ModuleDependency, +}; + +#[derive(Debug, Clone)] +pub struct ContainerEntryDependency { + id: DependencyId, + pub name: String, + pub exposes: Vec<(String, ExposeOptions)>, + pub share_scope: String, + resource_identifier: String, +} + +impl ContainerEntryDependency { + pub fn new(name: String, exposes: Vec<(String, ExposeOptions)>, share_scope: String) -> Self { + let resource_identifier = format!("container-entry-{}", &name); + Self { + id: DependencyId::new(), + name, + exposes, + share_scope, + resource_identifier, + } + } +} + +impl Dependency for ContainerEntryDependency { + fn dependency_debug_name(&self) -> &'static str { + "ContainerEntryDependency" + } + + fn id(&self) -> &DependencyId { + &self.id + } + + fn category(&self) -> &DependencyCategory { + &DependencyCategory::Esm + } + + fn dependency_type(&self) -> &DependencyType { + &DependencyType::ContainerEntry + } + + fn resource_identifier(&self) -> Option<&str> { + Some(&self.resource_identifier) + } +} + +impl ModuleDependency for ContainerEntryDependency { + fn request(&self) -> &str { + &self.resource_identifier + } + + fn user_request(&self) -> &str { + &self.resource_identifier + } + + fn set_request(&mut self, _request: String) {} +} + +impl AsContextDependency for ContainerEntryDependency {} +impl AsDependencyTemplate for ContainerEntryDependency {} diff --git a/crates/rspack_core/src/mf/container/container_entry_module.rs b/crates/rspack_core/src/mf/container/container_entry_module.rs new file mode 100644 index 00000000000..969e1cd27d2 --- /dev/null +++ b/crates/rspack_core/src/mf/container/container_entry_module.rs @@ -0,0 +1,268 @@ +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::{RawSource, Source, SourceExt}; + +use super::{container_exposed_dependency::ContainerExposedDependency, ExposeOptions}; +use crate::{ + basic_function, block_promise, module_raw, returning_function, throw_missing_module_error_block, + AsyncDependenciesBlock, AsyncDependenciesBlockIdentifier, BuildContext, BuildInfo, BuildMeta, + BuildMetaExportsType, BuildResult, ChunkGroupOptions, CodeGenerationResult, Compilation, Context, + DependenciesBlock, DependencyId, GroupOptions, LibIdentOptions, Module, ModuleDependency, + ModuleIdentifier, ModuleType, RuntimeGlobals, RuntimeSpec, SourceType, +}; + +#[derive(Debug)] +pub struct ContainerEntryModule { + blocks: Vec, + dependencies: Vec, + identifier: ModuleIdentifier, + name: String, + exposes: Vec<(String, ExposeOptions)>, + share_scope: String, +} + +impl ContainerEntryModule { + pub fn new(name: String, exposes: Vec<(String, ExposeOptions)>, share_scope: String) -> Self { + Self { + blocks: Vec::new(), + dependencies: Vec::new(), + identifier: ModuleIdentifier::from(format!( + "container entry ({}) {}", + share_scope, + serde_json::to_string(&exposes).expect("should able to json to_string") + )), + name, + exposes, + share_scope, + } + } +} + +impl Identifiable for ContainerEntryModule { + fn identifier(&self) -> Identifier { + self.identifier + } +} + +impl DependenciesBlock for ContainerEntryModule { + 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 ContainerEntryModule { + fn size(&self, _source_type: &SourceType) -> f64 { + 42.0 + } + + fn module_type(&self) -> &ModuleType { + &ModuleType::JsDynamic + } + + fn source_types(&self) -> &[SourceType] { + &[SourceType::JavaScript] + } + + fn original_source(&self) -> Option<&dyn Source> { + None + } + + fn readable_identifier(&self, _context: &Context) -> Cow { + "container entry".into() + } + + fn lib_ident(&self, _options: LibIdentOptions) -> Option> { + Some(format!("webpack/container/entry/{}", self.name).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![]; + for (name, options) in &self.exposes { + let mut block = AsyncDependenciesBlock::new(self.identifier, name); + block.set_group_options(GroupOptions::ChunkGroup(ChunkGroupOptions { + name: options.name.clone(), + })); + for request in options.import.iter() { + let dep = ContainerExposedDependency::new(name.clone(), request.clone()); + block.add_dependency(Box::new(dep)); + } + blocks.push(block); + } + + Ok( + BuildResult { + build_info: BuildInfo { + hash: Some(hash), + strict: true, + ..Default::default() + }, + build_meta: BuildMeta { + exports_type: BuildMetaExportsType::Namespace, + ..Default::default() + }, + dependencies: vec![], + 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::DEFINE_PROPERTY_GETTERS); + code_generation_result + .runtime_requirements + .insert(RuntimeGlobals::HAS_OWN_PROPERTY); + code_generation_result + .runtime_requirements + .insert(RuntimeGlobals::EXPORTS); + let mut getters = vec![]; + for block_id in self.get_blocks() { + let block = block_id.expect_get(compilation); + let modules_iter = block.get_dependencies().iter().map(|dependency_id| { + let dep = compilation + .module_graph + .dependency_by_id(dependency_id) + .expect("should have dependency"); + let dep = dep + .downcast_ref::() + .expect("dependencies of ContainerEntryModule should be ContainerExposedDependency"); + let name = dep.exposed_name.as_str(); + let module = compilation.module_graph.get_module(dependency_id); + let user_request = dep.user_request(); + (name, module, user_request, dependency_id) + }); + let name = modules_iter.clone().next().expect("should have item").0; + let str = if modules_iter + .clone() + .any(|(_, module, _, _)| module.is_none()) + { + throw_missing_module_error_block( + &modules_iter + .map(|(_, _, request, _)| request) + .collect::>() + .join(", "), + ) + } else { + let block_promise = block_promise( + Some(block_id), + &mut code_generation_result.runtime_requirements, + compilation, + ); + let module_raw = returning_function( + &returning_function( + &modules_iter + .map(|(_, _, request, dependency_id)| { + module_raw( + compilation, + &mut code_generation_result.runtime_requirements, + dependency_id, + request, + false, + ) + }) + .collect::>() + .join(", "), + "", + ), + "", + ); + format!("return {}.then({});", block_promise, module_raw) + }; + getters.push(format!( + "{}: {}", + serde_json::to_string(name).expect("should able to json to_string"), + basic_function("", &str) + )) + } + let source = format!( + r#"var moduleMap = {{ + {getters} +}}; +var get = function(module, getScope) {{ + {current_remote_get_scope} = getScope; + getScope = ( + {has_own_property}(moduleMap, module) + ? moduleMap[module]() + : Promise.resolve().then(() => {{ + throw new Error('Module "' + module + '" does not exist in container.'); + }}) + ); + {current_remote_get_scope} = undefined; + return getScope; +}}; +var init = (shareScope, initScope) => {{ + if (!{share_scope_map}) return; + var name = {share_scope} + var oldScope = {share_scope_map}[name]; + if(oldScope && oldScope !== shareScope) throw new Error("Container initialization failed as it has already been initialized with a different share scope"); + {share_scope_map}[name] = shareScope; + return {initialize_sharing}(name, initScope); +}}; + +// This exports getters to disallow modifications +{define_property_getters}(exports, {{ + get: () => (get), + init: () => (init) +}});"#, + getters = getters.join(",\n"), + current_remote_get_scope = RuntimeGlobals::CURRENT_REMOTE_GET_SCOPE, + has_own_property = RuntimeGlobals::HAS_OWN_PROPERTY, + share_scope_map = RuntimeGlobals::SHARE_SCOPE_MAP, + share_scope = + serde_json::to_string(&self.share_scope).expect("should able to json to_string"), + initialize_sharing = RuntimeGlobals::INITIALIZE_SHARING, + define_property_getters = RuntimeGlobals::DEFINE_PROPERTY_GETTERS, + ); + code_generation_result = + code_generation_result.with_javascript(RawSource::from(source).boxed()); + Ok(code_generation_result) + } +} + +impl Hash for ContainerEntryModule { + fn hash(&self, state: &mut H) { + "__rspack_internal__ContainerEntryModule".hash(state); + self.identifier().hash(state); + } +} + +impl PartialEq for ContainerEntryModule { + fn eq(&self, other: &Self) -> bool { + self.identifier() == other.identifier() + } +} + +impl Eq for ContainerEntryModule {} diff --git a/crates/rspack_core/src/mf/container/container_entry_module_factory.rs b/crates/rspack_core/src/mf/container/container_entry_module_factory.rs new file mode 100644 index 00000000000..2ffcd830fbe --- /dev/null +++ b/crates/rspack_core/src/mf/container/container_entry_module_factory.rs @@ -0,0 +1,37 @@ +// TODO: move to rspack_plugin_mf + +use async_trait::async_trait; +use rspack_error::{internal_error, IntoTWithDiagnosticArray, Result, TWithDiagnosticArray}; + +use super::{ + container_entry_dependency::ContainerEntryDependency, + container_entry_module::ContainerEntryModule, +}; +use crate::{ModuleFactory, ModuleFactoryCreateData, ModuleFactoryResult}; + +pub struct ContainerEntryModuleFactory; + +#[async_trait] +impl ModuleFactory for ContainerEntryModuleFactory { + async fn create( + mut self, + data: ModuleFactoryCreateData, + ) -> Result> { + let dep = data + .dependency + .downcast_ref::() + .ok_or_else(|| { + internal_error!( + "dependency of ContainerEntryModuleFactory should be ContainerEntryDependency" + ) + })?; + Ok( + ModuleFactoryResult::new(Box::new(ContainerEntryModule::new( + dep.name.clone(), + dep.exposes.clone(), + dep.share_scope.clone(), + ))) + .with_empty_diagnostic(), + ) + } +} diff --git a/crates/rspack_core/src/mf/container/container_exposed_dependency.rs b/crates/rspack_core/src/mf/container/container_exposed_dependency.rs new file mode 100644 index 00000000000..2c32ea565c2 --- /dev/null +++ b/crates/rspack_core/src/mf/container/container_exposed_dependency.rs @@ -0,0 +1,61 @@ +use crate::{ + AsContextDependency, AsDependencyTemplate, Dependency, DependencyCategory, DependencyId, + DependencyType, ModuleDependency, +}; + +#[derive(Debug, Clone)] +pub struct ContainerExposedDependency { + id: DependencyId, + request: String, + pub exposed_name: String, + resource_identifier: String, +} + +impl ContainerExposedDependency { + pub fn new(exposed_name: String, request: String) -> Self { + let resource_identifier = format!("exposed dependency {}={}", exposed_name, request); + Self { + id: DependencyId::new(), + request, + exposed_name, + resource_identifier, + } + } +} + +impl Dependency for ContainerExposedDependency { + fn dependency_debug_name(&self) -> &'static str { + "ContainerExposedDependency" + } + + fn id(&self) -> &DependencyId { + &self.id + } + + fn category(&self) -> &DependencyCategory { + &DependencyCategory::Esm + } + + fn dependency_type(&self) -> &DependencyType { + &DependencyType::ContainerExposed + } + + fn resource_identifier(&self) -> Option<&str> { + Some(&self.resource_identifier) + } +} + +impl ModuleDependency for ContainerExposedDependency { + fn request(&self) -> &str { + &self.request + } + + fn user_request(&self) -> &str { + &self.request + } + + fn set_request(&mut self, _request: String) {} +} + +impl AsContextDependency for ContainerExposedDependency {} +impl AsDependencyTemplate for ContainerExposedDependency {} diff --git a/crates/rspack_core/src/mf/container/container_plugin.rs b/crates/rspack_core/src/mf/container/container_plugin.rs new file mode 100644 index 00000000000..77bea09fb2c --- /dev/null +++ b/crates/rspack_core/src/mf/container/container_plugin.rs @@ -0,0 +1,68 @@ +use async_trait::async_trait; +use serde::Serialize; + +use super::container_entry_dependency::ContainerEntryDependency; +use crate::{ + Compilation, Dependency, EntryOptions, EntryRuntime, Filename, LibraryOptions, MakeParam, Plugin, + PluginContext, PluginMakeHookOutput, +}; + +#[derive(Debug)] +pub struct ContainerPluginOptions { + pub name: String, + pub share_scope: String, + pub library: LibraryOptions, + pub runtime: Option, + pub filename: Option, + pub exposes: Vec<(String, ExposeOptions)>, +} + +#[derive(Debug, Clone, Serialize)] +pub struct ExposeOptions { + pub name: Option, + pub import: Vec, +} + +#[derive(Debug)] +pub struct ContainerPlugin { + options: ContainerPluginOptions, +} + +impl ContainerPlugin { + pub fn new(options: ContainerPluginOptions) -> Self { + Self { options } + } +} + +#[async_trait] +impl Plugin for ContainerPlugin { + fn name(&self) -> &'static str { + "rspack.ContainerPlugin" + } + + async fn make( + &self, + _ctx: PluginContext, + compilation: &mut Compilation, + param: &mut MakeParam, + ) -> PluginMakeHookOutput { + let dep = ContainerEntryDependency::new( + self.options.name.clone(), + self.options.exposes.clone(), + self.options.share_scope.clone(), + ); + let dependency_id = *dep.id(); + compilation.add_entry( + Box::new(dep), + EntryOptions { + name: Some(self.options.name.clone()), + runtime: self.options.runtime.clone(), + filename: self.options.filename.clone(), + library: Some(self.options.library.clone()), + ..Default::default() + }, + ); + param.add_force_build_dependency(dependency_id, None); + Ok(()) + } +} diff --git a/crates/rspack_core/src/mf/container/mod.rs b/crates/rspack_core/src/mf/container/mod.rs new file mode 100644 index 00000000000..a900030823f --- /dev/null +++ b/crates/rspack_core/src/mf/container/mod.rs @@ -0,0 +1,8 @@ +mod container_entry_dependency; +mod container_entry_module; +mod container_entry_module_factory; +mod container_exposed_dependency; +mod container_plugin; + +pub use container_entry_module_factory::ContainerEntryModuleFactory; +pub use container_plugin::{ContainerPlugin, ContainerPluginOptions, ExposeOptions}; diff --git a/crates/rspack_core/src/mf/mod.rs b/crates/rspack_core/src/mf/mod.rs new file mode 100644 index 00000000000..1bbe8f5654a --- /dev/null +++ b/crates/rspack_core/src/mf/mod.rs @@ -0,0 +1,3 @@ +// TODO: move to rspack_plugin_mf once we remove the hardcoded DependencyType => ModuleFactory +mod container; +pub use container::*; diff --git a/crates/rspack_core/src/module.rs b/crates/rspack_core/src/module.rs index 7b5523acf96..468c6675930 100644 --- a/crates/rspack_core/src/module.rs +++ b/crates/rspack_core/src/module.rs @@ -109,7 +109,6 @@ impl Display for ExportsArgument { #[derive(Debug, Default, Clone, Hash)] pub struct BuildMeta { - pub strict: bool, pub strict_harmony_module: bool, pub has_top_level_await: bool, pub esm: bool, diff --git a/crates/rspack_core/src/module_graph/mod.rs b/crates/rspack_core/src/module_graph/mod.rs index 6c4b205c4bb..d6c4c251955 100644 --- a/crates/rspack_core/src/module_graph/mod.rs +++ b/crates/rspack_core/src/module_graph/mod.rs @@ -125,6 +125,10 @@ impl ModuleGraph { self.blocks.get(block_id) } + pub fn dependencies(&self) -> &HashMap { + &self.dependencies + } + pub fn add_dependency(&mut self, dependency: BoxDependency) { self.dependencies.insert(*dependency.id(), dependency); } @@ -155,10 +159,9 @@ impl ModuleGraph { self.dependency_id_to_module_identifier.get(id) } - pub fn get_module(&self, id: &DependencyId) -> Option<&BoxModule> { - self - .module_identifier_by_dependency_id(id) - .and_then(|m| self.module_by_identifier(m)) + pub fn get_module(&self, dependency_id: &DependencyId) -> Option<&BoxModule> { + let connection = self.connection_by_dependency(dependency_id)?; + self.module_by_identifier(&connection.module_identifier) } /// Add a connection between two module graph modules, if a connection exists, then it will be reused. diff --git a/crates/rspack_core/src/runtime_globals.rs b/crates/rspack_core/src/runtime_globals.rs index 8fb0f892e96..e285b9e7b25 100644 --- a/crates/rspack_core/src/runtime_globals.rs +++ b/crates/rspack_core/src/runtime_globals.rs @@ -222,6 +222,12 @@ bitflags! { const SYSTEM_CONTEXT = 1 << 49; const THIS_AS_EXPORTS = 1 << 50; + + const CURRENT_REMOTE_GET_SCOPE = 1 << 51; + + const SHARE_SCOPE_MAP = 1 << 52; + + const INITIALIZE_SHARING = 1 << 53; } } @@ -293,6 +299,9 @@ impl RuntimeGlobals { R::NODE_MODULE_DECORATOR => "__webpack_require__.nmd", R::SYSTEM_CONTEXT => "__webpack_require__.y", R::THIS_AS_EXPORTS => "top-level-this-exports", + R::CURRENT_REMOTE_GET_SCOPE => "__webpack_require__.R", + R::SHARE_SCOPE_MAP => "__webpack_require__.S", + R::INITIALIZE_SHARING => "__webpack_require__.I", r => panic!( "Unexpected flag `{r:?}`. RuntimeGlobals should only be printed for one single flag." ), diff --git a/crates/rspack_core/src/tree_shaking/mod.rs b/crates/rspack_core/src/tree_shaking/mod.rs index e9e58a6202f..8d8e2856d61 100644 --- a/crates/rspack_core/src/tree_shaking/mod.rs +++ b/crates/rspack_core/src/tree_shaking/mod.rs @@ -67,6 +67,7 @@ bitflags::bitflags! { const COMMONJS_EXPORTS = 1 << 2; const DYNAMIC_IMPORT = 1 << 3; const CONTEXT_MODULE = 1 << 4; + const CONTAINER_EXPOSED = 1 << 5; } } diff --git a/crates/rspack_core/src/utils/mod.rs b/crates/rspack_core/src/utils/mod.rs index 7872cbe4222..660e6c6c8b7 100644 --- a/crates/rspack_core/src/utils/mod.rs +++ b/crates/rspack_core/src/utils/mod.rs @@ -6,6 +6,9 @@ use rustc_hash::FxHashMap as HashMap; mod identifier; pub use identifier::*; +mod runtime; +pub use runtime::*; + mod property_name; pub use property_name::*; diff --git a/crates/rspack_core/src/utils/runtime.rs b/crates/rspack_core/src/utils/runtime.rs new file mode 100644 index 00000000000..ba4dbfbbe4c --- /dev/null +++ b/crates/rspack_core/src/utils/runtime.rs @@ -0,0 +1,9 @@ +use std::sync::Arc; + +use crate::{EntryOptions, RuntimeSpec}; + +pub fn get_entry_runtime(name: &str, options: &EntryOptions) -> RuntimeSpec { + RuntimeSpec::from_iter([Arc::from( + options.runtime.clone().unwrap_or_else(|| name.to_string()), + )]) +} diff --git a/crates/rspack_plugin_entry/src/lib.rs b/crates/rspack_plugin_entry/src/lib.rs index 67d4fcc1f4f..0dcc5309f78 100644 --- a/crates/rspack_plugin_entry/src/lib.rs +++ b/crates/rspack_plugin_entry/src/lib.rs @@ -39,10 +39,9 @@ impl Plugin for EntryPlugin { self.entry_request.clone(), self.context.clone(), )); - let dependency_id = dependency.id(); - compilation.add_entry(*dependency_id, self.options.clone()); - param.add_force_build_dependency(*dependency_id, None); - compilation.module_graph.add_dependency(dependency); + let dependency_id = *dependency.id(); + compilation.add_entry(dependency, self.options.clone()); + param.add_force_build_dependency(dependency_id, None); Ok(()) } } diff --git a/crates/rspack_plugin_library/src/export_property_library_plugin.rs b/crates/rspack_plugin_library/src/export_property_library_plugin.rs index 40da566ed1b..cf67afc529b 100644 --- a/crates/rspack_plugin_library/src/export_property_library_plugin.rs +++ b/crates/rspack_plugin_library/src/export_property_library_plugin.rs @@ -17,11 +17,15 @@ struct ExportPropertyLibraryPluginParsed<'a> { #[derive(Debug, Default)] pub struct ExportPropertyLibraryPlugin { library_type: LibraryType, + _ns_object_used: bool, } impl ExportPropertyLibraryPlugin { - pub fn new(library_type: LibraryType) -> Self { - Self { library_type } + pub fn new(library_type: LibraryType, ns_object_used: bool) -> Self { + Self { + library_type, + _ns_object_used: ns_object_used, + } } fn parse_options<'a>( diff --git a/crates/rspack_plugin_library/src/lib.rs b/crates/rspack_plugin_library/src/lib.rs index 92b3b5dda4b..e80ae3ebe19 100644 --- a/crates/rspack_plugin_library/src/lib.rs +++ b/crates/rspack_plugin_library/src/lib.rs @@ -17,6 +17,7 @@ pub use system_library_plugin::SystemLibraryPlugin; pub use umd_library_plugin::UmdLibraryPlugin; pub fn enable_library_plugin(library_type: String, plugins: &mut Vec) { + let ns_object_used = library_type != "module"; match library_type.as_str() { "var" => plugins.push( AssignLibraryPlugin::new(AssignLibraryPluginOptions { @@ -99,19 +100,21 @@ pub fn enable_library_plugin(library_type: String, plugins: &mut Vec) .boxed(), ), "umd" | "umd2" => { - plugins.push(ExportPropertyLibraryPlugin::default().boxed()); + plugins.push(ExportPropertyLibraryPlugin::new(library_type.clone(), ns_object_used).boxed()); plugins.push(UmdLibraryPlugin::new("umd2" == library_type, library_type).boxed()); } "amd" | "amd-require" => { - plugins.push(ExportPropertyLibraryPlugin::default().boxed()); + plugins.push(ExportPropertyLibraryPlugin::new(library_type.clone(), ns_object_used).boxed()); plugins.push(AmdLibraryPlugin::new("amd-require" == library_type, library_type).boxed()); } "module" => { - plugins.push(ExportPropertyLibraryPlugin::default().boxed()); + plugins.push(ExportPropertyLibraryPlugin::new(library_type.clone(), ns_object_used).boxed()); plugins.push(ModuleLibraryPlugin::default().boxed()); } "system" => { - plugins.push(ExportPropertyLibraryPlugin::default().boxed()); + plugins.push( + ExportPropertyLibraryPlugin::new(library_type.clone(), library_type != "module").boxed(), + ); plugins.push(SystemLibraryPlugin::default().boxed()); } _ => {} diff --git a/packages/rspack-dev-server/tests/normalizeOptions.test.ts b/packages/rspack-dev-server/tests/normalizeOptions.test.ts index 5e1cbcbb3ad..f7a3522a811 100644 --- a/packages/rspack-dev-server/tests/normalizeOptions.test.ts +++ b/packages/rspack-dev-server/tests/normalizeOptions.test.ts @@ -175,7 +175,7 @@ async function getAdditionEntries( await server.start(); const entries = compiler.builtinPlugins .filter(p => p.name === "EntryPlugin") - .map(p => p.raw().options) + .map(p => p.options) .reduce((acc, cur: any) => { const name = cur.options.name; const request = cur.entry; diff --git a/packages/rspack/src/Compiler.ts b/packages/rspack/src/Compiler.ts index 14a150b59f4..8fa00f5e820 100644 --- a/packages/rspack/src/Compiler.ts +++ b/packages/rspack/src/Compiler.ts @@ -58,7 +58,7 @@ class Compiler { webpack = rspack; // @ts-expect-error compilation: Compilation; - builtinPlugins: RspackBuiltinPlugin[]; + builtinPlugins: binding.BuiltinPlugin[]; root: Compiler; running: boolean; idle: boolean; @@ -241,7 +241,7 @@ class Compiler { this.#_instance = new instanceBinding.Rspack( rawOptions, - this.builtinPlugins.map(bp => bp.raw()), + this.builtinPlugins, { beforeCompile: this.#beforeCompile.bind(this), afterCompile: this.#afterCompile.bind(this), @@ -1101,7 +1101,7 @@ class Compiler { return source.buffer(); } - __internal__registerBuiltinPlugin(plugin: RspackBuiltinPlugin) { + __internal__registerBuiltinPlugin(plugin: binding.BuiltinPlugin) { this.builtinPlugins.push(plugin); } } diff --git a/packages/rspack/src/builtin-plugin/EntryPlugin.ts b/packages/rspack/src/builtin-plugin/EntryPlugin.ts index 6a4cf13276c..58a6f684c45 100644 --- a/packages/rspack/src/builtin-plugin/EntryPlugin.ts +++ b/packages/rspack/src/builtin-plugin/EntryPlugin.ts @@ -5,9 +5,12 @@ import { EntryRuntime, Filename, LibraryOptions, - PublicPath -} from ".."; -import { getRawLibrary } from "../config"; + PublicPath, + getRawChunkLoading, + getRawEntryRuntime, + getRawLibrary +} from "../config"; +import { isNil } from "../util"; export type EntryOptions = { name?: string; @@ -43,8 +46,10 @@ function getRawEntryOptions(entry: EntryOptions): RawEntryOptions { name: entry.name, publicPath: entry.publicPath, baseUri: entry.baseUri, - runtime: runtime === false ? undefined : runtime, - chunkLoading: chunkLoading === false ? "false" : chunkLoading, + runtime: !isNil(runtime) ? getRawEntryRuntime(runtime) : undefined, + chunkLoading: !isNil(chunkLoading) + ? getRawChunkLoading(chunkLoading) + : undefined, asyncChunks: entry.asyncChunks, filename: entry.filename, library: entry.library && getRawLibrary(entry.library) diff --git a/packages/rspack/src/builtin-plugin/base.ts b/packages/rspack/src/builtin-plugin/base.ts index 6f909cc203d..3b748585e87 100644 --- a/packages/rspack/src/builtin-plugin/base.ts +++ b/packages/rspack/src/builtin-plugin/base.ts @@ -25,14 +25,15 @@ export enum BuiltinPluginName { SwcCssMinimizerRspackPlugin = "SwcCssMinimizerRspackPlugin", LimitChunkCountPlugin = "LimitChunkCountPlugin", WebWorkerTemplatePlugin = "WebWorkerTemplatePlugin", - MergeDuplicateChunksPlugin = "MergeDuplicateChunksPlugin" + MergeDuplicateChunksPlugin = "MergeDuplicateChunksPlugin", + ContainerPlugin = "ContainerPlugin" } export abstract class RspackBuiltinPlugin implements RspackPluginInstance { - abstract raw(): binding.BuiltinPlugin; + abstract raw(compiler: Compiler): binding.BuiltinPlugin; abstract name: BuiltinPluginName; apply(compiler: Compiler) { - compiler.__internal__registerBuiltinPlugin(this); + compiler.__internal__registerBuiltinPlugin(this.raw(compiler)); } } diff --git a/packages/rspack/src/config/adapter.ts b/packages/rspack/src/config/adapter.ts index dcceccae71e..50720428782 100644 --- a/packages/rspack/src/config/adapter.ts +++ b/packages/rspack/src/config/adapter.ts @@ -17,7 +17,8 @@ import type { RawFuncUseCtx, RawRspackFuture, RawLibraryName, - JsModule + RawLibraryOptions, + JsModule, } from "@rspack/binding"; import assert from "assert"; import { Compiler } from "../Compiler"; @@ -56,7 +57,8 @@ import { RspackFutureOptions, JavascriptParserOptions, LibraryName, - OptimizationSplitChunksNameFunction + EntryRuntime, + ChunkLoading } from "./zod"; import { ExperimentsNormalized, @@ -201,7 +203,7 @@ function getRawOutput(output: OutputNormalized): RawOptions["output"] { assetModuleFilename: output.assetModuleFilename!, filename: output.filename!, chunkFilename: output.chunkFilename!, - chunkLoading: chunkLoading === false ? "false" : chunkLoading, + chunkLoading: getRawChunkLoading(chunkLoading), crossOriginLoading: getRawCrossOriginLoading(output.crossOriginLoading!), cssFilename: output.cssFilename!, cssChunkFilename: output.cssChunkFilename!, @@ -236,9 +238,7 @@ function getRawOutput(output: OutputNormalized): RawOptions["output"] { }; } -export function getRawLibrary( - library: LibraryOptions -): RawOptions["output"]["library"] { +export function getRawLibrary(library: LibraryOptions): RawLibraryOptions { const { type, name, @@ -889,3 +889,11 @@ function getRawStats(stats: StatsValue): RawOptions["stats"] { colors: statsOptions.colors ?? false }; } + +export function getRawEntryRuntime(runtime: EntryRuntime) { + return runtime === false ? undefined : runtime; +} + +export function getRawChunkLoading(chunkLoading: ChunkLoading) { + return chunkLoading === false ? "false" : chunkLoading; +} diff --git a/packages/rspack/src/container/ContainerPlugin.ts b/packages/rspack/src/container/ContainerPlugin.ts new file mode 100644 index 00000000000..1a837d7303e --- /dev/null +++ b/packages/rspack/src/container/ContainerPlugin.ts @@ -0,0 +1,75 @@ +import { RawContainerPluginOptions, BuiltinPlugin } from "@rspack/binding"; +import { BuiltinPluginName, RspackBuiltinPlugin } from "../builtin-plugin/base"; +import { + EntryRuntime, + Filename, + LibraryOptions, + getRawEntryRuntime, + getRawLibrary +} from "../config"; +import { isNil } from "../util"; +import { parseOptions } from "../container/options"; +import { Compiler } from "../Compiler"; + +export type ContainerPluginOptions = { + exposes: Exposes; + filename?: Filename; + library?: LibraryOptions; + name: string; + runtime?: EntryRuntime; + shareScope?: string; +}; +export type Exposes = (ExposesItem | ExposesObject)[] | ExposesObject; +export type ExposesItem = string; +export type ExposesItems = ExposesItem[]; +export type ExposesObject = { + [k: string]: ExposesConfig | ExposesItem | ExposesItems; +}; +export type ExposesConfig = { + import: ExposesItem | ExposesItems; + name?: string; +}; + +export class ContainerPlugin extends RspackBuiltinPlugin { + name = BuiltinPluginName.ContainerPlugin; + _options: RawContainerPluginOptions; + _library; + + constructor(options: ContainerPluginOptions) { + super(); + const library = (this._library = options.library || { + type: "var", + name: options.name + }); + const runtime = options.runtime; + this._options = { + name: options.name, + shareScope: options.shareScope || "default", + library: getRawLibrary(library), + runtime: !isNil(runtime) ? getRawEntryRuntime(runtime) : undefined, + filename: options.filename, + exposes: parseOptions( + options.exposes, + item => ({ + import: Array.isArray(item) ? item : [item], + name: undefined + }), + item => ({ + import: Array.isArray(item.import) ? item.import : [item.import], + name: item.name || undefined + }) + ).map(([key, r]) => ({ key, ...r })) + }; + } + + raw(compiler: Compiler): BuiltinPlugin { + const library = this._library; + if (!compiler.options.output.enabledLibraryTypes!.includes(library.type)) { + compiler.options.output.enabledLibraryTypes!.push(library.type); + } + return { + name: this.name as any, + options: this._options + }; + } +} diff --git a/packages/rspack/src/container/ModuleFederationPlugin.ts b/packages/rspack/src/container/ModuleFederationPlugin.ts new file mode 100644 index 00000000000..a44ae1eeef6 --- /dev/null +++ b/packages/rspack/src/container/ModuleFederationPlugin.ts @@ -0,0 +1,70 @@ +import { Compiler } from "../Compiler"; +import { ContainerPlugin, Exposes } from "./ContainerPlugin"; +import { LibraryOptions, EntryRuntime } from "../config"; + +export interface ModuleFederationPluginOptions { + exposes?: Exposes; + filename?: string; + library?: LibraryOptions; + name: string; + // remoteType?: ExternalsType; + // remotes?: Remotes; + runtime?: EntryRuntime; + shareScope?: string; + // shared?: Shared; +} + +export class ModuleFederationPlugin { + constructor(private _options: ModuleFederationPluginOptions) {} + + apply(compiler: Compiler) { + const { _options: options } = this; + const library = options.library || { type: "var", name: options.name }; + // const remoteType = + // options.remoteType || + // (options.library && isValidExternalsType(options.library.type) + // ? /** @type {ExternalsType} */ (options.library.type) + // : "script"); + if ( + library && + !compiler.options.output.enabledLibraryTypes!.includes(library.type) + ) { + compiler.options.output.enabledLibraryTypes!.push(library.type); + } + compiler.hooks.afterPlugins.tap("ModuleFederationPlugin", () => { + if ( + options.exposes && + (Array.isArray(options.exposes) + ? options.exposes.length > 0 + : Object.keys(options.exposes).length > 0) + ) { + new ContainerPlugin({ + name: options.name, + library, + filename: options.filename, + runtime: options.runtime, + shareScope: options.shareScope, + exposes: options.exposes + }).apply(compiler); + } + // if ( + // options.remotes && + // (Array.isArray(options.remotes) + // ? options.remotes.length > 0 + // : Object.keys(options.remotes).length > 0) + // ) { + // new ContainerReferencePlugin({ + // remoteType, + // shareScope: options.shareScope, + // remotes: options.remotes + // }).apply(compiler); + // } + // if (options.shared) { + // new SharePlugin({ + // shared: options.shared, + // shareScope: options.shareScope + // }).apply(compiler); + // } + }); + } +} diff --git a/packages/rspack/src/container/options.ts b/packages/rspack/src/container/options.ts new file mode 100644 index 00000000000..8c1557b0ff2 --- /dev/null +++ b/packages/rspack/src/container/options.ts @@ -0,0 +1,52 @@ +type ContainerOptionsFormat = + | (string | Record)[] + | Record; + +const process = ( + options: ContainerOptionsFormat, + normalizeSimple: (a: string | string[], b: string) => N, + normalizeOptions: (a: T, b: string) => N, + fn: (a: string, b: N) => void +) => { + const array = (items: (string | Record)[]) => { + for (const item of items) { + if (typeof item === "string") { + fn(item, normalizeSimple(item, item)); + } else if (item && typeof item === "object") { + object(item); + } else { + throw new Error("Unexpected options format"); + } + } + }; + const object = (obj: Record) => { + for (const [key, value] of Object.entries(obj)) { + if (typeof value === "string" || Array.isArray(value)) { + fn(key, normalizeSimple(value, key)); + } else { + fn(key, normalizeOptions(value, key)); + } + } + }; + if (!options) { + return; + } else if (Array.isArray(options)) { + array(options); + } else if (typeof options === "object") { + object(options); + } else { + throw new Error("Unexpected options format"); + } +}; + +export const parseOptions = ( + options: ContainerOptionsFormat, + normalizeSimple: (a: string | string[], b: string) => R, + normalizeOptions: (a: T, b: string) => R +) => { + const items: [string, R][] = []; + process(options, normalizeSimple, normalizeOptions, (key, value) => { + items.push([key, value]); + }); + return items; +}; diff --git a/packages/rspack/src/exports.ts b/packages/rspack/src/exports.ts index 4850f7d02b4..e1bb917a246 100644 --- a/packages/rspack/src/exports.ts +++ b/packages/rspack/src/exports.ts @@ -119,6 +119,19 @@ export const webworker = { WebWorkerTemplatePlugin }; import { LimitChunkCountPlugin } from "./builtin-plugin"; export const optimize = { LimitChunkCountPlugin }; +import { ContainerPlugin } from "./container/ContainerPlugin"; +import { ModuleFederationPlugin } from "./container/ModuleFederationPlugin"; +export type { ModuleFederationPluginOptions } from "./container/ModuleFederationPlugin"; +export type { + ContainerPluginOptions, + Exposes, + ExposesItem, + ExposesItems, + ExposesObject, + ExposesConfig +} from "./container/ContainerPlugin"; +export const container = { ContainerPlugin, ModuleFederationPlugin }; + ///// Rspack Postfixed Internal Plugins ///// export { HtmlRspackPlugin } from "./builtin-plugin"; export type { HtmlRspackPluginOptions } from "./builtin-plugin"; diff --git a/webpack-test/configCases/container/container-entry/test.filter.js b/webpack-test/configCases/container/container-entry/test.filter.js deleted file mode 100644 index 3be456dcd23..00000000000 --- a/webpack-test/configCases/container/container-entry/test.filter.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = () => {return false} \ No newline at end of file diff --git a/webpack-test/configCases/container/container-entry/webpack.config.js b/webpack-test/configCases/container/container-entry/webpack.config.js index e9bfb1ce811..bcc032caf68 100644 --- a/webpack-test/configCases/container/container-entry/webpack.config.js +++ b/webpack-test/configCases/container/container-entry/webpack.config.js @@ -1,4 +1,4 @@ -const { ContainerPlugin } = require("../../../../").container; +const { ContainerPlugin } = require("../../../../packages/rspack").container; /** @type {import("@rspack/core").Configuration} */ module.exports = {