作者 | 大果

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
對應的子應用 bundle 了。/seller
- 通過劫持
和history.pushState
兩個 API,同時監聽history.replaceState
事件,保證能夠捕獲到到所有路由變化。當捕獲到路由變化時,根據路由查找對應的微應用,如果對應的還是目前這個微應用則什麼事情都不做,如果對應的是新的一個微應用則解除安裝之前的微應用,同時加載新的微應用并渲染。popstate
- 架構應用中包含系統 Layout,我們需要将微應用渲染到 Layout 裡面,但是單頁面應用都是直接通過
的方式渲染,如果直接執行那麼渲染的位置是無法被控制的,是以 icestark 提供了一個ReactDOM.render(<App />, document.getElementById('#root'))
的 API 保證微應用能夠渲染到正确的節點裡。getMountNode()
微子產品加載
微子產品主要是針對一些跨應用的共享子產品、二方接入子產品等場景,此部分能力正在建設中,具體可參考文末的 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」
把握阿裡巴巴前端新動向