Webpack 4在2018年2月25日正式发布了,带来了很多新特性和性能提升,号称"零配置"开箱即用,构建速度比Webpack 3快了很多。
作为一个前端工程师,我对Webpack 4期待已久。发布之后,我就迫不及待地把项目从Webpack 3升级到了Webpack 4。本以为会很顺利,结果踩了一堆坑,熬了好几个通宵才搞定。
今天,我想记录一下我Webpack 4配置过程中踩过的坑,以及我是怎么解决这些问题的。希望能给正在升级或者使用Webpack 4的朋友一些参考,让你们少踩坑,少熬夜。
一、为什么要升级到Webpack 4
先说说我为什么要升级到Webpack 4。
我们的项目,之前用的是Webpack 3,用了快一年了,整体还可以,但是有几个痛点:
- 构建速度慢:项目大了之后,构建速度越来越慢,开发环境热更新要等好几秒,生产环境构建要好几分钟,严重影响开发效率。
- 配置复杂:Webpack 3的配置很复杂,loader、plugin、各种配置项,加起来好几百行,新人来了根本看不懂,维护起来很麻烦。
- 代码分割不够灵活:Webpack 3的代码分割(CommonsChunkPlugin)配置起来比较麻烦,而且不够灵活,很难做到按需加载和最优的代码分割。
- Tree Shaking效果不好:Webpack 3虽然支持Tree Shaking,但是效果不好,很多没用的代码还是被打包进去了,产物体积大。
Webpack 4针对这些痛点,做了很多改进:
- 构建速度大幅提升:官方说比Webpack 3快了40%-98%,实际使用下来,确实快了很多
- 零配置开箱即用:提供了mode配置,很多默认配置都帮你做好了,不需要写一大堆配置
- 更灵活的代码分割:用SplitChunksPlugin替代了CommonsChunkPlugin,配置更灵活,效果更好
- 更好的Tree Shaking:改进了Tree Shaking算法,能更有效地剔除没用的代码
- 更好的性能优化:提供了很多性能优化的配置和工具,帮助你优化产物体积和加载速度
因为这些改进,我决定把项目升级到Webpack 4。现在看来,虽然踩了很多坑,但是升级是值得的,构建速度和产物体积都有了明显的改善。
二、升级过程中踩的坑
下面说说我升级过程中踩的具体的坑。
坑一:mode配置,必须设置
Webpack 4新增了mode配置,有三个可选值:development、production、none。这个配置是必须的,如果你不设置,Webpack会报警告,而且会默认用production模式。
我最开始升级的时候,不知道mode是必须的,没有设置,结果Webpack报警告:"The 'mode' option has not been set, webpack will fallback to 'production' for this value."而且,开发环境的构建产物被压缩了,没法调试,热更新也有问题。
后来我才知道,mode配置很重要,它会根据不同的模式,自动设置很多默认配置:
- development模式:开启调试工具,不压缩代码,不做Tree Shaking,热更新更快,适合开发环境
- production模式:压缩代码,做Tree Shaking,优化产物体积,适合生产环境
- none模式:不做任何默认优化,所有配置都自己来
解决方法很简单,在配置文件里设置mode就好了:
module.exports = {
mode: process.env.NODE_ENV === 'production' ? 'production' : 'development',
// ...
}或者,在命令行里用--mode参数:
webpack --mode development
webpack --mode production设置了mode之后,很多默认配置Webpack都帮你做好了,不需要像Webpack 3那样手动配置了,比如:
- development模式自动开启NamedModulesPlugin、HotModuleReplacementPlugin
- production模式自动开启UglifyJsPlugin、ModuleConcatenationPlugin、NoEmitOnErrorsPlugin等
这就是Webpack 4"零配置"的体现,虽然不是真的零配置,但是确实少写了很多配置。
坑二:CommonsChunkPlugin被移除了
Webpack 4移除了CommonsChunkPlugin,用SplitChunksPlugin和RuntimeChunkPlugin替代了。
我最开始升级的时候,配置里还留着CommonsChunkPlugin,结果Webpack直接报错:"Error: webpack.optimize.CommonsChunkPlugin has been removed, please use config.optimization.splitChunks instead."
这个改动比较大,因为SplitChunksPlugin的配置方式和CommonsChunkPlugin完全不一样。我花了不少时间,才把CommonsChunkPlugin的配置迁移到SplitChunksPlugin。
SplitChunksPlugin的默认配置其实已经很不错了,大部分情况下,你只需要这样配置就够了:
module.exports = {
optimization: {
splitChunks: {
chunks: 'all',
},
runtimeChunk: true,
},
}这样,Webpack会自动把公共代码提取出来,把运行时代码单独打包,效果比CommonsChunkPlugin好很多。
如果你需要更精细的控制,可以配置更多参数:
module.exports = {
optimization: {
splitChunks: {
chunks: 'all',
minSize: 30000, // 最小体积,超过这个体积才会被提取
minChunks: 1, // 最小被引用次数
maxAsyncRequests: 5, // 最大异步请求数
maxInitialRequests: 3, // 最大初始请求数
automaticNameDelimiter: '~', // 文件名分隔符
name: true,
cacheGroups: {
vendors: {
test: /[\\/]node_modules[\\/]/,
priority: -10,
},
default: {
minChunks: 2,
priority: -20,
reuseExistingChunk: true,
},
},
},
runtimeChunk: {
name: 'runtime',
},
},
}SplitChunksPlugin比CommonsChunkPlugin灵活很多,功能也更强大。虽然迁移的时候花了点时间,但是迁移完之后,代码分割的效果更好了,产物体积也更小了。
坑三:很多插件不兼容Webpack 4
这是升级过程中最头疼的问题。Webpack 4的API有一些变化,导致很多第三方插件不兼容,需要升级到支持Webpack 4的版本,或者替换成其他插件。
我遇到的不兼容的插件有:
- extract-text-webpack-plugin:这个插件用来把CSS从JS中提取出来,但是它不兼容Webpack 4,而且官方已经不推荐用了,推荐用mini-css-extract-plugin替代。
- 解决方法:换成mini-css-extract-plugin,配置方式类似,但是API有一些区别。
- html-webpack-plugin:这个插件用来生成HTML文件,旧版本不兼容Webpack 4,需要升级到最新版本。
- 解决方法:升级到html-webpack-plugin@3.x以上版本。
- webpack-dev-server:旧版本的webpack-dev-server不兼容Webpack 4,需要升级到最新版本。
- 解决方法:升级到webpack-dev-server@3.x以上版本。
- uglifyjs-webpack-plugin:Webpack 4内置了UglifyJsPlugin,不需要再手动引入了。如果你还手动引入旧版本的uglifyjs-webpack-plugin,可能会有冲突。
- 解决方法:移除手动引入的uglifyjs-webpack-plugin,用Webpack 4内置的就好了。如果需要自定义配置,可以在optimization.minimizer里配置。
- copy-webpack-plugin:旧版本不兼容Webpack 4,需要升级。
- 解决方法:升级到最新版本。
- 一些比较小众的插件:有些比较小众的、维护不活跃的插件,可能还没有支持Webpack 4,这时候就需要找替代方案,或者自己fork一份修改。
我的建议是,升级Webpack 4之前,先检查一下你用的所有插件,看看有没有支持Webpack 4的版本。如果有,就一起升级;如果没有,就找替代方案。不要只升级Webpack,不升级插件,那样肯定会出问题。
坑四:loader配置的变化
Webpack 4对loader的配置也有一些变化,主要是module.rules的配置更严格了。
我遇到的问题有:
- loader的写法:Webpack 4推荐用"use"数组的方式来配置loader,而不是"loader"字符串的方式。虽然旧的写法还支持,但是会有警告。
- 推荐写法: ``javascript module: { rules: [ { test: /\.css$/, use: ['style-loader', 'css-loader'], }, ], } ``
- json-loader不再需要了:Webpack 4内置了对JSON文件的支持,不需要再配置json-loader了。如果你还配置了json-loader,会有警告。
- 解决方法:移除json-loader的配置。
- url-loader和file-loader的配置:这两个loader的配置基本没变,但是要注意limit参数的设置,合理设置图片转base64的阈值,优化产物体积。
- babel-loader的配置:babel-loader的配置基本没变,但是要注意babel的版本,推荐用babel 7(虽然babel 7还在beta阶段,但是已经可以用了),配合babel-preset-env,根据目标浏览器自动转译,产物体积更小。
坑五:开发环境的配置变化
Webpack 4对开发环境的配置也有一些变化,主要是webpack-dev-server和热更新的配置。
我遇到的问题有:
- 热更新的配置:Webpack 4的热更新配置更简单了,在development模式下,只需要在webpack-dev-server的配置里设置hot: true,就可以开启热更新,不需要再手动引入HotModuleReplacementPlugin了。
- 配置示例: ``javascript devServer: { hot: true, // ... } ``
- devtool的选择:Webpack 4推荐在development模式下用cheap-module-eval-source-map,构建速度快,调试也方便;在production模式下用source-map或者不生成source map。
- 配置示例: ``javascript devtool: process.env.NODE_ENV === 'production' ? 'source-map' : 'cheap-module-eval-source-map', ``
- webpack-dev-server的contentBase:contentBase的配置基本没变,但是要注意路径的设置,确保静态资源能正确访问。
坑六:生产环境的性能优化
Webpack 4在生产环境的性能优化方面,做了很多改进,但是也有一些需要注意的地方。
我做的优化有:
- 代码压缩:Webpack 4的production模式默认会用UglifyJsPlugin压缩代码,但是默认配置可能不是最优的。你可以自定义UglifyJsPlugin的配置,比如开启多进程压缩、缓存、去除console.log等。
- 配置示例: ```javascript const UglifyJsPlugin = require('uglifyjs-webpack-plugin');
module.exports = { optimization: { minimizer: [ new UglifyJsPlugin({ cache: true, parallel: true, uglifyOptions: { compress: { drop_console: true, // 去除console.log }, }, }), ], }, } ```
- Tree Shaking:Webpack 4的Tree Shaking效果更好了,但是要注意,Tree Shaking只对ES6模块生效,对CommonJS模块不生效。所以,要确保你的代码用的是ES6模块(import/export),而不是CommonJS(require/module.exports)。另外,babel-preset-env默认会把ES6模块转成CommonJS,要设置modules: false,保留ES6模块,这样Tree Shaking才能生效。
- babel配置示例: ``json { "presets": [ ["env", { "modules": false }] ] } ``
- Scope Hoisting:Webpack 4的production模式默认会开启ModuleConcatenationPlugin(Scope Hoisting),把模块合并到一个函数里,减少函数声明,提升运行时性能,也能减小产物体积。这个功能默认开启,不需要手动配置,但是要注意,Scope Hoisting只对ES6模块生效,对CommonJS模块不生效。
- 持久化缓存:为了提升构建速度,可以开启缓存,比如babel-loader的cacheDirectory,uglifyjs-webpack-plugin的cache等。这样,第二次构建的时候,会快很多。
- 多进程构建:可以用happypack或者thread-loader,把loader的处理放到多进程里,提升构建速度。对于大项目来说,效果很明显。
坑七:构建产物的文件名和路径
Webpack 4对构建产物的文件名和路径的配置基本没变,但是有一些需要注意的地方。
我遇到的问题有:
- contenthash的使用:Webpack 4推荐用contenthash来命名静态资源,这样文件内容变化的时候,文件名才会变化,有利于浏览器缓存。
- 配置示例: ``javascript output: { filename: '[name].[contenthash:8].js', chunkFilename: '[name].[contenthash:8].js', } ``
- 静态资源的路径:要注意publicPath的设置,确保静态资源的路径正确,特别是部署到CDN或者子路径的时候。
- html-webpack-plugin的模板:html-webpack-plugin的配置基本没变,但是要注意模板的路径和变量的使用,确保HTML能正确生成。
三、我的最终配置
经过一番踩坑和调优,我最终的Webpack 4配置大概是这样的(简化版):
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const isProd = process.env.NODE_ENV === 'production';
module.exports = {
mode: isProd ? 'production' : 'development',
entry: {
app: './src/index.js',
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: isProd ? '[name].[contenthash:8].js' : '[name].js',
chunkFilename: isProd ? '[name].[contenthash:8].js' : '[name].js',
publicPath: '/',
},
devtool: isProd ? 'source-map' : 'cheap-module-eval-source-map',
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
cacheDirectory: true,
},
},
},
{
test: /\.css$/,
use: [
isProd ? MiniCssExtractPlugin.loader : 'style-loader',
'css-loader',
'postcss-loader',
],
},
{
test: /\.less$/,
use: [
isProd ? MiniCssExtractPlugin.loader : 'style-loader',
'css-loader',
'postcss-loader',
'less-loader',
],
},
{
test: /\.(png|jpg|gif|svg)$/,
use: [
{
loader: 'url-loader',
options: {
limit: 8192,
name: 'images/[name].[hash:8].[ext]',
},
},
],
},
{
test: /\.(woff|woff2|eot|ttf|otf)$/,
use: [
{
loader: 'file-loader',
options: {
name: 'fonts/[name].[hash:8].[ext]',
},
},
],
},
],
},
plugins: [
new CleanWebpackPlugin(['dist']),
new HtmlWebpackPlugin({
template: './src/index.html',
minify: isProd ? {
removeComments: true,
collapseWhitespace: true,
removeAttributeQuotes: true,
} : false,
}),
...(isProd ? [new MiniCssExtractPlugin({
filename: 'css/[name].[contenthash:8].css',
})] : []),
],
optimization: {
splitChunks: {
chunks: 'all',
cacheGroups: {
vendors: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
priority: 10,
},
commons: {
name: 'commons',
minChunks: 2,
priority: 5,
reuseExistingChunk: true,
},
},
},
runtimeChunk: {
name: 'runtime',
},
},
devServer: {
hot: true,
port: 3000,
historyApiFallback: true,
proxy: {
'/api': 'http://localhost:8080',
},
},
resolve: {
extensions: ['.js', '.json'],
alias: {
'@': path.resolve(__dirname, 'src'),
},
},
};这个配置,比我之前Webpack 3的配置简洁了很多,很多默认配置Webpack都帮我做好了。而且,构建速度快了很多,开发环境热更新从原来的3-5秒降到了1秒以内,生产环境构建从原来的5分钟降到了2分钟左右。产物体积也小了不少,JS体积减少了约20%,CSS体积减少了约15%。
四、升级建议和经验总结
最后,总结一下我升级Webpack 4的建议和经验。
建议一:先看官方文档和迁移指南
升级之前,一定要先看Webpack 4的官方文档和迁移指南,了解有哪些breaking changes,有哪些新特性,有哪些配置变化。这样升级的时候,心里有数,不会踩太多坑。
Webpack 4的迁移指南在这里:https://webpack.js.org/migrate/4/
建议二:逐步升级,不要一步到位
如果你的项目很大,配置很复杂,建议逐步升级,不要一步到位。可以先升级Webpack和核心插件,确保能跑起来,然后再逐步优化配置,做性能优化。这样风险小,出了问题也容易定位。
建议三:插件一起升级
升级Webpack的时候,一定要把相关的插件和loader也一起升级,确保它们都支持Webpack 4。不要只升级Webpack,不升级插件,那样肯定会出问题。
升级之前,可以先检查一下每个插件的最新版本,看看有没有支持Webpack 4。如果有,就一起升级;如果没有,就找替代方案。
建议四:利用mode配置,减少手动配置
Webpack 4的mode配置很强大,会根据不同的模式自动设置很多默认配置。要充分利用mode配置,不要像Webpack 3那样,什么都手动配置。这样不仅配置更简洁,而且不容易出错,也能享受到Webpack官方的优化。
建议五:用SplitChunksPlugin替代CommonsChunkPlugin
Webpack 4移除了CommonsChunkPlugin,用SplitChunksPlugin替代了。虽然迁移的时候有点麻烦,但是SplitChunksPlugin更灵活,功能更强大,效果也更好。建议花点时间学习一下SplitChunksPlugin的配置,把代码分割做好,这对产物体积和加载性能影响很大。
建议六:开启Tree Shaking和Scope Hoisting
Webpack 4的Tree Shaking和Scope Hoisting效果都很好,能有效减小产物体积,提升运行时性能。要确保这两个功能生效,需要注意:
- 用ES6模块(import/export),不要用CommonJS
- babel-preset-env设置modules: false,保留ES6模块
- production模式下,这两个功能默认开启
建议七:做好构建性能优化
Webpack 4的构建速度已经比Webpack 3快很多了,但是对于大项目来说,还是可以进一步优化。比如:
- 开启缓存(babel-loader的cacheDirectory,uglifyjs的cache等)
- 用多进程构建(happypack、thread-loader)
- 合理设置include/exclude,减少loader处理的文件
- 用DllPlugin预编译第三方库(虽然Webpack 4的性能已经很好了,但是对于超大项目,还是有用)
建议八:做好产物体积分析
升级之后,建议用webpack-bundle-analyzer分析一下产物体积,看看哪些模块比较大,有没有可以优化的地方。比如,有没有把整个lodash都打包进去(可以用lodash-es或者按需引入),有没有把moment的所有语言包都打包进去(可以只引入需要的语言包),等等。
通过体积分析,可以发现很多可以优化的点,进一步减小产物体积。
结语
Webpack 4的升级,虽然踩了很多坑,熬了好几个通宵,但是最终的结果是好的。构建速度快了,产物体积小了,配置也更简洁了。
Webpack作为前端工程化的核心工具,一直在不断进化。Webpack 4的"零配置"理念,虽然还不是真正的零配置,但是已经比之前简单了很多。相信未来的Webpack会越来越好用,越来越智能。
如果你还在用Webpack 3,或者更老的版本,我建议你找个时间升级到Webpack 4。虽然升级的过程可能会踩一些坑,但是升级之后的收益是实实在在的。
希望我的踩坑记录,能给正在升级或者使用Webpack 4的朋友一些参考,让你们少踩坑,少熬夜。
最后,用一句话总结:"Webpack 4,值得升级。虽然有坑,但是坑后的风景很美。"
愿每一个前端工程师,都能玩转Webpack,构建出高性能的前端应用。
评论(0)
暂无评论,快来抢沙发~
评论功能仅对会员开放,请先登录
登录