做者:阿里巴巴淘系前端工程師 弗申 逆葵html
Rax 小程序官網webpack
通過持續的迭代,Rax 小程序迎來了一個大的升級,支持全新的運行時方案。站在 2020 年初這個時間點,咱們想從 Rax 小程序的特色出發,進行一次全面的梳理與總結,而且在文末附上了 Rax 與當前主流的小程序開發框架的對比。本文將從 API 設計與性能、雙引擎架構、優秀的多端組件協議設計和基於 webpack 的工程架構四個方向展開。git
當決定一個產品的技術選型的時候,咱們每每會從幾個方面考慮,(1)可用生態,即周邊相關的工具是否知足產品開發的條件;(2)風險率,即出現問題是否可以快速定位解決,所使用的技術是否會持續維護;(3)上手成本,即需不須要很大代價才能達到可以使用的階段;(4)性能,即可以知足產品既定的性能標準以及用戶體驗。github
本節主要會介紹 Rax 小程序在後面兩點上的優點。web
框架總體的上手成本是比較小的,Rax 小程序鏈路從框架上是繼承自 Rax(構建多端應用的漸進式類 React 框架)。因此只要你會 Rax Web/Weex 開發或者 React,那麼你就會用 Rax 開發小程序,而且能夠同時投放到 Rax 所支持的其它端。npm
可是因爲小程序端的特殊性,總會存在沒法抹平以及須要單獨處理的地方。得益於 Rax 已經作了比較久的多端方案,咱們認爲,每一個端獨立的屬性不該該入侵基礎框架自己,保證基礎框架的純淨有利於作更多的擴展。json
如下面的代碼爲例:小程序
Taro:微信小程序
import Taro, { Component } from '@tarojs/taro' import { View, Text } from '@tarojs/components' export default class Index extends Component { config = { navigationBarTitleText: '首頁' } componentWillMount () { } componentDidMount () { } componentWillUnmount () { } componentDidShow () { } componentDidHide () { } render () { return ( <View> <Text>1</Text> </View> ) } }
Rax:
import { createElement, Component } from 'rax'; import View from 'rax-view'; import Text from 'rax-text'; import { isMiniApp } from 'universal-api'; import { registerNativeListeners, addNativeEventListener, removeNativeEventListener } from 'rax-app'; function handlePageShow() {} class Index extends Component { componentWillMount () { } componentDidMount () { if (isMiniApp) { addNativeEventListener('onShow', handlePageShow); } } componentWillUnmount () { if (isMiniApp) { removeNativeEventListener('onShow', handlePageShow); } } render () { return ( <View> <Text>1</Text> </View> ) } } if (isMiniApp) { registerNativeListeners(Index, ['onShow']); } export default Index;
在和 Taro 的對比中,能夠看出主要是兩點差別:(1)Rax 沒有 componentDidShow
componentDidHide
的概念,新增了和 W3C 標準相似的 addNativeEventLisenter
removeEventListener
等 API;(2)組件實例上沒有一個叫作 config
的靜態屬性用來設置頁面的 title
等配置。
這就是前文所說的不入侵基礎框架自己,React 自己實際上是沒有 componentDidShow
這些概念,由於這和組件自己的生命週期實際上是無關的。咱們更指望引導用戶用標準的 API 來寫業務代碼。同時,這種寫法的設計帶來的還有性能相關的提高,後文會具體說明。
固然這種設計自己會致使代碼量必定的膨脹,可是經過工程上的手段,是能夠保證最後產物代碼的體積幾乎毫無差別。
小程序的性能問題是在業務開發中常常會遇到的,爲此 Rax 小程序現有的編譯時方案也作了不少的努力。經過阿里小程序真機雲測的功能,咱們對一個無限下拉的列表作了測試。
頁面結構以下:
<img src="https://img.alicdn.com/tfs/TB19YlZv7L0gK0jSZFxXXXWHVXa-542-962.png" width="300" />
根據真機測試報告,原生小程序三次平均是 2008 ms,Taro 是 2003ms,Rax 是 1924ms,固然其實相差並很少,可是實際的業務場景其實遠比上面的頁面結構更加複雜。
與 Taro 相似的,Rax 小程序側的基礎框架沒有在邏輯層弄一個 VDOM,而是經過數據合併、傳統的數據 diff,來避免用戶更新冗餘的數據。更多的是,阿里小程序原生提供了私有方法 $spliceData
來進行性能優化,Rax 底層會去識別用戶須要更新的值是不是數組,而後自動根據場景使用 $spliceData
來優化渲染。
另外須要提到的是,前面說的原生事件監聽,小程序自己須要預先註冊才能監聽事件(這也是爲了保障性能),即須要:
Page({ onShow() {} });
而不能動態註冊:
const config = {} Page(config); setTimeout(() => { config.onShow = () => {}; }, 1000);
因此加入 componentDidShow
這類概念真的不是好的作法,這會致使頁面因爲不知道是否須要註冊 onShow
屬性而將全部的原生事件所有註冊監聽,這不只會形成開發者不能靈活擴展,更會致使內存泄漏的風險。
因而 Rax 小程序引入了 registerNativeListeners
,只給開發者一種新的認知,就是須要先在頁面上註冊事件才能進行監聽。這樣不只解決了擴展性的問題,還解決了潛在的性能問題。
固然,Rax 小程序能作的性能優化到此爲止了麼?在可計劃的將來,Rax 小程序編譯時方案已經有一些明確的 action,好比進一步減輕框架對 props
更新的管理,更多的利用小程序原生的能力來實現組件更新,從而避免和小程序基礎框架作重複的事情致使性能損耗。
Rax (可能)是業界首個同時支持編譯時和運行時方案的小程序解決方案。兩種方案之間的切換無比簡單,咱們將高性能 or 完整語法的選擇權真正地交給了用戶。雙引擎驅動的 Rax 小程序架構以下: 下面咱們將分別介紹兩種編譯方案。
Rax 小程序編譯時方案是基於 AST 轉譯的前提下,將 Rax 代碼經過詞法、語法分析轉譯成小程序原生代碼的過程。因爲 JavaScript 的動態能力以及 JSX 自己不是一個傳統模板類型語法,因此在這個方案中引入一個較爲輕量的運行時墊片。
Rax 小程序編譯時架構的核心主要分爲兩個部分,AST 轉譯和運行時墊片。下文會針對這兩個部分作簡要的介紹。
AST 轉譯部分的架構相比同類產品 Taro 來講,更加清晰以及可維護性更強。這裏不得不提到,它的分語法場景轉譯以及洋蔥模型。咱們能夠粗略的看一下,分語法場景轉譯部分的代碼結構:
<img src="https://img.alicdn.com/tfs/TB19kyNwaL7gK0jSZFBXXXZZpXa-318-664.png" width="200" />
能夠比較清晰的看到,針對須要轉譯的每個語法場景都有一個模塊專門負責轉譯,這就讓整個轉譯的過程輕鬆了起來,只要每一部分的轉譯結果符合預期,那麼轉譯結果就是符合預期的。這樣的設計可讓咱們可以充分利用單元測試來對轉譯先後的代碼進行比較。
而洋蔥模型的設計則是AST 轉譯的另外一個主要設計,整個轉譯過程實際上分爲 4 個步驟:
<img src="https://img.alicdn.com/tfs/TB1xNrbweH2gK0jSZJnXXaT1FXa-1592-154.png" height="60" />
洋蔥模型主要進行的是後面三步,在 parser 層將原有的 AST 樹修改成符合預期的新 AST 樹,而後在 generate 層將新的 AST 樹轉譯成小程序代碼。
因爲 JSX 的動態能力以及 Rax 本來提供的一些例如 hooks 之類的特性。因此,Rax 小程序編譯時方案提供了一個運行時墊片,用來對齊模擬 Rax core API 。
既然引入了運行時,天然能夠基於這套機制對數據流作更多的管理,以及提供 Rax 工程在其餘端上的 API,好比路由相關的 history
location
等。
Rax 小程序的運行時方案沒有自研,而是『站在了巨人的肩膀上』,複用了 kbone 的架構並對其做了必定程度的改造以接入 Rax 小程序的工程體系。關於運行時方案的實現原理能夠點擊這裏查看,此處再也不詳細介紹。首先須要介紹的是 Rax 小程序同時也是 kbone 的優勢:
而在 kbone 的基礎上,Rax 小程序運行時方案還新增了很多特性,歸納起來有如下幾點:
最後,咱們也不能迴避的是,Rax 小程序運行時方案具備全部運行時方案都存在的問題:性能損耗。事實上,運行時方案就是以必定的性能損耗來換取更爲全面的 Web 端特性支持。因此,若是你對小程序有必定的性能要求,建議使用編譯時方案;若是對性能要求不高,那麼運行時方案就是助你快速開發小程序的利器。雙引擎驅動的 Rax 小程序,總有一處可以擊中你的心裏。
Rax 小程序編譯時方案支持項目級開發和組件級開發。與 Taro 將組件統一在項目中進行編譯產出爲小程序代碼不一樣,Rax 在組件工程中便可構建出小程序組件。結合一套優秀的多端組件協議設計,咱們作到了在 Rax 小程序項目和原生小程序項目中都能正常使用 Rax 小程序組件,同時保持統一的多端開發體驗。該協議定義在 package.json 中的 miniappConfig
字段中,其具體用法設計能夠參見文檔 Rax 小程序——多端組件開發。
對於那些已經使用原生語法開發了完整的小程序的開發者來講,一個很合理的需求就是漸進地切換到 Rax 開發鏈路上來,畢竟整個項目遷移可能成本高昂。而 Rax 依託多端組件協議,可以幫助開發者平滑過渡。
按照設計,Rax 小程序組件工程的構建產物爲符合小程序語法的組件,所以其理所固然能夠直接在原生小程序項目中使用。這意味着,若是你想漸進式地使用 Rax 來開發小程序,能夠以組件或者頁面爲單位將以前使用原生語法開發的小程序逐漸地遷移到 Rax 上來。而這,也是 Taro 等其餘框架不具有的能力。在 Rax 的使用方中,浙江省網上政務平臺『浙裏辦』支付寶小程序即採用了漸進式接入 Rax 的方式。
當使用 Rax 組件工程發佈的小程序組件在 Rax 項目中使用時,構建器會自動經過 miniappConfig
規定的路徑去尋找該組件的小程序實現從而實現替換。用戶在業務代碼編寫層面無需像傳統引入原生小程序組件的方式同樣寫具體路徑,而是與 Web/Weex 端保持一致便可,示例以下:
// Wrong import CustomComponent from 'custom-component/miniapp/index' // Correct import CustomComponent from 'custom-component'
除此以外,多端組件協議還能夠擴展成多端組件庫協議,支持更靈活的相似 import { Button } from 'fusion-mobile'
的寫法。以 Rax 多端組件協議爲基礎,你能夠快速爲你的多端項目開發通用組件或者組件庫。好比,Rax 基礎組件就都是以該方式開發的。
Rax 工程以阿里巴巴集團前端統一的 CLI 工具 @alib/build-script 爲基礎,其依賴 webpack,經過插件體系支持各個場景,同時基於 webpack-chain 提供了靈活的 webpack 配置能力,用戶能夠經過組合各類插件實現工程需求。Rax 小程序的編譯時方案經過 webpack loader 來處理自身邏輯。以 app/page/component 等文件角色分類的 webpack loader 會調用 jsx-compiler 進行代碼的 AST 分析及處理,再將處理完的代碼交由 loader 生成對應的小程序文件;運行時方案直接複用 Web 端的編譯配置,再經過額外的 webpack 插件生成具體的小程序代碼。
相比其餘自研的工程體系,整套架構具備以下優勢:
最後,附上 Rax 和現有主流小程序框架的對比。
以上是咱們對 Rax 小程序的核心競爭力的階段性總結與思考。小程序已經不是初生牛犢,小程序的解決方案也早已汗牛充棟,但咱們相信,Rax 的入局,會讓你的小程序開發有那麼一些不同。
更多關於 Rax 小程序的內容,歡迎訪問 https://rax.js.org/miniapp 瞭解!