LsLoader——通用移动端Web App离线化方案
由于JavaScript(以下簡稱JS)語言的特性,前端作用域拆分一直是前端開發中的首要關卡。從簡單的全局變量分配,到RequireJS實現的AMD模塊方式,browserify/webpack實現的靜態引用方式。前端的業務邏輯也從一個個精心按順序排好的CSS、JS變成了入口文件為根的有向無環圖。圖上的節點可能是JS方法/CSS糖/.vue單文件模塊。我們的開發源文件到瀏覽器一般經歷下圖的過程:
細化拆分是前端趨勢,有利于代碼的分割和維護。但是現有構建工具的一攬子打包過程,會造成如下性能沖突:包打大了多頁面間模塊文件重復下載,時間浪費,頁面加載時間也變長;包小了緩存率提高,但是HTTP請求又過多,同樣影響加載時間。下圖為RequireJS/webpack打包后瀏覽器運行的文件:
要提高Web App的性能,我們需要這樣的一個工具,它能實現如下特性:
1) JS按照模塊拆開緩存。
2) 每次更新通過線上combo合并成一個HTTP請求。
3) 最好緩存過程和原來技術棧分離,即插即用。
結合業內其它離線化方案,我們在業務開發中推出了LsLoader.js解決緩存問題,把模塊文件緩存到瀏覽器localStorage中,用JS控制模塊文件更新與運行。
用localStorage緩存的優劣勢有哪些呢?
首先優勢:
1) localStorage對于移動端兼容好,主流手機瀏覽器、WebView都有支持,且沒有iOS UIWebView的退出進程緩存失效bug。
2) 完全JS控制,前端代碼不依賴宿主環境配合。
3) 弱網情況下對加載速度提升明顯,3G信號較差情況下,100K的前端代碼加載block頁面能達到1~3秒。
4) 對比類似的Progressive Web App(PWA)和微信小程序,localStorage可應用業務線廣,環境如微信/客戶端WebView/瀏覽器,業務形式如單頁/多頁Web/H5活動頁。
高兼容、高效率緩存就是localStorage緩存體系的核心優勢。
但是localStorage也不是萬能,其劣勢如下:
1) 鑒于執行機制問題,append JS代碼執行速度略低于script標簽運行JS文件,append解析文件內容需要額外耗費時間。
2) PC環境網絡條件好,模塊級別更新節省的時間收益不明顯,不如不用。
為了兼容各種業務線的前端構建棧,從原始的全局作用域分塊,到RequireJS的AMD格式,再到現如今的webpack1/2,LsLoader采用了3層分離式設計。
3層結構包括了:
1) 前端構建層:你業務原有的gulp、webpack構建流。
2) LsLoader處理層/編譯層:通過UglifyJS或者Babylon.js,分析你JS源碼里的依賴關系,提取成Lsloader能識別的格式。
3) 構建結果層:一個壓縮后2K的內聯腳本,定義了如何緩存/加載/更新模塊文件的瀏覽器庫,支持iOS/安卓各種瀏覽器、WebView,對禁用localStorage的環境,寫滿localStorage的情況也都做了兼容。
前端構建層和LsLoader加工層互相隔離,業務代碼不需要調整模塊依賴方式,只需要原來構建流程的模式上在外加個轉換流程即可。LsLoader轉換過程會分析源碼把模塊文件的依賴關系歸納成數組形式,在瀏覽器端緩存/加載后按順序執行。
這種設計不同于美團移動版i.meituan.com的TruckJS或者Scrat.js等構建工具,使用前面兩種構建方案需要使用它的一整套構建配置,LsLoader只做一個中間件形式的構建工具,方便從你的業務代碼中接入/去除。
上面的3層結構中,前端構建流程不多贅述,對應RequireJS或者webpack的文檔指南。
LsLoader處理層做的工作原理圖如下:
對于RequireJS構建:首先把源文件用Babylon或者Uglify處理成語法文檔樹,然后找到對應的define函數和參數,遞歸尋找去除重復引用,最后排列成按依賴順序的模塊數組。
對于webpack構建:首先把源文件用Babylon或者Uglify處理成語法文檔樹,遍歷入口JS的import依賴,把公用依賴的JS提取成數組傳給commonChunksPlugin配置,讓webpack進行文件拆分。同時依賴關系傳遞給LsLoader前端部分,讓頁面按照依賴關系加載運行webpackJSONP包。
經過處理后,對應的文件列表在瀏覽器端以數組的方式運行/緩存,流程如下:
每個模塊文件通過/combojs/注釋來分割,支持各種格式的前端包裹格式如define、webpackJSONP。
宏觀效果:
美團外賣紅包完全加載時間從3s下降到2s,下降30%;可交互2.2s下降到1.2s,下降了45%。
美團外賣i版首頁完全加載2.8s下降到1.8s。
每天靜態資源加載大小為80K*450萬=343G,CDN請求大小為78G,節省流量為265G。
微觀效果:
線上運行示例
https://i.waimai.meituan.com/node/demo/vue
localStorage:
我們可以看到,這個頁面有1個入口文件、3個依賴包,都被分別緩存在了localStorage里面,每次更新也只有一個模塊文件的下載過程。
網絡請求:
3個模塊文件被合并成一個請求,返回結果用注釋符做切割。
通用的頁面,如果我們不用拆分緩存的方案,打大包的話,結果是生成一個80K的單文件。這僅僅是個簡單的Vue列表,如果多頁多組件應用下載浪費會更嚴重。
下面我再帶來個復雜點的頁面:
一個Vue實現的2頁面切換的單頁手機界面,使用LsLoader和不使用LsLoader的區別有多大?
從上圖我們可以看出,這個App依賴了4個組件文件、2個類庫、3個功能JS模塊、總共9個包,120K。
采用單一大包,每次上線的代價是120K;使用Lsloader,每次模塊級別更新,通常迭代修改影響1~2個文件,更新代價約為10K~20K。
而且這9個文件中,6個文件可以頁面間公用,包括2個類庫(Vue、Zepto);3個JS模塊(getURLParams、historyState、WebView API)通用的邏輯函數;1個公共Vue組件(no-data.vue)兜底頁面的組件。
這些模塊在用戶打開此頁面后同域名其他引用模塊的頁面也得到了提前加速頁面的效果。開發保證邏輯清晰的同時也提升了整站的性能。
由于有著對各種Web構建的天生兼容,LsLoader可以自己定制擴展附加功能。比如單頁應用的按需分割加載/緩存的支持。在Hybird開發中,我們可以把關鍵頁面的資源列表生成個預加載的空頁面,讓客戶端進入后提前打開隱藏的WebView加載這個頁面,利用localStorage緩存實現預加載靜態資源提升首屏H5的預加載功能。
環宇,美團外賣高級前端研發工程師。2016年加入美團外賣,負責外賣i版、外賣紅包頁、外賣客戶端Hybird頁面等用戶端頁面開發。對組件化開發、頁面提速、頁面體驗提升等較為關注。主導了外賣H5頁面從gulp+Requirejs到webpack 2+Vue 2+LsLoader技術棧的一步步升級遷移。希望用最小的開發代價來解決業務線性能問題,把最便捷的方案和同行分享。
總結
以上是生活随笔為你收集整理的LsLoader——通用移动端Web App离线化方案的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 美团点评联盟广告场景化定向排序机制
- 下一篇: Spring Boot 2.2 正式发布