qiankun引入vue3子项目实践
背景
vue3优点就不再赘述了,反正就是很多(性能优化、优雅使用上ts、Composition API...),最主要的能使用前沿的技术架构,这是每一位技术的执念。
怎么样无感知切换到使用vue3开发成了当前的问题,前段时间有同事提出了可以通过qiankun嵌入,okokok!那就是它了,在我们老项目中通过qiankun引入一个vue3子服务来写业务模块,同时新老项目也可以通过qiankun内置方式来进行数据交互,如下图:
[图片上传失败...(image-44c6bb-1679899817625)]
准备工作
创建vue3子服务项目
这里我们使用webpack方式创建,暂时不要使用vite,因为vite暂时不支持qiankun框架.
创建过程不在赘述,可参考:https://segmentfault.com/a/1190000039255646
创建完成之后我们新建两个路由地址,如下:
import { createRouter, createWebHistory, RouteRecordRaw } from "vue-router";
import Home from "../views/Home.vue";
const routes: Array<RouteRecordRaw> = [
{
path: "/",
name: "Home",
component: Home,
},
{
path: "/home",
name: "Home2",
component: Home,
},
{
path: "/about",
name: "About",
component: () =>
import(/* webpackChunkName: "about" */ "../views/About.vue"),
},
];
const router = createRouter({
history: createWebHistory(process.env.BASE_URL),
routes,
});
export default { router, routes };
下面我们就以/home以及/about路由为例在老项目中引入。
安装
主服务安装
cnpm i qiankun
创建子服务容器
主服务增加router地址/qiankunVue3/*来承载vue3子项目,接入之后主服务所有/qiankunVue3的路由地址都将由子服务渲染。
下面代码省略了引入部分,导出的micoapp会合并到主服务的router.routes对象中
主服务增加一个routes对象:
const qiankunVue3Layout = () => import(/* webpackChunkName: "micoapp" */ '@/views/common/qiankunVue3Layout.vue')
let micoapp = [
{
path: '/qiankunVue3/*',
name: '乾坤Vue3',
meta: {
keepAlive: false,
isMicoApp: true
},
component: qiankunVue3Layout
}
]
export default micoapp
其中新建qiankunVue3Layout.vue简写之后的代码如下,由<section id="qiankunVue3Dom" />
代替<router-view>
来承载子服务
// qiankunVue3Layout.vue
<template>
<div class="cfpa-home-wrapper" id="main">
<!--头部-->
<v-header v-if="showHeader"></v-header>
<!--侧边栏-->
<v-aside v-if="isSHowMenu"></v-aside>
<div class="cfpa-content" :class="{'cfpa-content-collapse':collapse, 'cfpa-header-show': showHeader, 'cfpa-content-fullScreen': !isSHowMenu}">
<!--tag栏-->
<v-tags v-if="isSHowMenu"></v-tags>
<div class="cfpa-content-box">
<!--子服务渲染dom,这个id在后续将会用到-->
<section id="qiankunVue3Dom" />
</div>
</div>
</div>
</template>
<script>
...省略
export default {
...
}
</script>
<style lang="scss" scoped>
</style>
注册子服务
主服务新建src/qiankun.js
import store from '@/store'
import { registerMicroApps, initGlobalState } from 'qiankun'
/** location.pathname是否以 routerPrefix 前缀开头*/
function genActiveRule(routerPrefix, currentRoute = '') {
return location => location.pathname.startsWith(routerPrefix)
}
// 主应用共享出去的数据
let msg = {
state: store.state,
token: store.state.userInfo.token,
isQiankun: true,
system: 'fmas'
}
// 注册子应用
registerMicroApps(
[
{
/**
* name: 子服务有唯一性 - 这个需要与子服务webpack name一致
* entry: 子服务入口 - 通过该地址加载微应用
* container: 子服务挂载节点 - 微应用加载完成后将挂载在该节点上 - 与上述qiankunVue3Layout.vue id一致
* activeRule: 子服务触发的路由规则 - 触发路由规则后将加载该微应用 - 与上述创建子服务路由前缀一致
* props 共享数据到子服务
* sandbox 开启沙箱
*/
name: 'xxx-vue3', // 根据实际情况来
entry: '//localhost:10200',
container: '#qiankunVue3Dom',
activeRule: genActiveRule('/qiankunVue3'),
props: msg, // 共享数据到子服务
sandbox: {
strictStyleIsolation: true
}
}
],
{
// 挂载前回调
beforeLoad: [
app => {
console.log('before load', app)
}
],
// 挂载后回调
beforeMount: [
app => {
console.log('before mount', app)
}
],
// 卸载后回调
afterUnmount: [
app => {
console.log('after unload', app)
}
]
}
)
src/main.js中引入
import './qiankun'
配置子服务
在主应用注册好了微应用后,我们还需要对微应用进行一系列的配置。首先,我们在 Vue 的入口文件 main.js 中,导出 qiankun 主应用所需要的三个生命周期钩子函数,另外,webpack 默认的 publicPath 为 "" 空字符串,会基于当前路径来加载资源。我们在主应用中加载微应用时需要重新设置 publicPath,这样才能正确加载微应用的相关资源。增加如下代码:
子服务新增@src/public-path.js
// @src/public-path.js
if (window.__POWERED_BY_QIANKUN__) {
// eslint-disable-next-line no-undef
__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}
子服务修改:src/main.ts
// src/main.ts
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
import { createApp } from "vue";
import App from "./App.vue";
import "./public-path.js";
import router from "./router";
import store from "./store";
import { createRouter, createWebHistory } from "vue-router";
// eslint-disable-next-line no-unused-vars
let instance = null;
let Router = null;
function render(props: any = {}) {
const { container } = props;
// 如果是乾坤环境
if ((window as any).__POWERED_BY_QIANKUN__) {
const routes = router.routes;
// 在 render 中创建 VueRouter,可以保证在卸载微应用时,移除 location 事件监听,防止事件污染
const base = (window as any).__POWERED_BY_QIANKUN__ ? "/qiankunVue3" : "/";
Router = createRouter({
history: createWebHistory(base),
routes,
});
instance = createApp(App)
.use(store)
.use(Router)
.mount(container ? container.querySelector("#app") : "#app");
} else {
instance = createApp(App).use(store).use(router.router).mount("#app");
}
}
// 独立运行时,直接挂载应用
if (!(window as any).__POWERED_BY_QIANKUN__) {
render();
}
/**
* bootstrap 只会在微应用初始化的时候调用一次,下次微应用重新进入时会直接调用 mount 钩子,不会再重复触发 bootstrap。
* 通常我们可以在这里做一些全局变量的初始化,比如不会在 unmount 阶段被销毁的应用级别的缓存等。
*/
export async function bootstrap(props: any) {
console.log("VueMicroApp bootstrap", props);
}
/**
* 应用每次进入都会调用 mount 方法,通常我们在这里触发应用的渲染方法
*/
export async function mount(props: any) {
console.log("VueMicroApp mount", props);
render(props);
}
/**
* 应用每次 切出/卸载 会调用的方法,通常在这里我们会卸载微应用的应用实例
*/
export async function unmount(props: any) {
console.log("VueMicroApp unmount", props);
// instance.$destroy();
instance = null;
Router = null;
}
在配置好了入口文件 main.js 后,我们还需要配置 webpack,使 main.js 导出的生命周期钩子函数可以被 qiankun 识别获取。
子服务修改: vue.config.js
// vue.config.js
// eslint-disable-next-line @typescript-eslint/no-var-requires
const path = require("path");
module.exports = {
devServer: {
// 监听端口
port: 10200,
// 关闭主机检查,使微应用可以被 fetch
disableHostCheck: true,
// 配置跨域请求头,解决开发环境的跨域问题
headers: {
"Access-Control-Allow-Origin": "*",
},
},
configureWebpack: {
resolve: {
alias: {
"@": path.resolve(__dirname, "src"),
},
},
// 这一块是新增本次新增的
output: {
// 子服务的包名,这里与主应用中注册的微应用名称一致
library: "xxx-vue3", // 根据实际情况来
// 将你的 library 暴露为所有的模块定义下都可运行的方式
libraryTarget: "umd",
// 按需加载相关,设置为 webpackJsonp_子服务名称 即可
jsonpFunction: `webpackJsonp_xxx-vue3`, // 根据实际情况来
},
},
};
启动
在主服务qiankunVue3Layout.vue启动qiankun
主服务修改:qiankunVue3Layout.vue
<template>
<div class="cfpa-home-wrapper" id="main">
<!--头部-->
<v-header v-if="showHeader"></v-header>
<!--侧边栏-->
<v-aside v-if="isSHowMenu"></v-aside>
<div class="cfpa-content" :class="{'cfpa-content-collapse':collapse, 'cfpa-header-show': showHeader, 'cfpa-content-fullScreen': !isSHowMenu}">
<!--tag栏-->
<v-tags v-if="isSHowMenu"></v-tags>
<div class="cfpa-content-box">
<!--子服务渲染dom,这个id在后续将会用到-->
<section id="qiankunVue3Dom" />
</div>
</div>
</div>
</template>
<script>
...
import { start } from 'qiankun'
export default {
...
mounted() {
if (!window.qiankunStarted) {
window.qiankunStarted = true
start()
}
}
}
</script>
<style lang="scss" scoped>
...
</style>
最后重新启动主服务和子服务。访问主服务/qiankunVue3/home
[图片上传失败...(image-d25168-1679899817625)]
坑点
1.建vue3项目时,选择通过webpack打包,因为vite暂时没有支持乾坤框架
2.报错 application '*****' died in status SKIP_BECAUSE_BROKEN: [qiankun]: Target container with #qiankunDom not existed while fmas mounting!
a.首先查一下 注册子服务步骤中,下列字段是否正确
[图片上传失败...(image-e9ac62-1679899817625)]
b. 是否在 配置主服务步骤中 main.js中导出3个生命周期函数
[图片上传失败...(image-14bbd0-1679899817625)]
c. 是否在 配置主服务步骤中配置webpack
[图片上传失败...(image-b9e340-1679899817625)]
流水线发版改造
目标
主服务子服务项目使用同一个git服务及同一个流水线,实现一键提交,一键发版。
实现思路
将子服务拷贝到主服务根目录里,并且将子服务的出包路径修改为主服务的出包路径(eg: 主服务出包路径为/dist,则子服务的出包路径为../dist/包名),打包构建时先构建主服务在构建子服务
流水线构建命令:
rm -rf node_modules && rm -rf dist && npm install && npm run build:dev && cd xxx-vue3 && rm -rf node_modules && npm install && npm run build:dev
构建完成之后会生成一个类似如下结构的包
[图片上传失败...(image-c2ea30-1679899817625)]
此时我们将这个包部署到服务器之后就可以通过 https://....com/访问当主服务,通过https://....com/子服务包名 访问当子服务
至此,我们只需要修改主服务引入子服务的入口地址为https://....com/子服务包名 既可。
实现步骤
将子服务拷贝至主服务根目录(子服务不需要单独git服务,如子服务有git,删除掉子服务.git文件夹),如图
[图片上传失败...(image-4dc00a-1679899817625)]
修改子服务静态资源路径及出包路径(这里子服务包名为vue3Serve):
// vue.config.js
...
publicPath:'/vue3Serve/',
outputDir:"../dist/vue3Serve",
...
修改主服务注册子服务时的入口
// qinnkun.js
{
name: 'vue3Serve',
entry: process.env.VUE_APP_MICO_FMAS_FRONT_VUE3, // 只修改了这行
container: '#qiankunVue3Dom',
activeRule: genActiveRule('/qiankunVue3'),
props: qiankunActions.initialState,
sandbox: {
strictStyleIsolation: true,
experimentalStyleIsolation: true,
},
},
项目如下:
本地://localhost:10200/vue3Serve
dev:https://.....-dev/com.cn/vue3Serve
test:https://......-test/com.cn/vue3Serve
生产:https://........com.cn/vue3Serve
修改流水线编排构建命令
rm -rf node_modules && rm -rf dist && npm install && npm run build:dev && cd xxx-vue3 && rm -rf node_modules && npm install && npm run build:dev
完事!
作者: 快落的小海疼