webpack打包优化实践
事情缘由
近段时间在做基于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合理使用
纯属个人经验,有错误请大家多指正!