likes
comments
collection
share

『微前端』qiankun的初体验

作者站长头像
站长
· 阅读数 7

目前在做的项目,涉及到了多个管理端的系统交互,随着需求的迭代升级,有部分老的代码面临着重构优化的窘态,但是新的需求也一直在来。同时也为了让用户能有更好的体验感,发挥单页面应用的优点,因此有了将项目改造成微前端的想法。现有的项目多为webpack+vue2vite+vue3的项目,将其改造成微应用,再使用vite+Vue3搭建一个主应用来注册上述改造后的微应用,迁移所有的权限控制相关的功能到主应用,由主应用统一进行权限控制。

一、到底要不要用微前端?

当考虑微前端时,面临着一个最前提的条件,我们的项目需不需要用微前端,或者说适合不适合使用微前端?然后便看到了这篇文章《你可能并不需要微前端》,另外就是要先弄清楚微前端的优点和难点。

  • 优点
    • 解耦,模块化开发
    • 技术栈无关
    • 并行开发
    • 独立部署
  • 难点
    • 性能问题
    • 技术复杂度问题
    • 安全性问题

二、qiankun2.Xvite的“情感纠葛”

目前qiankun2.X官方并不支持vite的构建

1. 存在问题

  • vite的模块加载方式是esm,而qiankun并不支持在非module script标签内解析ems格式的代码,导致子应用无法正确加载。
  • 对于 react-refresh 需要全局变量 __vite_plugin_react_preamble_installed, 由于 qiankun 使用 window+proxy 实现了 js 沙箱,所以这里的全局变量实际上会被挂载到 window.proxy 上,导致后续访问全局变量会报错。
  • vite 不支持运行时 publicPath 的配置,而 qiankun 需要在运行时动态修改 publicPath 来加载子应用的资源。

2. 解决方案

当前可以借助vite-plugin-qiankun插件解决上述问题,它通过 dynamic import() 的形式来加载子应用程序,从而使得可以在模块系统【 ESMCommonJS 】下均可正确加载子应用程序;同时它还会在 window 上挂载 __INJECTED_PUBLIC_PATH_BY_QIANKUN__ 全局变量,用于在运行时动态修改 publicPath 来加载子应用的资源。详情参考

三、使用vue3 + vite构建主应用(hash路由模式)

主应用不限技术栈,只需要提供一个容器 DOM,然后注册微应用并 start 即可。

因为目前的项目都是hash路由模式,所以先尝试了基于hash路由模式搭建vue框架的qiankun主应用,虽然这种方式并不推荐。

  1. 使用vite初始化一个vue3项目
# 使用npm
$ npm create vite@latest qiankun-main --template vue-ts

# 使用yarn
$ yarn create vite qiankun-main —-template vue-ts
  1. 安装qiankun
$  yarn add qiankun
  1. 新建qiankun/index.ts文件
import {registerMicroApps, start} from 'qiankun'

const DEV = import.meta.env.DEV

/**
 * 要注册的微应用
 */
const microApps = [
  {
    name: 'foo', // 微应用名称,具有唯一性,可理解为微应用的唯一id
    entry: DEV ? '//localhost:8081' : '/main/microApps/foo', // 微应用入口,通过该地址加载微应用
    container:'#container', // 微应用挂载节点,微应用加载完成后将挂载在该节点上
    activeRule:'#/micro/foo', // 微应用触发的路由规则,触发路由规则后将加载该微应用
    props:{} // 主应用需要传递给微应用的数据
  },
   {
    name: 'bar', // 微应用名称,具有唯一性,可理解为微应用的唯一id
    entry: DEV ? '//localhost:8082' : '/main/microApps/bar', // 微应用入口,通过该地址加载微应用
    container:'#container', // 微应用挂载节点,微应用加载完成后将挂载在该节点上
    activeRule:'#/micro/bar', // 微应用触发的路由规则,触发路由规则后将加载该微应用
    props:{} // 主应用需要传递给微应用的数据
  }
]

/**
 * 在主应用中注册微应用
 */
 registerMicroApps(microApps,{
   beforeLoad:(app) => {
     console.log('before load',app.name)
     return Promise.resolve()
   }
 })
 
 // 启动qiankun 推荐在页面中开启qiankun
 start()

微应用加载方式

  • 基于路由配置:通过将微应用关联到一些 url 规则的方式,实现当浏览器 url 发生变化时,自动加载相应的微应用的功能。
  • 手动加载微应用:适用于需要手动 加载/卸载 一个微应用的场景
  1. mian.ts文件中引入qiankun/index.ts文件
import './qiankun/index.ts

  1. 修改router/index.ts文件
import {createRouter, createWebHashHistory} from 'vue-router'
import microAppVue = '@/views/microApp.vue'

const routes = [
  {
    path:'/',
    components:() => import('@/views/home.vue')
  },
  {
    path:'/micro/:chapters+',
    components:microAppVue
  }
]

const router = createRouter({
  hisrory:createWebHashHistory(),
  routes
})

export default router
  1. 新增views/microApp.vue文件
<template>
  <div id="container"> </div>
</template>

四、使用vue2 + webpack构建微应用(hash路由模式)

可参考qiankun官网 - 项目实践 - vue微应用

  1. 使用vue-cli构建一个vue2或者vue3的项目

    略......

  2. src目录新增public-path.js文件

if (window.__POWERED_BY_QIANKUN__) {
  __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}
  1. 修改main.js文件,为了避免根 id #app 与其他的 DOM 冲突,需要限制查找范围。
import './public-path';
import Vue from 'vue';
import App from './App.vue';
import router from './router/index.js';

Vue.config.productionTip = false;

let router = null;
let instance = null;

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

// 独立运行时
if (!window.__POWERED_BY_QIANKUN__) {
  render();
}

export async function bootstrap() {
  console.log('[vue] vue app bootstraped');
}
export async function mount(props) {
  console.log('[vue] props from main framework', props);
  render(props);
}
export async function unmount() {
  instance.$destroy();
  instance.$el.innerHTML = '';
  instance = null;
  router = null;
}
  1. 修改router/index.js文件
import VueRouter from 'vue-router'

const  routes = [
  {
    path:'/micro/foo/home', // 此处的path前缀要与主应用里的activeRule:'#/micro/bar'一一对应,但不需要#
    component:() => import('@/views/home.vue')
  }
]
  1. 修改vue.config.js文件
const { name } = require('./package');

module.exports = {
  devServer: {
    headers: {
      'Access-Control-Allow-Origin': '*',
    },
  },
  configureWebpack: {
    output: {
      library: `${name}-[name]`,
      libraryTarget: 'umd', // 把微应用打包成 umd 库格式
      jsonpFunction: `webpackJsonp_${name}`, // webpack 5 需要把 jsonpFunction 替换成 chunkLoadingGlobal
    },
  },
};

五、使用vue3 + vite构建微应用

  1. 安装yarn add vite-plugin-qiankun
  2. 修改vite.config.js
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import qiankun from 'vite-plugin-qiankun'

// https://vitejs.dev/config/
export default defineConfig({
  base: DEV? '/' : '/main/microApps/bar', // 部署时此路径应与主应用注册的entry需保持一致
  plugins: [
    vue(),
    qiankun('bar', {
      useDevMode: true
    })
  ],
  server: {
    port: 7001,
    cors: true,
    headers: {
      'Access-Control-Allow-Origin': '*'
    }
  }
})
  1. 修改路由配置
import {  createRouter, createWebHistory } from 'vue-router'
import routes from './routes'
import { qiankunWindow } from 'vite-plugin-qiankun/es/helper'

const router = createRouter({
  history: createWebHistory(),
  routes
})

export default router
  1. main.js文件,添加生命周期
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import { renderWithQiankun,  qiankunWindow } from 'vite-plugin-qiankun/es/helper'

let app

const render = (container) => {
  app = createApp(App)
  app
    .use(router)
    .mount(container ? container.querySelector('#app') : '#app')
}

const initQianKun = () => {
  renderWithQiankun({
    mount(props) {
      const { container } = props
      render(container)
    },
    bootstrap() {},
    unmount() {
      app.unmount()
    }
  })
}

qiankunWindow.__POWERED_BY_QIANKUN__ ? initQianKun() : render()

六、服务器部署结构

└── main  // 主应用文件夹
    ├── microApp  // 所有微应用的父级文件夹
    │   └── foo  // 微应用一
    │   └── bar  // 微应用二
    ├── css
    ├── favicon.ico
    ├── index.html
    └── js