基於 React & TypeScript & Webpack 的微前端應用模板

王下邀月熊發表於2019-01-28

基於 React & TypeScript & Webpack 的微前端應用模板

m-fe/react-ts-webpack

Web 開發導論/微前端與大前端一文中,筆者簡述了微服務與微前端的設計理念以及微前端的潛在可行方案。微服務與微前端,都是希望將某個單一的單體應用,轉化為多個可以獨立執行、獨立開發、獨立部署、獨立維護的服務或者應用的聚合,從而滿足業務快速變化及分散式多團隊並行開發的需求。如康威定律(Conway’s Law)所言,設計系統的組織,其產生的設計和架構等價於組織間的溝通結構;微服務與微前端不僅僅是技術架構的變化,還包含了組織方式、溝通方式的變化。微服務與微前端原理和軟體工程,物件導向設計中的原理同樣相通,都是遵循單一職責(Single Responsibility)、關注分離(Separation of Concerns)、模組化(Modularity)與分而治之(Divide & Conquer)等基本的原則。

基於 React & TypeScript & Webpack 的微前端應用模板

fe-boilerplates 是筆者的前端專案模板集錦,包含了單模組單頁面、單模組多頁面、(偽)多模組單頁面、微前端專案等不同型別的模板,其中微前端專案模組 m-fe/react-ts-webpack 與前者的區別即在於微前端中的各個模組能夠獨立開發,獨立版本釋出,獨立部署,獨立載入。分散式協作勢必會帶來協同以及開發流程上的挑戰,在設計微前端專案架構的時候開發易用性也是非常重要的考量點。在年度總結中我也討論了使用 TS 面向重構程式設計的意義,歡迎參考 Backend-Boilerplates/node 中的 ts-* 專案,使用 TS 進行全棧開發。

當我們考量專案框架、模板或者腳手架的時候,首先想到的點就是希望儘可能對上層遮蔽細節,但是對於長期維護的、多人協作的中大型專案而言,如果專案的主導者直接使用了部分抽象的腳手架,不免會給未來的更新、迭代帶來一定的技術負債;同時,目前也有很多成熟的工程化腳手架,因此筆者選擇以專案模板的形式抽象出微前端中所需要的部分。儘可能地遵循簡約、直觀的原則,減少抽象/Magic Function 等;大型專案可能會抽象出專用的開發工具流,但是對於大部分專案而言,在現有框架/工具鏈的基礎上進行適當封裝會是較優選擇。

# 拉取並且提取出子專案
git clone https://github.com/wxyyxc1992/fe-boilerplate
cp fe-boilerplate/micro-frontend/react-ts-webpack ../

# 新增全域性的依賴更新工具
$ yarn global add npm-check-updates

# 為各個子專案安裝依賴,以及連結各個子專案
$ npm run bootstrap && npm run build

# 執行預編譯操作
$ npm run build

# 以基礎模式執行 Host APP,此時 Host APP 作為獨立應用啟動
$ cd packages/rtw-host-app & npm run dev:sa

# 以標準模式執行子應用
$ cd packages/rtw-mobx-app & npm run dev

# 返回根目錄
$ cd .. & npm start
複製程式碼

值得說明的是,微前端作為概念對於不同人承載了不同的考量,其實現方式、落地路徑也是見仁見智,若有不妥,敬請指教。

Features

  • 非 APP 類可單獨釋出,APP 類可單獨執行,與釋出。釋出版本可包含 ES, CJS, UMD 等,dist 目錄下包含 ES/CJS 模組,build 目錄下包含 APP 完整資源以及 UMD 模組。
  • 版本控制: 子應用資源不使用 Hash 方式,而是使用語義化版本,/[cdnHost]/[projectName]/[subAppName]/[x.y.z]/index.{js,css}
  • 樣式,LESS 檔案支援 CSS Modules,CSS/SCSS 使用標準 CSS
  • 狀態管理,靈活支援 Redux/MobX/Dva 等不同的狀態管理框架,對於 Redux 提供全域性統一的 Store 宣告

Structure | 專案結構

完整的微前端應用,可能會包含以下組成部分:

  • Module | 模組: 模組是可單獨編譯、釋出的基礎單元,基礎模式下可直接打包入主應用,標準模式下多專案共用時可單獨打包為 AMD/UMD 格式,通過 SystemJS 引入
  • Page | 頁面: 頁面不可單獨編譯,使用 Webpack SplitChunk 或其他機制進行非同步載入
  • App | 應用: 應用是對模組的擴充套件,是實際使用者可見的部分
  • Widget | 控制元件: 控制元件是特殊的模組,譬如通用的無業務元件等
  • Extension | 擴充套件: 擴充套件是特殊的應用,提供了跨模組的通用功能,類似於 Chrome Extension 的定位

基於此,我們可以將某個微前端應用抽象為如下不同的模組組:

基礎模組:

  • rtw: 根目錄,public 目錄下包含了部分跨模組整合測試的程式碼

核心模組:

  • rtw-core/rtw-sdk/rtw-shared: 暴露給子應用可用的通用基礎類、模型定義、部分無介面獨立模組等。rtw-core 建議不放置介面相關,使用 Jest UT 方式進行功能驗證。
  • rtw-bootstrap: 完整專案級別編譯與啟動入口,包含專案的執行時配置、依賴配置\訊息匯流排、註冊中心、核心模組載入機制等。
  • rtw-host-app: 提供介面基礎容器,譬如應用標準的 Layout,Menu 等元件;提供 Redux 核心 Store。

子業務應用:

  • rtw-mobx-app: MobX 示例應用
  • rtw-redux-app: Redux 示例應用

擴充套件模組:

  • rtw-widgets: 包含部分業務型控制元件,提供給所有的子應用使用,提取通用業務邏輯、對上遮蔽部分第三方依賴關係,類似於完整的 OSS 檔案上傳控制元件等。
  • rtw-extensions: 包含部分業務無關的通用型外掛,類似於 Chrome Extension 的定位。
  • rtw-worker: 包含通用的 Web Worker & WASM 計算模組,子應用內也可以通過 Buffer 方式直接引入自定義的 Worker

如果希望在子應用 A 中載入子應用 B 的例項,則應該使用類似於依賴注入的方式,從統一的註冊中心中獲取該例項物件。所有各個模組共享的基礎庫,都必須以 UMD 模式載入到全域性;rtw-host-app 中宣告與使用需要展示哪些模組,rtw-bootstrap 中註冊可提供的 UMD 子模組。

開發模式

筆者一直推崇漸進式的工程架構,因此該模板對於複雜度要求較低的專案而言,可以直接從基礎模式啟動,與其他 TS 專案並無太大區別。

基礎模式

基礎模式類似於(偽)多模組單頁面,僅有唯一的 Host APP 作為編譯與執行的入口,其他包體(譬如 rtw-core)直接打包進主包體中,不使用 SystemJS 進行獨立載入。

rtw-core

rtw-core 及相似的庫承載了公共的結構定義、工具類等,在該包體目錄下執行 npm run build 命令即可以生成 ES/CJS/UMD 等多種型別檔案,以及 types 型別定義;可以直接通過 npm publish 來發布到公共/私有的 NPM 倉庫中。

其他包體通過 NPM 安裝 rtw-core 並使用,如果以標準模式執行,則需要首先載入該庫到全域性作用域,利用 RequireJS/SystemJS 等工具遵循 AMD 規範來注入到其他依賴的庫/應用中。

值得一提的是,對於子應用中,如果存在需要共享元件/型別的情景。對於型別資訊,建議是將子應用同樣編譯打包釋出到 NPM 倉庫中,純元件可以直接引入,對於業務元件建議通過全域性的註冊中心來獲取。

rtw-host-app

在 rtw-host-app 包下,執行 npm run dev:sa 命令,會從 src/index.sa 檔案啟動應用;如上文所述,該模式僅會基於 Webpack Splitted Chunk 進行非同步載入,其開發流程與標準的單模組應用並無區別。

標準模式

rtw-bootstrap & rtw-host-app

rtw-bootstrap 是微前端應用的實際啟動點,其核心功能是執行依賴與子應用的註冊。在啟動時,其會根據傳入的 __HOST_APP____DEV_APP__ 等變數資訊完成應用的順序載入與啟動。在標準模式下,rtw-host-app 的入口是 src/index 檔案,該模式下,index 檔案會對外暴露 render 函式,該函式會由 rtw-bootstrap 注入 importApp 函式來執行子應用載入:

export function render(_importApp: Function) {

  importApp = _importApp;

  ReactDOM.render(
    ...
  );
}
複製程式碼

換言之,rtw-bootstrap 提供了應用載入的能力,而 rtw-host-app 決定了應該載入哪些應用;在實際案例中,我們應該將使用者許可權控制、選單與子應用資訊獲取等業務操作放置在 rtw-host-app 中。

rtw-redux-app & rtw-mobx-app

這裡以 rtw-mobx-app 為例介紹如何進行子應用開發,如果是專案已經發布上線,那麼我們可以通過 Resource Overrides 等線上資源請求轉發的工具將線上資源請求轉發到本地伺服器。在進行本地開發時,因為子應用本身並不會包含 ReactDOM.render 或者類似的將 Virtual DOM 渲染到介面的函式,因此在執行 npm run dev 之後,本地會開啟生成 UMD 檔案的 Webpack Dev Server。參考子應用的 public/index.html 檔案:

<script src="./bootstrap/static.js" type="text/javascript"></script>
<script src="./bootstrap/runtime.js" type="text/javascript"></script>
<script src="./bootstrap/vendors.js" type="text/javascript"></script>

<script>
  // 聯調環境
  //   window.__HOST_APP__ = {
  //     id: 'host',
  //     name: 'HOST APP',
  //     module: 'http://0.0.0.0:8081/index.js',
  //     css: 'http://0.0.0.0:8081/index.css'
  //   };

  // 正式開發環境
  window.__HOST_APP__ = {
    title: 'HOST APP',
    module: '/release/rtw-host-app/index.js',
    css: '/release/rtw-host-app/index.css'
  };

  window.__DEV_APP__ = { id: 'dev', name: 'DEV APP', module: '/index.js' };
</script>
<script src="./bootstrap/index.js" type="text/javascript"></script>
複製程式碼

可以看出子應用的啟動需要依賴於 rtw-bootstrap 以及 rtw-host-app,如果專案已經發布上線,那麼建議是直接從 CDN 載入資源;否則可以將資源放置到 public/release 目錄下。如果本地需要同時除錯 Host APP,則直接也將 Host APP 以開發方式執行(npm run dev),然後直接引入 Webpack Dev Server 生成的資源地址即可。

延伸閱讀

相關文章