微前端将大型单体前端应用拆分为可独立开发、部署的子应用,每个子应用可以使用不同的技术栈。qiankun基于single-spa封装,通过HTML Entry加载子应用、JS沙箱隔离运行环境、CSS样式隔离防止冲突。微前端的工程挑战集中在子应用隔离、通信机制和公共依赖处理三个方向。
主应用注册与子应用加载
主应用通过registerMicroApps注册子应用列表,qiankun在路由匹配时自动加载对应子应用的HTML、CSS和JS资源。子应用导出生命周期函数供主应用调用。
主应用配置:
// main-app/src/micro-apps.ts
import { registerMicroApps, start, setDefaultMountApp } from 'qiankun';
const microApps = [
{
name: 'user-app',
entry: '//localhost:7101',
container: '#sub-app-container',
activeRule: '/user',
props: { token: localStorage.getItem('token'), apiBaseUrl: '/api' }
},
{
name: 'order-app',
entry: '//localhost:7102',
container: '#sub-app-container',
activeRule: '/order',
props: { token: localStorage.getItem('token'), apiBaseUrl: '/api' }
}
];
registerMicroApps(microApps, {
beforeLoad: [(app) => { console.log(`[主应用] ${app.name} 开始加载`); return Promise.resolve(); }],
beforeMount: [(app) => { console.log(`[主应用] ${app.name} 开始挂载`); return Promise.resolve(); }],
afterUnmount: [(app) => { console.log(`[主应用] ${app.name} 已卸载`); return Promise.resolve(); }]
});
setDefaultMountApp('/user');
start({ prefetch: true, sandbox: { strictStyleIsolation: false, experimentalStyleIsolation: true } });
子应用生命周期适配
子应用需要导出bootstrap、mount、unmount三个生命周期函数。以React子应用为例:
// user-app/src/main.tsx
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
let root: ReactDOM.Root | null = null;
function render(props: any) {
const { container } = props;
const mountNode = container ? container.querySelector('#root') : document.getElementById('root');
root = ReactDOM.createRoot(mountNode);
root.render(<App {...props} />);
}
export async function bootstrap() { console.log('[user-app] bootstrap'); }
export async function mount(props: any) {
console.log('[user-app] mount', props);
render(props);
}
export async function unmount(props: any) {
console.log('[user-app] unmount');
if (root) { root.unmount(); root = null; }
}
if (!(window as any).__POWERED_BY_QIANKUN__) { render({}); }
子应用Webpack配置需要设置打包格式为UMD,并允许跨域:
module.exports = {
output: { library: 'user-app', libraryTarget: 'umd', chunkLoadingGlobal: 'webpackJsonp_user_app' },
devServer: { headers: { 'Access-Control-Allow-Origin': '*' } }
};
JS沙箱与样式隔离机制
qiankun的JS沙箱有两种模式:LegacySandbox(单实例,通过Proxy劫持window对象)和ProxySandbox(多实例,每个子应用有独立的fakeWindow)。样式隔离通过experimentalStyleIsolation在子应用所有样式选择器前添加属性前缀:
/* 隔离前的子应用样式 */
.container { padding: 20px; }
.title { color: #333; }
/* 开启experimentalStyleIsolation后的编译结果 */
div[data-qiankun="user-app"] .container { padding: 20px; }
div[data-qiankun="user-app"] .title { color: #333; }
注意:experimentalStyleIsolation无法隔离通过JavaScript动态插入的样式(如styled-components运行时注入的style标签)。此类场景需要配合strictStyleIsolation使用Shadow DOM,但Shadow DOM会导致第三方组件库的弹窗appendToBody行为异常,需要逐个排查。
跨应用通信:initGlobalState
qiankun提供initGlobalState实现主应用与子应用的全局状态共享。通信基于发布订阅模式,主应用初始化GlobalState后通过props传递给子应用:
// 主应用 - 初始化全局状态
import { initGlobalState } from 'qiankun';
const actions = initGlobalState({ user: null, theme: 'light', locale: 'zh-CN' });
actions.onGlobalStateChange((state, prev) => {
console.log('[主应用] 全局状态变更:', state);
});
actions.setGlobalState({ user: { id: 1, name: '张三' }, theme: 'dark' });
// 子应用 - 接收并监听全局状态
export async function mount(props: any) {
props.onGlobalStateChange((state, prev) => {
console.log('[user-app] 收到全局状态:', state);
if (state.user) {
root?.render(<App user={state.user} theme={state.theme} />);
}
}, true);
props.setGlobalState({ theme: 'dark' });
render(props);
}
公共依赖提取与共享方案
多个子应用可能依赖相同的库(React、antd、lodash等),重复加载浪费带宽和内存。通过Webpack Module Federation或外部CDN加载实现共享:
// 方案一:externals + CDN
module.exports = {
externals: { 'react': 'React', 'react-dom': 'ReactDOM', 'antd': 'antd' }
};
// 主应用index.html引入CDN
<script src="https://cdn.example.com/react/18.2.0/react.production.min.js"></script>
// 方案二:Module Federation(Webpack 5+)
// 主应用 - Host
new ModuleFederationPlugin({
name: 'host',
shared: {
react: { singleton: true, requiredVersion: '^18.2.0' },
'react-dom': { singleton: true, requiredVersion: '^18.2.0' }
}
})
路由冲突与子应用路由适配
子应用使用BrowserRouter时,basename需要与主应用的activeRule一致,避免路由冲突:
import { BrowserRouter } from 'react-router-dom';
function App({ basename }: { basename?: string }) {
return (
<BrowserRouter basename={window.__POWERED_BY_QIANKUN__ ? '/user' : '/'}>
<Routes>
<Route path="/" element={<UserList />} />
<Route path="/detail/:id" element={<UserDetail />} />
<Route path="/settings" element={<UserSettings />} />
</Routes>
</BrowserRouter>
);
}
资源预加载与加载性能优化
qiankun默认开启prefetch,在浏览器空闲时间预加载未激活的子应用资源。对于体积较大的子应用,可以手动控制预加载策略:
start({
prefetch: (apps) => apps.filter(app => app.name === 'user-app'),
excludeAssetFilter: (assetUrl) => assetUrl.includes('.woff') || assetUrl.includes('.ttf')
});
// 按需加载
import { loadMicroApp } from 'qiankun';
const microApp = loadMicroApp({
name: 'dashboard-widget',
entry: '//localhost:7103',
container: '#widget-container',
props: { widgetId: 'sales-overview' }
});
microApp.unmount();
常见问题排查
子应用加载白屏:检查子应用entry是否可访问,CORS是否配置。浏览器Network面板查看子应用HTML和JS是否加载成功。若子应用资源路径错误,在Webpack配置output.publicPath为子应用完整URL。
样式被主应用覆盖:开启experimentalStyleIsolation。若使用antd等组件库,检查是否使用了CSS-in-JS方案导致样式未被子应用容器包裹。
子应用卸载后内存泄漏:确保unmount中清理了所有事件监听器、定时器和全局变量。检查React组件的useEffect cleanup是否完整执行。
微前端的拆分边界应基于业务域而非技术层。过细的拆分(按页面拆)会增加通信成本和加载开销。实际项目中,按业务模块拆分3-5个子应用是比较合理的粒度,配合公共依赖共享和路由预加载,可以兼顾开发效率和用户体验。
原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/wei-qian-duan-jia-gou-shi-zhan-qiankun-zi-ying-yong-ge-li/