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

m-fe/react-ts-webpack

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

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 生成的資源地址即可。

延伸閱讀

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章