一個基於 vite 的微前端框架 - vite-micro

一、微前端的由來

由於現代項目迭代應用越來越龐大並且業務複雜,業務模塊之間的關係錯綜複雜,跨組多人協作開發難以維護,爲解決上述問題,微前端框架思想應運而生,

按照 qiankun 和 Garfish 等框架的設計理念,巨石應用會拆分爲各個應用並獨立維護,並在頁面上整合各個應用,在用戶層感知爲一個單品。

二、業界微前端方案難以解決的痛點

但是 qiankun 和 Garfish 等框架 是以應用維度進行載入,且是一個比較重量的解決方案

比如我們在業務中會遇到如下問題:

  1. 子應用過多,開發時服務器啓動數量比較多,雖然有工具可以降低啓停維護成本,但性能上開銷比較大

  2. 發佈時,需要多個子應用按照業務指定順序發佈,在沒有一個很好的發佈平臺自動管理的情況下,發佈簡直就是噩夢

  3. 我需要僅僅在 A 應用中複用 b 應用的一個組件,在 b 應用中複用 A 應用的一個組件,此時一般的做法有以下幾種:

(a)將組件 copy 一份,顯然這種做法快速但不利於後期維護

(b)將組件提取到組件庫中,但如果組件的複用範圍比較小,又或者包含較多業務邏輯,此時放到組件庫多少有些不合適,且不利於與項目統一維護

尤其在 tob 的大型 saas 應用中,會不可避免的將整個企業管理生命週期劃分各個業務模塊,甚至劃分各個業務組來分別管理對應的業務項目,但是實際情況是各個業務模塊間存在着比較複雜的業務關聯,比如:用戶需要在訂單模塊展示和編輯企業信息,用戶需要在企業控制後臺管理訂單業務等

三、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 提供加載遠程腳本的版本管理功能

  1. 默認配置會每次加載最新版本的遠程組件,vite-micro 默認去獲取遠程應用的 remoteEntrys 腳本,remoteEntrys 負責引入遠程應用的最新版本

  2. 可以配置每次加載指定版本的遠程組件

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()
}
  1. Host 端拿到 Remote 微應用入口文件後,會執行裏面的 mount 方法初始化並掛載微應用
  2. 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 端使用遠程模塊

  1. 使用微組件方式
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");
  1. 使用微應用入口方式
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 需要注意的地方

  1. vite-micro 目前是輕量化的微前端框架,滿足了基礎和常用的功能,還有些特定場景功能待完善

2 . vite-micro. 目前 沒有提供沙箱的功能,這就要求 每個微應用需要在開發時約定代碼的規則,定義 window 下的全局變量儘可能提供命名空間,css 名稱儘量符合特定項目特徵

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/HODIYnhQKU6Gi4nCjZiq8A