qiankun 微前端框架的使用。内容将分为以下几个核心部分:

  1. qiankun 是什么?
  2. 主应用(Master Application)的配置与常用 API
  3. 微应用(Micro Application / Sub-Application)的配置
  4. 主应用与微应用间的通信方法
  5. CSS 样式隔离的方式
  6. 常见问题与注意事项

1. qiankun 是什么?

qiankun 是一个基于 single-spa 的微前端实现库。它旨在帮助大家能更简单、无痛地构建一个生产可用的微前端架构系统。

  • 核心思想:将一个大型的单页应用(SPA)拆分为多个更小、更简单的“微应用”。这些微应用可以独立开发、独立部署、独立运行,最后由主应用统一协调和集成。
  • 技术栈无关:主应用和微应用可以使用不同的前端框架(React, Vue, Angular, jQuery等)。

2. 主应用(Master Application)的配置与常用 API

主应用负责注册、加载、卸载微应用,并提供公共的依赖和通信机制。

安装
npm install qiankun -S
# 或
yarn add qiankun
常用 API 与配置
  1. registerMicroApps(apps, lifeCycles?) - 核心API,注册微应用
    这是最常用的方式,用于注册一组微应用。当浏览器的 URL 发生变化时,它会自动匹配并加载对应的微应用。

    // main.js (主应用入口文件)
    import { registerMicroApps, start } from 'qiankun';
    
    registerMicroApps([
      {
        name: 'vue-app', // 微应用名称,唯一标识
        entry: '//localhost:7101', // 微应用的入口地址(开发时是dev-server地址,生产时是部署后的URL)
        container: '#subapp-viewport', // 微应用挂载到主应用的哪个DOM节点上
        activeRule: '/vue', // 激活规则,当URL以/vue开头时,加载这个微应用
        // 可选,传递给微应用的 props,用于通信
        props: {
          token: 'main-app-token',
          onGlobalStateChange: mainAppGlobalStateChange,
          setGlobalState: mainAppSetGlobalState,
        }
      },
      {
        name: 'react-app',
        entry: '//localhost:7102',
        container: '#subapp-viewport',
        activeRule: '/react',
        props: { ... }
      },
    ], {
      // 可选的全局生命周期钩子
      beforeLoad: (app) => { console.log('before load', app.name); },
      beforeMount: (app) => { console.log('before mount', app.name); },
      afterMount: (app) => { console.log('after mount', app.name); },
      beforeUnmount: (app) => { console.log('before unmount', app.name); },
      afterUnmount: (app) => { console.log('after unmount', app.name); },
    });
    
    // 启动 qiankun
    start();
    
  2. start(options?) - 启动 qiankun
    在注册完微应用后,调用 start() 方法来启动 qiankun。

    start({
      sandbox: { strictStyleIsolation: true }, // 开启严格的样式隔离
      prefetch: 'all', // 预加载策略:'all' | '[]' | function
      singular: true, // 是否为单实例场景(同一时间只加载一个微应用)
    });
    
  3. setDefaultMountApp(appLink) - 设置默认进入的微应用
    用于指定主应用启动后默认进入哪个微应用。

    import { setDefaultMountApp } from 'qiankun';
    // 当主应用启动后,默认会跳转到 /vue 路由,从而加载 vue-app
    setDefaultMountApp('/vue');
    
  4. runAfterFirstMounted(effect) - 第一个微应用挂载后的回调
    常用于一些在微应用加载后才需要执行的逻辑,比如监控。

    runAfterFirstMounted(() => {
      console.log('第一个微应用已挂载');
    });
    
  5. loadMicroApp(app, configuration?) - 手动加载微应用
    适用于需要更精细控制微应用加载/卸载的场景(例如一个页面同时运行多个微应用),它返回一个 microApp 对象用于后续的卸载。

    import { loadMicroApp } from 'qiankun';
    
    // 在某个组件内
    this.microApp = loadMicroApp({
      name: 'vue-app',
      entry: '//localhost:7101',
      container: '#vue-container',
      props: { ... }
    });
    
    // 在组件卸载时,手动卸载微应用
    // this.microApp.unmount();
    

3. 微应用(Micro Application)的配置

微应用不需要安装 qiankun,但需要暴露三个生命周期钩子函数(bootstrap, mount, unmount)供主应用在适当的时机调用。

对于使用 webpack 构建的微应用(以 Vue/React 为例)
  1. 导出生命周期钩子

    // main.js (微应用的入口文件)
    import Vue from 'vue';
    import App from './App.vue';
    import router from './router';
    
    let instance = null;
    
    function render(props = {}) {
      const { container } = props;
      instance = new Vue({
        router,
        render: h => h(App),
      }).$mount(container ? container.querySelector('#app') : '#app'); // 重要:使用主应用传来的 container
    }
    
    // 独立运行时,直接渲染
    if (!window.__POWERED_BY_QIANKUN__) {
      render();
    }
    
    // 导出 qiankun 需要的三个生命周期函数
    export async function bootstrap() {
      console.log('[vue] vue app bootstraped');
    }
    
    export async function mount(props) {
      console.log('[vue] props from main framework', props);
      // 存储从主应用传来的方法,用于通信
      Vue.prototype.$onGlobalStateChange = props.onGlobalStateChange;
      Vue.prototype.$setGlobalState = props.setGlobalState;
      render(props);
    }
    
    export async function unmount() {
      instance.$destroy();
      instance.$el.innerHTML = '';
      instance = null;
    }
    
  2. 配置 webpack (vue.config.js)
    必须跨域,并且输出格式为 umd

    const { defineConfig } = require('@vue/cli-service')
    const packageName = require('./package.json').name;
    
    module.exports = defineConfig({
      transpileDependencies: true,
      devServer: {
        port: 7101, // 确保端口与主应用注册的entry一致
        headers: {
          'Access-Control-Allow-Origin': '*', // 允许主应用跨域访问
        },
      },
      configureWebpack: {
        output: {
          library: `${packageName}-[name]`,
          libraryTarget: 'umd', // 将微应用的 library 打包为 umd 格式
          jsonpFunction: `webpackJsonp_${packageName}`, // webpack 5 以下需要,解决 webpackJsonp 冲突问题
          // chunkLoadingGlobal: `webpackJsonp_${packageName}`, // webpack 5 使用这个
        },
      },
    });
    

4. 主应用与微应用间的通信方法

qiankun 官方提供了两种通信方式:

方式一:基于 props 的通信(官方 Actions 通信)

这是最推荐的方式。主应用通过 registerMicroAppsloadMicroAppprops 参数向下传递数据和方法。

  1. 主应用初始化状态并传递方法

    // main-app/src/shared/actions.js
    import { initGlobalState } from 'qiankun';
    
    const initialState = {
      user: { name: 'Main App' },
      token: 'abc123',
    };
    
    const actions = initGlobalState(initialState);
    
    // 监听状态变化
    actions.onGlobalStateChange((state, prevState) => {
      // state: 变更后的状态;prevState: 变更前的状态
      console.log('主应用监听到状态变化: ', state, prevState);
    });
    
    // 封装一个设置状态的方法,传递给微应用
    export const setGlobalState = (state) => {
      // 按一级属性设置状态,微应用只能修改已存在的一级属性
      actions.setGlobalState(state);
    };
    
    export default actions;
    
    // 在主应用注册微应用时传入
    import actions from './shared/actions';
    
    registerMicroApps([
      {
        name: 'vue-app',
        entry: '//localhost:7101',
        container: '#subapp-viewport',
        activeRule: '/vue',
        props: {
          // 传递数据和通信方法
          onGlobalStateChange: actions.onGlobalStateChange,
          setGlobalState: actions.setGlobalState,
          ...initialState // 传递初始状态
        }
      },
    ]);
    
  2. 微应用使用状态和方法

    // 在微应用的 mount 生命周期中接收
    export async function mount(props) {
      console.log('从主应用传来的数据: ', props.user, props.token);
    
      // 监听全局状态变化
      props.onGlobalStateChange((state, prev) => {
        console.log('微应用监听到状态变化: ', state, prev);
        // 更新微应用自身的状态,例如 Vue 的 data 或 React 的 state
      }, true); // true 表示立即执行一次
    
      // 更新全局状态
      props.setGlobalState({
        user: { ...props.user, name: 'Vue App Modified' },
      });
    }
    
方式二:自定义事件(Event Bus)

你可以使用任何你熟悉的事件库(如 mitt, EventEmitter)来实现一个简单的发布-订阅模式。

  1. 在主应用中创建 Event Bus 并导出
  2. 通过 props$on, $emit 等方法传递给微应用
  3. 主应用和微应用分别进行事件的监听和触发

这种方式更灵活,但需要自己维护事件机制。

// 主应用派发事件
window.dispatchEvent(new CustomEvent('main-event', {
  detail: { type: 'NOTIFY', payload: 'Hello' }
}));

// 子应用监听事件
window.addEventListener('main-event', (e) => {
  console.log('收到事件:', e.detail);
});

5. CSS 样式隔离的方式

qiankun 提供了两种可选的样式隔离方案,在 start 函数中配置。

  1. strictStyleIsolation (严格样式隔离)

    • 原理:通过 Shadow DOM 将每个微应用包裹起来,实现真正的 DOM 隔离。
    • 配置start({ sandbox: { strictStyleIsolation: true } })
    • 优点:隔离性最强。
    • 缺点:微应用内的弹窗(Modal, Drawer)等需要挂载到 body 下的组件,会因为不在 Shadow DOM 内而丢失样式。需要额外处理。
  2. experimentalStyleIsolation (实验性样式隔离)

    • 原理:qiankun 会动态地为微应用的所有样式选择器添加一个特殊的前缀(例如 div[name='vue-app']),将其限制在微应用的容器内。
    • 配置start({ sandbox: { experimentalStyleIsolation: true } })
    • 优点:解决了大部分样式冲突问题,且对弹窗等组件更友好。
    • 缺点:不是 100% 隔离,对于动态插入的样式(如 style 标签、第三方UI库的样式)可能处理不完美。

最佳实践

  • 大部分场景下,使用 experimentalStyleIsolation 即可。
  • 对于UI库的样式,尽量使用按需引入,避免引入全量全局样式。
  • 微应用自身的样式,使用 CSS ModulesScoped CSS(Vue)来避免样式污染。
  • 如果主应用和微应用使用了不同的 UI 库(如主应用用 Antd,微应用用 Element),需要注意其全局样式类名(如 .btn)可能冲突,需要为其中一方或双方添加命名空间。

6. 常见问题与注意事项

  1. 路由问题

    • 主应用和微应用的路由模式(hash / history)可以混用,但通常建议统一。
    • 微应用需要设置 base 路径(例如 Vue Router 的 base 选项),使其路由能在主应用的活动规则下正常工作。
  2. 公共依赖(如 Vue, React)

    • 可以通过 webpackexternals 将公共库从打包文件中排除,由主应用统一提供(html-entry 方式),减少体积。
    • 也可以不排除,qiankun 的沙箱可以避免全局污染,但会重复加载。
  3. 动态样式丢失: 在使用 strictStyleIsolation 时,挂载到 document.body 的组件会丢失样式,需要将样式插入到 Shadow DOM 外部的 document.head 中。

  4. 开发环境跨域: 微应用的 dev-server 必须设置 headers: { 'Access-Control-Allow-Origin': '*' }

  5. 生命周期函数: 微应用导出的生命周期函数必须是 Promise 或者 async 函数。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐