笔者最近在准备给fle-cli
升级到webpack4
版本,觉得有必要将探索过程的经验分享给大家,遂决定写这篇文章。(不知道fle-cli
?看这里)
webpack4是大趋势,升级是必然的,那么为什么现在才升级?
原因有以下几个方面:
- 刚发布的版本还不稳定,潜在风险大,而目前版本已更新到4.8.3,基本处于稳定;
- webpack社区工具未完全跟上节奏,好多工具都得自己搞,劳心劳力(其实主要就是懒哈哈);
- webpack本身及社区工具存在或多或少的问题,未经时间沉淀,维护成本高。
然而现在,笔者认为以上这些已经成熟,是时候来一波升级了。
前言
本文不会讲解webpack配置的每个细节点,因为这些官方文档都可以看到。笔者会挑一些难以理解的新概念、可能会碰到的问题,以及笔者总结下来的优化方案来分享,希望可以给大家带来一些帮助。
配置
mode
mode是webpack4新增的参数选项,它的值有3个:development、production、none,能够帮助我们加载一些默认配置,none即不加载默认配置。下面将对应的默认配置列出来供大家参考,以免重复配置。
development
注重提升代码构建速度和开发体验
module.exports = {
cache: true,
devtools: "eval",
plugins: [
new webpack.NamedModulesPlugin(),
new webpack.DefinePlugin({ "process.env.NODE_ENV": JSON.stringify("development") })
]
}
prodution
提供代码优化,如压缩、作用域提升等
var UglifyJsPlugin = require('uglifyjs-webpack-plugin')
module.exports = {
plugins: [
new UglifyJsPlugin(/* ... */),
new webpack.DefinePlugin({ "process.env.NODE_ENV": JSON.stringify("production") }),
new webpack.optimize.ModuleConcatenationPlugin(),
new webpack.NoEmitOnErrorsPlugin()
]
}
optimization
这个选项也是webpack4新增的,主要是用来自定义一些优化打包策略。
minimizer
在production模式,该配置会默认为我们压缩混淆代码,但这显然满足不了我们对于优化代码的诉求。下面笔者分享一套自身实践总结下来的配置及解释:
var UglifyJsPlugin = require('uglifyjs-webpack-plugin')
var OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin')
module.exports = {
optimization: {
minimizer: [
// 自定义js优化配置,将会覆盖默认配置
new UglifyJsPlugin({
exclude: /\.min\.js$/, // 过滤掉以".min.js"结尾的文件,我们认为这个后缀本身就是已经压缩好的代码,没必要进行二次压缩
cache: true,
parallel: true, // 开启并行压缩,充分利用cpu
sourceMap: false,
extractComments: false, // 移除注释
uglifyOptions: {
compress: {
unused: true,
warnings: false,
drop_debugger: true
},
output: {
comments: false
}
}
}),
// 用于优化css文件
new OptimizeCssAssetsPlugin({
assetNameRegExp: /\.css$/g,
cssProcessorOptions: {
safe: true,
autoprefixer: { disable: true }, // 这里是个大坑,稍后会提到
mergeLonghand: false,
discardComments: {
removeAll: true // 移除注释
}
},
canPrint: true
})
]
}
}
UglifyJsPlugin
这款插件相信大家也是经常用到,这里不再多说,这里的亮点是过滤掉本身已经是压缩的js文件,能够提升我们的编译效率以及避免二次混淆压缩而造成的未知bug。
OptimizeCssAssetsPlugin
这款插件主要用来优化css文件的输出,默认使用cssnano
,其优化策略主要包括:摈弃重复的样式定义、砍掉样式规则中多余的参数、移除不需要的浏览器前缀等,更多优化规则看这里。前文我们提到这里有个大坑,相信你已经察觉到了,没错,就是这货把我们通过autoprefixer
加好了前缀给移除了。笔者查阅了许多资料,依旧没有找到满意的答案,没办法,只有硬着头皮去源码中找答案了,于是便有了这段配置autoprefixer: { disable: true }
,禁用掉cssnano对于浏览器前缀的处理。
runtimeChunk
分离出webpack编译运行时的代码,也就是我们先前称为manifest
的代码块,好处是方便我们做文件的持久化缓存。它可以设置多种类型的值,具体可以看这里,其中single
即将所有chunk的运行代码打包到一个文件中,multiple
就是给每一个chunk的运行代码打包一个文件。
我们可以配合InlineManifestWebpackPlugin
插件将运行代码直接插入html文件中,因为这段代码非常少,这样做可以避免一次请求的开销,但是新版插件的配置和之前有些不太一样,接下来详细讲解一下如何配置。
var HtmlWebpackPlugin = require('html-webpack-plugin')
var InlineManifestWebpackPlugin = require('inline-manifest-webpack-plugin')
module.exports = {
entry: {
app: 'src/index.js'
},
optimization: {
runtimeChunk: 'single'
// 等价于
// runtimeChunk: {
// name: 'runtime'
// }
},
plugins: [
new HtmlWebpackPlugin({
title: 'fle-cli',
filename: 'index.html',
template: 'xxx',
inject: true,
chunks: ['runtime', 'app'], // 将runtime插入html中
chunksSortMode: 'dependency',
minify: {/* */}
}),
new InlineManifestWebpackPlugin('runtime')
]
}
这段配置会产生一个叫做runtime
的代码块,和老版本不同的是,我们并不需要在html模版中添加<%= htmlWebpackPlugin.files.webpackManifest %>
,只需将runtime加入chunks即可。这里有一个点要注意,InlineManifestWebpackPlugin插件的顺序一定要在HtmlWebpackPlugin之后,否则会导致编译失败。
splitChunks
终于要讲到重头戏了,也是笔者个人认为最难以理解的一个配置项。webpack4移除了CommonsChunkPlugin插件,取而代之的是splitChunks。
我们先来看下默认配置:
splitChunks: {
chunks: "async",
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
}
}
}
默认配置只会作用于异步加载的代码块,它限制了分离文件的最小体积,即30KB(注意这个体积是压缩之前的),这个是前提条件,然后它有两个分组:属于node_modules模块,或者被至少2个入口文件引用,它才会被打包成独立的文件。
为什么要限制最小体积呢?因为webpack认为小于30KB的代码块分离出来,还要额外消耗一次请求去加载它,成本太高,当然这个值也不是随便意淫出来的,而是经过大量的实践总结得到的,笔者个人认为这是一个非常好策略。
而maxAsyncRequests
(最大的异步请求数)和maxInitialRequests
(最大的初始请求数)这两个参数则是为了限制代码块划分的过于细致,导致大量的文件请求。
但是只分离异步代码块显然满足不了我们的需求,因此接下来笔者分享一套相对来说比较优雅的分离打包配置:
splitChunks: {
cacheGroups: {
vendors: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
minSize: 30000,
minChunks: 1,
chunks: 'initial',
priority: 1 // 该配置项是设置处理的优先级,数值越大越优先处理
},
commons: {
test: /[\\/]src[\\/]common[\\/]/,
name: 'commons',
minSize: 30000,
minChunks: 3,
chunks: 'initial',
priority: -1,
reuseExistingChunk: true // 这个配置允许我们使用已经存在的代码块
}
}
}
首先是将node_modules的模块分离出来,这点就不再累述了。异步加载的模块将会继承默认配置,这里我们就不需要二次配置了。
第二点是分离出共享模块,笔者认为一个优雅的项目结构,其公共代码(或者称为可复用的代码)应该是放置于同一个根目录下的,基于这点我们可以将src/common
中的公用代码提取出来。
当然你还可以有另外一种选择,将后缀为.js
且使用次数超过3次的文件提取出来,但是笔者不建议这个做,因为这不利于持久化缓存,新增或删除文件都有可能影响到使用次数,从而导致原先的公共文件失效。
文末
原先还想着讲一下css插件部分的配置,限于篇幅,本文就不再进行讲解说明了,感兴趣的小哥哥小姐姐可以在这里翻看源码:webpack4-test。
顺便在这里推荐一款好用的全局通用脚手架fle-cli:旨在帮助我们从复杂繁琐的编译配置中解放出来,全身心地投入业务开发中,提高开发效率;同时它也是真正意义上的全局脚手架,区别于市面上其他的全局脚手架,它不会在项目工程中生成各种编译配置文件,也不会给你安装一系列编译的依赖包,这意味着你的项目工程可以非常干净纯粹。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。