作者 | 大果
2017 年中旬,飛冰(ICE)團隊接到一個叫做「阿里創作平臺」的項目,這個產品為創作者提供了入駐、帳號管理、內容管理、內容發佈、粉絲運營、數據分析等等非常完備的功能,頁面數 50+、項目一期有 3-6 個前端外加部分後端同學同時開發、業務未來有二方業務接入的需求……針對這些訴求,傳統的單頁面應用方案實在有點力不從心,因此在詳細的技術評估之後我們自研了一套叫做 AppLoader 的方案,AppLoader 即應用加載器,我們將整個系統拆分成多個應用(倉庫)然後通過 AppLoader 進行管理加載保證路由的正常渲染,最終實現對一個巨型系統的解耦。
AppLoader 誕生之後,先後服務過幾個業務,但因為場景比較特殊因此我們一直未將這個方案對外開源,直到 2019 年年初,一方面社區中微前端的概念逐漸普及,另一方面在阿里內外也出現越來越多的類似業務場景,因此我們對 AppLoader 做了一次能力和品牌的升級,同時面向社區開源,升級之後的品牌便是我們開源在 GitHub 上的 icestark,歡迎 star/pr 或者提 issue。
技術方案對比
如前文所說,兩年多之前我們接到「阿里創作平臺」這個項目,這個產品承載了創作者從入駐到創作完整的生命週期,相比於普通業務有以下兩個不同點:頁面數非常多、頻繁的多人協作與多需求並行、未來有二方業務接入的需求,針對這些差異點我們也對當下主流的一些方案做了一輪分析對比。
單/多頁面應用
無論是 SPA 還是 MPA,本質上都是一個唯一的項目倉庫,即便我們將一些公共組件抽離出去單獨維護,最終也是需要在項目倉庫中進行打包構建,這樣的單項目倉庫所帶來的問題:
- 代碼量達到一定量的時候,單次構建時間很長,開發&發佈效率極低
- 涉及到二方/三方的依賴升級會影響整個應用,加之頁面數很多導致迴歸成本極高,長期下去面對類似 React 版本升級這樣的技術訴求很難落地
- 對於多人協作、多需求並行非常不友好,需要花費大量的精力在協作流程上
iframe
將系統拆分成多個應用,每個應用獨立開發獨立部署,然後通過 iframe 的方式將這些應用嵌入到 portal 系統中,這個方案解決了很多協作、隔離之類的問題,但 iframe 的體驗問題一直是個難以解決的問題:
- iframe 體驗問題:頁面加載慢;雙滾動條問題;內部蒙層無法遮罩到外部框架,同時佈局無法居中;內部跳轉後外部無響應,刷新後又會回到 iframe 首頁……
- 每個應用依然需要依賴服務部署,需要有域名,應用的部署成本偏高
關於 iframe 的體驗問題,有的可以解決,有的幾乎無法解決,因此結合體驗的重要性我們最終放棄了 iframe 這個方案。
封裝框架組件
封裝一個統一的框架 UI 組件,發佈到 NPM,然後每個應用自行接入框架組件,但是這個方案有以下幾個問題:
- 用戶訪問入口不統一,本質還是多個系統,只是看起來框架是一致的
- 每個應用依然需要依賴服務部署,需要有域名,應用的部署成本偏高
這個方案更適合於多個業務間有統一的框架訴求,但從業務、前端、後端服務都比較獨立,沒有中心化管理的需求。
AppLoader 與 icestark
2017 年中旬我們還沒有瞭解到「微前端」的概念,因此結合業務訴求自研了 AppLoader 的類微前端解決方案,再到 2019 年將整體品牌升級到 icestark。
關於 icestark
這個章節核心介紹 icestark 的設計思路以及如何使用。
架構與概念
上圖即整體的設計與分層:
- 框架應用:又叫主應用、宿主應用、底座應用,負責系統整體佈局、微應用的註冊加載渲染、微模塊的管理
- 微應用:又叫子應用,一般是一個 SPA 應用,可以獨立運行,也可以註冊到框架應用中運行
- 微模塊:針對多個微應用共享的模塊或者頁面上的一些二方模塊,微模塊與傳統的業務組件類似,特殊點是微模塊通過動態加載資源並渲染,而普通的業務組件一般是通過 npm 引入靜態打包編譯
之所以將微應用和微模塊的概念獨立開來,是因為兩者的使用場景和表現差異都是比較大的,微應用只會同時存在一個,需要考慮路由的問題,微模塊會同時存在多個因此需要核心考慮沙箱隔離、公共依賴提取等問題。
框架應用
框架應用的 API 設計我們參考了 react-router,react-router 是頁面級路由,而 icestark 是應用級路由。同時為了保證開發體驗,目前框架應用我們跟 React 做了耦合,讓開發者可以通過 jsx 的方式聲明應用路由,因此框架應用必須基於 React,未來我們也會結合用戶訴求提供 vue 版本的 icestark。
// src/app.js
import React from 'react';
import { AppRouter, AppRoute } from '@ice/stark';
export default class App extends React.Component {
render() {
return (
<BasicLayout>
<AppRouter>
<AppRoute
path="/seller"
title="商家平臺"
url={[
'//unpkg.com/icestark-child-seller/build/js/index.js',
'//unpkg.com/icestark-child-seller/build/css/index.css',
]}
/>
<AppRoute
path="/settings"
title="設置"
entry="//unpkg.com/icestark-child-seller/build/index.html"
/>
<AppRoute
path="/"
title="通用頁面"
render={() => {
return (
<ReactRouter>
<Switch>
<Route path="/home" component={Home}>
<Route path="/about" component={About}>
</Switch>
</ReactRouter>
)
}}
>
</AppRouter>
</BasicLayout>
);
}
}
如上所述,每個 AppRoute 就對應一個微應用,微應用支持幾種不同的註冊方式:
- url:對應渲染微應用需要的資源,針對微應用資源情況比較明確的情況
- entry:對應微應用構建出來的 html,針對微應用資源情況不明確,比如有 external、 不同的 vendor、資源地址有 hash 等場景
- render:可以渲染一個自定義的 React 組件,比如渲染一個 iframe,甚至框架應用自身的一個 SPA 應用
微應用加載
微應用通常是一個 SPA 應用,支持 React/Vue/Angular 等不同的應用類型,在傳統 SPA 應用基礎上添加相關的應用聲明週期註冊方法即可:
import React from 'react';
import ReactDOM from 'react-dom';
import { isInIcestark, getMountNode, registerAppEnter, registerAppLeave } from '@ice/stark-app';
import App from './App';
if (isInIcestark()) {
registerAppEnter(() => {
ReactDOM.render(<App />, getMountNode());
});
registerAppLeave(() => {
ReactDOM.unmountComponentAtNode(getMountNode());
});
} else {
ReactDOM.render(<App />, document.getElementById('ice-container'));
}
對於微應用的加載與渲染,除了將其正常渲染出來,核心要解決的是路由跳轉的問題,基本思路:
- 建立微應用和路由的映射關係,為每個微應用分配一個基準路由如
/seller
,這個微應用保證所有的路由定義在/seller
下,那麼當從其他路由跳轉到/seller
路由時我們就可以加載渲染/seller
對應的子應用 bundle 了。 - 通過劫持
history.pushState
和history.replaceState
兩個 API,同時監聽popstate
事件,保證能夠捕獲到到所有路由變化。當捕獲到路由變化時,根據路由查找對應的微應用,如果對應的還是當前這個微應用則什麼事情都不做,如果對應的是新的一個微應用則卸載之前的微應用,同時加載新的微應用並渲染。 - 框架應用中包含系統 Layout,我們需要將微應用渲染到 Layout 裡面,但是單頁面應用都是直接通過
ReactDOM.render(<App />, document.getElementById('#root'))
的方式渲染,如果直接執行那麼渲染的位置是無法被控制的,因此 icestark 提供了一個getMountNode()
的 API 保證微應用能夠渲染到正確的節點裡。
微模塊加載
微模塊主要是針對一些跨應用的共享模塊、二方接入模塊等場景,此部分能力正在建設中,具體可參考文末的 issue 鏈接。
沙箱隔離
目前 icestark 主要服務的是二方可控的業務場景,因此從整體設計上我們優先關注與傳統 SPA 應用的開發體驗一致性,次要關注沙箱隔離。如果有三方接入的場景,icestark 也支持了 render + iframe 的方案。
腳本隔離
基於 Proxy 能力對 window 做了一層代理,保證每個微應用/微模塊渲染時使用的都是獨立的 window 對象,進而實現全局 window 的隔離,放置應用汙染 window 導致系統出錯,我們將這部分能力封裝為 @ice/sandbox
,因此在其它類似的場景也可以獨立使用。
樣式隔離
- 針對框架應用與微應用基礎組件樣式衝突的問題,推薦框架應用上通過工程能力將基礎組件的 Class 前綴替換掉。
- 微應用層面建議使用 CSS Modules,通過工程保障不引入全局樣式。
存在的問題
當然,跟其他方案一樣,微前端的方案也有自身的缺陷,因此還是需要結合業務情況來判斷是否使用,而不是一股腦什麼東西都往微前端上靠,這裡列舉兩個問題:
- 存在一定的技術複雜度,結合對社區同學的一些答疑,微前端整體還是增加了一些複雜度的,因此在落地微前端方案需要確保有一個相對資深的同學進行整體的把控,避免引入新的問題
- 微前端的框架應用本質是一箇中心化的系統,我們建議微應用盡量降低對中心化系統的依賴度,比如提供 React、各種 API 甚至一些組件,因為依賴越多意味著中心化系統的變更風險越高
業務場景與價值
大型系統
面向文章開頭說的大型系統,微前端帶來的價值:
- 多人協作成本:按照業務域區分成不同的應用,每個應用獨立開發獨立部署,保證開發效率
- 穩定性:單個應用可升級,不需要依賴全局
- 技術選擇靈活性:新的應用可以選擇新版本的技術體系(比如 [email protected]),甚至某些簡單的應用也可以選擇可視化搭建的方式
- 二方開放能力:二方業務可按照規範開發微應用
portal 系統
很多中小公司裡會有很多小系統,這些系統很多時候都是獨立開發部署的,系統間沒有任何複用的能力,如果引入微前端:
- 部署成本:單個應用只需要發佈資源,不需要考慮域名、Nginx 託管之類的問題
- 通用能力的複用:諸如通用的登錄、鑑權等邏輯,不需要每個應用重複實現
- 可監控可管理:能夠收集到所有應用的信息、技術框架、版本等信息,進而可以做到更好的管控
業務落地
icestark 目前在阿里內部落地了 20 個左右的平臺型系統,不同系統可能有 5、10 甚至數量更多的微應用。
阿里創作者平臺
包含 20+ 子應用,其中 5-8 個子應用由二方業務開發。
阿里健康-熙牛醫療雲醫院信息系統
淘系小二工作臺
面向淘系運營小二的後臺都將已子應用的方式接入小二工作臺,打造面向運營小二的操作系統。
未來
icestark 目前整體的能力已經趨於完善,接下來我們會結合業務訴求持續完善解決方案,比如更好的權限方案、埋點方案等等,幫助業務更加簡單的落地微前端方案。歡迎大家關注 icestark ,關注我們的進展。
相關鏈接
關注「Alibaba F2E」
把握阿里巴巴前端新動向