Vue-cli脚手架中webpack配置基础文件详解(逐个击破,干货满满!!!)

788 阅读15分钟

Vue-cli脚手架中webpack配置基础文件详解

前提:基于vue-cli2.9.6、webpack3.6.0、vue2.5.2、vue单页面应用、"node": ">= 6.0.0"、"npm": ">= 3.0.0"

webpack文档

==创建项目==

要求:

1、已安装node:nodejs.org/en/download…

​ 根据需要安装相应的版本(开始→运行→cmd→运行命令node -v即可查看安装的node版本号)

​ 会自动安装npm(开始→运行→cmd→运行命令npm -v即可查看安装的npm版本号)

2、安装脚手架

npm install --g vue-cli
vue -V //查看脚手架版本号

3、构建项目(会出现一下弹窗选项)

vue init webpack xxx //(xxx为你的项目名称,此处为mypro)

1647916493416.png

==项目整体结构==

├─build                 ---------打包配置文件(下面会详细介绍)
├─config                ---------环境变量的一些配置(下面会详细介绍)
├─dist                  ---------打包后生成的文件夹
├─mock                  ---------mock数据用(自己建的)
├─node_modules          ---------根据package.json安装时生成的的依赖包
├─src                   ---------页面文件夹(开发代码目录)
│ ├─api                 ---------axios请求接口(自己建的)
│ ├─assets              ---------存放静态资源文件夹
│ ├─components          ---------vue中公用组件文件夹
│ ├─lang                ---------设置多语言国际化(自己建的)
│ ├─router              ---------路由跳转文件夹
│ ├─store               ---------vuex状态管理工具(自己建的)
│ ├─style               ---------样式或共有样式文件夹,还可放字体图标等文件(自己建的)
│ ├─utils               ---------vue中的一些工具函数(自己建的)
│ ├─App.vue             ---------主组件,路由出口,通过使用<router-view/>
│ ├─main.js             ---------打包入口js文件
├─static                ---------静态资源
├─.babelrc              ---------es6解析的一个配置
├─.editorconfig         ---------帮助不同的编译器或多个开发人员维护一致编码风格
├─.gitignore            ---------git自动忽略的文件或目录
├─.postcssrc.js         ---------postcss-loader包的一个配置
├─favicon.ico           ---------图标
├─index.html            ---------页面入口,经过编译之后的代码将插入到这来
├─package-lock.json     ---------不长更新的依赖,需上传到git(保持大家一致)
├─package.json          ---------安装依赖和依赖版本号
└─README.md             ---------填写项目介绍等

一、build文件夹(打包配置文件)

1.文件结构

├─build 
│ ├─build.js 
│ ├─check-versions.js 
│ ├─logo.png
│ ├─utils.js 
│ ├─vue-loader.conf.js 
│ ├─webpack.base.conf.js 
│ ├─webpack.dev.conf.js 
│ ├─webpack.prod.conf.js 

2.js文件详解

① build.js(生产环境打包时用)

ora传送

chalk传送

rimraf传送

'use strict'
// 引入检查版本文件  
// package.json文件中版本有要求"engines": {"node": ">= 6.0.0","npm": ">= 3.0.0"},加()代表直接调用该函数
require('./check-versions')()
// process是node中的global全局对象的属性,env设置环境变量(当前是生成环境)
process.env.NODE_ENV = 'production'
// ora模块。作用:一个命令行转圈圈动画插件,好看用的(加载动画)
const ora = require('ora')
// rimraf模块。作用:执行UNIX命令rm和-rf的用来删除文件夹和文件
const rm = require('rimraf')
// node.js路径模块。作用:拼接路径,例子:path.join('/foo', 'bar', 'baz/asdf', 'quux', '..');// 返回: '/foo/bar/baz/asdf'
const path = require('path')
// chalk插件。作用:在控制台中输出不同的颜色的字,用法:chalk.blue('Hello world'),只能改变命令行中的字体颜色
const chalk = require('chalk')
// webpack模块。作用:使用内置插件和webpack方法
const webpack = require('webpack')
// 引入config目录中的index.js配置文件(默认)
const config = require('../config')
// 引入webpack生产环境的核心配置文件
const webpackConfig = require('./webpack.prod.conf')
// 开启转圈圈动画:调用start的方法实现加载动画,优化用户体验
const spinner = ora('building for production...')
spinner.start()

// 调用rm方法,删除dist文件再生成新文件,因为有时候会使用hash来命名,删除整个文件可避免冗余
rm(path.join(config.build.assetsRoot, config.build.assetsSubDirectory), err => {
    // 如果删除的过程中出现错误,停止转圈,就抛出这个错误,同时程序终止
    if (err) throw err
    // 没有错误,就执行webpack编译
    webpack(webpackConfig, (err, stats) => {
    	// 这个回调函数是webpack编译过程中执行
    	spinner.stop()// 停止转圈圈动画
        if (err) throw err // 如果有错误就抛出错误
        // 没有错误就执行下面的代码,process.stdout.write和console.log类似,输出对象
        process.stdout.write(stats.toString({
        	// stats对象中保存着编译过程中的各种消息
      		colors: true,// 增加控制台颜色开关
      		modules: false,// 不增加内置模块信息
      		children: false, // 不增加子级信息
      		chunks: false,// 允许较少的输出
      		chunkModules: false // 不将内置模块的信息加到包信息
    	}) + '\n\n')
    	// 上面是在编译过程中,持续打印消息 
    	// 下面是编译成功的终端输入的消息
    	if (stats.hasErrors()) {
      		console.log(chalk.red('Build failed with errors.\n'))
      		process.exit(1)
    	}
    	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'
    	))
  })
})

② check-versions.js(用来检测node和npm版本)-------只打包时被build.js使用

semver传送

shelljs传送

'use strict'
// chalk插件。作用:在控制台中输出不同的颜色的字,用法:chalk.blue('Hello world'),只能改变命令行中的字体颜色
const chalk = require('chalk')
// semver插件。作用:对版本进行处理,例
// semver.gt('1.2.3', '9.8.7') // false 1.2.3版本比9.8.7版本低
// semver.satisfies('1.2.3', '1.x || >=2.5.0 || 5.0.0 - 7.2.3') // true 1.2.3的版本符合后面的规则
const semver = require('semver')
// 引入package.json文件,需使用查看engines项,注意require是直接可以导入json文件的,并且requrie返回的就是json对象
const packageConfig = require('../package.json')
// shelljs插件。作用:用来执行Unix系统命令
const shell = require('shelljs')

//函数exec作用:把cmd这个参数传递的值转化成前后没有空格的字符串,也就是版本号
function exec(cmd) {
  return require('child_process').execSync(cmd).toString().trim()
}

// 存node版本信息的一个数组
const versionRequirements = [
  {
    name: 'node',
    currentVersion: semver.clean(process.version),// 使用semver插件把版本信息转化成规定格式,例: '=v1.2.3 ' ---> '1.2.3' 这种功能
    versionRequirement: packageConfig.engines.node//pakage.json中engines选项的node版本信息 "node": ">= 6.0.0"
  }
]
//同node相同格式的npm版本信息,添加到versionRequirements这个数组中
if (shell.which('npm')) {
    versionRequirements.push({
    name: 'npm',
    currentVersion: exec('npm --version'),// 自动调用npm --version命令,并且把参数返回给exec函数,从而获取纯净的版本号
    versionRequirement: packageConfig.engines.npm//pakage.json中engines选项的npm版本信息 "npm": ">= 3.0.0"
  })
}

module.exports = function () {
  const warnings = []
  for (let i = 0; i < versionRequirements.length; i++) {
    const mod = versionRequirements[i]
    //判断版本号:不符合package.json文件中指定的版本号,就执行下面的所有代码
    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)
  }
}
③ utils.js(用来处理css的文件的工具)

vue-loader官网

extract-text-webpack-plugin传送

'use strict'
// node.js路径模块。作用:拼接路径,例子:path.join('/foo', 'bar', 'baz/asdf', 'quux', '..');// 返回: '/foo/bar/baz/asdf'
const path = require('path')
// 引入config目录中的index.js配置文件(默认)
const config = require('../config')
// 引入extract-text-webpack-plugin插件,用来将css提取到单独的css文件中
const ExtractTextPlugin = require('extract-text-webpack-plugin')
// 导入package.json文件,要使用里面的name选项,要注意require是直接可以导入json文件的,并且requrie返回的就是json对象
const packageConfig = require('../package.json')
// exports其实就是一个对象,用来导出方法的,最终还是使用module.exports,此处导出assetsPath
// 根据环境判断开发环境和生产环境:为config文件中index.js文件中定义的build.assetsSubDirectory或dev.assetsSubDirectory
exports.assetsPath = function (_path) {
    // 此处无论是生产还是开发都是'static'(哈哈....哈哈.....哈哈)
    const assetsSubDirectory = process.env.NODE_ENV === 'production'? config.build.assetsSubDirectory
    : config.dev.assetsSubDirectory
   // path.join和path.posix.join的区别就是,前者返回的是完整的路径,后者返回的是完整路径的相对根路径
   // 例:path.join的路径是C:xxx/mypro/build,那么path.posix.join就是build
   // 所以这个方法的作用就是返回一个干净的相对根路径
    return path.posix.join(assetsSubDirectory, _path)
}
// 下面是导出cssLoaders的相关配置
exports.cssLoaders = function (options) {
    // options是用来传递参数给loader的 
  	options = options || {}
	// cssLoader的基本配置
  	const cssLoader = {
    	loader: 'css-loader',
      	options: {
       	    // minimize表示压缩,如果是生产环境就压缩css代码
        	minimize: process.env.NODE_ENV === 'production',
            // 是否开启cssmap,默认是false
            sourceMap: options.sourceMap
        }
    }
    // 负责进一步处理 CSS 文件,比如添加浏览器前缀,压缩 CSS 等
    // 通过options.usePostCSS属性来判断是否使用postcssLoader中的压缩等方法
    const postcssLoader = {
    	loader: 'postcss-loader',
    	options: {
            sourceMap: options.sourceMap
    	}
  }

    function generateLoaders(loader, loaderOptions) {
    // 将上面的基础cssLoader配置放在数组loaders里面,根据options.usePostCSS,是否添加postcssLoader配置
    const loaders = options.usePostCSS ? [cssLoader, postcssLoader] : [cssLoader]
    // 如果该函数传递了单独的loader就加到这个loaders数组里面,这个loader可能是less,sass之类的
    if (loader) {
        // 加载对应的loader
        loaders.push({
            loader: loader + '-loader',
            //Object.assign是es6语法的浅拷贝(后两者合并后复制完成赋值,变成一个对象)通过loaderOptions形参可配置不同loader的额外属性
            options: Object.assign({}, loaderOptions, {
          	sourceMap: options.sourceMap
        })
      })
    }
    // 注意这个extract是自定义的属性,可调用时定义在options里面
    // 作用:当配置为true就把文件单独提取,false表示不单独提取(一般生产阶段true)
    if (options.extract) {
        //ExtractTextPlugin可提取出文本,代表首先使用上面处理的loaders,当未能正确引入时使用vue-style-loader
      	return ExtractTextPlugin.extract({
        	use: loaders,
        	fallback: 'vue-style-loader'
      		})
        } else {
            //返回vue-style-loader连接loaders的最终值
      		return ['vue-style-loader'].concat(loaders)
    }
  }
  // 上面这段代码就是用来返回最终读取和导入loader,来处理对应类型的文件
  return {
    css: generateLoaders(),// css对应 vue-style-loader 和 css-loader
    postcss: generateLoaders(),// postcss对应 vue-style-loader 和 css-loader(三者已通过cssLoaders初始化存在,只是postcss需要用户传递的属性判断是否启用)
    less: generateLoaders('less'),//less对应 vue-style-loader 和 less-loader
    sass: generateLoaders('sass', { indentedSyntax: true }),// sass对应 vue-style-loader 和 sass-loader
    scss: generateLoaders('sass'),//scss对应 vue-style-loader 和 sass-loader
    stylus: generateLoaders('stylus'),// stylus对应 vue-style-loader 和 stylus-loader
    styl: generateLoaders('stylus')// styl对应 vue-style-loader 和 styl-loader
  }
}

// 下面这个主要处理import这种方式导入的文件类型的打包,上面的exports.cssLoaders在这里被调用
// 将各种css,less,sass等综合在一起得出结果输出output
exports.styleLoaders = function (options) {
    const output = []
    // 下面就是生成的各种css文件的loader对象
  	const loaders = exports.cssLoaders(options)
    // 把每一种文件的laoder都提取出来,添加到outout数组中
  	for (const extension in loaders) {
    	const loader = loaders[extension]
        // 把最终的结果都push到output数组中
    	output.push({
      		test: new RegExp('\\.' + extension + '$'),
      		use: loader
    	})
  	}
  	return output
}
// 处理错误的
exports.createNotifierCallback = () => {
   // 支持使用node发送跨平台的本地通知
  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')
    })
  }
}

  • exports.cssLoaders的返回(前提options里配置了extract:true,sourceMap: true)
{
    css: ExtractTextPlugin.extract({
              use: [{
                loader: 'css-loader',
                options: {
                  sourceMap: true,
                  extract: true
                }
              }],
              fallback: 'vue-style-loader'
            }),
    postcss: ExtractTextPlugin.extract({
              use: [{
                loader: 'css-loader',
                options: {
                  sourceMap: true,
                  extract: true
                }
              }],
              fallback: 'vue-style-loader'
            }),
    less: ExtractTextPlugin.extract({
              use: [
                      {
                        loader: 'css-loader',
                        options: {
                          sourceMap: true,
                          extract: true
                        }
                      },
                      {
                        loader: 'less-loader',
                        options: {
                          sourceMap: true,
                          extract: true
                        }
                      }
                 ],
              fallback: 'vue-style-loader'
            }),
    sass: ExtractTextPlugin.extract({
              use: [
                      {
                        loader: 'css-loader',
                        options: {
                          sourceMap: true,
                          extract: true
                        }
                      },
                      {
                        loader: 'sass-loader',
                        options: {
                          sourceMap: true,
                          extract: true,
                          indentedSyntax: true
                        }
                      }
                 ],
              fallback: 'vue-style-loader'
            }),
   //剩下的同理
  }
  • exports.styleLoaders的返回
[
  {
      test: new RegExp('\\.css$'),
       use: ExtractTextPlugin.extract({
              use: [{
                loader: 'css-loader',
                options: {
                  sourceMap: true,
                  extract: true
                }
              }],
              fallback: 'vue-style-loader'
            })
  },
  {
      test: new RegExp('\\.postcss$'),
       use: ExtractTextPlugin.extract({
              use: [{
                loader: 'css-loader',
                options: {
                  sourceMap: true,
                  extract: true
                }
              }],
              fallback: 'vue-style-loader'
            })
  },
  {
      test: new RegExp('\\.less$'),
       use: ExtractTextPlugin.extract({
              use: [
                      {
                        loader: 'css-loader',
                        options: {
                          sourceMap: true,
                          extract: true
                        }
                      },
                      {
                        loader: 'less-loader',
                        options: {
                          sourceMap: true,
                          extract: true
                        }
                      }
                 ],
              fallback: 'vue-style-loader'
            })
  },
  {
      test: new RegExp('\\.sass$'),
       use: ExtractTextPlugin.extract({
              use: [
                      {
                        loader: 'css-loader',
                        options: {
                          sourceMap: true,
                          extract: true
                        }
                      },
                      {
                        loader: 'sass-loader',
                        options: {
                          sourceMap: true,
                          extract: true,
                          indentedSyntax: true
                        }
                      }
                 ],
              fallback: 'vue-style-loader'
            })
  },
  //剩下的略
]

==补充:==

  1. extract-text-webpack-plugin

​ 作用:将文本从bundle中提取到一个单独的文件中

官网配置

const ExtractTextPlugin = require("extract-text-webpack-plugin");
module.exports = {
	module: {
    	rules: [
         		{
          	 		test: /\.css$/, //主要用来处理css文件
            		use: ExtractTextPlugin.extract({
            		fallback: "style-loader", // fallback表示如果css文件没有成功导入就使用style-loader导入
            		use: "css-loader" // 表示使用css-loader从js读取css文件
             		})
           		}
           	],
    	plugins: [
           	new ExtractTextPlugin("styles.css") //表示生成styles.css文件
          ]
      }
}
④ vue-loader.conf.js(配合utils.js处理.vue文件)

解析这个文件中的每个语言块(template、script、style),转换成js可用的js模块

'use strict'
// 引入当前目录下的utils文件用来解决css相关loader
const utils = require('./utils')
// 引入config目录中的index.js配置文件(默认)
const config = require('../config')
// process是node中的global全局对象的属性,env设置环境变量(当前是生成环境)
const isProduction = process.env.NODE_ENV === 'production'
// 是否开启源码映射(方便错误时调试)
const sourceMapEnabled = isProduction? config.build.productionSourceMap
  : config.dev.cssSourceMap
//处理项目中的css文件,生产环境和测试环境默认是打开sourceMap,而extract中的提取样式到单独文件只有在生产环境中才需要
module.exports = {
  loaders: utils.cssLoaders({
    sourceMap: sourceMapEnabled,
    extract: isProduction//设置为true表示生成单独样式文件(前面)
  }),
  cssSourceMap: sourceMapEnabled,
  cacheBusting: config.dev.cacheBusting,//开发环境下为true,//使soucemaps缓存失效
  // 在模版编译过程中,编译器可以将某些属性,如 src 路径,转换为require调用,以便目标资源可以由 webpack 处理.
  transformToRequire: {
    video: ['src', 'poster'],
    source: 'src',
    img: 'src',
    image: 'xlink:href'
  }
}
⑤ webpack.base.conf.js(开发和生产共同使用提取出的基础配置文件)

主要实现配制入口,配置输出环境,配置模块resolve和插件等

'use strict'
// node.js路径模块。作用:拼接路径,例子:path.join('/foo', 'bar', 'baz/asdf', 'quux', '..');// 返回: '/foo/bar/baz/asdf'
const path = require('path')
// 引入当前目录下的utils文件用来解决css相关loader
const utils = require('./utils')
// 引入config目录中的index.js配置文件(默认)
const config = require('../config')
// 引入vue-loader.conf配置文件是用来解决各种css文件的,定义了诸如css,less,sass之类的和样式有关的loader
const vueLoaderConfig = require('./vue-loader.conf')
// 此函数用来返回当前目录的平行目录的路径,因为有个'..'(拼接出绝对路径)
// 此处永远输出/dir
function resolve (dir) {
  return path.join(__dirname, '..', dir)
}

module.exports = {
	//path.join将路径片段进行拼接,而path.resolve将以/开始的路径片段作为根目录,在此之前的路径将会被丢弃
	//path.join('/a', '/b') // 'a/b',path.resolve('/a', '/b') // '/b'
 	context: path.resolve(__dirname, '../'),// 输出/
 	// 入口文件是src目录下的main.js
	entry: {
		app: './src/main.js'
	},
	output: {
		// 路径是config目录下的index.js中的build配置中的assetsRoot,也就是dist目录
    	path: config.build.assetsRoot,
    	// 文件名称这里使用默认的name也就是main
    	filename: '[name].js',
    	// 上线地址,也就是真正的文件引用路径,其实这里都是 '/'
    	publicPath: process.env.NODE_ENV === 'production'
      	? config.build.assetsPublicPath
      	: config.dev.assetsPublicPath
    	},
    	// resolve是webpack的内置选项,也就是说当使用 import "jquery",该如何去执行这件事情,就是resolve配置项要做的,import jQuery from "./additional/dist/js/jquery" 这样会很麻烦,可以起个别名简化操作
    	resolve: {
       		//自动的扩展后缀,比如一个js文件,则引用时书写可不要写.js
        	extensions: ['.js', '.vue', '.json'], 
        	//创建路径的别名,比如增加'components': resolve('src/components')等
        	//后面的$符号指精确匹配,也就是说只能使用 import vuejs from "vue" 这样的方式导入vue.esm.js文件,不能在后面跟上 vue/vue.js
        	alias: {
        	// 可自行添加
        	'vue$': 'vue/dist/vue.esm.js',
        	// resolve('src') 其实在这里就是项目根目录中的src目录(@=/src),使用 import somejs from "@/some.js"(some.js在src目录下面)
        	'@': resolve('src'),
    	}
    },
	// module用来解析不同的模块
    module: {
        rules: [
        // 对vue文件使用vue-loader,该loader是vue单文件组件的实现核心,专门用来解析.vue文件的
        {
          test: /\.vue$/,
          loader: 'vue-loader',
          // 将vueLoaderConfig当做参数传递给vue-loader,就可以解析文件中的css相关文件
          options: vueLoaderConfig
         },
        // 处理js文件
        {
          test: /\.js$/,
          // 对js文件使用babel-loader转码,该插件是用来解析es6等代码
          loader: 'babel-loader',
          // 指明src和test目录下的js文件要使用该loader
          include: [resolve('src'), resolve('test'), resolve('node_modules/webpack-dev-server/client')]
        },
       // 对图片相关的文件使用 url-loader 插件,这个插件的作用是将一个足够小的文件生成一个64位的DataURL
       // DataURL概念:当一个图片足够小,为了避免单独请求可以把图片的二进制代码变成64位的
       // DataURL,使用src加载,也就是把图片当成一串代码,避免请求
       {
          test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
          loader: 'url-loader',
          options: {
          // 限制 10000 个字节以下的图片才使用DataURL
          limit: 10000,
          // [name].[hash:7].[ext],保持之前图片的名字,去前7位hash值,保持原图片格式:banner.png---->banner.da519d42.png
          name: utils.assetsPath('img/[name].[hash:7].[ext]')// static/img/banner.da519d42.png
       }
      },
      //处理音频,和上面一样
      {
        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]')
        }
      }
    ]
    },
  //以下选项是Node.js全局变量或模块,这里主要是防止webpack注入一些Node.js的东西到vue中 
  node: {
    setImmediate: false,
    dgram: 'empty',
    fs: 'empty',
    net: 'empty',
    tls: 'empty',
    child_process: 'empty'
  }
}
⑥ webpack.dev.conf.js(webpack开发环境的核心配置文件)

friendly-errors-webpack-plugin传送

copy-webpack-plugin传送

html-webpack-plugin传送

HotModuleReplacementPlugin传送

friendly-errors-webpack-plugin传送

portfinder

process传送

'use strict'
// 引入utils文件用来解决css相关loader
const utils = require('./utils')
// webpack模块。作用:使用内置插件和webpack方法
const webpack = require('webpack')
// 引入config目录中的index.js配置文件(默认)
const config = require('../config')
// webpack-merge模块。作用:合并webpack配置对象,可以把webpack配置文件拆分成几个小的模块,然后合并(这里用来合并当前目录下的webpack.base.conf.js配置文件)
const merge = require('webpack-merge')
// node.js路径模块。作用:拼接路径,例子:path.join('/foo', 'bar', 'baz/asdf', 'quux', '..');// 返回: '/foo/bar/baz/asdf'
const path = require('path')
// 引入当前目录下的webpack.base.conf.js配置文件(打包的基础配置)
const baseWebpackConfig = require('./webpack.base.conf')
// copy-webpack-plugin模块。作用:复制文件或者文件夹到指定的目录
const CopyWebpackPlugin = require('copy-webpack-plugin')
// html-webpack-plugin模块。作用:自动生成html文件,把资源自动加载到html文件,引入了外部资源,可通过此项进行多页面的配置
const HtmlWebpackPlugin = require('html-webpack-plugin')
// friendly-errors-webpack-plugin模块。作用:美化webpack的错误信息和日志
const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin')
// portfinder模块。作用:查看空闲端,端口进程,默认情况下搜索8000这个端口
const portfinder = require('portfinder')
// processs为node的一个全局对象获取当前程序的环境变量
const HOST = process.env.HOST
const PORT = process.env.PORT && Number(process.env.PORT)
// 把当前开发配置对象和基础的配置对象合并
const devWebpackConfig = merge(baseWebpackConfig, {
    module: {
    	// 规则是工具utils中处理出来的styleLoaders,生成了css,less,postcss等规则
    	rules: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap, usePostCSS: true })
	},
	devtool: config.dev.devtool, // 用来指定如何生成sourcemap文件
    // webpack服务器配置
    devServer: {
        clientLogLevel: 'warning',// 控制台显示的选项有none, error, warning 或者 info
        // 当使用h5 history api时,任意的404响应都可能需要被替代为index.html,通过historyApiFallback:true控制;通过传入一个对象,比如使用rewrites这个选项进一步控制
        historyApiFallback: {
          rewrites: [
            { from: /.*/, to: path.posix.join(config.dev.assetsPublicPath, 'index.html') },
          ],
        },
    hot: true,// 自动刷新,针对开发环境,效果:界面无刷新更新
    contentBase: false, // 这里禁用了该功能。本来是告诉服务器从哪里提供内容,一般是本地静态资源
    compress: true,// 一切服务是否都启用gzip压缩
    host: HOST || config.dev.host,// 指定一个host,默认是localhost。如果有全局host就用全局,否则就用index.js中的设置。
    port: PORT || config.dev.port,// 指定端口
    open: config.dev.autoOpenBrowser,// 调试时自动打开浏览器
    overlay: config.dev.errorOverlay// 当有编译器错误时,是否在浏览器中显示全屏覆盖。
      ? { warnings: false, errors: true }
      : false,// warning 和 error 都要显示
    publicPath: config.dev.assetsPublicPath,// 此路径下的打包文件可在浏览器中访问
    proxy: config.dev.proxyTable,// 如果你有单独的后端开发服务器api,并且希望在同域名下发送api请求,那么代理某些URL会很有用。
    quiet: true, // 控制台是否禁止打印警告和错误,若用FriendlyErrorsPlugin 此处为 true
    watchOptions: {
    	poll: config.dev.poll,// 文件系统改动监测,是否使用轮询
    	}
  	},
    plugins: [
    	// DefinePlugin:webpack内置方法,专门用来定义全局变量的,下面定义一个全局变量 process.env 并且值是如下
    	new webpack.DefinePlugin({
      		'process.env': require('../config/dev.env')
    	}),
        // 模块热替换插件,修改模块时不需要刷新页面更新内容
    	new webpack.HotModuleReplacementPlugin(),
        // 显示文件的正确名字
    	new webpack.NamedModulesPlugin(), 
    	// webpack编译错误的时候,来中断打包进程,防止错误代码打包到文件中
    	new webpack.NoEmitOnErrorsPlugin(),
    	// 是用来生成html文件的,有很灵活的配置项,下面是基本的一些用法,或使用模板文件将编辑好的代码注入进去
    	new HtmlWebpackPlugin({
        filename: 'index.html',//生成的文件的名称
        template: 'index.html',//可以指定模块html文件
        inject: true// 将js文件放到body标签的结尾
    	}),
    	// 用来复制一个单独的文件或者整个目录到新建的文件夹下(复制自定义的静态资源)
    	new CopyWebpackPlugin([
      		{
        		from: path.resolve(__dirname, '../static'),
        		to: config.dev.assetsSubDirectory,
        		ignore: ['.*']//忽略.*的文件
      		}
    	])
  	]
})
// webpack将运行由配置文件导出的函数,并且等待promise返回,便于需要异步地加载所需的配置变量。
module.exports = new Promise((resolve, reject) => {
	//查找端口号
    portfinder.basePort = process.env.PORT || config.dev.port
    portfinder.getPort((err, port) => {
    	if (err) {
      		reject(err)
    	} else {
			//端口被占用时就重新设置evn和devServer的端口
        	process.env.PORT = port
        	devWebpackConfig.devServer.port = port
 			//友好地输出信息
      		devWebpackConfig.plugins.push(new FriendlyErrorsPlugin({//出错友好处理插件
        		compilationSuccessInfo: {//build成功的话会执行这块
          		messages: [`Your application is running here: http://${devWebpackConfig.devServer.host}:${port}`],
        		},
                // 如果出错就执行这块,其实是utils里面配置好的提示信息
        		onErrors: config.dev.notifyOnErrors
        		? utils.createNotifierCallback()
        		: undefined
      		}))
      		resolve(devWebpackConfig)
    	}
    })
})
⑦ webpack.prod.conf.js(webpack生产环境的核心配置文件)
'use strict'
// node.js路径模块。作用:拼接路径,例子:path.join('/foo', 'bar', 'baz/asdf', 'quux', '..');// 返回: '/foo/bar/baz/asdf'
const path = require('path')
// 引入utils文件用来解决css相关loader
const utils = require('./utils')
// webpack模块。作用:使用内置插件和webpack方法
const webpack = require('webpack')
// 引入config目录中的index.js配置文件(默认)
const config = require('../config')
// webpack-merge模块。作用:合并webpack配置对象,可以把webpack配置文件拆分成几个小的模块,然后合并(这里用来合并当前目录下的webpack.base.conf.js配置文件)
const merge = require('webpack-merge')
// 引入当前目录下的webpack.base.conf.js配置文件(打包的基础配置)
const baseWebpackConfig = require('./webpack.base.conf')
// copy-webpack-plugin模块。作用:复制文件或者文件夹到指定的目录
const CopyWebpackPlugin = require('copy-webpack-plugin')
// html-webpack-plugin模块。作用:自动生成html文件,把资源自动加载到html文件,引入了外部资源,可通过此项进行多页面的配置
const HtmlWebpackPlugin = require('html-webpack-plugin')
// 引入extract-text-webpack-plugin插件,用来将css提取到单独的css文件中
const ExtractTextPlugin = require('extract-text-webpack-plugin')
// optimize-css-assets-webpack-plugin插件。作用:压缩提取出的css,并解决extract-text-webpack-plugin分离出的js重复问题(多个文件引入同一css文件)(两个插件比较类似)
const OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin')
// uglifyjs-webpack-plugin插件。作用:压缩js文件
const UglifyJsPlugin = require('uglifyjs-webpack-plugin')
// 如果当前环境变量NODE_ENV的值是testing,则导入 test.env.js配置文,设置env为"testing"(根据需要在环境配置文件夹去配置)
// 这里设置env为"production"
const env = require('../config/prod.env')
// 把当前的生产配置对象和基础的配置对象合并
const webpackConfig = merge(baseWebpackConfig, {
	module: {
    	// 就是把utils配置好的处理各种css类型的配置拿过来,和dev设置一样,就是这里多了个extract: true,此项是自定义项,设置为true表示,生成独立的文件
    	rules: utils.styleLoaders({
      	sourceMap: config.build.productionSourceMap,
      	extract: true,
      	usePostCSS: true
    	})
    },
	// devtool开发工具,用来生成个sourcemap方便调试
  	// 按理说这里不用生成sourcemap多次一举,这里生成了source-map类型的map文件,只用于生产环境
  	devtool: config.build.productionSourceMap ? config.build.devtool : false,
    output: {
    	// 打包后的文件放在dist目录里面
        path: config.build.assetsRoot,
        // 文件名称使用 static/js/[name].[chunkhash].js, 其中name就是main,chunkhash就是模块的hash值,用于浏览器缓存的
        filename: utils.assetsPath('js/[name].[chunkhash].js'),
     	// chunkFilename是非入口模块文件,也就是说filename文件中引用了chunckFilename
    	chunkFilename: utils.assetsPath('js/[id].[chunkhash].js')
  	},
	plugins: [
       	// 下面是利用DefinePlugin插件,定义process.env环境变量为env
    	new webpack.DefinePlugin({
      		'process.env': env
    	}),
    	// UglifyJsPlugin插件是专门用来压缩js文件的
    	new UglifyJsPlugin({
      		uglifyOptions: {
        		compress: {
          			warnings: false// 警告:true保留警告,false不保留
        		}
      		},
      		sourceMap: config.build.productionSourceMap,// 压缩后是否生成map文件
      		parallel: true
    	}),
      // 生成独立的css文件,下面是生成独立css文件的名称
      new ExtractTextPlugin({
		// 抽取文本。比如打包之后的index页面有style插入,就是这个插件抽取出来的,减少请求
      	filename: utils.assetsPath('css/[name].[contenthash].css'),
      	allChunks: true,//为falsez则不会从代码分割块中提取CSS。
    		}),
      // 压缩css文件
      new OptimizeCSSPlugin({
      	cssProcessorOptions: config.build.productionSourceMap
        ? { safe: true, map: { inline: false } }
        : { safe: true }
      }),
     // 生成html页面
     new HtmlWebpackPlugin({
      	//非测试环境生成index.html
      	filename: process.env.NODE_ENV === 'testing'
      	? 'index.html'
      	: config.build.index,
      	template: 'index.html',
        inject: true,// 将js文件放到body标签的结尾
        // 压缩产出后的html页面
      	minify: {
        	removeComments: true,//删除注释
        	collapseWhitespace: true,//删除空格
        	removeAttributeQuotes: true//删除属性的引号   
      	},
      	chunksSortMode: 'dependency'//模块排序,按照我们需要的顺序排序(分类要插到html页面的模块)
	}),
    new webpack.HashedModuleIdsPlugin(),

    new webpack.optimize.ModuleConcatenationPlugin(),

    // 将打包后的文件中的第三方库文件抽取出来,便于浏览器缓存,提高程序的运行速度(抽取公共模块)
      new webpack.optimize.CommonsChunkPlugin({
        // common 模块的名称
      name: 'vendor',
      minChunks (module) {
           // 将所有依赖于node_modules下面文件打包到vendor中
        return (
          module.resource &&
          /\.js$/.test(module.resource) &&
          module.resource.indexOf(
            path.join(__dirname, '../node_modules')
          ) === 0
        )
      }
    }),

       // 把webpack的runtime代码和module manifest代码提取到manifest文件中,防止修改了代码但是没有修改第三方库文件导致第三方库文件也打包的问题
    new webpack.optimize.CommonsChunkPlugin({
      name: 'manifest',
      minChunks: Infinity
    }),
    // 这个实例从代码分割的块中提取共享块,并将它们打包成一个单独的块,类似于供应商块 
    new webpack.optimize.CommonsChunkPlugin({
      name: 'app',
      async: 'vendor-async',
      children: true,
      minChunks: 3
    }),
	// 用来复制一个单独的文件或者整个目录到新建的文件夹下(复制自定义的静态资源)
    new CopyWebpackPlugin([
      {
        from: path.resolve(__dirname, '../static'),
        to: config.build.assetsSubDirectory,
        ignore: ['.*']
      }
    ])
  ]
})

if (config.build.productionGzip) {
    // 开启Gzi压缩打包后的文件,就跟你打包压缩包一样,把这个压缩包给浏览器,浏览器自动解压的
    // vue-cli默认将这个神奇的功能禁用掉的,理由是Surge 和 Netlify 静态主机默认帮你把上传的文件gzip了
  const CompressionWebpackPlugin = require('compression-webpack-plugin')

  webpackConfig.plugins.push(
    new CompressionWebpackPlugin({
      asset: '[path].gz[query]',
      algorithm: 'gzip',
      test: new RegExp(// 这里是把js和css文件压缩
        '\\.(' +
        config.build.productionGzipExtensions.join('|') +
        ')$'
      ),
      threshold: 10240,
      minRatio: 0.8
    })
  )
}

if (config.build.bundleAnalyzerReport) {
    // 打包编译后的文件打印出详细的文件信息,vue-cli默认把这个禁用了,个人觉得还是有点用的,可以自行配置
  const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
  webpackConfig.plugins.push(new BundleAnalyzerPlugin())
}

module.exports = webpackConfig

==补充==

开发环境(development)生产环境(production)构建目标差异很大。在开发环境中,我们需要具有强大的、具有实时重新加载(live reloading)或热模块替换(hot module replacement)能力的 source map 和 localhost server。而在生产环境中,我们的目标则转向于关注更小的 bundle,更轻量的 source map,以及更优化的资源,以改善加载时间。由于要遵循逻辑分离,我们通常建议为每个环境编写彼此独立的 webpack 配置。

config文件夹(环境,路径等相关一些变量导出)

1.文件夹结构

|-config
|---dev.env.js
|---index.js
|---prod.env.js

2.js文件夹详解

① dev.env.js(开发环境用)
'use strict'
//优雅降级,还是只有一个开发环境
const merge = require('webpack-merge')
const prodEnv = require('./prod.env')
//配置开发环境
module.exports = merge(prodEnv, {
  NODE_ENV: '"development"'
})
② index.js(开发与生产基本变量配置)
'use strict'
// node.js路径模块。作用:拼接路径,例子:path.join('/foo', 'bar', 'baz/asdf', 'quux', '..');// 返回: '/foo/bar/baz/asdf'
const path = require('path')
module.exports = {
	// 开发环境下面的配置
	dev: {
    	assetsSubDirectory: 'static',//静态资源文件夹,默认static,一般存放css,js,image等文件
    	assetsPublicPath: '/',//发布路径(根目录)
    	proxyTable: {},//利用该属性解决跨域的问题
    	host: 'localhost', // 可以被process.env.HOST覆盖
    	port: 8080, // 端口号设置,端口号占用出现问题可在此处修改
    	autoOpenBrowser: false,//是否在编译(输入命令行npm run dev)后打开http://localhost:8080/页面,以前配置为true,近些版本改为false
    	errorOverlay: true,//浏览器错误提示
    	notifyOnErrors: true,//跨平台错误提示
    	poll: false, // 跟devserver相关的一个配置,webpack为我们提供的devserver是可以监控文件改动的,但在有些情况下却不能工作,我们可以设置一个轮询(poll)来解决
    	devtool: 'cheap-module-eval-source-map',//错误的源码映射,该属性为原始源代码(仅限行)不可在生产环境中使用
        cacheBusting: true,//一个配合devtool的配置,当给文件名插入新的hash导致清除缓存时是否生成souce maps,默认在开发环境下为true,使缓存失效
    	cssSourceMap: true//代码压缩后进行调bug定位将非常困难,于是引入sourcemap记录压缩前后的位置信息记录,当产生错误时直接定位到未压缩前的位置,将大大的方便我们调试
  },
// 生产环境下面的配置
	build: {
    index: path.resolve(__dirname, '../dist/index.html'),//index编译后生成的位置和名字,根据需要改变后缀,比如index.php
    assetsRoot: path.resolve(__dirname, '../dist'),//编译后存放生成环境代码的位置
    assetsSubDirectory: 'static',//js,css,images存放文件夹名
    assetsPublicPath: '/',//发布的根目录,通常本地打包dist后打开文件会报错,此处修改为./。如果是上线的文件,可根据文件存放位置进行更改路径

    /**
     * Source Maps
     */

    productionSourceMap: true,//true,打包后有相同的被压缩的文件.js.map--false就不会有
    devtool: '#source-map',//定位出错源文件
   //压缩代码,web端和服务器共同支持gzip
    //为true前确保包装了依赖
    // npm install --save-dev compression-webpack-plugin
    productionGzip: false,
    productionGzipExtensions: ['js', 'css'],
    // 运行`npm run build --report`,打包文件分析报告,vue-cli2,3有区别
    bundleAnalyzerReport: process.env.npm_config_report
  }
}
③ prod.env.js(生产环境用)
'use strict'
module.exports = {
  NODE_ENV: '"production"'
}

==补充==

development和production一定要加双引号,不然会报错!!