vue-cli webpack 配置信息分析(optimization 及 plugins)
使用 vue-cli3 生成 webpack 配置(default)
项目生成和完毕后,切换到项目目录,执行命令 npx vue-cli-service inspect
, 生成的 webpack 信息如下:
webpack 中 module、chunk、bundle 的概念
- module webpack 支持 commonJS、ES6 等模块化规范。
- chunk chunk 是 webpack 根据功能拆分出来的,包含三种情况: 1. 项目入口文件 2. 动态引入的代码
import (*.js)
3. webpack splitChunks 拆分出来的代码,chunk 中包含 module 可能是一对一也可能是一对多。 - bundle 是 webpack 打包后的各个文件, 一般与 chunk 是一对一的关系。
optimization
splitChunks
splitChunks 的作用
- 抽离相同的代码到一个共享块
- 脚本懒加载,使得初始下载的代码更小
在 webpack4 之前通常使用的是 CommonsChunkPlugin 作为拆分工具。
CommonsChunkPlugin 弊端
example1:
entryA: vue vuex someComponents
entryB: vue axios someComponents
entryC: vue vuex axios someComponents
minchunks: 2
产出后的 chunk:
vendor-chunk:vue vuex axios
chunkA~chunkC: only the components
example2:
entryA: vue vuex someComponents
asyncB:vue axios someComponents
entryC: vue vux axios someComponents
minchunks: 2
vendor-chunk:vue vuex
chunkA: only the components
chunkB: vue axios someComponents
chunkC: axios someComponents
CommmonsChunkPlugin 的思路是即将满足 minChunks 配置想所设置的条件的模块移到一个新的 chunk 文件中去,这个思路是基于父子关系的,也就是这个新产出的 new chunk 是所有 chunk 的父亲,在加载孩子 chunk 的时候,父亲 chunk 是必须要提前加载的。
由于 webpack4 是开箱即用的,在没有配置情况下(没有 webpack.config.js)optimization.splitChunks 配置如下:
module.exports = {
//...
optimization: {
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,
},
},
},
},
}
splitChunks.chunks
chunks 选项有 async、initial、all
- async 表示只从异步加载的模块进行拆分
- initial 表示只从入口模块进行拆分
- all 两者以上
splitChunks.minSize
被分割文件(没有被压缩前的体积)是否大于 splitChunks.minSize, 如果体积不够容保留在原来的引入文件中。
splitChunks.minChunks
被分割文件为被引用的数量大于或等于 splitChunks.minChunks, cacheGroups 内的约束条件可以单独设置。
splitChunks.maxAsyncRequests
按需加载的最大并行请求数。
splitChunks.maxInitialRequests
一个入口最大并行请求数。
splitChunks.automaticNameDelimiter
自动命名连接符, 默认 ~
。
splitChunks.cacheGroups
cacheGroups 中的每一可以单独配置,覆盖 cacheGroups 之外默认的配置。 cacheGroups 之外设置的约束条件比如说默认配置里面的 chunks、minSize、minChunks 等等都会作用于 cacheGroups,除了 test, priority、 reuseExistingChunk。
cacheGroups 为一个数组,每一个对象定义的是一个拆分规则。
splitChunks.cacheGroups.test
匹配指定的文件或目录如: node_modules。
splitChunks.cacheGroups.priority
权重,如:上面的 webpack 的默认配置中 vendors 与 default,vendors 默认匹配引用的 node_modules 的模块,default 匹配的是 minChunks(引用次数大于等于 2)的模块,会有一种情况两种匹配规则都满足,但 webpack 的规则是 priority 值越大,表示优先级越高,故匹配的包优先打到 vendors chunks 中。
splitChunks.cacheGroups.reuseExistingChunk
复用其他 chunk 内已拥有的模块 当 chunks 引用了已经存在的被抽离的 chunks 时不会新创建一个 chunk 而是复用 chunk。
vue-cli 配置:
splitChunks: {
cacheGroups: {
vendors: {
name: 'chunk-vendors',
test: /[\\\/]node_modules[\\\/]/,
priority: -10,
chunks: 'initial'
},
common: {
name: 'chunk-common',
minChunks: 2,
priority: -20,
chunks: 'initial',
reuseExistingChunk: true
}
}
}
minimize、minimizer
minimize
minimize 默认为 true,默认使用 terser-webpack-plugin 压缩 bundle 代码, 如果对 terser-webpack-plugin 使用自定义配置,可以在 minimizer 中配置。同时 minimizer 中也可以使用其他的压缩插件进行处理, 如: uglifyjs。
terser-webpack-plugin 与 glifyjs-webpack-plugin
Terser 是一款兼容 ES2015 + 的 JavaScript 压缩器。与 UglifyJS(许多项目的早期标准)相比,它是面向未来的选择。有一个 UglifyJS 的分支—— uglify-es,但由于它不再维护,于是就从这个分支诞生出了一个独立分支,它就是 terser。主要保留了与 ugly -es 和 ugly -js@3 的 API 和 CLI 兼容性。
minimizer
minimizer 主要用来覆盖压缩处理器,可以配置一个到多个。
常用配置方式:
常用配置:
const TerserPlugin = require('terser-webpack-plugin')
module.exports = {
optimization: {
minimizer: [
new TerserPlugin({
cache: true,
parallel: true,
sourceMap: true, // Must be set to true if using source-maps in production
terserOptions: {
// https://github.com/webpack-contrib/terser-webpack-plugin#terseroptions
},
}),
],
},
}
函数形式配置:
module.exports = {
optimization: {
minimizer: [
(compiler) => {
const TerserPlugin = require('terser-webpack-plugin')
new TerserPlugin({
/* your config */
}).apply(compiler)
},
],
},
}
使用 ...
作为默认配置
module.exports = {
optimization: {
minimizer: [new CssMinimizer(), '...'],
},
}
vue-cli 配置:
const TerserPlugin = require('terser-webpack-plugin')
{
minimizer: [
/* config.optimization.minimizer('terser') */
new TerserPlugin({
terserOptions: {
compress: {
// 是否将代码转换成箭头函数(注意浏览器兼容)
arrows: false,
// 内嵌定义了但是只用到一次的变量
collapse_vars: false,
comparisons: false,
// 将对象计算属性转换为对象常规属性
computed_props: false,
// 变量提升
hoist_funs: false,
// 属性提升
hoist_props: false,
// 变量提升
hoist_vars: false,
//
inline: false,
// 优化循环
loops: false,
// 对立即执行函数执行否定
negate_iife: false,
// 对象获取使用点操作符
properties: false,
reduce_funcs: false,
reduce_vars: false,
// 重复和删除不可到达的开关分支
switches: false,
// 在顶层范围内删除未引用的函数(“funcs”)和/或变量(“vars”)
toplevel: false,
// 转换 typeof foo == "undefined" into foo === void
typeofs: false,
// 优化布尔值变量
booleans: true,
// 优化 if/return and if/continue
if_return: true,
// 使用逗号操作连接多个语句
sequences: true,
// 去除未引用的代码
unused: true,
// 对 if-else 条件表达式进行优化
conditionals: true,
// 去除不会被执行的代码
dead_code: true,
// 尝试求取常量表达式的值
evaluate: true,
},
// 代码混淆
mangle: {
safari10: true,
},
},
// 是否生成 source-map 文件
sourceMap: true,
// 是否使用缓存 缓存默认存储在 `/.cache/terser-webpack-plugin`
cache: true,
// 是否开启多线程
parallel: true,
// 注释是否被拆分到一个单独的文件
extractComments: false,
}),
]
}
compress 具体参看 terser 配置。
plugins
vue-cli 配置:
plugins: [
/* config.plugin('vue-loader') */
new VueLoaderPlugin(),
/* config.plugin('define') */
new DefinePlugin({
'process.env': {
NODE_ENV: '"development"',
BASE_URL: '"/"',
},
}),
/* config.plugin('case-sensitive-paths') */
new CaseSensitivePathsPlugin(),
/* config.plugin('friendly-errors') */
new FriendlyErrorsWebpackPlugin({
additionalTransformers: [
function () {
/* omitted long function */
},
],
additionalFormatters: [
function () {
/* omitted long function */
},
],
}),
/* config.plugin('html') */
new HtmlWebpackPlugin({
title: 'test',
templateParameters: function () {
/* omitted long function */
},
template: 'H:\\code\\own\\test\\public\\index.html',
}),
/* config.plugin('preload') */
new PreloadPlugin({
rel: 'preload',
include: 'initial',
fileBlacklist: [/\.map$/, /hot-update\.js$/],
}),
/* config.plugin('prefetch') */
new PreloadPlugin({
rel: 'prefetch',
include: 'asyncChunks',
}),
/* config.plugin('copy') */
new CopyPlugin([
{
from: 'H:\\code\\own\\test\\public',
to: 'H:\\code\\own\\test\\dist',
toType: 'dir',
ignore: [
'.DS_Store',
{
glob: 'index.html',
matchBase: false,
},
],
},
]),
]
VueLoaderPlugin
// webpack.config.js
const VueLoaderPlugin = require('vue-loader/lib/plugin')
module.exports = {
module: {
rules: [
// ... 其它规则
{
test: /\.vue$/,
loader: 'vue-loader',
},
],
},
plugins: [
// 请确保引入这个插件!
new VueLoaderPlugin(),
],
}
vue-loader/lib/plugin
的职责是将你定义过的其它规则复制并应用到 .vue 文件里相应语言的块。例如,如果你有一条匹配 /.js$/ 的规则,那么它会应用到 .vue 文件里的
DefinePlugin
DefinePlugin 允许创建一个在编译时可以配置的全局常量。 如:process.env.NODE_ENV
。
CaseSensitivePathsPlugin
case-sensitive-paths-webpack-plugin
CaseSensitivePathsPlugin 要求被依赖的文件路径必须为精确匹配磁盘上的实际路径。
FriendlyErrorsWebpackPlugin
friendly-errors-webpack-plugin
FriendlyErrorsWebpackPlugin 可以识别 webpack 中的某些类错误,并对它们进行清理、聚合和排序,以提供更好的开发体验。
HtmlWebpackPlugin
HtmlWebpackPlugin 将会生成一个 HTML5 文件,简化了 HTML 文件的创建,这对于在文件名中包含每次会随着编译而发生变化哈希的 webpack bundle 尤其有用。
如果有任何 CSS assets 在 webpack 的输出中(例如, 利用 ExtractTextPlugin 提取 CSS), 那么这些将被包含在 HTML head 中的 标签内。
HtmlWebpackPlugin 默认模板语法为 ejs,可以通过配置设置打包时间。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width,initial-scale=1.0" />
<link rel="shortcut icon" href="./static/favicon.ico" type="image/x-icon" />
<meta
name="build-time"
content="<%= htmlWebpackPlugin.options.buildTime %>"
/>
</head>
<body></body>
</html>
plugins: [
new HtmlWebpackPlugin({
template: 'src/index.html,
buildTime: new Date().toString()
})
]
多 html 文件
plugins: [
new HtmlWebpackPlugin(), // Generates default index.html
new HtmlWebpackPlugin({
// Also generate a test.html
filename: 'test.html',
template: 'src/assets/test.html',
}),
]
PreloadPlugin
针对异步的 javascript 文件生成 或 代码块。
注: 此插件依赖 html-webpack-plugin
preload 与 prefetch
使用 preload 与 prefetch 可以有效的减少页面的首次加载时间,缩短用户的可交互时间,提高用户体验。
preload
preload 是声明式的 fetch,可以强制浏览器请求资源,同时不阻塞文档 onload 事件
prefetch
Prefetch 提示浏览器这个资源将来可能需要,但是把决定是否和什么时间加载这个资源的决定权交给浏览器
vue-cli3 中 入口文件使用 preload 处理文件的加载,对于异步的文件则使用 prefetch。
CopyPlugin
将已经存在的文件拷贝到打包目录。