为什么要代码分割?
- 减少代码重复,比如在多页面的应用中,如果多个页面都同时引用了某些module的情况;
- 支持缓存,比如第三方库通常是不会怎么变的,将他们单独抽离出来有利于浏览器缓存。
什么情况下代码分割?
- 入口文件,每个入口文件将有单独的一次代码分割
- 使用SplitChunksPlugin插件;
- 异步加载,比如使用import()。
做代码分割时,通常会对以下情况进行处理:
- 为第三方依赖库(vendor)单独打包
- 为webpack自己的runtime代码(manifest)单独打包
- 为公共业务代码单独打包
webpack4默认分割配置
Webpack4放弃了CommonsChunkPlugin,使用SplitChunksPlugin,并通过内置的optimization配置段进行配置。详情在 官方文档,重点的几项:
- 默认只对两种情况进行分割:一是异步加载的module,二是被其他chunk引用次数大于等于2的module。
- 默认生产chunk最小为30k
- 默认有两个cacheGroup,一个vendors用于处理第三方依赖库;一个是default(处理当module被引用>=2的情况)
由于一个module有可能同属于多个cacheGroup,因此可以通过设置某个cacheGroup的优先级(priority)来解决,priority值越大,表示优先级越高,也即会优先其作用。
Webpack的两个默认cacheGroup的优先级都被设置成了负数,而我们自定义的cacheGroup的默认priority为0,因此可以初步保证自定义的cacheGroup总会优先于默认的起作用。
单入口代码分割
webpack默认配置下的代码分割
引入webpack-bundle-analyzer插件,打包文件分析工具
src文件夹下建立以下几个文件:
- index.js依赖于A-module,axios库以及MathJS库,异步依赖于lodash库(async-lodash)和underscore库(async-underscore),B-module(async-b)和C-module(async-c)
- B-module和C-module都依赖于D-module
- C-module依赖于E-module
执行 yarn build:
1 | dist/ |
- B-module和C-module由于是index.js异步引入的,因此分别为其创建了一个输出文件
- 对于异步引入的lodash和underscore而言,由于来自于第三方库,webpack对第三方库有默认的配置(配置有名为vendors的cacheGroup),因此webpakc会为每个第三方库单独默认生成对应的异步加载文件
- main.d524a562ae289f073b19.js中包含了所有非异步加载的模块,包含了第三方库axios和MathJS,以及A-module。
定制代码分割
默认配置下生成的bundle存在缺点:
- webpack本身的runtime代码没有分离出来
- main.d524a562ae289f073b19.js既包含了第三方库,又包含了我们自己的代码
- B-module和C-module同时依赖了D-module,但是D-module在B-module和C-module中重复存在
- webpack默认通过数字编号给module起名,如果module发生增减,会导致包含第三方库在内的bundle都会发生改变
定制代码分割要针对以上问题作出处理:
- 问题1:通过webpack自带的runtimeChunk配置解决runtime代码分离问题:
1 | ... |
webpack会为runtime代码单独生成名为manifest-*.js的文件。
- 问题2:需要将所有的第三方库从main.js中抽离出来。webpack默认情况下只会对异步加载的第三方库进行分割,此时我们需要修改一下配置:
1 | spliteChunks: { |
将chunks值改为all(先前的默认值为async),表示对所有的第三方库进行代码分割(包括async和initial)。此时生产的代码中多了一个vendors~main.*.js文件。
- 问题3:要新加一个cacheGroup,该cacheGroup对于被引用
此时大于等于2的module进行分割:
1 | cacheGroups: { |
webpack官网其实并不建议设置name属性,原文如下:
When assigning equal names to different split chunks, all vendor modules are placed into a single shared chunk, though it’s not recommend since it can result in more code downloaded.
参数说明:
参数 | 说明 |
---|---|
minChunks: 2 | 表示被引用次数大于等于2的module符合该cacheGroup的条件 |
name:’commons’ | 表示将所有符合条件的module都放到同一个名为commons的文件中,如果不配置此项,webapck会默认根据module被引用情况生成多个bundle文件 |
chunks: ‘async’ | 表示通过异步加载的module符合条件,B-module和C-module虽然本身是通过async引入的,但是他们对D-module的引用是通过initial的方式引入的,因此按理应该设置成chunks: ‘initial‘才对,然而事实上这里必须设置成async,D-module才会独立出来。 |
priority: 10 | 表示优先处理,因为webpack默认的两个cacheGroup的优先级为负数。 |
- module发生增减,会导致包含第三方库在内的bundle都会发生改变
问题,加入HashedModuleIdsPlugin插件即可解决:
1 | ... |
单入口项目建议配置
1 | const path = require('path'); |
运行 yarn build 输出如下:
1 | dist/ |
多入口代码分割
webpack默认配置下的代码分割
src文件夹下建立以下几个文件:
- 3个html入口文件,index1.html,index2.html,index3.html
- 每个入口文件对应一个js入口文件,index1.js,index2.js,index3.js
- index1.js依赖A-module、F-module、G-module、axios、lodash,异步加载B-module、C-module
- index2.js依赖A-module、G-module、H-module、axios、jquery,异步加载lodash、B-module
- index3.js依赖A-module、F-module、H-module、lodash、jquery,异步加载C-module
- B-module依赖于D-module
- C-module依赖于D-module、E-module
更改entry和HtmlWebpackPlugin配置,如下:
1 | ... |
运行 yarn build:
1 | dist/ |
webpack的默认配置:
- 只对异步加载的moudule进行分割处理
- 生成的分割文件要大于30k
- 对异步加载的第三方库(node_modules目录下的库)进行处理
- 对引用大于等于2的module进行分割
由上面的打包结果可以看出,在默认情况下,webpack只对异步加载的库做了分割处理(生成了async-b.3e6a567c8ad867591384.js、async-c.faa08dc1c722e8898326.js和vendors~async-lodash.d810246d214be5df29ec.js),而其他所有的module都打包到了对应的index.*.js文件中。
改进默认配置
可以对配置稍作修改,即将默认的两个cacheGroup的chunks改为all,表示同时对静态加载(initial)和动态加载(async)起作用,以及设置生产的chunk不受最大文件大小限制。
1 | ... |
运行 yarn build 输出:
1 | dist/ |
- async-b~async-c.6d7cd2350dd7e9f02e59.js文件是新产生的,由于B-module和C-module同时依赖于D-module产生
- vendors~async-lodash~index1~index3.6d7cd2350dd7e9f02e59.js含了lodash库,是由于index1.js、index3.js静态依赖了lodash,同时index2.js中对lodash有异步依赖;
- vendors~index1~index2.6d7cd2350dd7e9f02e59.js包含了axios库,是由于index1和index2同时引用了axios;
- vendors~index2~index3.6d7cd2350dd7e9f02e59.js包含了jquery库,是由于index2和index3同时引用了jquery。
此时仍然存在以下几个问题:
- 通过HtmlWebpackPlugin生成的三个index.html文件引用了所有的静态依赖,没有达到分离页面的目的;
- 三个index.js文件共享或者部分共享了A-module、F-module、G-module和H-module,但是共享模块还是重复出现在了生成的index.js文件中。
想要达到的目的:
- 提取webpack的runtime代码到单独的文件
- 所有静态依赖第三方库被分割到同一个文件中
- 所有动态依赖的第三方库分别分割到单独的文件,这样才能享受异步加载的好处,即只加载所需要的
- 所有动态依赖的自研模块分别分割到单独的文件
- 被多次引用的自研发模块统一放到一个文件中,便于多个入口共享
- 配置缓存
定制代码分割
先去除掉默认的2个cacheGroup:
1 | optimization: { |
运行 yarn build,输出:
1 | dist/ |
- 提取webpack的runtime代码
1 | ... |
2.将所有静态的第三方依赖放到同一个文件中,加入新的cacheGroup:
1 | ... |
yarn build 输出结果:
1 | dist/ |
多了一个vendor.68aff42cd15644abcd53.js,包含所有共享的三方依赖(jquey,lodash和axios)
如果去掉 name: ‘vendor’,得到的输出为:
1 | dist/ |
此时webpack会根据实际模块的共享关系,分开生成精确的chunk文件,比如如果A和B同时依赖于C,那么webpack会专门为A和B生成对应的chunk文件。
对比发现配置name属性后,所有的依赖都在一个池子里,好处是指定了名字在稍后配置HtmlWebpackPlugin时好操作一下,缺点是如果一个chunk只依赖于池子中的某一小部分,那么也需要引用整个池子。webpack官网推荐不要配置name:’vendor’,但是在多页面的场景下,配置了name:’vendor’会更方便一下。
3.将所有动态依赖的第三方库放到各自单独的文件中,这是webpack的默认行为。
4.所有动态依赖的自研发模块分别分割到单独的文件,这也是webpack的默认行为。
5.将所有依赖的自研发模块放到同一个文件中,这里我们配置只有被依赖2次或者以上的模块才被放到common中,并且优先级低于vendor,配置如下:
1 | ... |
yarn build打包结果多了一个common.6d1f3c64bb3f7cea425f.js文件,其中包含了A-module,F-module,G-moduel,H-module和D-module;而对于E-module,由于只被C-module依赖了一次,因此直接消化在了C-module对应的输出文件中。
6.配置缓存需要用到HashedModuleIdsPlugin
1 | ... |
看下此时输出的3个index.html文件引入的js:
index1.html:
1 | <script type="text/javascript" src="manifest.6d1f3c64bb3f7cea425f.js"></script> |
index2.html:
1 | <script type="text/javascript" src="manifest.6d1f3c64bb3f7cea425f.js"></script> |
index3.html:
1 | <script type="text/javascript" src="manifest.6d1f3c64bb3f7cea425f.js"></script> |
可以看到所有index.html文件中都包含了所有的依赖文件,这当然不是我们想要的结果,因此修改HtmlWebpackPlugin插件:
1 | new HtmlWebpackPlugin({ |
HtmlWebpackPlugin依赖于chunk的名字,这也是为什么前文提到需要为cacheGroup设置name字段的原因,不然webpack会自动为我们生成很多动态的名字,这样无法配置HtmlWebpackPlugin。
多入口项目建议配置
1 | const path = require('path'); |