12 min read

整合qiankun微前端框架心得笔记

由于项目需要,使用qiankun框架整合了三个系统,一个vue3+vite2的项目和两个vue2的项目,主应用是vue3+vite2的项目。 这次整合涉及到vue3、vite以及服务器部署,所以踩了很多坑,最终搞定了本地和测试环境的整合。

整合qiankun微前端框架心得笔记

本文主要讲整合思路和一些心得小结,而不仅仅是简单地讲操作步骤。然后列出一些要点,帮大家避坑。
建议最好先看qiankun官网对qiankun有一点了解再看本文,或者官网文档结合本文一起看。

接入qiankun框架前需要准备的工作

使用qiankun框架一般是用来整合PC端后台管理系统。在整合这些后台系统之前,你需要考虑和准备以下事情:

  1. 新开一个简单的项目作为主应用:主应用是用来放统一登录的页面,和各子应用入口的一个页面。首先是需要一个新项目做为qiankun框架的主应用,在其次是考虑到权限问题,不能拿任一子应用作为主应用入口,然后也方便开发和维护。
  2. 整合后系统登录方式:使用单点登录还是什么方式,这个需要根据自己的项目情况和项目需求决定。不做统一登录的话,整合起来后各个子系统还需要各自登录。
  3. 整合后服务器端部署方案:整合前可能几个项目部署在不同服务器,整合后需要考虑是不是放在同一台服务器。
  4. 路由的history模式最好统一:本文几个子系统的history模式统一使用的history

整合前主要是考虑和准备好这几点,可以在整合过程中少走弯路。这每一点又涉及到很多细节,需要在整合开发的过程去解决,比如子应用跟主应用的通信、构建打包等问题,需要看官方文档,然后根据自己项目现状去修改调整。

整合前后的变化

事项 整合前 整合后
用户信息 各子系统各自获取 主应用统一获取
登录退出 各子系统单独登录退出 主应用统一登录退出
服务器部署 部署在不同服务器,简单配置nginx 考虑是否部署在一台服务器,统一配置nginx
| history模式 有的hash,有的history 统一使用history(建议)

整合过程需要处理的疑难问题

把网上qiankun配置好的demo下载下来运行起来,感觉比较简单比较好配置,但是在现有项目上修改配置会发现完全不一样。以下是一些需要注意的事项:

  1. vite配置修改:应该是这些配置工作里最麻烦最需要仔细的,vite的qiankun全局变量名跟webpack的是不一样的。
  2. 路由配置修改:现有项目的路由守卫里面涉及到重定向页面的,在调通qiankun前可以暂时注释掉,不然会造成主应用页面白屏,子系统页面加载不出来。
  3. 生命周期:按照qiankun官网的文档,给每个子应用添加生命周期,记得要销毁。
  4. 应用通信:主应用登录完下发token给子应用,子应用超时需要通知主应用,以及子应用加载完成通知主应用等等,都需要主应用和子应用通信,按照官网文档使用。
  5. 碰到问题可以多看看官网的 常见问题 ,部署相关多看入门教程,不少问题最终都是根据官网文档解决的。

Vite2+Vue3主应用配置要点

main.js里的配置基本上按照官网文档来就好:

import { initGlobalState } from 'qiankun'
import storage from 'store'
import { ACCESS_TOKEN } from '@/store/mutation-types'
import { registerMicroApps, start } from 'qiankun'

const initialState = {
    // 这里可以写初始化数据
    project_id: '项目5',
    token: storage.get(ACCESS_TOKEN)||'',
    spinning: false
}
const actions = initGlobalState(initialState) //初始化state

// 监听actions全局公共状态数据的变化
actions.onGlobalStateChange((state, prevState) => {
    console.log("主应用变更前:", prevState);
    console.log("主应用变更后:", state);
})

function loader(loading) {
  console.log('loading',loading)
  if(loading){
    actions.setGlobalState({ spinning: loading });
  }
}

registerMicroApps([
  {
    name: 'app-aaa', // 与子应用打包配置里的一致
    entry: import.meta.env.DEV ? '//localhost:7100' : '/subapp/aaa/', //本地开发是localhost,部署到测试和生产环境是用后面的
    container: '#appContainer',
    activeRule: '/app-aaa',
    props: { actions },   //向子应用传递创建的全局状态
    loader
  },
  {
    name: 'app-bbb', // 与子应用打包配置里的一致
    entry: import.meta.env.DEV ? '//localhost:7200' : '/subapp/bbb/', //本地开发是localhost,部署到测试和生产环境是用后面的
    container: '#appContainer',
    activeRule: '/app-bbb',
    props: { actions },   //向子应用传递创建的全局状态
    loader
  }
])

start()

主应用配置要点:

  1. 在主应用里初始化应用和初始化通信;
  2. 测试环境entry名字,不能和activeRule一样,否则主应用页面刷新就变成微应用,官网有强调;
  3. 测试环境entry名字,是跟子应用构建里面的publicPath或者assetsPublicPath一致的,这里划重点;
  4. activeRule就是浏览器地址栏里的后半截路径;
  5. 如果主应用是vite2+vue3应用,vite配置文件不需要额外加output那些打包成umd的配置,不需要,加了打包会报错;
  6. 要把所有子应用里的proxy接口代理,拷到主应用里面,这样才能在主应用里访问到子应用里的接口。

Vite2+Vue3子应用配置要点

vite.config.ts文件需要增加的内容:

import qiankun from "vite-plugin-qiankun";

export default defineConfig({
  base: "/subapp/aaa/", // 与主应用里面测试环境的entry一致
  headers: {
    // 允许跨域访问
    "Access-Control-Allow-Origin": "*"
  },
  port: 7100, // 与主应用里注册的开发环境entry一致
  output: {
    // 把子应用打包成 umd 库格式
    library: `${name}-[name]`, // 我这里library名字跟package.json里的name不一样,也是可以的
    libraryTarget: "umd", // 重点,子应用要打包成umd格式
    jsonpFunction: `webpackJsonp_${name}`
  },
  plugins: [
    // 增加qiankun插件
    qiankun("app-aaa", { // app-aaa是与主应用main.ts里注册子应用的name一致
      useDevMode: true
    }),
  ]
})

src目录增加public-path.ts文件:

import { renderWithQiankun, qiankunWindow } from 'vite-plugin-qiankun/dist/helper';
if (qiankunWindow.__POWERED_BY_QIANKUN__) {
    // eslint-disable-next-line no-undef
    __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}

路由文件需要修改的内容:

import { qiankunWindow } from 'vite-plugin-qiankun/dist/helper';

export default createRouter({
  history: createWebHistory(
    qiankunWindow.__POWERED_BY_QIANKUN__ ? '/app-aaa/' : '/subapp/aaa/'
  ),
});

src目录增加action.ts文件:

function emptyAction() {
  // 警告:提示当前使用的是空 Action
  console.warn("Current execute action is empty!");
}

// 我们首先设置一个用于通信的Actions类
class Actions {
  actions = {
    onGlobalStateChange: emptyAction,
    setGlobalState: emptyAction
  }
  
  // 默认值为空Action
  constructor() {
  }

  // 设置actions
  setActions(actions) {
    console.log(actions)
    this.actions = actions
  }

  // 映射
  onGlobalStateChange(...args) {
    return this.actions.onGlobalStateChange(...args)
  }

  // 映射
  setGlobalState(...args) {
    return this.actions.setGlobalState(...args)
  }
}

const actions = new Actions()
export default actions

main.ts文件需要增加的内容:

import { renderWithQiankun, qiankunWindow } from 'vite-plugin-qiankun/dist/helper';
import actions from './action';

let instance: any;
let history: any;

declare global {
  interface Window {
    __POWERED_BY_QIANKUN__?: string;
  }
}

if (!qiankunWindow.__POWERED_BY_QIANKUN__) {
  // 独立访问时的代码
  instance = createApp(App)
  instance
    .use(router)
    .use(store)
  instance.mount('#subApp')
  // ... ...
} else {
  // 在qiankun内访问
  renderWithQiankun({
    mount(props) {
      history = createWebHistory(
        qiankunWindow.__POWERED_BY_QIANKUN__ ? '/app-aaa' : '/subapp/aaa'
      );
      instance = createApp(App)
      instance
        .use(router)
        .use(store)
      instance.mount(
        (props.container
          ? props.container.querySelector('#subApp')
          : document.getElementById('subApp')) as Element
      )
      if (props) {//子应用接收主应用值
        actions.setActions(props)
        actions.onGlobalStateChange((state) => {
          console.log("我是子应用,我检测到数据了:", state);
          if(!state.token){
              console.log('document.location.hostname', window.location.origin)
          }
        }, true);
      }
    },
    bootstrap() {
      console.log('--bootstrap')
    },
    update() {
      console.log('--update')
    },
    unmount() {
      instance.unmount()
      console.log('--unmount')
      console.log('instance',instance)
      console.log('history',history)

      instance._container.innerHTML = '';
      history.destroy();// 不卸载  router 会导致其他应用路由失败
      instance = null;
    },
  })
}

vite2+vue3子应用配置要点:

  1. 安装qiankun
  2. 安装vite-plugin-qiankun
  3. vite.config.ts文件里面增加baseportheaders跨域、outputqiankun插件配置;
  4. 注意qiankunWindow.__POWERED_BY_QIANKUN__里的变量名是qiankunWindowqiankunWindow相当于一个假的window对象,是作为子应用js代码执行时的全局变量,几个文件都用到这个,划重点;
  5. main.ts文件里增加判断是否是qiankunWindow.__POWERED_BY_QIANKUN__来分别写独立访问时和qiankun里访问时的代码;
  6. main.ts文件里的qiankun访问的代码是放在renderWithQiankun里的mount生命周期里面;
  7. 有两处路由相关的修改,在main.ts和路由文件里。
  8. 路由文件和vite.config.ts里面的/subapp/aaa/,和主应用main.ts里注册子应用的entry是一致的。

Vue2子应用配置要点

vue.config.js增加的配置:

  devServer: {
    port: 7200, // 与主应用里注册的开发环境entry一致
    headers: {
      'Access-Control-Allow-Origin': '*'
    }
  }
  publicPath: process.env.NODE_ENV === 'development' ? '/' : '/subapp/bbb/', // 增加了判断,生产环境的路径跟主应用里一致
  output: {
    library: `${name}-[name]`,
    libraryTarget: 'umd', // 把微应用打包成 umd 库格式
    jsonpFunction: `webpackJsonp_${name}`
  }

src目录增加public-path.js文件:

if (window.__POWERED_BY_QIANKUN__) {
    // eslint-disable-next-line no-undef
    __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__
}

路由文件需要修改的内容:

export default new Router({
  mode: 'history',
  base: window.__POWERED_BY_QIANKUN__ ? '/app-bbb' : '/',
})

src目录增加action.js文件,与上面子应用的一样:

function emptyAction() {
  // 警告:提示当前使用的是空 Action
  console.warn("Current execute action is empty!");
}

// 我们首先设置一个用于通信的Actions类
class Actions {
  actions = {
    onGlobalStateChange: emptyAction,
    setGlobalState: emptyAction
  }
  
  // 默认值为空Action
  constructor() {
  }

  // 设置actions
  setActions(actions) {
    console.log(actions)
    this.actions = actions
  }

  // 映射
  onGlobalStateChange(...args) {
    return this.actions.onGlobalStateChange(...args)
  }

  // 映射
  setGlobalState(...args) {
    return this.actions.setGlobalState(...args)
  }
}

const actions = new Actions()
export default actions

main.js文件需要增加的内容:

import router from './router'
import store from './store'
import actions from './action'

let instance = null
function render(props = {}) {
  const { container } = props
  instance = new Vue({
    router,
    store,
    render: h => h(App)
  }).$mount(container ? container.querySelector('#subApp') : '#subApp')
}

if (!window.__POWERED_BY_QIANKUN__) {
  render()
}

export async function bootstrap () {
  console.log('[vue] vue app bootstraped')
}

export async function mount(props) {
  console.log('receive props', props)
  // Vue.prototype.$qiankun=props
  if (props) { // 子应用接收主应用值
    actions.setActions(props)
    actions.onGlobalStateChange((state) => {
      console.log('我是子应用bbb,我检测到数据了:', state)
    }, true)
  }
  render(props)
}

export async function unmount() {
  instance.$destroy()
  instance.$el.innerHTML = ''
  instance = null
}

vue2子应用配置要点:

  1. 不需要安装什么,整体配置相对比较简单;
  2. vue.config.js文件里面修改publicPath配置,如上面代码加上开发环境判断;
  3. vue.config.js文件里面在devServer下增加portheaders跨域配置;
  4. vue.config.js文件里面在configureWebpack下增加output配置;
  5. 注意window.__POWERED_BY_QIANKUN__里的变量名是window
  6. main.js文件里基本按照官网来修改就可以,router可以使用你现有的,然后在路由文件里修改就好。
  7. vue.config.js里面的/subapp/bbb/,和主应用main.ts里注册子应用的entry是一致的。
  8. 路由文件里面的app-bbb,和主应用main.ts里注册子应用的name是一致的。

部署的nginx配置:

基本上按照官网的配置来的,官网写的很详细了,按官网配置没错。这个配置是主应用跟子应用部署在同一台服务器的配置,适用于测试和生产环境。

server{  
  listen 80;  
  server_name yourdomain.com;
  add_header Access-Control-Allow-Origin *;
  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,Authorization';
  
  #lijian
  location / {
    root /www/html/app;
    index index.html;
    try_files $uri $uri/ /index.html;
  }

  location /admin111 {
    proxy_pass http://111.111.111.111:8888;
  }

  location /admin222 {
    proxy_pass http://222.222.222.222:8888;
  }

}
...
└── app/                 # 主应用的目录(/www/html/app)
    ├── css/             # 主应用的css文件夹
    ├── js/              # 主应用的js文件夹
    ├── index.html       # 主应用的index.html
    ├── subapp/          # 存放所有子应用的目录
    |     ├── aaa/       # 存放子应用 aaa 的目录
    |     ├── bbb/       # 存放子应用 bbb 的目录
...

nginx配置说明:

  1. 这是主应用和子应用都部署在同一台服务器的基本配置,划重点;
  2. 注意上面配置,这里只需要配置主应用的目录就好,服务器/www/html/app目录下放主应用的页面文件。
  3. 需要在服务器/www/html/app目录下建立subapp目录,subapp目录是与本文上面的保持一致的。
  4. 子应用aaa在服务器/www/html/app/subapp下面建立aaa目录,把构建好的页面放进去。
  5. 子应用bbb同理,在服务器/www/html/app/subapp下面建立bbb目录,把构建好的页面放进去。
  6. 这里的subapp目录,以及subapp下面建立的aaabbb两个目录,是跟本文上面主应用和子应用配置里的/subapp/aaa//subapp/bbb/一致的,重点重点重点。
  7. 之前本地开发要把各个子应用里面配置的代理拷贝到主应用里面去,上到测试或者生产环境则需要nginx里面做转发,如上面配置,ip可以换成域名。

心得和总结

  1. 基于vite的vue3应用在本地开发和测试生产都是可以搞定的,不用把vite替换换成webpack(网上见有替换的)。
  2. 基于vite的应用需要安装qiankun插件,变量名不一样。
  3. 基于vue2的应用接入qiankun相对简单得多。
  4. 本地开发和测试生产的entrypublicPath等配置不一样,需要像本文一样做是不是本地开发环境的判断。
  5. Vue2项目有的资源路径名是publicPath,有的是assetsPublicPath,根据实际项目来。
  6. 整合qiankun最花时间的不是qiankun本身,是各个系统的登录改造,以及不断试错尝试,本文是这样。
  7. 碰到问题多看官网的 常见问题 ,部署相关的问题多看入门教程,特别是部署相关的,根据官网文档来没错。
  8. 一定要仔细再仔细,耐心再耐心,不断地试错。

以上就是整合qiankun的心得总结,如果有错误和不完善的地方,欢迎指正。

转载说明

原文标题:整合qiankun微前端框架的心得笔记
原文地址:hanhan.pro/using-qiankun-micro-frontends-solution/
原文作者:xiaohan
转载请注明出处