在现代前端工程化演进过程中,面对日益复杂的业务系统,微前端架构已成为解决巨石应用(Monolithic)维护难题的主流方案。作为蚂蚁金服开源的微前端实现库,qiankun基于Single-SPA进行二次封装,提供了更完整的生命周期管理、样式隔离和资源预加载能力。其核心设计理念可概括为:
实际项目中,我们选择qiankun主要基于以下技术考量:
主应用作为微前端架构的调度中心,需要完成以下关键配置(以Vue3为例):
javascript复制// main-app/src/micro-fe/register.js
import { registerMicroApps, start } from 'qiankun';
registerMicroApps([
{
name: 'vue3-subapp',
entry: '//localhost:7101',
container: '#subapp-container',
activeRule: '/vue3',
props: {
mainAppRouter: router,
onGlobalStateChange: (state, prev) => console.log(state)
}
}
]);
// 启动时预加载子应用资源
start({ prefetch: 'all' });
关键参数说明:
entry: 子应用访问入口(开发环境可配本地服务,生产环境需配置CDN地址)activeRule: 路由匹配规则(支持字符串、函数或正则表达式)props: 向子应用传递的通信对象和方法子应用需要导出规定的生命周期钩子(以React 18为例):
javascript复制// subapp/src/public-path.js
if (window.__POWERED_BY_QIANKUN__) {
__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}
// subapp/src/index.js
let instance = null;
function render(props) {
const { container } = props;
instance = ReactDOM.createRoot(
container ? container.querySelector('#root') : document.getElementById('root')
);
instance.render(<App />);
}
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');
instance.unmount();
instance = null;
}
if (!window.__POWERED_BY_QIANKUN__) {
render({});
}
特别注意:
publicPath动态适配主应用分配的路径qiankun官方推荐使用initGlobalState实现主-子应用通信:
javascript复制// main-app/src/store/global-state.js
import { initGlobalState } from 'qiankun';
const initialState = {
user: { name: 'Admin', role: 'super' },
token: 'xxxxxx'
};
const actions = initGlobalState(initialState);
// 主应用监听变化
actions.onGlobalStateChange((state, prev) => {
console.log('主应用收到变更:', prev, '→', state);
});
// 暴露给子应用的通信方法
export const microAppActions = actions;
子应用通过props接收通信方法:
javascript复制// 子应用mount生命周期内
export async function mount(props) {
props.onGlobalStateChange((state, prev) => {
console.log('子应用收到变更:', state);
});
// 更新全局状态
props.setGlobalState({
user: { ...state.user, department: 'Tech' }
});
}
对于复杂场景,可结合CustomEvent实现更灵活的通信:
javascript复制// 主应用派发事件
document.dispatchEvent(
new CustomEvent('main-app-event', {
detail: { type: 'DATA_UPDATE', payload: {...} }
})
);
// 子应用监听
window.addEventListener('main-app-event', (event) => {
console.log('收到自定义事件:', event.detail);
});
// 子应用触发事件
parent.document.dispatchEvent(
new CustomEvent('subapp-event', {
detail: { from: 'vue-app', data: [...] }
})
);
警告:避免高频事件通信(建议超过10次/秒时考虑状态管理方案)
qiankun通过Proxy实现JS沙箱隔离,具体包括:
快照沙箱(SnapshotSandbox)
window对象快照比对恢复环境代理沙箱(ProxySandbox)
window代理强制开启沙箱模式配置:
javascript复制start({
sandbox: {
strictStyleIsolation: true, // 开启shadow DOM样式隔离
experimentalStyleIsolation: true // 实验性样式隔离
}
});
常见问题及应对策略:
| 问题现象 | 解决方案 | 实现示例 |
|---|---|---|
| 子应用样式污染主应用 | 开启strictStyleIsolation | sandbox: { strictStyleIsolation: true } |
| 主应用样式影响子应用 | 增加样式前缀 | postcss-plugin-namespace |
| 第三方UI库样式异常 | 动态加载样式表 | document.createElement('link') |
| 弹窗组件定位错误 | 调整挂载节点 | appendChild(document.body) |
实测建议:
strictStyleIsolation: false方便调试推荐采用CDN+版本号部署策略:
code复制https://cdn.example.com/
├── main-app/1.0.0/
├── vue-subapp/2.1.3/
└── react-subapp/3.0.0-beta/
配置子应用webpack输出:
javascript复制output: {
libraryTarget: 'umd',
filename: '[name].[contenthash:8].js',
chunkFilename: '[name].[contenthash:8].chunk.js',
publicPath: process.env.NODE_ENV === 'production'
? 'https://cdn.example.com/vue-subapp/2.1.3/'
: '/',
}
通过window.performance获取关键指标:
javascript复制// 子应用加载耗时
const subappLoadTime =
performance.getEntriesByName('qiankun:load')[0].duration;
// 首屏渲染时间
const firstPaint =
performance.getEntriesByType('paint')[1].startTime;
推荐监控阈值:
| 错误现象 | 可能原因 | 解决方案 |
|---|---|---|
Application died in status LOADING_SOURCE_CODE |
子应用入口文件未导出生命周期 | 检查bootstrap/mount/unmount导出 |
Target container with #container not found |
容器节点未渲染完成 | 主应用使用nextTick延迟加载 |
| 样式完全失效 | 开启了strictStyleIsolation | 改用experimentalStyleIsolation |
| 路由跳转异常 | 主/子应用路由模式冲突 | 统一使用history模式 |
| 接口403错误 | 跨域或token失效 | 配置主应用代理或token传递 |
javascript复制// 在子应用mount生命周期内检查
console.log('是否在qiankun环境:', window.__POWERED_BY_QIANKUN__);
console.log('当前沙箱代理:', window.__SANDBOX_PROXY__);
javascript复制// 主应用监听所有状态变更
actions.onGlobalStateChange((state, prev) => {
console.groupCollapsed('[qiankun] state change');
console.log('Previous:', prev);
console.log('Current:', state);
console.trace('Call stack');
console.groupEnd();
});
javascript复制// 记录子应用加载时间线
performance.mark('qiankun:start');
window.addEventListener('qiankun:load', () => {
performance.mark('qiankun:end');
performance.measure('qiankun:load', 'qiankun:start', 'qiankun:end');
});
实现按需加载微应用配置:
javascript复制// 从接口获取微应用配置
const fetchMicroAppConfig = async () => {
const res = await fetch('/api/micro-apps');
return res.json();
};
// 动态注册微应用
const setupMicroApps = async () => {
const apps = await fetchMicroAppConfig();
registerMicroApps(apps.map(app => ({
name: app.id,
entry: app.entry,
container: `#${app.id}-container`,
activeRule: `/app/${app.id}`,
props: app.props
})));
};
建立基于Pub/Sub的通信机制:
javascript复制// shared/event-bus.js
class MicroAppEventBus {
constructor() {
this.events = {};
}
on(event, callback) {
if (!this.events[event]) this.events[event] = [];
this.events[event].push(callback);
}
emit(event, data) {
(this.events[event] || []).forEach(fn => fn(data));
}
}
// 主应用初始化
window.eventBus = new MicroAppEventBus();
// 子应用使用
window.parent.eventBus.on('data-update', (payload) => {
console.log('收到跨应用事件:', payload);
});
结合Redux实现全局状态共享:
javascript复制// main-app/src/store.js
import { createStore } from 'redux';
const rootReducer = (state, action) => {
// 处理各微应用action
};
export const store = createStore(rootReducer);
// 向子应用传递dispatch方法
initGlobalState({
dispatch: store.dispatch,
subscribe: store.subscribe
});
// 子应用连接store
props.onGlobalStateChange(({ dispatch }) => {
dispatch({ type: 'SUBAPP_READY' });
});
javascript复制const safeData = (obj) => {
return JSON.parse(JSON.stringify(obj, (key, value) => {
return typeof value === 'string'
? DOMPurify.sanitize(value)
: value;
}));
};
code复制Content-Security-Policy:
default-src 'self' cdn.example.com;
script-src 'unsafe-inline' 'self';
style-src 'unsafe-inline' 'self';
主应用统一管理接口鉴权:
javascript复制// 主应用注入token
initGlobalState({
getToken: () => localStorage.getItem('token'),
refreshToken: () => { /* ... */ }
});
// 子应用封装安全请求
const safeFetch = async (url, options = {}) => {
const token = await props.getToken();
return fetch(url, {
...options,
headers: {
Authorization: `Bearer ${token}`,
...options.headers
}
});
};
javascript复制// 测试子应用挂载功能
describe('MicroApp Integration', () => {
beforeAll(async () => {
await start({ sandbox: false });
});
it('should load vue subapp', async () => {
const container = document.createElement('div');
await mountMicroApp('vue-subapp', { container });
expect(container.querySelector('.vue-app')).toBeTruthy();
});
});
javascript复制// 测试生命周期钩子
describe('SubApp Lifecycles', () => {
it('should bootstrap correctly', async () => {
await bootstrap();
expect(window.__QIANKUN_APP_STATUS__).toBe('bootstrapped');
});
});
基准测试建议:
实测数据参考(MacBook Pro M1):
| 场景 | Chrome | Firefox | Safari |
|---|---|---|---|
| 冷启动 | 1200ms | 1500ms | 900ms |
| 热切换 | 200ms | 300ms | 180ms |
| 内存占用 | +15MB/app | +18MB/app | +12MB/app |
识别边界:
渐进式迁移:
mermaid复制graph LR
A[原单体应用] --> B{路由匹配}
B -->|/order/*| C[订单微应用]
B -->|/user/*| D[用户微应用]
B -->|其他| A
javascript复制// 动态加载非核心微应用
const loadReportApp = () => import('./report-app');
registerMicroApps([{
name: 'report',
activeRule: '/report',
loader: () => loadReportApp().then(mod => mod.getMicroAppConfig())
}]);
javascript复制start({
prefetch: {
urls: ['//cdn.example.com/subapp1/', '//cdn.example.com/subapp2/'],
mode: 'mobile-prefetch' // 根据网络类型调整
}
});
nginx复制# CDN配置长期缓存
location ~* \.(js|css)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
推荐采用语义化版本+合约测试:
json复制// micro-app-manifest.json
{
"dependencies": {
"user-app": "^2.1.0",
"order-app": "~3.0.0"
}
}
javascript复制// 子应用package.json
{
"qiankun": {
"exports": {
"./UserService": "./src/services/user.js"
}
}
}
关键监控指标采集:
javascript复制// 性能数据上报
const reportPerf = () => {
const timing = performance.timing;
const data = {
loadTime: timing.loadEventEnd - timing.navigationStart,
subappReady: performance.now()
};
navigator.sendBeacon('/monitor', data);
};
// 异常监控
window.addEventListener('error', (e) => {
if (e.message.includes('qiankun')) {
Sentry.captureException(e);
}
});
开发约定:
@org/user-app)文档要求:
markdown复制## 微应用集成文档
### 环境要求
- 主应用qiankun版本: >=2.4.0
- 必须导出的生命周期: bootstrap/mount/unmount
### 通信接口
```typescript
interface MicroAppProps {
onGlobalStateChange: (callback: (state, prev) => void) => void;
setGlobalState: (state: Record<string, any>) => boolean;
}
code复制
结合Webpack 5模块联邦能力:
javascript复制// 子应用webpack配置
new ModuleFederationPlugin({
name: 'userApp',
filename: 'remoteEntry.js',
exposes: {
'./UserService': './src/services/user.js'
},
shared: ['react', 'react-dom']
});
// 主应用动态加载
const UserService = await import('userApp/UserService');
qiankun SSR适配方案:
javascript复制app.get('*', (req, res) => {
const html = renderToString(<App />);
res.send(`
<div id="root">${html}</div>
<script>
window.__INITIAL_STATE__ = ${JSON.stringify(store.getState())};
</script>
`);
});
javascript复制export async function mount(props) {
hydrateRoot(
props.container || document.getElementById('root'),
<App store={props.initialState} />
);
}
基于CDN边缘节点的微前端部署:
nginx复制# 边缘节点配置
location /micro-apps/ {
proxy_pass https://origin-server/;
proxy_cache micro_app_cache;
proxy_cache_valid 200 1d;
add_header X-Edge-MicroApp "enabled";
}
性能优化效果: