一、改造核心目标
将独立的 Vue 项目(Vue2/Vue3 均适用)改造为 qiankun 可识别的子应用,实现:
- 主应用通过域名/路径无感知嵌入子应用
- 主-子应用间通信、路由隔离
- 样式隔离、资源加载不冲突
- 支持独立运行(改造后仍可单独启动调试)
二、前置准备
环境要求:
- 主应用已集成 qiankun(参考 qiankun 官方文档)
- 子应用 Vue 版本:Vue2(需配合 vue-router 3.x)、Vue3(需配合 vue-router 4.x)
- 打包工具:Webpack(Vue CLI 默认)或 Vite(需额外配置)
关键概念:
- 子应用需暴露
bootstrap、mount、unmount三个生命周期钩子 - 路由需配置为基于主应用分配的基础路径(如主应用通过
/vue-app访问子应用,则子应用路由 base 为/vue-app) - 资源需支持跨域访问(主应用加载子应用静态资源时触发 CORS)
- 子应用需暴露
三、分步骤改造(Vue CLI 项目,Vue2/Vue3 通用)
步骤 1:安装 qiankun 依赖(子应用)
子应用需引入 qiankun 的辅助依赖,用于暴露生命周期:
# Vue2/Vue3 均适用
npm install qiankun --save
# 或 yarn add qiankun步骤 2:配置子应用入口文件(main.js)
核心是暴露 qiankun 要求的三个生命周期钩子,同时兼容独立运行模式。
Vue2 示例(main.js):
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
let instance = null;
let routerInstance = null;
// 初始化 Vue 实例(兼容独立运行和qiankun嵌入)
function render(props = {}) {
const { container, routerBase = '/' } = props; // 主应用传递的参数(容器、基础路径)
// 初始化路由(基于主应用传递的 base 路径)
routerInstance = new VueRouter({
mode: 'history', // 必须使用 history 模式(hash 模式需主应用特殊配置)
base: window.__POWERED_BY_QIANKUN__ ? routerBase : process.env.BASE_URL, // 独立运行时用默认 base
routes: router.options.routes // 复用原路由配置
});
// 挂载实例(容器优先用主应用传递的 container,独立运行时用 #app)
instance = new Vue({
router: routerInstance,
store,
render: h => h(App)
}).$mount(container ? container.querySelector('#app') : '#app');
}
// 独立运行时直接渲染
if (!window.__POWERED_BY_QIANKUN__) {
render();
}
// 1. bootstrap:子应用初始化(只执行一次)
export async function bootstrap() {
console.log('Vue2 子应用 bootstrap');
}
// 2. mount:子应用挂载(主应用切换到子应用时执行)
export async function mount(props) {
console.log('Vue2 子应用 mount,接收主应用参数:', props);
render(props); // 传入主应用参数渲染
}
// 3. unmount:子应用卸载(主应用切换离开子应用时执行)
export async function unmount() {
console.log('Vue2 子应用 unmount');
instance.$destroy(); // 销毁 Vue 实例
instance = null;
routerInstance = null;
}
// 可选:子应用更新(主应用触发子应用更新时执行)
export async function update(props) {
console.log('Vue2 子应用 update,接收更新参数:', props);
}Vue3 示例(main.js):
import { createApp } from 'vue'
import App from './App.vue'
import { createRouter, createWebHistory } from 'vue-router'
import routes from './router/routes' // 抽离的路由配置
import store from './store'
let app = null;
let router = null;
function render(props = {}) {
const { container, routerBase = '/' } = props;
// 初始化路由
router = createRouter({
history: createWebHistory(window.__POWERED_BY_QIANKUN__ ? routerBase : import.meta.env.BASE_URL),
routes
});
// 创建并挂载应用
app = createApp(App);
app.use(router);
app.use(store);
app.mount(container ? container.querySelector('#app') : '#app');
}
// 独立运行时直接渲染
if (!window.__POWERED_BY_QIANKUN__) {
render();
}
// 生命周期钩子
export async function bootstrap() {
console.log('Vue3 子应用 bootstrap');
}
export async function mount(props) {
console.log('Vue3 子应用 mount,接收主应用参数:', props);
render(props);
}
export async function unmount() {
console.log('Vue3 子应用 unmount');
app.unmount(); // 销毁 Vue3 应用
app = null;
router = null;
}
export async function update(props) {
console.log('Vue3 子应用 update,接收更新参数:', props);
}步骤 3:配置路由(router/index.js)
核心要求:路由模式必须为 history(hash 模式需主应用配置 supportHistory: false,不推荐),且 base 路径动态适配。
Vue2 路由示例(router/index.js):
import Vue from 'vue'
import VueRouter from 'vue-router'
import Home from '@/views/Home.vue'
import About from '@/views/About.vue'
Vue.use(VueRouter);
// 抽离路由规则(供 main.js 复用)
const routes = [
{ path: '/', name: 'Home', component: Home },
{ path: '/about', name: 'About', component: About }
];
// 独立运行时的路由实例(嵌入 qiankun 时会重新创建)
const router = new VueRouter({
mode: 'history',
base: process.env.BASE_URL,
routes
});
export default router;Vue3 路由示例(router/routes.js):
// 抽离路由规则
export default [
{ path: '/', name: 'Home', component: () => import('@/views/Home.vue') },
{ path: '/about', name: 'About', component: () => import('@/views/About.vue') }
];步骤 4:配置打包(vue.config.js)
关键配置:设置 publicPath(资源基础路径)、output(库模式打包)、devServer(跨域支持)。
Vue2/Vue3 通用 vue.config.js:
const { name } = require('./package.json');
module.exports = {
// 1. 资源基础路径:嵌入 qiankun 时,主应用需通过绝对路径加载子应用资源
publicPath: process.env.NODE_ENV === 'production'
? '//你的子应用线上域名/' // 生产环境:子应用部署后的绝对域名(如 https://sub-vue-app.example.com/)
: '//localhost:8081/', // 开发环境:子应用本地端口(避免和主应用端口冲突)
// 2. 打包配置:输出为库模式(qiankun 需加载 umd 格式的包)
configureWebpack: {
output: {
library: `${name}-[name]`,
libraryTarget: 'umd', // 关键:umd 格式支持全局引入和模块化引入
chunkLoadingGlobal: `webpackJsonp_${name}`, // 避免 chunk 命名冲突
},
},
// 3. 开发环境配置:允许主应用跨域访问(主应用地址需替换为实际地址)
devServer: {
port: 8081, // 子应用本地端口(不要和主应用冲突,如主应用 8080,子应用用 8081)
headers: {
'Access-Control-Allow-Origin': '*', // 开发环境允许所有跨域(生产环境需指定主应用域名)
'Access-Control-Allow-Methods': '*',
'Access-Control-Allow-Headers': '*',
},
},
};步骤 5:样式隔离配置
避免子应用样式污染主应用,或主应用样式影响子应用,两种方案可选:
方案 1:CSS Modules(推荐)
Vue 组件样式默认支持 scoped,但部分全局样式仍可能冲突,建议:
组件内样式统一加
scoped:<style scoped> /* 组件内样式,自动添加哈希前缀,避免冲突 */ .container { margin: 0; } </style>全局样式(如
main.css)添加唯一前缀(如vue-sub-app-):/* 全局样式前缀 */ .vue-sub-app-container { padding: 20px; } .vue-sub-app-btn { background: #42b983; }
方案 2:qiankun 自带样式隔离
主应用注册子应用时,配置 sandbox: { strictStyleIsolation: true },qiankun 会通过 Shadow DOM 隔离样式:
// 主应用注册子应用时的配置
import { registerMicroApps } from 'qiankun';
registerMicroApps([
{
name: 'vue-sub-app', // 子应用名称(需和子应用 package.json name 一致)
entry: '//localhost:8081', // 子应用入口(开发环境本地地址,生产环境线上域名)
container: '#sub-app-container', // 主应用中挂载子应用的容器
activeRule: '/vue-app', // 主应用访问子应用的路径(如主应用域名/vue-app 触发加载)
sandbox: {
strictStyleIsolation: true, // 开启样式隔离(Vue 子应用推荐开启)
},
props: {
routerBase: '/vue-app', // 传递给子应用的基础路径(需和 activeRule 一致)
// 其他需要传递的参数(如用户信息、全局配置)
userInfo: { name: 'test' },
},
},
]);步骤 6:主-子应用通信配置
qiankun 提供两种通信方式:props 传递(一次性初始化参数)和 initGlobalState(全局状态管理,支持双向通信)。
方式 1:props 传递(初始化参数)
主应用注册子应用时通过 props 传递参数,子应用在 mount 钩子中接收:
// 主应用注册时传递 props
registerMicroApps([
{
name: 'vue-sub-app',
entry: '//localhost:8081',
container: '#sub-app-container',
activeRule: '/vue-app',
props: {
routerBase: '/vue-app',
userInfo: { id: 1, name: '张三' },
onSubAppMessage: (msg) => { // 主应用接收子应用消息的回调
console.log('主应用收到子应用消息:', msg);
},
},
},
]);
// 子应用 mount 钩子中接收 props
export async function mount(props) {
console.log('子应用接收主应用参数:', props.userInfo);
// 子应用调用主应用回调传递消息
props.onSubAppMessage('子应用已加载完成');
render(props);
}方式 2:全局状态管理(双向通信)
适用于主-子应用需要实时同步状态的场景(如用户登录状态、主题切换):
主应用创建全局状态:
// 主应用 import { initGlobalState } from 'qiankun'; // 初始化全局状态 const initialState = { theme: 'light', userInfo: null, }; const actions = initGlobalState(initialState); // 主应用监听状态变化 actions.onGlobalStateChange((state, prev) => { console.log('主应用全局状态变化:', state, prev); }); // 主应用更新状态 actions.setGlobalState({ theme: 'dark' }); // 将 actions 传递给子应用(通过 props) registerMicroApps([ { name: 'vue-sub-app', entry: '//localhost:8081', container: '#sub-app-container', activeRule: '/vue-app', props: { routerBase: '/vue-app', globalStateActions: actions, // 传递全局状态操作方法 }, }, ]);子应用使用全局状态:
// 子应用 mount 钩子中接收并使用 export async function mount(props) { const { globalStateActions } = props; // 子应用监听全局状态变化 globalStateActions.onGlobalStateChange((state) => { console.log('子应用收到全局状态:', state.theme); // 可将状态同步到子应用的 Vuex/Pinia store.commit('setTheme', state.theme); }, true); // 第二个参数为 true,立即触发一次回调 // 子应用更新全局状态 globalStateActions.setGlobalState({ userInfo: { id: 1, name: '张三' } }); render(props); }
四、测试验证
1. 独立运行测试
子应用单独启动,访问 http://localhost:8081,确认路由、功能正常。
2. 嵌入主应用测试
- 启动主应用(如
http://localhost:8080) - 访问主应用的子应用路径(如
http://localhost:8080/vue-app) 验证:
- 子应用正常挂载,无样式冲突
- 子应用路由正常(如
/vue-app/about访问子应用的 About 页面) - 主-子应用通信正常
- 切换主应用其他页面时,子应用正常卸载
3. 生产环境测试
- 子应用打包:
npm run build,部署到线上服务器(如https://sub-vue-app.example.com) 主应用注册子应用时,将
entry改为线上域名:registerMicroApps([ { name: 'vue-sub-app', entry: 'https://sub-vue-app.example.com', // 生产环境入口 container: '#sub-app-container', activeRule: '/vue-app', props: { routerBase: '/vue-app' }, }, ]);- 访问主应用线上地址,验证子应用加载正常。
五、常见问题排查
1. 子应用加载失败:qiankun: application xxx died in status LOADING_SOURCE_CODE
- 原因:子应用资源跨域(生产环境未配置 CORS)
解决:生产环境子应用服务器配置
Access-Control-Allow-Origin为住应用域名(如 Nginx 配置):server { listen 80; server_name sub-vue-app.example.com; location / { root /path/to/vue-sub-app/dist; try_files $uri $uri/ /index.html; # 允许主应用域名跨域 add_header 'Access-Control-Allow-Origin' 'https://main-app.example.com'; add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS'; add_header 'Access-Control-Allow-Headers' 'DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type'; } }
2. 子应用路由跳转 404
原因:
- 子应用路由
base未配置为主应用传递的routerBase - 主应用未配置子应用路径的 fallback(刷新子应用页面时主应用路由拦截)
- 子应用路由
解决:
- 确认子应用
render函数中路由base正确使用props.routerBase 主应用路由添加 fallback 配置(以 Vue Router 为例):
// 主应用路由 const router = new VueRouter({ mode: 'history', routes: [ // 其他主应用路由 { path: '/vue-app/*', // 匹配子应用所有路径 component: () => import('@/views/SubAppContainer.vue'), // 主应用挂载子应用的容器组件 }, ], });
- 确认子应用
3. 样式冲突
- 原因:子应用全局样式未加前缀,或未开启 qiankun 样式隔离
解决:
- 全局样式添加唯一前缀
- 主应用注册子应用时开启
strictStyleIsolation: true
4. 子应用打包后资源路径错误
- 原因:
vue.config.js中publicPath配置错误(生产环境需为绝对域名) - 解决:生产环境
publicPath改为子应用线上绝对域名(如https://sub-vue-app.example.com/)
六、总结
Vue 项目接入 qiankun 的核心是:
- 暴露生命周期钩子(
bootstrap/mount/unmount),兼容独立运行和嵌入模式 - 路由
history模式 + 动态base路径适配 - 打包配置为 umd 格式 + 跨域支持
- 样式隔离 + 主-子通信配置
改造后,子应用既可以独立开发调试,也能无缝嵌入主应用,实现微前端架构的核心目标。如果是 Vite 构建的 Vue 项目,需额外配置 Vite 的 build.lib 和 server.headers,可参考 qiankun Vite 适配文档。
评论区(暂无评论)