TypeScript Modern.js MFE App Static
A sophisticated micro frontend application archetype built with Modern.js framework, featuring Module Federation for seamless integration with shell hosts, optimized Rspack bundling, and production-ready containerization.
Overview
This archetype generates a complete micro frontend application using Modern.js with TypeScript, designed to be consumed by MFE shell hosts through Module Federation. It provides a self-contained, deployable micro frontend that can be developed, tested, and deployed independently while seamlessly integrating into larger applications.
Architecture Diagram
Technology Stack
- Modern.js: React-based framework with Rspack bundling
- TypeScript 5+: Strong typing and modern JavaScript features
- Module Federation: Webpack's micro frontend solution
- Rspack: Ultra-fast Rust-based bundler
- React 18: Modern React with concurrent features
- Tailwind CSS: Utility-first CSS framework
- Vitest: Unit testing framework
- Playwright: End-to-end testing
- Docker: Containerization for deployment
- Kubernetes: Container orchestration support
Key Features
Micro Frontend Architecture
- Module Federation: Exposes components and routes as federated modules
- Independent Deployment: Self-contained application with own CI/CD
- Runtime Integration: Dynamic loading into shell applications
- Isolated Development: Independent development and testing
- Version Management: Semantic versioning for federated modules
Modern.js Features
- File-based Routing: Automatic route generation from file structure
- Server-Side Rendering: Optional SSR for better SEO
- Code Splitting: Automatic code splitting with Rspack
- Hot Module Replacement: Fast development experience
- Built-in Testing: Integrated testing utilities
Production Features
- Docker Optimization: Multi-stage builds for production
- Kubernetes Ready: Helm charts and deployment manifests
- CDN Integration: Static asset optimization
- Performance Monitoring: Built-in performance metrics
- Health Checks: Application health monitoring
Project Structure
{{ project-name }}/
├── src/
│ ├── App.tsx # Root application component
│ ├── components/
│ │ └── {{ project-name }}-content.tsx # Main content component
│ ├── modern-app-env.d.ts # Modern.js environment types
│ ├── modern.runtime.ts # Runtime configuration
│ ├── pages/
│ │ ├── index.css # Page styles
│ │ ├── layout.tsx # Root layout
│ │ └── page.tsx # Home page
│ ├── router.tsx # Router configuration
│ └── routes/ # Additional routes (if needed)
├── public/
│ └── health/
│ └── index.html # Health check endpoint
├── biome.json # Code quality and formatting
├── Dockerfile # Container configuration
├── modern.config.ts # Modern.js configuration
├── module-federation.config.ts # Module Federation setup
├── nginx.conf # Production server config
├── package.json # Dependencies and scripts
├── pnpm-lock.yaml # Lock file
├── postcss.config.js # PostCSS configuration
├── README.md # Project documentation
├── tailwind.config.js # Tailwind CSS configuration
└── tsconfig.json # TypeScript configuration
Module Federation Configuration
Federation Configuration
// module-federation.config.ts
import { ModuleFederationPlugin } from '@module-federation/enhanced'
export default {
name: '{{ project_name }}',
exposes: {
'./App': './src/App',
'./Content': './src/components/{{ project-name }}-content'
},
shared: {
react: {
singleton: true,
requiredVersion: '^18.0.0'
},
'react-dom': {
singleton: true,
requiredVersion: '^18.0.0'
},
'@modern-js/runtime': {
singleton: true
}
}
}
Main Application Component
// src/App.tsx
import React from 'react'
import { BrowserRouter, Routes, Route } from 'react-router-dom'
import { HomePage } from './pages/page'
import { Layout } from './pages/layout'
const App: React.FC = () => {
return (
<BrowserRouter>
<Layout>
<Routes>
<Route path="/" element={<HomePage />} />
{/* Add more routes as needed */}
</Routes>
</Layout>
</BrowserRouter>
)
}
export default App
Content Component
// src/components/{{ project-name }}-content.tsx
import React from 'react'
interface ContentProps {
title?: string
children?: React.ReactNode
}
export const {{ ProjectName }}Content: React.FC<ContentProps> = ({
title = "{{ project_name }}",
children
}) => {
return (
<div className="container mx-auto px-4 py-8">
<h1 className="text-3xl font-bold text-gray-900 mb-6">{title}</h1>
<div className="bg-white rounded-lg shadow-sm p-6">
{children || (
<p className="text-gray-600">
Welcome to your {{ project_name }} micro frontend application!
</p>
)}
</div>
</div>
)
}
Development Workflow
Modern.js Configuration
Modern.js Config
// modern.config.ts
import { defineConfig } from '@modern-js/app-tools'
import { moduleFederationPlugin } from '@module-federation/modern-js'
export default defineConfig({
runtime: {
router: true,
state: true
},
server: {
port: 3001,
host: '0.0.0.0'
},
dev: {
port: 3001,
host: '0.0.0.0'
},
html: {
title: 'Customer Management MFE',
meta: {
description: 'Customer management micro frontend application'
}
},
output: {
distPath: {
root: 'dist'
},
assetPrefix: process.env.NODE_ENV === 'production'
? 'https://cdn.example.com/customer-app/'
: 'http://localhost:3001/'
},
tools: {
rspack: {
optimization: {
splitChunks: {
chunks: 'all',
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
chunks: 'all'
}
}
}
}
},
tailwindcss: {
config: './tailwind.config.js'
}
},
plugins: [
moduleFederationPlugin({
name: 'customer_app',
exposes: {
'./CustomerModule': './src/components/federated/CustomerModule',
'./CustomerRoutes': './src/routes/customers',
'./CustomerService': './src/services/customerService'
},
shared: {
react: { singleton: true },
'react-dom': { singleton: true },
'@modern-js/runtime': { singleton: true }
}
})
],
testing: {
transformer: 'ts-jest'
}
})
Testing Strategy
Component Testing
// tests/unit/CustomerCard.test.tsx
import { render, screen, fireEvent } from '@testing-library/react'
import { CustomerCard } from '@/components/customer/CustomerCard'
import type { Customer } from '@/types/customer'
const mockCustomer: Customer = {
id: '1',
firstName: 'John',
lastName: 'Doe',
email: 'john.doe@example.com',
company: 'Acme Corp',
status: 'active',
segment: 'enterprise',
totalOrders: 15,
lifetimeValue: 5000,
createdAt: '2023-01-01T00:00:00Z',
updatedAt: '2023-01-01T00:00:00Z'
}
describe('CustomerCard', () => {
it('renders customer information correctly', () => {
render(<CustomerCard customer={mockCustomer} />)
expect(screen.getByText('John Doe')).toBeInTheDocument()
expect(screen.getByText('john.doe@example.com')).toBeInTheDocument()
expect(screen.getByText('Acme Corp')).toBeInTheDocument()
expect(screen.getByText('active')).toBeInTheDocument()
expect(screen.getByText('enterprise')).toBeInTheDocument()
expect(screen.getByText('15')).toBeInTheDocument()
expect(screen.getByText('$5,000')).toBeInTheDocument()
})
it('calls onSelect when card is clicked', () => {
const onSelect = jest.fn()
render(<CustomerCard customer={mockCustomer} onSelect={onSelect} />)
fireEvent.click(screen.getByText('John Doe'))
expect(onSelect).toHaveBeenCalledWith(mockCustomer)
})
it('calls onEdit when edit button is clicked', () => {
const onEdit = jest.fn()
render(<CustomerCard customer={mockCustomer} onEdit={onEdit} />)
fireEvent.click(screen.getByText('Edit'))
expect(onEdit).toHaveBeenCalledWith(mockCustomer)
})
it('hides actions when showActions is false', () => {
render(<CustomerCard customer={mockCustomer} showActions={false} />)
expect(screen.queryByText('Edit')).not.toBeInTheDocument()
expect(screen.queryByText('Delete')).not.toBeInTheDocument()
})
})
Federation Testing
// tests/unit/federation/CustomerModule.test.tsx
import { render, screen } from '@testing-library/react'
import CustomerModule from '@/components/federated/CustomerModule'
import type { CustomerModuleProps } from '@/types/federation'
// Mock the customer service
jest.mock('@/services/customerService')
const defaultProps: CustomerModuleProps = {
basePath: '/customers',
apiBaseUrl: 'http://localhost:3000/api',
permissions: { read: true, write: true, delete: false }
}
describe('CustomerModule Federation', () => {
it('renders without crashing', () => {
render(<CustomerModule {...defaultProps} />)
expect(screen.getByTestId('customer-module')).toBeInTheDocument()
})
it('applies theme correctly', () => {
const theme = { name: 'dark', className: 'dark-theme' }
render(<CustomerModule {...defaultProps} theme={theme} />)
const module = screen.getByTestId('customer-module')
expect(module).toHaveClass('dark-theme')
expect(module).toHaveAttribute('data-theme', 'dark')
})
it('respects permission settings', () => {
const restrictedPermissions = { read: true, write: false, delete: false }
render(<CustomerModule {...defaultProps} permissions={restrictedPermissions} />)
// Should not show create/edit buttons with restricted permissions
expect(screen.queryByText('Add Customer')).not.toBeInTheDocument()
})
})
Deployment
Docker Configuration
# Multi-stage build for Modern.js MFE app
FROM node:18-alpine AS base
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
FROM node:18-alpine AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM nginx:alpine AS production
COPY --from=build /app/dist /usr/share/nginx/html
COPY docker/nginx.conf /etc/nginx/nginx.conf
# Add health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD wget --no-verbose --tries=1 --spider http://localhost/ || exit 1
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
Kubernetes Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: customer-mfe-app
labels:
app: customer-mfe-app
spec:
replicas: 3
selector:
matchLabels:
app: customer-mfe-app
template:
metadata:
labels:
app: customer-mfe-app
spec:
containers:
- name: customer-mfe-app
image: your-registry/customer-mfe-app:latest
ports:
- containerPort: 80
env:
- name: NODE_ENV
value: "production"
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 500m
memory: 512Mi
livenessProbe:
httpGet:
path: /
port: 80
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
httpGet:
path: /
port: 80
initialDelaySeconds: 5
periodSeconds: 5
---
apiVersion: v1
kind: Service
metadata:
name: customer-mfe-app-service
spec:
selector:
app: customer-mfe-app
ports:
- port: 80
targetPort: 80
type: ClusterIP
Quick Start
-
Generate the application:
archetect render git@github.com:p6m-archetypes/typescript-modernjs-mfe-app-static.archetype.git -
Install dependencies:
npm install -
Start development server:
npm run dev -
Access the application:
http://localhost:3001 -
Run complete MFE demo:
./scripts/create_mfe_demo.sh -
Run tests:
npm run test
npm run test:e2e -
Build for production:
npm run build
Best Practices
Module Federation
- Keep shared dependencies minimal and well-versioned
- Design federated components to be self-contained
- Implement proper error boundaries for federation failures
- Use semantic versioning for exposed modules
Performance
- Optimize bundle sizes for faster loading
- Implement proper lazy loading strategies
- Use CDN for static assets
- Monitor federation loading performance
Development
- Maintain independent development environments
- Use proper TypeScript types for federation contracts
- Implement comprehensive testing for federated components
- Document federation APIs clearly
Deployment
- Use independent CI/CD pipelines
- Implement proper rollback strategies
- Monitor federated module health
- Use container orchestration for scalability