1.啥是 Qiankun?Vue2 项目为啥选它做微前端?
现在很多团队里还跑着大量 Vue2 老项目,想做微前端拆分又不想推翻重构技术栈?Qiankun 是个主流选择,但 Vue2 项目接 Qiankun 可不是装个依赖就完事——从主应用配置、子应用改造到通信、样式、路由这些环节,处处藏着“小陷阱”,今天用问答形式,把 Vue2 + Qiankun 落地的关键问题拆碎了讲,帮你少走弯路。
Qiankun 是阿里系的微前端框架,基于 single - spa 封装,把“多应用聚合时咋管理生命周期、咋做 JS/CSS 隔离、咋加载子应用资源”这些复杂逻辑都封装好了,对比自己基于 single - spa 手写逻辑,Qiankun 文档全、生态成熟,对 Vue2 这种老技术栈特别友好——毕竟很多团队的 Vue2 项目还要持续迭代,用 Qiankun 改造成本低,不用把项目推翻重写,要是团队里 Vue2 项目多、想渐进式拆分成微应用,选它准没错。Vue2 主应用咋配置 Qiankun?
核心是注册子应用 + 启动 Qiankun + 处理路由匹配,分步骤看:- 装依赖:先在主应用里装 Qiankun,命令行跑
npm i qiankun -S
。 - 注册子应用:在主应用入口(
main.js
或者专门的microApp.js
)里,用registerMicroApps
方法,每个子应用配个对象,import { registerMicroApps, start } from 'qiankun';
registerMicroApps([ { name: 'vue2-subapp', // 子应用唯一标识,要和子应用打包名一致 entry: '//localhost:8081', // 子应用访问地址(本地/线上部署地址) container: '#subapp-container', // 主应用里挂载子应用的 DOM 容器 activeRule: '/subapp', // 访问主应用的 /subapp 时激活子应用 }, ]);
- <strong>启动 Qiankun</strong>:注册完子应用,调用 <code>start()</code> 启动,它会帮你处理子应用的加载、挂载逻辑。
- <strong>页面加挂载容器</strong>:主应用的页面(<code>App.vue</code>)里得有个 DOM 容器,像这样:
```vue
<template>
<div id="app">
<router-view></router-view> <!-- 主应用自己的路由 -->
<div id="subapp-container"></div> <!-- 子应用要挂载到这里 -->
</div>
</template>
路由匹配坑点:要是主应用用 hash 路由(路径长这样 /#/home
),子应用的 activeRule
得适配 hash,比如想让主应用访问 /#/subapp
时激活子应用,activeRule
得写成函数:
activeRule: (location) => location.hash.includes('/subapp')
要是主应用是 history 路由,子应用也用 history,那子应用的 router.base
得设成和 activeRule
对应的前缀(/subapp
),保证路由不冲突。
Vue2 子应用咋适配 Qiankun?
子应用得改生命周期导出 + 打包配置 + 路由/资源处理,逐个看:- 导出生命周期:Qiankun 要求子应用导出
bootstrap、mount、unmount
这 3 个生命周期函数,在子应用的main.js
里,原来直接new Vue
得改成这样:let instance = null; function render(props = {}) { const { container } = props; // 优先挂载到主应用给的 container,否则挂载到自己的 #app instance = new Vue({ router, store, render: h => h(App), }).$mount(container ? container.querySelector('#app') : '#app'); }
// 本地单独运行时(没被 Qiankun 加载),自己渲染 if (!window.POWERED_BY_QIANKUN) { render(); }
// 导出 Qiankun 需要的生命周期 export async function bootstrap() {} export async function mount(props) { render(props); // 挂载时把主应用给的 props 传进去 } export async function unmount() { instance.$destroy(); // 卸载时销毁 Vue 实例 instance.$el.innerHTML = ''; instance = null; }
- <strong>调整打包配置</strong>:子应用得打包成 <strong>umd 格式</strong>,让主应用能加载,要是用 <strong>vue - cli(webpack)</strong>,改 <code>vue.config.js</code>:
```js
module.exports = {
configureWebpack: {
output: {
library: 'vue2-subapp', // 和主应用注册时的 name 一致
libraryTarget: 'umd',
chunkLoadingGlobal: `webpackJsonp_vue2-subapp`, // 防止和主应用 chunk 名冲突
},
},
devServer: {
port: 8081, // 子应用端口
headers: {
'Access-Control-Allow-Origin': '*', // 允许主应用跨域加载
},
},
};
要是用 Vite 构建 Vue2 项目,改 vite.config.js
:
export default defineConfig({ base: '/subapp/', // 子应用基础路径,对应主应用 activeRule build: { rollupOptions: { output: { format: 'umd', name: 'vue2-subapp', }, }, }, server: { port: 8081, cors: true, // 允许跨域 }, });
-
资源路径处理:子应用的静态资源(图片、JS、CSS)得能被主应用正确加载,在子应用
main.js
里,动态设置publicPath
:if (window.__POWERED_BY_QIANKUN__) { // webpack 项目用这个 __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__; // Vite 项目用这个:import.meta.env.BASE_URL = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__; }
-
路由基础路径:子应用的
router
得设base
,和主应用的activeRule
对应,比如主应用激活路径是/subapp
,子应用router.js
里:const router = new VueRouter({ base: process.env.BASE_URL || '/subapp/', // 对应主应用的 activeRule mode: 'history', // 或 hash,看主应用配置 routes, });
跨应用通信在 Vue2 + Qiankun 里咋搞?
Qiankun 提供了initGlobalState
来管理全局状态,主应用和子应用都能订阅、修改。
- 主应用发消息 + 监听:
import { initGlobalState } from 'qiankun';
// 初始化全局状态 const globalState = { theme: 'light' }; const actions = initGlobalState(globalState);
// 主应用监听状态变化 actions.onGlobalStateChange((newState, oldState) => { console.log('主应用监听到状态变化:', newState, oldState); });
// 主应用修改状态 actions.setGlobalState({ theme: 'dark' });
- <strong>子应用订阅 + 改状态</strong>:在子应用的 <code>mount</code> 生命周期里,从 <code>props</code> 里拿到通信方法:
```js
export async function mount(props) {
const { onGlobalStateChange, setGlobalState } = props;
// 订阅主应用的状态变化
onGlobalStateChange((newState) => {
// 比如用 Vuex 存主题
store.commit('SET_THEME', newState.theme);
});
render(props); // 渲染子应用
}
// 子应用想改全局状态,直接调 setGlobalState
setGlobalState({ theme: 'light' });
要是团队里用 Vuex 或 Pinia,也能结合着用:主应用把全局状态的 actions
暴露给子应用,子应用调用;或者子应用内部用自己的状态管理,跨应用通信只用 Qiankun 的全局状态做“桥”。
样式冲突咋解决?
主要有Qiankun 沙箱 + 手动加前缀 + 动态加载卸载这几种思路:-
Qiankun CSS 沙箱:主应用启动时开实验性配置
experimentalStyleIsolation
,子应用的样式会自动加上属性选择器([data - qiankun=子应用 name]
),限制作用域,配置如下:start({ experimentalStyleIsolation: true, });
但要注意,子应用里复杂的嵌套样式可能受影响,得测试兼容性。
-
手动加样式前缀:用
postcss - plugin - namespace
给子应用所有样式加前缀,先装依赖npm i postcss - plugin - namespace - D
,然后改postcss.config.js
:module.exports = { plugins: { 'postcss-plugin-namespace': '.subapp-vue2', // 前缀类名 }, };
子应用的根元素(
App.vue
的最外层div
)得加上这个类名
,确保样式只作用于该容器内。... -
动态加载卸载样式:Qiankun 默认会处理子应用样式的加载(根据
entry
里的 CSS 资源),但如果有动态添加的样式(JS 里创建 ),得自己在子应用mount
时加载、unmount
时移除,避免污染其他应用。
路由冲突咋处理?
核心是让主、子应用路由“各司其职”,分场景讲:-
主、子都用 hash 路由:主应用路径是
/#/home
,子应用激活路径是/#/subapp
,子应用的activeRule
得匹配 hash,activeRule: (location) => location.hash.startsWith('#/subapp')
子应用的
router.base
设为'/subapp'
(对应 hash 里的路径)。 -
主、子都用 history 路由:主应用路径是
/home
,子应用激活路径是/subapp
,子应用的activeRule
设为'/subapp'
,router.base
也设为'/subapp'
,这样子应用内部路由是/subapp/page1
,主应用路由匹配不到,就不会拦截。 -
混合模式(主 hash、子 history):这种情况容易出问题,建议尽量统一路由模式,要是实在没法改,子应用的
activeRule
得匹配主应用的 hash 路径,router.base
对应,同时用 hash 模拟 history(比如子应用路由用mode: 'hash'
),减少复杂度。
主应用里要是有自己的路由守卫,得判断当前路径是否是子应用路径,跳过拦截逻辑,避免“主应用路由把子应用路径吞了”。
子应用打包和部署要注意啥?
- 打包格式:必须是 umd,否则主应用加载不了,不管用 Vue - cli 还是 Vite,都得改配置输出 umd(前面第 3 点讲过)。 - 资源路径:子应用的静态资源得能被主应用访问到,要是子应用独立部署在https://subapp.com
,主应用的 entry
就写这个地址;要是和主应用同域名下的子路径(比如主应用是 https://main.com
,子应用是 https://main.com/subapp/
),子应用的 publicPath
得设为 '/subapp/'
,保证打包后资源路径正确。
- 跨域问题:本地开发时,子应用的 devServer
要配 headers: { 'Access - Control - Allow - Origin': '*' }
允许跨域,线上部署要是主、子应用不同域,得配 CORS,或者用 nginx 反向代理到同一域下。
- 独立运行:子应用得支持单独打开(比如直接访问 http://localhost:8081
能正常运行),所以在 main.js
里要判断 window.__POWERED_BY_QIANKUN__
,没被 Qiankun 加载时就自己渲染。
Vue2 + Qiankun 实战常见坑有哪些?
踩过这些坑,才算真正懂了怎么结合~-
坑①:子应用 mount 后页面空白
现象:主应用能加载子应用,但页面一片空白。
原因:要么生命周期没正确导出(mount
里没调render
),要么挂载容器没找到(主应用的container
是#subapp - container
,但子应用里写的是container.querySelector('#app - wrong')
)。
解决:检查main.js
里的render
函数,确保容器选择器和主应用一致;确认bootstrap、mount、unmount
这几个函数都正确导出了。 -
坑②:样式隔离失效
现象:子应用样式把主应用样式冲了,或者反过来。
原因:Qiankun 沙箱没开(或实验性沙箱不兼容),或者手动加前缀时postcss
配置没生效。
解决:先试开experimentalStyleIsolation
,测试子应用样式是否被自动加属性选择器;要是用手动前缀,检查postcss.config.js
是否配置正确,构建时有没有触发postcss
插件。 -
坑③:路由跳转 404
现象:子应用内部路由跳转后页面 404。
原因:子应用router.base
和activeRule
不匹配,或者主应用路由拦截了子应用路径。
解决:确认子应用router.base
等于主应用的activeRule
(比如都是/subapp
);主应用路由守卫里,判断路径是子应用时跳过拦截。 -
坑④:全局变量污染
现象:子应用里定义的window.a
,把主应用的window.a
覆盖了,或者反过来。
原因:JS 沙箱没开(Qiankun 默认开了,但如果手动关了就会出问题),或者自己没封装命名空间。
解决:确认 Qiankun 的 JS 沙箱是开启状态(默认开启,不需要额外配置);要是必须用全局变量,给变量加命名空间,window.__MY_SUBAPP__ = { a: 1 }
。 -
坑⑤:资源加载 404
现象:子应用的图片、JS、CSS 加载失败,报 404。
原因:子应用的publicPath
没动态
版权声明
本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。
发表评论:
◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。