面向大型工做臺的微前端解決方案 icestark

2017 年中旬,ICE 團隊接到一個叫作「阿里創做平臺」的項目,這個產品爲創做者提供了入駐、賬號管理、內容管理、內容發佈、粉絲運營、數據分析等等很是完備的功能,頁面數 50+、項目一期有 3-6 個前端同時開發、業務將來有二方業務接入的需求……針對這些問題,傳統的單頁面應用方案實在有點力不從心,所以在詳細的技術評估以後咱們最終自研了一套叫作 AppLoader 的方案,而在此時社區可能尚未微前端這個概念。事實上這個方案也是近乎完美的解決了咱們的業務問題,但因爲場景比較受限所以咱們一直沒有將其對外。javascript

2019 年年初,淘寶有一個很是重要的項目「小二工做臺」,簡單來說「小二工做臺」要打造面向運營小二的操做系統,解決多個後臺體驗不一致、頻繁跳轉效率低、重複建設等問題,因而 AppLoader 重出江湖。同時結合淘寶業務發展以及微前端這個概念在社區的普及度,咱們判斷將來此類業務場景會愈來愈多,所以咱們對 AppLoader 作了一次能力和品牌的升級,同時面向社區開源,這即是 icestark 的由來。css

業務背景

智者說:拋開業務場景談技術方案(微前端)都是瞎扯淡,所以在介紹 icestark 以前,咱們先了解下當時面臨的業務背景是怎樣的。前端

如前文所說,兩年多以前咱們接到「阿里創做平臺」這個項目,這個產品承載了創做者從入駐到創做完整的生命週期,相比於普通業務有如下兩個不一樣點:頁面數很是多、將來有二方業務接入的需求,針對這兩個特色前端方案須要核心解決的問題:vue

  • 用戶端必須是「一個系統」的心智,從域名到體驗
  • 可以根據功能拆分紅多個子應用,每一個子應用獨立開發獨立部署
  • 子應用盡可能保證跟傳統單頁面應用同樣的開發體驗,不要讓開發者有太多學習成本
  • 全部子應用可被統一管理起來,不能無限制的泛濫

其中第二點提到的拆分子應用是方案的核心,在上面所說的業務背景下,這個項目的代碼量必定會快速增多,那麼若是是經過一個前端應用來管理全部代碼,不管是當下流行的單頁面應用仍是傳統的多頁面應用都沒法規避如下問題:java

  • 代碼量達到必定量的時候,單次構建時間很長,開發&發佈效率極低
  • 代碼庫中依賴升級會影響整個應用,而代碼量又很是大,致使迴歸成本極高
  • 變成一個很是臃腫的巨石應用,徹底失去靈活性,不管是多人協做仍是業務接入成本都會大大增長

針對這些問題咱們作了一輪完整的技術調研。react

技術調研

單/多頁面應用

如上所述,不具備可行性。git

iframe

每一個子應用獨立開發獨立部署,而後經過 iframe 的方式將這些應用嵌入到同一個系統中,這是一個很完全的方案同時也是使用最多的一個方案,但 iframe 的體驗問題一直是個難以解決的問題:github

  • iframe 體驗問題:頁面加載慢;雙滾動條問題;內部蒙層沒法遮罩到外部框架,同時佈局也沒法居中;內部跳轉後外部無響應,刷新後又會回到 iframe 首頁……
  • 每一個子應用須要依賴服務部署,方案變重,成本增長,同時可能會引入跨域之類的問題

這些問題有的能夠解決,有的很難解決,有的幾乎沒法解決,所以咱們捨棄了 iframe 的方案。npm

封裝框架組件

封裝一個統一的框架 UI 組件,每一個子應用自行接入框架組件,框架組件能夠是一個 npm 或者覆蓋式的 cdn 資源或者 vmcommon,這個就相似淘寶 PC 端業務接統一吊頂的場景。可是這個方案有如下幾個問題:後端

  • 訪問入口不統一,本質仍是多個系統,只是看起來框架是一致的
  • 子應用沒法被統一管控,可能會無限制的增長

事實上這個方案更適合於各個應用很是獨立,從前端到後端服務所有都歸屬於某個業務方,並且各個業務方之間也比較獨立,沒有中心化管理的需求。

微前端?

比較遺憾的是在 2017 年中旬咱們尚未了解到「微前端」這個概念(可能還沒誕生?),至於今天社區中相對有名的微前端解決方案 single-spa 可能尚未或者知名度比較小,並無進入到咱們的調研列表裏。

另外,微前端這個概念我也是今年才真正接觸到,也才意識到咱們兩年前作的方案就是今天所謂的微前端。

自研 icestark

完成技術調研以後,咱們最終決定自研一套方案即 icestark,這套方案具體的設計思路和使用方式參考下文。

架構設計

image.png

如上圖所示,首先引入框架應用和子應用的概念,框架應用負責系統總體佈局以及子應用的註冊、加載與渲染,同時在設計原則上咱們但願「子應用盡可能保持跟傳統單頁面應用同樣的開發體驗」,保證子應用自身可獨立運行、存量應用可快速遷移適配、增量應用跟傳統方式開發體驗一致。

在確認上述核心設計思路以後,接下來就是對問題進行具體拆解:子應用是一個傳統的 SPA 應用(可包含一個或多個頁面),會打包出 bundle 同時發佈到 CDN,那麼咱們須要在框架應用中註冊管理全部子應用,而後在適當的時機加載對應的子應用 bundle 並將其渲染到指定節點(系統佈局裏面)。在這個流程裏核心要解決的技術問題以下。

1. 何時加載哪一個子應用?

子應用包含多個頁面即路由,只有頁面路由的變化會引發子應用的切換,那麼咱們只要創建子應用和路由的映射關係便可。爲每一個子應用分配一個基準路由如 /seller ,這個子應用保證全部的路由定義在 /seller 下,那麼當從其餘路由跳轉到 /seller 路由時咱們就能夠加載渲染 /seller 對應的子應用 bundle 了。PS:除了基準路由這種約束方式也支持其餘更加鬆散的方式。

2. 如何捕獲到系統中全部路由的變化?

icestark 經過劫持 history.pushState 和 history.replaceState 兩個 API,同時監聽 popstate 事件,保證可以捕獲到到全部路由變化。當捕獲到路由變化時,根據路由查找對應的子應用,若是對應的仍是當前這個子應用則什麼事情都不作,若是對應的是新的一個子應用則卸載以前的子應用,同時加載新的子應用並渲染之。

3. 如何將子應用的 bundle 渲染到指定節點?

框架應用有系統的 Layout,咱們須要將子應用渲染到 Layout 裏面,可是單頁面應用都是直接經過 ReactDOM.render(<App />, document.getElementById('#root'))  的方式渲染,若是直接執行那麼渲染的位置是沒法被控制的,因而 icestark 爲子應用提供了一個 getMountNode() 的 API 保證子應用可以渲染到指定的節點裏。

4. 子應用使用不一樣的前端框架怎麼辦?

在咱們內部使用時其實並無考慮這個問題,由於咱們內部目前都是 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 的子應用有三個須要定製的地方:

  1. 須要主動註冊&觸發應用的卸載事件
  2. 應用渲染的節點須要經過 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')));
複製代碼
  1. 路由須要定義在約定的基準路由下面:
// 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>
  );
};
複製代碼

完整的文檔請參考 子應用開發與遷移 。

沙箱與隔離

說到微前端必定逃不了沙箱的相關話題,可是針對這個問題目前業界尚未一個很是完美的機制,具體可參考文檔 樣式和腳本隔離,這裏面有咱們的一些探索。

針對這個問題咱們的一些觀點:

  • 大部分業務沒有三方接入需求,支持很是完全的沙箱機制沒有太大意義,至少在咱們目前落地的業務中尚未出現相互污染的問題
  • 對於可控的二方應用接入,咱們推薦進行一些規範約定便可,好比不要污染全局變量、定時器及時清除、CSS 樣式儘可能經過前綴或者 CSS Modules 作隔離
  • 對於不可控的三方應用,建議暫時先經過 iframe 的方式接入
  • 沙箱機制有機會經過 Shadow DOM 和 Web Worker 之類的方案解決,還在探索中,歡迎交流

與 single-spa 的關係

icestark 與 single-spa 都屬於微前端的解決方案,二者在能力上並沒有太大差異,這裏簡單梳理下我的的一些觀點:

  • single-spa 社區知名度更高,生態以及能力上會完善一些,這個是 icestark 要持續追趕的
  • 子應用方面:icestark 對子應用的侵入幾乎能夠忽略,使用成本更低,同時子應用也能夠獨立運行,而 single-spa 相對多了一些侵入,須要瞭解各類生命週期,子應用是否能單獨運行須要確認下
  • API 設計層面:icestark 將應用路由類比爲頁面路由,類 react-router 的 API 設計更加簡單直觀一些
  • 架構設計上:icestark 更加簡單,single-spa 要重一些,好比須要 single-spa-react/vue/preact 這種設計,具體能夠看下二者的核心代碼,其實都比較簡單

另外 qiankun 是對 single-spa 的一層封裝,核心作了構建層面的一些約束以及沙箱能力,構建層面的約束(好比 umd)我的以爲會讓子應用變複雜,不必定是一個好的方案,而後沙箱這塊 icestark 是將 onAppEnter/onAppLeave 這種鉤子暴露給框架應用,讓業務自身去按需作一些好比全局變量凍結之類的事情。

最佳實踐

  • 框架應用職責明確,只作總體佈局以及子應用的註冊管理,不作其餘任何 UI 或邏輯,由於框架應用本質是一箇中心化部件,中心化的部分越簡單整個系統就越穩定
  • 子應用經過基準路由來劃分,這樣應用的管理更加簡單直觀,也很難出現應用間路由衝突的問題
  • 子應用盡可能避免依賴框架應用的能力,好比框架層面提供一些全局的 API 或者組件都是很是很差的架構設計
  • 子應用的信息推薦經過配置平臺管理,而後 server 端經過全局變量的方式暴露給框架應用,這樣子應用的版本變化、增長刪除等都不須要依賴框架應用發佈

業務落地

icestark 目前主要落地在阿里內部的業務,社區裏可能也有幾個項目,不過目前尚未作過專門的統計,若是有用到的話歡迎反饋給咱們。

阿里創做者平臺

包含 20+ 子應用,其中 5-8 個子應用由二方業務開發。

阿里健康-熙牛醫療雲醫院信息系統

淘系小二工做臺

面向淘系運營小二的後臺都將已子應用的方式接入小二工做臺,打造面向運營小二的操做系統。

微前端的將來

微前端當下主要仍是在解決工程問題,好比系統的解耦、多人協做之類的,因此其實去看下核心代碼都是很是簡單易懂的。在工程問題的基礎上接下來咱們會有兩個方向:第一是探索沙箱機制,讓二方業務更加安全的運行,同時讓不可控的三方業務接入逐漸成爲可能;第二針對微前端的業務場景逐步完善生態,好比一些鑑權之類的業務需求,這塊有需求歡迎反饋。

最後歡迎評論交流 & 點贊 & star,以及經過釘釘羣跟咱們溝通。

相關連接

相關文章
相關標籤/搜索