使用vue-cli工具构建的vue+webpack项目配置详解

使用vue-cli构建的vue+webpack的项目可以说是把一切东西都给搭建好了,简直就是开箱即用.也不用过多的关注其中的配置文件,但是当真要去修改的时候反而又是一知半解的,还要去反复的去查文档,所以最近就通读了webpack的官方文档和vue-cli的文档配置,然后对vue+webpack的配置做了一次比较完整的注释.

参考资料:https://juejin.im/post/584e48b2ac502e006c74a120

使用的版本

node版本:6.10.2
npm版本:3.10.10
vue版本:^2.3.3
webpack版本:^2.6.1
在介绍文件内容之前建议大家去读一下webpack的中文官网,最好通读里面的概念,指南,文档,你还需要去了解一下nodejs的process对象和path对象,这对你理解注释内容有很大的帮助.
npm的指令相信大家都比较熟悉了,那接下来就直接根据命令开始介绍了.

1
npm run dev

这个指令你看一下package.json文件就会发现它执行了dev-server.js,那我们就从dev-server.js开始.

dev-server.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
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
require('./check-versions')()//检查node和npm的版本

/*获取config/index.js中的默认配置,config后面没有配置项会自动找index.js*/
var config = require('../config')

/*如果Node环境无法判断是dev还是product环境则使用config.dev.env.NODE_ENV作为当前执行环境*/
if (!process.env.NODE_ENV) {
process.env.NODE_ENV = JSON.parse(config.dev.env.NODE_ENV)
}

var opn = require('opn')//一个可以强制打开浏览器并跳转到指定url的插件

var path = require('path')//使用Node自带的文件路径工具

var express = require('express')//使用express

var webpack = require('webpack')//使用webpack

var proxyMiddleware = require('http-proxy-middleware')//一个Node的代理中间件
var webpackConfig = process.env.NODE_ENV === 'testing'
? require('./webpack.prod.conf')
: require('./webpack.dev.conf')//根据不同的Node环境加载不同的webpack配置

// default port where dev server listens for incoming traffic,如果没有指定端口就是用config.dev.port作为运行端口
var port = process.env.PORT || config.dev.port

// automatically open browser, if not set will be false根据config.dev.autoOpenBrowser选择是否自动打开浏览器
var autoOpenBrowser = !!config.dev.autoOpenBrowser

// Define HTTP proxies to your custom API backend
// https://github.com/chimurai/http-proxy-middleware
//使用 config.dev.proxyTable 的配置作为 proxyTable 的代理配置
var proxyTable = config.dev.proxyTable

var app = express()//使用express启动一个服务

var compiler = webpack(webpackConfig)//启动webpack进行编译

// 启动 webpack-dev-middleware,将 编译后的文件暂存到内存中
var devMiddleware = require('webpack-dev-middleware')(compiler, {
publicPath: webpackConfig.output.publicPath,
quiet: true
})

// 启动 webpack-hot-middleware,也就是我们常说的 Hot-reload,https://www.npmjs.com/package/webpack-hot-middleware
var hotMiddleware = require('webpack-hot-middleware')(compiler, {
log: () => {},
heartbeat: 2000
})
// 当html-webpack-plugin模板更改时,强制页面重新加载
compiler.plugin('compilation', function (compilation) {
compilation.plugin('html-webpack-plugin-after-emit', function (data, cb) {
hotMiddleware.publish({ action: 'reload' })
cb()
})
})

// proxy api requests
//将 proxyTable 中的请求配置挂在到启动的 express 服务上
Object.keys(proxyTable).forEach(function (context) {
var options = proxyTable[context]
if (typeof options === 'string') {
options = { target: options }
}
app.use(proxyMiddleware(options.filter || context, options))
})

// handle fallback for HTML5 history API
//使用 connect-history-api-fallback 匹配资源,如果不匹配就可以重定向到指定地址
// https://www.npmjs.com/package/connect-history-api-fallback
app.use(require('connect-history-api-fallback')())

// serve webpack bundle output
//将暂存到内存中的 webpack 编译后的文件挂在到 express 服务上
app.use(devMiddleware)

// enable hot-reload and state-preserving
// compilation error display
//将 Hot-reload 挂在到 express 服务上
app.use(hotMiddleware)

// serve pure static assets
//拼接 static 文件夹的静态资源路径
var staticPath = path.posix.join(config.dev.assetsPublicPath, config.dev.assetsSubDirectory)
app.use(staticPath, express.static('./static'))

// 让我们这个 express 服务监听 port 的请求,并且将此服务作为 dev-server.js 的接口暴露出去
var uri = 'http://localhost:' + port
var _resolve
var readyPromise = new Promise(resolve => {
_resolve = resolve
})

console.log('> Starting dev server...')
devMiddleware.waitUntilValid(() => {
console.log('> Listening at ' + uri + '\n')

// when env is testing, don't need open it
// 如果不是测试环境,自动打开浏览器并跳到我们的开发地址
if (autoOpenBrowser && process.env.NODE_ENV !== 'testing') {
opn(uri)
}
_resolve()
})

var server = app.listen(port)

module.exports = {
ready: readyPromise,
close: () => {
server.close()
}
}

这个文件引用了三个配置文件,分别是config/index.js,webpack.prod.conf.js,
webpack.dev.conf.js,那我们就直接按照先后顺序来吧.

index.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
52
53
54
55
56
57
58
// see http://vuejs-templates.github.io/webpack for documentation.
var path = require('path')//使用Node自带的文件路径插件

module.exports = {

//生产环境配置
build: {//http://vuejs-templates.github.io/webpack/backend.html

// 使用 config/prod.env.js 中定义的编译环境
env: require('./prod.env'),

index: path.resolve(__dirname, '../dist/index.html'),// 编译注入的 index.html 文件,必须是本地的绝对路径

assetsRoot: path.resolve(__dirname, '../dist'),// 编译输出的静态资源根路径

assetsSubDirectory: 'static',// 编译输出的二级目录
assetsPublicPath: '/',// 编译发布上线路径的根目录,可配置为资源服务器域名或 CDN 域名

productionSourceMap: true,//生成用于生产构建的源映射

// Gzip off by default as many popular static hosts such as
// Surge or Netlify already gzip all static assets for you.
// Before setting to `true`, make sure to:
// npm install --save-dev compression-webpack-plugin
productionGzip: false,// 是否开启 gzip

productionGzipExtensions: ['js', 'css'],// 需要使用 gzip 压缩的文件扩展名

// Run the build command with an extra argument to
// View the bundle analyzer report after build finishes:
// `npm run build --report`
// Set to `true` or `false` to always turn it on or off
bundleAnalyzerReport: process.env.npm_config_report//一个实用工具,用于分析项目的依赖关系https://www.npmjs.com/package/webpack-bundle-analyzer
},
//开发环境
dev: {
env: require('./dev.env'),// 使用 config/dev.env.js 中定义的编译环境

port: 8080,// 运行测试页面的端口

autoOpenBrowser: true,//是否自动打开浏览器

assetsSubDirectory: 'static',// 编译输出的二级目录

assetsPublicPath: '/',// 编译发布上线路径的根目录,可配置为资源服务器域名或 CDN 域名

proxyTable: {
//https://github.com/chimurai/http-proxy-middleware,配置方式
},// 需要 proxyTable 代理的接口(可跨域)http://vuejs-templates.github.io/webpack/proxy.html

// CSS Sourcemaps off by default because relative paths are "buggy"
// with this option, according to the CSS-Loader README
// (https://github.com/webpack/css-loader#sourcemaps)
// In our experience, they generally work as expected,
// just be aware of this issue when enabling this option.
cssSourceMap: false// 是否开启 cssSourceMap
}
}

注:更详细的配置项请参考gitbook上的介绍

webpack.base.conf.js

之所以这里先介绍base是因为这是webpack的一个高级用法,webpack-merge,它可以把基本配置和不同环境的配置合并到一起,避免代码重复.所以就先介绍一下基础的配置项.

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
var path = require('path')// 使用 NodeJS 自带的文件路径插件

var utils = require('./utils')//封装了一些方法的工具

var config = require('../config')//使用 config/index.js

var vueLoaderConfig = require('./vue-loader.conf')//使用vue-loader.conf

// 拼接我们的工作区路径为一个绝对路径
function resolve (dir) {
return path.join(__dirname, '..', dir)
}

module.exports = {
entry: {
// 编译文件入口
app: './src/main.js'
},
output: {

//使用chonfig/index.js中build的assetsRoot作为输出根路径
path: config.build.assetsRoot,

filename: '[name].js',//编译输入的文件名

publicPath: process.env.NODE_ENV === 'production'// 正式发布环境下编译输出的发布路径
? config.build.assetsPublicPath
: config.dev.assetsPublicPath
},
resolve: {//https://doc.webpack-china.org/configuration/resolve/

// 自动补全的扩展名,能够使用户在引入模块时不带扩展
extensions: ['.js', '.vue', '.json'],

// 默认路径代理,例如 import Vue from 'vue$',会自动到 'vue/dist/vue.esm.js'中寻找
alias: {
'vue$': 'vue/dist/vue.esm.js',
'@': resolve('src')
}
},
module: {//https://doc.webpack-china.org/loaders/ loader列表
rules: [//https://doc.webpack-china.org/configuration/module/

//模块的规则数组,详情请参考上述链接的loader列表
{
test: /\.(js|vue)$/,//
loader: 'eslint-loader',
enforce: 'pre',
include: [resolve('src'), resolve('test')],
options: {
formatter: require('eslint-friendly-formatter')
}
},
{
test: /\.vue$/,
loader: 'vue-loader',
options: vueLoaderConfig
},
{
test: /\.js$/,
loader: 'babel-loader',
include: [resolve('src'), resolve('test')]
},
{
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
loader: 'url-loader',
options: {
limit: 10000,
name: utils.assetsPath('img/[name].[hash:7].[ext]')
}
},
{
test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/,
loader: 'url-loader',
options: {
limit: 10000,
name: utils.assetsPath('media/[name].[hash:7].[ext]')
}
},
{
test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
loader: 'url-loader',
options: {
limit: 10000,
name: utils.assetsPath('fonts/[name].[hash:7].[ext]')
}
}
]
}
}

注:详细的loader列表可以到webpack的loaders去查看

webpack.prod.conf.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
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
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
var path = require('path')//...
var utils = require('./utils')//...
var webpack = require('webpack')//...
var config = require('../config')//...
var merge = require('webpack-merge')//...
var baseWebpackConfig = require('./webpack.base.conf')//...

//可以将单个文件或整个目录复制到构建目录中
var CopyWebpackPlugin = require('copy-webpack-plugin')

// 一个可以插入 html 并且创建新的 .html 文件的插件
var HtmlWebpackPlugin = require('html-webpack-plugin')

// 一个 webpack 扩展,可以提取一些代码并且将它们和文件分离开
// 如果我们想将 webpack 打包成一个文件 css js 分离开,那我们需要这个插件
var ExtractTextPlugin = require('extract-text-webpack-plugin')

//一个个优化/最小化css资源的插件
var OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin')

var env = process.env.NODE_ENV === 'testing'
? require('../config/test.env')
: config.build.env//如果不是测试环境就直接使用生产环境
//合并 webpack.base.conf.js中的配置,里面具体的配置参考webpack.base.conf.js里面的注释
var webpackConfig = merge(baseWebpackConfig, {
module: {
rules: utils.styleLoaders({
sourceMap: config.build.productionSourceMap,
extract: true
})
},
devtool: config.build.productionSourceMap ? '#source-map' : false,
output: {
path: config.build.assetsRoot,//指定生产环境输出路径

filename: utils.assetsPath('js/[name].[chunkhash].js'),//编译输出带hash的文件名,可以指定hash长度(chunkhash:6)

chunkFilename: utils.assetsPath('js/[id].[chunkhash].js')// 没有指定输出名的文件输出的文件名
},
plugins: [
// http://vuejs.github.io/vue-loader/en/workflow/production.html
// definePlugin 接收字符串插入到代码当中, 所以你需要的话可以写上 JS 的字符串
new webpack.DefinePlugin({
'process.env': env
}),

// 压缩 js (同样可以压缩 css)
new webpack.optimize.UglifyJsPlugin({
compress: {
warnings: false
},
sourceMap: true
}),

// extract css into its own file
//将 css 文件分离出来
new ExtractTextPlugin({
filename: utils.assetsPath('css/[name].[contenthash].css')
}),

// Compress extracted CSS. We are using this plugin so that possible
// duplicated CSS from different components can be deduped.
//压缩css代码
new OptimizeCSSPlugin({
cssProcessorOptions: {
safe: true
}
}),

// generate dist index.html with correct asset hash for caching.
// you can customize output by editing /index.html
// see https://github.com/ampedandwired/html-webpack-plugin
// 输入输出的 .html 文件
new HtmlWebpackPlugin({
filename: process.env.NODE_ENV === 'testing'
? 'index.html'
: config.build.index,
template: 'index.html',

inject: true,// 是否注入 html

minify: {// 压缩的方式
removeComments: true,//移除带html的注释
collapseWhitespace: true,//移除空格
removeAttributeQuotes: true//移除属性的引号
// more options:
// https://github.com/kangax/html-minifier#options-quick-reference
},

// necessary to consistently work with multiple chunks via CommonsChunkPlugin
//https://doc.webpack-china.org/plugins/commons-chunk-plugin/
//
chunksSortMode: 'dependency'//资源按照依赖关系去插入
}),

// split vendor js into its own file//将引用的库文件拆出来打包到一个[name].js文件中
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor',
minChunks: function (module, count) {
// any required modules inside node_modules are extracted to vendor
//任何一个从node_modules中引用的模块都会被打包进来
return (
module.resource &&
/\.js$/.test(module.resource) &&
module.resource.indexOf(
path.join(__dirname, '../node_modules')
) === 0
)
}
}),

// extract webpack runtime and module manifest to its own file in order to
// prevent vendor hash from being updated whenever app bundle is updated
//https://doc.webpack-china.org/concepts/manifest/
//把webpack的runtime和manifest这些webpack管理所有模块交互的代码打包到[name].js文件中,防止build之后vendor的hash值被更新
new webpack.optimize.CommonsChunkPlugin({
name: 'manifest',
chunks: ['vendor']
}),

// copy custom static assets
//复制自定义的静态资源文件到dist/static文件夹中
new CopyWebpackPlugin([
{
from: path.resolve(__dirname, '../static'),
to: config.build.assetsSubDirectory,
ignore: ['.*']
}
])
]
})

// 开启 gzip 的情况下使用下方的配置
if (config.build.productionGzip) {

// Gzip依赖 compression-webpack-plugin 插件
var CompressionWebpackPlugin = require('compression-webpack-plugin')

// 向webpackconfig.plugins中加入下方的插件
webpackConfig.plugins.push(

// 使用 compression-webpack-plugin 插件进行压缩,https://doc.webpack-china.org/plugins/compression-webpack-plugin/
new CompressionWebpackPlugin({

asset: '[path].gz[query]',//目标资源名称

algorithm: 'gzip',//压缩方式

test: new RegExp(
'\\.(' +
config.build.productionGzipExtensions.join('|') +
')$'
),//所有匹配该正则的资源都会被处理。默认值是全部资源。

threshold: 10240,//只有大小大于该值的资源会被处理。单位是 bytes。默认值是 0。

minRatio: 0.8//只有压缩率小于这个值的资源才会被处理。默认值是 0.8。
})
)
}

//配置项目分析工具加载下方插件
if (config.build.bundleAnalyzerReport) {
var BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
webpackConfig.plugins.push(new BundleAnalyzerPlugin())
}

module.exports = webpackConfig

webpack.dev.conf.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
52
53
54
55
56
57
58
59
60
var utils = require('./utils')//封装了一些方法的工具

var webpack = require('webpack')//使用 webpack

var config = require('../config')//使用 config/index.js

var merge = require('webpack-merge')//使用 webpack 配置合并插件

var baseWebpackConfig = require('./webpack.base.conf')// 加载 webpack.base.conf

var HtmlWebpackPlugin = require('html-webpack-plugin')// 使用 html-webpack-plugin 插件,这个插件可以帮我们自动生成 html 并且注入到 .html 文件中

//https://www.npmjs.com/package/friendly-errors-webpack-plugin,可以识别某些类别的Webpack错误并进行清理,聚合和优先排序
var FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin')

// add hot-reload related code to entry chunks
//将 Hol-reload 相对路径添加到 webpack.base.conf 的 对应 entry 前
Object.keys(baseWebpackConfig.entry).forEach(function (name) {
baseWebpackConfig.entry[name] = ['./build/dev-client'].concat(baseWebpackConfig.entry[name])
})

// 将我们 webpack.dev.conf.js 的配置和 webpack.base.conf.js 的配置合并
module.exports = merge(baseWebpackConfig, {
module: {

// 使用 styleLoaders
rules: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap })
},

// 使用 #cheap-module-eval-source-map 模式作为开发辅助调试工具
// 具体配置请参考https://doc.webpack-china.org/configuration/devtool/
devtool: '#cheap-module-eval-source-map',

plugins: [
// definePlugin 接收字符串插入到代码当中, 需要的话可以写上 JS 的字符串
new webpack.DefinePlugin({
'process.env': config.dev.env
}),

// https://github.com/glenjamin/webpack-hot-middleware#installation--usage
// HotModule 插件在页面进行变更的时候只会重回对应的页面模块,不会重绘整个 html 文件
new webpack.HotModuleReplacementPlugin(),

//https://doc.webpack-china.org/plugins/no-emit-on-errors-plugin/
//在编译出现错误时,使用 NoEmitOnErrorsPlugin 来跳过输出阶段。这样可以确保输出资源不会包含错误。
new webpack.NoEmitOnErrorsPlugin(),

// https://github.com/ampedandwired/html-webpack-plugin
// 将 index.html 作为入口,注入 html 代码后生成 index.html文件
//https://doc.webpack-china.org/plugins/html-webpack-plugin/ webpack插件列表(中文)
new HtmlWebpackPlugin({
filename: 'index.html',
template: 'index.html',
inject: true
}),

//看上面
new FriendlyErrorsPlugin()
]
})

接下来是

1
npm run build

build.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
require('./check-versions')()// 检查 Node 和 npm 版本

process.env.NODE_ENV = 'production'//指定生产环境

var ora = require('ora')// 一个很好看的 loading 插件

var rm = require('rimraf')//提供node版本的UNIX的rm -rf命令

var path = require('path')//使用Node自带的文件路径插件

var chalk = require('chalk')//控制台高亮显示的插件

var webpack = require('webpack')//使用 webpack

var config = require('../config')//使用 config/index.js

var webpackConfig = require('./webpack.prod.conf')// 加载 webpack.prod.conf

// 使用 ora 打印出 loading + log
var spinner = ora('building for production...')
spinner.start()

//https://www.npmjs.com/package/rimraf
rm(path.join(config.build.assetsRoot, config.build.assetsSubDirectory), err => {
if (err) throw err//如果回调函数出现错误就抛出异常

// 开始 webpack 的编译
webpack(webpackConfig, function (err, stats) {//编译回调函数
spinner.stop()
if (err) throw err//编译失败就抛出异常

process.stdout.write(stats.toString({//标准输出流
colors: true,
modules: false,
children: false,
chunks: false,
chunkModules: false
}) + '\n\n')

console.log(chalk.cyan(' Build complete.\n'))
console.log(chalk.yellow(
' Tip: built files are meant to be served over an HTTP server.\n' +
' Opening index.html over file:// won\'t work.\n'
))
})
})

build.js依赖的webpack.prod.conf.js已经在上面介绍了,所以翻上去看看吧.

dev.env.js, prod.env.js, test.env.js

这三个js里面的代码比较简单,就是输出了三个不同的node环境, 分别对应开发环境, 生产环境, 测试环境.

check-versions.js

这里面的代码主要是通过调用shell去打印出node和npm的版本信息,同时会给出版本依赖的警告信息.

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
var chalk = require('chalk')// 用于在控制台输出高亮字体的插件
var semver = require('semver')// 语义化版本检查插件
var packageConfig = require('../package.json')// 引入package.json
var shell = require('shelljs')//引入shelljs
// 开辟子进程执行指令cmd并返回结果
function exec (cmd) {
return require('child_process').execSync(cmd).toString().trim()
}
// node和npm版本需求
var versionRequirements = [
{
name: 'node',
currentVersion: semver.clean(process.version),
versionRequirement: packageConfig.engines.node
},
]

if (shell.which('npm')) {
versionRequirements.push({
name: 'npm',
currentVersion: exec('npm --version'),
versionRequirement: packageConfig.engines.npm
})
}

module.exports = function () {
var warnings = []
// 依次判断版本是否符合要求
for (var i = 0; i < versionRequirements.length; i++) {
var mod = versionRequirements[i]
if (!semver.satisfies(mod.currentVersion, mod.versionRequirement)) {
warnings.push(mod.name + ': ' +
chalk.red(mod.currentVersion) + ' should be ' +
chalk.green(mod.versionRequirement)
)
}
}

if (warnings.length) {
console.log('')
// 如果有警告则将其输出到控制台
console.log(chalk.yellow('To use this template, you must update following to modules:'))
console.log()
for (var i = 0; i < warnings.length; i++) {
var warning = warnings[i]
console.log(' ' + warning)
}
console.log()
process.exit(1)
}
}

dev-client.js

这里主要是开发服务器热重载脚本,用来实现开发阶段的页面自动刷新.

你要是觉得好的话,可以点一下哦!