1、项目搭建

通过vite构建4个应用,分别为:

basic-vue-app–(vue3基座应用)
micro-vue2-app–(vue2微应用)
micro-vue3-app–(vue3微应用)
micro-react-app–(react微应用)

打开我们的基座项目basic-vue-app 添加项目依赖,载入乾坤的包:

npm install qiankun

编写qiankun配置文件:

(1).搭建微应用的列表,进行注册
(2).启动qiankun
(3).在入口文件引入qiankun配置文件

import { registerMicroApps, start } from 'qiankun'
registerMicroApps([
    {
        name: 'vue3-app', // 必须与微应用注册名字相同
        entry: import.meta.env.MODE === 'development' ? 'http://127.0.0.1:5174' : '/vue3-app/', // 入口路径,开发时为微应用所启本地服务,上线时为微应用线上路径
        container: '#sub-app-container', // 微应用挂载的节点
        activeRule: '/vue3-app', // 当访问路由为 /vue3-app 时加载微应用
        props: {
            msg: "我是来自主应用的值-vue3"  // 主应用向微应用传递参数
        }
    },
    {
        name: 'vue2-app',
        entry: import.meta.env.MODE === 'development' ? 'http://127.0.0.1:5176' : '/micro-vue2-app/',
        container: '#sub-app-container',
        activeRule: '/vue2-app',
        props: {
            msg: "我是来自主应用的值-vue2"
        }
    },
    {
        name: 'react-app',
        entry: import.meta.env.MODE === 'development' ? 'http://127.0.0.1:5175' : '/micro-react-app/',
        container: '#sub-app-container',
        activeRule: '/react-app',
        props: {
            msg: "我是来自主应用的值-react"
        }
    }
])
start()

注册micro-vue2-app微应用:

import Vue from "vue";
import App from "./App.vue";
import router from "./router";
import store from "./store";
import { renderWithQiankun, qiankunWindow } from 'vite-plugin-qiankun/dist/helper'

let instance = null;

const initQianKun = () => {
    renderWithQiankun({
        // 当前应用在主应用中的生命周期
        // 文档 https://qiankun.umijs.org/zh/guide/getting-started#
        mount(props) {
            console.log('vue2微应用开始挂载了')
            render(props.container)
            //  可以通过props读取主应用的参数:msg 监听主应用传值
            props.onGlobalStateChange((res) => {
                store.count = res.count
                console.log('来自主应用的count值:' + res.count)
            })
        },
        bootstrap() {
            console.log('-- bootstrap --')
        },
        update() {
            console.log('-- update --')
        },
        unmount() {
          instance.$destroy();
          instance.$el.innerHTML = '';
          instance = null;
            console.log('-- unmount --')
        },
    })
}
const render = (container) => {
    // 如果是在主应用的环境下就挂载主应用的节点,否则挂载到本地
    console.log('主应用的挂载节点' + container)
    const appDom = container ? container.querySelector('#app') : "#app"
    instance = new Vue({
      router,
      store,
      render: (h) => h(App),
    }).$mount(appDom);
}
// 判断当前应用是否在主应用中
qiankunWindow.__POWERED_BY_QIANKUN__ ? initQianKun() : render()

注册micro-vue3-app微应用:

import { createApp } from 'vue'
import App from './App.vue'
import { store } from './store'
import router from './router'

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

const initQianKun = () => {
    renderWithQiankun({
        // 当前应用在主应用中的生命周期
        // 文档 https://qiankun.umijs.org/zh/guide/getting-started#
        mount(props) {
            console.log('vue3微应用开始挂载了')
            render(props.container)
            // 可以通过props读取主应用的参数:msg
            // 监听主应用传值
            props.onGlobalStateChange((res) => {
                store.count = res.count
                console.log('来自主应用的count值:' + res.count)
            })
        },
        bootstrap() {
            console.log('-- bootstrap --')
        },
        update() {
            console.log('-- update --')
        },
        unmount() {
            console.log('-- unmount --')
        },
    })
}

const render = (container) => {
    // 如果是在主应用的环境下就挂载主应用的节点,否则挂载到本地
    console.log('主应用的挂载节点' + container)
    const appDom = container ? container : "#app"
    createApp(App).use(router).mount(appDom)
}

console.log('qiankunWindow对象')
console.log(qiankunWindow)
console.log(qiankunWindow.__POWERED_BY_QIANKUN__)

// 判断当前应用是否在主应用中
qiankunWindow.__POWERED_BY_QIANKUN__ ? initQianKun() : render()

注册micro-react-app微应用:

import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App'

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

const initQianKun = () => {
  renderWithQiankun({
    // 当前应用在主应用中的生命周期
    // 文档 https://qiankun.umijs.org/zh/guide/getting-started#

    mount(props) {
      render(props.container)
      //  可以通过props读取主应用的参数:msg
      // 监听主应用传值
      props.onGlobalStateChange((res) => {
        console.log(res.count)
      })
    },
    bootstrap() { },
    unmount() { },
  })
}

const render = (container) => {
  // 如果是在主应用的环境下就挂载主应用的节点,否则挂载到本地
  const appDom = container ? container : document.getElementById('root')
  ReactDOM.createRoot(appDom).render(
    <React.StrictMode>
      <App />
    </React.StrictMode>
  )
}

// 判断当前应用是否在主应用中
qiankunWindow.__POWERED_BY_QIANKUN__ ? initQianKun() : render()

Vite配置:

以micro-vue2-app为例,部署在同一域名二级目录下需要设置base。

import { createVuePlugin } from 'vite-plugin-vue2';
import { defineConfig } from 'vite';
import qiankun from 'vite-plugin-qiankun'

// https://vitejs.dev/config/
export default ({ mode }) => {
  return defineConfig ({
    base: mode === 'development' ? './' : '/micro-vue2-app/',
    resolve: {
      alias: [{ find: '@', replacement: '/src' }],
    },
    server: {
      host: '0.0.0.0',
      port: 5176,
      proxy: {
        '/api': {
          target: 'http://127.0.0.1:8000/api',
          changeOrigin: true,
          rewrite: (path) => path.replace(/^\/api/, ''),
        },
      },
    },
    css: {
      preprocessorOptions: {
        scss: {
          additionalData: `@import "@/assets/style/index.scss";`,
        },
      },
    },
    plugins: [
      createVuePlugin(),
      qiankun('vue2-app', { // 微应用名字,与主应用注册的微应用名字保持一致
        useDevMode: true
      }),
    ],
  });
}

本地分别启动主应用和子应用:

以micro-vue2-app为例:激活路由为/vue2-app则加载vue2子应用挂载在基座应用指定容器内,子应用路由正常切换。

前端微服务子项目和主项目 前端微服务 乾坤_App

2、运维部署

本地window机器安装nginx:

https://nginx.org/en/download.html 下载nginx安装包解压到指定目录,启动nginx:

前端微服务子项目和主项目 前端微服务 乾坤_App_02

hosts文件配置本地ip映射域名:

C:\Windows\System32\drivers\etc下增加:

127.0.0.1 demofe.noahgroup.com

nginx的conf目录下新建conf.d目录,以域名为文件名新建文件:

前端微服务子项目和主项目 前端微服务 乾坤_vue.js_03

nginx配置文件做如下更改,设置根目录为基座应用build后的dist文件夹:

前端微服务子项目和主项目 前端微服务 乾坤_前端微服务子项目和主项目_04

重启nginx:

前端微服务子项目和主项目 前端微服务 乾坤_前端微服务子项目和主项目_05

npm run build基座应用,浏览器输入域名:

前端微服务子项目和主项目 前端微服务 乾坤_vue.js_06

分别打包micro-vue2-app,micro-vue3-app,micro-react-app,因为是部署在同一个域名下,有两种方案:

(1)、将打包后的dist文件分别copy到基座应用dist目录下,以子应用的enter为文件名命名:

前端微服务子项目和主项目 前端微服务 乾坤_App_07

(2)、nginx通过alias配置将子应用映射到相应的子目录,请注意将主应用和子应用的部署路径替换为实际的路径,以vue3-app为例:

前端微服务子项目和主项目 前端微服务 乾坤_App_08


前端微服务子项目和主项目 前端微服务 乾坤_javascript_09


前端微服务子项目和主项目 前端微服务 乾坤_前端微服务子项目和主项目_10

重启nginx,浏览器查看,以micro-vue2-app为例,点击“进入Vue2-App应用的Home页” 进入到vue2微应用/home路由下:

前端微服务子项目和主项目 前端微服务 乾坤_javascript_11

以上就是将qiankun微前端基座应用和微应用部署在同一个域名下,主应用和微应用都可独立部署,如若分别部署在不同的域名下,将微应用入口entey改为独立部署的地址。

3、遇到问题

(1)、Vite中不能导出qiankun生命周期函数

  • vite 构建的 js 内容必须在 type=module 的 script 脚本里;
  • qiankun 的源码依赖之一 import-html-entry 则不支持 type=module 这个属性
  • qiankun 是通过 eval 来执行这些 js 的内容,而 vite 里面 import/export 没有被转码, 所以直接接入会报错:不允许在非type=module 的 script 里面使用 import
方案一:

因为vite使用的rollup,所以我们使用可以使用rollup插件 rollup/plugin-html,然后修改vite.config.js中build的配置,把vite默认输出的target模式修改一下,将module改为esnext。有如下缺点:

  • 可以实现生产环境接入,开发环境不行;
  • vite没有动态publicPath的支持;所以 Vite.config 中 base 配置需要写死
  • vite code-splitting(代码分割)功能并不支持iife和umd两种格式,导致路由无法懒加载;
  • 图片资源只会被打包成 base64,无论图片大小
    https://github.com/umijs/qiankun/issues/1268
方案二:(推荐)

vite-plugin-qiankun插件,https://github.com/tengmaoqing/vite-plugin-qiankun

  • 保留 vite 构建 es 模块的优势
  • 一键配置,不影响已有的 vite 配置
  • 支持 vite 开发环境
    修改vite.config.ts配置和main.ts中导出生命周期钩子如上。