vue-cli的webpack模板项目配置文件说明

时间:2021-06-02 05:12:09

如果没有vue-cli,那么进行vue项目的开发环境配置将是很费力的一件事情,现在脚手架可以快速构建一个开发环境,是很不错的。不过对于脚手架构建的配置,还是要大概了解一下,方便自己调试配置。

初始化一个vue项目操作命令如下:

# 全局安装 vue-cli
$ npm install --global vue-cli # 创建一个基于 webpack 模板的新项目
$ vue init webpack my-project # 安装依赖
$ cd my-project
$ npm install
$ npm run dev

一般来说,vue-cli构建项目时候回生成类似下面的项目文件结构:

├─build
│ ├─build.js
│ ├─check-versions.js
│ ├─dev-client.js
│ ├─dev-server.js
│ ├─utils.js
│ ├─vue-loader.conf.js
│ ├─webpack.base.conf.js
│ ├─webpack.dev.conf.js
│ ├─webpack.prod.conf.js
├─config
│ ├─dev.env.js
│ ├─index.js
│ ├─prod.env.js
├─...
└─package.json

一、项目指令

我们可以在package.json里面的看到scripts字段是这样:

"scripts": {
"dev": "node build/dev-server.js",
"build": "node build/build.js",
"unit": "cross-env BABEL_ENV=test karma start test/unit/karma.conf.js --single-run",
"e2e": "node test/e2e/runner.js",
"test": "npm run unit && npm run e2e",
"lint": "eslint --ext .js,.vue src test/unit/specs test/e2e/specs"
}

这里包含开发运行,项目打包,单元测试等命令,测试的东西先放一边,看dev和build命令。运行”npm run dev”的时候执行的是build/dev-server.js文件,运行”npm run build”的时候执行的是build/build.js文件,我们可以从这两个文件开始进行代码阅读分析。

二、build文件夹配置

1、build/dev-server.js

这个文件主要是完成下面操作:

 检查node和npm的版本
引入相关插件和配置
创建express服务器和webpack编译器
配置开发中间件(webpack-dev-middleware)和热重载中间件(webpack-hot-middleware)
挂载代理服务和中间件
配置静态资源
启动服务器监听特定端口(8080)
自动打开浏览器并打开特定网址(localhost:8080)

Ps:express服务器提供静态文件服务,不过它还使用了http-proxy-middleware,一个http请求代理的中间件。前端开发过程中需要使用到后台的API的话,可以通过配置proxyTable来将相应的后台请求代理到专用的API服务器。

代码配置注释:

 // 检查NodeJS和npm的版本
require('./check-versions')() // 获取配置
var config = require('../config')
// 如果Node的环境变量中没有设置当前的环境(NODE_ENV),则使用config中的配置作为当前的环境
if (!process.env.NODE_ENV) {
process.env.NODE_ENV = JSON.parse(config.dev.env.NODE_ENV)
} // 一个可以调用默认软件打开网址、图片、文件等内容的插件
// 这里用它来调用默认浏览器打开dev-server监听的端口,例如:localhost:8080
var opn = require('opn')
var path = require('path')
var express = require('express')
var webpack = require('webpack') // 一个express中间件,用于将http请求代理到其他服务器
// 例:localhost:8080/api/xxx --> localhost:3000/api/xxx
// 这里使用该插件可以将前端开发中涉及到的请求代理到API服务器上,方便与服务器对接
var proxyMiddleware = require('http-proxy-middleware') // 根据 Node 环境来引入相应的 webpack 配置
var webpackConfig = process.env.NODE_ENV === 'testing'
? require('./webpack.prod.conf')
: require('./webpack.dev.conf') // dev-server 监听的端口,默认为config.dev.port设置的端口,即8080
var port = process.env.PORT || config.dev.port // 用于判断是否要自动打开浏览器的布尔变量,当配置文件中没有设置自动打开浏览器的时候其值为 false
var autoOpenBrowser = !!config.dev.autoOpenBrowser // 定义 HTTP 代理表,代理到 API 服务器
var proxyTable = config.dev.proxyTable // 创建1个 express 实例
var app = express() // 根据webpack配置文件创建Compiler对象
var compiler = webpack(webpackConfig) // webpack-dev-middleware使用compiler对象来对相应的文件进行编译和绑定
// 编译绑定后将得到的产物存放在内存中而没有写进磁盘
// 将这个中间件交给express使用之后即可访问这些编译后的产品文件
var devMiddleware = require('webpack-dev-middleware')(compiler, {
publicPath: webpackConfig.output.publicPath,
quiet: true
}) // webpack-hot-middleware,用于实现热重载功能的中间件
var hotMiddleware = require('webpack-hot-middleware')(compiler, {
log: () => {}
}) // 当html-webpack-plugin提交之后通过热重载中间件发布重载动作使得页面重载
compiler.plugin('compilation', function (compilation) {
compilation.plugin('html-webpack-plugin-after-emit', function (data, cb) {
hotMiddleware.publish({ action: 'reload' })
cb()
})
}) // 将 proxyTable 中的代理请求配置挂在到express服务器上
Object.keys(proxyTable).forEach(function (context) {
var options = proxyTable[context]
// 格式化options,例如将'www.example.com'变成{ target: 'www.example.com' }
if (typeof options === 'string') {
options = { target: options }
}
app.use(proxyMiddleware(options.filter || context, options))
}) // handle fallback for HTML5 history API
// 重定向不存在的URL,常用于SPA
app.use(require('connect-history-api-fallback')()) // serve webpack bundle output
// 使用webpack开发中间件
// 即将webpack编译后输出到内存中的文件资源挂到express服务器上
app.use(devMiddleware) // enable hot-reload and state-preserving
// compilation error display
// 将热重载中间件挂在到express服务器上
app.use(hotMiddleware) // serve pure static assets
// 静态资源的路径
var staticPath = path.posix.join(config.dev.assetsPublicPath, config.dev.assetsSubDirectory) // 将静态资源挂到express服务器上
app.use(staticPath, express.static('./static')) // 应用的地址信息,例如:http://localhost:8080
var uri = 'http://localhost:' + port // webpack开发中间件合法(valid)之后输出提示语到控制台,表明服务器已启动
devMiddleware.waitUntilValid(function () {
console.log('> Listening at ' + uri + '\n')
}) // 启动express服务器并监听相应的端口(8080)
module.exports = app.listen(port, function (err) {
if (err) {
console.log(err)
return
} // when env is testing, don't need open it
// 如果符合自动打开浏览器的条件,则通过opn插件调用系统默认浏览器打开对应的地址uri
if (autoOpenBrowser && process.env.NODE_ENV !== 'testing') {
opn(uri)
}
})

2、build/webpack.base.conf.js

从代码中看到,dev-server使用的webpack配置来自build/webpack.dev.conf.js文件(测试环境下使用的是build/webpack.prod.conf.js,这里暂时不考虑测试环境)。而build/webpack.dev.conf.js中又引用了webpack.base.conf.js,所以这里先看webpack.base.conf.js。

webpack.base.conf.js主要完成下面的操作:

 配置webpack编译入口
配置webpack输出路径和命名规则
配置模块resolve规则
配置不同类型模块的处理规则

代码配置注释:

 var path = require('path')
var utils = require('./utils')
var config = require('../config')
var vueLoaderConfig = require('./vue-loader.conf') // 给出正确的绝对路径
function resolve (dir) {
return path.join(__dirname, '..', dir)
} module.exports = {
// 配置webpack编译入口
entry: {
app: './src/main.js'
}, // 配置webpack输出路径和命名规则
output: {
// webpack输出的目标文件夹路径(例如:/dist)
path: config.build.assetsRoot,
// webpack输出bundle文件命名格式
filename: '[name].js',
// webpack编译输出的发布路径
publicPath: process.env.NODE_ENV === 'production'
? config.build.assetsPublicPath
: config.dev.assetsPublicPath
}, // 配置模块resolve的规则
resolve: {
// 自动resolve的扩展名
extensions: ['.js', '.vue', '.json'],
// resolve模块的时候要搜索的文件夹
modules: [
resolve('src'),
resolve('node_modules')
],
// 创建路径别名,有了别名之后引用模块更方便,例如
// import Vue from 'vue/dist/vue.common.js'可以写成 import Vue from 'vue'
alias: {
'vue$': 'vue/dist/vue.common.js',
'src': resolve('src'),
'assets': resolve('src/assets'),
'components': resolve('src/components')
}
}, // 配置不同类型模块的处理规则
module: {
rules: [
{// 对src和test文件夹下的.js和.vue文件使用eslint-loader
test: /\.(js|vue)$/,
loader: 'eslint-loader',
enforce: "pre",
include: [resolve('src'), resolve('test')],
options: {
formatter: require('eslint-friendly-formatter')
}
},
{// 对所有.vue文件使用vue-loader
test: /\.vue$/,
loader: 'vue-loader',
options: vueLoaderConfig
},
{// 对src和test文件夹下的.js文件使用babel-loader
test: /\.js$/,
loader: 'babel-loader',
include: [resolve('src'), resolve('test')]
},
{// 对图片资源文件使用url-loader,query.name指明了输出的命名规则
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
loader: 'url-loader',
query: {
limit: 10000,
name: utils.assetsPath('img/[name].[hash:7].[ext]')
}
},
{// 对字体资源文件使用url-loader,query.name指明了输出的命名规则
test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
loader: 'url-loader',
query: {
limit: 10000,
name: utils.assetsPath('fonts/[name].[hash:7].[ext]')
}
}
]
}
}

3、build/webpack.dev.conf.js

接下来看webpack.dev.conf.js,这里面在webpack.base.conf的基础上增加完善了开发环境下面的配置,主要完成下面操作:

 将hot-reload相关的代码添加到entry chunks
合并基础的webpack配置
使用styleLoaders
配置Source Maps
配置webpack插件

代码配置注释:

 var utils = require('./utils')
var webpack = require('webpack')
var config = require('../config') // 一个可以合并数组和对象的插件
var merge = require('webpack-merge')
var baseWebpackConfig = require('./webpack.base.conf') // 一个用于生成HTML文件并自动注入依赖文件(link/script)的webpack插件
var HtmlWebpackPlugin = require('html-webpack-plugin') // 用于更友好地输出webpack的警告、错误等信息
var FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin') // add hot-reload related code to entry chunks
Object.keys(baseWebpackConfig.entry).forEach(function (name) {
baseWebpackConfig.entry[name] = ['./build/dev-client'].concat(baseWebpackConfig.entry[name])
}) // 合并基础的webpack配置
module.exports = merge(baseWebpackConfig, {
// 配置样式文件的处理规则,使用styleLoaders
module: {
rules: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap })
}, // 配置Source Maps。在开发中使用cheap-module-eval-source-map更快
devtool: '#cheap-module-eval-source-map', // 配置webpack插件
plugins: [
new webpack.DefinePlugin({
'process.env': config.dev.env
}),
// https://github.com/glenjamin/webpack-hot-middleware#installation--usage
new webpack.HotModuleReplacementPlugin(),
// 后页面中的报错不会阻塞,但是会在编译结束后报错
new webpack.NoEmitOnErrorsPlugin(),
// https://github.com/ampedandwired/html-webpack-plugin
new HtmlWebpackPlugin({
filename: 'index.html',
template: 'index.html',
inject: true
}),
new FriendlyErrorsPlugin()
]
})

4、build/utils.js

utils主要完成下面3个操作:

 配置静态资源路径
生成cssLoaders用于加载.vue文件中的样式
生成styleLoaders用于加载不在.vue文件中的单独存在的样式文件

5、build/vue-loader.conf.js

vue-loader.conf则只配置了css加载器以及编译css之后自动添加前缀。

代码配置注释:

var utils = require('./utils')
var config = require('../config')
var isProduction = process.env.NODE_ENV === 'production' module.exports = {
// css加载器
loaders: utils.cssLoaders({
sourceMap: isProduction
? config.build.productionSourceMap
: config.dev.cssSourceMap,
extract: isProduction
}), // 让 vue-loader 知道需要对 audio 的 src 属性的内容转换为模块
transformToRequire: {
video: ‘src‘,
source: ‘src‘,
img: ‘src‘,
image: ‘xlink:href‘
}
}

6、build/build.js

build.js主要完成下面操作:

 loading动画
删除创建目标文件夹
webpack编译
输出信息

Ps: webpack编译之后会输出到配置里面指定的目标文件夹;删除目标文件夹之后再创建是为了去除旧的内容,以免产生不可预测的影响。

代码配置注释:

 // 检查NodeJS和npm的版本
require('./check-versions')() process.env.NODE_ENV = 'production' //ora插件,实现node.js 命令行环境的 loading效果, 和显示各种状态的图标等
var ora = require('ora')
var rm = require('rimraf')
var path = require('path') // 用于在控制台输出带颜色字体的插件
var chalk = require('chalk') var webpack = require('webpack')
var config = require('../config')
var webpackConfig = require('./webpack.prod.conf') var spinner = ora('building for production...')
spinner.start() //rimraf插件,每次启动编译或者打包之前,先把整个dist文件夹删除,然后再重新生成dist
rm(path.join(config.build.assetsRoot, config.build.assetsSubDirectory), err => {
if (err) throw err
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'
))
})
})

7、build/webpack.prod.conf.js

构建的时候用到的webpack配置来自webpack.prod.conf.js,该配置同样是在webpack.base.conf基础上的进一步完善。主要完成下面操作:

 合并基础的webpack配置
使用styleLoaders
配置webpack的输出
配置webpack插件
gzip模式下的webpack插件配置
webpack-bundle分析

代码配置注释:

 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 HtmlWebpackPlugin = require('html-webpack-plugin') // 用于从webpack生成的bundle中提取文本到特定文件中的插件
// 可以抽取出css,js文件将其与webpack输出的bundle分离
var ExtractTextPlugin = require('extract-text-webpack-plugin') var env = process.env.NODE_ENV === 'testing'
? require('../config/test.env')
: config.build.env // 合并基础的webpack配置
var webpackConfig = merge(baseWebpackConfig, {
module: {
rules: utils.styleLoaders({
sourceMap: config.build.productionSourceMap,
extract: true
})
},
devtool: config.build.productionSourceMap ? '#source-map' : false,
// 配置webpack的输出
output: {
// 编译输出目录
path: config.build.assetsRoot,
// 编译输出文件名格式
filename: utils.assetsPath('js/[name].[chunkhash].js'),
// 没有指定输出名的文件输出的文件名格式
chunkFilename: utils.assetsPath('js/[id].[chunkhash].js')
}, // 配置webpack插件
plugins: [
// http://vuejs.github.io/vue-loader/en/workflow/production.html
new webpack.DefinePlugin({
'process.env': env
}), // 丑化压缩代码
new webpack.optimize.UglifyJsPlugin({
compress: {
warnings: false
},
sourceMap: true
}), // 抽离css文件
new ExtractTextPlugin({
filename: utils.assetsPath('css/[name].[contenthash].css')
}), // 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
new HtmlWebpackPlugin({
filename: process.env.NODE_ENV === 'testing'
? 'index.html'
: config.build.index,
template: 'index.html',
inject: true,
minify: {
removeComments: true,
collapseWhitespace: true,
removeAttributeQuotes: true
// more options:
// https://github.com/kangax/html-minifier#options-quick-reference
},
// necessary to consistently work with multiple chunks via CommonsChunkPlugin
chunksSortMode: 'dependency'
}), // split vendor js into its own file
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor',
minChunks: function (module, count) {
// 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
)
}
}),
// extract webpack runtime and module manifest to its own file in order to
// prevent vendor hash from being updated whenever app bundle is updated
new webpack.optimize.CommonsChunkPlugin({
name: 'manifest',
chunks: ['vendor']
})
]
}) // gzip模式下需要引入compression插件进行压缩
if (config.build.productionGzip) {
var CompressionWebpackPlugin = require('compression-webpack-plugin')
webpackConfig.plugins.push(
new CompressionWebpackPlugin({
asset: '[path].gz[query]',
algorithm: 'gzip',
test: new RegExp(
'\\.(' +
config.build.productionGzipExtensions.join('|') +
')$'
),
threshold: 10240,
minRatio: 0.8
})
)
} if (config.build.bundleAnalyzerReport) {
var BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
webpackConfig.plugins.push(new BundleAnalyzerPlugin())
} module.exports = webpackConfig

8、build/check-versions.js

check-version.js完成对node和npm的版本检测。

代码配置注释:

 // 用于在控制台输出带颜色字体的插件
var chalk = require('chalk') // 语义化版本检查插件(The semantic version parser used by npm)
var semver = require('semver') // 引入package.json
var packageConfig = require('../package.json') // 开辟子进程执行指令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
},
{
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)
}
}

三、config文件夹

1、config/index.js

config文件夹下最主要的文件就是index.js了,在这里面描述了开发和构建两种环境下的配置,前面的build文件夹下也有不少文件引用了index.js里面的配置。

代码配置注释:

 // see http://vuejs-templates.github.io/webpack for documentation.
var path = require('path') module.exports = {
// 构建产品时使用的配置
build: {
// webpack的编译环境
env: require('./prod.env'),
// 编译输入的index.html文件
index: path.resolve(__dirname, '../dist/index.html'),
// webpack输出的目标文件夹路径
assetsRoot: path.resolve(__dirname, '../dist'),
// webpack编译输出的二级文件夹
assetsSubDirectory: 'static',
// webpack编译输出的发布路径
assetsPublicPath: '/',
// 使用SourceMap
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
// 默认不打开开启gzip模式
productionGzip: false,
// gzip模式下需要压缩的文件的扩展名
productionGzipExtensions: ['js', 'css'],
// 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
},
// 开发过程中使用的配置
dev: {
// webpack的编译环境
env: require('./dev.env'),
// dev-server监听的端口
port: 8080,
// 启动dev-server之后自动打开浏览器
autoOpenBrowser: true,
// webpack编译输出的二级文件夹
assetsSubDirectory: 'static',
// webpack编译输出的发布路径
assetsPublicPath: '/',
// 请求代理表,在这里可以配置特定的请求代理到对应的API接口
// 例如将'/api/xxx'代理到'www.example.com/api/xxx'
proxyTable: {},
// 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
cssSourceMap: false
}
}

Ps:config/dev.env.js、config/prod.env.js,这两个个文件就简单设置了环境变量而已,没什么特别。