diff --git a/crates/node_binding/binding.d.ts b/crates/node_binding/binding.d.ts index e8ce45a471f9..908b56bb0b0a 100644 --- a/crates/node_binding/binding.d.ts +++ b/crates/node_binding/binding.d.ts @@ -143,6 +143,9 @@ export const enum BuiltinPluginName { WebWorkerTemplatePlugin = 'WebWorkerTemplatePlugin', MergeDuplicateChunksPlugin = 'MergeDuplicateChunksPlugin', ContainerPlugin = 'ContainerPlugin', + ContainerReferencePlugin = 'ContainerReferencePlugin', + ShareRuntimePlugin = 'ShareRuntimePlugin', + MFScopeRuntimePlugin = 'MFScopeRuntimePlugin', HttpExternalsRspackPlugin = 'HttpExternalsRspackPlugin', CopyRspackPlugin = 'CopyRspackPlugin', HtmlRspackPlugin = 'HtmlRspackPlugin', @@ -487,13 +490,6 @@ export interface JsStatsWarning { formatted: string } -export interface NodeFS { - writeFile: (...args: any[]) => any - removeFile: (...args: any[]) => any - mkdir: (...args: any[]) => any - mkdirp: (...args: any[]) => any -} - export interface PathData { filename?: string hash?: string @@ -622,6 +618,12 @@ export interface RawContainerPluginOptions { exposes: Array } +export interface RawContainerReferencePluginOptions { + remoteType: string + remotes: Array + shareScope?: string +} + export interface RawCopyGlobOptions { caseSensitiveMatch?: boolean dot?: boolean @@ -1020,6 +1022,12 @@ export interface RawRelayConfig { language: 'javascript' | 'typescript' | 'flow' } +export interface RawRemoteOptions { + key: string + external: Array + shareScope: string +} + export interface RawResolveOptions { preferRelative?: boolean extensions?: Array @@ -1129,11 +1137,3 @@ export function registerGlobalTrace(filter: string, layer: "chrome" | "logger", /** Builtin loader runner */ export function runBuiltinLoader(builtin: string, options: string | undefined | null, loaderContext: JsLoaderContext): Promise -export interface ThreadsafeNodeFS { - writeFile: (...args: any[]) => any - removeFile: (...args: any[]) => any - mkdir: (...args: any[]) => any - mkdirp: (...args: any[]) => any - removeDirAll: (...args: any[]) => any -} - 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 9fc732116807..21cf01a9afd8 100644 --- a/crates/rspack_binding_options/src/options/raw_builtins/mod.rs +++ b/crates/rspack_binding_options/src/options/raw_builtins/mod.rs @@ -13,7 +13,11 @@ use napi::{ }; use napi_derive::napi; use rspack_core::{ - mf::ContainerPlugin, BoxPlugin, Define, DefinePlugin, PluginExt, Provide, ProvidePlugin, + mf::{ + container_plugin::ContainerPlugin, container_reference_plugin::ContainerReferencePlugin, + mf_scope_runtime_plugin::MFScopeRuntimePlugin, share_runtime_plugin::ShareRuntimePlugin, + }, + BoxPlugin, Define, DefinePlugin, PluginExt, Provide, ProvidePlugin, }; use rspack_error::Result; use rspack_napi_shared::NapiResultExt; @@ -38,6 +42,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; pub use self::{ raw_banner::RawBannerPluginOptions, raw_copy::RawCopyRspackPluginOptions, raw_html::RawHtmlRspackPluginOptions, raw_limit_chunk_count::RawLimitChunkCountPluginOptions, @@ -72,6 +77,9 @@ pub enum BuiltinPluginName { WebWorkerTemplatePlugin, MergeDuplicateChunksPlugin, ContainerPlugin, + ContainerReferencePlugin, + ShareRuntimePlugin, + MFScopeRuntimePlugin, // rspack specific plugins HttpExternalsRspackPlugin, @@ -184,6 +192,18 @@ impl RawOptionsApply for BuiltinPlugin { .boxed(), ); } + BuiltinPluginName::ContainerReferencePlugin => { + plugins.push( + ContainerReferencePlugin::new( + downcast_into::(self.options)?.into(), + ) + .boxed(), + ); + } + BuiltinPluginName::ShareRuntimePlugin => plugins.push(ShareRuntimePlugin::default().boxed()), + BuiltinPluginName::MFScopeRuntimePlugin => { + plugins.push(MFScopeRuntimePlugin::default().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 2b3bd729e50d..da4b72ed654e 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,5 +1,8 @@ use napi_derive::napi; -use rspack_core::mf::{ContainerPluginOptions, ExposeOptions}; +use rspack_core::mf::{ + container_plugin::{ContainerPluginOptions, ExposeOptions}, + container_reference_plugin::{ContainerReferencePluginOptions, RemoteOptions}, +}; use crate::RawLibraryOptions; @@ -46,3 +49,41 @@ impl From for (String, ExposeOptions) { ) } } + +#[derive(Debug)] +#[napi(object)] +pub struct RawContainerReferencePluginOptions { + pub remote_type: String, + pub remotes: Vec, + pub share_scope: Option, +} + +impl From for ContainerReferencePluginOptions { + fn from(value: RawContainerReferencePluginOptions) -> Self { + Self { + remote_type: value.remote_type, + remotes: value.remotes.into_iter().map(|e| e.into()).collect(), + share_scope: value.share_scope, + } + } +} + +#[derive(Debug)] +#[napi(object)] +pub struct RawRemoteOptions { + pub key: String, + pub external: Vec, + pub share_scope: String, +} + +impl From for (String, RemoteOptions) { + fn from(value: RawRemoteOptions) -> Self { + ( + value.key, + RemoteOptions { + external: value.external, + share_scope: value.share_scope, + }, + ) + } +} diff --git a/crates/rspack_core/src/compiler/queue.rs b/crates/rspack_core/src/compiler/queue.rs index 9c45c8a6f392..a935187e03b8 100644 --- a/crates/rspack_core/src/compiler/queue.rs +++ b/crates/rspack_core/src/compiler/queue.rs @@ -97,7 +97,7 @@ impl WorkerTask for FactorizeTask { .split_into_parts() } DependencyType::ContainerEntry => { - let factory = crate::mf::ContainerEntryModuleFactory; + let factory = crate::mf::container_entry_module_factory::ContainerEntryModuleFactory; factory .create(ModuleFactoryCreateData { resolve_options: self.resolve_options, diff --git a/crates/rspack_core/src/dependency/dependency_type.rs b/crates/rspack_core/src/dependency/dependency_type.rs index 21115f520e3f..90b9c5ba2842 100644 --- a/crates/rspack_core/src/dependency/dependency_type.rs +++ b/crates/rspack_core/src/dependency/dependency_type.rs @@ -66,6 +66,8 @@ pub enum DependencyType { ContainerExposed, /// container entry, ContainerEntry, + /// remote to external, + RemoteToExternal, Custom(Box), // TODO it will increase large layout size } @@ -107,6 +109,7 @@ impl DependencyType { DependencyType::ImportMetaContext => Cow::Borrowed("import.meta context"), DependencyType::ContainerExposed => Cow::Borrowed("container exposed"), DependencyType::ContainerEntry => Cow::Borrowed("container entry"), + DependencyType::RemoteToExternal => Cow::Borrowed("remote to external"), } } } diff --git a/crates/rspack_core/src/dependency/runtime_template.rs b/crates/rspack_core/src/dependency/runtime_template.rs index 6a9aa06d67d8..a917e03e0314 100644 --- a/crates/rspack_core/src/dependency/runtime_template.rs +++ b/crates/rspack_core/src/dependency/runtime_template.rs @@ -247,7 +247,12 @@ pub fn module_namespace_promise( } runtime_requirements.insert(RuntimeGlobals::CREATE_FAKE_NAMESPACE_OBJECT); if matches!( - compilation.module_graph.is_async(&module.identifier()), + compilation.module_graph.is_async( + &compilation + .module_graph + .module_identifier_by_dependency_id(dep_id) + .expect("should have module") + ), Some(true) ) { if let Some(header) = header { diff --git a/crates/rspack_core/src/external_module.rs b/crates/rspack_core/src/external_module.rs index 429a52dacc35..ce95def7d618 100644 --- a/crates/rspack_core/src/external_module.rs +++ b/crates/rspack_core/src/external_module.rs @@ -9,7 +9,7 @@ use rustc_hash::FxHashMap as HashMap; use serde::Serialize; use crate::{ - property_access, + extract_url_and_global, property_access, rspack_sources::{BoxSource, RawSource, Source, SourceExt}, to_identifier, AsyncDependenciesBlockIdentifier, BuildContext, BuildInfo, BuildMetaExportsType, BuildResult, ChunkInitFragments, ChunkUkey, CodeGenerationDataUrl, CodeGenerationResult, @@ -134,12 +134,45 @@ impl ExternalModule { ) } + fn get_source_for_script_external( + &self, + url_and_global: &ExternalRequestValue, + runtime_requirements: &mut RuntimeGlobals, + ) -> Result { + let url_and_global = extract_url_and_global(url_and_global.primary())?; + runtime_requirements.insert(RuntimeGlobals::LOAD_SCRIPT); + Ok(format!( + r#" +var __webpack_error__ = new Error(); +module.exports = new Promise(function(resolve, reject) {{ + if(typeof {global} !== "undefined") return resolve(); + {load_script}({url_str}, function(event) {{ + if(typeof {global} !== "undefined") return resolve(); + var errorType = event && (event.type === 'load' ? 'missing' : event.type); + var realSrc = event && event.target && event.target.src; + __webpack_error__.message = 'Loading script failed.\n(' + errorType + ': ' + realSrc + ')'; + __webpack_error__.name = 'ScriptExternalLoadError'; + __webpack_error__.type = errorType; + __webpack_error__.request = realSrc; + reject(__webpack_error__); + }}, {global_str}); +}}).then(function() {{ return {global}; }}); +"#, + global = url_and_global.global, + global_str = + serde_json::to_string(url_and_global.global).map_err(|e| internal_error!(e.to_string()))?, + url_str = + serde_json::to_string(url_and_global.url).map_err(|e| internal_error!(e.to_string()))?, + load_script = RuntimeGlobals::LOAD_SCRIPT.name() + )) + } + fn get_source( &self, compilation: &Compilation, request: Option<&ExternalRequestValue>, external_type: &ExternalType, - ) -> (BoxSource, ChunkInitFragments, RuntimeGlobals) { + ) -> Result<(BoxSource, ChunkInitFragments, RuntimeGlobals)> { let mut chunk_init_fragments: ChunkInitFragments = Default::default(); let mut runtime_requirements: RuntimeGlobals = Default::default(); let source = match self.external_type.as_str() { @@ -231,15 +264,17 @@ impl ExternalModule { self.get_source_for_import(request, compilation) } } - // TODO "script" + "script" if let Some(request) = request => { + self.get_source_for_script_external(request, &mut runtime_requirements)? + } _ => "".to_string(), }; runtime_requirements.insert(RuntimeGlobals::MODULE); - ( + Ok(( RawSource::from(source).boxed(), chunk_init_fragments, runtime_requirements, - ) + )) } } @@ -379,7 +414,7 @@ impl Module for ExternalModule { } _ => { let (source, chunk_init_fragments, runtime_requirements) = - self.get_source(compilation, request, external_type); + self.get_source(compilation, request, external_type)?; cgr.add(SourceType::JavaScript, source); cgr.chunk_init_fragments = chunk_init_fragments; cgr.runtime_requirements.insert(runtime_requirements); diff --git a/crates/rspack_core/src/lib.rs b/crates/rspack_core/src/lib.rs index 964c7d17c521..4de85f20ff40 100644 --- a/crates/rspack_core/src/lib.rs +++ b/crates/rspack_core/src/lib.rs @@ -3,6 +3,7 @@ #![feature(iter_intersperse)] #![feature(box_patterns)] #![feature(anonymous_lifetime_in_impl_trait)] +#![feature(hash_raw_entry)] use std::sync::atomic::AtomicBool; use std::{fmt, sync::Arc}; @@ -95,6 +96,8 @@ pub enum SourceType { Css, Wasm, Asset, + Remote, + ShareInit, #[default] Unknown, } @@ -106,6 +109,8 @@ impl std::fmt::Display for SourceType { SourceType::Css => write!(f, "css"), SourceType::Wasm => write!(f, "wasm"), SourceType::Asset => write!(f, "asset"), + SourceType::Remote => write!(f, "remote"), + SourceType::ShareInit => write!(f, "share-init"), SourceType::Unknown => write!(f, "unknown"), } } @@ -132,6 +137,7 @@ pub enum ModuleType { AssetSource, Asset, Runtime, + Remote, } impl ModuleType { @@ -216,6 +222,7 @@ impl ModuleType { ModuleType::AssetResource => "asset/resource", ModuleType::AssetInline => "asset/inline", ModuleType::Runtime => "runtime", + ModuleType::Remote => "remote-module", } } } diff --git a/crates/rspack_core/src/mf/container/container_entry_dependency.rs b/crates/rspack_core/src/mf/container/container_entry_dependency.rs index c05e7796d6f7..efdbe0000594 100644 --- a/crates/rspack_core/src/mf/container/container_entry_dependency.rs +++ b/crates/rspack_core/src/mf/container/container_entry_dependency.rs @@ -1,4 +1,4 @@ -use super::ExposeOptions; +use super::container_plugin::ExposeOptions; use crate::{ AsContextDependency, AsDependencyTemplate, Dependency, DependencyCategory, DependencyId, DependencyType, ModuleDependency, @@ -52,12 +52,6 @@ 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 {} diff --git a/crates/rspack_core/src/mf/container/container_entry_module.rs b/crates/rspack_core/src/mf/container/container_entry_module.rs index 969e1cd27d28..490621b6f3b7 100644 --- a/crates/rspack_core/src/mf/container/container_entry_module.rs +++ b/crates/rspack_core/src/mf/container/container_entry_module.rs @@ -6,7 +6,9 @@ use rspack_hash::RspackHash; use rspack_identifier::{Identifiable, Identifier}; use rspack_sources::{RawSource, Source, SourceExt}; -use super::{container_exposed_dependency::ContainerExposedDependency, ExposeOptions}; +use super::{ + container_exposed_dependency::ContainerExposedDependency, container_plugin::ExposeOptions, +}; use crate::{ basic_function, block_promise, module_raw, returning_function, throw_missing_module_error_block, AsyncDependenciesBlock, AsyncDependenciesBlockIdentifier, BuildContext, BuildInfo, BuildMeta, @@ -20,13 +22,14 @@ pub struct ContainerEntryModule { blocks: Vec, dependencies: Vec, identifier: ModuleIdentifier, - name: String, + lib_ident: String, exposes: Vec<(String, ExposeOptions)>, share_scope: String, } impl ContainerEntryModule { pub fn new(name: String, exposes: Vec<(String, ExposeOptions)>, share_scope: String) -> Self { + let lib_ident = format!("webpack/container/entry/{}", &name); Self { blocks: Vec::new(), dependencies: Vec::new(), @@ -35,7 +38,7 @@ impl ContainerEntryModule { share_scope, serde_json::to_string(&exposes).expect("should able to json to_string") )), - name, + lib_ident, exposes, share_scope, } @@ -89,7 +92,7 @@ impl Module for ContainerEntryModule { } fn lib_ident(&self, _options: LibIdentOptions) -> Option> { - Some(format!("webpack/container/entry/{}", self.name).into()) + Some(self.lib_ident.as_str().into()) } async fn build( diff --git a/crates/rspack_core/src/mf/container/container_reference_plugin.rs b/crates/rspack_core/src/mf/container/container_reference_plugin.rs new file mode 100644 index 000000000000..7babcaa538ae --- /dev/null +++ b/crates/rspack_core/src/mf/container/container_reference_plugin.rs @@ -0,0 +1,114 @@ +use async_trait::async_trait; + +use super::{remote_module::RemoteModule, remote_runtime_module::RemoteRuntimeModule}; +use crate::{ + AdditionalChunkRuntimeRequirementsArgs, ExternalType, FactorizeArgs, ModuleExt, + ModuleFactoryResult, NormalModuleFactoryContext, Plugin, + PluginAdditionalChunkRuntimeRequirementsOutput, PluginContext, PluginFactorizeHookOutput, + RuntimeGlobals, +}; + +#[derive(Debug)] +pub struct ContainerReferencePluginOptions { + pub remote_type: ExternalType, + pub remotes: Vec<(String, RemoteOptions)>, + pub share_scope: Option, +} + +#[derive(Debug)] +pub struct RemoteOptions { + pub external: Vec, + pub share_scope: String, +} + +#[derive(Debug)] +pub struct ContainerReferencePlugin { + options: ContainerReferencePluginOptions, +} + +impl ContainerReferencePlugin { + pub fn new(options: ContainerReferencePluginOptions) -> Self { + Self { options } + } +} + +#[async_trait] +impl Plugin for ContainerReferencePlugin { + fn name(&self) -> &'static str { + "rspack.ContainerReferencePlugin" + } + + async fn factorize( + &self, + _ctx: PluginContext, + args: FactorizeArgs<'_>, + _job_ctx: &mut NormalModuleFactoryContext, + ) -> PluginFactorizeHookOutput { + let request = args.dependency.request(); + if !request.contains('!') { + for (key, config) in &self.options.remotes { + let key_len = key.len(); + if request.starts_with(key) + && (request.len() == key_len || request[key_len..].starts_with('/')) + { + let internal_request = &request[key_len..]; + let remote = RemoteModule::new( + request.to_owned(), + config + .external + .iter() + .enumerate() + .map(|(i, e)| { + if let Some(stripped) = e.strip_prefix("internal ") { + stripped.to_string() + } else { + format!( + "webpack/container/reference/{}{}", + key, + (i > 0) + .then(|| format!("/fallback-{}", i)) + .unwrap_or_default() + ) + } + }) + .collect(), + format!(".{}", internal_request), + config.share_scope.clone(), + ) + .boxed(); + return Ok(Some(ModuleFactoryResult::new(remote))); + } + } + } + Ok(None) + } + + fn runtime_requirements_in_tree( + &self, + _ctx: PluginContext, + args: &mut AdditionalChunkRuntimeRequirementsArgs, + ) -> PluginAdditionalChunkRuntimeRequirementsOutput { + if args + .runtime_requirements + .contains(RuntimeGlobals::ENSURE_CHUNK_HANDLERS) + { + args.runtime_requirements.insert(RuntimeGlobals::MODULE); + args + .runtime_requirements + .insert(RuntimeGlobals::MODULE_FACTORIES_ADD_ONLY); + args + .runtime_requirements + .insert(RuntimeGlobals::HAS_OWN_PROPERTY); + args + .runtime_requirements + .insert(RuntimeGlobals::INITIALIZE_SHARING); + args + .runtime_requirements + .insert(RuntimeGlobals::SHARE_SCOPE_MAP); + args + .compilation + .add_runtime_module(args.chunk, Box::::default()); + } + Ok(()) + } +} diff --git a/crates/rspack_core/src/mf/container/mf_scope_runtime_module.rs b/crates/rspack_core/src/mf/container/mf_scope_runtime_module.rs new file mode 100644 index 000000000000..8cf2f0d14af5 --- /dev/null +++ b/crates/rspack_core/src/mf/container/mf_scope_runtime_module.rs @@ -0,0 +1,35 @@ +use rspack_identifier::Identifier; +use rspack_sources::{BoxSource, RawSource, SourceExt}; + +use crate::{impl_runtime_module, ChunkUkey, Compilation, RuntimeGlobals, RuntimeModule}; + +#[derive(Debug, Eq)] +pub struct MFScopeRuntimeModule { + id: Identifier, + chunk: Option, +} + +impl Default for MFScopeRuntimeModule { + fn default() -> Self { + Self { + id: Identifier::from("webpack/runtime/mf_scope"), + chunk: None, + } + } +} + +impl RuntimeModule for MFScopeRuntimeModule { + fn name(&self) -> Identifier { + self.id + } + + fn generate(&self, _: &Compilation) -> BoxSource { + RawSource::from(format!(r#"{}.MF = {{}};"#, RuntimeGlobals::REQUIRE)).boxed() + } + + fn attach(&mut self, chunk: ChunkUkey) { + self.chunk = Some(chunk); + } +} + +impl_runtime_module!(MFScopeRuntimeModule); diff --git a/crates/rspack_core/src/mf/container/mf_scope_runtime_plugin.rs b/crates/rspack_core/src/mf/container/mf_scope_runtime_plugin.rs new file mode 100644 index 000000000000..1f2dc65300de --- /dev/null +++ b/crates/rspack_core/src/mf/container/mf_scope_runtime_plugin.rs @@ -0,0 +1,25 @@ +use super::mf_scope_runtime_module::MFScopeRuntimeModule; +use crate::{ + AdditionalChunkRuntimeRequirementsArgs, Plugin, PluginAdditionalChunkRuntimeRequirementsOutput, + PluginContext, +}; + +#[derive(Debug, Default)] +pub struct MFScopeRuntimePlugin; + +impl Plugin for MFScopeRuntimePlugin { + fn name(&self) -> &'static str { + "rspack.MFScopeRuntimePlugin" + } + + fn runtime_requirements_in_tree( + &self, + _ctx: PluginContext, + args: &mut AdditionalChunkRuntimeRequirementsArgs, + ) -> PluginAdditionalChunkRuntimeRequirementsOutput { + args + .compilation + .add_runtime_module(args.chunk, Box::::default()); + Ok(()) + } +} diff --git a/crates/rspack_core/src/mf/container/mod.rs b/crates/rspack_core/src/mf/container/mod.rs index a900030823ff..27fe1d9786af 100644 --- a/crates/rspack_core/src/mf/container/mod.rs +++ b/crates/rspack_core/src/mf/container/mod.rs @@ -1,8 +1,11 @@ -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}; +pub mod container_entry_dependency; +pub mod container_entry_module; +pub mod container_entry_module_factory; +pub mod container_exposed_dependency; +pub mod container_plugin; +pub mod container_reference_plugin; +pub mod mf_scope_runtime_module; +pub mod mf_scope_runtime_plugin; +pub mod remote_module; +pub mod remote_runtime_module; +pub mod remote_to_external_dependency; diff --git a/crates/rspack_core/src/mf/container/remote_module.rs b/crates/rspack_core/src/mf/container/remote_module.rs new file mode 100644 index 000000000000..43c3b7164f1b --- /dev/null +++ b/crates/rspack_core/src/mf/container/remote_module.rs @@ -0,0 +1,183 @@ +use std::borrow::Cow; +use std::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::remote_to_external_dependency::RemoteToExternalDependency; +use crate::{ + mf::share_runtime_module::{CodeGenerationDataShareInit, DataInit, ShareInitData}, + AsyncDependenciesBlockIdentifier, BoxDependency, BuildContext, BuildInfo, BuildResult, + CodeGenerationResult, Compilation, Context, DependenciesBlock, DependencyId, LibIdentOptions, + Module, ModuleIdentifier, ModuleType, RuntimeSpec, SourceType, +}; + +#[derive(Debug)] +pub struct RemoteModule { + blocks: Vec, + dependencies: Vec, + identifier: ModuleIdentifier, + readable_identifier: String, + lib_ident: String, + request: String, + external_requests: Vec, + pub internal_request: String, + pub share_scope: String, +} + +impl RemoteModule { + pub fn new( + request: String, + external_requests: Vec, + internal_request: String, + share_scope: String, + ) -> Self { + let readable_identifier = format!("remote {}", &request); + let lib_ident = format!("webpack/container/remote/{}", &request); + Self { + blocks: Default::default(), + dependencies: Default::default(), + identifier: ModuleIdentifier::from(format!( + "remote ({}) {} {}", + share_scope, + external_requests.join(" "), + internal_request + )), + readable_identifier, + lib_ident, + request, + external_requests, + internal_request, + share_scope, + } + } +} + +impl Identifiable for RemoteModule { + fn identifier(&self) -> Identifier { + self.identifier + } +} + +impl DependenciesBlock for RemoteModule { + 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 RemoteModule { + fn size(&self, _source_type: &SourceType) -> f64 { + 6.0 + } + + fn module_type(&self) -> &ModuleType { + &ModuleType::Remote + } + + fn source_types(&self) -> &[SourceType] { + &[SourceType::Remote, 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()) + } + + fn name_for_condition(&self) -> Option> { + Some(self.request.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 build_info = BuildInfo { + strict: true, + hash: Some(hasher.digest(&build_context.compiler_options.output.hash_digest)), + ..Default::default() + }; + + let mut dependencies = Vec::new(); + if self.external_requests.len() == 1 { + let dep = RemoteToExternalDependency::new(self.external_requests[0].clone()); + dependencies.push(Box::new(dep) as BoxDependency); + } + + Ok( + BuildResult { + build_info, + build_meta: Default::default(), + dependencies, + blocks: Vec::new(), + analyze_result: Default::default(), + } + .with_empty_diagnostic(), + ) + } + + #[allow(clippy::unwrap_in_result)] + fn code_generation( + &self, + compilation: &Compilation, + _runtime: Option<&RuntimeSpec>, + ) -> Result { + let mut codegen = CodeGenerationResult::default(); + let module = compilation.module_graph.get_module(&self.dependencies[0]); + let id = module.and_then(|m| { + compilation + .chunk_graph + .get_module_id(m.identifier()) + .as_deref() + }); + codegen.add(SourceType::Remote, RawSource::from("").boxed()); + codegen.data.insert(CodeGenerationDataShareInit { + items: vec![ShareInitData { + share_scope: self.share_scope.clone(), + init_stage: 20, + init: DataInit::ExternalModuleId(id.map(|id| id.to_string())), + }], + }); + Ok(codegen) + } +} + +impl Hash for RemoteModule { + fn hash(&self, state: &mut H) { + "__rspack_internal__RemoteModule".hash(state); + self.identifier().hash(state); + } +} + +impl PartialEq for RemoteModule { + fn eq(&self, other: &Self) -> bool { + self.identifier() == other.identifier() + } +} + +impl Eq for RemoteModule {} diff --git a/crates/rspack_core/src/mf/container/remote_runtime_module.rs b/crates/rspack_core/src/mf/container/remote_runtime_module.rs new file mode 100644 index 000000000000..501aac5afa13 --- /dev/null +++ b/crates/rspack_core/src/mf/container/remote_runtime_module.rs @@ -0,0 +1,105 @@ +use rspack_identifier::{Identifiable, Identifier}; +use rspack_sources::{BoxSource, RawSource, SourceExt}; +use rustc_hash::FxHashMap; + +use super::remote_module::RemoteModule; +use crate::{ + impl_runtime_module, ChunkUkey, Compilation, DependenciesBlock, RuntimeGlobals, RuntimeModule, + RuntimeModuleStage, SourceType, +}; + +#[derive(Debug, Eq)] +pub struct RemoteRuntimeModule { + id: Identifier, + chunk: Option, +} + +impl Default for RemoteRuntimeModule { + fn default() -> Self { + Self { + id: Identifier::from("webpack/runtime/remotes_loading"), + chunk: None, + } + } +} + +impl RuntimeModule for RemoteRuntimeModule { + fn name(&self) -> Identifier { + self.id + } + + fn stage(&self) -> RuntimeModuleStage { + RuntimeModuleStage::Attach + } + + fn generate(&self, compilation: &Compilation) -> BoxSource { + let chunk_ukey = self + .chunk + .expect("should have chunk in ::generate"); + let chunk = compilation.chunk_by_ukey.expect_get(&chunk_ukey); + let mut chunk_to_remotes_mapping = FxHashMap::default(); + let mut id_to_external_and_name_mapping = FxHashMap::default(); + for chunk in chunk.get_all_async_chunks(&compilation.chunk_group_by_ukey) { + let modules = compilation + .chunk_graph + .get_chunk_modules_iterable_by_source_type( + &chunk, + SourceType::Remote, + &compilation.module_graph, + ); + let mut remotes = Vec::new(); + for m in modules { + let Some(m) = m.downcast_ref::() else { + continue; + }; + let name = m.internal_request.as_str(); + let id = compilation + .chunk_graph + .get_module_id(m.identifier()) + .as_deref() + .expect("should have module_id at ::generate"); + let share_scope = m.share_scope.as_str(); + let dep = m.get_dependencies()[0]; + let external_module = compilation + .module_graph + .get_module(&dep) + .expect("should have module"); + let external_module_id = compilation + .chunk_graph + .get_module_id(external_module.identifier()) + .as_deref() + .expect("should have module_id at ::generate"); + remotes.push(id.to_string()); + id_to_external_and_name_mapping.insert(id, vec![share_scope, name, external_module_id]); + } + let chunk = compilation.chunk_by_ukey.expect_get(&chunk); + chunk_to_remotes_mapping.insert( + chunk + .id + .as_ref() + .expect("should have chunkId at ::generate"), + remotes, + ); + } + RawSource::from(format!( + r#" +var chunkMapping = {chunk_mapping}; +var idToExternalAndNameMapping = {id_to_external_and_name_mapping}; +{ensure_chunk_handlers}.remotes = function(chunkId, promises) {{ return {remotes_fn}({{ chunkId: chunkId, promises: promises, chunkMapping: chunkMapping, idToExternalAndNameMapping: idToExternalAndNameMapping }}); }}; +"#, + chunk_mapping = serde_json::to_string(&chunk_to_remotes_mapping) + .expect("chunk_to_remotes_mapping should able to json to_string"), + id_to_external_and_name_mapping = serde_json::to_string(&id_to_external_and_name_mapping) + .expect("id_to_external_and_name_mapping should able to json to_string"), + ensure_chunk_handlers = RuntimeGlobals::ENSURE_CHUNK_HANDLERS, + remotes_fn = "__webpack_require__.MF.remotes", + )) + .boxed() + } + + fn attach(&mut self, chunk: ChunkUkey) { + self.chunk = Some(chunk); + } +} + +impl_runtime_module!(RemoteRuntimeModule); diff --git a/crates/rspack_core/src/mf/container/remote_to_external_dependency.rs b/crates/rspack_core/src/mf/container/remote_to_external_dependency.rs new file mode 100644 index 000000000000..9fbb3a2e2562 --- /dev/null +++ b/crates/rspack_core/src/mf/container/remote_to_external_dependency.rs @@ -0,0 +1,46 @@ +use crate::{ + AsContextDependency, AsDependencyTemplate, Dependency, DependencyCategory, DependencyId, + DependencyType, ModuleDependency, +}; + +#[derive(Debug, Clone)] +pub struct RemoteToExternalDependency { + id: DependencyId, + request: String, +} + +impl RemoteToExternalDependency { + pub fn new(request: String) -> Self { + Self { + id: DependencyId::new(), + request, + } + } +} + +impl Dependency for RemoteToExternalDependency { + fn dependency_debug_name(&self) -> &'static str { + "RemoteToExternalDependency" + } + + fn id(&self) -> &DependencyId { + &self.id + } + + fn dependency_type(&self) -> &DependencyType { + &DependencyType::RemoteToExternal + } + + fn category(&self) -> &DependencyCategory { + &DependencyCategory::Esm + } +} + +impl ModuleDependency for RemoteToExternalDependency { + fn request(&self) -> &str { + &self.request + } +} + +impl AsContextDependency for RemoteToExternalDependency {} +impl AsDependencyTemplate for RemoteToExternalDependency {} diff --git a/crates/rspack_core/src/mf/mod.rs b/crates/rspack_core/src/mf/mod.rs index 1bbe8f5654ad..24c43f7c4abc 100644 --- a/crates/rspack_core/src/mf/mod.rs +++ b/crates/rspack_core/src/mf/mod.rs @@ -1,3 +1,5 @@ // TODO: move to rspack_plugin_mf once we remove the hardcoded DependencyType => ModuleFactory mod container; +mod sharing; pub use container::*; +pub use sharing::*; diff --git a/crates/rspack_core/src/mf/sharing/mod.rs b/crates/rspack_core/src/mf/sharing/mod.rs new file mode 100644 index 000000000000..dd0250d91ab9 --- /dev/null +++ b/crates/rspack_core/src/mf/sharing/mod.rs @@ -0,0 +1,2 @@ +pub mod share_runtime_module; +pub mod share_runtime_plugin; diff --git a/crates/rspack_core/src/mf/sharing/share_runtime_module.rs b/crates/rspack_core/src/mf/sharing/share_runtime_module.rs new file mode 100644 index 000000000000..5149351d4c25 --- /dev/null +++ b/crates/rspack_core/src/mf/sharing/share_runtime_module.rs @@ -0,0 +1,142 @@ +use hashlink::{LinkedHashMap, LinkedHashSet}; +use itertools::Itertools; +use rspack_identifier::Identifier; +use rspack_sources::{BoxSource, RawSource, SourceExt}; +use rustc_hash::FxHashMap; + +use crate::{ + impl_runtime_module, ChunkUkey, Compilation, RuntimeGlobals, RuntimeModule, SourceType, +}; + +#[derive(Debug, Eq)] +pub struct ShareRuntimeModule { + id: Identifier, + chunk: Option, +} + +impl Default for ShareRuntimeModule { + fn default() -> Self { + Self { + id: Identifier::from("webpack/runtime/sharing"), + chunk: None, + } + } +} + +impl RuntimeModule for ShareRuntimeModule { + fn name(&self) -> Identifier { + self.id + } + + fn generate(&self, compilation: &Compilation) -> BoxSource { + let chunk_ukey = self + .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(); + 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 + .chunk_graph + .get_chunk_modules_iterable_by_source_type( + &c, + SourceType::ShareInit, + &compilation.module_graph, + ) + .sorted_unstable_by_key(|m| m.identifier()); + for m in modules { + let code_gen = compilation + .code_generation_results + .get(&m.identifier(), Some(&chunk.runtime)).expect("should have code_generation_result of share-init sourceType module at ::generate"); + let Some(data) = code_gen.data.get::() else { + continue; + }; + for item in &data.items { + let (_, stages) = init_per_scope + .raw_entry_mut() + .from_key(&item.share_scope) + .or_insert_with(|| (item.share_scope.to_owned(), LinkedHashMap::default())); + let list = stages + .entry(item.init_stage) + .or_insert_with(LinkedHashSet::default); + list.insert(item.init.clone()); + } + } + } + let init_per_scope_body = init_per_scope + .into_iter() + .sorted_unstable_by_key(|(scope, _)| scope.to_string()) + .map(|(scope, stages)| { + 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, + }) + }) + .collect::>() + .join("\n"); + format!( + r#"case {}: {{ +{} +}} +break;"#, + serde_json::to_string(&scope).expect("should able to json to_string"), + stages + ) + }) + .collect::>() + .join("\n"); + RawSource::from(format!( + r#" +{share_scope_map} = {{}}; +var initPromises = {{}}; +var initTokens = {{}}; +var initPerScope = function(name, register, initExternal) {{ + switch(name) {{ +{init_per_scope_body} + }} +}}; +{initialize_sharing} = function(name, initScope) {{ return {initialize_sharing_fn}({{ name: name, initScope: initScope, initPerScope: initPerScope, initTokens: initTokens, initPromises: initPromises }}); }}; +"#, + share_scope_map = RuntimeGlobals::SHARE_SCOPE_MAP, + init_per_scope_body = init_per_scope_body, + initialize_sharing = RuntimeGlobals::INITIALIZE_SHARING, + initialize_sharing_fn = "__webpack_require__.MF.initializeSharing" + )) + .boxed() + } + + fn attach(&mut self, chunk: ChunkUkey) { + self.chunk = Some(chunk); + } +} + +impl_runtime_module!(ShareRuntimeModule); + +#[derive(Debug, Clone)] +pub struct CodeGenerationDataShareInit { + pub items: Vec, +} + +#[derive(Debug, Clone)] +pub struct ShareInitData { + pub share_scope: String, + pub init_stage: DataInitStage, + pub init: DataInit, +} + +pub type DataInitStage = i8; + +#[derive(Debug, Clone, Hash, PartialEq, Eq)] +pub enum DataInit { + ExternalModuleId(Option), +} diff --git a/crates/rspack_core/src/mf/sharing/share_runtime_plugin.rs b/crates/rspack_core/src/mf/sharing/share_runtime_plugin.rs new file mode 100644 index 000000000000..867973034e07 --- /dev/null +++ b/crates/rspack_core/src/mf/sharing/share_runtime_plugin.rs @@ -0,0 +1,25 @@ +use super::share_runtime_module::ShareRuntimeModule; +use crate::{ + AdditionalChunkRuntimeRequirementsArgs, Plugin, PluginAdditionalChunkRuntimeRequirementsOutput, + PluginContext, +}; + +#[derive(Debug, Default)] +pub struct ShareRuntimePlugin; + +impl Plugin for ShareRuntimePlugin { + fn name(&self) -> &'static str { + "rspack.ShareRuntimePlugin" + } + + fn runtime_requirements_in_tree( + &self, + _ctx: PluginContext, + args: &mut AdditionalChunkRuntimeRequirementsArgs, + ) -> PluginAdditionalChunkRuntimeRequirementsOutput { + args + .compilation + .add_runtime_module(args.chunk, Box::::default()); + Ok(()) + } +} diff --git a/crates/rspack_core/src/tree_shaking/optimizer.rs b/crates/rspack_core/src/tree_shaking/optimizer.rs index dfed043205bd..5b99c5ccc997 100644 --- a/crates/rspack_core/src/tree_shaking/optimizer.rs +++ b/crates/rspack_core/src/tree_shaking/optimizer.rs @@ -524,6 +524,7 @@ impl<'a> CodeSizeOptimizer<'a> { | DependencyType::DynamicImport | DependencyType::CjsRequire | DependencyType::ImportContext + | DependencyType::ContainerExposed ); if self.side_effects_free_modules.contains(module_identifier) @@ -1290,8 +1291,11 @@ impl<'a> CodeSizeOptimizer<'a> { Some(result) => result, None => { // TODO: checking if it is none js type - return; - // panic!("Can't get analyze result from entry_identifier {}", entry_identifier); + // return; + panic!( + "Can't get analyze result from entry_identifier {}", + entry_identifier + ); } }; diff --git a/crates/rspack_core/src/utils/extract_url_and_global.rs b/crates/rspack_core/src/utils/extract_url_and_global.rs new file mode 100644 index 000000000000..5a69d4a860af --- /dev/null +++ b/crates/rspack_core/src/utils/extract_url_and_global.rs @@ -0,0 +1,20 @@ +use rspack_error::{internal_error_bail, Result}; + +#[derive(Debug)] +pub struct UrlAndGlobal<'a> { + pub url: &'a str, + pub global: &'a str, +} + +pub fn extract_url_and_global(value: &str) -> Result { + let index = value.find('@'); + if let Some(index) = index + && index != 0 + { + return Ok(UrlAndGlobal { + url: &value[index + 1..], + global: &value[0..index], + }); + } + internal_error_bail!("Invalid request \"{}\"", value) +} diff --git a/crates/rspack_core/src/utils/mod.rs b/crates/rspack_core/src/utils/mod.rs index 660e6c6c8b78..7a944fa7f36c 100644 --- a/crates/rspack_core/src/utils/mod.rs +++ b/crates/rspack_core/src/utils/mod.rs @@ -3,6 +3,9 @@ use std::fmt::Display; use itertools::Itertools; use rustc_hash::FxHashMap as HashMap; +mod extract_url_and_global; +pub use extract_url_and_global::*; + mod identifier; pub use identifier::*; diff --git a/packages/rspack/src/builtin-plugin/base.ts b/packages/rspack/src/builtin-plugin/base.ts index 3b748585e87c..6a1287b51cf6 100644 --- a/packages/rspack/src/builtin-plugin/base.ts +++ b/packages/rspack/src/builtin-plugin/base.ts @@ -26,14 +26,18 @@ export enum BuiltinPluginName { LimitChunkCountPlugin = "LimitChunkCountPlugin", WebWorkerTemplatePlugin = "WebWorkerTemplatePlugin", MergeDuplicateChunksPlugin = "MergeDuplicateChunksPlugin", - ContainerPlugin = "ContainerPlugin" + ContainerPlugin = "ContainerPlugin", + ContainerReferencePlugin = "ContainerReferencePlugin", + ShareRuntimePlugin = "ShareRuntimePlugin", + MFScopeRuntimePlugin = "MFScopeRuntimePlugin" } export abstract class RspackBuiltinPlugin implements RspackPluginInstance { - abstract raw(compiler: Compiler): binding.BuiltinPlugin; + abstract raw(compiler: Compiler): binding.BuiltinPlugin | null; abstract name: BuiltinPluginName; apply(compiler: Compiler) { - compiler.__internal__registerBuiltinPlugin(this.raw(compiler)); + let raw = this.raw(compiler); + if (raw) compiler.__internal__registerBuiltinPlugin(raw); } } diff --git a/packages/rspack/src/config/normalization.ts b/packages/rspack/src/config/normalization.ts index b7039971c893..64fc552d1b77 100644 --- a/packages/rspack/src/config/normalization.ts +++ b/packages/rspack/src/config/normalization.ts @@ -194,6 +194,9 @@ export const getNormalizedRspackOptions = ( ? output.umdNamedDefine : libraryBase.umdNamedDefine }, + strictModuleErrorHandling: + output.strictModuleErrorHandling ?? + output.strictModuleExceptionHandling, trustedTypes: optionalNestedConfig( output.trustedTypes, trustedTypes => { diff --git a/packages/rspack/src/config/zod.ts b/packages/rspack/src/config/zod.ts index 51bece518d8c..d60246c2821d 100644 --- a/packages/rspack/src/config/zod.ts +++ b/packages/rspack/src/config/zod.ts @@ -215,6 +215,11 @@ export type Clean = z.infer; const outputModule = z.boolean(); export type OutputModule = z.infer; +const strictModuleExceptionHandling = z.boolean(); +export type StrictModuleExceptionHandling = z.infer< + typeof strictModuleExceptionHandling +>; + const strictModuleErrorHandling = z.boolean(); export type StrictModuleErrorHandling = z.infer< typeof strictModuleErrorHandling @@ -284,6 +289,7 @@ const output = z.strictObject({ amdContainer: amdContainer.optional(), auxiliaryComment: auxiliaryComment.optional(), module: outputModule.optional(), + strictModuleExceptionHandling: strictModuleExceptionHandling.optional(), strictModuleErrorHandling: strictModuleErrorHandling.optional(), globalObject: globalObject.optional(), importFunctionName: importFunctionName.optional(), @@ -634,7 +640,7 @@ export type Target = z.infer; //#endregion //#region ExternalsType -const externalsType = z.enum([ +export const externalsType = z.enum([ "var", "module", "assign", diff --git a/packages/rspack/src/container/ContainerReferencePlugin.ts b/packages/rspack/src/container/ContainerReferencePlugin.ts new file mode 100644 index 000000000000..6c6504aebc4f --- /dev/null +++ b/packages/rspack/src/container/ContainerReferencePlugin.ts @@ -0,0 +1,75 @@ +import { + BuiltinPlugin, + RawContainerReferencePluginOptions +} from "@rspack/binding"; +import { BuiltinPluginName, RspackBuiltinPlugin } from "../builtin-plugin/base"; +import { Compiler } from "../Compiler"; +import { ExternalsPlugin } from "../builtin-plugin/ExternalsPlugin"; +import { ExternalsType } from "../config"; +import { parseOptions } from "./options"; +import { RemoteRuntimeSingletonPlugin } from "./RemoteRuntimeSingletonPlugin"; + +export type ContainerReferencePluginOptions = { + remoteType: ExternalsType; + remotes: Remotes; + shareScope?: string; +}; +export type Remotes = (RemotesItem | RemotesObject)[] | RemotesObject; +export type RemotesItem = string; +export type RemotesItems = RemotesItem[]; +export type RemotesObject = { + [k: string]: RemotesConfig | RemotesItem | RemotesItems; +}; +export type RemotesConfig = { + external: RemotesItem | RemotesItems; + shareScope?: string; +}; + +export class ContainerReferencePlugin extends RspackBuiltinPlugin { + name = BuiltinPluginName.ContainerReferencePlugin; + _options: RawContainerReferencePluginOptions; + _remotes; + + constructor(options: ContainerReferencePluginOptions) { + super(); + this._remotes = parseOptions( + options.remotes, + item => ({ + external: Array.isArray(item) ? item : [item], + shareScope: options.shareScope || "default" + }), + item => ({ + external: Array.isArray(item.external) + ? item.external + : [item.external], + shareScope: item.shareScope || options.shareScope || "default" + }) + ); + this._options = { + remoteType: options.remoteType, + remotes: this._remotes.map(([key, r]) => ({ key, ...r })) + }; + } + + raw(compiler: Compiler): BuiltinPlugin { + const { remoteType } = this._options; + const remoteExternals: any = {}; + for (const [key, config] of this._remotes) { + let i = 0; + for (const external of config.external) { + if (external.startsWith("internal ")) continue; + remoteExternals[ + `webpack/container/reference/${key}${i ? `/fallback-${i}` : ""}` + ] = external; + i++; + } + } + new ExternalsPlugin(remoteType, remoteExternals).apply(compiler); + new RemoteRuntimeSingletonPlugin().apply(compiler); + + return { + name: this.name as any, + options: this._options + }; + } +} diff --git a/packages/rspack/src/container/MFScopeRuntimeSingletonPlugin.ts b/packages/rspack/src/container/MFScopeRuntimeSingletonPlugin.ts new file mode 100644 index 000000000000..380418d12279 --- /dev/null +++ b/packages/rspack/src/container/MFScopeRuntimeSingletonPlugin.ts @@ -0,0 +1,17 @@ +import { Compiler } from "../Compiler"; +import { BuiltinPluginName, create } from "../builtin-plugin/base"; + +const MFScopeRuntimePlugin = create( + BuiltinPluginName.MFScopeRuntimePlugin, + () => undefined +); + +let addded = false; + +export class MFScopeRuntimeSingletonPlugin { + apply(compiler: Compiler) { + if (addded) return; + addded = true; + new MFScopeRuntimePlugin().apply(compiler); + } +} diff --git a/packages/rspack/src/container/ModuleFederationPlugin.ts b/packages/rspack/src/container/ModuleFederationPlugin.ts index a44ae1eeef6e..a43be2ad0883 100644 --- a/packages/rspack/src/container/ModuleFederationPlugin.ts +++ b/packages/rspack/src/container/ModuleFederationPlugin.ts @@ -1,14 +1,21 @@ import { Compiler } from "../Compiler"; +import { + LibraryOptions, + EntryRuntime, + ExternalsType, + externalsType +} from "../config"; +import { isValidate } from "../util/validate"; import { ContainerPlugin, Exposes } from "./ContainerPlugin"; -import { LibraryOptions, EntryRuntime } from "../config"; +import { ContainerReferencePlugin, Remotes } from "./ContainerReferencePlugin"; export interface ModuleFederationPluginOptions { exposes?: Exposes; filename?: string; library?: LibraryOptions; name: string; - // remoteType?: ExternalsType; - // remotes?: Remotes; + remoteType?: ExternalsType; + remotes?: Remotes; runtime?: EntryRuntime; shareScope?: string; // shared?: Shared; @@ -20,11 +27,11 @@ export class ModuleFederationPlugin { 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"); + const remoteType = + options.remoteType || + (options.library && isValidate(options.library.type, externalsType) + ? (options.library.type as ExternalsType) + : "script"); if ( library && !compiler.options.output.enabledLibraryTypes!.includes(library.type) @@ -47,18 +54,18 @@ export class ModuleFederationPlugin { 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.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, diff --git a/packages/rspack/src/container/RemoteRuntimeSingletonPlugin.ts b/packages/rspack/src/container/RemoteRuntimeSingletonPlugin.ts new file mode 100644 index 000000000000..15f2a368c675 --- /dev/null +++ b/packages/rspack/src/container/RemoteRuntimeSingletonPlugin.ts @@ -0,0 +1,18 @@ +import { Compiler } from "../Compiler"; +import { EntryPlugin } from "../builtin-plugin/EntryPlugin"; +import { ShareRuntimeSingletonPlugin } from "../sharing/ShareRuntimeSingletonPlugin"; +import { MFScopeRuntimeSingletonPlugin } from "./MFScopeRuntimeSingletonPlugin"; + +let added = false; + +export class RemoteRuntimeSingletonPlugin { + apply(compiler: Compiler) { + new MFScopeRuntimeSingletonPlugin().apply(compiler); + new ShareRuntimeSingletonPlugin().apply(compiler); + if (added) return; + added = true; + new EntryPlugin(compiler.context, require.resolve("./remotesLoading.js"), { + name: undefined + }).apply(compiler); + } +} diff --git a/packages/rspack/src/container/remotesLoading.js b/packages/rspack/src/container/remotesLoading.js new file mode 100644 index 000000000000..af7e7421c1e9 --- /dev/null +++ b/packages/rspack/src/container/remotesLoading.js @@ -0,0 +1,72 @@ +// @ts-nocheck + +__webpack_require__.MF.remotes = function (data) { + var chunkId = data.chunkId, + promises = data.promises, + chunkMapping = data.chunkMapping, + idToExternalAndNameMapping = data.idToExternalAndNameMapping; + if (__webpack_require__.o(chunkMapping, chunkId)) { + chunkMapping[chunkId].forEach(function (id) { + var getScope = __webpack_require__.R; + if (!getScope) getScope = []; + var data = idToExternalAndNameMapping[id]; + if (getScope.indexOf(data) >= 0) return; + getScope.push(data); + if (data.p) return promises.push(data.p); + var onError = function (error) { + if (!error) error = new Error("Container missing"); + if (typeof error.message === "string") + error.message += '\nwhile loading "' + data[1] + '" from ' + data[2]; + __webpack_require__.m[id] = function () { + throw error; + }; + data.p = 0; + }; + var handleFunction = function (fn, arg1, arg2, d, next, first) { + try { + var promise = fn(arg1, arg2); + if (promise && promise.then) { + var p = promise.then(function (result) { + return next(result, d); + }, onError); + if (first) promises.push((data.p = p)); + else return p; + } else { + return next(promise, d, first); + } + } catch (error) { + onError(error); + } + }; + var onExternal = function (external, _, first) { + return external + ? handleFunction( + __webpack_require__.I, + data[0], + 0, + external, + onInitialized, + first + ) + : onError(); + }; + var onInitialized = function (_, external, first) { + return handleFunction( + external.get, + data[1], + getScope, + 0, + onFactory, + first + ); + }; + var onFactory = function (factory) { + data.p = 1; + __webpack_require__.m[id] = function (module) { + module.exports = factory(); + }; + }; + handleFunction(__webpack_require__, data[2], 0, 0, onExternal, 1); + }); + } +}; diff --git a/packages/rspack/src/exports.ts b/packages/rspack/src/exports.ts index e1bb917a246d..5baa498ba6bd 100644 --- a/packages/rspack/src/exports.ts +++ b/packages/rspack/src/exports.ts @@ -120,6 +120,7 @@ import { LimitChunkCountPlugin } from "./builtin-plugin"; export const optimize = { LimitChunkCountPlugin }; import { ContainerPlugin } from "./container/ContainerPlugin"; +import { ContainerReferencePlugin } from "./container/ContainerReferencePlugin"; import { ModuleFederationPlugin } from "./container/ModuleFederationPlugin"; export type { ModuleFederationPluginOptions } from "./container/ModuleFederationPlugin"; export type { @@ -130,7 +131,19 @@ export type { ExposesObject, ExposesConfig } from "./container/ContainerPlugin"; -export const container = { ContainerPlugin, ModuleFederationPlugin }; +export type { + ContainerReferencePluginOptions, + Remotes, + RemotesItem, + RemotesItems, + RemotesObject, + RemotesConfig +} from "./container/ContainerReferencePlugin"; +export const container = { + ContainerPlugin, + ContainerReferencePlugin, + ModuleFederationPlugin +}; ///// Rspack Postfixed Internal Plugins ///// export { HtmlRspackPlugin } from "./builtin-plugin"; diff --git a/packages/rspack/src/sharing/ShareRuntimeSingletonPlugin.ts b/packages/rspack/src/sharing/ShareRuntimeSingletonPlugin.ts new file mode 100644 index 000000000000..ec00d5c6d3c7 --- /dev/null +++ b/packages/rspack/src/sharing/ShareRuntimeSingletonPlugin.ts @@ -0,0 +1,25 @@ +import { Compiler } from "../Compiler"; +import { EntryPlugin } from "../builtin-plugin/EntryPlugin"; +import { BuiltinPluginName, create } from "../builtin-plugin/base"; +import { MFScopeRuntimeSingletonPlugin } from "../container/MFScopeRuntimeSingletonPlugin"; + +const ShareRuntimePlugin = create( + BuiltinPluginName.ShareRuntimePlugin, + () => undefined +); + +let added = false; + +export class ShareRuntimeSingletonPlugin { + apply(compiler: Compiler) { + new MFScopeRuntimeSingletonPlugin().apply(compiler); + if (added) return; + added = true; + new ShareRuntimePlugin().apply(compiler); + new EntryPlugin( + compiler.context, + require.resolve("./initializeSharing.js"), + { name: undefined } + ).apply(compiler); + } +} diff --git a/packages/rspack/src/sharing/initializeSharing.js b/packages/rspack/src/sharing/initializeSharing.js new file mode 100644 index 000000000000..519c1ef9f813 --- /dev/null +++ b/packages/rspack/src/sharing/initializeSharing.js @@ -0,0 +1,66 @@ +// @ts-nocheck + +__webpack_require__.MF.initializeSharing = function (data) { + var name = data.name, + initScope = data.initScope, + initPerScope = data.initPerScope, + initTokens = data.initTokens, + initPromises = data.initPromises; + if (!initScope) initScope = []; + // handling circular init calls + var initToken = initTokens[name]; + if (!initToken) initToken = initTokens[name] = {}; + if (initScope.indexOf(initToken) >= 0) return; + initScope.push(initToken); + // only runs once + if (initPromises[name]) return initPromises[name]; + // creates a new share scope if needed + if (!__webpack_require__.o(__webpack_require__.S, name)) + __webpack_require__.S[name] = {}; + // runs all init snippets from all modules reachable + var scope = __webpack_require__.S[name]; + var warn = function (msg) { + if (typeof console !== "undefined" && console.warn) console.warn(msg); + }; + var uniqueName = "app"; + var register = function (name, version, factory, eager) { + var versions = (scope[name] = scope[name] || {}); + var activeVersion = versions[version]; + if ( + !activeVersion || + (!activeVersion.loaded && + (!eager != !activeVersion.eager + ? eager + : uniqueName > activeVersion.from)) + ) + versions[version] = { get: factory, from: uniqueName, eager: !!eager }; + }; + var initExternal = function (id) { + var handleError = function (err) { + warn("Initialization of sharing external failed: " + err); + }; + try { + var module = __webpack_require__(id); + if (!module) return; + var initFn = function (module) { + return ( + module && + module.init && + module.init(__webpack_require__.S[name], initScope) + ); + }; + if (module.then) return promises.push(module.then(initFn, handleError)); + var initResult = initFn(module); + if (initResult && initResult.then) + return promises.push(initResult["catch"](handleError)); + } catch (err) { + handleError(err); + } + }; + var promises = []; + initPerScope(name, register, initExternal); + if (!promises.length) return (initPromises[name] = 1); + return (initPromises[name] = Promise.all(promises).then(function () { + return (initPromises[name] = 1); + })); +}; diff --git a/packages/rspack/src/util/validate.ts b/packages/rspack/src/util/validate.ts index e900d28cc3fb..cf611e45cdc8 100644 --- a/packages/rspack/src/util/validate.ts +++ b/packages/rspack/src/util/validate.ts @@ -27,3 +27,12 @@ export function validate(opts: any, schema: T) { } } } + +export function isValidate(opts: any, schema: T) { + try { + validate(opts, schema); + return true; + } catch { + return false; + } +} diff --git a/webpack-test/configCases/container/circular/test.filter.js b/webpack-test/configCases/container/circular/test.filter.js deleted file mode 100644 index 3be456dcd23c..000000000000 --- a/webpack-test/configCases/container/circular/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/circular/webpack.config.js b/webpack-test/configCases/container/circular/webpack.config.js index b4aee09245ae..2135a841a5fc 100644 --- a/webpack-test/configCases/container/circular/webpack.config.js +++ b/webpack-test/configCases/container/circular/webpack.config.js @@ -1,4 +1,4 @@ -const { ModuleFederationPlugin } = require("../../../../").container; +const { ModuleFederationPlugin } = require("../../../../packages/rspack").container; function createConfig() { return { diff --git a/webpack-test/configCases/container/container-reference/test.filter.js b/webpack-test/configCases/container/container-reference/test.filter.js deleted file mode 100644 index 3be456dcd23c..000000000000 --- a/webpack-test/configCases/container/container-reference/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-reference/webpack.config.js b/webpack-test/configCases/container/container-reference/webpack.config.js index 551ecd20c5e2..dbb217eaea37 100644 --- a/webpack-test/configCases/container/container-reference/webpack.config.js +++ b/webpack-test/configCases/container/container-reference/webpack.config.js @@ -1,4 +1,4 @@ -const { ContainerReferencePlugin } = require("../../../../").container; +const { ContainerReferencePlugin } = require("../../../../packages/rspack").container; /** @type {import("@rspack/core").Configuration} */ module.exports = { diff --git a/webpack-test/configCases/container/error-handling/test.filter.js b/webpack-test/configCases/container/error-handling/test.filter.js deleted file mode 100644 index 3be456dcd23c..000000000000 --- a/webpack-test/configCases/container/error-handling/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/error-handling/webpack.config.js b/webpack-test/configCases/container/error-handling/webpack.config.js index 06f0b652a68c..dac043abb22b 100644 --- a/webpack-test/configCases/container/error-handling/webpack.config.js +++ b/webpack-test/configCases/container/error-handling/webpack.config.js @@ -1,4 +1,4 @@ -const { ModuleFederationPlugin } = require("../../../../").container; +const { ModuleFederationPlugin } = require("../../../../packages/rspack").container; /** @type {import("@rspack/core").Configuration} */ module.exports = { diff --git a/webpack-test/configCases/container/module-federation/test.filter.js b/webpack-test/configCases/container/module-federation/test.filter.js deleted file mode 100644 index 3be456dcd23c..000000000000 --- a/webpack-test/configCases/container/module-federation/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/module-federation/webpack.config.js b/webpack-test/configCases/container/module-federation/webpack.config.js index 581faaa35c3a..a2f30e0700b6 100644 --- a/webpack-test/configCases/container/module-federation/webpack.config.js +++ b/webpack-test/configCases/container/module-federation/webpack.config.js @@ -1,4 +1,4 @@ -const { ModuleFederationPlugin } = require("../../../../").container; +const { ModuleFederationPlugin } = require("../../../../packages/rspack").container; function createConfig() { return {