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