計划把微信的文章也搬一份上來。
這篇主要介紹一下我在玩Webpack過程中的心得。通過實例介紹WebPack的安裝,插件使用及加載策略。感受構建工具給前端優化工作帶來的便利。
壹 | Fisrt
曾幾何時,我們是如上圖的方式引入JS資源的,相信現在很少遇見了。近年來Web前端開發領域朝着規范開發的方向演進。體現在以下兩點:
-
MVC研發構架。多多益處(邏輯清晰,程序注重數據與表現分離,可讀性強,利於規避和排查問題...)
-
構建工具層出不窮。多多益處(提升團隊協作,以及工程運維,避免人工處理瑣碎而重復的工作)
- 模塊化開發
- 將前端性能優化理論落地,代碼壓縮,合並,緩存控制,提取公共代碼等
- 其他的還包括比如你可以用ES 6 或CoffeeScript寫源碼,然后構建出瀏覽器支持的ES5
所以,前端這么好玩,如果還有項目沒有前后端分離的話,真的是守舊過頭了。
主流構建工具
市面上有許多構建工具,包括Grunt、Gulp、browserify等,這些和WebPack都是打包工具。但WebPack同時也具備以下特點:
-
相比Grunt,WebPack除了具備豐富的插件外,同時帶有一套加載(Loader)系統。使它支持多種規范的加載方式,包括ES6、CommonJS、AMD等方式,這是Grunt、Gulp所不具備的。
-
從代碼混淆的角度來看,WebPack更加的極致
-
代碼分片為處理單元(而不是文件),使得文件的分片更為靈活。
P.S.此處只做簡單的比較,不論孰優孰劣。其實工具都能滿足需求,關鍵是看怎么用,工具的使用背后是對前端性能優化的理解程度。
貳 | Second
WebPack安裝與使用
WebPack運行在 NodeJS之下,並且它及其插件都是使用NPM(NodeJS的包管理工具)管理。
-
安裝Node及NPM。到NodeJS官網安裝包,安裝即可
-
全局安裝WebPack。聯網情況下,執行命令行 $npm install webpack -g 即可。
此至即可使用WebPack了,到WebPack官網去按着Get start(http://webpack.github.io/docs/tutorials/getting-started/)的步驟來,感受一個最簡單的構建過程。
然而要把WebPack用好,只是跑起來是遠遠不夠的。
叄 | Third
WebPack插件
花較大篇幅介紹插件的使用,以下通過在一個DemoApp的構建過程中思考的一些問題(這些問題基本會在每個項目中遇到),讓這些插件逐一登場。
一、文件過大
DemoApp最初的構建結果如下:
這里生成了一個topic.xxx.js,這個文件本來很小,估計只有10Kb左右,但構建的結果居然快1Mb了。在3G網絡(750Kb/s)下的加載瀑布流如下圖:
這張圖(體現前端文件加載過程)曝露了幾個問題:
-
上面箭頭所指的很藍色柱子,說明了大部分時間消耗在加載topic.xxxx.js上。
-
所有JS文件相關的柱子是一根結束了另一根才開始,說明不合理的文件合並策略,導致文件串行加載。
-
結果就是如底部的箭頭所看到的,頁面的加載時間太長了(3G網絡,10+秒)。
觀察構建的文件,發現原來構建工具把React、jQuery等代碼庫合並到了topic.xxxx.js,造成此文件過大。如果再加一個activity模塊呢?很明顯,activity.xxx.js得到類似的結果,都太大了,並且React、jQuery等代碼庫重復被合並到activity和topic里,如下圖。如果再加模塊也會得到同樣的結果,模塊越多重復加載的情況越嚴重。
可見,提取公共代碼,情況可以得到改善,另外,壓縮代碼無疑是可以使文件變小的。
1. 提取React、jQuery等庫文件。它們很少變化,並且到處被復用,應該被提取出來,並且得到長時間的緩存。
此處使用插件:WebPack.optimize.CommonsChunkPlugin(WebPack內建插件)
2. 代碼壓縮。React由700+ Kb壓縮成100+ Kb
此處使用插件:WebPack.optimize.UglifyJsPlugin (WebPack內建插件)
處理后topic.xxx.js和activity.xxx.js都很小了,並且多了jquery.xxx.js和react.xxx.js
再看看文件加載的瀑布流,柱子所占比例短了,同時資源並行加載。
到此為止,這個問題算比較好地解決了,但還不夠,隨着項目越來越大,還有一個重要因素會導致文件很大。這部分內容放到本文的最后介紹。
二、如何緩存
緩存控制要做到兩件事情,提到緩存命中率
-
對於沒有修改的文件,從緩存中獲取文件
-
對於已經修改的文件,不要從緩存中獲取
圍繞這兩點,演繹出了很多方案,此處列兩種:
-
不處理,等待用戶瀏覽器緩存過期,自動更新。這是最偷懶的,命中率低一些,同時可能會出現部分文件沒有更新,導致報錯的情況。
-
Http頭對文件設置很大的max-age,例如1年。同時,給每個文件命名上帶上該文件的版本號,例如把文件的hash值做為版本號,topic. ef8bed6c.js。即是讓文件很長時間不過期。
-
當文件沒有更新時,使用緩存的文件自然不會出錯;
-
當文件已經有更新時,其hash值必然改變,此時文件名變了,自然不存在此文件的緩存,於是瀏覽器會去加載最新的文件。
從上面的截圖可以看出來,通過WebPack是可以很輕松做到第二點的——只需要給文件名配置上[chunkhash:8]即可,其中8是指hash長度為8,默認是16。
P.S.這樣的處理效果已經很好了,但同樣有劣處,即瀏覽器給這種緩存方式的緩存容量太少了,只有12Mb,且不分Host。所以更極致的做法是以文件名為Key,文件內容為value,緩存在localStorage里,命中則從緩存中取,不命中則去服務器取,雖然緩存容量也只有5Mb,但是每個Host是獨享這5Mb的。
三、自動生成頁面
文件名帶上版本號后,每一次文件變化,都需要Html文件里手動修改引用的文件名,這種重復工作很瑣碎且容易出錯。
使用 HtmlWebpackPlugin 和 ExtractTextPlugin 插件可以解決此問題。
-
生成帶JS的頁面
-
- 生成帶css的頁面
new ExtractTextPlugin("comm.[contenthash:9].css")
插件介紹到此為止,然而,還有一個關於同步加載和異步加載的問題,否則入口文件還是會很臃腫。
肆 | Fourth
關於同步加載和異步加載
使用WebPack打包,最爽的事情莫過於可以像服務器編程那樣直接require文件,看起來是同步地從服務器上取得文件直接就使用了。如下面的代碼一樣,沒有任何異步邏輯,代碼很干凈。
然而,這種爽是有代價的,對於直接require模塊,WebPack的做法是把依賴的文件都打包在一起,造成文件很臃腫。
所以在寫代碼的時候要注意適度同步加載,同步的代碼會被合成並且打包在一起;異步加載的代碼會被分片成一個個chunk,在需要該模塊時再加載,即按需加載,這個度是要開發者自己把握的,同步加載過多代碼會造成文件過大影響加載速度,異步過多則文件太碎,造成過多的Http請求,同樣影響加載速度。
-
同步加載的寫法,如:
var TopicItem = require('../topic/topicitem');
-
異步加載的寫法,如:
一個原則是:首屏需要的同步加載,首屏過后才需要的則按需加載(異步)
結語
以上是WebPack構建工具比較好的實踐,可見,要用好還是很考驗前端性能優化的功力的,比較什么時候同步,什么時候異步,如果做緩存等等。
如果覺得文章有用,順手點擊下方的推薦