做为一名使用了一段时间Vue.js的新手,相信和很多初入Vue的朋友同样,都对Vue-cli的配置只知其一;不知其二。后来经过对webpack的学习,也算是对脚手架的配置有了必定的了解,因此也想把这段时间本身的成果分享给你们,但愿能和你们一块儿进步。
有两点要说明的:javascript
先放一张本身整理的简易脑图:css
Vue-cli有两个文件——build和config:build文件包含了脚手架在开发环境和生产环境下webpack该如何配置。config文件则包含了build文件下webpack具体配置的值。换句话说,build下的webpack配置的值要引入config后才能获取到。html
config文件夹下一共有三个文件:vue
build文件夹下一共有七个文件:java
1.prod.env.js:node
//导出一个对象,对象有一个当前node环境的属性,值为“production”(生产环境)
module.exports = { NODE_ENV: '"production"'}复制代码
2.dev.env.js:
webpack
//导出另外一个对象,属性为当前的node环境,值为“development”(开发环境)
const merge = require('webpack-merge')const prodEnv = require('./prod.env')
module.exports = merge(prodEnv, { NODE_ENV: '"development"'})
复制代码
关于更多webpack-merge请点击www.npmjs.com/package/web…es6
3.index.js:web
index.js做为具体的配置值,我以为不必把代码贴出来了,你们能够拿上面的的脑图或者本身项目里的文件来结合我后面要说的代码来看。vue-cli
1.check.versions.js:
//chalk 是一个用来在命令行输出不一样颜色文字的包,可使用chalk.yellow("想添加颜色的文字....")
//来实现改变文字颜色的;
const chalk = require('chalk')
//semver 的是一个语义化版本文件的npm包,其实它就是用来控制版本的;
const semver = require('semver')const packageConfig = require('../package.json')
//一个用来执行unix命令的包
const shell = require('shelljs')
//child_process 是Node.js提供了衍生子进程功能的模块,execSync()方法同步执行一个cmd命令,
//将返回值的调用toString和trim方法
function exec (cmd) {
return require('child_process').execSync(cmd).toString().trim()
}
const versionRequirements = [
{ name: 'node',
//semver.clean()方法返回一个标准的版本号,切去掉两边的空格,好比semver.clean(" =v1.2.3 ")
//返回"1.2.3",此外semver还有vaild,satisfies,gt,lt等方法,
//这里查看https://npm.taobao.org/package/semver能够看到更多关于semver方法的内容
currentVersion: semver.clean(process.version),
versionRequirement: packageConfig.engines.node
}
]
//shell.which方法是去环境变量搜索有没有参数这个命令
if (shell.which('npm')) {
versionRequirements.push({
name: 'npm',
//执行"npm --version"命令
currentVersion: exec('npm --version'),
versionRequirement: packageConfig.engines.npm
}
)}
//后面这部分代码就比较好理解了
module.exports = function () { const warnings = []
for (let i = 0; i < versionRequirements.length; i++) { const 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 (let i = 0; i < warnings.length; i++) { const warning = warnings[i] console.log(' ' + warning) }
console.log() process.exit(1) }}复制代码
2.utils.js:
const path = require('path')const config = require('../config')
//这个plugin的做用是将打包后生成的css文件经过link的方式引入到html中,若是不适用这个插件css代码会
//放到head标签的style中
const ExtractTextPlugin = require('extract-text-webpack-plugin')
const packageConfig = require('../package.json')
//process.env.NODE_ENV是一个环境变量,它是由webpack.dev/prod.conf.js这两个文件声明的;
//这里的意思是判断当前是不是开发环境,若是是就把config下index.js文件中build.assetsSubDirectory或
//dev.assetsSubDirectory的值赋给assetsSubDirectory
exports.assetsPath = function (_path) {
const assetsSubDirectory = process.env.NODE_ENV === 'production'
? config.build.assetsSubDirectory
: config.dev.assetsSubDirectory
//path.posix.join是path.join的一种兼容性写法,它的做用是路径的拼接,这里返回的是"static/_path"
return path.posix.join(assetsSubDirectory, _path
)}
//cssLoaders的做用是导出一个供vue-loader的options使用的一个配置;
exports.cssLoaders = function (options) {
options = options || {}
const cssLoader = {
loader: 'css-loader',
options: {
sourceMap: options.sourceMap
}
}
const postcssLoader = {
loader: 'postcss-loader',
options: {
sourceMap: options.sourceMap
}
}
function generateLoaders (loader, loaderOptions) {
const loaders = options.usePostCSS ? [cssLoader, postcssLoader] : [cssLoader]
if (loader) {
loaders.push({
loader: loader + '-loader',
options: Object.assign({}, loaderOptions, {
sourceMap: options.sourceMap
})
})
}
if (options.extract) {
return ExtractTextPlugin.extract({
use: loaders,
fallback: 'vue-style-loader'
})
} else {
return ['vue-style-loader'].concat(loaders)
}
}
return {
css: generateLoaders(),
postcss: generateLoaders(),
less: generateLoaders('less'),
sass: generateLoaders('sass', { indentedSyntax: true }),
scss: generateLoaders('sass'),
stylus: generateLoaders('stylus'),
styl: generateLoaders('stylus')
}
}
// styleLoaders是用来给webpack提供全部和css相关的loader的配置,它也使用了cssLoaders()方法;
exports.styleLoaders = function (options) {
const output = [] const loaders = exports.cssLoaders(options)
for (const extension in loaders) {
const loader = loaders[extension]
output.push({
test: new RegExp('\\.' + extension + '$'),
use: loader
})
}
return output
}
//'node-notifier'是一个跨平台系统通知的页面,当遇到错误时,它能用系统原生的推送方式给你推送信息
exports.createNotifierCallback = () => {
const notifier = require('node-notifier')
return (severity, errors) => {
if (severity !== 'error') return
const error = errors[0]
const filename = error.file && error.file.split('!').pop()
notifier.notify({
title: packageConfig.name,
message: severity + ': ' + error.name,
subtitle: filename || '',
icon: path.join(__dirname, 'logo.png')
})
}
}
复制代码
这里可能有的朋友不了解cssLoaders()和styleLoaders()这两个方法返回的是个什么东西,我在这里简单的写一下:
ExtractTextPlugin.extract({
use: ["css-loader","less-loader","sass-loader"...],
fallback: 'vue-style-loader'
})
复制代码
这些css代码打包以link的方式放到HTML中。固然了,use的值确切的说应该是这样:
[ { loader: 'css-loader', options: { sourceMap: true } }, { loader: 'less-loader', options: { sourceMap: true } } ]
我为了方便看就简写了。
而在开发模式下,cssLoaders返回的是:
["vue-style-loader","css-loader","less-loader","sass-loader"...] //我仍是简写了
[
{
test: /\.css$/,
use: [ 'style-loader', 'css-loader' ]
},
...
]
复制代码
3.vue-loader.conf.js:
const utils = require('./utils')
const config = require('../config')
//不一样环境为isProduction 赋值: 生产环境为true,开发环境为false
const isProduction = process.env.NODE_ENV === 'production'
//不一样环境为sourceMapEnabled 赋值: 这里都为true
const sourceMapEnabled = isProduction
? config.build.productionSourceMap
: config.dev.cssSourceMap
//导出vue-loader的配置,这里咱们用了utils文件中的cssLoaders();
module.exports = {
loaders: utils.cssLoaders({
sourceMap: sourceMapEnabled,
extract: isProduction
}),
cssSourceMap: sourceMapEnabled,
cacheBusting: config.dev.cacheBusting,
//transformToRequire的做用是在模板编译的过程当中,编译器能够将某些属性,如src转换为require调用;
transformToRequire: {
video: ['src', 'poster'],
source: 'src',
img: 'src',
image: 'xlink:href'
}
}
复制代码
4.webpack.base.conf.js
const path = require('path')
const utils = require('./utils')
const config = require('../config')
const vueLoaderConfig = require('./vue-loader.conf')
//resolve这个函数返回的是当前目录下"../dir"这个文件夹,__dirname指的是当前文件所在路径
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
},
resolve: {
//自动解析扩展,好比引入对应的文件,js,vue,json的后缀名就能够省略了
extensions: ['.js', '.vue', '.json'],
alias: {
//精准匹配,使用vue来替代vue/dist/vue.esm.js
'vue$': 'vue/dist/vue.esm.js',
//使用@替代src路径,当你引入src下的文件是可使用import XXfrom "@/xx"
'@': resolve('src'),
}
},
//一些loader配置,避免篇幅过长我省略一部分,你们能够看本身的文件
module: {
rules: [
{
test: /\.vue$/,
loader: 'vue-loader',
options: vueLoaderConfig
},
{
test: /\.js$/,
loader: 'babel-loader',
include: [resolve('src'), resolve('test'), resolve('node_modules/webpack-dev-server/client')]
},
......
]
},
//node里的这些选项是都是Node.js全局变量和模块,这里主要是防止webpack注入一些Node.js的东西到vue中
node: {
setImmediate: false,
dgram: 'empty',
fs: 'empty',
net: 'empty',
tls: 'empty',
child_process: 'empty'
}
}复制代码
5.webpack.dev.conf.js
const utils = require('./utils')
const webpack = require('webpack')
const config = require('../config')
const merge = require('webpack-merge')
const path = require('path')
const baseWebpackConfig = require('./webpack.base.conf')
//一个负责拷贝资源的插件
const CopyWebpackPlugin = require('copy-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')
//一个更友好的展现webpack错误提示的插件
const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin')
//一个自动检索端口的包
const portfinder = require('portfinder')
const HOST = process.env.HOSTconst PORT = process.env.PORT && Number(process.env.PORT)
const devWebpackConfig = merge(baseWebpackConfig, {
module: {
rules: utils.styleLoaders({
sourceMap: config.dev.cssSourceMap,
usePostCSS: true
})
},
devtool: config.dev.devtool,
// devServer的配置你们看文档就行了
devServer: {
clientLogLevel: 'warning',
historyApiFallback: {
rewrites: [
{ from: /.*/, to: path.posix.join(config.dev.assetsPublicPath, 'index.html') },
],
},
hot: true,
contentBase: false,
compress: true,
host: HOST || config.dev.host,
port: PORT || config.dev.port,
open: config.dev.autoOpenBrowser,
overlay: config.dev.errorOverlay
? { warnings: false, errors: true }
: false,
publicPath: config.dev.assetsPublicPath,
proxy: config.dev.proxyTable,
quiet: true,
watchOptions: {
poll: config.dev.poll,
}
},
plugins: [
//还记得以前说的生产环境和开发环境的变量在哪儿定义的吗?对,就是这里
new webpack.DefinePlugin({
process.env: require('../config/dev.env')
}),
//模块热替换的插件,修改模块不须要刷新页面
new webpack.HotModuleReplacementPlugin(),
//当使用HotModuleReplacementPlugin时,这个插件会显示模块正确的相对路径
new webpack.NamedModulesPlugin(),
//在编译出错时,使用NoEmitOnErrorsPlugin来跳过输出阶段,这样能够确保输出资源不会包含错误
new webpack.NoEmitOnErrorsPlugin(),
new HtmlWebpackPlugin({
filename: 'index.html',
template: 'index.html',
inject: true
}),
// 将static文件夹和里面的内容拷贝到开发模式下的路径,好比static下有个img文件夹,里面有张图片
// 咱们能够这样访问:localhost:8080/static/img/logo.png
new CopyWebpackPlugin([
{
from: path.resolve(__dirname, '../static'),
to: config.dev.assetsSubDirectory,
ignore: ['.*']
}
])
]
})
//这里主要是作端口的检索以及npm run dev后对错误的处理,咱们能够看这里使用了前面引入的
//'friendly-errors-webpack-plugin'插件
module.exports = new Promise((resolve, reject) => {
portfinder.basePort = process.env.PORT || config.dev.port
portfinder.getPort((err, port) => {
if (err) {
reject(err)
} else {
// publish the new Port, necessary for e2e tests
process.env.PORT = port
// add port to devServer config
devWebpackConfig.devServer.port = port
// Add FriendlyErrorsPlugin
devWebpackConfig.plugins.push(new FriendlyErrorsPlugin({
compilationSuccessInfo: {
messages: [`Your application is running here: http://${devWebpackConfig.devServer.host}:${port}`],
},
onErrors: config.dev.notifyOnErrors
? utils.createNotifierCallback()
: undefined
}))
resolve(devWebpackConfig)
}
})
})复制代码
关于devServer有两点要说明一下:
6.webpack.prod.conf.js
通过前面这么多代码的分析,其实webpack.prod.conf.js的配置已经很简单了,大体跟webpack.dev.conf.js的配置方式差很少,就是多了几个plugins:
好了,plugins的介绍到此结束,接下来就是最后一个文件,也是npm run build编译时的入口文件——build.js了。
一样的,build.js文件其实也没什么可说的了,无非就是执行webpack.prod.conf.js文件,遇到错误时在命令行提示。须要注意的是,build.js里引入了“rimraf”的包,它的做用是每次编译时清空dist文件,避免屡次编译时形成文件夹的重复和混乱。
到这里其实关于Vue-cli配置的分析基本结束了,相信了解webpack的朋友看起来必定很是简单,配置主要麻烦的地方在于低耦合致使常常须要来回翻文件才能看懂配置,若是你们结合着文章开头的脑图看可能会相对容易些。
一个坏消息是这个文章发布的时候webpack4.0已经上线了,Vue-cli新版也进入了Beta测试阶段,因此这篇文章你们看看就好,了解一下思路,立刻配置又会更新的......