Fork me on GitHub

webpack4.x生产环境篇

 之前做过开发环境的webpack4.x迁移,但是由于一些原因后续生产的配置改造搁置了…近期正在着手处理这块,本文是对迁移过程的记录分享,包括一些新版本的处理方式和实际改写存在的问题。

  想看前期开发环境配置记录的可以走该传送门

  下面开始正文…

踩坑

mode

  上次的分享中有讨论过在新版本的webpack内,有约定大于配置一说,但是当时其实我配置的部分内容其实是有问题的,比如process.env.NODE_ENV在默认情况下将会得到development,在生产的config文件内,我们可以通过直接设置mode属性进行merge,它会被关联到process.env.NODE_ENV上。

1
2
3
4
let prodConfig = merge(baseConfig, {
mode: 'production',
// ...
});

  老版本也有类似下面的设置:

1
2
3
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify('production')
})

url-loader

  在配置生产环境打包文件的过程中,我发现之前处理文件输出的loader写法也存在问题。因为最终我们的静态资源都要放在一个dist目录下,并且通常是不会改变的(请结合自身实际业务场景)。那我需要的就是将url()引到的文件,原封不动地输出到dist中。那我之前的写法是怎么样的呢?

1
2
3
4
5
6
7
{
loader: 'url-loader',
options: {
limit: 1024,
outputPath: 'images'
}
}

  这样处理会有两个问题:

  1. 文件打包时输出路径并不在dist下。我们调整时,需关注output中的path设置。
  2. 没有配置name。该属性会指定文件输出时的名称,缺省状态下会生成一串哈希值(不包含原文件名)。

  改写:

1
2
3
4
5
6
7
8
{
loader: 'url-loader',
options: {
limit: 1024,
name: '[name].[ext]', // 原文件名.后缀 等价于输出原文件名
outputPath: './' // 结合path 定位输出目录为dist
}
}

DllReferencePlugin

  这是一个归属于webpack下的插件,通过如下方式配置,会检视manifest.json中的映射关系略过已被处理的模块。与开发环境配置一文中的处理相同。

1
2
3
4
new webpack.DllReferencePlugin({
context: __dirname,
manifest: require('./dist/vendor-manifest.json')
})

CleanWebpackPlugin

  用于清理文件目录的插件,通常我们会在重新打包编译前清空你存放部署文件的目录,比如我们的dist。引入方式务必注意,在webpack4.x版本中,我们须要通过const { CleanWebpackPlugin } = require('clean-webpack-plugin')的方式引用。

  前文我们有讨论会先打一个dll出来,而配置dll时,我们已经清理了一次目录,在build时,我们同样需要再清理一次之前可能打包过的旧内容,但像vendor.dll.jsvendor.manifest.json之类的dll生成内容需要保留,我们可以结合该插件提供的生命周期属性cleanOnceBeforeBuildPatterns介入:

1
2
3
new CleanWebpackPlugin({
cleanOnceBeforeBuildPatterns: ['**/*', '!vendor.dll.js', '!vendor-manifest.json', '!vendors~pdfjsWorker.dll.js'], // 数组格式,通过!保留你要的内容,第一个参数表明当前目录
})

CopyWebpackPlugin

  当然,我们可能有些静态资源不是通过url引用的,须要我们手动输出到dist下,可以通过CopyWebpackPlugin插件拷贝过去:

1
2
3
4
5
6
new CopyWebpackPlugin([
{
from: './public/',
to: './'
}
])

HtmlWebpackPlugin

  这个跟开发环境的配置也类似,不过我们部署时须要写入文件,并且要适当减小体积,可以如下操作:

1
2
3
4
5
6
7
8
9
10
11
new HtmlWebpackPlugin({
template: path.resolve(__dirname, './src/index.ejs'),
filename: 'index.html',
alwaysWriteToDisk: true, // 写入磁盘
chunks: ['vendor', 'index'], // 配置取决于你的分块内容,有分块加vendor
minify: {
removeComments: true,
collapseWhitespace: true,
removeAttributeQuotes: true
}
})

optimization压缩优化

  过去版本的webpack常采用UglifyJsPlugin进行代码压缩,在webpack4.x中则改为在optimization下配置minimizer的方案,接收一个数组,里面是使用插件。我们主要使用TerserPlugin

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
optimization: {
minimize: true,
minimizer: [
new TerserPlugin({
terserOptions: {
output: {
comments: false, // 配合下面的extractComments,移除代码中注释
compress: {}, // 默认 设置false可以跳过压缩环节 想自己定制 见 https://github.com/terser/terser#compress-options
},
},
// sourceMap: true,
extractComments: false, // 不单独提取/^\**!|@preserve|@license|@cc_on/i规则的注释,并生成xx.js.LICENSE
cache: true, // 开启缓存
parallel: true, // 是否并发 设置为true 并发数为 os.cpus().length - 1 即你的内核数 - 1 也可以手动指定数字
}),
]
}

stats

  这个属性是用来控制命令行输出统计内容的,默认情况下我们会看到一堆输出内容,非常冗余。事实上,我们只想看到最后输出了哪些文件以及报错时是什么问题,下面是我的配置:

1
2
3
4
5
6
7
8
stats: {
all: false, // 不输出全部信息
assets: true, // 输出最后的打包文件
errors: true, // 遇到错误时,输出内容
warnings: false, // 静默warning
moduleTrace: true, // 遇到错误时,定位文件
errorDetails: true, // 输出具体错误
}

比较

  以上大致就是本人迁移中遇到的一些问题,下面贴一下操作前后的比较图…

  原本脚手架自带的打包:

  这里可能有同学会问这个File sizes after gzip是什么,打出来的体积就是gzip压缩体积吗?其实不是的,它只是在react-dev-utils库中的一些方法帮助下计算了文件处理后的体积,并非是真实进行了处理。是否开启gzip需要服务器的处理。

  迁移魔改后:

  1. dll耗时:

  1. 生产build