事情緣由
近段時間在做基於scratch3.0的改造項目。基於scratch-gui改造,項目本身已經很大了,然后里面還要用到scratch-blocks,scratch-vm,scratch-render等外部第三方項目。官方的配置是所有的東西打入一個lib中,所有的html都使用這一個lib。
現在有一個需求是:h5頁面僅僅展示scratch做出來的作品,但是目前加載很慢,需要優化。
scratch原生的打包配置如下(html-webapck-plugin@3.2.0)
打包結果全部js在lib.min中,有26M左右
優化思路
第一階段(不更改代碼,僅僅做分包的優化):
- 利用webpack optimization.splitChunks的vendors配置將所有的第三方包提取到vendors中【本人額外配置了一個所有入口都使用的第三方包bundle:‘vender.min'】;
- 利用webpack optimization.splitChunks的default配置【默認的配置】自動提取各個入口js的共用代碼組成bundle的功能。
- 分離出manifest文件,確保沒有更改的包打包結果不會更改。
上面基本都利用了默認的配置【不配置的屬性即使用默認值】,webpack默認的配置【每一個版本還不太一樣,比如最近的maxAsyncRequests已經是6了,maxInitialRequests為4】如下

module.exports = { //... optimization: { splitChunks: { chunks: 'async', minSize: 30000, maxSize: 0, minChunks: 1, maxAsyncRequests: 5, maxInitialRequests: 3, automaticNameDelimiter: '~', cacheGroups: { vendors: { test: /[\\/]node_modules[\\/]/, priority: -10 }, default: { minChunks: 2, priority: -20, reuseExistingChunk: true } } } } };
優化配置如下,
打包結果如下:chunks中
其中vendors*.js 和 blocksonly~compatibilitytesting~gui~player.js即是默認的vendors和default配置提取出來的bundle。
是小了一點,但是項目還是太大了,特別是vendor.min.js。查看包發現工程引用的第三方模塊中用到很多相同的模塊
解決辦法:使用別名
resolve: { symlinks: false, extensions: ['.js', '.jsx', '.json'], alias: { // 別名,防止node_modules多個地方引入同樣的包會打多份 'scratch-l10n': path.resolve(__dirname, './node_modules/scratch-l10n'), 'scratch-blocks': path.resolve(__dirname, './node_modules/scratch-blocks'), 'scratch-render': path.resolve(__dirname, './node_modules/scratch-render'), 'scratch-svg-renderer': path.resolve(__dirname, './node_modules/scratch-svg-renderer'), 'scratch-audio': path.resolve(__dirname, './node_modules/scratch-audio'), 'immutable': path.resolve(__dirname, './node_modules/immutable') } },
打包后結果
小了3M。到目前為止,如果不更改代碼基本已經無法再壓縮了。
第二階段:更改代碼,一切與展示作品無關的東西都剝離后打包
- 修改maxInitialRequests為4
- 配置了公用vendors,優先提取大模塊(默認情況是不用更改的,本人的項目比較特殊,原因看后面的描述)
- player代碼和gui的代碼分開,去掉player中不必要的代碼引入
這一階段player的代碼進行精簡,不和blocksonly / compatibilitytesting / gui公用一套代碼。項目后面加上了一些hash.
由於項目中需要對scratch-blocks做更改,將scratch-blocks作為項目的git子模塊。
不配置vendors則和默認配置等同,等同於如下代碼
vendors配置如下:
為什么要自定義配置vendors?這和配置maxInitialRequests也有很大關系。
按照webpack默認的default和vendors配置會自動提取公用代碼生成新bundle,其中有兩個參數對這個提取影響比較大:maxAsyncRequests和maxInitialRequests。意思是,webpack會提取各個入口中的相同代碼組成一個個被至少兩個入口使用的bundle,比如下面
chunks/vendors~blocksonly~compatibilitytesting~gui.302b4bd4e64a213f38f4.js 表示是vendors提取出來給blocksonly | compatibilitytesting | gui 這三個入口公用的
chunks/vendors~blocksonly~compatibilitytesting~gui~player.aa28b51afdced7c4a928.js 表示是vendors提取出來給blocksonly | compatibilitytesting | gui | player 這四個入口公用的
如果不限制,則vendors~*.js這樣的組合會很多。一個player.html初始化加載時需要請求很多js。所以maxAsyncRequests和maxInitialRequests限制了這個請求數,讓打包后提取公共代碼js時保證每一個入口異步請求和初始化請求的js數量控制在設定的值。這樣就會出現某些多個入口使用的公用代碼不能被單獨提取出來的情況。
由於默認的初始化js請求數量限制為3,本人更改為4(當然也可以更大,本人這塊不想有跟多js請求,這塊根據各自的項目而定)
然后本人想把一寫體積比較大的(比如scratch-blocks)塊在有maxInitialRequests限制的情況下優先提取出來,其他的就打入各自的包。就直接更改了vendors
沒有配置vendors和配置了vendors的對比(后面的就是配置了vendors)
沒有配置vendors,子模塊scratch-blocks在多處打包,沒有被提取出來。修改vendors,去掉了只匹配“node_moddules”;只要三個以上chunks用到的模塊都提取到vendors。
配置vendors打包后的結果可以看到,在網頁端訪問player頁面,只需要引入三個js
vendor.min.938f6588ea10bb385ceb.js 7844K
vendors~blocksonly~compatibilitytesting~gui~player.b694ee6564cf5e22ed72.js 2998K
player.cb540c9b28aafd8d9503.js 95K
一共就10M多一點,比起之前要加載26M要好很多了。
這里面有一個點需要注意:
按照webpack默認的default和vendors配置會自動提取公用代碼生成新bundle,然后自動被html-webpack-plugin配置的html引用。但是,3.X的html-webpack-plugin如果不指定chunk,且在HtmlWebpackPlugin中顯式配置chunk的名稱,則不起作用。
比如:
上面的vendors~blocksonly~***.js文件生成了,但是沒有被引入到index.html代碼中,導致訪問index.html缺少這個文件直接展示不出來。
3.x的配置例子如下
一旦指定splitChunks.name的名稱,那么所有的入口必然引用這個bundle,無法生成各自入口的個性化bundle。所以建議升級html-webpack-plugin到4
后記:
====================================
2019.12.20 筆記寫的時候是19年6月左右。現在"vendor"的配置名稱改成了“lib.min”,畢竟vendors是第三方包的意思。html-webpack-plugin 4.x也不用再配置中指定“vendor.min”,只需要配置入口chunks就行,插件會自動查找入口chunk被提取出的公共代碼的路徑。
優化要點:
1.代碼壓縮/圖片壓縮
2.公用代碼提取,穩定資源最好提取到單獨的包(比如node_modules中的包)
3.非相關代碼拆分
4.tree shaking的使用。這塊難點比較大,特別是用babel處理的時候。
5. chunkhash和contenthash合理使用
純屬個人經驗,有錯誤請大家多指正!