Skip to content

Instantly share code, notes, and snippets.

@ScriptedAlchemy
Last active July 21, 2025 20:53
Show Gist options
  • Save ScriptedAlchemy/a71ccbdfb933e8a4cd0131801a2c26b5 to your computer and use it in GitHub Desktop.
Save ScriptedAlchemy/a71ccbdfb933e8a4cd0131801a2c26b5 to your computer and use it in GitHub Desktop.
Rspack Module Federation Hoisted Runtime Documentation

Module Federation now uses Rspack's runtime capabilities instead of patching user entry points, making federation available before entry point evaluation.

Previously, Module Federation was injected into user entry points as the first imported item. This approach caused several issues: federation runtime was duplicated across entry points when using a single runtime chunk, remote modules weren't available at startup unless entry points were loaded on the page, and programmatically created entry points like web workers weren't properly handled.

The new implementation adds a runtime module that initializes federation during Rspack's runtime phase, similar to webpack's v1 Module Federation. Federation runtime is hoisted into runtime chunks regardless of code splitting rules, ensuring it's eagerly available for startup.

This approach:

  • Eliminates "should have __webpack_require__.f.consumes" errors and sharing initialization failures
  • Prevents federation runtime duplication across entry points (~70kb savings per entry)
  • Fixes web worker compatibility issues by ensuring federation is in runtime, not entry points
  • Resolves remote container availability problems that caused startup errors

For applications with multiple entry points using runtimeChunk: 'single', this reduces federation overhead from 210kb to 70kb (67% reduction).

See Module Federation optimization options for further bundle size reductions.

Hoisted Federation Runtime - Internal Technical Documentation

Overview

The hoist-container-references branch implements a new federation runtime system with:

  • 67% bundle size reduction for multi-entry applications (210kb → 70kb)
  • Runtime initialization error fixes via startup wrapper pattern
  • Federation dependency hoisting to runtime chunks with async exclusion
  • Hook-based plugin coordination system

Technical Architecture

graph TB
    subgraph "Master Orchestrator"
        MFRP[ModuleFederationRuntimePlugin Auto-applies other plugins]
    end
    
    subgraph "Federation Plugin System"
        FMP[FederationModulesPlugin Hook Coordinator] 
        EFRP[EmbedFederationRuntimePlugin Startup Manager]
        HCRP[HoistContainerReferencesPlugin Module Optimizer]
    end
    
    subgraph "Runtime Components"
        EFRM[EmbedFederationRuntimeModule Startup Wrapper]
        FRD[FederationRuntimeDependency Dependency Tracker]
    end
    
    subgraph "Compilation Hooks"
        CCE[AddContainerEntryDependencyHook]
        CFR[AddFederationRuntimeDependencyHook]  
        CRD[AddRemoteDependencyHook]
    end
    
    subgraph "Hook Publishers"
        CP[ContainerPlugin during make]
        RM[RemoteModule during build]
    end
    
    MFRP --> EFRP
    MFRP --> HCRP
    MFRP --> CFR
    
    FMP --> CCE
    FMP --> CFR
    FMP --> CRD
    
    HCRP --> CCE
    HCRP --> CFR
    HCRP --> CRD
    
    EFRP --> CFR
    EFRP --> EFRM
    EFRP --> FRD
    
    CP --> CCE
    MFRP --> CFR
    RM --> CRD
    
    EFRM --> RuntimeStartup[__webpack_require__.startup]
Loading

Core Components

1. EmbedFederationRuntimeModule (embed_federation_runtime_module.rs)

New runtime module implementing prevStartup wrapper pattern:

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

impl EmbedFederationRuntimeModule {
  async fn generate(&self, compilation: &Compilation) -> Result<String> {
    // Collect federation runtime dependencies in this chunk
    let federation_runtime_modules = self.collect_runtime_modules(compilation);
    
    // Generate prevStartup wrapper pattern
    let startup = RuntimeGlobals::STARTUP.name();
    format!(r#"
var prevStartup = {startup};
var hasRun = false;
{startup} = function() {{
  if (!hasRun) {{
    hasRun = true;
{module_executions}
  }}
  if (typeof prevStartup === 'function') {{
    return prevStartup();
  }} else {{
    console.warn('[MF] Invalid prevStartup');
  }}
}};
"#)
  }
}

How it works:

  • Stage-11 Execution: Runs after RemoteRuntimeModule (stage 10) to get the order right
  • Dependency Snapshotting: Captures federation dependencies when injecting
  • Chunk-Aware Processing: Only handles dependencies that are actually in the chunk
  • Error Prevention: Checks prevStartup before calling it and logs errors
  • Run-Once Logic: hasRun flag prevents running federation initialization twice

Runtime Flow:

sequenceDiagram
    participant RS as Runtime Startup
    participant ESW as EmbedStartupWrapper
    participant DC as Dependency Check
    participant FD as Federation Dependencies
    participant PS as Previous Startup
    
    RS->>ESW: __webpack_require__.startup()
    ESW->>DC: Check collected_dependency_ids
    DC->>DC: has_federation_deps = !collected_deps.is_empty()
    alt has_federation_deps == false
        DC->>RS: return - Early exit optimization
    else has_federation_deps == true
        ESW->>ESW: Check hasRun flag
        alt hasRun == false
            ESW->>FD: Execute federation modules
            ESW->>ESW: Set hasRun = true
        end
        ESW->>PS: Call prevStartup()
        PS-->>RS: Return result
    end
Loading

2. EmbedFederationRuntimePlugin (embed_federation_runtime_plugin.rs)

Manages runtime module injection and figures out what kind of chunk we're dealing with:

#[plugin]
pub struct EmbedFederationRuntimePlugin {
  collected_dependency_ids: Arc<Mutex<FxHashSet<DependencyId>>>,
}

impl EmbedFederationRuntimePlugin {
  // Chunk Classification Strategy
  async fn additional_chunk_runtime_requirements_tree(&self, chunk_ukey: &ChunkUkey) {
    let has_runtime = chunk.has_runtime(&compilation.chunk_group_by_ukey);
    let has_entry_modules = compilation.chunk_graph.get_number_of_entry_modules(chunk_ukey) > 0;
    
    // Federation support for runtime OR entry chunks
    let is_enabled = has_runtime || has_entry_modules;
    if is_enabled {
      runtime_requirements.insert(RuntimeGlobals::STARTUP);
    }
  }

  // Runtime Module Injection (runtime chunks only)
  async fn runtime_requirement_in_tree(&self, chunk_ukey: &ChunkUkey) {
    if chunk.has_runtime(&compilation.chunk_group_by_ukey) {
      let snapshot = self.collected_dependency_ids.lock().unwrap().clone();
      compilation.add_runtime_module(chunk_ukey, 
        Box::new(EmbedFederationRuntimeModule::new(snapshot)))?;
    }
  }
}

How chunks are handled:

flowchart TD
    C[Chunk Analysis] --> BTC{Is Build Time Chunk?}
    BTC -->|Yes| SKIP[Skip Processing - Build chunks excluded]
    BTC -->|No| RT{Has Runtime?}
    C --> EM{Has Entry Modules?}
    
    RT -->|Yes| RTC[Runtime Chunk]
    RT -->|No| EM
    EM -->|Yes| EC[Entry Chunk] 
    EM -->|No| NC[Normal Chunk]
    
    RTC --> IRM[Inject Runtime Module]
    RTC --> ARS[Add STARTUP Requirement]
    
    EC --> ARS2[Add STARTUP Requirement]
    EC --> ASC[Add Startup Call via render_startup]
    
    NC --> Skip[Skip Processing]
    
    IRM --> ESW[EmbedFederationRuntimeModule with Startup Wrapper]
    ASC --> ESC[Explicit startup call]
Loading

Plugin Integration Points:

  • CompilationAdditionalChunkRuntimeRequirements: Adds STARTUP requirements
  • CompilationRuntimeRequirementInTree: Injects runtime modules with dependency snapshots
  • JavascriptModulesRenderStartup: Patches entry chunks with explicit startup calls
  • Hook Subscription: Registers with FederationModulesPlugin for dependency tracking

3. HoistContainerReferencesPlugin (hoist_container_references_plugin.rs)

Implements module hoisting that excludes async dependencies:

#[plugin]
pub struct HoistContainerReferencesPlugin {
  federation_deps: Arc<Mutex<FxHashSet<DependencyId>>>,
}

impl HoistContainerReferencesPlugin {
  // BFS Module Collection with Async Filtering
  fn get_all_referenced_modules(module: &dyn Module, ty: &str) -> FxHashSet<ModuleIdentifier> {
    let mut stack = VecDeque::new();
    let mut visited = FxHashSet::default();
    
    while let Some(current_id) = stack.pop_front() {
      for conn in module_graph.get_outgoing_connections(&current_id) {
        // Critical: Exclude async block dependencies for 'initial' type
        if ty == "initial" {
          let parent_block = module_graph.get_parent_block(&conn.dependency_id);
          if parent_block.is_some() {
            continue; // Skip async dependencies
          }
        }
        // Add to collection and continue traversal
        stack.push_back(conn.module_identifier());
      }
    }
  }

  // Enhanced Runtime Chunk Detection
  fn get_runtime_chunks(compilation: &Compilation) -> FxHashSet<ChunkUkey> {
    let mut runtime_chunks = FxHashSet::default();
    
    // Method 1: From entrypoints
    for entrypoint in compilation.entrypoints.values() {
      runtime_chunks.insert(entrypoint.get_runtime_chunk());
    }
    
    // Method 2: Direct chunk runtime detection (supports runtimeChunk: 'single')
    for (chunk_ukey, chunk) in compilation.chunk_by_ukey.iter() {
      if chunk.has_runtime(&compilation.chunk_group_by_ukey) {
        runtime_chunks.insert(*chunk_ukey);
      }
    }
  }
}

Three types of dependencies:

graph LR
    subgraph "Federation Dependencies"
        CED[Container Entry Dependencies]
        FRD[Federation Runtime Dependencies]
        RD[Remote Dependencies<br/>RemoteToExternal + Fallback]
    end
    
    subgraph "Collection Process"
        BFS[BFS Traversal]
        AF[Async Filtering]
        MC[Module Collection]
    end
    
    subgraph "Hoisting Process"
        RC[Runtime Chunks]
        CC[Connect to Chunks]
        CU[Cleanup Non-Runtime]
    end
    
    CED --> BFS
    FRD --> BFS  
    RD --> BFS
    
    BFS --> AF
    AF --> MC
    MC --> RC
    RC --> CC
    CC --> CU
Loading

Features:

  • Three collectors: Separate handling for container, runtime, and remote dependencies
  • BFS Algorithm: Breadth-first search to find all dependencies
  • Async Exclusion: Filters out async block dependencies that shouldn't be hoisted
  • Runtime Chunk Detection: Works with different runtimeChunk configurations
  • Cleanup: Removes empty chunks and updates named chunk registry
  • Timing: Runs at OPTIMIZE_CHUNKS_STAGE_ADVANCED + 1 to integrate properly

4. FederationModulesPlugin (federation_modules_plugin.rs)

Provides hooks for plugins to communicate during compilation:

// Hook Definitions for Plugin Communication
define_hook!(AddContainerEntryDependencyHook: Series(dependency: &ContainerEntryDependency));
define_hook!(AddFederationRuntimeDependencyHook: Series(dependency: &FederationRuntimeDependency));
define_hook!(AddRemoteDependencyHook: Series(dependency: &dyn Dependency));

pub struct FederationModulesPluginCompilationHooks {
  pub add_container_entry_dependency: Arc<TokioMutex<AddContainerEntryDependencyHookHook>>,
  pub add_federation_runtime_dependency: Arc<TokioMutex<AddFederationRuntimeDependencyHookHook>>,
  pub add_remote_dependency: Arc<TokioMutex<AddRemoteDependencyHookHook>>,
}

// Global hook registry with compilation isolation
static FEDERATION_MODULES_PLUGIN_HOOKS_MAP: OnceLock<
  Mutex<HashMap<CompilationId, Arc<FederationModulesPluginCompilationHooks>>>
> = OnceLock::new();

Hook system features:

  • Compilation Isolation: Each compilation gets its own hook instance
  • Type Safety: Hooks are strongly-typed for different dependency categories
  • Async Support: Uses TokioMutex for async hook execution
  • Publisher-Subscriber: Keeps plugins loosely coupled
  • Thread Safety: Global registry with proper synchronization

Hook Publishers and Subscribers

Hook Triggers (Publishers)

AddContainerEntryDependencyHook

  • Triggered by: ContainerPlugin during CompilerMake phase
  • When: Creating container entry dependencies for exposed modules
  • Dependency Type: ContainerEntryDependency

AddFederationRuntimeDependencyHook

  • Triggered by: ModuleFederationRuntimePlugin during CompilerFinishMake phase
  • When: Creating federation runtime dependencies for runtime entry
  • Dependency Type: FederationRuntimeDependency

AddRemoteDependencyHook

  • Triggered by: RemoteModule during module build() phase
  • When: Creating remote dependencies (RemoteToExternal or Fallback)
  • Dependency Types: RemoteToExternalDependency, FallbackDependency

Hook Subscribers

HoistContainerReferencesPlugin - Subscribes to ALL three hooks:

  • Collects all federation dependencies into shared set
  • Uses collected dependencies during optimize_chunks phase
  • Hoists federation modules to runtime chunks with BFS + async filtering

EmbedFederationRuntimePlugin - Subscribes to federation runtime hook only:

  • Collects federation runtime dependencies
  • Determines which chunks need startup wrapper
  • Injects EmbedFederationRuntimeModule into runtime chunks

5. FederationRuntimeDependency (federation_runtime_dependency.rs)

Custom dependency type for tracking federation runtime:

#[cacheable]
#[derive(Debug, Clone)]
pub struct FederationRuntimeDependency {
  id: DependencyId,
  request: Cow<'static, str>,
  // ... other fields
}

impl Dependency for FederationRuntimeDependency {
  fn category(&self) -> &DependencyCategory {
    &DependencyCategory::Esm
  }
  
  fn dependency_type(&self) -> &DependencyType {
    &DependencyType::EsmImport
  }
}

Implementation Details

Two-Phase Runtime Injection

sequenceDiagram
    participant MFRP as ModuleFederationRuntimePlugin
    participant CP as Compilation Process
    participant FMP as FederationModulesPlugin
    participant EFRP as EmbedFederationRuntimePlugin
    participant HCRP as HoistContainerReferencesPlugin
    participant EFRM as EmbedFederationRuntimeModule
    
    Note over MFRP: Main Orchestrator
    MFRP->>EFRP: apply() during setup
    MFRP->>HCRP: apply() during setup
    
    Note over CP: Phase 1: Dependency Collection
    CP->>FMP: Initialize compilation hooks
    CP->>HCRP: Subscribe to all federation hooks
    CP->>EFRP: Subscribe to runtime dependency hook
    MFRP->>FMP: Trigger add_federation_runtime_dependency in finish_make
    FMP->>HCRP: add_container_entry_dependency from ContainerPlugin
    FMP->>EFRP: add_federation_runtime_dependency 
    FMP->>HCRP: add_remote_dependency from RemoteModuleFactory
    
    Note over CP: Phase 2: Runtime Injection  
    CP->>EFRP: runtime_requirement_in_tree
    EFRP->>EFRP: Create dependency snapshot
    EFRP->>EFRM: Inject with snapshot
    EFRM->>EFRM: Generate prevStartup wrapper
Loading

Phase 1 - Collection: Federation dependencies collected via hook system during module processing Phase 2 - Injection: Runtime modules injected with dependency snapshots during runtime_requirement_in_tree

How chunks are classified

flowchart TB
    subgraph "Chunk Analysis Pipeline"
        CA[Chunk Analysis] 
        CA --> RTD{Has Runtime?}
        CA --> EMD{Has Entry Modules?}
        CA --> BTD{Is Build Time?}
    end
    
    subgraph "Processing Paths"  
        RTD -->|Yes + Yes| RTE[Runtime + Entry<br/>Natural Startup]
        RTD -->|Yes + No| RTO[Runtime Only<br/>Inject Runtime Module]
        EMD -->|No + Yes| EO[Entry Only<br/>Add Startup Call]
        BTD -->|Yes| SKIP[Skip Processing]
        
        RTD -->|No| EMD
        EMD -->|No| NC[Normal Chunk<br/>Skip]
    end
    
    subgraph "Actions"
        RTE --> NSH[Natural Startup Handling]
        RTO --> IRM[Inject EmbedFederationRuntimeModule]
        EO --> ASC[Add Explicit Startup Call] 
        IRM --> GSW[Generate Startup Wrapper]
        ASC --> DSC[Delegate to Runtime Chunk]
    end
Loading

Module Hoisting Flow

graph TD
    subgraph "Dependency Processing"
        FD[Federation Dependencies] --> TTC[Three-Type Classification]
        TTC --> CED[Container Entry Deps]
        TTC --> FRD[Federation Runtime Deps] 
        TTC --> RD[Remote Deps]
    end
    
    subgraph "Collection Algorithm"
        CED --> BFS1[BFS Traversal]
        FRD --> BFS2[BFS Traversal]
        RD --> BFS3[BFS Traversal]
        
        BFS1 --> AF1[Async Filtering]
        BFS2 --> AF2[Async Filtering]
        BFS3 --> AF3[Async Filtering]
    end
    
    subgraph "Hoisting Process"
        AF1 --> MC[Module Collection]
        AF2 --> MC
        AF3 --> MC
        
        MC --> RCR[Runtime Chunk Resolution]
        RCR --> CHC[Connect to Runtime Chunks]
        CHC --> CUP[Cleanup Non-Runtime Chunks]
    end
    
    subgraph "Optimization Results"
        CUP --> BSO[Bundle Size Optimization]
        CUP --> ECR[Empty Chunk Removal]
        CUP --> NCU[Named Chunk Updates]
    end
Loading

Configuration and Integration

Automatic Activation

The hoisted runtime system turns on automatically when you use Module Federation - no configuration needed:

// Zero-configuration activation
module.exports = {
  plugins: [
    new ModuleFederationPlugin({
      name: 'myApp',
      remotes: {
        'remote1': 'remote1@http://localhost:3001/remoteEntry.js'
      },
      exposes: {
        './Component': './src/Component'
      }
    })
  ]
};

What gets enabled automatically:

  • ✅ Startup wrapper pattern injection
  • ✅ Runtime error prevention
  • ✅ Proper initialization order
  • ✅ Web worker compatibility

Bundle Size Impact by Configuration:

Configuration Before After Reduction
Single Entry 140kb 140kb 0%
Multiple Entries (default) 210kb 210kb 0%
Multiple Entries + runtimeChunk: 'single' 210kb 70kb 67%
Multiple Entries + runtimeChunk: true 210kb 150kb 29%

Performance Impact & Technical Benefits

Runtime Error Elimination

The system fixes federation runtime errors in these ways:

graph LR
    subgraph "Error Types Eliminated"
        E1[Initialization Order Error]
        E2[Federation Dependencies Not Available]
        E4[Double Execution Race Conditions]
    end
    
    subgraph "Solutions Applied"
        S1[Startup Wrapper Pattern]
        S2[Runtime Dependency Hoisting]
        S4[hasRun Flag]
    end
    
    E1 --> S1
    E2 --> S2
    E4 --> S4
Loading

Bundle Size Optimization Metrics

Performance numbers:

Metric Before Enhancement After Enhancement Improvement
Multi-Entry Federation Bundle 210kb 70kb 67% reduction
Federation Runtime Per Entry ~70kb duplicate Shared in runtime ~70kb saved/entry
Memory Usage (Runtime) Fragmented across chunks Centralized Reduced fragmentation
Initialization Time Variable (race conditions) Deterministic Consistent startup

Runtime Performance Characteristics

gantt
    title Federation Initialization Timeline
    dateFormat X
    axisFormat %s
    
    section Before Enhancement
    Entry Chunk Load    :done, e1, 0, 100
    Federation Init     :crit, f1, 50, 150
    App Modules Load    :done, a1, 75, 200
    Runtime Errors      :active, er, 100, 120
    
    section After Enhancement  
    Runtime Chunk Load  :done, r2, 0, 50
    Federation Init     :done, f2, 10, 60
    Entry Chunk Load    :done, e2, 40, 100
    App Modules Load    :done, a2, 90, 150
    Clean Startup       :milestone, cs, 150, 0
Loading

Performance benefits:

  • Predictable startup: Federation dependencies always initialize before modules that depend on them
  • Single execution: hasRun flag prevents running initialization twice
  • Low overhead: Small wrapper function cost vs. error handling cost
  • Memory efficiency: Shared federation runtime reduces memory fragmentation
  • Better caching: Centralized runtime chunks work better with browser caching

Supporting Infrastructure & Integration

Hook System

The federation enhancement adds a hook-based communication system:

graph TB
    subgraph "Hook Registry Architecture"
        FMPHM[FederationModulesPlugin<br/>Hook Manager] --> GHR[Global Hook Registry<br/>CompilationId → Hooks]
        GHR --> CH1[Compilation 1 Hooks]
        GHR --> CH2[Compilation 2 Hooks]
        GHR --> CHN[Compilation N Hooks]
    end
    
    subgraph "Hook Types"
        CH1 --> ACEDH[AddContainerEntryDependencyHook]
        CH1 --> AFRDH[AddFederationRuntimeDependencyHook]  
        CH1 --> ARDH[AddRemoteDependencyHook]
    end
    
    subgraph "Plugin Subscribers"
        HCRP[HoistContainerReferencesPlugin] --> ACEDH
        HCRP --> AFRDH
        HCRP --> ARDH
        
        EFRP[EmbedFederationRuntimePlugin] --> AFRDH
    end
Loading

New Dependency Type System

// Specialized dependency types for federation tracking
pub struct FederationRuntimeDependency {
  id: DependencyId,
  request: Cow<'static, str>,
  category: DependencyCategory::Esm,
  dependency_type: DependencyType::EsmImport,
  affect_type: AffectType::False, // Non-affecting dependency
}

// Integration with existing dependency system
impl ModuleDependency for FederationRuntimeDependency {
  fn request(&self) -> &str { &self.request }
  fn user_request(&self) -> &str { &self.request }
  fn set_request(&mut self, request: String) { /* ... */ }
}

New Files

File Lines Purpose
embed_federation_runtime_module.rs 119 Runtime wrapper (prevStartup pattern, stage 11)
embed_federation_runtime_plugin.rs 247 Chunk classification and runtime injection
hoist_container_references_plugin.rs 320 BFS hoisting with async exclusion
federation_modules_plugin.rs 114 Hook registry for plugin coordination
federation_runtime_dependency.rs ~80 ESM dependency type for federation runtime
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment