微前端(Micro Frontend)将微服务理念延伸到前端领域,将大型单体前端应用拆分为多个独立开发、独立部署、独立运行的子应用。当单体前端项目代码量超过50万行、构建时间超过10分钟、多团队协作冲突频繁时,微前端架构能有效解决这些问题。qiankun是蚂蚁集团开源的微前端框架,基于Single-SPA实现,提供JS沙箱、CSS隔离、预加载等开箱即用的能力。
qiankun微前端架构设计
qiankun采用主子应用模式:主应用(Container)负责注册子应用、管理路由切换和公共依赖;子应用独立开发部署,通过导出生命周期函数接入主应用。主应用加载子应用时动态拉取子应用的HTML入口,执行JS并挂载到指定容器:
// 主应用:注册子应用
import { registerMicroApps, start } from 'qiankun';
// 子应用配置
const microApps = [
{
name: 'user-center',
entry: '//localhost:8081',
container: '#sub-app-container',
activeRule: '/user',
props: {
token: localStorage.getItem('token'),
apiBase: '/api/v1'
}
},
{
name: 'order-system',
entry: '//localhost:8082',
container: '#sub-app-container',
activeRule: '/order',
props: {
token: localStorage.getItem('token'),
apiBase: '/api/v1'
}
},
{
name: 'dashboard',
entry: '//localhost:8083',
container: '#sub-app-container',
activeRule: '/dashboard'
}
];
registerMicroApps(microApps, {
beforeLoad: [
(app) => {
console.log(`${app.name} 开始加载`);
// 可在此处添加loading状态
}
],
beforeMount: [
(app) => {
console.log(`${app.name} 开始挂载`);
}
],
afterUnmount: [
(app) => {
console.log(`${app.name} 已卸载`);
// 清理全局变量、事件监听等
}
]
});
// 启动微前端,开启预加载和沙箱
start({
prefetch: true, // 预加载子应用资源
sandbox: {
strictStyleIsolation: false, // 严格样式隔离(Shadow DOM)
experimentalStyleIsolation: true // 实验性样式隔离(Scoped CSS)
},
singular: true // 同时只激活一个子应用
});
子应用接入与生命周期导出
子应用需导出bootstrap、mount、unmount三个生命周期函数。以Vue3子应用为例,需配置webpack的publicPath和UMD导出:
// 子应用入口文件 main.js
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
let app = null;
// qiankun生命周期导出
export async function bootstrap(props) {
console.log('子应用启动', props);
// 可通过props接收主应用传入的数据
}
export async function mount(props) {
app = createApp(App);
app.use(router);
app.use(store);
// 将主应用传入的props挂载到全局
if (props.token) {
store.commit('setToken', props.token);
}
if (props.apiBase) {
store.commit('setApiBase', props.apiBase);
}
app.mount(props.container
? props.container.querySelector('#app')
: '#app');
}
export async function unmount(props) {
app.unmount();
app = null;
// 清理副作用:定时器、全局事件、WebSocket等
}
// 非qiankun环境独立运行
if (!window.__POWERED_BY_QIANKUN__) {
createApp(App).use(router).use(store).mount('#app');
}
子应用webpack配置需添加publicPath动态设置和UMD导出:
// vue.config.js 或 webpack.config.js
const { name } = require('./package.json');
module.exports = {
configureWebpack: {
output: {
library: `${name}-[name]`,
libraryTarget: 'umd',
jsonpFunction: `webpackJsonp_${name}`
},
devServer: {
port: 8081,
headers: {
'Access-Control-Allow-Origin': '*' // 允许主应用跨域加载
}
}
}
};
// 入口文件头部动态设置publicPath
if (window.__POWERED_BY_QIANKUN__) {
__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}
主子应用通信机制
qiankun提供initGlobalState API实现应用间通信,基于发布订阅模式。主应用创建全局状态,子应用通过props.onGlobalStateChange监听变化,通过props.setGlobalState修改状态:
// 主应用:初始化全局状态
import { initGlobalState } from 'qiankun';
const initialState = {
user: null,
theme: 'light',
language: 'zh-CN'
};
const actions = initGlobalState(initialState);
// 监听子应用的状态变更
actions.onGlobalStateChange((state, prev) => {
console.log('全局状态变更:', state);
if (state.theme) {
document.documentElement.setAttribute('data-theme', state.theme);
}
});
// 主应用主动设置状态
actions.setGlobalState({
user: { id: 1, name: 'admin', role: 'super' }
});
// 将actions传入子应用
registerMicroApps([{
name: 'user-center',
entry: '//localhost:8081',
container: '#sub-app-container',
activeRule: '/user',
props: {
// 通信方法通过props传入
onGlobalStateChange: actions.onGlobalStateChange,
setGlobalState: actions.setGlobalState
}
}]);
// 子应用:接收并使用通信方法
export async function mount(props) {
// 监听全局状态变化
props.onGlobalStateChange((state) => {
console.log('收到主应用状态:', state);
if (state.user) {
store.commit('setUser', state.user);
}
});
// 子应用修改全局状态
props.setGlobalState({ theme: 'dark' });
app.mount(props.container.querySelector('#app'));
}
Webpack Module Federation方案
Webpack 5原生支持的Module Federation提供了另一种微前端方案。与qiankun不同,Module Federation在构建时声明模块暴露和消费关系,运行时动态加载远程模块,无需HTML入口文件注入:
// 模块提供方 webpack.config.js
const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin');
module.exports = {
plugins: [
new ModuleFederationPlugin({
name: 'userApp',
filename: 'remoteEntry.js',
exposes: {
// 暴露的组件
'./UserList': './src/components/UserList.vue',
'./UserDetail': './src/components/UserDetail.vue',
'./store': './src/store/index.js'
},
shared: {
vue: { singleton: true, requiredVersion: '^3.3.0' },
'vue-router': { singleton: true }
}
})
]
};
// 模块消费方 webpack.config.js
new ModuleFederationPlugin({
name: 'host',
remotes: {
userApp: 'userApp@http://localhost:8081/remoteEntry.js',
orderApp: 'orderApp@http://localhost:8082/remoteEntry.js'
},
shared: {
vue: { singleton: true, requiredVersion: '^3.3.0' }
}
})
// 消费方动态加载远程组件
const UserList = defineAsyncComponent(() =>
import('userApp/UserList')
);
Module Federation的优势在于共享依赖(shared配置),多个子应用引用同一版本Vue、React等库时不会重复打包。但Module Federation要求所有应用使用Webpack 5+,且不支持运行时沙箱隔离,样式冲突需自行处理。
样式隔离与资源预加载
qiankun提供两种样式隔离策略。严格样式隔离使用Shadow DOM将子应用包裹在独立Shadow Root中,完全隔离CSS但不影响全局样式。实验性样式隔离通过给子应用CSS选择器添加scoped前缀实现近似隔离:
// start配置启用样式隔离
start({
sandbox: {
// 方案一:Shadow DOM完全隔离
// strictStyleIsolation: true,
// 问题:第三方UI库(Ant Design等)可能因Shadow DOM
// 无法获取全局样式而样式异常
// 方案二:Scoped CSS(推荐)
experimentalStyleIsolation: true
// 原理:给子应用所有CSS选择器添加 div[data-qiankun="app-name"] 前缀
// 效果:.ant-btn { } => div[data-qiankun="user-center"] .ant-btn { }
},
prefetch: {
// 预加载策略:首次访问子应用时预加载其他子应用静态资源
getPublicPath: (entry) => {
return entry.replace(/\/[^/]*$/, '/');
}
}
});
// 处理全局样式冲突的实践
// 子应用在mount时注入全局样式,unmount时移除
export async function mount(props) {
const styleEl = document.createElement('style');
styleEl.setAttribute('data-app', 'user-center');
styleEl.textContent = `
body { margin: 0; font-family: 'PingFang SC', sans-serif; }
`;
document.head.appendChild(styleEl);
app.mount(props.container.querySelector('#app'));
}
export async function unmount() {
// 清理子应用注入的全局样式
document.querySelectorAll('style[data-app="user-center"]').forEach(el => el.remove());
app.unmount();
}
部署架构与Nginx配置
生产部署中主应用和子应用分别独立部署到不同域名或路径。Nginx需配置CORS允许主应用跨域加载子应用资源:
# Nginx配置:子应用静态资源服务
server {
listen 8081;
server_name user-app.internal;
# 允许主应用域名跨域访问
add_header Access-Control-Allow-Origin "https://portal.yunthe.com";
add_header Access-Control-Allow-Methods "GET, OPTIONS";
add_header Access-Control-Allow-Headers "Origin, Content-Type, Accept";
location / {
root /usr/share/nginx/html/user-app;
try_files $uri $uri/ /index.html;
# 静态资源缓存
location ~* \.(js|css|png|jpg|svg)$ {
expires 30d;
add_header Cache-Control "public, immutable";
}
}
}
# 主应用配置
server {
listen 443 ssl;
server_name portal.yunthe.com;
location / {
root /usr/share/nginx/html/portal;
try_files $uri $uri/ /index.html;
}
# API代理
location /api/ {
proxy_pass http://backend-cluster;
}
}
qiankun和Module Federation各有适用场景。qiankun适合技术栈异构(Vue+React混用)、独立部署要求强、样式隔离要求高的场景。Module Federation适合同技术栈(全Vue或全React)、构建时集成、共享依赖优化的场景。大型项目可采用混合方案:qiankun管理应用级隔离和路由,Module Federation处理跨应用的组件级共享。
原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/wei-qian-duan-jia-gou-shi-zhan-qiankun-mo-kuai-lian-bang-yu/