vue中使用 qiankun微前端

136 阅读5分钟

qiankun 微前端

简介

微前端是一种多个团队通过独立发布功能的方式来共同构建现代化 web 应用的技术手段及方法策略

微前端具备以下特点:

  • 技术栈无关:主框架不限制接入应用的技术栈,子应用具备完全自主权
  • 独立开发、独立部署:既可以组合在一起运行,也可以单独运行
  • 增量升级:在面对各种复杂场景时,我们通常很难对一个已经存在的系统做全量的技术栈升级或重构,而微前端是一种非常好的实施渐进式重构的手段和策略
  • 独立运行时:每个微应用之间状态隔离,运行时状态不共享

搭建主应用 (vue3)

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

1.安装qiankun
yarn add qiankun 或者 npm i qiankun -S
2.注册微应用

目录 src 下新建 src/qiankun/index.js

import {registerMicroApps, start} from 'qiankun'

registerMicroApps([
    {
        name: 'sub-vue', // 子应用名称
        entry: '//localhost:5174', // 子应用入口
        container: '#sub-app', // 子应用挂载的DOM节点
        activeRule: '/sub-vue', // 子应用激活路径
        //向子应用传递参数
       	props:{
            ...
        }
    },
    {
        name: 'sub-react', // 子应用名称
        entry: '//localhost:3011', // 子应用入口
        container: '#sub-app', // 子应用挂载的DOM节点
        activeRule: '/sub-react', // 子应用激活路径
    }
])

export default start;
3.添加子应用的容器并启动应用
<template>
	//id 要与配置当中的 container 一致
  <div id="sub-app"></div>
</template>
<script setup>
import start from '@/qiankun/index';
import { onMounted } from 'vue'
onMounted(()=>{
    //启动qiankun
    if(!window.qiankunStarted){
        window.qiankunStarted = true
        start({
            sandbox: {
             // 样式隔离特性
             experimentalStyleIsolation: true,
            }
         })  
    }
})
</script>
4.添加配置路由
 {
	// 注册时的activeRule 配置的路径相同
	//:pathMatch(.*)* 匹配任意路径 不然子应用的路由跳转会出错 (vue项目)
    path: '/sub-vue:pathMatch(.*)*',
    name: 'sub-vue',
    component: () => import('@/views/qiankun/index.vue')
 },...
5.解决路由跳转失败

安装lodash

npm i lodash

添加路由守卫

router.beforeEach((to, from, next) => {
    if (_.isEmpty(history.state.current)) {
        _.assign(history.state, { current: from.fullPath });
    }
    next();
});

搭建vue子应用 (vite)

1.下载插件

目前 qiankun 还没有兼容 vite,还需借助第三方插件 vite-plugin-qiankun来实现

npm i vite-plugin-qiankun // 安装
2.vite.config.js 配置
import { fileURLToPath, URL } from 'node:url'

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
//导入插件
import qiankun from 'vite-plugin-qiankun'
// https://vitejs.dev/config/
export default defineConfig({
  plugins: [
    vue(),
     //注册qiankun插件
    qiankun('sub-vue', { // 微应用名字,与主应用注册的微应用名字保持一致
      useDevMode: true
    })
  ],
  server: {
    port: 5174,
    // 如果出现图片资源无法加载 请加上跨域
    //项目启动路径
    origin: "http://localhost:5174",
    cors: true,
  },
  resolve: {
    alias: {
      '@': fileURLToPath(new URL('./src', import.meta.url))
    }
  }
})

3.修改路由配置

分两种情况:

1、独立运行. 2、微服务中运行

import { qiankunWindow } from 'vite-plugin-qiankun/dist/helper'
const router = createRouter({
  // 子应用的路由配置判断
  history: createWebHistory(
      qiankunWindow.__POWERED_BY_QIANKUN__ 
      ? '/sub-vue' //配置的子应用的路径activeRule
      : import.meta.env.BASE_URL),
  routes: [
    {
      path: '/',
      name: 'home',
      component: HomeView
    },...
  ]
})

export default router
4.mian.js添加生命周期
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import { renderWithQiankun, qiankunWindow } from 'vite-plugin-qiankun/dist/helper'
import App from './App.vue'
import router from './router'

let app;
const render = (container = undefined)=>{
    app = createApp(App)
    app.use(createPinia())
    app.use(router)
    app.mount(container ? container.querySelector("#app") : '#app')
}
const initQianKun = ()=>{
    renderWithQiankun({
        //props为主应用传过来的data
        // 渲染子应用
        mount(props) {
           const {container} = props;
           render(container)
        }, 
        // 只会在微应用初始化的时候调用一次,下次微应用重新进入时会直接调用 mount 钩子,不会再重复触发 bootstrap
        bootstrap() {
            console.log('bootstrap')
        },
        // 卸载子应用
        unmount() {
            console.log('unmount')
        },
    })
}
//判断是否为qiankun环境
qiankunWindow.__POWERED_BY_QIANKUN__ ? initQianKun() : render()

搭建react子应用 (webpack)

1.在src目录下面新增public-path.js

如果不添加 可能会出现静态资源无法加载的问题,子应用在微服务中打开 它默认使用了父应用的前缀,导致子应用无法加载自身的静态资源

if (window.__POWERED_BY_QIANKUN__) {
    // eslint-disable-next-line no-undef
    __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}
2. index.js 导出生命周期函数,修改路由基础路径
import './public-path' //导入public-path.js
let root;
function createRoot(props) {
  // container中包含了qiankun创建的dom,它会插入一个带有id为root的dom
  const { container } = props;
  root = ReactDOM.createRoot(container ? container.querySelector('#root') : document.querySelector('#root'));
}

if (!window.__POWERED_BY_QIANKUN__) {
  createRoot({});
  root.render(
    <BrowserRouter>
      <App />
    </BrowserRouter>
  );
}

//  初始化
export async function bootstrap() {
}

// 挂载
export async function mount(props) {
  createRoot(props);
  //qiankun环境中渲染
  root.render(
	//判断路由是否是qiankun环境并设置基础路径
    <BrowserRouter basename={window.__POWERED_BY_QIANKUN__ ? '/sub-react' : '/'}>
      <App />
    </BrowserRouter>
  )
}
//卸载
export async function unmount() {
}
3.修改App.js
//使用这种路由模式
<Routes
	<Route path='/about' element={<div>about</div>}></Route>
    <Route path='/' element={<Home/>}></Route>
</Routes>
4.修改webpack配置

安装插件@craco/craco

npm i @craco/craco --save

根目录新增 craco.config.js

//package.json 的name必须和微应用的名称一致
const { name } = require("./package");

module.exports = {
  webpack: {
    configure: (webpackConfig) => {
      webpackConfig.output.library = `${name}-[name]`;
      webpackConfig.output.libraryTarget = "umd";
      webpackConfig.output.chunkLoadingGlobal = `webpackJsonp_${name}`;
      return webpackConfig;
    },
  },
  devServer: (devServerConfig) => {
    devServerConfig.historyApiFallback = true;
    devServerConfig.open = false;
    devServerConfig.hot = false;
    devServerConfig.watchFiles = [];
    devServerConfig.headers = {
      "Access-Control-Allow-Origin": "*",
    };
    return devServerConfig;
  },
};

修改package.json

  "scripts": {
    "start": "set PORT=3011 && craco start",
    "build": "craco build",
    "test": "craco test",
    "eject": "react-scripts eject"
  },

数据共享

1.主应用传递数据

放在 registerMicroApps 后面

const state = {
    counter: 0
}
//初始化
const actions = initGlobalState(state)
//监听数据变化
actions.onGlobalStateChange((value, prev) => {
    console.log('主应用观察到全局状态变化!', value, prev)
})
//修改数据
actions.setGlobalState({counter: 1})
2.子应用
mount(props) {
    //监听 
    props.onGlobalStateChange((state, prev) => {
          console.log('主应用接收到的数据', state)
    })
    //修改
    props.setGlobalState({
        counter: 1  
    })
}

子应用之间跳转

1.主应用、子应用路由都是hash模式

主应用根据 hash 来判断微应用,无需考虑该问题

2.主应用、子应用路由都是history模式
  1. history.pushState(state,title,url)方法

  2. 通过props传递主应用的路由实例 子应用挂载到全局 然后子应用 使用 主应用的router 跳转

    {
        name: 'sub-vue', // 子应用名称
        entry: '//localhost:5174', // 子应用入口
        container: '#sub-app', // 子应用挂载的DOM节点
        activeRule: '/sub-vue', // 子应用激活路径
        props: {
            //主应用的路由实例
             router
         }
    },