Micro-Frontend Architecture in Practice: Module Federation, qiankun, and Custom Solutions — Selection and Implementation
技术架构(Updated May 12, 2026)
Micro-Frontends: The Cure for Monolithic Applications
When a single-repo frontend exceeds 500K lines of code, 5+ teams, and 10-minute builds—micro-frontends become inevitable.
| Pain Point | Micro-Frontend Solution |
|---|---|
| Slow builds | Independent sub-app builds, incremental deployment |
| Team conflicts | Independent repos, independent releases |
| Tech stack lock-in | Each sub-app can choose its own tech stack |
| Tight coupling | Communication via contracts, not direct references |
1. Solution Comparison
| Solution | Isolation | Communication | Performance | Complexity | Tech Stack |
|---|---|---|---|---|---|
| iframe | Perfect | postMessage | Poor | Low | Any |
| qiankun (JS Sandbox) | Good | props/eventBus | Good | Medium | Any |
| Module Federation | None | Shared modules | Excellent | Medium | Webpack 5+ |
| single-spa | Good | Custom | Good | High | Any |
| EMP | None | Shared modules | Excellent | Medium | Webpack 5 |
2. iframe Approach: Simple but Limited
Implementation
<iframe
src="https://sub-app.example.com"
sandbox="allow-scripts allow-same-origin allow-forms"
style="width: 100%; height: 100%; border: none;"
></iframe>
Pros and Cons
| Advantages | Limitations |
|---|---|
| Perfect CSS/JS isolation | Poor performance (each iframe is a separate process) |
| Naturally secure | URL not synced, state lost on refresh |
| Zero migration cost | Modals/overlays clipped by iframe boundaries |
| Any tech stack | Communication limited to postMessage |
Suitable scenarios: Third-party embeds, low-interaction needs, security isolation priority.
3. qiankun: Alibaba's JS Sandbox Solution
Main App Configuration
import { registerMicroApps, start } from 'qiankun';
registerMicroApps([
{
name: 'app-order',
entry: '//localhost:8081',
container: '#subapp-container',
activeRule: '/order',
props: { mainStore, userInfo },
},
{
name: 'app-user',
entry: '//localhost:8082',
container: '#subapp-container',
activeRule: '/user',
props: { mainStore, userInfo },
},
]);
start({
prefetch: 'all', // Preload
sandbox: { strictStyleIsolation: true }, // Strict style isolation
singular: false, // Allow multiple sub-apps to mount simultaneously
});
Sub-App Adaptation
// Sub-app entry
let root = null;
function render(props) {
const { container, mainStore } = props;
root = createApp(App);
root.mount(container ? container.querySelector('#app') : '#app');
}
// Export lifecycle
export async function bootstrap() {}
export async function mount(props) { render(props); }
export async function unmount() { root.unmount(); }
// Standalone mode
if (!window.__POWERED_BY_QIANKUN__) {
render({});
}
JS Sandbox Principle
qiankun uses Proxy to intercept window operations:
const fakeWindow = new Proxy(window, {
get(target, key) {
// Prioritize sandbox value
if (sandbox.hasOwnProperty(key)) return sandbox[key];
return target[key];
},
set(target, key, value) {
// Write to sandbox, not real window
sandbox[key] = value;
return true;
},
});
Style Isolation
| Mode | Implementation | Limitations |
|---|---|---|
strictStyleIsolation |
Shadow DOM | Modal styles lost, global styles don't penetrate |
experimentalStyleIsolation |
CSS Scope prefix | Dynamically inserted styles not handled |
4. Module Federation: Webpack 5 Runtime Module Sharing
Basic Concept
App A (Host) ──remote load──→ Modules from App B (Remote)
↑ ↑
Shared deps Shared deps
(react, lodash...) (react, lodash...)
Remote Configuration (Providing Modules)
// webpack.config.js (App B - Remote)
const { ModuleFederationPlugin } = require('webpack').container;
module.exports = {
plugins: [
new ModuleFederationPlugin({
name: 'appOrder',
filename: 'remoteEntry.js',
exposes: {
'./OrderList': './src/components/OrderList',
'./OrderDetail': './src/pages/OrderDetail',
},
shared: {
react: { singleton: true, requiredVersion: '^18.0.0' },
'react-dom': { singleton: true, requiredVersion: '^18.0.0' },
},
}),
],
};
Host Configuration (Consuming Modules)
// webpack.config.js (App A - Host)
new ModuleFederationPlugin({
name: 'appMain',
remotes: {
appOrder: 'appOrder@http://localhost:8081/remoteEntry.js',
},
shared: {
react: { singleton: true, requiredVersion: '^18.0.0' },
'react-dom': { singleton: true, requiredVersion: '^18.0.0' },
},
})
Dynamically Loading Remote Components
const OrderList = React.lazy(() => import('appOrder/OrderList'));
function App() {
return (
<Suspense fallback={<Loading />}>
<OrderList />
</Suspense>
);
}
Module Federation 2.0 (2026)
| New Feature | Description |
|---|---|
| Native ESM support | No longer depends on Webpack runtime |
| Rspack compatibility | 5-10x build speed improvement |
| Vite plugin | @originjs/vite-plugin-federation |
| Type safety | Automatic type inference for remote modules |
| Runtime dynamic registration | No need to determine remote URL at build time |
5. Communication Strategies
Option 1: CustomEvent
// Sub-app publishes
window.dispatchEvent(new CustomEvent('order:created', {
detail: { orderId: '12345' },
}));
// Main app subscribes
window.addEventListener('order:created', (e) => {
console.log('New order:', e.detail);
});
Option 2: Shared State (Micro Store)
class MicroStore {
state = {};
listeners = new Map();
setState(key, value) {
this.state[key] = value;
this.listeners.get(key)?.forEach(fn => fn(value));
}
subscribe(key, listener) {
if (!this.listeners.has(key)) this.listeners.set(key, new Set());
this.listeners.get(key).add(listener);
return () => this.listeners.get(key).delete(listener);
}
}
// Main app creates, passes to sub-apps via props
const store = new MicroStore();
Option 3: URL Synchronization
// Sub-app communicates via URL params
const params = new URLSearchParams(location.search);
const token = params.get('token');
// Main app listens for route changes
router.afterEach((to) => {
// Notify sub-apps of route change
store.setState('route', to.path);
});
6. Shared Dependency Management
| Strategy | Implementation | Pros/Cons |
|---|---|---|
| externals + CDN | All shared libs via CDN | Simple but version locked |
| Module Federation shared | Runtime version negotiation | Flexible but complex config |
| Monorepo shared packages | pnpm workspace | Strong consistency but coupled |
| NPM private packages | Publish to private registry | Decoupled but requires publish flow |
Module Federation Shared Best Practices
shared: {
react: {
singleton: true, // Only load one version
strictVersion: true, // Error on version mismatch
requiredVersion: deps.react,
eager: false, // Async load
},
lodash: {
singleton: false, // Allow multiple versions
requiredVersion: deps.lodash,
},
}
7. Deployment Strategy
Independent Deployment
CDN
├── /main/ → Main app (hash: a1b2c3)
├── /app-order/ → Order sub-app (hash: d4e5f6)
├── /app-user/ → User sub-app (hash: g7h8i9)
└── /shared/ → Shared resources (react, lodash...)
Version Management
// remote-config.json (loaded dynamically by main app)
{
"apps": {
"app-order": {
"url": "https://cdn.example.com/app-order/d4e5f6/remoteEntry.js",
"version": "2.3.1"
},
"app-user": {
"url": "https://cdn.example.com/app-user/g7h8i9/remoteEntry.js",
"version": "1.8.0"
}
}
}
The main app fetches this config on startup, enabling independent deployment and canary releases for sub-apps.
8. Selection Decision Tree
Need perfect isolation?
├── Yes → iframe
└── No → Same tech stack?
├── Yes → Module Federation
└── No → Need sandbox isolation?
├── Yes → qiankun
└── No → single-spa / custom
2026 Recommendation: For new projects, prioritize Module Federation 2.0 + Rspack for both performance and developer experience. Consider qiankun only when strong isolation is required.
#微前端#Module Federation#qiankun#架构