vue-cli 3.0中webpack打包优化

一直以来想写一篇关于 Webpack 优化的博客,最近正好做了vue-cli 3.0中关于webpack 打包的优化正好来记录一下。

最近在整理季度的优化代码,然后发现了一个项目中集成了原来 4 - 5个管理后台的大型项目在 webpack 优化方面做的很一般。

未优化打包js

未优化打包css

由上面的图可以看出来不仅 vendors 打包体积到了 1300+ kb 而且 多个 chunk 体积在Gzip 之后也大于240kb 根据webpack 的建议 大于 240 kb的文件是要进行拆分的。

还可以看出来在项目中不仅js 打包单个文件的体积过大。而且存在 为抽取公共部分组件、css / js 打包之后文件过多的问题。

打包了很多小的css文件不仅不利于网页的加载,大量的时间耗费在 http 请求上。而且相互依赖的 css 会造成网页在解析的过程中不断的触发重绘,这将对性能造成极大的影响。

那么明确了项目中存在的问题之后我们来着手解决。

首先在开始前我们需要足够的了解项目的webpack默认配置

在vue-cli3.0的项目中使用 vue inspect > output.js 来输出一份当前webpack的配置文件。

这个命令是我们优化webpack配置项的基础。

然后我们需要借助 webpack-bundle-analyzer 来看下项目打包构建的过程中引用了哪些包和具体的文件size。

1
2
3
4
5
chainWebpack: (config) => {
config
.plugin('webpack-bundle-analyzer')
.use(require('webpack-bundle-analyzer').BundleAnalyzerPlugin)
}

然后在线项目中直接 npm run serve || npm run build 即可打开一份当前项目引用资源分析的页面。

基础配置

另外在此要讲一下的是,vue为了降低入门难度在vuecli3.0之后的版本中 vue对webpack的配置文件都默认隐藏了,因为大部分的功能都默认成开箱即用。

vue对webpack的配置的封装可以 参考 ,文档中出现的具体的项都可以直接配置,单如果需要更灵活的使用webpack的配置,就需要修改 configureWebpack 或者使用 chainWebpack 做修改(chainWebpack 加大了学习成本)

由于vuecli 默认配置的webpack中是打开了 SourceMap 的,而服务端的更新通常都是增量更新,前端每次在打包的时候输出的SourceMap 文件会造成服务端文件冗余。并且在开启 SourceMap 之后build 速度会慢下来很多。

由于vue自身优化了配置项,SourceMap 就处于其中,所以直接在配置中设置即可关闭。

1
2
3
module.exports = {
productionSourceMap: false,
}

打包优化

在有了项目分析和配置项文件之后就可以着手解决打包优化的问题了。首先是JS单个文件 size 过大,因为在vue-cli3.0 中使用的是 webapck: 4.26.1 所以 CommonChunkSplit 已经被删掉了,取代的是 splitChunks

然后我们需要先抽取代码中的重复引用的部分公共代码,那么首先我们来看文档中对文件 size 的优化的配置 minSize 代表单个文件最大的size。

1
2
3
4
5
6
7
cacheGroups: {
common: {
name: "chunk-common", // 抽取公共文件的名字
minChunks: 2, // 最小被引用多少次则被抽取
minSize: 30000 // 单个文件最小size
}
}

另外在项目中有很多的代码体积来自 console,这些只在开发中使用的代码不应该发布到线上,所以直接使用 UglifyJsPlugin 删除console 文件体积减少24kb+

1
2
3
4
5
6
7
8
9
10
new UglifyJsPlugin({
uglifyOptions: {
compress: {
warnings: false,
drop_console: true,
pure_funcs: ['console.log']
}
},
sourceMap: false
})

构建优化

构建优化上我们使用了 happypack 来利用多核CPU 加快打包的速度。

最终配置的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
configureWebpack: config => {
config.optimization = {
splitChunks: {
minSize: 1000000, // 单个文件的最小size
maxSize: 2000000, // 单个文件最大的size
minChunks: 2, // 最小被引用
maxAsyncRequests: 5, // 首页加载资源
maxInitialRequests: 3,
automaticNameDelimiter: '~', // 打包文件自定义的链接符
name: true,
chunks: 'async', // initial(初始块)、async(按需加载块)、all(默认,全部块)
// 这里需要注意的是如果使用initial 会将首页需要的依赖和项目本身的依赖打包2次增大文件体积
cacheGroups: {
default: false,
vendor: {
test(module) {
let path = module.resource
if (!path) return true
path = path.replace(/\\/g, '/')
let isNeed = path &&
/node_modules/.test(path) &&
/node_modules\/(?!vuetify)/.test(path) &&
/node_modules\/(?!muse)\n*/.test(path)
if (!isNeed && path.indexOf('node_modules') > -1) {
console.log('vendor not need::', path, isNeed)
}
return isNeed
},
name: "chunk-vendors",
priority: 10,
enforce: true
},
vue: {
test(module) {
let path = module.resource
if (!path) return false
path = path.replace(/\\/g, '/')
// return path && path.indexOf('node_modules') > -1 && path.indexOf('vuetify') > -1
return path && /node_modules\/vue/.test(path)
},
name: "chunk-vuetify",
priority: 9,
enforce: true
},
echarts: {
// test: (/node_modules/ && /muse\n*/),
test(module) {
let path = module.resource
if (!path) return false
path = path.replace(/\\/g, '/')
return path && /node_modules\/echarts\n*/.test(path)
},
name: "chunk-echarts",
priority: 8,
enforce: true
},
common: {
name: "chunk-common",
minChunks: 2,
minSize: 30000
}
}
}
}
},
hi you can see me