ReactNative是Facebook推出的跨端框架,可以用一套代碼開發Android、iOS應用,藉助社區的react-native-web框架,ReactNative應用還可以運行在瀏覽器中。那麼有沒有辦法把ReactNative應用運行在國內流行的小程序平臺呢?
這就是今天要介紹的Alita工具引擎,近期Alita發佈了2.0版本,其構建方式基於webpack進行了重構,藉助webpack的靈活性,2.0版本帶來了諸多新特性,包括完善npm支持,包大小分析,一鍵小程序分包等等。Alita侵入性很低,選用與否,並不會對你的原有ReactNative開發方式造成太大影響。看下實際效果(web由react-native-web運行提供)。
下面我們從基本原理、2.0版本新特性、性能優化這三個方面介紹一下Alita。
基本原理
與現有很多編譯時方案不同,Alita對React語法有全新的處理方式,支持在運行時處理React語法,JSX語法在運行期仍然是React.createElement調用。Alita的整體架構借鑑了ReactNative,其上層存在一個爲小程序定製的mini-react,底層是負責實際渲染的小程序原生代碼。而中間存在一個兩層互相聯繫的bridge。
mini-react負責運行所有React代碼邏輯,包括遞歸的構建組件樹結構,創建組件實例,執行組件對應生命週期,context計算等等。其最終將生成一份描述小程序視圖的數據。這份數據通過bridge模塊傳遞到底層小程序。底層小程序實例調用setData方法把數據刷給自身,完成渲染。
另外這種架構及渲染方式,我們在flutter平臺也做了些許的嘗試。
2.0版本新特性
自2.x版本以後,Alita改爲基於webpack打包構建,藉助於webpack的特性,2.x版本的Alita更加簡單易用。
完善npm包支持
微信小程序有一套自己的npm包使用方式,這種方式並不完全和官方npm兼容,這會導致很多常見的包不能直接在小程序平臺使用。比如很多包都是以如下的方式提供
if (process.env.NODE_ENV === 'production') {
module.exports = require('./index.production.min.js');
} else {
module.exports = require('./index.development.js');
}
但是微信小程序平臺並不支持process.env.NODE_ENV,導致以上的包無法使用,會報錯Uncaught ReferenceError: process is not defined
。
另外小程序怪異的npm包使用方式,要求其在安裝之後手動執行npm構建
過程,這給本地開發調試包帶了很多麻煩。
Alita基於webpack的打包等於是移除了對小程序npm能力的依賴,整個打包過程更加可控。另外藉助於webpack靈活的loader/plugin機制,可以實現諸多常見特性。比如藉助DefinePlugin插件就可以處理process.env.NODE_ENV,實現在開發模式/生產模式下加載不同包的需求。
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV)
});
小程序體積分析及優化
我們知道微信小程序的體積是有限制的,不能超過2M。通過Alita運行的小程序上當然也有這樣的限制,好在webpack內置了tree-shaking,但是仍然會有體積超過2M的情況,此時如何減少小程序大小呢?首先需要知道每一個文件、每一個包占據了多少空間。藉助webpack的BundleAnalyzerPlugin插件(Alita內置了這個插件),事情變得非常簡單,只需要在執行Alita打包命令的時候添加 --analyzer 參數即可。下面是Alita提供的案例代碼Todo,添加–analyzer 參數之後的大小分佈結構。
有了這份大小分佈圖,優化變得有的放矢。這裏lodash庫佔據了很多空間,有500多KB。實際上,通過分析發現只有redux-promise引用了lodash ,使用了個別的lodash庫方法,我們可以發佈一個簡單的包 redux-promise-nolodash 替換一下即可。再次執行 alita --analyzer ,結果如下,bundle的大小由700多KB,降到了200多KB:
上面的方式,需要手動把代碼中對redux-promise的引用改爲redux-promise-nolodash,當文件很大項目已經成熟的時候,顯然不太方便。更簡單的,由於Alita基於webpack構建打包,所以webpack的配置字段基本上可以在Alita的配置文件中複用,只需要配置resove alias 字段即可:
module.exports = {
resolve: {
alias: {
"redux-promise": "redux-promise-nolodash",
}
}
}
一鍵自動小程序分包
有時當小程序越發複雜的時候,體積的確是需要超過2M的,此外爲了減少首屏加載時長,需要減少初次加載的內容。這就需要使用到分包的能力,使用分包以後小程序體積可以支持到12M。小程序的分包如下:
小程序要求你在分包的時候,主從包的依賴必須手動管理好,比如分包A不能依賴分包B的代碼,當業務越發複雜,每一個模塊之間的依賴關係將變的特別複雜,這給小程序原生的手動分包帶來了很多麻煩,比如我們必須手動把共用依賴提取到主包中維護等諸如此類。
但是,管理模塊依賴是webpack的強項!webpack很早就有提取公共模塊拆分包的插件CommonsChunkPlugin,webpack4.0以後更是提供了更加強大的SplitChunkPlugin,基於SplitChunkPlugin插件Alita實現了非常易用的自動分包。比如:
<Router>
<Route key={"A"} component={A}/>
<Route subpage={'sub1'} key={"B"} component={B}/>
<Route subpage={'sub1'} key={"C"} component={C}/>
<Route subpage={'sub2'} key={"D"} component={D}/>
<Route subpage={'sub2'} key={"E"} component={E}/>
</Router>
只需要在路由配置中添加subpage屬性即可!如上的配置,Alita將把你的目標小程序分成3個包:主包包含A頁, sub1分包保護B,C頁,sub2包含D,E頁。共用依賴模塊將會被提取到主包,而B,C單獨依賴模塊項將只會在sub1分包存在,整個過程不需要任何的手動干預,也不需要開發者自己去管理模塊直接的依賴關係。
藉助於webpack靈活的plugin/loader機制,整個Alita構建打包的過程更加可控,擴展功能更加方便。官方及社區豐富的插件,也幫助我們實現了自動分包,包體積分析優化等諸多功能。
性能相關
通過Alita運行的小程序性能如何呢?後續文章我們會帶來更加詳細的性能評測數據,本文先從整體架構上分析一下性能相關的問題。正如前文所說,Alita運行階段存在兩個過程,首先是上層的React運行階段,包括遞歸遍歷組件樹,產生渲染數據等。然後通過bridge把這份數據刷給小程序,小程序接收到數據之後通過調用自身setData渲染。由此可見組件的渲染耗時主要由兩個部分組成:React計算時長和小程序渲染時長。
下面分別說明。
setState合併
首先,Alita會盡量減少整個渲染過程的頻率,移除無效渲染。具體的,Alita並不會對每次setState都執行計算過程,熟悉React的同學都知道,React會嘗試合併每次的setState。 比如
class A extends Component {
componentDidMount() {
this.setState({})
this.setState({})
this.setState({})
}
...
}
上面的A組件componentDidMount 調用了3次setState。更加複雜的情形
class A extends Component {
componentDidMount() {
xxx.setState({})
yyy.setState({})
zzz.setState({})
}
...
}
componentDidMount分別調用了xxx,yyy,zzz組件的setState。類似React,Alita的mini-react合併策略會保證上面的所有這些setState只發生一次實際的計算過程。
小程序批量更新
每一個上層React組件,Alita都會在小程序端生成一個對應的小程序自定義組件來負責這個組件的渲染,這保證了局部刷新只會影響局部組件,但是也帶來了問題。當計算過程結束,渲染數據傳遞過來的時候,會出現很多小程序組件同時需要調用setData更新自身的數據的情況,這在列表數組渲染多個組件的時候,尤其明顯。而setData調用是小程序最耗時的操作,頻繁調用會產生卡頓。有沒有辦法一次setData交互,就把所有小程序組件數據更新完成呢?答案是可以的,Alita內部利用了小程序groupSetData,實現了小程序批量更新,本質上每次上層React計算之後,底層小程序只需要交互一次就可以完成所有數據的刷新。這在頁面複雜,組件衆多,尤其是大量組件同時頻繁更新的業務場景下,會有非常顯著的性能提升。
冗餘數據與增量修改
state中的所有數據都會影響視圖嗎?不一定。有的時候我們會偷懶直接把後端返回的數據放在state,或者放置一個複雜的對象卻只用到個別屬性渲染,比如一個 Date對象。所以一般來說state裏面都會存在不涉及視圖渲染的冗餘數據,這些數據在mini-react的計算過程中會自動移除掉,傳遞給bridge的渲染數據只涉及視圖相關數據。
更近一步,這份數據還會被diff 處理,最終小程序setData設置的只是極簡的增量數據。
比如類似以上的新舊數據,diff之後只有age字段發生了改變,所以小程序實際更新只會執行:setData({“person.age”: 19})
除了上文提及的優化之外,Alita還會對ReactNative官方組件節點做特別的優化,View,Image等組件會直接用小程序view,image組件替代,另外Alita還會嘗試扁平過深的節點結構。
結束語
“Talk is cheap. Show me the code!!”,Alita的所有代碼都託管在了github,包括上文提到的flutter渲染在小程序的方案(flutter方案由於時間關係,更新非常有限)。地址如下:
Alita: https://github.com/areslabs/alita
flutter_mp: https://github.com/areslabs/flutter_mp
歡迎使用 & 交流 & issue & star !!
作者介紹:
京東ARES嚴康、劉豔