详解 webpack 构建优化
当项目越来越复杂时,会面临着构建速度慢和构建出来的文件体积大的问题。webapck 构建优化对于大项目是必须要考虑的一件事,下面我们就从速度和体积两方面来探讨构建优化的策略。
分析工具
在优化之前,我们需要了解一些量化分析的工具,使用它们来帮助我们分析需要优化的点。
webpackbar
webpackbar 可以在打包时实时显示打包进度。配置也很简单,在 plugins 数组中加入即可:
speed-measure-webpack-plugin
使用speed-measure-webpack-plugin
可以看到每个 loader 和 plugin 的耗时情况。
和普通插件的使用略有不同,需要用它的 wrap 方法包裹整个 webpack 配置项。
打包后,在命令行的输出信息如下,我们可以看出哪些 loader 和 plugin 耗时比较久,然后对其进行优化。
webpack-bundle-analyzer
webpack-bundle-analyzer
以可视化的方式让我们直观地看到打包的 bundle 中到底包含哪些模块内容,以及每一个模块的体积大小。我们可以根据这些信息去分析项目结构,调整打包配置,进行优化。
在 plugins 数组中加入该插件。构建完成后,默认会在http://127.0.0.1:8888/
展示分析结果。
webpack-bundle-analyzer
会计算出模块文件在三种情形下的大小:
stat:文件未经过任何转换的原始大小
parsed:文件经过转换后的输出大小(比如 babel-loader 转换 ES6->ES5、UglifyJsPlugin 压缩等等)
gzip:parsed 后的文件,经过 Gzip 压缩的大小使用
speed-measure-webpack-plugin
和webpack-bundle-analyzer
本身也会增加打包时间(webpack-bundle-analyzer
特别耗时),所以建议这两个插件在开发分析时使用,而在生产环境去掉。
优化构建速度
多进程构建
运行在 Node.js 之上的 Webpack 是单线程的,就算有多个任务同时存在,它们也只能一个一个排队执行。当项目比较复杂时,构建就会比较慢。如今大多数 CPU 都是多核的,我们可以借助一些工具,充分释放 CPU 在多核并发方面的优势。
比较常见的有happypack
、thread-loader
。##### 参考 webpack 视频讲解:进入学习
happypack
happypack 能够将构建任务分解给多个子进程去并发执行,子进程处理完后再把结果发送给主进程。使用配置如下,就是把原有的 loader 的配置转移到 happyPack 中去处理。
thread-loader
happypack 的作者已经没有这个项目进行维护了,在 webpack4 之后,可以使用thread-loader
。
thread-loader
使用起来很简单,就是把它放置在其它 loader 之前,如下所示。放置在这个thread-loader
之后的 loaders 会运行在一个单独的 worker 池中。
如果是小项目,不建议开启多进程构建,因为开启进程是需要花费时间的,构建速度反而会变慢。
利用缓存
利用缓存可以提升二次构建速度(下面的对比图都是二次构建的速度)。使用缓存后,在 node_modules 中会有一个.cache
目录,用于存放缓存的内容。
cache-loader
在一些性能开销较大的 loader 之前添加此cache-loader
,以将结果缓存到磁盘中。
可以看出,使用cache-loader
后,构建速度有非常明显的提升。
对babel-loader
使用缓存,也可以不借助cache-loader
,直接在babel-loader
后面加上?cacheDirectory=true
hard-source-webpack-plugin
hard-source-webpack-plugin
用于开启模块的缓存。
使用hard-source-webpack-plugin
后,二次构建速度大概提升了 90%。
include/exclude
通常来说,loader 会处理符合匹配规则的所有文件。比如 babel-loader,会遍历项目中用到的所有 js 文件,对每个文件的代码进行编译转换。而 node_modules 里的 js 文件基本上都是转译好了的,不需要再次处理,所以我们用 include/exclude 来帮我们避免这种不必要的转译。
include 直接指定查找文件夹,比 exclude 效率更高,更能提升构建速度。
动态链接库
上面的 babel-loader 可以通过 include/exclude,避免处理 node_modules 里的第三方库。
但如果将第三方库代码和业务代码都打包进一个 bundle 文件,那么处理这个 bundle 文件的插件,比如 uglifyjs-webpack-plugin、terser-webpack-plugin 等,就没办法不处理里面第三方库内容。
其实第三方库代码基本都是成熟的,不用作什么处理。因此,我们可以将项目的第三方库代码分离出来。
常见的处理方式有三种:
Externals
SplitChunks
DllPlugin
Externals 可以避免处理第三方库,但每一个第三方库都得在 html 文档中增加一个 script 标签来引入,一个页面过多的 js 文件下载会影响网页性能,而且有时我们只使用第三方库中的一小部分功能,用 script 标签全量引入不太合理。
SplitChunks 在每一次构建时都会重新构建第三方库,不能有效提升构建速度。
这里推荐使用 DllPlugin 和 DLLReferencePlugin(配合使用),它们是 webpack 的内置插件。DllPlugin 会将不频繁更新的第三方库单独打包,当这些第三方库版本没有变化时,就不需要重新构建。
使用方法:
使用 DllPlugin 打包第三方库
使用 DLLReferencePlugin 引用 manifest.json,去关联第 1 步中已经打好的包
首先,新建一个 webpack 配置文件
webpack.dll.js
用于打包第三方库(第 1 步)
打包好后,可以看到,在dist
目录下增加了一个 lib 文件夹。
然后,我们在
webpack.base.js
做一下修改,去关联第 1 步中已经打好的包(第 2 步)
再次打包后可以看到,相比于一开始整个项目的体积 9.11MB,体积减小了 90%,因为这是一个多页面打包(多页面打包配置)的应用,每个页面都引用了体积庞大的three.js核心文件
,我们把体积最大的three.js核心文件
从每个页面的 bundle 中抽离出来后,bundle 的体积大大减小。
再来看看构建时间:相比于使用 DllPlugin 之前,时间减少了 30% 。
不仅仅是第三方库,业务代码中的基础库也可以通过进行 DllPlugin 分离。
优化构建体积
代码分割
分离第三方库和业务代码中的基础库,可以避免单个 bundle.js 体积过大,加载时间过长。并且在多页面构建中,还能减少重复打包。
常见的操作是通过 SplitChunks(之前的文章已经详细地写过了:SplitChunks)和 动态链接库(如上所示),这里都不再赘述。
动态 import
动态 import 的作用主要减少首屏资源的体积,非首屏的资源在用到的时候再去请求,从而提高首屏的加载速度。一个常见的例子就是单页面应用的路由管理(比如vue-router
)
不是直接 import 组件(import List from '../views/List.vue'
),那样会把组件都打包进同一个 bundle。而是动态 import 组件,凡是通过import()
引用的模块都会打包到独立的 bundle,使用到的时候再去加载。对于功能复杂,又不是首屏必须的资源都推荐使用动态 import。
treeShaking
使用 ES6 的 import/export 语法,并且使用下面的方式导入导出你的代码,而不要使用 export default。
那么在mode:production
生产环境,就会自动开启tree-shaking
,移除没有使用到的代码,上面例子中的afunc函数
就不会被打包到 bundle 中。
代码压缩
常用的 js 代码压缩插件有:uglifyjs-webpack-plugin
和 terser-webpack-plugin
。
在 webpack4 中,生产环境默认开启代码压缩。我们也可以自己配置去覆盖默认配置,来完成更定制化的需求。
v4.26.0 版本之前,webpack 内置的压缩插件是 uglifyjs-webpack-plugin,从 v4.26.0 版本开始,换成了terser-webpack-plugin
。我们这里也以terser-webpack-plugin
为例,和普通插件使用不同,在optimization.minimizer
中配置压缩插件
雪碧图
雪碧图将多张小图标拼接成一张大图,在 HTTP1.x 环境下,雪碧图可以减少 HTTP 请求,加速网页的显示速度。
用于合成雪碧图的图标体积要小,较大的图片不建议拼接成雪碧图;同时要是网站静态图标,不是通过 ajax 请求动态获取的图标。所以通常是作为网站 logo、icon 之类的图片。
开发时,可以是 UI 提供雪碧图,但是每新增一个图标,就要重新制作一次,重新计算偏移量,比较麻烦。通过 webpack 插件合成雪碧图,就可以在开发时直接使用单个小图标,在打包时,自动合成雪碧图,并自动自动修改 css 中的background-position
的值。
下面,我们借助postcss-sprites
来自动合成雪碧图。
首先,在webpack.base.js
中配置postcss-loader
:
然后在项目根目录下新建.postcssrc.js
,配置postcss-sprites
。
默认会把图片合并到名为sprite.png
的雪碧图中。
在 css 中直接指定小图标当背景:
打包完成后可以看到,自动修改了background-image
和background-position
。
gzip
开启 gzip 压缩,可以减小文件体积。在浏览器支持 gzip 的情况下,可以加快资源加载速度。服务端和客户端都可以完成 gzip 压缩,服务端响应请求时压缩,客户端应用构建时压缩。但压缩文件这个过程本身是需要耗费时间和 CPU 资源的,如果存在大量的压缩需求,会加大服务器的负担。
所以可以在构建打包时候就生成 gzip 压缩文件,作为静态资源放在服务器上,接收到请求后直接把压缩文件返回。
使用 webpack 生成 gzip 文件需要借助compression-webpack-plugin
,使用配置如下:
打包完成后除了生成打包文件外,还会额外生成 .gz 后缀的压缩文件。可以看出,gzip 压缩文件的体积比未压缩文件的体积小很多。
评论