Code前端首页关于Code前端联系我们

1.啥是 Qiankun?Vue2 项目为啥选它做微前端?

terry 1天前 阅读数 13 #Vue
文章标签 Qiankun;微前端

现在很多团队里还跑着大量 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.baseactiveRule 不匹配,或者主应用路由拦截了子应用路径。
    解决:确认子应用 router.base 等于主应用的 activeRule(比如都是 /subapp);主应用路由守卫里,判断路径是子应用时跳过拦截。

  • 坑④:全局变量污染
    现象:子应用里定义的 window.a,把主应用的 window.a 覆盖了,或者反过来。
    原因:JS 沙箱没开(Qiankun 默认开了,但如果手动关了就会出问题),或者自己没封装命名空间。
    解决:确认 Qiankun 的 JS 沙箱是开启状态(默认开启,不需要额外配置);要是必须用全局变量,给变量加命名空间,window.__MY_SUBAPP__ = { a: 1 }

  • 坑⑤:资源加载 404
    现象:子应用的图片、JS、CSS 加载失败,报 404。
    原因:子应用的 publicPath 没动态

版权声明

本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。

发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

热门