2017 年中旬,ICE 團隊接到一個叫作「阿里創做平臺」的項目,這個產品爲創做者提供了入駐、賬號管理、內容管理、內容發佈、粉絲運營、數據分析等等很是完備的功能,頁面數 50+、項目一期有 3-6 個前端同時開發、業務將來有二方業務接入的需求……針對這些問題,傳統的單頁面應用方案實在有點力不從心,所以在詳細的技術評估以後咱們最終自研了一套叫作 AppLoader 的方案,而在此時社區可能尚未微前端這個概念。事實上這個方案也是近乎完美的解決了咱們的業務問題,但因爲場景比較受限所以咱們一直沒有將其對外。javascript
2019 年年初,淘寶有一個很是重要的項目「小二工做臺」,簡單來說「小二工做臺」要打造面向運營小二的操做系統,解決多個後臺體驗不一致、頻繁跳轉效率低、重複建設等問題,因而 AppLoader 重出江湖。同時結合淘寶業務發展以及微前端這個概念在社區的普及度,咱們判斷將來此類業務場景會愈來愈多,所以咱們對 AppLoader 作了一次能力和品牌的升級,同時面向社區開源,這即是 icestark 的由來。css
智者說:拋開業務場景談技術方案(微前端)都是瞎扯淡,所以在介紹 icestark 以前,咱們先了解下當時面臨的業務背景是怎樣的。前端
如前文所說,兩年多以前咱們接到「阿里創做平臺」這個項目,這個產品承載了創做者從入駐到創做完整的生命週期,相比於普通業務有如下兩個不一樣點:頁面數很是多、將來有二方業務接入的需求,針對這兩個特色前端方案須要核心解決的問題:vue
其中第二點提到的拆分子應用是方案的核心,在上面所說的業務背景下,這個項目的代碼量必定會快速增多,那麼若是是經過一個前端應用來管理全部代碼,不管是當下流行的單頁面應用仍是傳統的多頁面應用都沒法規避如下問題:java
針對這些問題咱們作了一輪完整的技術調研。react
如上所述,不具備可行性。git
每一個子應用獨立開發獨立部署,而後經過 iframe 的方式將這些應用嵌入到同一個系統中,這是一個很完全的方案同時也是使用最多的一個方案,但 iframe 的體驗問題一直是個難以解決的問題:github
這些問題有的能夠解決,有的很難解決,有的幾乎沒法解決,所以咱們捨棄了 iframe 的方案。npm
封裝一個統一的框架 UI 組件,每一個子應用自行接入框架組件,框架組件能夠是一個 npm 或者覆蓋式的 cdn 資源或者 vmcommon,這個就相似淘寶 PC 端業務接統一吊頂的場景。可是這個方案有如下幾個問題:後端
事實上這個方案更適合於各個應用很是獨立,從前端到後端服務所有都歸屬於某個業務方,並且各個業務方之間也比較獨立,沒有中心化管理的需求。
比較遺憾的是在 2017 年中旬咱們尚未了解到「微前端」這個概念(可能還沒誕生?),至於今天社區中相對有名的微前端解決方案 single-spa 可能尚未或者知名度比較小,並無進入到咱們的調研列表裏。
另外,微前端這個概念我也是今年才真正接觸到,也才意識到咱們兩年前作的方案就是今天所謂的微前端。
完成技術調研以後,咱們最終決定自研一套方案即 icestark,這套方案具體的設計思路和使用方式參考下文。
如上圖所示,首先引入框架應用和子應用的概念,框架應用負責系統總體佈局以及子應用的註冊、加載與渲染,同時在設計原則上咱們但願「子應用盡可能保持跟傳統單頁面應用同樣的開發體驗」,保證子應用自身可獨立運行、存量應用可快速遷移適配、增量應用跟傳統方式開發體驗一致。
在確認上述核心設計思路以後,接下來就是對問題進行具體拆解:子應用是一個傳統的 SPA 應用(可包含一個或多個頁面),會打包出 bundle 同時發佈到 CDN,那麼咱們須要在框架應用中註冊管理全部子應用,而後在適當的時機加載對應的子應用 bundle 並將其渲染到指定節點(系統佈局裏面)。在這個流程裏核心要解決的技術問題以下。
子應用包含多個頁面即路由,只有頁面路由的變化會引發子應用的切換,那麼咱們只要創建子應用和路由的映射關係便可。爲每一個子應用分配一個基準路由如 /seller
,這個子應用保證全部的路由定義在 /seller
下,那麼當從其餘路由跳轉到 /seller
路由時咱們就能夠加載渲染 /seller
對應的子應用 bundle 了。PS:除了基準路由這種約束方式也支持其餘更加鬆散的方式。
icestark 經過劫持 history.pushState
和 history.replaceState
兩個 API,同時監聽 popstate
事件,保證可以捕獲到到全部路由變化。當捕獲到路由變化時,根據路由查找對應的子應用,若是對應的仍是當前這個子應用則什麼事情都不作,若是對應的是新的一個子應用則卸載以前的子應用,同時加載新的子應用並渲染之。
框架應用有系統的 Layout,咱們須要將子應用渲染到 Layout 裏面,可是單頁面應用都是直接經過 ReactDOM.render(<App />, document.getElementById('#root'))
的方式渲染,若是直接執行那麼渲染的位置是沒法被控制的,因而 icestark 爲子應用提供了一個 getMountNode()
的 API 保證子應用可以渲染到指定的節點裏。
在咱們內部使用時其實並無考慮這個問題,由於咱們內部目前都是 React 的技術棧,基本不存在這樣的問題,可是若是要將這個方案開源,那這個特性是必須支持的。
比較有意思的是回顧上述的核心設計,icestark 對子應用的約束很是簡單:路由須要規範最好是經過基準路由約束、須要渲染到指定節點裏,那麼子應用是經過 Vue 或者 ReactDOM 亦或是 jQuery 渲染都無所謂了,總體方案對此沒有任何依賴。
這即是 icestark 核心的幾塊設計思路,接下來咱們簡單看下這套方案如何使用。
安裝 iceworks CLI 工具:
$ npm i -g iceworks
複製代碼
經過命令行初始化一個框架應用:
$ mkdir icestark-framework-app && cd icestark-framework-app $ iceworks init @icedesign/stark-layout-scaffold 複製代碼
完成初始化以後安裝依賴而後經過 npm start
便可進行預覽。
在 src/App.jsx
中咱們能夠看到核心的子應用註冊代碼:
import React from 'react'; import { AppRouter, AppRoute } from '@ice/stark'; export default class App extends React.Component { render() { return ( <BasicLayout pathname={pathname}> <AppRouter onRouteChange={this.handleRouteChange} onAppLeave={this.handleAppLeave} onAppEnter={this.handleAppEnter} > <AppRoute path="/seller" basename="/seller" title="商家平臺" url={[ '//unpkg.com/icestark-child-seller/build/js/index.js', '//unpkg.com/icestark-child-seller/build/css/index.css', ]} /> <AppRoute // ... /> </AppRouter> </BasicLayout> ); } } 複製代碼
子應用註冊的核心信息:
{ // 爲子應用分配的基準路由 path: '/seller', // 子應用的 bundle 地址,用來渲染子應用 url: [ '//unpkg.com/icestark-child-seller/build/js/index.js', '//unpkg.com/icestark-child-seller/build/css/index.css', ], } 複製代碼
經過命令行初始化子應用:
$ mkdir icestark-child-app-test && cd icestark-child-app-test # 基於 React 的子應用 $ iceworks init project @icedesign/stark-child-scaffold # 基於 Vue 的子應用 $ iceworks init project @vue-materials/icestark-child-app 複製代碼
一樣安裝依賴執行 npm start
便可單獨開發預覽子應用,若是想在框架應用中預覽,替換相關 bundle 地址便可。
相比於傳統的單頁面應用,icestark 的子應用有三個須要定製的地方:
getMountNode()
API 來獲取// src/index.jsx import ReactDOM from 'react-dom'; import { getMountNode, registerAppLeave } from '@ice/stark-app'; import router from './router'; registerAppLeave(() => { ReactDOM.unmountComponentAtNode(mountNode); }); ReactDOM.render(router(), getMountNode(document.getElementById('mountNode'))); 複製代碼
// src/router.jsx import { BrowserRouter as Router, Switch } from 'react-router-dom'; import { getBasename } from '@ice/stark-app'; export default () => { return ( <Router basename={getBasename()}> <Switch> // ... </Switch> </Router> ); }; 複製代碼
完整的文檔請參考 子應用開發與遷移 。
說到微前端必定逃不了沙箱的相關話題,可是針對這個問題目前業界尚未一個很是完美的機制,具體可參考文檔 樣式和腳本隔離,這裏面有咱們的一些探索。
針對這個問題咱們的一些觀點:
icestark 與 single-spa 都屬於微前端的解決方案,二者在能力上並沒有太大差異,這裏簡單梳理下我的的一些觀點:
另外 qiankun 是對 single-spa 的一層封裝,核心作了構建層面的一些約束以及沙箱能力,構建層面的約束(好比 umd)我的以爲會讓子應用變複雜,不必定是一個好的方案,而後沙箱這塊 icestark 是將 onAppEnter/onAppLeave
這種鉤子暴露給框架應用,讓業務自身去按需作一些好比全局變量凍結之類的事情。
icestark 目前主要落地在阿里內部的業務,社區裏可能也有幾個項目,不過目前尚未作過專門的統計,若是有用到的話歡迎反饋給咱們。
包含 20+ 子應用,其中 5-8 個子應用由二方業務開發。
面向淘系運營小二的後臺都將已子應用的方式接入小二工做臺,打造面向運營小二的操做系統。
微前端當下主要仍是在解決工程問題,好比系統的解耦、多人協做之類的,因此其實去看下核心代碼都是很是簡單易懂的。在工程問題的基礎上接下來咱們會有兩個方向:第一是探索沙箱機制,讓二方業務更加安全的運行,同時讓不可控的三方業務接入逐漸成爲可能;第二針對微前端的業務場景逐步完善生態,好比一些鑑權之類的業務需求,這塊有需求歡迎反饋。
最後歡迎評論交流 & 點贊 & star,以及經過釘釘羣跟咱們溝通。