diff --git a/crates/rspack_core/src/mf/container/remote_module.rs b/crates/rspack_core/src/mf/container/remote_module.rs index 6fb0715805d..893cb88a100 100644 --- a/crates/rspack_core/src/mf/container/remote_module.rs +++ b/crates/rspack_core/src/mf/container/remote_module.rs @@ -9,7 +9,7 @@ use rspack_sources::{RawSource, Source, SourceExt}; use super::remote_to_external_dependency::RemoteToExternalDependency; use crate::{ - mf::share_runtime_module::{CodeGenerationDataShareInit, ShareInitData}, + mf::share_runtime_module::{CodeGenerationDataShareInit, DataInitInfo, ShareInitData}, AsyncDependenciesBlockIdentifier, BoxDependency, BuildContext, BuildInfo, BuildResult, CodeGenerationResult, Compilation, Context, DependenciesBlock, DependencyId, LibIdentOptions, Module, ModuleIdentifier, ModuleType, RuntimeSpec, SourceType, @@ -160,10 +160,7 @@ impl Module for RemoteModule { items: vec![ShareInitData { share_scope: self.share_scope.clone(), init_stage: 20, - init: format!( - "initExternal({});", - serde_json::to_string(&id).expect("module_id should able to json to_string") - ), + init: DataInitInfo::ExternalModuleId(id.map(|i| i.to_owned())), }], }); Ok(codegen) diff --git a/crates/rspack_core/src/mf/container/remote_runtime_module.rs b/crates/rspack_core/src/mf/container/remote_runtime_module.rs index 501aac5afa1..7555755d462 100644 --- a/crates/rspack_core/src/mf/container/remote_runtime_module.rs +++ b/crates/rspack_core/src/mf/container/remote_runtime_module.rs @@ -83,16 +83,15 @@ impl RuntimeModule for RemoteRuntimeModule { } 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 }}); }}; +__webpack_require__.MF.remotesLoadingData = {{ chunkMapping: {chunk_mapping}, moduleIdToRemoteDataMapping: {id_to_external_and_name_mapping} }}; +{ensure_chunk_handlers}.remotes = function(chunkId, promises) {{ return {remotes_fn}(chunkId, promises); }}; "#, 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", + remotes_fn = "__webpack_require__.MF.remotesLoading", )) .boxed() } diff --git a/crates/rspack_core/src/mf/mod.rs b/crates/rspack_core/src/mf/mod.rs index 24c43f7c4ab..d096f832fb0 100644 --- a/crates/rspack_core/src/mf/mod.rs +++ b/crates/rspack_core/src/mf/mod.rs @@ -3,3 +3,13 @@ mod container; mod sharing; pub use container::*; pub use sharing::*; + +mod utils { + use std::fmt; + + use serde::Serialize; + + pub fn json_stringify(v: &T) -> String { + serde_json::to_string(v).unwrap_or_else(|e| panic!("{e}: {v:?} should able to json stringify")) + } +} diff --git a/crates/rspack_core/src/mf/sharing/consume_shared_module.rs b/crates/rspack_core/src/mf/sharing/consume_shared_module.rs index e07c28e0b19..858e86e0d93 100644 --- a/crates/rspack_core/src/mf/sharing/consume_shared_module.rs +++ b/crates/rspack_core/src/mf/sharing/consume_shared_module.rs @@ -4,14 +4,15 @@ 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 rspack_sources::Source; use super::{ consume_shared_fallback_dependency::ConsumeSharedFallbackDependency, consume_shared_plugin::ConsumeOptions, + consume_shared_runtime_module::CodeGenerationDataConsumeShared, }; use crate::{ - async_module_factory, returning_function, sync_module_factory, AsyncDependenciesBlock, + async_module_factory, sync_module_factory, AsyncDependenciesBlock, AsyncDependenciesBlockIdentifier, BoxDependency, BuildContext, BuildInfo, BuildResult, CodeGenerationResult, Compilation, Context, DependenciesBlock, DependencyId, LibIdentOptions, Module, ModuleIdentifier, ModuleType, RuntimeGlobals, RuntimeSpec, SourceType, @@ -193,8 +194,8 @@ impl Module for ConsumeSharedModule { } else if self.options.singleton { function += "Singleton"; } - if let Some(fallback) = &self.options.import { - let code = if self.options.eager { + let factory = self.options.import.as_ref().map(|fallback| { + if self.options.eager { sync_module_factory( &self.get_dependencies()[0], fallback, @@ -208,15 +209,20 @@ impl Module for ConsumeSharedModule { compilation, &mut code_generation_result.runtime_requirements, ) - }; - function += "Fallback"; - args.push(code); - }; - function += "("; - function += &args.join(", "); - function += ")"; - let code = returning_function(&function, "loaders"); - code_generation_result.add(SourceType::ConsumeShared, RawSource::from(code).boxed()); + } + }); + code_generation_result + .data + .insert(CodeGenerationDataConsumeShared { + share_scope: self.options.share_scope.clone(), + share_key: self.options.share_key.clone(), + import: self.options.import.clone(), + required_version: self.options.required_version.clone(), + strict_version: self.options.strict_version, + singleton: self.options.singleton, + eager: self.options.eager, + fallback: factory, + }); Ok(code_generation_result) } } diff --git a/crates/rspack_core/src/mf/sharing/consume_shared_runtime_module.rs b/crates/rspack_core/src/mf/sharing/consume_shared_runtime_module.rs index 2e5e7952ca4..dadb71ece24 100644 --- a/crates/rspack_core/src/mf/sharing/consume_shared_runtime_module.rs +++ b/crates/rspack_core/src/mf/sharing/consume_shared_runtime_module.rs @@ -2,9 +2,10 @@ use rspack_identifier::Identifier; use rspack_sources::{BoxSource, RawSource, SourceExt}; use rustc_hash::FxHashMap; +use super::consume_shared_plugin::ConsumeVersion; use crate::{ - impl_runtime_module, Chunk, ChunkUkey, Compilation, ModuleIdentifier, RuntimeGlobals, - RuntimeModule, RuntimeModuleStage, SourceType, + impl_runtime_module, mf::utils::json_stringify, Chunk, ChunkUkey, Compilation, ModuleIdentifier, + RuntimeGlobals, RuntimeModule, RuntimeModuleStage, SourceType, }; #[derive(Debug, Eq)] @@ -16,7 +17,7 @@ pub struct ConsumeSharedRuntimeModule { impl Default for ConsumeSharedRuntimeModule { fn default() -> Self { Self { - id: Identifier::from("webpack/runtime/consumes"), + id: Identifier::from("webpack/runtime/consumes_loading"), chunk: None, } } @@ -37,7 +38,7 @@ impl RuntimeModule for ConsumeSharedRuntimeModule { .expect("should have chunk in ::generate"); let chunk = compilation.chunk_by_ukey.expect_get(&chunk_ukey); let mut chunk_to_module_mapping = FxHashMap::default(); - let mut module_id_to_source_mapping = FxHashMap::default(); + let mut module_id_to_consume_data_mapping = FxHashMap::default(); let mut initial_consumes = Vec::new(); let mut add_module = |module: ModuleIdentifier, chunk: &Chunk, ids: &mut Vec| { let id = compilation @@ -49,9 +50,19 @@ impl RuntimeModule for ConsumeSharedRuntimeModule { if let Ok(code_gen) = compilation .code_generation_results .get(&module, Some(&chunk.runtime)) - && let Some(source) = code_gen.get(&SourceType::ConsumeShared) + && let Some(data) = code_gen.data.get::() { - module_id_to_source_mapping.insert(id, source.clone()); + module_id_to_consume_data_mapping.insert(id, format!( + "{{ shareScope: {}, shareKey: {}, import: {}, requiredVersion: {}, strictVersion: {}, singleton: {}, eager: {}, fallback: {} }}", + json_stringify(&data.share_scope), + json_stringify(&data.share_key), + json_stringify(&data.import), + json_stringify(&data.required_version.as_ref().map(|v| v.to_string()).unwrap_or_else(|| "*".to_string())), + json_stringify(&data.strict_version), + json_stringify(&data.singleton), + json_stringify(&data.eager), + data.fallback.as_deref().unwrap_or("undefined"), + )); } }; for chunk in chunk.get_all_async_chunks(&compilation.chunk_group_by_ukey) { @@ -88,31 +99,28 @@ impl RuntimeModule for ConsumeSharedRuntimeModule { add_module(module.identifier(), chunk, &mut initial_consumes); } } - if module_id_to_source_mapping.is_empty() { + if module_id_to_consume_data_mapping.is_empty() { return RawSource::from("").boxed(); } - let module_to_handler_mapping = module_id_to_source_mapping + let module_id_to_consume_data_mapping = module_id_to_consume_data_mapping .into_iter() .map(|(k, v)| { format!( "{}: {}", serde_json::to_string(&k) .expect("module_id_to_source_mapping key should able to json to_string"), - v.source() + v ) }) .collect::>() .join(", "); let mut source = format!( r#" -var chunkMapping = {chunk_mapping}; -var moduleToHandlerMapping = {{ {module_to_handler_mapping} }}; -var initialConsumes = {initial_consumes}; -__webpack_require__.MF.initialConsumesData = {{ initialConsumes: initialConsumes, moduleToHandlerMapping: moduleToHandlerMapping }}; +__webpack_require__.MF.consumesLoadingData = {{ chunkMapping: {chunk_mapping}, moduleIdToConsumeDataMapping: {{ {module_to_consume_data_mapping} }}, initialConsumeModuleIds: {initial_consumes} }}; "#, chunk_mapping = serde_json::to_string(&chunk_to_module_mapping) .expect("chunk_to_module_mapping should able to json to_string"), - module_to_handler_mapping = module_to_handler_mapping, + module_to_consume_data_mapping = module_id_to_consume_data_mapping, initial_consumes = serde_json::to_string(&initial_consumes) .expect("initial_consumes should able to json to_string"), ); @@ -122,9 +130,9 @@ __webpack_require__.MF.initialConsumesData = {{ initialConsumes: initialConsumes .runtime_requirements .contains(RuntimeGlobals::ENSURE_CHUNK_HANDLERS) { - source += &format!("{ensure_chunk_handlers}.consumes = function(chunkId, promises) {{ return {consumes_loading_fn}({{ chunkId: chunkId, promises: promises, chunkMapping: chunkMapping, moduleToHandlerMapping: moduleToHandlerMapping }}); }};", + source += &format!("{ensure_chunk_handlers}.consumes = function(chunkId, promises) {{ return {consumes_loading_fn}(chunkId, promises); }};", ensure_chunk_handlers = RuntimeGlobals::ENSURE_CHUNK_HANDLERS, - consumes_loading_fn = "__webpack_require__.MF.consumes", + consumes_loading_fn = "__webpack_require__.MF.consumesLoading", ); } RawSource::from(source).boxed() @@ -136,3 +144,15 @@ __webpack_require__.MF.initialConsumesData = {{ initialConsumes: initialConsumes } impl_runtime_module!(ConsumeSharedRuntimeModule); + +#[derive(Debug, Clone)] +pub struct CodeGenerationDataConsumeShared { + pub share_scope: String, + pub share_key: String, + pub import: Option, + pub required_version: Option, + pub strict_version: bool, + pub singleton: bool, + pub eager: bool, + pub fallback: Option, +} diff --git a/crates/rspack_core/src/mf/sharing/provide_shared_module.rs b/crates/rspack_core/src/mf/sharing/provide_shared_module.rs index 86a5a9f3a51..62025f021a3 100644 --- a/crates/rspack_core/src/mf/sharing/provide_shared_module.rs +++ b/crates/rspack_core/src/mf/sharing/provide_shared_module.rs @@ -9,7 +9,9 @@ use rspack_sources::Source; use super::{ provide_for_shared_dependency::ProvideForSharedDependency, provide_shared_plugin::ProvideVersion, - share_runtime_module::{CodeGenerationDataShareInit, ShareInitData}, + share_runtime_module::{ + CodeGenerationDataShareInit, DataInitInfo, ProvideSharedInfo, ShareInitData, + }, }; use crate::{ async_module_factory, sync_module_factory, AsyncDependenciesBlock, @@ -154,34 +156,33 @@ impl Module for ProvideSharedModule { code_generation_result .runtime_requirements .insert(RuntimeGlobals::INITIALIZE_SHARING); - let init = format!( - "register({}, {}, {}{})", - serde_json::to_string(&self.name).expect("ProvideSharedModule name should able to json to_string"), - serde_json::to_string(&self.version.to_string()).expect("ProvideVersion::Version should able to json to_string in ProvideSharedModule::code_generation"), - if self.eager { - sync_module_factory( - &self.get_dependencies()[0], - &self.request, - compilation, - &mut code_generation_result.runtime_requirements, - ) - } else { - async_module_factory( - &self.get_blocks()[0], - &self.request, - compilation, - &mut code_generation_result.runtime_requirements, - ) - }, - if self.eager { ", 1" } else { "" }, - ); + let factory = if self.eager { + sync_module_factory( + &self.get_dependencies()[0], + &self.request, + compilation, + &mut code_generation_result.runtime_requirements, + ) + } else { + async_module_factory( + &self.get_blocks()[0], + &self.request, + compilation, + &mut code_generation_result.runtime_requirements, + ) + }; code_generation_result .data .insert(CodeGenerationDataShareInit { items: vec![ShareInitData { share_scope: self.share_scope.clone(), init_stage: 10, - init, + init: DataInitInfo::ProvideSharedInfo(ProvideSharedInfo { + name: self.name.clone(), + version: self.version.clone(), + factory, + eager: self.eager, + }), }], }); Ok(code_generation_result) diff --git a/crates/rspack_core/src/mf/sharing/provide_shared_plugin.rs b/crates/rspack_core/src/mf/sharing/provide_shared_plugin.rs index ddad5ff4e68..ddfdba7f0d8 100644 --- a/crates/rspack_core/src/mf/sharing/provide_shared_plugin.rs +++ b/crates/rspack_core/src/mf/sharing/provide_shared_plugin.rs @@ -46,7 +46,7 @@ impl ProvideOptions { } } -#[derive(Debug, Default, Clone)] +#[derive(Debug, Default, Clone, PartialEq, Eq, Hash)] pub enum ProvideVersion { Version(String), #[default] diff --git a/crates/rspack_core/src/mf/sharing/share_runtime_module.rs b/crates/rspack_core/src/mf/sharing/share_runtime_module.rs index 0f5e9d45321..90ce09fd3d8 100644 --- a/crates/rspack_core/src/mf/sharing/share_runtime_module.rs +++ b/crates/rspack_core/src/mf/sharing/share_runtime_module.rs @@ -4,8 +4,10 @@ use rspack_identifier::Identifier; use rspack_sources::{BoxSource, RawSource, SourceExt}; use rustc_hash::FxHashMap; +use super::provide_shared_plugin::ProvideVersion; use crate::{ - impl_runtime_module, ChunkUkey, Compilation, RuntimeGlobals, RuntimeModule, SourceType, + impl_runtime_module, mf::utils::json_stringify, ChunkUkey, Compilation, RuntimeGlobals, + RuntimeModule, SourceType, }; #[derive(Debug, Eq)] @@ -33,8 +35,10 @@ impl RuntimeModule for ShareRuntimeModule { .chunk .expect("should have chunk in ::generate"); let chunk = compilation.chunk_by_ukey.expect_get(&chunk_ukey); - let mut init_per_scope: FxHashMap>> = - FxHashMap::default(); + 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 @@ -64,42 +68,41 @@ impl RuntimeModule for ShareRuntimeModule { } } } - let init_per_scope_body = init_per_scope + let scope_to_data_init = init_per_scope .into_iter() .sorted_unstable_by_key(|(scope, _)| scope.to_string()) .map(|(scope, stages)| { - let stages = stages + let stages: Vec = stages .into_iter() .sorted_unstable_by_key(|(stage, _)| *stage) .flat_map(|(_, inits)| inits) - .collect::>() - .join("\n"); - format!( - r#"case {}: {{ -{} -}} -break;"#, - serde_json::to_string(&scope).expect("should able to json to_string"), - stages - ) + .map(|info| match info { + DataInitInfo::ExternalModuleId(Some(id)) => json_stringify(&id), + DataInitInfo::ProvideSharedInfo(info) => { + format!( + "[{}, {}, {}, {}]", + json_stringify(&info.name), + json_stringify(&info.version.to_string()), + info.factory, + if info.eager { "1" } else { "0" } + ) + } + _ => "".to_string(), + }) + .collect(); + format!("{}: [{}]", json_stringify(&scope), stages.join(", ")) }) .collect::>() - .join("\n"); + .join(", "); 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, uniqueName: {unique_name}, initTokens: initTokens, initPromises: initPromises }}); }}; +__webpack_require__.MF.initializeSharingData = {{ scopeToSharingDataMapping: {{ {scope_to_data_init} }}, uniqueName: {unique_name} }}; +{initialize_sharing} = function(name, initScope) {{ return {initialize_sharing_fn}(name, initScope); }}; "#, share_scope_map = RuntimeGlobals::SHARE_SCOPE_MAP, - init_per_scope_body = init_per_scope_body, - unique_name = serde_json::to_string(&compilation.options.output.unique_name).expect("uniqueName should able to json to_string"), + scope_to_data_init = scope_to_data_init, + unique_name = json_stringify(&compilation.options.output.unique_name), initialize_sharing = RuntimeGlobals::INITIALIZE_SHARING, initialize_sharing_fn = "__webpack_require__.MF.initializeSharing" )) @@ -122,7 +125,21 @@ pub struct CodeGenerationDataShareInit { pub struct ShareInitData { pub share_scope: String, pub init_stage: DataInitStage, - pub init: String, + pub init: DataInitInfo, } pub type DataInitStage = i8; + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub enum DataInitInfo { + ExternalModuleId(Option), + ProvideSharedInfo(ProvideSharedInfo), +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct ProvideSharedInfo { + pub name: String, + pub version: ProvideVersion, + pub factory: String, + pub eager: bool, +} diff --git a/packages/rspack/src/container/ModuleFederationPlugin.ts b/packages/rspack/src/container/ModuleFederationPlugin.ts index dde18df0469..1b1175080a8 100644 --- a/packages/rspack/src/container/ModuleFederationPlugin.ts +++ b/packages/rspack/src/container/ModuleFederationPlugin.ts @@ -21,6 +21,8 @@ export interface ModuleFederationPluginOptions { runtime?: EntryRuntime; shareScope?: string; shared?: Shared; + + runtimePlugins?: string[]; } export class ModuleFederationPlugin { @@ -74,6 +76,10 @@ export class ModuleFederationPlugin { shareScope: options.shareScope }).apply(compiler); } + const runtimePlugins = options.runtimePlugins ?? []; + for (let plugin of runtimePlugins) { + ModuleFederationRuntimePlugin.addPlugin(compiler, plugin); + } }); } } diff --git a/packages/rspack/src/container/remotesLoading.js b/packages/rspack/src/container/remotesLoading.js index a73d1b3bfaf..700ec2479c9 100644 --- a/packages/rspack/src/container/remotesLoading.js +++ b/packages/rspack/src/container/remotesLoading.js @@ -1,16 +1,15 @@ // @ts-nocheck if (__webpack_require__.MF) { - __webpack_require__.MF.remotes = function (data) { - var chunkId = data.chunkId, - promises = data.promises, - chunkMapping = data.chunkMapping, - idToExternalAndNameMapping = data.idToExternalAndNameMapping; + __webpack_require__.MF.remotesLoading = function (chunkId, promises) { + var chunkMapping = __webpack_require__.MF.remotesLoadingData.chunkMapping; + var moduleIdToRemoteDataMapping = + __webpack_require__.MF.remotesLoadingData.moduleIdToRemoteDataMapping; if (__webpack_require__.o(chunkMapping, chunkId)) { chunkMapping[chunkId].forEach(function (id) { var getScope = __webpack_require__.R; if (!getScope) getScope = []; - var data = idToExternalAndNameMapping[id]; + var data = moduleIdToRemoteDataMapping[id]; if (getScope.indexOf(data) >= 0) return; getScope.push(data); if (data.p) return promises.push(data.p); diff --git a/packages/rspack/src/sharing/consumesLoading.js b/packages/rspack/src/sharing/consumesLoading.js index b7b9bb1b053..e09290842d5 100644 --- a/packages/rspack/src/sharing/consumesLoading.js +++ b/packages/rspack/src/sharing/consumesLoading.js @@ -483,28 +483,75 @@ if (__webpack_require__.MF) { if (!scope || !__webpack_require__.o(scope, key)) return fallback(); return getStrictSingletonVersion(scope, scopeName, key, version); }); - var loaders = { - parseRange: parseRange, - load: load, - loadFallback: loadFallback, - loadVersionCheck: loadVersionCheck, - loadSingleton: loadSingleton, - loadSingletonVersionCheck: loadSingletonVersionCheck, - loadStrictVersionCheck: loadStrictVersionCheck, - loadStrictSingletonVersionCheck: loadStrictSingletonVersionCheck, - loadVersionCheckFallback: loadVersionCheckFallback, - loadSingletonFallback: loadSingletonFallback, - loadSingletonVersionCheckFallback: loadSingletonVersionCheckFallback, - loadStrictVersionCheckFallback: loadStrictVersionCheckFallback, - loadStrictSingletonVersionCheckFallback: - loadStrictSingletonVersionCheckFallback + var resolveHandler = function (data) { + var strict = false; + var singleton = false; + var versionCheck = false; + var fallback = false; + var args = [data.shareScope, data.shareKey]; + if (data.requiredVersion) { + if (data.strictVersion) strict = true; + if (data.singleton) singleton = true; + args.push(parseRange(data.requiredVersion)); + versionCheck = true; + } else if (data.singleton) singleton = true; + if (data.fallback) { + fallback = true; + args.push(data.fallback); + } + if (strict && singleton && versionCheck && fallback) + return function () { + return loadStrictSingletonVersionCheckFallback.apply(null, args); + }; + if (strict && versionCheck && fallback) + return function () { + return loadStrictVersionCheckFallback.apply(null, args); + }; + if (singleton && versionCheck && fallback) + return function () { + return loadSingletonVersionCheckFallback.apply(null, args); + }; + if (strict && singleton && versionCheck) + return function () { + return loadStrictSingletonVersionCheck.apply(null, args); + }; + if (singleton && fallback) + return function () { + return loadSingletonFallback.apply(null, args); + }; + if (versionCheck && fallback) + return function () { + return loadVersionCheckFallback.apply(null, args); + }; + if (strict && versionCheck) + return function () { + return loadStrictVersionCheck.apply(null, args); + }; + if (singleton && versionCheck) + return function () { + return loadSingletonVersionCheck.apply(null, args); + }; + if (singleton) + return function () { + return loadSingleton.apply(null, args); + }; + if (versionCheck) + return function () { + return loadVersionCheck.apply(null, args); + }; + if (fallback) + return function () { + return loadFallback.apply(null, args); + }; + return function () { + return load.apply(null, args); + }; }; var installedModules = {}; - __webpack_require__.MF.consumes = function (data) { - var chunkId = data.chunkId, - promises = data.promises, - chunkMapping = data.chunkMapping, - moduleToHandlerMapping = data.moduleToHandlerMapping; + __webpack_require__.MF.consumesLoading = function (chunkId, promises) { + var chunkMapping = __webpack_require__.MF.consumesLoadingData.chunkMapping; + var moduleIdToConsumeDataMapping = + __webpack_require__.MF.consumesLoadingData.moduleIdToConsumeDataMapping; if (__webpack_require__.o(chunkMapping, chunkId)) { chunkMapping[chunkId].forEach(function (id) { if (__webpack_require__.o(installedModules, id)) @@ -524,7 +571,7 @@ if (__webpack_require__.MF) { }; }; try { - var promise = moduleToHandlerMapping[id](loaders); + var promise = resolveHandler(moduleIdToConsumeDataMapping[id])(); if (promise.then) { promises.push( (installedModules[id] = promise.then(onFactory)["catch"](onError)) @@ -536,16 +583,18 @@ if (__webpack_require__.MF) { }); } }; - __webpack_require__.MF.initialConsumes = function (data) { - var initialConsumes = data.initialConsumes, - moduleToHandlerMapping = data.moduleToHandlerMapping; - if (initialConsumes) { - initialConsumes.forEach(function (id) { + __webpack_require__.MF.initialConsumes = function () { + var initialConsumeModuleIds = + __webpack_require__.MF.consumesLoadingData.initialConsumeModuleIds; + var moduleIdToConsumeDataMapping = + __webpack_require__.MF.consumesLoadingData.moduleIdToConsumeDataMapping; + if (initialConsumeModuleIds) { + initialConsumeModuleIds.forEach(function (id) { __webpack_require__.m[id] = function (module) { // Handle case when module is used sync installedModules[id] = 0; delete __webpack_require__.c[id]; - var factory = moduleToHandlerMapping[id](loaders); + var factory = resolveHandler(moduleIdToConsumeDataMapping[id])(); if (typeof factory !== "function") throw new Error( "Shared module is not available for eager consumption: " + id @@ -555,9 +604,7 @@ if (__webpack_require__.MF) { }); } }; - if (__webpack_require__.MF.initialConsumesData) { - __webpack_require__.MF.initialConsumes( - __webpack_require__.MF.initialConsumesData - ); + if (__webpack_require__.MF.consumesLoadingData?.initialConsumeModuleIds) { + __webpack_require__.MF.initialConsumes(); } } diff --git a/packages/rspack/src/sharing/initializeSharing.js b/packages/rspack/src/sharing/initializeSharing.js index 3ed1de47ba4..613a64d1d4f 100644 --- a/packages/rspack/src/sharing/initializeSharing.js +++ b/packages/rspack/src/sharing/initializeSharing.js @@ -1,12 +1,11 @@ // @ts-nocheck if (__webpack_require__.MF) { - __webpack_require__.MF.initializeSharing = function (data) { - var name = data.name, - initScope = data.initScope, - initPerScope = data.initPerScope, - initTokens = data.initTokens, - initPromises = data.initPromises; + var initPromises = {}; + var initTokens = {}; + __webpack_require__.MF.initializeSharing = function (name, initScope) { + var scopeToSharingDataMapping = + __webpack_require__.MF.initializeSharingData.scopeToSharingDataMapping; if (!initScope) initScope = []; // handling circular init calls var initToken = initTokens[name]; @@ -23,7 +22,7 @@ if (__webpack_require__.MF) { var warn = function (msg) { if (typeof console !== "undefined" && console.warn) console.warn(msg); }; - var uniqueName = data.uniqueName; + var uniqueName = __webpack_require__.MF.initializeSharingData.uniqueName; var register = function (name, version, factory, eager) { var versions = (scope[name] = scope[name] || {}); var activeVersion = versions[version]; @@ -59,7 +58,13 @@ if (__webpack_require__.MF) { } }; var promises = []; - initPerScope(name, register, initExternal); + if (scopeToSharingDataMapping[name]) { + scopeToSharingDataMapping[name].forEach(function (stage) { + if (typeof stage === "string" && stage) initExternal(stage); + else if (Array.isArray(stage)) + register(stage[0], stage[1], stage[2], stage[3]); + }); + } if (!promises.length) return (initPromises[name] = 1); return (initPromises[name] = Promise.all(promises).then(function () { return (initPromises[name] = 1); diff --git a/webpack-test/configCases/container/module-federation-with-shareScope/App.js b/webpack-test/configCases/container/module-federation-with-shareScope/App.js new file mode 100644 index 00000000000..43f44221946 --- /dev/null +++ b/webpack-test/configCases/container/module-federation-with-shareScope/App.js @@ -0,0 +1,10 @@ +import React from "react"; +import ComponentA from "containerA/ComponentA"; +import ComponentB from "containerB/ComponentB"; +import LocalComponentB from "./ComponentB"; + +export default () => { + return `App rendered with [${React()}] and [${ComponentA()}] and [${ComponentB()}]`; +}; + +expect(ComponentB).not.toBe(LocalComponentB); diff --git a/webpack-test/configCases/container/module-federation-with-shareScope/ComponentB.js b/webpack-test/configCases/container/module-federation-with-shareScope/ComponentB.js new file mode 100644 index 00000000000..1943469c746 --- /dev/null +++ b/webpack-test/configCases/container/module-federation-with-shareScope/ComponentB.js @@ -0,0 +1,5 @@ +import React from "react"; + +export default () => { + return `ComponentB rendered with [${React()}]`; +}; diff --git a/webpack-test/configCases/container/module-federation-with-shareScope/ComponentC.js b/webpack-test/configCases/container/module-federation-with-shareScope/ComponentC.js new file mode 100644 index 00000000000..3ff3832c718 --- /dev/null +++ b/webpack-test/configCases/container/module-federation-with-shareScope/ComponentC.js @@ -0,0 +1,7 @@ +import React from "react"; +import ComponentA from "containerA/ComponentA"; +import ComponentB from "containerB/ComponentB"; + +export default () => { + return `ComponentC rendered with [${React()}] and [${ComponentA()}] and [${ComponentB()}]`; +}; diff --git a/webpack-test/configCases/container/module-federation-with-shareScope/index.js b/webpack-test/configCases/container/module-federation-with-shareScope/index.js new file mode 100644 index 00000000000..cedabf6db95 --- /dev/null +++ b/webpack-test/configCases/container/module-federation-with-shareScope/index.js @@ -0,0 +1,20 @@ +it("should load the component from container", async () => { + await __webpack_init_sharing__("test-scope"); + + // 2 scopes for "0-container-full-mjs" & "mf-with-shareScope-mjs" + expect(Object.keys(__webpack_share_scopes__["test-scope"].react).length).toBe(2); + + return import("./App").then(({ default: App }) => { + const rendered = App(); + expect(rendered).toBe( + "App rendered with [This is react 2.1.0] and [ComponentA rendered with [This is react 2.1.0]] and [ComponentB rendered with [This is react 2.1.0]]" + ); + return import("./upgrade-react").then(({ default: upgrade }) => { + upgrade(); + const rendered = App(); + expect(rendered).toBe( + "App rendered with [This is react 3.2.1] and [ComponentA rendered with [This is react 3.2.1]] and [ComponentB rendered with [This is react 3.2.1]]" + ); + }); + }); +}); diff --git a/webpack-test/configCases/container/module-federation-with-shareScope/node_modules/package.json b/webpack-test/configCases/container/module-federation-with-shareScope/node_modules/package.json new file mode 100644 index 00000000000..87032da008a --- /dev/null +++ b/webpack-test/configCases/container/module-federation-with-shareScope/node_modules/package.json @@ -0,0 +1,3 @@ +{ + "version": "2.1.0" +} diff --git a/webpack-test/configCases/container/module-federation-with-shareScope/node_modules/react.js b/webpack-test/configCases/container/module-federation-with-shareScope/node_modules/react.js new file mode 100644 index 00000000000..97d35a4bc9c --- /dev/null +++ b/webpack-test/configCases/container/module-federation-with-shareScope/node_modules/react.js @@ -0,0 +1,3 @@ +let version = "2.1.0"; +export default () => `This is react ${version}`; +export function setVersion(v) { version = v; } diff --git a/webpack-test/configCases/container/module-federation-with-shareScope/package.json b/webpack-test/configCases/container/module-federation-with-shareScope/package.json new file mode 100644 index 00000000000..be6238fec84 --- /dev/null +++ b/webpack-test/configCases/container/module-federation-with-shareScope/package.json @@ -0,0 +1,9 @@ +{ + "private": true, + "engines": { + "node": ">=10.13.0" + }, + "dependencies": { + "react": "*" + } +} diff --git a/webpack-test/configCases/container/module-federation-with-shareScope/test.config.js b/webpack-test/configCases/container/module-federation-with-shareScope/test.config.js new file mode 100644 index 00000000000..2d0d66fd4c0 --- /dev/null +++ b/webpack-test/configCases/container/module-federation-with-shareScope/test.config.js @@ -0,0 +1,5 @@ +module.exports = { + findBundle: function (i, options) { + return i === 0 ? "./main.js" : "./module/main.mjs"; + } +}; diff --git a/webpack-test/configCases/container/module-federation-with-shareScope/test.filter.js b/webpack-test/configCases/container/module-federation-with-shareScope/test.filter.js new file mode 100644 index 00000000000..391740dc97c --- /dev/null +++ b/webpack-test/configCases/container/module-federation-with-shareScope/test.filter.js @@ -0,0 +1,2 @@ +const { FilteredStatus } = require("../../../lib/util/filterUtil") +module.exports = () => {return [FilteredStatus.PARTIAL_PASS, "https://github.com/web-infra-dev/rspack/issues/4784"]} \ No newline at end of file diff --git a/webpack-test/configCases/container/module-federation-with-shareScope/upgrade-react.js b/webpack-test/configCases/container/module-federation-with-shareScope/upgrade-react.js new file mode 100644 index 00000000000..2cadfc0b71a --- /dev/null +++ b/webpack-test/configCases/container/module-federation-with-shareScope/upgrade-react.js @@ -0,0 +1,5 @@ +import { setVersion } from "react"; + +export default function upgrade() { + setVersion("3.2.1"); +} diff --git a/webpack-test/configCases/container/module-federation-with-shareScope/webpack.config.js b/webpack-test/configCases/container/module-federation-with-shareScope/webpack.config.js new file mode 100644 index 00000000000..c7c7c547e5a --- /dev/null +++ b/webpack-test/configCases/container/module-federation-with-shareScope/webpack.config.js @@ -0,0 +1,68 @@ +// eslint-disable-next-line node/no-unpublished-require +const { ModuleFederationPlugin } = require("../../../../").container; + +const common = { + entry: { + main: "./index.js" + }, + optimization: { + runtimeChunk: "single" + } +}; + +/** @type {ConstructorParameters[0]} */ +const commonMF = { + runtime: false, + exposes: { + "./ComponentB": "./ComponentB", + "./ComponentC": "./ComponentC" + }, + shared: ["react"], + shareScope: "test-scope" +}; + +/** @type {import("../../../../types").Configuration[]} */ +module.exports = [ + { + ...common, + output: { + filename: "[name].js", + uniqueName: "mf-with-shareScope" + }, + plugins: [ + new ModuleFederationPlugin({ + name: "container", + library: { type: "commonjs-module" }, + filename: "container.js", + remotes: { + containerA: "../0-container-full/container.js", + containerB: "./container.js" + }, + ...commonMF + }) + ] + }, + { + ...common, + experiments: { + outputModule: true + }, + output: { + filename: "module/[name].mjs", + uniqueName: "mf-with-shareScope-mjs" + }, + plugins: [ + new ModuleFederationPlugin({ + name: "container", + library: { type: "module" }, + filename: "module/container.mjs", + remotes: { + containerA: "../../0-container-full/module/container.mjs", + containerB: "./container.mjs" + }, + ...commonMF + }) + ], + target: "node14" + } +];