qiankun引入vue3子項目實踐

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

完事!
作者: 快落的小海疼

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末贾富,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子牺六,更是在濱河造成了極大的恐慌颤枪,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,427評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件淑际,死亡現(xiàn)場離奇詭異畏纲,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)庸追,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,551評論 3 395
  • 文/潘曉璐 我一進(jìn)店門霍骄,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人淡溯,你說我怎么就攤上這事读整。” “怎么了咱娶?”我有些...
    開封第一講書人閱讀 165,747評論 0 356
  • 文/不壞的土叔 我叫張陵米间,是天一觀的道長。 經(jīng)常有香客問我膘侮,道長屈糊,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,939評論 1 295
  • 正文 為了忘掉前任琼了,我火速辦了婚禮逻锐,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘雕薪。我一直安慰自己昧诱,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,955評論 6 392
  • 文/花漫 我一把揭開白布所袁。 她就那樣靜靜地躺著盏档,像睡著了一般。 火紅的嫁衣襯著肌膚如雪燥爷。 梳的紋絲不亂的頭發(fā)上蜈亩,一...
    開封第一講書人閱讀 51,737評論 1 305
  • 那天,我揣著相機(jī)與錄音前翎,去河邊找鬼稚配。 笑死押搪,一個胖子當(dāng)著我的面吹牛杈笔,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播搓扯,決...
    沈念sama閱讀 40,448評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼愤惰!你這毒婦竟也來了苇经?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,352評論 0 276
  • 序言:老撾萬榮一對情侶失蹤宦言,失蹤者是張志新(化名)和其女友劉穎扇单,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體奠旺,經(jīng)...
    沈念sama閱讀 45,834評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡蜘澜,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,992評論 3 338
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了响疚。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片鄙信。...
    茶點故事閱讀 40,133評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖忿晕,靈堂內(nèi)的尸體忽然破棺而出装诡,到底是詐尸還是另有隱情,我是刑警寧澤践盼,帶...
    沈念sama閱讀 35,815評論 5 346
  • 正文 年R本政府宣布鸦采,位于F島的核電站,受9級特大地震影響咕幻,放射性物質(zhì)發(fā)生泄漏渔伯。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,477評論 3 331
  • 文/蒙蒙 一肄程、第九天 我趴在偏房一處隱蔽的房頂上張望锣吼。 院中可真熱鬧,春花似錦蓝厌、人聲如沸玄叠。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,022評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽诸典。三九已至描函,卻和暖如春崎苗,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背舀寓。 一陣腳步聲響...
    開封第一講書人閱讀 33,147評論 1 272
  • 我被黑心中介騙來泰國打工胆数, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人互墓。 一個月前我還...
    沈念sama閱讀 48,398評論 3 373
  • 正文 我出身青樓必尼,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子判莉,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,077評論 2 355

推薦閱讀更多精彩內(nèi)容