您當前位置>首頁 » 新聞資(zī)訊 » 小程序相關(guān) >
Prepack詳細介紹及微信小程序優化的新思路(lù)
發表時間:2021-4-30
發布人:葵宇科技
浏覽次數:49
前言
Prepack前幾個(gè)月(yuè)剛出來的時候已經得到了前端界的大範圍關(guān)注,而在不久之後又逐漸退出了人們的視線。此時這篇文(wén)章出來可(kě)能顯得有些滞後,個(gè)人還是比較看好它未來對于前端代碼預編譯優化所帶來的收益。所以再詳細地介紹一下(xià)Prepack和(hé)它給我帶來的思考。
在前端技術(shù)疊代更新速度較快、前端人力寶貴的情況下(xià),面對新技術(shù)的不斷湧現我們需要保持冷(lěng)靜和(hé)嚴謹的态度去接受這些新技術(shù),所以一般在一個(gè)新技術(shù)湧現時,我都會先弄清楚這幾個(gè)問(wèn)題再考慮是否要推動(dòng)和(hé)更疊現有的技術(shù)棧:
是什麼?
解決了什麼問(wèn)題?
帶來了什麼新的問(wèn)題?
新的問(wèn)題和(hé)解決的問(wèn)題在目前場景下(xià)權重是怎麼樣的?
投入産出比如(rú)何?
帶着這幾個(gè)問(wèn)題進入正題。
一、什麼是Prepack
官網的第一句是:A tool for making JavaScript code run faster. —— 一個(gè)讓JavaScript代碼運行更快的工具。
實際上Prepack 就是一個(gè)部分求值器(qì)(Partial Evaluator),代碼打包編譯時提前将計算結果放到編譯後的代碼中(zhōng),從而去除冗餘的代碼(相對運行時來說,對應的代碼邏輯本身并不是冗餘的),而不是在代碼真正運行時才去求值。消除那些本可(kě)以在編譯階段完成的運行時計算,一定程度上減少(shǎo)了javascript資(zī)源包大小以及浏覽器(qì)運行js所耗費的時間。詳情移步prepack.io/
另外要說明的是,Prepack雖然能一定程度上減少(shǎo)代碼量的大小,但是Prepack開發團隊開發它的初衷并不是為了減少(shǎo)代碼包大小,主要還是減少(shǎo)運行時的時間消耗,代碼包的大小減少(shǎo)算是一個(gè)附加收益。
二、工作原理
根據官網列的幾個(gè)點依次進行詳細解釋。
Abstract Syntax Tree (AST) 抽象語法樹(shù) (粒度) Prepack在抽象語法樹(shù)的級别運行,使用Babel解析并生成JavaScript源代碼。 那什麼是“在抽象語法樹(shù)的級别運行”? 将一種結構化語言編譯成另一個(gè)結構化語言的代碼過程如(rú)下(xià):
1、Parse,将代碼解析成抽象語法樹(shù)(AST);
2、對抽象語法樹(shù)(AST)進行遍曆(traverse)和(hé)替換(replace) 生成新的 抽象語法樹(shù)(AST);
3、Generator—— 根據新的 AST 生成編譯後的代碼; “在抽象語法樹(shù)的級别運行” 說明了Prepack的編譯流程,也說明了編譯粒度。 比抽象語法樹(shù)粒度更細的還有一個(gè)分析樹(shù)。 例如(rú)以下(xià)一個(gè)3+4 例子(zǐ)的對比: 分析樹(shù):
抽象語法樹(shù):
概括來說,分析樹(shù)包含了記号序列和(hé)推導的各個(gè)步驟,拆分粒度更細;抽象語法樹(shù)則隻包含了關(guān)鍵的信息,不知道具體的文(wén)法和(hé)運算符細節。 在抽象語法樹(shù)的級别編譯,也就是說不用依賴于具體的文(wén)法,不依賴于語言的細節。也進一步說明,原code經過語法分析後總是構造出相同的抽象語法樹(shù),再通(tōng)過Prepack進行轉換處理成新的抽象語法樹(shù),Prepack不會對原有的代碼文(wén)法和(hé)處理邏輯帶來影響,對編譯器(qì)的接口的統一性也不會造成影響。 對編譯更多細節感興趣的可(kě)讀 兩周自制腳本語言 (圖靈程序設計叢書)
Concrete Execution 具體執行 Prepack的核心是一個(gè)JavaScript解釋器(qì),它與ECMAScript 5幾乎完全兼容,而且緊密地保持與 ECMAScript 2016語言規範 的一緻性,你(nǐ)可(kě)以将Prepack中(zhōng)的解釋器(qì)視為完全參照JavaScript實現的。
解釋器(qì)能夠跟蹤并撤銷包括所有對象Mutation在内的結果,從而能夠進行推測優化(Speculative Optimization)。
Symbolic Execution 符号執行 除了對具體值進行計算外,Prepack的解釋器(qì)還可(kě)以操作受環境相互作用影響的抽象值。例如(rú)Date.now可(kě)以返回一個(gè)抽象值,你(nǐ)可(kě)以通(tōng)過helper輔助函數(如(rú)__abstract())手動(dòng)注入抽象值。Prepack會跟蹤所有在抽象值上執行的操作,在遇到分支時,Prepack會執行并探索所有可(kě)能性。所以,Prepack實現了一套JavaScript的符号執行引擎。 以上是官網的翻譯,就是符号可(kě)以代表一個(gè)根據環境不同而可(kě)變的一個(gè)方法執行結果,例如(rú)以下(xià)例子(zǐ)中(zhōng)的_0就是這樣的一個(gè)抽象值,它無法提前計算,因為在不同的浏覽器(qì)或運行時機下(xià)結果是不同的。左邊編譯前代碼,x 存在if 判斷的分支情況,Prepack會彙總所有的分支情況進行優化編譯,如(rú)例子(zǐ)中(zhōng)将所有分支彙總(2個(gè)分支)替換成三目運算符表達。
Abstract Interpretation 抽象釋義 符号執行在遇到抽象值的分支時會分叉(fork),Prepack會在控制流合并點加入分歧執行(Diverged Execution)來實現抽象釋義的形式。連接變量和(hé)堆屬性可(kě)能會得到條件抽象值,Prepack會跟蹤有關(guān)抽象值和(hé)型域(Type Domain)的信息。 還是之前的例子(zǐ),就是抽象值“_0 ” 存在2個(gè)分支,Prepack底層會彙總分支情況,生成新的抽象語法樹(shù),在優化解析時(Generator)通(tōng)過連接變量和(hé)堆屬性,得到各分支下(xià)的抽象值“_0 ”可(kě)能的對應情況,并跟蹤相關(guān)情況的信息計算出結果形成新的代碼。
Heap Serialization堆序列化
這裡的堆序列化其實指的是當全局代碼返回,初始化階段結束時,也就是在generator 過程中(zhōng)Prepack 會捕獲最終的堆,按順序遍曆堆創建、鍊接堆中(zhōng)的可(kě)及對象。堆中(zhōng)的一些值可(kě)能是對抽象值進行計算的結果,Prepack 将會根據這些值生成執行計算的代碼,最終組成新的代碼。
總結一下(xià)Prepack的工作過程:
開發的代碼經過解析(Parse)形成抽象語法樹(shù)
Transform時期預先計算出的可(kě)求的值,對存在受環境影響的抽象值進行抽象釋義,轉換成新的抽象樹(shù)
Generate時期按順序遍曆堆進行符号執行和(hé)跟蹤有關(guān)抽象值和(hé)型域信息處理抽象值等,最終組成新的代碼。
三、示例
官網(prepack.io/)有舉代表性的例子(zǐ),這裡不再詳述。還可(kě)以通(tōng)過線上的Prepackprepack.io/repl.html将自己的代碼輸入動(dòng)态查看解析結果。
四、Prepack 引入會帶來的問(wèn)題
Prepack 帶來的優勢是把 js 的 AST 編譯到更加低級的語義,提前計算出值, 減少(shǎo) js 代碼在浏覽器(qì)端初始化運行消耗的時間,附帶收益是減少(shǎo)了代碼包的大小,還可(kě)以與Closure Compiler 配合進一步縮小代碼包.,但同時也帶來了新的問(wèn)題:
沒有官方統一、成熟的方案支持打包集成到開發環境——需要開發者另辟蹊徑完成集成
不能識别 document 和(hé) window,會識别為 undefined——浏覽器(qì)環境代碼利用Prepack存在較大局限性
沒有完全支持浏覽器(qì)環境和(hé)node.js環境——node代碼不能全面接入Prepack
生成的代碼沒有針對引擎做好優化——運行效率會存在變低的情況
存在一些尚未解決的issues——會有更多的“坑”需要踩
五、Prepack 未來規劃
目前Prepack仍處于早期開發階段,尚未準備好在生産環境中(zhōng)使用,官方建議僅嘗試使用,并且歡迎提供反饋以幫助修複錯誤。
Prepack團隊對未來的規劃如(rú)下(xià):
短(duǎn)期
穩定現有功能集,用于預優化(Prepack)React Native代碼包
集成React Native工具鍊
根據React Native所用模塊系統的假設來構建優化
中(zhōng)期
進一步優化序列化(Serialization),包括:消除不暴露特性(identity)的對象;消除未使用的導出屬性,等等
預優化每個(gè)函數、基本代碼塊、語句、表達式
與ES6保持完全一緻
支持廣泛的模塊系統
假設ES6支持某些功能,延遲完成或直接忽略Polyfill應用
進一步實現Web和(hé)Node.js環境中(zhōng)的兼容性目标
深入集成JavaScript虛拟機,改進堆反序列化過程,包括 :暴露“對象懶初始化”的概念 - 以一種JavaScript無感知的方式,在首次使用對象時對其進行初始化;通(tōng)過專門的字節碼提高普通(tōng)對象創建的編碼效率;将代碼分為兩個(gè)階段:1) 非環境依賴階段,虛拟機可(kě)以安全地捕獲并恢複生成的堆;2)環境依賴階段,通(tōng)過從環境中(zhōng)獲得的值執行所有剩餘的計算過程來拼湊具體的堆,等等
總結循環和(hé)遞歸
長期 - 利用Prepack作為一個(gè)平台
JavaScript Playground - 通(tōng)過調整JavaScript引擎體驗JavaScript特性,這些引擎由JavaScript所編寫,托管在浏覽器(qì)中(zhōng);你(nǐ)可(kě)以把它想象成一個(gè)“Babel虛拟機”,實現了不能被編譯的JavaScript新特性
捉Bug - 發現異常崩潰、執行問(wèn)題……
效果分析,例如(rú)檢測模塊工廠函數可(kě)能的副作用或強制純淨注釋
類型分析
信息流分析
調用圖推理,允許内聯和(hé)代碼索引
自動(dòng)測試生成,利用符号執行的特性與約束求解器(qì)(Constraint Solver)結合來計算執行不同執行路(lù)徑的輸入
智能模糊(Smart Fuzzing)
JavaScript沙盒 - 以不可(kě)觀察的方式有效地測試JavaScript代碼
六、Prepack給微信小程序優化帶來的新思路(lù)
從以上Prepack帶來的優勢和(hé)問(wèn)題來看,在普遍的前端項目中(zhōng)使用的話投入較大于産出,目前必然是不适宜投入到項目中(zhōng)使用。
雖然目前Prepack尚未完善,但是開拓了一個(gè)新的js代碼優化方向。減少(shǎo) js 代碼在浏覽器(qì)端初始化運行消耗的時間,這對于提升用戶體驗來說也是很關(guān)鍵的點。例如(rú) app本身的啟動(dòng)一般也比較占用時間,所以一般app 都會有一個(gè)啟動(dòng)時的廣告頁或靜态介紹頁,借此消除啟動(dòng)時間較長給用戶帶來的不良體驗。 App可(kě)以用廣告頁和(hé)靜态介紹頁吸引用戶的注意力。
最近在做小程序相關(guān)的業(yè)務,微信小程序本質是app,在業(yè)務邏輯較重的小程序也同樣存在啟動(dòng)時間消耗帶來不良用戶體驗的問(wèn)題,但是卻不能以普通(tōng)app的解決方案來解決。因為微信本身比較注重用戶體驗,是一個(gè)擅長做減法且克制的産品,加入廣告和(hé)啟動(dòng)頁不僅會影響用戶體驗還破壞産品的“克制”性。 所以小程序的啟動(dòng)優化就需要新的解決思路(lù),而Prepack就剛好帶來了曙光。
微信小程序不存在 document 和(hé) window 對象,也非在node.js環境,不受它帶來的問(wèn)題所局限。可(kě)以通(tōng)過腳本或其他集成方式将Prepack加入到打包流程中(zhōng)完成優化,也可(kě)降級處理部分主要文(wén)件發揮他的作用。總之和(hé)微信小程序之間存在着可(kě)能性和(hé)可(kě)行性,後續會再出針對小程序和(hé)Prepack的實踐文(wén)章,感興趣的同學可(kě)待續。
以上純屬個(gè)人觀點,可(kě)能存在不足或錯誤之處,歡迎指正。
作者介紹:雪(xuě)婧,美團點評點餐團隊成員。