Skip to content

feat(plugin_mf): Module Hoisting, Entry initialization via __webpack_require__.x, Custom Hooks #10524

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 50 commits into from
Jul 18, 2025
Merged
Show file tree
Hide file tree
Changes from 37 commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
5a73444
feat(module-federation): add HoistContainerReferencesPlugin
ScriptedAlchemy May 13, 2025
10881bb
fix(federation): update ModuleFederationPlugin to use finishMake hook
ScriptedAlchemy May 13, 2025
2d019a7
feat(mf): implement Module Federation runtime system in Rust - Add Em…
ScriptedAlchemy May 23, 2025
d83d6a5
refactor(mf): clean up debug macros and logging from development - Re…
ScriptedAlchemy May 23, 2025
70ef9df
feat(test): convert Module Federation webpack configs to rspack confi…
ScriptedAlchemy May 23, 2025
0b308a1
feat: add intelligent build optimization and clean up basic example s…
ScriptedAlchemy May 24, 2025
effb92b
choreL lock
ScriptedAlchemy May 24, 2025
4cc9a2d
refactor(container): simplify import statement for ExposeOptions in c…
ScriptedAlchemy May 24, 2025
6029eb4
feat: implement EmbedFederationRuntimePlugin for module federation ru…
ScriptedAlchemy May 29, 2025
d0f2d83
refactor(plugin-mf): remove unused EmbedFederationRuntimePluginOption…
ScriptedAlchemy May 30, 2025
e1c3814
fix(plugin-mf): resolve clippy warnings and add missing runtime_chunk…
ScriptedAlchemy May 30, 2025
f21a105
refactor(plugin-mf): remove debugging println statements from contain…
ScriptedAlchemy May 30, 2025
d80fb1c
docs(plugin-mf): add comprehensive documentation to container plugins…
ScriptedAlchemy May 30, 2025
95e5425
docs(plugin-mf): enhance EmbedFederationRuntimePlugin documentation w…
ScriptedAlchemy May 30, 2025
271795a
docs(plugin-mf): correct STARTUP runtime requirement documentation - …
ScriptedAlchemy May 30, 2025
a81b193
docs(plugin-mf): simplify and streamline all container plugin documen…
ScriptedAlchemy May 30, 2025
0bd4ad5
docs(plugin-mf): further simplify federation startup call comment - C…
ScriptedAlchemy May 30, 2025
7bbdaf5
refactor(plugin-mf): clean up comments in ModuleFederationRuntimePlug…
ScriptedAlchemy May 30, 2025
6652291
docs(plugin-mf): remove verbose documentation from ContainerPlugin - …
ScriptedAlchemy May 30, 2025
4b4b57c
chore: fix example
ScriptedAlchemy May 30, 2025
cc40e47
refactor(plugin-mf): clean up remaining comments in container plugins…
ScriptedAlchemy May 30, 2025
4328cfe
refactor: rename federation_runtime_module to federation_data_runtime…
ScriptedAlchemy May 30, 2025
ef69998
refactor: fix import style in container_plugin.rs for consistency
ScriptedAlchemy May 30, 2025
3dc7649
refactor: remove unnecessary variable declarations in hook calls
ScriptedAlchemy May 30, 2025
b8b8850
Merge branch 'main' into hoist-container-references
ScriptedAlchemy May 30, 2025
637872c
fix: resolve compilation errors in plugin_mf
ScriptedAlchemy May 30, 2025
410040a
Merge branch 'main' into hoist-container-references
ScriptedAlchemy May 30, 2025
91d3abf
chore: revert mistakes
ScriptedAlchemy May 30, 2025
74508ad
chore: revert mistakes
ScriptedAlchemy May 30, 2025
0ba4e5a
refactor: remove unused runtime_chunk option from ModuleFederationRun…
ScriptedAlchemy May 30, 2025
28db4f8
refactor: consolidate federation dependency processing in HoistContai…
ScriptedAlchemy May 30, 2025
fae970d
chore: remove unused dependencies from rspack_plugin_mf (rkyv, once_c…
ScriptedAlchemy May 30, 2025
69ed29e
chore: update Cargo.lock after removing unused dependencies
ScriptedAlchemy May 30, 2025
9617f90
test: fix Module Federation runtime test to handle both CommonJS and …
ScriptedAlchemy May 30, 2025
e1ff251
Merge branch 'main' into hoist-container-references
ScriptedAlchemy May 30, 2025
4bf769a
test: fix Module Federation test for Windows path separators
ScriptedAlchemy May 30, 2025
f50ac94
Merge branch 'main' into hoist-container-references
ScriptedAlchemy May 30, 2025
6438807
Resolve merge conflicts in Module Federation plugin
ScriptedAlchemy Jul 17, 2025
0372784
feat: sync Module Federation enhancements from external PR #3903
ScriptedAlchemy Jul 17, 2025
05c2bda
Merge branch 'main' into hoist-container-references
ScriptedAlchemy Jul 18, 2025
c0e3751
fix: resolve clippy warnings in federation_data_runtime_module
ScriptedAlchemy Jul 18, 2025
7038f14
fix: resolve all clippy warnings in Module Federation plugin
ScriptedAlchemy Jul 18, 2025
9db7558
Merge branch 'main' into hoist-container-references
ScriptedAlchemy Jul 18, 2025
402da01
chore: remove edits
ScriptedAlchemy Jul 18, 2025
07f4858
Optimize binary size in Module Federation plugin
ScriptedAlchemy Jul 18, 2025
566ec2d
Further optimize binary size in Module Federation plugin
ScriptedAlchemy Jul 18, 2025
c5c3e07
Revert variable name minification in generated JavaScript
ScriptedAlchemy Jul 18, 2025
30b4621
Revert runtime module JavaScript minification
ScriptedAlchemy Jul 18, 2025
9285899
Resolve merge conflict: use main branch version of binding.d.ts
ScriptedAlchemy Jul 18, 2025
acfeaed
Merge branch 'main' into hoist-container-references
ScriptedAlchemy Jul 18, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions crates/node_binding/binding.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2162,6 +2162,10 @@ export interface RawModuleArg {
path: string
}

export interface RawModuleFederationRuntimePluginOptions {
entryRuntime?: string | undefined
}

export interface RawModuleFilenameTemplateFnCtx {
identifier: string
shortIdentifier: string
Expand Down
5 changes: 4 additions & 1 deletion crates/node_binding/src/raw_options/raw_builtins/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ use napi_derive::napi;
use raw_dll::{RawDllReferenceAgencyPluginOptions, RawFlagAllModulesAsUsedPluginOptions};
use raw_ids::RawOccurrenceChunkIdsPluginOptions;
use raw_lightning_css_minimizer::RawLightningCssMinimizerRspackPluginOptions;
use raw_mf::RawModuleFederationRuntimePluginOptions;
use raw_sri::RawSubresourceIntegrityPluginOptions;
use rspack_core::{BoxPlugin, Plugin, PluginExt};
use rspack_error::{Result, ToStringResultToRspackResultExt};
Expand Down Expand Up @@ -426,7 +427,9 @@ impl<'a> BuiltinPlugin<'a> {
.boxed(),
),
BuiltinPluginName::ModuleFederationRuntimePlugin => {
plugins.push(ModuleFederationRuntimePlugin::default().boxed())
let options = downcast_into::<RawModuleFederationRuntimePluginOptions>(self.options)
.map_err(|report| napi::Error::from_reason(report.to_string()))?;
plugins.push(ModuleFederationRuntimePlugin::new(options.into()).boxed())
}
BuiltinPluginName::NamedModuleIdsPlugin => {
plugins.push(NamedModuleIdsPlugin::default().boxed())
Expand Down
18 changes: 17 additions & 1 deletion crates/node_binding/src/raw_options/raw_builtins/raw_mf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ use napi::Either;
use napi_derive::napi;
use rspack_plugin_mf::{
ConsumeOptions, ConsumeSharedPluginOptions, ConsumeVersion, ContainerPluginOptions,
ContainerReferencePluginOptions, ExposeOptions, ProvideOptions, ProvideVersion, RemoteOptions,
ContainerReferencePluginOptions, ExposeOptions, ModuleFederationRuntimePluginOptions,
ProvideOptions, ProvideVersion, RemoteOptions,
};

use crate::{
Expand Down Expand Up @@ -208,3 +209,18 @@ impl From<RawVersionWrapper> for ConsumeVersion {
}
}
}

#[derive(Debug)]
#[napi(object)]
pub struct RawModuleFederationRuntimePluginOptions {
#[napi(ts_type = "string | undefined")]
pub entry_runtime: Option<String>,
}

impl From<RawModuleFederationRuntimePluginOptions> for ModuleFederationRuntimePluginOptions {
fn from(value: RawModuleFederationRuntimePluginOptions) -> Self {
Self {
entry_runtime: value.entry_runtime,
}
}
}
20 changes: 11 additions & 9 deletions crates/rspack_plugin_mf/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,17 @@ version = "0.2.0"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
rspack_cacheable = { workspace = true }
rspack_collections = { workspace = true }
rspack_core = { workspace = true }
rspack_error = { workspace = true }
rspack_hash = { workspace = true }
rspack_hook = { workspace = true }
rspack_loader_runner = { workspace = true }
rspack_plugin_runtime = { workspace = true }
rspack_util = { workspace = true }
rspack_cacheable = { workspace = true }
rspack_collections = { workspace = true }
rspack_core = { workspace = true }
rspack_error = { workspace = true }
rspack_hash = { workspace = true }
rspack_hook = { workspace = true }
rspack_loader_runner = { workspace = true }
rspack_plugin_javascript = { workspace = true }
rspack_plugin_runtime = { workspace = true }
rspack_sources = { workspace = true }
rspack_util = { workspace = true }

async-trait = { workspace = true }
hashlink = { workspace = true }
Expand Down
12 changes: 11 additions & 1 deletion crates/rspack_plugin_mf/src/container/container_plugin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ use serde::Serialize;
use super::{
container_entry_dependency::ContainerEntryDependency,
container_entry_module_factory::ContainerEntryModuleFactory,
expose_runtime_module::ExposeRuntimeModule,
expose_runtime_module::ExposeRuntimeModule, federation_modules_plugin::FederationModulesPlugin,
};

#[derive(Debug)]
Expand Down Expand Up @@ -71,6 +71,16 @@ async fn make(&self, compilation: &mut Compilation) -> Result<()> {
self.options.share_scope.clone(),
self.options.enhanced,
);

// Call federation hook for dependency tracking
let hooks = FederationModulesPlugin::get_compilation_hooks(compilation);
hooks
.add_container_entry_dependency
.lock()
.await
.call(&dep)
.await?;

compilation
.add_entry(
Box::new(dep),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
//! # EmbedFederationRuntimeModule
//!
//! Runtime module that wraps the startup function to ensure federation runtime dependencies
//! execute before other modules. Generates an "oldStartup wrapper" pattern that intercepts
//! and modifies the startup execution order.

use rspack_cacheable::cacheable;
use rspack_collections::Identifier;
use rspack_core::{
impl_runtime_module, module_raw, ChunkUkey, Compilation, DependencyId, RuntimeGlobals,
RuntimeModule, RuntimeModuleStage,
};
use rspack_error::Result;

#[cacheable]
#[derive(Debug, Default, Clone, Hash, PartialEq, Eq)]
pub struct EmbedFederationRuntimeModuleOptions {
pub collected_dependency_ids: Vec<DependencyId>,
}

#[impl_runtime_module]
#[derive(Debug)]
pub struct EmbedFederationRuntimeModule {
id: Identifier,
chunk: Option<ChunkUkey>,
options: EmbedFederationRuntimeModuleOptions,
}

impl EmbedFederationRuntimeModule {
pub fn new(options: EmbedFederationRuntimeModuleOptions) -> Self {
Self::with_default(
Identifier::from("webpack/runtime/embed_federation_runtime"),
None,
options,
)
}
}

#[async_trait::async_trait]
impl RuntimeModule for EmbedFederationRuntimeModule {
fn name(&self) -> Identifier {
self.id
}

async fn generate(&self, compilation: &Compilation) -> Result<String> {
let chunk_ukey = self
.chunk
.expect("Chunk should be attached to RuntimeModule");

let collected_deps = &self.options.collected_dependency_ids;

if collected_deps.is_empty() {
return Ok("// No federation runtime dependencies to embed.".to_string());
}

let module_graph = compilation.get_module_graph();
let mut federation_runtime_modules = Vec::new();

// Find federation runtime dependencies in this chunk
for dep_id in collected_deps.iter() {
if let Some(module_dyn) = module_graph.get_module_by_dependency_id(dep_id) {
let is_in_chunk = compilation
.chunk_graph
.is_module_in_chunk(&module_dyn.identifier(), chunk_ukey);
if is_in_chunk {
federation_runtime_modules.push(*dep_id);
}
}
}

if federation_runtime_modules.is_empty() {
return Ok("// Federation runtime entry modules not found in this chunk.".to_string());
}

// Generate module execution code for each federation runtime dependency
let mut runtime_requirements = RuntimeGlobals::default();
let mut module_executions = Vec::new();

for dep_id in federation_runtime_modules {
let module_str = module_raw(compilation, &mut runtime_requirements, &dep_id, "", false);
module_executions.push(format!("\t\t{}", module_str));
}

// Generate oldStartup wrapper pattern
let result = format!(
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

patch webpack_require.x which is entry startup function.

first initialize federation, then call previous startup if exists - if it exists, it will be webpack require of entrypoint module.

If startup doesnt exist in entrypoint bootstrap, like in single runtime chunk, another plugin will manually add require.x() to the entrypoint chunks at the begenning of startup

r#"var oldStartup = {startup};
var hasRun = false;
{startup} = function() {{
if (!hasRun) {{
hasRun = true;
{module_executions}
}}
return oldStartup();
}};"#,
startup = RuntimeGlobals::STARTUP.name(),
module_executions = module_executions.join("\n")
);

Ok(result)
}

fn attach(&mut self, chunk: ChunkUkey) {
self.chunk = Some(chunk);
}

fn stage(&self) -> RuntimeModuleStage {
RuntimeModuleStage::from(11) // Run after RemoteRuntimeModule (stage 10)
}
}
Loading
Loading