use std::sync::{Arc, Mutex};
use rspack_core::{
ChunkUkey, Compilation, CompilationAdditionalChunkRuntimeRequirements, CompilationParams,
CompilationRuntimeRequirementInTree, CompilerCompilation, DependencyId, ModuleIdentifier, Plugin,
RuntimeCodeTemplate, RuntimeGlobals, RuntimeModule,
rspack_sources::{ConcatSource, RawStringSource, SourceExt},
};
use rspack_error::Result;
use rspack_hook::{plugin, plugin_hook};
use rspack_plugin_javascript::{JavascriptModulesRenderStartup, JsPlugin, RenderSource};
use rustc_hash::FxHashSet;
use super::{
embed_federation_runtime_module::{
EmbedFederationRuntimeModule, EmbedFederationRuntimeModuleOptions,
},
federation_modules_plugin::{AddFederationRuntimeDependencyHook, FederationModulesPlugin},
federation_runtime_dependency::FederationRuntimeDependency,
module_federation_runtime_plugin::ModuleFederationRuntimeExperimentsOptions,
};
struct FederationRuntimeDependencyCollector {
collected_dependency_ids: Arc<Mutex<FxHashSet<DependencyId>>>,
}
#[async_trait::async_trait]
impl AddFederationRuntimeDependencyHook for FederationRuntimeDependencyCollector {
async fn run(&self, dependency: &FederationRuntimeDependency) -> Result<()> {
self
.collected_dependency_ids
.lock()
.expect("Failed to lock collected_dependency_ids")
.insert(dependency.id);
Ok(())
}
}
#[plugin]
#[derive(Debug)]
pub struct EmbedFederationRuntimePlugin {
experiments: ModuleFederationRuntimeExperimentsOptions,
collected_dependency_ids: Arc<Mutex<FxHashSet<DependencyId>>>,
}
impl EmbedFederationRuntimePlugin {
pub fn new(experiments: ModuleFederationRuntimeExperimentsOptions) -> Self {
Self::new_inner(experiments, Arc::new(Mutex::new(FxHashSet::default())))
}
}
#[plugin_hook(CompilationAdditionalChunkRuntimeRequirements for EmbedFederationRuntimePlugin)]
async fn additional_chunk_runtime_requirements_tree(
&self,
compilation: &Compilation,
chunk_ukey: &ChunkUkey,
runtime_requirements: &mut RuntimeGlobals,
_runtime_modules: &mut Vec<Box<dyn RuntimeModule>>,
) -> Result<()> {
let chunk = compilation
.build_chunk_graph_artifact
.chunk_by_ukey
.expect_get(chunk_ukey);
if chunk.name() == Some("build time chunk") {
return Ok(());
}
let has_runtime = chunk.has_runtime(&compilation.build_chunk_graph_artifact.chunk_group_by_ukey);
let has_entry_modules = compilation
.build_chunk_graph_artifact
.chunk_graph
.get_number_of_entry_modules(chunk_ukey)
> 0;
let is_enabled = has_runtime || has_entry_modules;
if is_enabled {
runtime_requirements.insert(RuntimeGlobals::STARTUP);
if self.experiments.async_startup {
runtime_requirements.insert(RuntimeGlobals::STARTUP_ENTRYPOINT);
runtime_requirements.insert(RuntimeGlobals::ENSURE_CHUNK_HANDLERS);
}
}
Ok(())
}
#[plugin_hook(CompilationRuntimeRequirementInTree for EmbedFederationRuntimePlugin)]
async fn runtime_requirement_in_tree(
&self,
compilation: &Compilation,
chunk_ukey: &ChunkUkey,
_all_runtime_requirements: &RuntimeGlobals,
_runtime_requirements: &RuntimeGlobals,
_runtime_requirements_mut: &mut RuntimeGlobals,
runtime_modules_to_add: &mut Vec<(ChunkUkey, Box<dyn RuntimeModule>)>,
) -> Result<Option<()>> {
let chunk = compilation
.build_chunk_graph_artifact
.chunk_by_ukey
.expect_get(chunk_ukey);
if chunk.name() == Some("build time chunk") {
return Ok(None);
}
let has_runtime = chunk.has_runtime(&compilation.build_chunk_graph_artifact.chunk_group_by_ukey);
if has_runtime {
let collected_ids_snapshot = self
.collected_dependency_ids
.lock()
.expect("Failed to lock collected_dependency_ids")
.iter()
.copied()
.collect::<Vec<DependencyId>>();
let emro = EmbedFederationRuntimeModuleOptions {
collected_dependency_ids: collected_ids_snapshot,
experiments: self.experiments.clone(),
};
runtime_modules_to_add.push((
*chunk_ukey,
Box::new(EmbedFederationRuntimeModule::new(
&compilation.runtime_template,
emro,
)),
));
}
Ok(None)
}
#[plugin_hook(CompilerCompilation for EmbedFederationRuntimePlugin)]
async fn compilation(
&self,
compilation: &mut Compilation,
_params: &mut CompilationParams,
) -> Result<()> {
let collector = FederationRuntimeDependencyCollector {
collected_dependency_ids: Arc::clone(&self.collected_dependency_ids),
};
let federation_hooks = FederationModulesPlugin::get_compilation_hooks(compilation);
federation_hooks
.add_federation_runtime_dependency
.lock()
.await
.tap(collector);
let js_hooks = JsPlugin::get_compilation_hooks_mut(compilation.id());
js_hooks
.write()
.await
.render_startup
.tap(render_startup::new(self));
Ok(())
}
#[plugin_hook(JavascriptModulesRenderStartup for EmbedFederationRuntimePlugin)]
async fn render_startup(
&self,
compilation: &Compilation,
chunk_ukey: &ChunkUkey,
_module: &ModuleIdentifier,
render_source: &mut RenderSource,
runtime_template: &RuntimeCodeTemplate<'_>,
) -> Result<()> {
let chunk = compilation
.build_chunk_graph_artifact
.chunk_by_ukey
.expect_get(chunk_ukey);
if chunk.name() == Some("build time chunk") {
return Ok(());
}
let collected_deps = self
.collected_dependency_ids
.lock()
.expect("Failed to lock collected_dependency_ids")
.iter()
.copied()
.collect::<Vec<DependencyId>>();
let has_federation_deps = !collected_deps.is_empty();
if !has_federation_deps {
return Ok(());
}
let has_runtime = chunk.has_runtime(&compilation.build_chunk_graph_artifact.chunk_group_by_ukey);
let has_entry_modules = compilation
.build_chunk_graph_artifact
.chunk_graph
.get_number_of_entry_modules(chunk_ukey)
> 0;
if has_runtime && has_entry_modules {
return Ok(());
}
if !has_runtime && has_entry_modules {
if self.experiments.async_startup {
return Ok(());
}
let mut startup_with_call = ConcatSource::default();
startup_with_call.add(RawStringSource::from_static(
"\n// Federation startup call\n",
));
let startup_global = runtime_template.render_runtime_globals(&RuntimeGlobals::STARTUP);
startup_with_call.add(RawStringSource::from(format!("{startup_global}();\n",)));
startup_with_call.add(render_source.source.clone());
render_source.source = startup_with_call.boxed();
}
Ok(())
}
impl Plugin for EmbedFederationRuntimePlugin {
fn name(&self) -> &'static str {
"EmbedFederationRuntimePlugin"
}
fn apply(&self, ctx: &mut rspack_core::ApplyContext<'_>) -> Result<()> {
ctx.compiler_hooks.compilation.tap(compilation::new(self));
ctx
.compilation_hooks
.additional_chunk_runtime_requirements
.tap(additional_chunk_runtime_requirements_tree::new(self));
ctx
.compilation_hooks
.runtime_requirement_in_tree
.tap(runtime_requirement_in_tree::new(self));
Ok(())
}
}
impl Default for EmbedFederationRuntimePlugin {
fn default() -> Self {
Self::new(ModuleFederationRuntimeExperimentsOptions::default())
}
}