简介

经历过vue-cli2的时代,优化过项目打包配置,趁最近面试整理一下。分为默认配置介绍和自定义配置方法。

webpack可靠文档地址:https://webpack.docschina.org/configuration/optimization/

vue-cli官方文档:https://cli.vuejs.org/zh/config/

一、默认配置

1、vue-cli4

电脑上当前vue-cli是4.5.7版本,查看项目webpack版本为4.46.0

vue-cli对webpack配置进行了抽象,不能直接查看,但是vue-cli-service暴露了inspect命令用于查看webpack配置

命令:vue inspect > webpconfig.js

生成一个webpconfig.js文件(默认配置共1300+行,这里删除了一些细节,保留了整体结构,具体可通过以上命令自行查看)

这里对选项加了注释:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
{
mode: 'development', // 环境,不同环境使用不同优化的配置。vue-cli通过vue-cli-service的命令参数来更改打包时所用的环境。
context: '/Users/lishichang/Desktop/未命名文件夹/test', // entry等文件的上下文,entry路径在context目录下写相对路径
node: {}, // 这可以使最初为 Node.js 环境编写的代码,在其他环境(如浏览器)中运行。
output: {}, // 指示 webpack 如何去输出、以及在哪里输出你的bundle
resolve: {
alias: { // 别名
'@': '/Users/lishichang/Desktop/未命名文件夹/test/src',
vue$: 'vue/dist/vue.runtime.esm.js'
},
extensions: [], // 扩展名,默认以下这些,不写扩展名会去搜索文件,'.mjs','.js','.jsx','.vue','.json','.wasm'
modules: [ // 告诉 webpack 解析模块时应该搜索的目录。
'node_modules',
'/Users/lishichang/Desktop/未命名文件夹/test/node_modules',
'/Users/lishichang/Desktop/未命名文件夹/test/node_modules/@vue/cli-service/node_modules'
],
plugins: [ //应该使用的额外的解析插件
/* config.resolve.plugin('pnp') */
{}
]
},
resolveLoader: {}, // 这组选项与上面的 resolve 对象的属性集合相同,但仅用于解析 webpack 的 loader 包
module: {
noParse: /^(vue|vue-router|vuex|vuex-router-sync)$/, // 防止 webpack 解析那些任何与给定正则表达式相匹配的文件。忽略大型的 library 可以提高构建性能。
// module.rules就是loader配置,这里保留了vue的loader,可以看到至少vue-cli4.5.7,默认就加了cache-loader优化,cli2没有
rules: [
/* config.module.rule('vue') */
{
test: /\.vue$/,
use: [
/* config.module.rule('vue').use('cache-loader') */
{
loader: '/Users/lishichang/Desktop/未命名文件夹/test/node_modules/cache-loader/dist/cjs.js',
options: {
cacheDirectory: '/Users/lishichang/Desktop/未命名文件夹/test/node_modules/.cache/vue-loader',
cacheIdentifier: '9d856fc8'
}
},
/* config.module.rule('vue').use('vue-loader') */
{
loader: '/Users/lishichang/Desktop/未命名文件夹/test/node_modules/vue-loader/lib/index.js',
options: {
compilerOptions: {
whitespace: 'condense'
},
cacheDirectory: '/Users/lishichang/Desktop/未命名文件夹/test/node_modules/.cache/vue-loader',
cacheIdentifier: '9d856fc8'
}
}
]
}
]
},
optimization: { // 从 webpack 4 开始,会根据你选择的 mode 来执行不同的优化,不过所有的优化还是可以手动配置和重写。
splitChunks: { // webpack4.0后提供一个新的分包插件SplitChunksPlugin,开箱即用,高度抽象,移除了CommonsChunkPlugin
// 并且vue-cli4给了默认配置,一般不需要再优化了
cacheGroups: {
vendors: {
name: 'chunk-vendors',
test: /[\\/]node_modules[\\/]/,
priority: -10,
chunks: 'initial'
},
common: {
name: 'chunk-common',
minChunks: 2,
priority: -20,
chunks: 'initial',
reuseExistingChunk: true
}
}
},
minimizer: [ // 允许你通过提供一个或多个定制过的 TerserPlugin 实例,覆盖默认压缩工具(minimizer)。
/* config.optimization.minimizer('terser') */
new TerserPlugin(
{
terserOptions: {
compress: {},
mangle: {
safari10: true
}
},
sourceMap: true,
cache: true,
parallel: true,
extractComments: false
}
)
]
},
plugins: [
/* config.plugin('vue-loader') */
new VueLoaderPlugin(), // 处理rule,可以理解为loader做不了的事扔给plugin
/* config.plugin('define') */
// dotenv 是node.js的一个package,可以被node.js读取,但是不能被js读取,所以我们需要借助webpack将它们load进来,然后转换成js的全局环境变量。
new DefinePlugin(
{
'process.env': {
NODE_ENV: '"development"',
BASE_URL: '"/"'
}
}
),
/* config.plugin('case-sensitive-paths') */
new CaseSensitivePathsPlugin(),
/* config.plugin('friendly-errors') */
new FriendlyErrorsWebpackPlugin(),
/* config.plugin('html') */
new HtmlWebpackPlugin(),
/* config.plugin('preload') */
new PreloadPlugin(),
/* config.plugin('prefetch') */
new PreloadPlugin(),
/* config.plugin('copy') */
new CopyPlugin()
],
entry: {
app: [
'./src/main.js'
]
}
}

2、vue-cli2

回看了一下2018年优化过的项目,采用vue-cli2搭建,webpack版本为3.12.0。

Vue-cli2搭建项目部分目录结构如下,webpack配置文件在build文件夹中

vue-cli2目录

webpack.base.config.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
'use strict'
const path = require('path')
const utils = require('./utils')
const config = require('../config')
const vueLoaderConfig = require('./vue-loader.conf')
function resolve(dir) {
return path.join(__dirname, '..', dir)
}
module.exports = {
context: path.resolve(__dirname, '../'),
entry: {
app: './src/main.js'
},
output: {
path: config.build.assetsRoot,
filename: '[name].js',
publicPath: process.env.NODE_ENV === 'production' ?
config.build.assetsPublicPath : config.dev.assetsPublicPath
},
externals: {
'vue': 'Vue',
},
resolve: {
extensions: ['.js', '.vue', '.json'],
alias: {
'vue$': 'vue/dist/vue.esm.js',
'@': resolve('src'),
'vendor': path.resolve(__dirname, '../src/vendor')
}
},
module: {
rules: [{
test: /\.vue$/,
loader: 'vue-loader',
options: vueLoaderConfig
},
{
test: /\.less$/,
loader: "style-loader!css-loader!less-loader",
},
{
test: /\.js$/,
loader: 'babel-loader',
include: [resolve('src'), resolve('test'), resolve('node_modules/webpack-dev-server/client')],
exclude: /node_modules/,
}
...// 省略了一些不重要的字体、文件loader
]
},
node: {}
}
webpack.dev.config.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 代码过多,只放我想放的,毕竟vue-cli2已经很少了
const devWebpackConfig = merge(baseWebpackConfig, {
devServer: {
proxyTable: {
'/api': { //将www.exaple.com印射为/apis
target: 'http://***/admin', // 接口域名
changeOrigin: true, //是否跨域
pathRewrite: {
'^/api': '' //需要rewrite的,
}
},
},
}
}
})
webpack.prod.config.js

vue-cli2默认配置了CommonsChunkPlugin插件

webpack4.0后提供一个新的分包插件SplitChunksPlugin,开箱即用,移除了CommonsChunkPlugin

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
'use strict'
const path = require('path')
const utils = require('./utils')
const webpack = require('webpack')
const config = require('../config')
const merge = require('webpack-merge')
const baseWebpackConfig = require('./webpack.base.conf')
const CopyWebpackPlugin = require('copy-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const ExtractTextPlugin = require('extract-text-webpack-plugin')
const OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin')
const UglifyJsPlugin = require('uglifyjs-webpack-plugin')
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
if (config.build.productionGzip) {
const CompressionWebpackPlugin = require('compression-webpack-plugin')
}
// keep module.id stable when vendor modules does not change
new webpack.HashedModuleIdsPlugin(),
// enable scope hoisting
new webpack.optimize.ModuleConcatenationPlugin(),
// split vendor js into its own file
// vue-cli2默认配置了CommonsChunkPlugin插件
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor',
minChunks (module) {
// any required modules inside node_modules are extracted to vendor
return (
module.resource &&
/\.js$/.test(module.resource) &&
module.resource.indexOf(
path.join(__dirname, '../node_modules')
) === 0
)
}
})
new OptimizeCSSPlugin({
cssProcessorOptions: config.build.productionSourceMap
? { safe: true, map: { inline: false } }
: { safe: true }
})

二、自定义配置和优化

针对vue-cli4以上版本,vue.config.js,配置文档:https://cli.vuejs.org/zh/config/

1.cache-loader,缓存,放在其他loader前,尤其开销大的loader适合使用。vue-cli4默认使用。

2.thread-loader,

使用时,需将此 loader 放置在其他 loader 之前。放置在此 loader 之后的 loader 会在一个独立的 worker 池中运行。

在 worker 池中运行的 loader 是受到限制的。例如:

  • 这些 loader 不能生成新的文件。
  • 这些 loader 不能使用自定义的 loader API(也就是说,不能通过插件来自定义)。
  • 这些 loader 无法获取 webpack 的配置。
  • 请仅在耗时的操作中使用此 loader,如babel-loader

3.devtool,对sourcemap的使用。cheap-module-eval-source-map性能更好,生产环境就把source-map关掉。

4.开发环境不要启用压缩代码这些。生成环境如无必要,不用压缩代码、混淆代码,这些插件影响构建性能。

5.CommonsChunksPlugin,提取出manifest,这样如果没有更改的模块,不会重新生成hash值。vue-cli2默认使用,vue-cli4移除,增加了SplitChunksPlugin,默认使用SplitChunksPlugin

6.loader用在必要的地方,test选项中加include字段来规定使用loader的范围,避免查找和转换不必要的地方

7.常用打包分析插件分析如bundleAnalyzerReport,vendor包太大,用externals去除不必要的第三方包参与打包,改为使用cdn引入。

8.少写extensions,把扩展名写上,减少程序搜索文件次数。

9.更新版本,vue,vue-cli,webpack等,新版本都会有一些性能的优化。

10.优化代码,写合理的逻辑。

// 新的脚手架都默认做了很多优化,一般情况下已经无需大动。