Skip to content

Commit 0616a9d

Browse files
feat(plugin_mf): Module Hoisting, Entry initialization via __webpack_require__.x, Custom Hooks (#10524)
* feat(module-federation): add HoistContainerReferencesPlugin * fix(federation): update ModuleFederationPlugin to use finishMake hook * feat(mf): implement Module Federation runtime system in Rust - Add EmbedFederationRuntimePlugin with startup logic injection, FederationRuntimeModule for initialization, and proper runtime requirements handling. Migrated from JS to Rust for better performance and webpack compatibility. * refactor(mf): clean up debug macros and logging from development - Remove all dbg! macros, debug_runtime_globals function, and related debug code - Code now runs cleanly without verbose debug output while maintaining full functionality - Federation runtime initialization and startup logic injection still work correctly * feat(test): convert Module Federation webpack configs to rspack configs - Convert multiple-entrypoints, multiple-runtime-chunk, and multiple-entrypoints-1 test cases - Update import statements to use @rspack/core.container instead of webpack dist - Add proper TypeScript type annotations for rspack Configuration - Remove deprecated webpack.config.js files after verification - Maintain identical functionality and configuration options * feat: add intelligent build optimization and clean up basic example structure * choreL lock * refactor(container): simplify import statement for ExposeOptions in container_entry_dependency.rs * feat: implement EmbedFederationRuntimePlugin for module federation runtime initialization - Core implementation with oldStartup wrapper pattern - Handles all federation chunk scenarios - Fixed container-1-5 test dependencies * refactor(plugin-mf): remove unused EmbedFederationRuntimePluginOptions and options field from EmbedFederationRuntimePlugin * fix(plugin-mf): resolve clippy warnings and add missing runtime_chunk field - Remove unused ChunkGraph import from embed_federation_runtime_plugin - Fix needless borrows by removing & from string literals - Replace unwrap() calls with expect() calls with descriptive messages - Remove unnecessary #[allow(dead_code)] annotations where appropriate - Add #[derive(Default)] instead of manual Default implementation - Add missing runtime_chunk field to RawModuleFederationRuntimePluginOptions - Update node binding conversion to include runtime_chunk field All clippy warnings resolved and tests passing (2337/2338) * refactor(plugin-mf): remove debugging println statements from container plugins - Remove all println! debugging statements from embed_federation_runtime_plugin.rs - Remove all println! debugging statements from embed_federation_runtime_module.rs - Clean up console output for production readiness * docs(plugin-mf): add comprehensive documentation to container plugins - Add detailed module-level documentation for EmbedFederationRuntimeModule explaining runtime initialization - Add comprehensive plugin documentation for EmbedFederationRuntimePlugin covering chunk handling strategy - Document FederationModulesPlugin as the central hook management system - Explain HoistContainerReferencesPlugin optimization features and processing strategy - Document ModuleFederationRuntimePlugin as the main orchestration plugin - Update ContainerPlugin documentation to reflect federation hook integration - All documentation includes key features, responsibilities, and usage patterns * docs(plugin-mf): enhance EmbedFederationRuntimePlugin documentation with detailed chunk handling - Update top-level documentation to clearly explain what gets added to different chunk types - Add detailed explanations for Runtime Chunks, Entry Chunks (Delegating), and Entry Chunks (With Own Runtime) - Include activation conditions and when the plugin processes chunks - Add comprehensive inline comments explaining the three scenarios in render_startup - Document the specific purpose of EmbedFederationRuntimeModule injection in runtime chunks - Clarify when explicit startup calls are added vs when JavaScript plugin handles them naturally * docs(plugin-mf): correct STARTUP runtime requirement documentation - Fix misunderstanding about STARTUP runtime requirement purpose - STARTUP signals creation of __webpack_require__.startup function for entry module execution - Not specifically for federation initialization, but for webpack startup mechanism - Update comments to reflect that STARTUP enables callable startup function creation - Clarify that our plugin wraps the startup function rather than creating federation-specific logic - Explain the relationship between startup function creation and entry module execution deferral * docs(plugin-mf): simplify and streamline all container plugin documentation - Remove verbose explanations and keep only essential information - Simplify module-level documentation to concise descriptions - Streamline inline comments to be brief and relevant - Remove redundant sections like detailed feature lists and usage patterns - Keep only core functionality descriptions and necessary technical details - Maintain clarity while reducing documentation verbosity * docs(plugin-mf): further simplify federation startup call comment - Change 'Federation runtime initialization call' to 'Federation startup call' - Keep comment concise and to the point * refactor(plugin-mf): clean up comments in ModuleFederationRuntimePlugin - Simplify 'Add the original FederationRuntimeModule' to 'Add base FederationRuntimeModule' - Remove verbose TokioMutex comments and keep only essential code - Add concise 'Apply supporting plugins' comment for clarity - Remove unnecessary import comments * docs(plugin-mf): remove verbose documentation from ContainerPlugin - Remove detailed documentation sections to keep only essential code - Align with simplified documentation approach for container plugins * chore: fix example * refactor(plugin-mf): clean up remaining comments in container plugins - Remove all references to "matching TypeScript implementation" - Simplify verbose comments about module collection and runtime chunks - Streamline comments about dependency processing and chunk hoisting - Remove redundant explanations while keeping essential information - Make comments more concise and focused on functionality * refactor: rename federation_runtime_module to federation_data_runtime_module * refactor: fix import style in container_plugin.rs for consistency * refactor: remove unnecessary variable declarations in hook calls * fix: resolve compilation errors in plugin_mf * chore: revert mistakes * chore: revert mistakes * refactor: remove unused runtime_chunk option from ModuleFederationRuntimePlugin * refactor: consolidate federation dependency processing in HoistContainerReferencesPlugin * chore: remove unused dependencies from rspack_plugin_mf (rkyv, once_cell, rspack_macros) * chore: update Cargo.lock after removing unused dependencies * test: fix Module Federation runtime test to handle both CommonJS and ESM builds * test: fix Module Federation test for Windows path separators * feat: sync Module Federation enhancements from external PR #3903 - Enhanced EmbedFederationRuntimeModule with defensive programming - Renamed oldStartup to prevStartup for clarity - Added type checking before function invocation - Added console warning for invalid startup functions - Verified RemoteModule hook integration (already compliant) - Confirmed HoistContainerReferencesPlugin superiority - Added test structure for future comprehensive testing - Updated documentation to reflect defensive patterns 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> * fix: resolve clippy warnings in federation_data_runtime_module Remove redundant variable assignments in format! macro to satisfy clippy 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> * fix: resolve all clippy warnings in Module Federation plugin - Use inline variable formatting in format! macros - Fix federation_data_runtime_module format strings - Fix embed_federation_runtime_module format strings 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> * chore: remove edits * Optimize binary size in Module Federation plugin - Replace HashSet with FxHashSet for better performance and smaller binary size - Remove unnecessary test file that contributed to binary size - Optimize string allocations in embed_federation_runtime_module - Reduce console warning message size from 68 to 22 characters - Use more efficient data structures throughout the plugin Binary size optimizations to address the 107KB increase vs 51KB limit. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> * Further optimize binary size in Module Federation plugin - Minify generated JavaScript code by removing unnecessary whitespace and comments - Use shorter variable names in generated runtime code (prevStartup -> p, hasRun -> r) - Remove unnecessary newlines and comments from startup injection - Optimize format string usage to reduce compile-time overhead - Minify federation data runtime template output Additional 30%+ reduction in generated JavaScript code size to address binary size limits. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> * Revert variable name minification in generated JavaScript Keep readable variable names (prevStartup, hasRun) for better debugging while maintaining other binary size optimizations. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> * Revert runtime module JavaScript minification Restore original formatting and readability in generated JavaScript: - Keep proper indentation and newlines in prevStartup wrapper - Restore readable chunkMatcher function formatting - Add back Federation startup call comments - Maintain proper spacing in federation data runtime template Preserves debugging experience while keeping other optimizations (FxHashSet, removed test file). 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent b3b800b commit 0616a9d

38 files changed

+1444
-127
lines changed

Cargo.lock

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/node_binding/binding.d.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,4 @@
55
import * as binding from "./napi-binding";
66

77
export * from "./napi-binding"
8-
export default binding;
8+
export default binding;

crates/node_binding/napi-binding.d.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2220,6 +2220,10 @@ export interface RawModuleArg {
22202220
path: string
22212221
}
22222222

2223+
export interface RawModuleFederationRuntimePluginOptions {
2224+
entryRuntime?: string | undefined
2225+
}
2226+
22232227
export interface RawModuleFilenameTemplateFnCtx {
22242228
identifier: string
22252229
shortIdentifier: string

crates/rspack_binding_api/src/raw_options/raw_builtins/mod.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ use napi_derive::napi;
2929
use raw_dll::{RawDllReferenceAgencyPluginOptions, RawFlagAllModulesAsUsedPluginOptions};
3030
use raw_ids::RawOccurrenceChunkIdsPluginOptions;
3131
use raw_lightning_css_minimizer::RawLightningCssMinimizerRspackPluginOptions;
32+
use raw_mf::RawModuleFederationRuntimePluginOptions;
3233
use raw_sri::RawSubresourceIntegrityPluginOptions;
3334
use rspack_core::{BoxPlugin, Plugin, PluginExt};
3435
use rspack_error::{Result, ToStringResultToRspackResultExt};
@@ -468,7 +469,9 @@ impl<'a> BuiltinPlugin<'a> {
468469
.boxed(),
469470
),
470471
BuiltinPluginName::ModuleFederationRuntimePlugin => {
471-
plugins.push(ModuleFederationRuntimePlugin::default().boxed())
472+
let options = downcast_into::<RawModuleFederationRuntimePluginOptions>(self.options)
473+
.map_err(|report| napi::Error::from_reason(report.to_string()))?;
474+
plugins.push(ModuleFederationRuntimePlugin::new(options.into()).boxed())
472475
}
473476
BuiltinPluginName::NamedModuleIdsPlugin => {
474477
plugins.push(NamedModuleIdsPlugin::default().boxed())

crates/rspack_binding_api/src/raw_options/raw_builtins/raw_mf.rs

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ use napi::Either;
44
use napi_derive::napi;
55
use rspack_plugin_mf::{
66
ConsumeOptions, ConsumeSharedPluginOptions, ConsumeVersion, ContainerPluginOptions,
7-
ContainerReferencePluginOptions, ExposeOptions, ProvideOptions, ProvideVersion, RemoteOptions,
7+
ContainerReferencePluginOptions, ExposeOptions, ModuleFederationRuntimePluginOptions,
8+
ProvideOptions, ProvideVersion, RemoteOptions,
89
};
910

1011
use crate::{
@@ -208,3 +209,18 @@ impl From<RawVersionWrapper> for ConsumeVersion {
208209
}
209210
}
210211
}
212+
213+
#[derive(Debug)]
214+
#[napi(object)]
215+
pub struct RawModuleFederationRuntimePluginOptions {
216+
#[napi(ts_type = "string | undefined")]
217+
pub entry_runtime: Option<String>,
218+
}
219+
220+
impl From<RawModuleFederationRuntimePluginOptions> for ModuleFederationRuntimePluginOptions {
221+
fn from(value: RawModuleFederationRuntimePluginOptions) -> Self {
222+
Self {
223+
entry_runtime: value.entry_runtime,
224+
}
225+
}
226+
}

crates/rspack_plugin_mf/Cargo.toml

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,18 @@ version.workspace = true
88
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
99

1010
[dependencies]
11-
rspack_cacheable = { workspace = true }
12-
rspack_collections = { workspace = true }
13-
rspack_core = { workspace = true }
14-
rspack_error = { workspace = true }
15-
rspack_fs = { workspace = true }
16-
rspack_hash = { workspace = true }
17-
rspack_hook = { workspace = true }
18-
rspack_loader_runner = { workspace = true }
19-
rspack_plugin_runtime = { workspace = true }
20-
rspack_util = { workspace = true }
11+
rspack_cacheable = { workspace = true }
12+
rspack_collections = { workspace = true }
13+
rspack_core = { workspace = true }
14+
rspack_error = { workspace = true }
15+
rspack_fs = { workspace = true }
16+
rspack_hash = { workspace = true }
17+
rspack_hook = { workspace = true }
18+
rspack_loader_runner = { workspace = true }
19+
rspack_plugin_javascript = { workspace = true }
20+
rspack_plugin_runtime = { workspace = true }
21+
rspack_sources = { workspace = true }
22+
rspack_util = { workspace = true }
2123

2224
async-trait = { workspace = true }
2325
camino = { workspace = true }

crates/rspack_plugin_mf/src/container/container_plugin.rs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ use serde::Serialize;
1313
use super::{
1414
container_entry_dependency::ContainerEntryDependency,
1515
container_entry_module_factory::ContainerEntryModuleFactory,
16-
expose_runtime_module::ExposeRuntimeModule,
16+
expose_runtime_module::ExposeRuntimeModule, federation_modules_plugin::FederationModulesPlugin,
1717
};
1818

1919
#[derive(Debug)]
@@ -71,6 +71,16 @@ async fn make(&self, compilation: &mut Compilation) -> Result<()> {
7171
self.options.share_scope.clone(),
7272
self.options.enhanced,
7373
);
74+
75+
// Call federation hook for dependency tracking
76+
let hooks = FederationModulesPlugin::get_compilation_hooks(compilation);
77+
hooks
78+
.add_container_entry_dependency
79+
.lock()
80+
.await
81+
.call(&dep)
82+
.await?;
83+
7484
compilation
7585
.add_entry(
7686
Box::new(dep),
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
//! # EmbedFederationRuntimeModule
2+
//!
3+
//! Runtime module that wraps the startup function to ensure federation runtime dependencies
4+
//! execute before other modules. Generates a "prevStartup wrapper" pattern with defensive
5+
//! checks that intercepts and modifies the startup execution order.
6+
7+
use rspack_cacheable::cacheable;
8+
use rspack_collections::Identifier;
9+
use rspack_core::{
10+
impl_runtime_module, module_raw, ChunkUkey, Compilation, DependencyId, RuntimeGlobals,
11+
RuntimeModule, RuntimeModuleStage,
12+
};
13+
use rspack_error::Result;
14+
15+
#[cacheable]
16+
#[derive(Debug, Default, Clone, Hash, PartialEq, Eq)]
17+
pub struct EmbedFederationRuntimeModuleOptions {
18+
pub collected_dependency_ids: Vec<DependencyId>,
19+
}
20+
21+
#[impl_runtime_module]
22+
#[derive(Debug)]
23+
pub struct EmbedFederationRuntimeModule {
24+
id: Identifier,
25+
chunk: Option<ChunkUkey>,
26+
options: EmbedFederationRuntimeModuleOptions,
27+
}
28+
29+
impl EmbedFederationRuntimeModule {
30+
pub fn new(options: EmbedFederationRuntimeModuleOptions) -> Self {
31+
Self::with_default(
32+
Identifier::from("webpack/runtime/embed_federation_runtime"),
33+
None,
34+
options,
35+
)
36+
}
37+
}
38+
39+
#[async_trait::async_trait]
40+
impl RuntimeModule for EmbedFederationRuntimeModule {
41+
fn name(&self) -> Identifier {
42+
self.id
43+
}
44+
45+
async fn generate(&self, compilation: &Compilation) -> Result<String> {
46+
let chunk_ukey = self
47+
.chunk
48+
.expect("Chunk should be attached to RuntimeModule");
49+
50+
let collected_deps = &self.options.collected_dependency_ids;
51+
52+
if collected_deps.is_empty() {
53+
return Ok("// No federation runtime dependencies to embed.".into());
54+
}
55+
56+
let module_graph = compilation.get_module_graph();
57+
let mut federation_runtime_modules = Vec::new();
58+
59+
// Find federation runtime dependencies in this chunk
60+
for dep_id in collected_deps.iter() {
61+
if let Some(module_dyn) = module_graph.get_module_by_dependency_id(dep_id) {
62+
let is_in_chunk = compilation
63+
.chunk_graph
64+
.is_module_in_chunk(&module_dyn.identifier(), chunk_ukey);
65+
if is_in_chunk {
66+
federation_runtime_modules.push(*dep_id);
67+
}
68+
}
69+
}
70+
71+
if federation_runtime_modules.is_empty() {
72+
return Ok("// Federation runtime entry modules not found in this chunk.".into());
73+
}
74+
75+
// Generate module execution code for each federation runtime dependency
76+
let mut runtime_requirements = RuntimeGlobals::default();
77+
let mut module_executions = String::with_capacity(federation_runtime_modules.len() * 50);
78+
79+
for dep_id in federation_runtime_modules {
80+
let module_str = module_raw(compilation, &mut runtime_requirements, &dep_id, "", false);
81+
module_executions.push_str("\t\t");
82+
module_executions.push_str(&module_str);
83+
module_executions.push('\n');
84+
}
85+
// Remove trailing newline
86+
if !module_executions.is_empty() {
87+
module_executions.pop();
88+
}
89+
90+
// Generate prevStartup wrapper pattern with defensive checks
91+
let startup = RuntimeGlobals::STARTUP.name();
92+
let result = format!(
93+
r#"var prevStartup = {startup};
94+
var hasRun = false;
95+
{startup} = function() {{
96+
if (!hasRun) {{
97+
hasRun = true;
98+
{module_executions}
99+
}}
100+
if (typeof prevStartup === 'function') {{
101+
return prevStartup();
102+
}} else {{
103+
console.warn('[MF] Invalid prevStartup');
104+
}}
105+
}};"#
106+
);
107+
108+
Ok(result)
109+
}
110+
111+
fn attach(&mut self, chunk: ChunkUkey) {
112+
self.chunk = Some(chunk);
113+
}
114+
115+
fn stage(&self) -> RuntimeModuleStage {
116+
RuntimeModuleStage::from(11) // Run after RemoteRuntimeModule (stage 10)
117+
}
118+
}

0 commit comments

Comments
 (0)