前端早早聊大會,與掘金聯合舉辦。加 codingdreamer 進大會技術羣,贏在新的起跑線。前端
第二十八屆|前端 WebGL 專場,2021 年要不要押寶 WebGL 彎道超車,6-26 全天直播,9 位講師(阿里雲/螞蟻/美團/小米等),報名上車👉 ):react
全部往期都有全程錄播,上手年票一次性解鎖所有webpack
本文是第十七屆 - 前端早早聊框架專場,也是早早聊第 119 場,來自淘系 - 弗申 的分享web
你們好,我是來自阿里巴巴淘系技術部前端架構團隊的弗申,今天我給你們分享的主題是如何實現跨端框架的標準化研發模式。算法
前端發展到如今,工程架構日趨複雜,業務須要投放到的容器也不盡相同,那麼你們在不一樣的業務領域可能會有本身不一樣的業務訴求,在大背景下怎麼去打造一個標準化的研發模式,以及一個同基礎通用的跨端研發框架,這件事情變得更加劇要。npm
由於技術體系的發展決定了業務架構的魯棒性,因此接下來我會圍繞阿里巴巴集團使用面最廣的漸進式 React 的框架 Rax,來給你們分享一下咱們在框架設計方面的一些思考,以及在此次分享過程當中會從一些比較小的點,着手分析業務中是真實會遇到的問題,而後講解咱們是怎麼去解決以及思考的。json
咱們來先簡單看一下此次分享的提綱,主要是四個部分,重點是前面兩個部分。redux
在設計框架的時候,咱們須要用到三段式思惟方式來完成設計,就是 Why、What、How。小程序
Rax 的跨端方案誕生的時間大概是在2015年,容器在不斷的發生變化。由於當時有 WebView,而後有 ReactNative,還有 Weex。對咱們當時社區有了 React Native 的狀況下,爲何咱們要有 Rax 呢?後端
首先第一個是當時咱們阿里巴巴集團的前端大部分的技術棧,因爲咱們當時是 Allin 無線,因此你們的技術棧仍是在 React 那邊,所以在集團有 Weex 的狀況下,咱們須要讓開發者可以使用 React 來開發 Weex,而且同時咱們的業務須要投放到 Web,那麼咱們跨端方向的一個大背景就是容器在不斷髮生變化。
第二個就是一次編寫,須要多端投放的業務訴求。
第三個就是前端技術生態比較豐富,咱們但願可以儘量的運用前端生態技術來解決一些跨端的問題。
Rax 誕生的背景就是 React 加上 Weex,即 Rax 是一個漸進式的 React 框架。在社區有了 React Native 的狀況下,爲何須要 Rax ?Rax 解決了哪些問題?React Native 首先在那個時代背景下是無法支持 Web 的業務場景的。發展到如今 React 也有本身的 React Reconcile 的概念,能夠基於它去作一些多端的事情。可是在當時那個時候是沒有的,然而咱們的業務又有須要投放到 Web 的場景,這是一個強訴求。
第二點是 React Native 相似方案 Weex 的誕生,可是 Weex 當時是 DSL 是 hack 了一份 Vue 對與咱們的 PC React 爲主的技術棧是不匹配的。
第三個是咱們嘗試將 React 和 Weex 直接相結合的時候,發現有不少問題,包括一些性能上的問題。實際上你們對 React 是比較瞭解的,其實能夠看到 react-dom 這個包,它在 gzip 以後,其實體積仍是比較大的。
Rax 解決了什麼問題, Rax 主要核心解決了下面三個問題。
由於這一切的緣由,因此誕生了 Rax。因此在目前爲止,由於 Rax 發展了到今年已經大概是第5年了。 Rax 已經普遍的應用在阿里巴巴集團的各個場景,包括歷年的雙11,覆蓋的 BU 有淘系、飛豬、優酷、阿里巴巴等等。
接下來我給你們分享一下咱們跨端框架的設計思路,但願可以讓你們在作業務技術選型的時候,或者說是在架構設計的時候,給你們帶來一些幫助和啓發。
咱們先來看一下框架體系的概覽。首先底層咱們的 DSL 的標準是遵循 React 的,Rax DSL 再加上一個 JSX Plus 的規範,你們對 JSX Plus 可能不夠了解,給你們簡單介紹一下:JSX Plus 實際上就是可以在 JSX 上面去用一些簡單的指令,好比說像 Vue 裏面的 v-if,或者說 v-for 經過指令來作一些更加簡便的操做。
往上面就是咱們的工程體系,工程體系的最底層是 Build Scripts,這個是基於「阿里巴巴前端委員會」共建的一套工程體系底層能力,可讓開發者經過插件的方式擴展 Webpack 配置。再往上是 Rax-App 研發框架,這套研發框架是基於Build Scripts 去設計的,其中包含了豐富的運行時功能,以及開箱即用的工程配置能力,最終可以讓你們的代碼實現一碼多端,也就是一次編寫就能夠跑在多個容器上面。更上層是咱們的基礎生態體系,包括跨端基礎組件,以及跨端 API。往上最後一層就是生態體系,包括組件庫等等。
咱們但願這一整套體系給到開發者的是:開發者不用去關心底層容器是什麼,只須要使用 Rax 提供的 DSL ,包括咱們提供的工程,以及多端體驗一致的一些生態體系、組件、 API 等等,就能夠運行到 Rax 支持的全部容器當中。好比說 Web 、小程序,這裏小程序包括微信小程序、阿里小程序、字節跳動小程序等等。還有 Flutter ,基於 Kraken 方案的一個 Flutter 的方案。而後還有 Weex 等等。
接下來我給你們介紹一下咱們的 DSL 設計。
Rax DSL 自己是遵循 React 的標準的,也就是說開發者徹底能夠用熟悉的 JSX 語法來開發業務,而後沒有任何的上手成本。固然和 React 相似,Rax 也是一樣是基於 VDOM 的,爲何選擇 VDOM ?有些同窗可能會說 VDOM 樹能夠減小操做真實 VDOM 的頻率,從而提高性能。其實事實並不是徹底是這個樣子的,由於 VDOM 帶來的性能優點歷來不是最大的,在現代瀏覽器中直接操做真實 DOM 效率或者說是性能,可能比 VDOM 的效果更好,性能更高。
那麼咱們爲何選擇爲 VDOM 呢?VDOM 給咱們帶來了主要是有兩方面的收益:
接下來給你們介紹一下咱們基於 VDOM 怎麼去作一個跟容器解耦的事情。咱們是基於 Driver 的概念,站在今天這個角度來說的話,你們可能比較熟悉的一個概念是 React 中的 reconciler。其實這種方案和理念, Rax 比 React 提出的要更早一些。也就是說咱們經過 Driver 實現的這些讓你們乍一看很像 DOM API 的函數,可以抹平對應容器的渲染能力。好比說 getElementById 這個方法,在 Rax DSL 裏面,由於咱們在 Rax 核心庫裏面至關因而直接去調用的 Driver.getElementById 方法,因此 Rax DSL 自己是不關心 getElementById 是怎麼實現的,咱們能夠針對特定的容器來實現這個方法,從而達到跨端的一個目的。
咱們的研發框架主要分爲三個部分: 第一部分,框架運行時:它提供了整個 APP 級別的就是應用級別的生命週期,包括
第二部分,工程構建能力
第三部分,也是比較重要的一部分,就是咱們的高度插件化的能力。
這樣的一種擴展運行時能力帶來的一種收益是什麼?假如說你的業務須要一個跨端的能力,那麼你能夠選擇 Rax APP 研發框架。當你的業務到了一個深水區,好比說是互動領域,你有本身的一些垂直的 API 須要去注入或者是有本身的品牌,你能夠基於咱們的 Rax APP,再去作一個更上層的框架,而後在上層框架裏面去注入。以互動領域爲例,能夠注入互動的一些運行時的 API ,例如:import xxx from 框架名,同時你還享有 Rax APP 提供的一些基礎的運行時API。
下面給你們來介紹一下咱們框架運行時,在此以前咱們先看一下整個研發框架的概覽。咱們的整個設計思路:
首先是下面的基礎能力:
再往上層是咱們的框架核心:也就一個是工程構建中臺,我大概講一下里麪包含了各類好比說 CLI 註冊一些參數,你能夠經過 --https 去開啓 https 的一些調試能力,好比說是狀態管理這些能力都是經過構建中臺去包裝的。
再往上層是渲染器:其中包括小程序的渲染器,例如更新小程序的視圖。第二個是 Rax 應用的渲染器。第三個是 React應用的渲染器。能夠在底層經過 Rax 或者是 React 的 setState 操做來更新視圖。
更上層的是容器:咱們支持 Web、小程序、Weex 、Kraken ,其中 Kraken 是 Flutter 的方案。
再往上層是核心框架:咱們這套整個下層的體系支持了 icejs(中後臺),還有一個是 Rax APP 無線跨端。
更往上層的業務框架:已經有不少團隊開始基於咱們的框架去作他們本身的研發框架。好比說阿里雲團隊,政務釘釘或者是阿里體育團隊。
繼續講咱們的框架運行時。
我會從後面幾個比較小的點來着手分析,經過框架運行時的一些能力,咱們解決了怎樣的問題。
首先第一部分是生命週期。若是你們接觸 React 多的話,可能對 componentWillMount、componentDidMount 這些組件的實例的生命週期比較熟悉。完整的生命週期,更多的指的是什麼?更多指的是一個應用的生命週期。若是你們開發太小程序可能比較清楚,就是小程序的一些生命週期。
完整的生命週期能夠作哪些事情?
因此基於上面這些需求,以及一些業務上的真實痛點,咱們作了一個多端體驗一致的生命週期,主要分爲兩部分:
這裏同窗會問說爲何 usePageShow 、usePageHide 這裏稱爲 Hooks,而 onShow,onHide 稱爲事件。這是由於在社區裏面咱們看到的常見的 useXXX,其實就是 API 的調用,並非真正的 Hooks,也就是說這種 API 的調用方式跟 Hooks 自己並無關係。咱們這裏的 usePageShow 實際上是真正的 Hooks,它跟 onShow 不一樣之處在於,ousePageShow 的觸發時機是在組件渲染完成以後,而 onShow 是在組件示例初始化的時候,也就是頁面剛進來的時候,就會觸發這個 onShow 事件。
咱們還提供了基於小程序的若干小程序獨有的聲明週期。若是有接觸太小程序的同窗應該知道,諸如頁面觸底的一些事件。不管是在 Weex,仍是在 Kraken ,仍是在小程序,或是在 Web 等等業務形態上,咱們的方案表現都是徹底一致的,包括觸發時機和觸發順序,這樣可以讓你的業務邏輯在開發時保持徹底一致。usePageShow 是在組件渲染完成以後,onShow 是在組件實例化的時候,也就是頁面剛進入時。
接着我想介紹的這個是咱們的生命週期的一個演示代碼。左邊是應用入口的演示,在應用入口中註冊本身的生命週期,而後右邊是頁面的演示 usePageShow 和 usePageHide,你能夠嘗試這樣去作。你們能夠簡單看一下。
接下來主要是給你們介紹的是咱們的狀態管理,其實飛冰這個產品也是屬於咱們團隊的,咱們核心能力也是基於飛冰 ICE Store 提供的狀態管理的能力。咱們但願可以把狀態管理這件事情作得更加簡單,更加符合業務使用的直覺,而不須要本身去包裹 Provider 或者是去理解更多的概念,你只須要用一些最基礎的能力就能夠完成狀態管理。
首先是建立 store,你能夠在 src 裏面新建一個 store.ts 的文件,你能夠去建立 state 、 reducers 、effects。分別對應 redux 裏面的一些概念,即 state、 reducers 、還有會產生反作用的一些方法。在頁面裏面能夠去消費這些全局的狀態,能夠直接去 import store 就是 APP 的 store ,從根路由引入。
而後經過 useModel 的方法拿到 state 的狀態,經過 APP 上的 dispatchers 去觸發一些視圖上的更新,就能夠很輕鬆的作到狀態管理的 0 成本上手。只須要簡單的看一下示例代碼,就能夠徹底涵蓋到無線端場景的大部分狀態管理的訴求。固然因爲咱們提供了內置的狀態管理的能力,包括一些注入的方法。這樣可能致使咱們的包體積變大,但咱們但願在無線端給到你們的方案是足夠輕量的,因此在一些無線端比較簡單的頁面中,可能業務沒有必要去用到狀態管理,也不建議你們在無線端去大量的使用狀態管理。
由於無線端咱們儘量的把事情作簡單,作的更輕量,因此咱們提供了一種方式,就是在咱們的配置文件裏面,就是 build.json 裏手動關閉狀態管理。能夠用很簡單的一個開關,設置 store 爲 false ,就能夠關掉狀態管理。根據咱們關閉 store 先後的打包文件的體積對比,在打開 gzip 壓縮的以後,關掉狀態管理,咱們的代碼體積能夠縮小大概 20KB ,在一個無線端場景 20KB 其實能夠算是優化中的一個比較重要的指標。
關於路由方案,咱們採用的是標準協議約定式路由,在 app.json 裏面配置路由。以 home 頁面爲例,設置 path 、source(頁面來源),同時給 home 頁面配置一個 title,能夠用這種方式建立頁面。
還能夠對 tabBar 進行配置,利用剛纔提到的協議配置,能夠很容易的在應用中配置 tabBar, 因爲咱們有一套相對完美的降級到 Web 應用的方案,因此這個 tabBar 的配置能力不只僅做用於小程序,也能夠在 Web 應用中實現對應的 tabBar 功能。
路由操做及數據獲取,經過 Rax App 中引入 history 、getSearchParams 就能夠去很輕鬆的拿到咱們須要的當前頁面的history 相關數據。在 react-router 中,也提供了相似的 useHistory,useLocation 這樣的API,其底層是利用了 React 的 useContext 的能力,使用了像 hooks 相似的命名,但其實它們與命名並無直接關係,因此在 Rax App 中咱們把它設計成爲直接能夠獲取,並進行操做。相似的 getSearchParams 也是替換了原來的 hooks 寫法。
從架構設計的角度來講,history 的設計思路是什麼呢?因爲咱們要適配多端,因此咱們須要支持 Web、Weex 以及 Kraken。咱們使用的是社區中的一個 history 包提供的能力。可是因爲小程序自己沒有 history 的概念,因此咱們模擬了 mini-app 本身去封裝實現了一套 history 的 API 暴露給用戶使用。這樣能夠幫助用戶向用其餘端同樣在小程序中調用諸如 history.push、history.replace 等等方法。對開發者而言,只須要理解一個 history 概念就好。
在實際開發過程當中,經常遇到如下的問題:
這些問題在 Rax App 中都變得很容易。好比經過 npm start -- --https 就能夠開啓本地的 https 調試,經過 npm start -- --analyzer 就能夠一鍵開啓依賴分析,並不須要額外的引入社區的插件。添加 babel 插件,只須要在配置中添加 babelPlugins 對應的插件便可,再也不須要去社區裏找一堆須要引入的插件。
其餘的一些配置,好比能夠經過簡單的配置 minify 關閉壓縮,這在開發環境中經常用到。經過配置 proxy 便可將 API 請求代理到本地,以此來解決跨域問題。一樣的環境變量也能夠再也不依賴 process.env 這種方式,只須要在 define 中配置對應的變量便可注入業務中須要的環境變量。
一碼多端能力,經過以下圖所示在配置文件中寫入對應的 targets 端,就能夠一次命令同時開啓多個端的業務構建,如 Web、Weex、Kraken、小程序等等。同時也支持 Web 中的 SSR,MPA 的特殊需求。
針對小程序,也有小程序獨有的配置,包括針對微信小程序或者阿里小程序的比較獨特的一些配置。同時後面還會跟你們說到一個小程序的特有 runtimeDependencies 的配置項。
代碼體積,在一碼多端的場景下,代碼體積的大小是一個很重要的性能指標。Rax App 中是經過特定的 Babel 插件 rax-platform-loader 來判斷不一樣的端場景,並只保留對應的端場景下的代碼,從而控制代碼體積。
如今咱們介紹剛纔賣的關子——小程序的實現部分。咱們小程序方案主要是基於雙引擎來驅動的一個完整的小程序開發體系。以此來知足開發者既要高性能,同時也要代碼的靈活度的需求。
編譯時引擎,好比像 Taro (應該是 Taro 1.x、Taro 2.x 的版本)更多的是基於編譯時的引擎,將 AST 語法樹在編譯打包時就進行解析,以保證可以 1:1 的儘量還原到小程序的 DSL ,並能正常運行。
運行時引擎,實現起來並不複雜,本質上就是將 VDOM 樹經過小程序的操做 setData ,而後去遍歷遞歸,最終渲染到模板上。固然這其中涉及到不少渲染時的優化,好比如何避免頻繁的渲染操做,如何可以支持不一樣的小程序,由於不一樣平臺的小程序底層實現的方式也是有所差別的,所以優化方式也會有所區別。
如圖中所示,Rax App 實際上是同時支持了編譯時引擎和運行時引擎兩種模式。
小程序編譯時的引擎,是經過對 AST 樹的解析,解析用戶的一些條件的代碼如 map 的 list 循環,在編譯時會把它和小程序作吻合解析,識別出 map 循環,並將它轉化成小程序的 wx-for 等語法。整個編譯時的引擎,主要是一個洋蔥式的模型。若是瞭解過 koa 框架,應該比較容易理解洋蔥式的模型是什麼概念。每個文件進來以後,它會被解析成 AST 樹,再出去的時候,咱們會作一些 AST 的修改,再轉成代碼文件,最後產出小程序的代碼。
運行時墊片,完整的模擬了 React / Rax 的核心 API。即在編譯時裏提到的 useEffect 其實和 React / Rax 的 useEffect 徹底不一樣,是另外一套實現。從下圖中能夠看出,運行時墊片實際上是將小程序實例與 Rax 組件實例進行了相互綁定的操做。即當 Rax 組件實例更新,會反饋到小程序實例進行改變;而小程序實例的建立、銷燬、更新都會通知到 Rax 組件實例。
在此之上,還實現了 Rax 的一些核心 API,如 Hooks、Component、Event、Lifecycle 等等。
運行時方案的背景,彌補編譯時方案不足
運行時方案的特色,用性能換取完整語法支持的訴求
基於 Rax 體系,如何實現小程序運行時這套方案?
實現小程序運行時的這套方案,須要瞭解以前提到的 dirver 的概念,在 Web 和 Weex 的應用端會模擬操做 DOM 的 API。在小程序端,實際上是經過 worker 線程調用並計算須要渲染的 DOM ,傳遞給 render 線程,而後經過小程序的 setData (微信小程序)/ $spliceData (阿里小程序)進行視圖渲染。
在開發中,每每咱們有各類各樣的要求:好比開發者但願可以擁有運行時的能力,同時又想擁有使用小程序原生組件的能力;想使用小程序編譯時的組件,想節省渲染時的節點數,又想在 worker 和 render 之間傳遞更大的 JSON 數據;想提升渲染效率,又想讓工程師可以使用咱們已有的方案體系,組件體系等等。
針對上述的這些要求,在社區裏,比較而言,Rax App 實際上是更符合這類需求的綜合類的解決方案。
下圖示例中,向你們說明了如何作到小程序的雙引擎混用。其中左邊爲雙引擎混用的工程目錄示例,右邊爲使用方法示例。
插件化包括:運行時插件和工程插件。工程插件是基於 build scripts 實現。框架運行時插件能夠經過本身開發一個插件,而後在 runtime.tsx 的文件,利用框架暴露的一些 API 在業務項目中使用的 Rax App 核心包注入運行時的能力。當開發者使用時,能夠直接從 Rax App 中導出已經注入的運行時能力。
下圖示例瞭如何開發和使用插件的示例。
Rax 的框架架構圖,主要包括 2 個部分,框架中臺和框架品牌。例如 rax-app 和 ice.js 其實都是基於框架中臺實現了,這也意味着開發者能夠根據本身的需求去自定義的封裝出適用於自身業務的框架,甚至也能夠將本身定製化開發的框架開源,回饋社區。
簡單列舉了框架提供的常見的一些 API ,詳細的能夠去官方文檔中進行查閱。
下圖示例了框架爲用戶封裝的業務組件和基礎組件的示例,詳細的能夠去官方文檔中進行查閱。
Rax 的使命,是讓多端開發簡單而美好。 Rax 的願景,基於前端生態打通多端體系。
咱們但願可以融合豐富的前端生態,包括 npm 以及前端其餘很美好的東西,咱們但願可以把他們引入到多端體系中。而不但願用戶因爲今天有了 Flutter,須要去學習 Dart,有了另一個框架,就須要學習另一門語言,或者是另一個生態體系。
咱們就但願前端可以用本身的生態體系來作各個平臺的渲染。關於渲染這件事情,咱們但願可以讓它變得更簡單,入門更加容易。
爲何選擇 Rax APP? Rax主要是有6個點:
高度可擴展性,即用戶能夠無限的擴展本身的需求。
通過充分的業務驗證,是由於咱們有一個可靠的團隊去維護它,保證它的穩定性。你們若是用過飛冰應該知道,飛冰已經運行維護好幾年了,一樣的 Rax 也是運行維護好幾年了。同時阿里巴巴集團內也有大量的業務去驗證這套技術方案。
團隊作技術選型,能夠分爲 6 個部分:
代碼可維護性,一個更小的團隊,好比說 35 我的 510 我的的團隊,要去開發一個業務代碼,可維護性的迫切程度是至關高的。Rax App 提供了跟 icejs 同樣的一個 ESLint 標準,或者說是一個基於 iceworks 的代碼健康檢查插件,包括智能化輔助工具,可讓你很好的去管理你的項目,可以讓你的項目的代碼健壯度更高,可以有更大更高的穩定性。
多場景的支撐,在業務的場景不少的狀況下,在作技術選型的時候,不能只考慮當下,須要考慮將來 1 年或者是 3~5 年以後你所選擇的框架如何適應業務,而不是成爲一個歷史包袱。咱們但願在業務中作技術選型的時候,不管是選擇了 Rax 仍是飛冰這樣的技術方案,都可以對你的多業務場景提供可持續的維護性,編寫高質量的代碼。
業務可擴展性,當你今天在完成一個 A 需求時,可以爲明天的 B 需求作好鋪墊,提早作好對應的考慮。
可用生態,Rax 選擇 React 做爲標準的緣由,以及爲何是一個 React 的漸進式框架?是由於 React 生態是足夠的豐富的。咱們但願可以有更多的生態能夠直接在 Rax 中使用。
給你們推薦一本 《INTRODUCTION OF ALGORITHMS》 的算法書。不管在哪一個領域,並非說在算法僅在後端纔會使用的更多。在前端開發中,尤爲是跟前端渲染相關的優化,跟多時候均可以利用算法來幫助咱們提升渲染性能。一樣在處理一些業務邏輯時,一些必要的算法也能夠幫助咱們高效優雅的實現功能及優化。
這本書經過 case by case 的方式向咱們介紹了常見算法的一些 example 示例,而且解釋了爲何要去使用這個算法,同時也會有一些與其餘算法的對比,很是值得你們去學習,而且動手實踐。
向你們展現一下咱們今年雙 11 的合照。這個就是咱們的團隊,看上去仍是比較大的。
最後的就是招人環節了,咱們主要負責渲染這一部分,咱們團隊還有作 Node 架構相關的工做,因此咱們要 C++ 開發、客戶端開發、前端開發、也要服務端開發。其實不管你的技術棧是什麼都無所謂,咱們更關心的是你的學習能力,對業務的深度理解,以及對當下技術架構設計的想法。
如下是咱們團隊的官網、專欄、開源倉庫及我我的的信息,歡迎你們與咱們交流,或者也能夠直接聯繫我。
不管是技術交流也好,或是對咱們團隊感興趣也好,均可以經過郵件(fushen.jzw@alibaba-inc.com)或是微信(加 codingdreamer 推名片)的方式聯繫我。
別忘了第二十八屆|前端 WebGL 專場,2021 年要不要押寶 WebGL 彎道超車,6-26 全天直播,9 位講師(阿里雲/螞蟻/美團/小米等),點我上車👉 (報名地址):
全部往期都有全程錄播,能夠購買年票一次性解鎖所有
點贊,評論,求 Mark。