Module Federation
Build scalable micro-frontend architectures with Lunx's first-class Module Federation support. Share code, components, and state across independently deployed applications.
What is Module Federation?
Module Federation (MFE) allows multiple independently-deployed JavaScript applications to share code at runtime. With Lunx, you declare which modules each application exposes and which remotes it consumes — the Rust bundler handles everything else.
- Shell / Host app — the main application that loads remote modules
- Remote app — independently deployed app that exposes components or utilities
- Shared modules — singletons like React that must have only one instance at runtime
Setting up a Host application
import { defineConfig } from 'lunx' export default defineConfig({ framework: 'react', server: { port: 3000 }, federation: { name: 'shell', remotes: { // Name: 'remoteName@remoteEntry URL' auth: 'auth@http://localhost:3001/remoteEntry.js', dashboard: 'dashboard@http://localhost:3002/remoteEntry.js', profile: 'profile@http://localhost:3003/remoteEntry.js', }, shared: { react: { singleton: true, requiredVersion: '^18.0.0' }, 'react-dom': { singleton: true, requiredVersion: '^18.0.0' }, 'react-router-dom': { singleton: true }, }, }, })
Setting up a Remote application
import { defineConfig } from 'lunx' export default defineConfig({ framework: 'react', server: { port: 3002 }, federation: { name: 'dashboard', filename: 'remoteEntry.js', // entry point consumed by host exposes: { // './LocalName': './path/to/component' './Widget': './src/components/Widget.tsx', './StatCard': './src/components/StatCard.tsx', './useStats': './src/hooks/useStats.ts', }, shared: { react: { singleton: true, requiredVersion: '^18.0.0' }, 'react-dom': { singleton: true, requiredVersion: '^18.0.0' }, }, }, })
Consuming a remote component
Use React.lazy to import remote components. Lunx resolves the dashboard/Widget import to the configured remote URL at build time.
import { lazy, Suspense } from 'react' // These are loaded from the remote application at runtime const DashboardWidget = lazy(() => import('dashboard/Widget')) const DashboardStatCard = lazy(() => import('dashboard/StatCard')) export default function App() { return ( <div className="p-8"> <h1>Shell Application</h1> <Suspense fallback={<div>Loading dashboard...</div>}> <DashboardWidget title="Revenue" value="$124,340" /> <DashboardStatCard metric="Active Users" count={8_291} /> </Suspense> </div> ) }
TypeScript type sharing
Lunx can generate TypeScript declaration files for all exposed modules, enabling full type safety across remote boundaries without manual type duplication.
export default defineConfig({ framework: 'react', federation: { name: 'dashboard', exposes: { './Widget': './src/components/Widget.tsx' }, // Generate .d.ts files for all exposed modules dts: { outputPath: './dist/types' }, }, })
# In the shell app, install the remote's types npm install dashboard-app@http://localhost:3002/types.tgz # Now imports are fully typed: import type { WidgetProps } from 'dashboard/Widget'
Production deployment
Each micro-frontend is deployed independently. The host application only needs the URL of each remote's remoteEntry.js — update a remote without redeploying the host.
export default defineConfig(({ mode }) => ({ framework: 'react', federation: { name: 'shell', remotes: { // Use environment variables for production URLs dashboard: `dashboard@${ mode === 'production' ? 'https://dashboard.example.com/remoteEntry.js' : 'http://localhost:3002/remoteEntry.js' }`, }, }, }))
react, react-dom, and routing libraries as { singleton: true }. Multiple React instances cause context failures and hooks to behave incorrectly. Lunx will warn you at startup if duplicates are detected.