前端工程Technical Deep Dive
微前端落地实战 —— qiankun、single-spa 与模块联邦
发布时间2026/03/29
分类前端工程
预计阅读2 分钟
作者吴长龙
*
为什么需要微前端
01.内容
为什么需要微前端
随着应用复杂度增长,单体前端架构面临挑战:
code snippetcode
单体应用问题:
├── 代码仓库庞大,团队协作困难
├── 构建时间长,每次修改需要全量构建
├── 技术栈升级困难,存量代码难以迁移
├── 多人维护同一项目,冲突频繁
└── 无法按需加载,性能受限微前端:将大型应用拆分为独立的小应用。
微前端方案对比
| 方案 | 原理 | 优点 | 缺点 |
|---|---|---|---|
| iframe | 嵌入页面 | 隔离强 | 通信难、体验差 |
| Web Components | 浏览器标准 | 原生支持 | 生态弱 |
| single-spa | 路由劫持 | 兼容性好 | 功能少 |
| qiankun | single-spa + sandbox | 功能完善 | 蚂蚁技术栈 |
| 模块联邦 | webpack 5 | 原生支持 | 限制较多 |
| Garfish | 插件化 | 中立 | 文档少 |
single-spa:基础框架
javascript snippetjavascript
import { registerApplication, start } from 'single-spa';
// 注册子应用
registerApplication(
'app1',
() => import('./app1/App.js'),
(activityFn) => activityFn.hashChanges('#/app1')
);
registerApplication(
'app2',
() => import('./app2/App.js'),
(activityFn) => activityFn.hashChanges('#/app2')
);
start();javascript snippetjavascript
// 子应用导出生命周期
const App = () => <div>App 1</div>;
export const bootstrap = async () => console.log('bootstrap');
export const mount = async (props) => {
ReactDOM.render(<App />, document.getElementById('app1-root'));
};
export const unmount = async (props) => {
ReactDOM.unmountComponentAtNode(document.getElementById('app1-root'));
};qiankun:生产级方案
qiankun 是蚂蚁金服的微前端方案,基于 single-spa,提供了更完善的功能:
#### 主应用
javascript snippetjavascript
// main/src/index.js
import { start, registerMicroApps, setDefaultMountApp } from 'qiankun';
registerMicroApps([
{
name: 'react-app',
entry: '//localhost:3001',
container: '#container',
activeRule: '/react',
},
{
name: 'vue-app',
entry: '//localhost:3002',
container: '#container',
activeRule: '/vue',
},
{
name: 'static-app',
entry: '//localhost:3003',
container: '#container',
activeRule: '/static',
},
]);
setDefaultMountApp('/react');
start({
sandbox: {
strictStyleIsolation: true, // 样式隔离
experimentalStyleIsolation: true, // CSS Modules 方案
},
});javascript snippetjavascript
// 主应用布局
function Layout() {
const [app, setApp] = useState('react');
return (
<div>
<nav>
<button onClick={() => setApp('react')}>React</button>
<button onClick={() => setApp('vue')}>Vue</button>
</nav>
<main id="container">
{/* 子应用容器 */}
</main>
</div>
);
}#### React 子应用
javascript snippetjavascript
// 子应用入口 react-app/src/index.js
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
let root;
function render(props) {
const { container } = props;
root = ReactDOM.createRoot(container ? container.querySelector('#root') : document.getElementById('root'));
root.render(<App />);
}
function unmount() {
if (root) {
root.unmount();
}
}
// 导出生命周期
export async function bootstrap() {
console.log('[react] bootstrap');
}
export async function mount(props) {
console.log('[react] mount', props);
render(props);
}
export async function unmount() {
console.log('[react] unmount');
unmount();
}
// 开发模式独立运行
if (!window.__POWERED_BY_QIANKUN__) {
render({});
}javascript snippetjavascript
// webpack 配置 react-app/webpack.config.js
const { name } = require('./package.json');
module.exports = {
output: {
library: `${name}-[name]`,
libraryTarget: 'umd',
globalObject: 'window',
},
devServer: {
port: 3001,
headers: {
'Access-Control-Allow-Origin': '*',
},
},
};#### Vue 子应用
javascript snippetjavascript
// Vue 子应用 src/main.js
import Vue from 'vue';
import App from './App.vue';
let app;
export function bootstrap() {
console.log('[vue] bootstrap');
}
export async function mount(props) {
console.log('[vue] mount', props);
app = new Vue({
render: h => h(App),
}).$mount('#app');
}
export async function unmount() {
console.log('[vue] unmount');
if (app) {
app.$destroy();
app.$el.innerHTML = '';
}
}
// 开发模式独立运行
if (!window.__POWERED_BY_QIANKUN__) {
mount();
}模块联邦 (Module Federation)
Webpack 5 的原生微前端方案:
javascript snippetjavascript
// host/webpack.config.js
const { ModuleFederationPlugin } = require('webpack').container;
module.exports = {
plugins: [
new ModuleFederationPlugin({
name: 'host',
remotes: {
remoteApp: 'remoteApp@http://localhost:3001/remoteEntry.js',
},
shared: {
react: { singleton: true },
'react-dom': { singleton: true }
},
}),
],
};javascript snippetjavascript
// host/src/App.js
import React, { Suspense } from 'react';
const RemoteButton = React.lazy(() => import('remoteApp/Button'));
function App() {
return (
<Suspense fallback="Loading...">
<RemoteButton />
</Suspense>
);
}javascript snippetjavascript
// remote/webpack.config.js
const { ModuleFederationPlugin } = require('webpack').container;
module.exports = {
plugins: [
new ModuleFederationPlugin({
name: 'remoteApp',
exposes: {
'./Button': './src/Button',
},
shared: {
react: { singleton: true, eager: true },
'react-dom': { singleton: true }
},
}),
],
};通信机制
#### 1. 基于 Props
javascript snippetjavascript
// 主应用
<MicroApp
onDataChange={(data) => console.log(data)}
/>
// 子应用
export async function mount(props) {
props.onDataChange({ from: 'child', message: 'hello' });
}#### 2. 基于 Event
javascript snippetjavascript
// 工具函数
import { emit } from 'qiankun';
function dispatch(props) {
// 发送消息
emit('app1-event', { data: 'hello' });
}
// 监听
import { on } from 'qiankun';
export async function mount(props) {
on('app1-event', (data) => {
console.log('received:', data);
});
}#### 3. 共享状态
javascript snippetjavascript
// host/src/store.js
import { createStore } from 'qiankun/state';
export const { getGlobalState, setGlobalState, subscribe } = createStore({
user: null,
token: null,
});
// 子应用使用
import { useStore } from 'qiankun/state';
function App() {
const user = useStore('user');
return <div>{user?.name}</div>;
}样式隔离
#### 1. CSS Modules
javascript snippetjavascript
// webpack 配置
{
loader: 'css-loader',
options: {
modules: {
localIdentName: '[name]__[local]--[hash:base64:5]',
},
},
}#### 2. Shadow DOM
javascript snippetjavascript
// qiankun 配置
start({
sandbox: {
styleSheetStrategy: 'shadowDOM',
},
});#### 3. 动态添加前缀
javascript snippetjavascript
// 主应用
import Garfish from '@garfish/runtime';
Garfish.run({
apps: [{
name: 'child',
entry: '//localhost:3001',
props: { prefix: 'child' },
}],
});最佳实践
#### 1. 应用加载策略
javascript snippetjavascript
registerMicroApps([
{
name: 'app1',
entry: '//localhost:3001',
loader: (loading) => <Spin loading={loading} />,
},
]);#### 2. 预加载
javascript snippetjavascript
// qiankun 自动预加载
start({
prefetch: true, // 默认 true
});#### 3. 错误处理
javascript snippetjavascript
// 全局错误处理
import { addErrorHandler } from 'qiankun';
addErrorHandler((error) => {
console.error('Global error:', error);
// 上报错误
});总结
| 方案 | 适用场景 | 推荐度 |
|---|---|---|
| qiankun | React/Vue 为主 | ⭐⭐⭐⭐⭐ |
| 模块联邦 | Webpack 5 项目 | ⭐⭐⭐⭐ |
| single-spa | 简单场景 | ⭐⭐⭐ |
| iframe | 强隔离需求 | ⭐⭐ |
微前端不是银弹,适合才最好。