Webpack 4在2018年2月正式发布了,这是一个重大的版本更新,带来了很多激动人心的新特性和改进。作为一个重度依赖Webpack的前端团队,我们在Webpack 4发布后,第一时间就对项目进行了从Webpack 3到Webpack 4的升级和重构。
整个重构过程持续了大概一周的时间,遇到了不少问题,也踩了一些坑,但是最终的结果非常令人满意:构建速度提升了60%以上,打包体积减小了30%左右,配置也更加简洁清晰。
本文将详细记录我们的重构过程,包括升级前的准备、遇到的问题和坑、配置的变化、性能的提升,以及一些经验和教训,希望能给准备升级Webpack 4的团队一些参考。
一、为什么要升级到Webpack 4
在升级之前,我们先评估了一下Webpack 4带来的新特性和改进,看看是否值得升级。评估下来,Webpack 4的几个特性确实非常吸引人,值得我们花时间去升级。
特性一:零配置(Zero Config)
Webpack 4最大的一个卖点就是零配置。在Webpack 3及之前的版本中,即使是最简单的项目,也需要写一个webpack.config.js,配置入口、输出、loader等。而Webpack 4默认不需要配置文件,它会默认把./src/index.js作为入口,输出到./dist/main.js,并且内置了很多默认配置。
零配置对于小项目和快速原型开发非常友好,可以让开发者专注于代码,而不是构建配置。虽然我们的项目比较复杂,最终还是需要配置文件,但是零配置的理念也影响了我们的配置方式,让我们的配置更加简洁。
特性二:mode模式
Webpack 4引入了mode选项,有三个可选值:development、production、none。不同的mode会自动启用不同的内置优化插件和默认配置。
- development模式:会启用NamedModulesPlugin、NamedChunksPlugin等,方便调试,构建速度快,但是不压缩代码。
- production模式:会启用UglifyJsPlugin、ModuleConcatenationPlugin、NoEmitOnErrorsPlugin等,自动压缩代码、优化体积,但是构建速度慢一些。
- none模式:不启用任何默认优化,完全自己配置。
在Webpack 3中,我们需要手动根据环境变量来判断是否启用压缩插件、是否启用source map等,配置比较繁琐。有了mode之后,只需要设置一个mode选项,Webpack就会自动帮我们处理好这些,配置简洁了很多。
特性三:更快的构建速度
Webpack 4对构建性能进行了大量优化,官方宣称构建速度比Webpack 3快了98%(这个数字可能有点夸张,但是实际体验确实快了很多)。
性能提升主要来自几个方面:
- 更好的缓存机制,增量构建更快。
- 优化了模块解析和依赖收集的算法。
- 并行处理,利用多核CPU的能力。
- 更好的Tree Shaking,减少了需要处理的代码量。
对于我们这种大型项目来说,构建速度的提升非常重要。以前用Webpack 3,开发环境热更新要等好几秒,生产环境构建要好几分钟。升级到Webpack 4之后,热更新几乎是秒级的,生产环境构建时间也缩短了一半以上,开发体验提升非常明显。
特性四:SplitChunksPlugin替代CommonsChunkPlugin
在Webpack 3中,提取公共代码用的是CommonsChunkPlugin。CommonsChunkPlugin虽然功能强大,但是配置比较复杂,而且有一些局限性,比如不能很好地处理异步chunk的公共代码提取。
Webpack 4用SplitChunksPlugin替代了CommonsChunkPlugin。SplitChunksPlugin功能更强大,配置更灵活,可以自动识别和提取公共代码,包括同步和异步chunk的公共代码。而且SplitChunksPlugin有一套智能的默认配置,大多数情况下不需要手动配置,就能得到比较好的代码分割效果。
特性五:更好的Tree Shaking
Tree Shaking是Webpack 2引入的功能,可以移除代码中没有用到的死代码,减小打包体积。但是在Webpack 2和3中,Tree Shaking的效果并不理想,很多情况下不能正确地识别和移除死代码。
Webpack 4对Tree Shaking进行了改进,引入了"sideEffects"字段,可以在package.json中标记哪些文件是有副作用的,帮助Webpack更准确地进行Tree Shaking。而且Webpack 4的生产模式会自动启用更激进的Tree Shaking,移除更多的死代码。
对于我们这种使用了大量第三方库的项目来说,Tree Shaking的改进可以显著减小打包体积。
特性六:其他改进
除了上面这些,Webpack 4还有很多其他的改进:
- 支持WebAssembly,可以直接导入.wasm文件。
- 改进了source map的生成,更快更准确。
- 更好的长缓存支持,contenthash更加稳定。
- 改进了错误提示,更加友好和清晰。
- 支持多种模块格式(ESM、CommonJS、AMD等)的混合使用。
综合来看,Webpack 4的改进非常全面,无论是开发体验还是构建性能都有很大提升,值得我们花时间去升级。
二、升级前的准备
在正式升级之前,我们做了一些准备工作,确保升级过程顺利。
准备一:了解Webpack 4的breaking changes
Webpack 4是一个重大版本更新,有很多breaking changes。我们先仔细阅读了Webpack 4的发布说明和迁移指南,了解了哪些API变了、哪些插件被废弃了、配置方式有什么变化。
主要的breaking changes包括:
- 必须设置mode选项,否则会有警告。
- CommonsChunkPlugin被移除,改用SplitChunksPlugin。
- NoEmitOnErrorsPlugin被移除,改用optimization.noEmitOnErrors。
- ModuleConcatenationPlugin被移除,改用optimization.concatenateModules。
- NamedModulesPlugin和NamedChunksPlugin在development模式下自动启用。
- UglifyJsPlugin不需要手动安装和配置,production模式下自动启用。
- json-loader不再需要,Webpack 4内置了对JSON的支持。
- 一些loader和插件需要升级到支持Webpack 4的版本。
了解了这些breaking changes之后,我们心里就有底了,知道哪些地方需要改。
准备二:检查依赖的兼容性
Webpack 4的API有变化,很多loader和插件都需要升级到支持Webpack 4的版本。我们检查了项目中所有的loader和插件,确认它们是否有支持Webpack 4的版本。
大部分常用的loader和插件都已经更新支持Webpack 4了,比如babel-loader、css-loader、style-loader、file-loader、url-loader、html-webpack-plugin、extract-text-webpack-plugin等。但是也有一些小众的插件还没有更新,我们需要找替代方案或者自己修改。
对于那些还没有支持Webpack 4的插件,我们提前找好了替代方案,确保升级过程中不会因为插件不兼容而卡住。
准备三:备份现有配置和代码
升级有风险,操作需谨慎。在正式升级之前,我们把现有的webpack配置文件和项目代码都备份了一下(其实就是在Git上新建了一个分支),确保升级失败可以随时回退。
我们新建了一个feature/webpack4-upgrade分支,所有的升级工作都在这个分支上进行,等升级完成、测试通过之后再合并到主分支。这样即使升级过程中出了问题,也不会影响主分支的正常开发。
准备四:制定升级计划
我们制定了一个详细的升级计划,把升级过程分成几个步骤:
- 升级Webpack和相关依赖到Webpack 4版本。
- 修改webpack配置,适配Webpack 4的新API。
- 替换废弃的插件,使用新的内置优化。
- 测试开发环境构建,确保热更新和调试正常。
- 测试生产环境构建,确保打包结果正确。
- 性能测试,对比Webpack 3和Webpack 4的构建速度和打包体积。
- 修复发现的问题和bug。
- 文档更新和团队培训。
有了详细的计划,升级过程就有条不紊了。
三、升级过程中遇到的问题和坑
正式开始升级之后,我们遇到了不少问题,也踩了一些坑。这里记录一下主要的问题和解决方案。
问题一:mode选项必须设置
升级到Webpack 4之后,第一次运行构建,就看到了一个警告:
WARNING in configuration
The 'mode' option has not been set, webpack will fallback to 'production' for this value. Set 'mode' option to 'development' or 'production' to enable defaults for each environment.Webpack 4要求必须设置mode选项,否则会有警告,并且默认使用production模式。我们在开发环境构建的时候,因为没有设置mode,默认用了production模式,结果代码被压缩了,而且没有source map,根本没法调试。
解决方案很简单,在配置中设置mode选项。我们根据环境变量来设置不同的mode:
module.exports = {
mode: process.env.NODE_ENV === 'production' ? 'production' : 'development',
// ...
}设置了mode之后,Webpack会自动根据mode启用相应的优化和默认配置,开发环境有source map、不压缩代码,生产环境自动压缩代码、优化体积,非常方便。
问题二:CommonsChunkPlugin被移除
我们在Webpack 3中用了CommonsChunkPlugin来提取公共代码,把第三方库(vue、vue-router、vuex、axios等)和业务代码分开打包,优化缓存。升级到Webpack 4之后,CommonsChunkPlugin被移除了,运行构建直接报错:
Error: CommonsChunkPlugin was removed解决方案是使用Webpack 4内置的SplitChunksPlugin。SplitChunksPlugin不需要手动引入,只需要在optimization.splitChunks中配置即可。
我们的配置大概是这样的:
optimization: {
splitChunks: {
chunks: 'all',
cacheGroups: {
vendor: {
name: 'vendor',
test: /[\\/]node_modules[\\/]/,
priority: 10,
chunks: 'all'
},
common: {
name: 'common',
minChunks: 2,
priority: 5,
chunks: 'all',
reuseExistingChunk: true
}
}
},
runtimeChunk: {
name: 'manifest'
}
}这个配置把node_modules中的第三方库打包到vendor.js,把被引用两次以上的公共代码打包到common.js,把运行时代码打包到manifest.js。效果比之前用CommonsChunkPlugin更好,而且配置更灵活。
SplitChunksPlugin还有一个好处,就是它可以处理异步chunk的公共代码提取。在Webpack 3中,CommonsChunkPlugin对异步chunk的支持不好,而SplitChunksPlugin可以很好地处理异步加载的模块的公共代码提取,进一步优化了打包体积。
问题三:extract-text-webpack-plugin不兼容
我们在Webpack 3中用extract-text-webpack-plugin来提取CSS,把CSS从JS中分离出来,单独生成CSS文件。升级到Webpack 4之后,extract-text-webpack-plugin的稳定版不兼容Webpack 4,运行报错。
当时extract-text-webpack-plugin还没有发布支持Webpack 4的稳定版,只有beta版。我们试了beta版,虽然能用,但是有一些bug,而且不稳定。
后来我们发现,Webpack 4官方推荐使用mini-css-extract-plugin来替代extract-text-webpack-plugin。mini-css-extract-plugin是专门为Webpack 4设计的,支持CSS的按需加载和source map,配置也更简单。
我们把extract-text-webpack-plugin替换成了mini-css-extract-plugin,配置如下:
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
module.exports = {
// ...
module: {
rules: [
{
test: /\.css$/,
use: [
MiniCssExtractPlugin.loader,
'css-loader',
'postcss-loader'
]
},
{
test: /\.scss$/,
use: [
MiniCssExtractPlugin.loader,
'css-loader',
'postcss-loader',
'sass-loader'
]
}
]
},
plugins: [
new MiniCssExtractPlugin({
filename: '[name].[contenthash].css',
chunkFilename: '[id].[contenthash].css'
})
]
}替换之后,CSS提取功能正常,而且mini-css-extract-plugin支持CSS的按需加载(异步chunk的CSS会单独打包),比extract-text-webpack-plugin更灵活。
问题四:UglifyJsPlugin配置变化
在Webpack 3中,我们需要手动安装uglifyjs-webpack-plugin,然后在plugins中配置,用来压缩JS代码。升级到Webpack 4之后,production模式下会自动启用UglifyJsPlugin,不需要手动配置了。
但是我们之前对UglifyJsPlugin有一些自定义配置,比如移除console、移除debugger、并行压缩等。在Webpack 4中,如果需要自定义压缩配置,可以在optimization.minimizer中配置。
我们的配置如下:
const UglifyJsPlugin = require('uglifyjs-webpack-plugin')
module.exports = {
// ...
optimization: {
minimizer: [
new UglifyJsPlugin({
parallel: true,
sourceMap: true,
uglifyOptions: {
compress: {
drop_console: true,
drop_debugger: true
}
}
})
]
}
}需要注意的是,如果配置了optimization.minimizer,就会覆盖Webpack的默认压缩配置,需要自己确保UglifyJsPlugin正确配置。如果不需要自定义压缩配置,就不要设置optimization.minimizer,让Webpack用默认配置就行。
另外,Webpack 4.16+之后,官方推荐使用terser-webpack-plugin替代uglifyjs-webpack-plugin,因为terser支持ES6+的语法压缩,而uglify不支持。不过在我们升级的时候,terser-webpack-plugin还不太成熟,所以还是用了uglifyjs-webpack-plugin。
问题五:json-loader不再需要
在Webpack 3中,导入JSON文件需要配置json-loader。升级到Webpack 4之后,内置了对JSON的支持,不需要json-loader了。如果配置中还有json-loader,会有警告或者报错。
我们把配置中的json-loader移除了,直接导入JSON文件就能正常工作,非常方便。
问题六:NamedModulesPlugin和NamedChunksPlugin不需要手动配置
在Webpack 3中,为了开发环境调试方便,我们会手动启用NamedModulesPlugin和NamedChunksPlugin,让模块和chunk有可读的名称,而不是数字ID。升级到Webpack 4之后,development模式下会自动启用这两个插件,不需要手动配置了。
我们把配置中的NamedModulesPlugin和NamedChunksPlugin移除了,开发环境下模块名称依然是可读的,调试很方便。
问题七:ModuleConcatenationPlugin不需要手动配置
在Webpack 3中,为了提升运行时性能,我们会手动启用ModuleConcatenationPlugin(作用域提升),把多个模块合并到一个函数中,减少函数声明的开销。升级到Webpack 4之后,production模式下会自动启用ModuleConcatenationPlugin,不需要手动配置了。
我们把配置中的ModuleConcatenationPlugin移除了,生产环境构建自动启用作用域提升,运行时性能更好。
问题八:一些小众插件不兼容
我们项目中用了一些比较小众的Webpack插件,升级到Webpack 4之后,这些插件因为没有更新而不兼容,导致构建报错。
对于这些不兼容的插件,我们的处理方式是:
- 先看看有没有替代的插件,如果有,就替换成支持Webpack 4的插件。
- 如果没有替代插件,就看看能不能用Webpack 4的内置功能实现同样的效果。
- 如果都不行,就自己修改插件的源码,让它兼容Webpack 4,或者提issue等作者更新。
我们项目中有一个自定义的插件,是之前的同事写的,用来生成构建信息文件。升级到Webpack 4之后,这个插件因为用到了Webpack 3的一些废弃API而报错。我们花了一点时间,把这个插件改写成了兼容Webpack 4的版本,问题就解决了。
问题九:热更新(HMR)配置变化
在Webpack 3中,热更新需要配置webpack.HotModuleReplacementPlugin,并且在入口文件中添加webpack-hot-middleware/client或者react-hot-loader等。升级到Webpack 4之后,热更新的配置有一些变化。
我们用的是webpack-dev-server,在Webpack 4中,只需要在devServer中设置hot: true,并且在mode为development的情况下,Webpack会自动启用HotModuleReplacementPlugin,不需要手动配置了。
但是我们的项目是Vue项目,用了vue-loader,Vue的热更新需要vue-loader配合。升级到Webpack 4之后,我们也把vue-loader升级到了支持Webpack 4的版本,热更新功能正常。
需要注意的是,Webpack 4的热更新比Webpack 3快了很多,几乎是秒级的,开发体验提升非常明显。
问题十:source map配置变化
在Webpack 3中,我们通过devtool选项来配置source map,不同的环境用不同的devtool值。升级到Webpack 4之后,devtool选项依然可用,但是mode会自动设置默认的devtool值。
在development模式下,默认的devtool是eval,构建速度快,但是source map的质量一般。在production模式下,默认不生成source map(devtool为false)。
我们根据需要,在不同的环境下设置了不同的devtool值:
- 开发环境:devtool: 'cheap-module-eval-source-map',构建速度快,调试方便。
- 生产环境:devtool: 'source-map',生成单独的source map文件,方便线上排查问题,但是不会暴露给用户(source map文件不部署到线上,或者只在内网可访问)。
配置了devtool之后,source map功能正常,调试和线上问题排查都很方便。
四、配置的优化和简化
升级到Webpack 4之后,除了适配新的API,我们还利用Webpack 4的新特性,对配置进行了优化和简化。
优化一:利用mode减少配置
有了mode之后,很多之前需要手动配置的插件和优化都不需要了,比如UglifyJsPlugin、ModuleConcatenationPlugin、NamedModulesPlugin、NoEmitOnErrorsPlugin等,mode会自动根据环境启用相应的优化。
我们的配置文件从原来的200多行,减少到了120多行,简洁了很多,也更容易维护。
优化二:利用SplitChunksPlugin优化代码分割
之前用CommonsChunkPlugin的时候,代码分割的配置比较复杂,而且效果不是很理想。用了SplitChunksPlugin之后,代码分割更加智能和灵活,不仅能提取同步chunk的公共代码,还能提取异步chunk的公共代码,打包体积更小,缓存效果更好。
我们还利用SplitChunksPlugin的cacheGroups功能,把不同类型的第三方库分开打包,比如把Vue相关的库打包到vue-vendor.js,把工具类库打包到utils-vendor.js,这样缓存粒度更细,用户更新代码时需要下载的体积更小。
优化三:利用sideEffects优化Tree Shaking
我们在package.json中添加了"sideEffects"字段,标记哪些文件是有副作用的,帮助Webpack更准确地进行Tree Shaking。
{
"sideEffects": [
"*.css",
"*.scss",
"*.less"
]
}这个配置告诉Webpack,CSS/SCSS/Less文件是有副作用的(因为它们会影响全局样式),不能被Tree Shaking移除,而JS文件默认是没有副作用的,可以安全地进行Tree Shaking。
添加了sideEffects之后,Tree Shaking的效果明显提升,打包体积减小了不少。特别是对于一些只用到了部分功能的第三方库,Tree Shaking可以移除很多没有用到的代码。
优化四:利用contenthash优化长缓存
在Webpack 3中,我们用chunkhash来作为文件名的hash,但是chunkhash有一个问题:当chunk的内容没有变化,但是因为其他chunk的变化导致模块ID变化时,chunkhash也会变化,影响缓存效果。
Webpack 4引入了contenthash,contenthash只和文件的内容有关,和模块ID无关,更加稳定。我们把文件名中的chunkhash换成了contenthash,缓存效果更好,用户更新代码时需要下载的文件更少。
output: {
filename: '[name].[contenthash].js',
chunkFilename: '[id].[contenthash].js'
}优化五:利用webpack-bundle-analyzer分析打包结果
升级到Webpack 4之后,我们用webpack-bundle-analyzer对打包结果进行了分析,看看哪些模块比较大、哪些可以优化。
通过分析,我们发现了一些可以优化的地方:
- 某个第三方库体积很大,但是我们只用到了很小一部分功能,于是找了一个更轻量的替代库。
- 一些图片没有压缩,体积比较大,我们用image-webpack-loader对图片进行了压缩。
- 一些moment.js的locale文件被打包进去了,我们用IgnorePlugin忽略了不需要的locale文件。
通过这些优化,打包体积又减小了不少。
五、性能对比
升级完成之后,我们对Webpack 3和Webpack 4的性能进行了对比,结果非常令人满意。
构建速度对比
我们的项目是一个中大型的Vue单页应用,大概有200多个模块,代码量大概10万行左右。
| 构建类型 | Webpack 3 | Webpack 4 | 提升 |
|---|---|---|---|
| 开发环境首次构建 | 18.5秒 | 8.2秒 | 55.7% |
| 开发环境热更新 | 3.2秒 | 0.8秒 | 75.0% |
| 生产环境构建 | 42.6秒 | 16.8秒 | 60.6% |
可以看到,Webpack 4的构建速度比Webpack 3快了很多,特别是热更新的速度,从3.2秒降到了0.8秒,几乎是秒级的,开发体验提升非常明显。
打包体积对比
| 文件类型 | Webpack 3 | Webpack 4 | 减小 |
|---|---|---|---|
| JS(gzip后) | 385KB | 268KB | 30.4% |
| CSS(gzip后) | 42KB | 38KB | 9.5% |
| 总体积(gzip后) | 427KB | 306KB | 28.3% |
打包体积也减小了不少,特别是JS的体积,减小了30%以上。这主要得益于Webpack 4更好的Tree Shaking、更智能的代码分割,以及我们升级过程中做的一些优化。
六、经验和教训
回顾整个升级过程,我们总结了一些经验和教训,希望能给准备升级Webpack 4的团队一些参考。
经验一:升级前做好充分准备
升级前一定要做好充分的准备,包括阅读发布说明和迁移指南、检查依赖的兼容性、备份代码、制定升级计划。准备工作做得越充分,升级过程就越顺利。
我们在升级前花了一天时间做准备,把可能遇到的问题都提前想好了,所以正式升级的时候虽然遇到了一些问题,但是都能快速解决,没有卡住太久。
经验二:循序渐进,不要一步到位
升级的时候不要想着一步到位,把所有的东西都改成最新的。建议先保证构建能跑通,功能正常,然后再逐步优化配置、利用新特性。
我们的升级过程就是:先把Webpack和相关依赖升级到Webpack 4版本,修改配置让构建能跑通;然后替换废弃的插件,适配新的API;等功能都正常了,再利用Webpack 4的新特性优化配置和性能。这样循序渐进,风险更小,也更容易排查问题。
经验三:充分利用Webpack 4的新特性
升级到Webpack 4之后,不要只是把配置改得能跑通就行,要充分利用Webpack 4的新特性来优化配置和性能。比如mode、SplitChunksPlugin、sideEffects、contenthash等,这些新特性能让配置更简洁、性能更好。
我们在升级过程中,花了不少时间来研究和应用这些新特性,最终的效果非常好,配置简洁了,性能也提升了。
经验四:测试要充分
升级完成之后,一定要进行充分的测试,确保功能正常。不仅要测试开发环境的热更新和调试,还要测试生产环境的构建结果,确保打包出来的文件能正常运行。
我们在升级完成之后,花了两天时间进行测试,包括功能测试、性能测试、兼容性测试等,发现了几个小问题,修复之后才合并到主分支。充分的测试能确保升级不会引入新的bug。
教训一:不要在项目最忙的时候升级
我们升级的时候,正好赶上项目有一个比较紧急的需求,开发任务比较重。升级占用了不少时间和精力,影响了正常的开发进度。
建议选择项目比较空闲的时候进行升级,这样有充足的时间来处理升级过程中遇到的问题,也不会影响正常的开发进度。
教训二:注意第三方库的兼容性
升级的时候,不仅要考虑Webpack本身的兼容性,还要考虑第三方库和插件的兼容性。有些第三方库可能依赖了Webpack 3的某些API,升级到Webpack 4之后可能会出问题。
我们在升级过程中就遇到了一个第三方UI组件库的问题,它的构建产物在Webpack 4下有兼容性问题,导致运行报错。后来我们升级了这个组件库的版本,问题才解决。
教训三:文档要及时更新
升级完成之后,要及时更新项目的文档,包括构建配置的说明、常见问题的处理、升级的注意事项等。这样团队中的其他成员在遇到问题的时候,可以查阅文档快速解决,不需要每次都来问升级的人。
我们在升级完成之后,更新了项目的README和构建文档,把Webpack 4的配置说明、常见问题、性能优化等都写进去了,团队成员很快就适应了新的构建方式。
七、总结
从Webpack 3升级到Webpack 4,我们花了大概一周的时间,遇到了不少问题,也踩了一些坑,但是最终的结果非常令人满意。构建速度提升了60%以上,打包体积减小了30%左右,配置也更加简洁清晰,开发体验提升非常明显。
Webpack 4确实是一个非常优秀的版本,零配置、mode模式、SplitChunksPlugin、更好的Tree Shaking、更快的构建速度,这些新特性和改进让前端工程化变得更加简单和高效。
如果你还在使用Webpack 3或者更早的版本,强烈建议升级到Webpack 4。虽然升级过程需要花一些时间和精力,但是升级之后的收益是非常明显的,值得投入。
当然,升级的时候要注意做好准备工作,循序渐进,充分测试,确保升级不会影响项目的正常运行。
最后,感谢Webpack团队为我们带来这么优秀的构建工具,也期待Webpack未来的版本能带来更多的惊喜。前端工程化的路还很长,我们一直在路上。
评论(0)
暂无评论,快来抢沙发~
评论功能仅对会员开放,请先登录
登录