Skip to main content

rspack_plugin_mf/container/
module_federation_runtime_plugin.rs

1//! # ModuleFederationRuntimePlugin
2//!
3//! Main orchestration plugin for Module Federation runtime functionality.
4//! Coordinates federation plugins, manages runtime dependencies, and adds the base FederationRuntimeModule.
5
6use rspack_cacheable::cacheable;
7use rspack_core::{
8  AsyncModulesArtifact, BoxDependency, ChunkUkey, Compilation,
9  CompilationAdditionalTreeRuntimeRequirements, CompilationFinishModules, CompilationParams,
10  CompilerCompilation, CompilerFinishMake, DependencyType, EntryOptions, ExportsInfoArtifact,
11  Plugin, RuntimeGlobals, RuntimeModule, SideEffectsStateArtifact,
12};
13use rspack_error::Result;
14use rspack_hook::{plugin, plugin_hook};
15use serde::Deserialize;
16
17use super::{
18  container_entry_module::ContainerEntryModule,
19  embed_federation_runtime_plugin::EmbedFederationRuntimePlugin,
20  federation_data_runtime_module::FederationDataRuntimeModule,
21  federation_modules_plugin::FederationModulesPlugin,
22  federation_runtime_dependency::FederationRuntimeDependency,
23  hoist_container_references_plugin::HoistContainerReferencesPlugin,
24};
25
26#[derive(Debug, Default, Deserialize, Clone)]
27pub struct ModuleFederationRuntimePluginOptions {
28  pub entry_runtime: Option<String>,
29  #[serde(default)]
30  pub experiments: ModuleFederationRuntimeExperimentsOptions,
31}
32
33#[cacheable]
34#[derive(Debug, Default, Deserialize, Clone, Hash, PartialEq, Eq)]
35pub struct ModuleFederationRuntimeExperimentsOptions {
36  #[serde(default)]
37  pub async_startup: bool,
38}
39
40#[plugin]
41#[derive(Debug)]
42pub struct ModuleFederationRuntimePlugin {
43  options: ModuleFederationRuntimePluginOptions,
44}
45
46impl ModuleFederationRuntimePlugin {
47  pub fn new(options: ModuleFederationRuntimePluginOptions) -> Self {
48    Self::new_inner(options)
49  }
50}
51
52#[plugin_hook(CompilerCompilation for ModuleFederationRuntimePlugin)]
53async fn compilation(
54  &self,
55  compilation: &mut Compilation,
56  params: &mut CompilationParams,
57) -> Result<()> {
58  compilation.set_dependency_factory(
59    DependencyType::FederationRuntime,
60    params.normal_module_factory.clone(),
61  );
62  Ok(())
63}
64
65#[plugin_hook(CompilationAdditionalTreeRuntimeRequirements for ModuleFederationRuntimePlugin)]
66async fn additional_tree_runtime_requirements(
67  &self,
68  compilation: &Compilation,
69  _chunk_ukey: &ChunkUkey,
70  _runtime_requirements: &mut RuntimeGlobals,
71  runtime_modules: &mut Vec<Box<dyn RuntimeModule>>,
72) -> Result<()> {
73  // Add base FederationRuntimeModule which is responsible for providing bundler data to the runtime.
74  runtime_modules.push(Box::new(FederationDataRuntimeModule::new(
75    &compilation.runtime_template,
76  )));
77
78  Ok(())
79}
80
81#[plugin_hook(CompilerFinishMake for ModuleFederationRuntimePlugin)]
82async fn finish_make(&self, compilation: &mut Compilation) -> Result<()> {
83  if let Some(entry_request) = self.options.entry_runtime.clone() {
84    let federation_runtime_dep = FederationRuntimeDependency::new(entry_request.clone());
85
86    let hooks = FederationModulesPlugin::get_compilation_hooks(compilation);
87
88    hooks
89      .add_federation_runtime_dependency
90      .lock()
91      .await
92      .call(&federation_runtime_dep)
93      .await?;
94
95    let boxed_dep: BoxDependency = Box::new(federation_runtime_dep);
96    let entry_options = EntryOptions::default();
97    let args = vec![(boxed_dep, entry_options)];
98
99    compilation.add_include(args).await?;
100  }
101
102  Ok(())
103}
104
105// When MF async startup is enabled, STARTUP may resolve asynchronously even if the entry module
106// itself doesn't have top-level await. Mark MF container entry modules as async so downstream
107// renderers (e.g. library wrappers) can safely `await` exports without sprinkling MF-specific
108// RuntimeGlobals checks across generic plugins.
109#[plugin_hook(CompilationFinishModules for ModuleFederationRuntimePlugin, stage = 1000)]
110async fn finish_modules(
111  &self,
112  compilation: &Compilation,
113  async_modules_artifact: &mut AsyncModulesArtifact,
114  _exports_info_artifact: &mut ExportsInfoArtifact,
115  _side_effects_state_artifact: &mut SideEffectsStateArtifact,
116) -> Result<()> {
117  if !self.options.experiments.async_startup {
118    return Ok(());
119  }
120
121  let module_graph = compilation.get_module_graph();
122  for (module_identifier, module) in module_graph.modules() {
123    if module
124      .as_ref()
125      .as_any()
126      .downcast_ref::<ContainerEntryModule>()
127      .is_some()
128    {
129      async_modules_artifact.insert(*module_identifier);
130    }
131  }
132
133  Ok(())
134}
135
136impl Plugin for ModuleFederationRuntimePlugin {
137  fn name(&self) -> &'static str {
138    "rspack.container.ModuleFederationRuntimePlugin"
139  }
140
141  fn apply(&self, ctx: &mut rspack_core::ApplyContext<'_>) -> Result<()> {
142    ctx.compiler_hooks.compilation.tap(compilation::new(self));
143
144    ctx
145      .compilation_hooks
146      .additional_tree_runtime_requirements
147      .tap(additional_tree_runtime_requirements::new(self));
148
149    ctx
150      .compilation_hooks
151      .finish_modules
152      .tap(finish_modules::new(self));
153
154    ctx.compiler_hooks.finish_make.tap(finish_make::new(self));
155
156    // Apply supporting plugins
157    EmbedFederationRuntimePlugin::new(self.options.experiments.clone()).apply(ctx)?;
158    HoistContainerReferencesPlugin::default().apply(ctx)?;
159
160    Ok(())
161  }
162}