qiankun引入vue3子項目實踐
背景
vue3優(yōu)點就不再贅述了,反正就是很多(性能優(yōu)化、優(yōu)雅使用上ts、Composition API...),最主要的能使用前沿的技術(shù)架構(gòu)亿汞,這是每一位技術(shù)的執(zhí)念。
怎么樣無感知切換到使用vue3開發(fā)成了當(dāng)前的問題,前段時間有同事提出了可以通過qiankun嵌入,okokok剖膳!那就是它了,在我們老項目中通過qiankun引入一個vue3子服務(wù)來寫業(yè)務(wù)模塊叹话,同時新老項目也可以通過qiankun內(nèi)置方式來進(jìn)行數(shù)據(jù)交互,如下圖:
[圖片上傳失敗...(image-44c6bb-1679899817625)]
準(zhǔn)備工作
創(chuàng)建vue3子服務(wù)項目
這里我們使用webpack方式創(chuàng)建,暫時不要使用vite,因為vite暫時不支持qiankun框架.
創(chuàng)建過程不在贅述,可參考:https://segmentfault.com/a/1190000039255646
創(chuàng)建完成之后我們新建兩個路由地址股冗,如下:
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路由為例在老項目中引入攒霹。
安裝
主服務(wù)安裝
cnpm i qiankun
創(chuàng)建子服務(wù)容器
主服務(wù)增加router地址/qiankunVue3/*來承載vue3子項目集峦,接入之后主服務(wù)所有/qiankunVue3的路由地址都將由子服務(wù)渲染速妖。
下面代碼省略了引入部分妨马,導(dǎo)出的micoapp會合并到主服務(wù)的router.routes對象中
主服務(wù)增加一個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>
來承載子服務(wù)
// qiankunVue3Layout.vue
<template>
<div class="cfpa-home-wrapper" id="main">
<!--頭部-->
<v-header v-if="showHeader"></v-header>
<!--側(cè)邊欄-->
<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">
<!--子服務(wù)渲染dom杀赢,這個id在后續(xù)將會用到-->
<section id="qiankunVue3Dom" />
</div>
</div>
</div>
</template>
<script>
...省略
export default {
...
}
</script>
<style lang="scss" scoped>
</style>
注冊子服務(wù)
主服務(wù)新建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)
}
// 主應(yīng)用共享出去的數(shù)據(jù)
let msg = {
state: store.state,
token: store.state.userInfo.token,
isQiankun: true,
system: 'fmas'
}
// 注冊子應(yīng)用
registerMicroApps(
[
{
/**
* name: 子服務(wù)有唯一性 - 這個需要與子服務(wù)webpack name一致
* entry: 子服務(wù)入口 - 通過該地址加載微應(yīng)用
* container: 子服務(wù)掛載節(jié)點 - 微應(yīng)用加載完成后將掛載在該節(jié)點上 - 與上述qiankunVue3Layout.vue id一致
* activeRule: 子服務(wù)觸發(fā)的路由規(guī)則 - 觸發(fā)路由規(guī)則后將加載該微應(yīng)用 - 與上述創(chuàng)建子服務(wù)路由前綴一致
* props 共享數(shù)據(jù)到子服務(wù)
* sandbox 開啟沙箱
*/
name: 'xxx-vue3', // 根據(jù)實際情況來
entry: '//localhost:10200',
container: '#qiankunVue3Dom',
activeRule: genActiveRule('/qiankunVue3'),
props: msg, // 共享數(shù)據(jù)到子服務(wù)
sandbox: {
strictStyleIsolation: true
}
}
],
{
// 掛載前回調(diào)
beforeLoad: [
app => {
console.log('before load', app)
}
],
// 掛載后回調(diào)
beforeMount: [
app => {
console.log('before mount', app)
}
],
// 卸載后回調(diào)
afterUnmount: [
app => {
console.log('after unload', app)
}
]
}
)
src/main.js中引入
import './qiankun'
配置子服務(wù)
在主應(yīng)用注冊好了微應(yīng)用后烘跺,我們還需要對微應(yīng)用進(jìn)行一系列的配置。首先滤淳,我們在 Vue 的入口文件 main.js 中,導(dǎo)出 qiankun 主應(yīng)用所需要的三個生命周期鉤子函數(shù)砌左,另外脖咐,webpack 默認(rèn)的 publicPath 為 "" 空字符串铺敌,會基于當(dāng)前路徑來加載資源。我們在主應(yīng)用中加載微應(yīng)用時需要重新設(shè)置 publicPath屁擅,這樣才能正確加載微應(yīng)用的相關(guān)資源偿凭。增加如下代碼:
子服務(wù)新增@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__;
}
子服務(wù)修改: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;
// 如果是乾坤環(huán)境
if ((window as any).__POWERED_BY_QIANKUN__) {
const routes = router.routes;
// 在 render 中創(chuàng)建 VueRouter,可以保證在卸載微應(yīng)用時派歌,移除 location 事件監(jiān)聽弯囊,防止事件污染
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");
}
}
// 獨立運行時,直接掛載應(yīng)用
if (!(window as any).__POWERED_BY_QIANKUN__) {
render();
}
/**
* bootstrap 只會在微應(yīng)用初始化的時候調(diào)用一次胶果,下次微應(yīng)用重新進(jìn)入時會直接調(diào)用 mount 鉤子匾嘱,不會再重復(fù)觸發(fā) bootstrap。
* 通常我們可以在這里做一些全局變量的初始化早抠,比如不會在 unmount 階段被銷毀的應(yīng)用級別的緩存等霎烙。
*/
export async function bootstrap(props: any) {
console.log("VueMicroApp bootstrap", props);
}
/**
* 應(yīng)用每次進(jìn)入都會調(diào)用 mount 方法,通常我們在這里觸發(fā)應(yīng)用的渲染方法
*/
export async function mount(props: any) {
console.log("VueMicroApp mount", props);
render(props);
}
/**
* 應(yīng)用每次 切出/卸載 會調(diào)用的方法蕊连,通常在這里我們會卸載微應(yīng)用的應(yīng)用實例
*/
export async function unmount(props: any) {
console.log("VueMicroApp unmount", props);
// instance.$destroy();
instance = null;
Router = null;
}
在配置好了入口文件 main.js 后悬垃,我們還需要配置 webpack,使 main.js 導(dǎo)出的生命周期鉤子函數(shù)可以被 qiankun 識別獲取甘苍。
子服務(wù)修改: vue.config.js
// vue.config.js
// eslint-disable-next-line @typescript-eslint/no-var-requires
const path = require("path");
module.exports = {
devServer: {
// 監(jiān)聽端口
port: 10200,
// 關(guān)閉主機(jī)檢查尝蠕,使微應(yīng)用可以被 fetch
disableHostCheck: true,
// 配置跨域請求頭,解決開發(fā)環(huán)境的跨域問題
headers: {
"Access-Control-Allow-Origin": "*",
},
},
configureWebpack: {
resolve: {
alias: {
"@": path.resolve(__dirname, "src"),
},
},
// 這一塊是新增本次新增的
output: {
// 子服務(wù)的包名羊赵,這里與主應(yīng)用中注冊的微應(yīng)用名稱一致
library: "xxx-vue3", // 根據(jù)實際情況來
// 將你的 library 暴露為所有的模塊定義下都可運行的方式
libraryTarget: "umd",
// 按需加載相關(guān)趟佃,設(shè)置為 webpackJsonp_子服務(wù)名稱 即可
jsonpFunction: `webpackJsonp_xxx-vue3`, // 根據(jù)實際情況來
},
},
};
啟動
在主服務(wù)qiankunVue3Layout.vue啟動qiankun
主服務(wù)修改:qiankunVue3Layout.vue
<template>
<div class="cfpa-home-wrapper" id="main">
<!--頭部-->
<v-header v-if="showHeader"></v-header>
<!--側(cè)邊欄-->
<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">
<!--子服務(wù)渲染dom扇谣,這個id在后續(xù)將會用到-->
<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>
最后重新啟動主服務(wù)和子服務(wù)昧捷。訪問主服務(wù)/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.首先查一下 注冊子服務(wù)步驟中靡挥,下列字段是否正確
[圖片上傳失敗...(image-e9ac62-1679899817625)]
b. 是否在 配置主服務(wù)步驟中 main.js中導(dǎo)出3個生命周期函數(shù)
[圖片上傳失敗...(image-14bbd0-1679899817625)]
c. 是否在 配置主服務(wù)步驟中配置webpack
[圖片上傳失敗...(image-b9e340-1679899817625)]
流水線發(fā)版改造
目標(biāo)
主服務(wù)子服務(wù)項目使用同一個git服務(wù)及同一個流水線,實現(xiàn)一鍵提交鸯绿,一鍵發(fā)版跋破。
實現(xiàn)思路
將子服務(wù)拷貝到主服務(wù)根目錄里,并且將子服務(wù)的出包路徑修改為主服務(wù)的出包路徑(eg: 主服務(wù)出包路徑為/dist瓶蝴,則子服務(wù)的出包路徑為../dist/包名)毒返,打包構(gòu)建時先構(gòu)建主服務(wù)在構(gòu)建子服務(wù)
流水線構(gòu)建命令:
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
構(gòu)建完成之后會生成一個類似如下結(jié)構(gòu)的包
[圖片上傳失敗...(image-c2ea30-1679899817625)]
此時我們將這個包部署到服務(wù)器之后就可以通過 https://....com/訪問當(dāng)主服務(wù),通過https://....com/子服務(wù)包名 訪問當(dāng)子服務(wù)
至此舷手,我們只需要修改主服務(wù)引入子服務(wù)的入口地址為https://....com/子服務(wù)包名 既可拧簸。
實現(xiàn)步驟
將子服務(wù)拷貝至主服務(wù)根目錄(子服務(wù)不需要單獨git服務(wù),如子服務(wù)有g(shù)it男窟,刪除掉子服務(wù).git文件夾)盆赤,如圖
[圖片上傳失敗...(image-4dc00a-1679899817625)]
修改子服務(wù)靜態(tài)資源路徑及出包路徑(這里子服務(wù)包名為vue3Serve):
// vue.config.js
...
publicPath:'/vue3Serve/',
outputDir:"../dist/vue3Serve",
...
修改主服務(wù)注冊子服務(wù)時的入口
// 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
生產(chǎn):https://........com.cn/vue3Serve
修改流水線編排構(gòu)建命令
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
完事!
作者: 快落的小海疼