开发多页应用的时候,如果不对webpack打包进行优化,当某个模块被多个入口模块引用时,它就会被打包多次(在最终打包出来的某几个文件里,它们都会有一份相同的代码)。当项目业务越来越复杂,打包出来的代码会非常冗余,文件体积会非常庞大。大体积文件会增加编译时间,影响开发效率;如果直接上线,还会拉长请求和加载时长,影响网站体验。作为一个追求极致体验的攻城狮,是不能忍的。所以在多页应用中优化打包尤为必要。那么如何优化webpack打包呢?
一、概念
在一切开始前,有必要先理清一下这三个概念:
module: 模块,在webpack眼里,任何可以被导入导出的文件都是一个模块。
chunk: chunk是webpack拆分出来的:
每个入口文件都是一个chunk
通过 import、require 引入的代码也是
通过 splitChunks 拆分出来的代码也是
bundle: webpack打包出来的文件,也可以理解为就是对chunk编译压缩打包等处理后的产出。
二、问题分析
首先,简单分析下,我们刚才提到的打包问题:
核心问题就是:多页应用打包后代码冗余,文件体积大。
究其原因就是:相同模块在不同入口之间没有得到复用,bundle之间比较独立。
弄明白了问题的原因,那么大致的解决思路也就出来了:
我们在打包的时候,应该把不同入口之间,共同引用的模块,抽离出来,放到一个公共模块中。这样不管这个模块被多少个入口引用,都只会在最终打包结果中出现一次。——解决代码冗余。
另外,当我们把这些共同引用的模块都堆在一个模块中,这个文件可能异常巨大,也是不利于网络请求和页面加载的。所以我们需要把这个公共模块再按照一定规则进一步拆分成几个模块文件。——减小文件体积。
至于如何拆分,方式因人而异,因项目而异。我个人的拆分原则是:
业务代码和第三方库分离打包,实现代码分割;
业务代码中的公共业务模块提取打包到一个模块;
第三方库最好也不要全部打包到一个文件中,因为第三方库加起来通常会很大,我会把一些特别大的库分别独立打包,剩下的加起来如果还很大,就把它按照一定大小切割成若干模块。
optimization.splitChunks
webpack提供了一个非常好的内置插件帮我们实现这一需求:CommonsChunkPlugin。不过在 webpack4 中CommonsChunkPlugin被删除,取而代之的是optimization.splitChunks, 所幸的是optimization.splitChunks更强大!
三、 实现
通过一个多页应用的小demo,我们一步一步来实现上述思路的配置。
demo目录结构:
|--public/
| |--a.html
| |--index.html
|--src/
| |--a.js
| |--b.js
| |--c.js
| |--index.js
|--package.json
|--webpack.config.js
代码逻辑很简单,index模块中引用了 a 和 b 2个模块,a 模块中引用了 c 模块和 jquery库,b 模块中也引用了 c 模块和 jquery 库,c 是一个独立的模块没有其他依赖。
index.js代码如下:
//index.js import a from './a.js'; import b from './b.js'; function fn() { console.log('index-------'); } fn();
a.js代码如下:
//a.js require('./c.js'); const $ = require('jquery') function fn() { console.log('a-------'); } module.exports = fn();
b.js代码如下:
//b.js require('./c.js'); const $ = require('jquery') function fn() { console.log('b-------'); } module.exports = fn();
c.js代码如下:
//c.js function fn() { console.log('c-------'); } module.exports = fn();
1. 基本配置
webpack先不做优化,只做基本配置,看看效果。项目配置了2个入口,搭配html-webpack-plugin实现多页打包:
const path = require('path'); const HtmlWebpackPlugin = require('html-webpack-plugin'); module.exports = { entry: { index: './src/index.js', a: './src/a.js' }, output: { path: path.resolve(__dirname, 'dist'), filename: '[name].js' }, plugins: [ new HtmlWebpackPlugin({ template: './public/index.html', filename: 'index.html' }), new HtmlWebpackPlugin({ template: './public/a.html', filename: 'a.html' }) ] }
在开发模式下运行webpack:
可以看到,打包出两个html和两个体积很大的(300多K)的文件a.js,index.js。
进入dist目录检查js文件:
a.js里包含c模块代码和jquery代码
index.js里包含a模块、b模块、c模块和jquery代码
看,同样的代码c和jquery被打包了2遍。
2. 初步添加splitChunks优化配置
首先解决相同代码打包2次的问题,我们需要让webpack把c和jquery提取出来打包为公共模块。
在webpack配置文件添加splitChunks:
//webpack.config.js optimization: { splitChunks: { cacheGroups: { default: { name: 'common', chunks: 'initial' } } } }
- cacheGroups
cacheGroups是splitChunks配置的核心,对代码的拆分规则全在cacheGroups缓存组里配置。
缓存组的每一个属性都是一个配置规则,我这里给他的default属性进行了配置,属性名可以不叫default可以自己定。
属性的值是一个对象,里面放的我们对一个代码拆分规则的描述。
- name
name:提取出来的公共模块将会以这个来命名,可以不配置,如果不配置,就会生成默认的文件名,大致格式是index~a.js这样的。
- chunks