一個基於 vite 的微前端框架 - vite-micro
一、微前端的由來
由於現代項目迭代應用越來越龐大並且業務複雜,業務模塊之間的關係錯綜複雜,跨組多人協作開發難以維護,爲解決上述問題,微前端框架思想應運而生,
按照 qiankun 和 Garfish 等框架的設計理念,巨石應用會拆分爲各個應用並獨立維護,並在頁面上整合各個應用,在用戶層感知爲一個單品。
二、業界微前端方案難以解決的痛點
但是 qiankun 和 Garfish 等框架 是以應用維度進行載入,且是一個比較重量的解決方案
比如我們在業務中會遇到如下問題:
-
子應用過多,開發時服務器啓動數量比較多,雖然有工具可以降低啓停維護成本,但性能上開銷比較大
-
發佈時,需要多個子應用按照業務指定順序發佈,在沒有一個很好的發佈平臺自動管理的情況下,發佈簡直就是噩夢
-
我需要僅僅在 A 應用中複用 b 應用的一個組件,在 b 應用中複用 A 應用的一個組件,此時一般的做法有以下幾種:
(a)將組件 copy 一份,顯然這種做法快速但不利於後期維護
(b)將組件提取到組件庫中,但如果組件的複用範圍比較小,又或者包含較多業務邏輯,此時放到組件庫多少有些不合適,且不利於與項目統一維護
尤其在 tob 的大型 saas 應用中,會不可避免的將整個企業管理生命週期劃分各個業務模塊,甚至劃分各個業務組來分別管理對應的業務項目,但是實際情況是各個業務模塊間存在着比較複雜的業務關聯,比如:用戶需要在訂單模塊展示和編輯企業信息,用戶需要在企業控制後臺管理訂單業務等
-
使用 qiankun 框架雖然可以將各個應用整合在一個頁面,但是難以比較細粒度的複用各個應用的業務代碼和功能。
-
Garfish 雖然可以通過打包配置來生成單個業務腳本入口提供給主應用引入,尤其在 vue 項目中想要以正常引入組件方式來複用遠程業務組件,並且公共基礎庫又不想重新加載,則還需要做更多的事情。
三、vite-micro 的理念
爲實現上述的設想,vite-micro 基於 vite 將 模塊聯邦思想融入到微前端框架中,以組件維度進行載入,更細粒度的來複用業務,在 vite-micro 中,一個腳本,一個應用頁面也可以稱爲一個組件,微應用類比後端的微服務,向外暴露的組件可以叫做接口,接口的 request 可以叫做依賴或者公共依賴,接口的 response 可以返回前端業務組件, vite-micro 使得微應用可以提供內部 api 供其他微應用相互調用,也可以提供開放 api 給客戶使用,從而實現業務的閉環。
vite-micro 架構圖
四、vite-micro 的核心原理
1. 服務器按需啓動機制
vite-micro 基於 monorap 的架構構建了一個全局服務器,並根據 workspace 裏面的模塊包裝爲中間件,當訪問頁面時,中間件會根據路由地址動態的啓動子應用的服務,這個服務本質是 vite 編譯對應的配置文件返回的一箇中間件。
vite-micro 按需啓動原理
2. 便於路由配置
vite-micro 在追求輕裝的前提下,不對路由進行過渡設計,將路由的權力交給項目,vue-router 或者 react-router 等;vite-micr 專注於幫助項目生成遠程組件,加載遠程組件,比如 vite-micro 提供 entryImportVue 接口加載遠程組件並封裝爲 vue 組件,開發者按照正常的路由配置思路配置路由即可。
import { entryImportVue, remoteImport } from 'vite-micro/client'
const mainRoute = [
{
path: '/home',
component: () => import('../../views/Home.vue'),
},
{
path: '/user',
component: () => entryImportVue('remote_app/entry'),
},
{
path: '/button',
component: () => remoteImport('remote_app/Button'),
},
]
3. 模塊聯邦
vite-micro 底層基於 @originjs/vite-plugin-federation,模塊的加載方式分爲生產模式和開發模式,生產模式下模塊的加載和打包方式採用 @originjs/vite-plugin-federation,開發模式下模塊無需打包,採用 ES6 原生的 import 方式加載代碼,vite-micro 分爲 node 和 client 兩個結構,node 負責打包生成遠程組件,client 負責幫助應用加載遠程組件。
4. 子應用生命週期
應用分爲 mounted,unMouted, destroyed 這 3 個階段(後續有需要會擴展)
vite-micro 生命週期
5. 版本管理
由於遠程組件會被遠程應用依賴使用,遠程組件的維護和發佈就變得更爲謹慎。vite-micro 提供加載遠程腳本的版本管理功能
-
默認配置會每次加載最新版本的遠程組件,vite-micro 默認去獲取遠程應用的 remoteEntrys 腳本,remoteEntrys 負責引入遠程應用的最新版本
-
可以配置每次加載指定版本的遠程組件
remotes: {
// 默認會引入loginRemote 應用的remoteEntrys.js , 這個文件會去加載該應用最新版本的remoteEntry文件
'loginRemote': {
url: `/assets/login`
},
// 會將 `/assets/login/0.0.1/remoteEntry.js` 作爲入口文件引入
'userRemote': {
url: `/assets/login`,
filename: '0.0.1/remoteEntry.js'
},
}
五、如何使用
vite-micro 架構需要採用 monorapo 項目結構,可參考 example 的項目結構,
packages 裏面通常會有 2 個或 2 個以上的微應用,一個作爲 Host 端,一個作爲 Remote 端。
步驟一:Remote 端配置暴露的模塊
// vite.config.js
import { federation } from 'vite-micro/node'
export default {
build: {
// 如果出現top level await問題,則需使用import topLevelAwait from 'vite-plugin-top-level-await'
target: ['chrome89', 'edge89', 'firefox89', 'safari15'],
// 輸出目錄
outDir: `${path.resolve(__dirname, '../../dist')}`,
// 資源存放目錄
assetsDir: `assets/user/${packageJson.version}`,
},
plugins: [
federation({
mode
// 需要暴露的模塊,
//遠程模塊對外暴露的組件列表,遠程模塊必填
exposes: {
Button: './src/Button.vue',
entry: './src/bootstrap.ts',
},
shared: ['vue'],
}),
],
}
這裏的 entry 對應的 bootstrap.ts 來源於 main.ts(項目的入口文件) , 如果有以下配置,則需使用 bootstrap.ts, 否則會產生衝突錯誤
rollupOptions: {
input: main: `${path.resolve(__dirname, './src/main.ts')}`,
}
// bootstrap.ts
export { mount, unMount } from './main'
步驟二:Remote 端配置應用入口文件(如果 Host 端需要調用 Remote 微應用)
// main.ts
import { createApp } from 'vue'
import App from './App.vue'
let app: any = null
export async function mount(name: string, base: string) {
app = createApp(App)
// 其他配置......
app.mount(name)
console.log('start mount!!!', name)
return app
}
export function unMount() {
console.log('start unmount --->')
app && app.$destroy()
}
- Host 端拿到 Remote 微應用入口文件後,會執行裏面的 mount 方法初始化並掛載微應用
- mount 和 unmount 方法 約定導出
步驟三:Host 端配置暴露的模塊
// vite.config.js
import { federation } from 'vite-micro/node'
export default {
build: {
// 如果出現top level await問題,則需使用import topLevelAwait from 'vite-plugin-top-level-await'
target: ['chrome89', 'edge89', 'firefox89', 'safari15'],
// 輸出目錄
outDir: `${path.resolve(__dirname, '../../dist')}`,
// 資源存放目錄
assetsDir: `assets/main/${packageJson.version}`,
},
plugins: [
federation({
mode
remotes: {
loginRemote: {
url: `/assets/login`,
},
userRemote: {
url: '/assets/user',
},
},
shared: ['vue'],
}),
],
}
步驟四:Host 端使用遠程模塊
- 使用微組件方式
import { createApp, defineAsyncComponent } from "vue";
import { remoteImport } from 'vite-micro/client'
const app = createApp(Layout);
...
const RemoteButton = defineAsyncComponent(() => remoteImport("remote_app/Button"));
app.component("RemoteButton", RemoteButton);
app.mount("#root");
- 使用微應用入口方式
import { entryImportVue, remoteImport } from 'vite-micro/client'
const mainRoute = [
{
path: '/home',
component: () => import('../../views/Home.vue'),
},
{
path: '/user',
component: () => entryImportVue('remote_app/entry'),
},
{
path: '/button',
component: () => remoteImport('remote_app/Button'),
},
]
- entryImportVue('remote_app/entry') 在本質上也是一個微組件,同樣可以使用微組件方式調用
- 對於 Remote 模塊暴露的腳本有時並非 vue 組件,也可能是 React 組件或其他,也可能是遠程應用的入口文件,這種類型的腳本很明顯是無法直接被 Host 模塊 vue 項目所消費的,entryImportVue 的內部使用一個簡單的 vue 組件將這些腳本包裹進來形成一個可以直接被 vue 項目使用的組件
- 對於可以直接被 Host 模塊直接引用的遠程組件,直接使用 remoteImport 即可
七、vite-micro 需要注意的地方
- vite-micro 目前是輕量化的微前端框架,滿足了基礎和常用的功能,還有些特定場景功能待完善
2 . vite-micro. 目前 沒有提供沙箱的功能,這就要求 每個微應用需要在開發時約定代碼的規則,定義 window 下的全局變量儘可能提供命名空間,css 名稱儘量符合特定項目特徵
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/HODIYnhQKU6Gi4nCjZiq8A