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

一、前言

原文:https://segmentfault.com/a/1190000014804826

vue-cli 是构建 vue 单页应用的脚手架,输入一串指定的命令行从而自动生成 vue.js+wepack 的项目模板。这其中 webpack 发挥了很大的作用,它使得我们的代码模块化,引入一些插件帮我们完善功能可以将文件打包压缩,图片转 base64 等。后期对项目的配置使得我们对于脚手架自动生成的代码的理解更为重要,接下来我将基于 webpack3.6.0 版本结合文档将文件各个击破,纯干料。
重点章节点击查看:package.jsonconfig/index.jswebpack.base.conf.jswebpack.dev.conf.jswebpack.prod.conf.js

二、主体结构

├─build 
├─config 
├─dist
├─node_modules
├─src
│ ├─assets
│ ├─components
│ ├─router
│ ├─App.vue
│ ├─main.js
├─static
├─.babelrc
├─.editorconfig
├─.gitignore
├─.postcssrc.js
├─index.html
├─package-lock.json
├─package.json
└─README.md

1、 package.json

项目作为一个大家庭,每个文件都各司其职。package.json 来制定名单,需要哪些 npm 包来参与到项目中来,npm install 命令根据这个配置文件增减来管理本地的安装包。

{
// 从 name 到 private 都是 package 的配置信息,也就是我们在脚手架搭建中输入的项目描述
  "name": "shop",// 项目名称:不能以.(点) 或者 _(下划线)开头,不能包含大写字母,具有明确的的含义与现有项目名字不重复
  "version": "1.0.0",// 项目版本号:遵循“大版本. 次要版本. 小版本”
  "description": "A Vue.js project",// 项目描述
  "author": "qietuniu",// 作者名字
  "private": true,// 是否私有
  //scripts 中的子项即是我们在控制台运行的脚本的缩写
  "scripts": {
   //①webpack-dev-server: 启动了 http 服务器,实现实时编译;
   //inline 模式会在 webpack.config.js 入口配置中新增 webpack-dev-server/client?http://localhost:8080/ 的入口, 使得我们访问路径为 localhost:8080/index.html(相应的还有另外一种模式 Iframe);
   //progress: 显示打包的进度
    "dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js",  
    "start": "npm run dev",// 与 npm run dev 相同,直接运行开发环境
    "build": "node build/build.js"// 使用 node 运行 build 文件
  },
  //②dependencies(项目依赖库): 在安装时使用 --save 则写入到 dependencies
  "dependencies": {
    "vue": "^2.5.2",//vue.js
    "vue-router": "^3.0.1"//vue 的路由插件
  },
  // 和 devDependencies(开发依赖库):在安装时使用 --save-dev 将写入到 devDependencies
  "devDependencies": {
    "autoprefixer": "^7.1.2",//autoprefixer 作为 postcss 插件用来解析 CSS 补充前缀,例如 display: flex 会补充为 display:-webkit-box;display: -webkit-flex;display: -ms-flexbox;display: flex。
    //babel: 以下几个 babel 开头的都是针对 es6 解析的插件。用最新标准编写的 JavaScript 代码向下编译成可以在今天随处可用的版本
    "babel-core": "^6.22.1",//babel 的核心,把 js 代码分析成 ast ,方便各个插件分析语法进行相应的处理。
    "babel-helper-vue-jsx-merge-props": "^2.0.3",// 预制 babel-template 函数,提供给 vue,jsx 等使用
    "babel-loader": "^7.1.1",// 使项目运行使用 Babel 和 webpack 来传输 js 文件,使用 babel-core 提供的 api 进行转译
    "babel-plugin-syntax-jsx": "^6.18.0",// 支持 jsx
    "babel-plugin-transform-runtime": "^6.22.0",// 避免编译输出中的重复,直接编译到 build 环境中
    "babel-plugin-transform-vue-jsx": "^3.5.0",//babel 转译过程中使用到的插件,避免重复
    "babel-preset-env": "^1.3.2",// 转为 es5,transform 阶段使用到的插件之一
    "babel-preset-stage-2": "^6.22.0",//ECMAScript 第二阶段的规范
    "chalk": "^2.0.1",// 用来在命令行输出不同颜色文字
    "copy-webpack-plugin": "^4.0.1",// 拷贝资源和文件
    "css-loader": "^0.28.0",//webpack 先用 css-loader 加载器去解析后缀为 css 的文件,再使用 style-loader 生成一个内容为最终解析完的 css 代码的 style 标签,放到 head 标签里
    "extract-text-webpack-plugin": "^3.0.0",// 将一个以上的包里面的文本提取到单独文件中
    "file-loader": "^1.1.4",//③打包压缩文件,与 url-loader 用法类似
    "friendly-errors-webpack-plugin": "^1.6.1",// 识别某些类别的 WebPACK 错误和清理,聚合和优先排序,以提供更好的开发经验
    "html-webpack-plugin": "^2.30.1",// 简化了 HTML 文件的创建,引入了外部资源,创建 html 的入口文件,可通过此项进行多页面的配置
    "node-notifier": "^5.1.2",// 支持使用 node 发送跨平台的本地通知
    "optimize-css-assets-webpack-plugin": "^3.2.0",// 压缩提取出的 css,并解决 ExtractTextPlugin 分离出的 js 重复问题 (多个文件引入同一 css 文件)
    "ora": "^1.2.0",// 加载(loading)的插件
    "portfinder": "^1.0.13",// 查看进程端口
    "postcss-import": "^11.0.0",// 可以消耗本地文件、节点模块或 web_modules
    "postcss-loader": "^2.0.8",// 用来兼容 css 的插件
    "postcss-url": "^7.2.1",//URL 上重新定位、内联或复制
    "rimraf": "^2.6.0",// 节点的 UNIX 命令 RM—RF, 强制删除文件或者目录的命令
    "semver": "^5.3.0",// 用来对特定的版本号做判断的
    "shelljs": "^0.7.6",// 使用它来消除 shell 脚本在 UNIX 上的依赖性,同时仍然保留其熟悉和强大的命令,即可执行 Unix 系统命令
    "uglifyjs-webpack-plugin": "^1.1.1",// 压缩 js 文件
    "url-loader": "^0.5.8",// 压缩文件,可将图片转化为 base64
    "vue-loader": "^13.3.0",//VUE 单文件组件的 WebPACK 加载器
    "vue-style-loader": "^3.0.1",// 类似于样式加载程序,您可以在 CSS 加载器之后将其链接,以将 CSS 动态地注入到文档中作为样式标签
    "vue-template-compiler": "^2.5.2",// 这个包可以用来预编译 VUE 模板到渲染函数,以避免运行时编译开销和 CSP 限制
    "webpack": "^3.6.0",// 打包工具
    "webpack-bundle-analyzer": "^2.9.0",// 可视化 webpack 输出文件的大小
    "webpack-dev-server": "^2.9.1",// 提供一个提供实时重载的开发服务器
    "webpack-merge": "^4.1.0"// 它将数组和合并对象创建一个新对象。如果遇到函数,它将执行它们,通过算法运行结果,然后再次将返回的值封装在函数中
  },
  //engines 是引擎,指定 node 和 npm 版本
  "engines": {
    "node": ">= 6.0.0",
    "npm": ">= 3.0.0"
  },
  // 限制了浏览器或者客户端需要什么版本才可运行
  "browserslist": [
    "> 1%",
    "last 2 versions",
    "not ie <= 8"
  ]
}

注释:
①、点这里→webpack 运行时的配置文档传送门

②、devDependencies 和 dependencies 的区别: devDependencies 里面的插件只用于开发环境,不用于生产环境,即辅助作用,打包的时候需要,打包完成就不需要了。而 dependencies 是需要发布到生产环境的,自始至终都在。比如 wepack 等只是在开发中使用的包就写入到 devDependencies,而像 vue 这种项目全程依赖的包要写入到 dependencies
点这里→更多安装包文档搜索页传送门

③、file-loader 和 url-loader 的区别:以图片为例,file-loader 可对图片进行压缩,但是还是通过文件路径进行引入,当 http 请求增多时会降低页面性能,而 url-loader 通过设定 limit 参数,小于 limit 字节的图片会被转成 base64 的文件,大于 limit 字节的将进行图片压缩的操作。总而言之,url-loader 是 file-loader 的上层封装。
点这里→file-loader 和 url-loader 详解
点这里→file-loader 文档传送门
点这里→url-loader 文档传送门

2、.postcssrc.js

.postcssrc.js 文件其实是 postcss-loader 包的一个配置,在 webpack 的旧版本可以直接在 webpack.config.js 中配置,现版本中 postcss 的文档示例独立出.postcssrc.js,里面写进去需要使用到的插件

module.exports = {
  "plugins": {
    "postcss-import": {},//①
    "postcss-url": {},//②
    "autoprefixer": {}//③
  }
}

注释:
①、点这里→postcss-import 文档传送门
②、点这里→postcss-url 文档传送门
③、点这里→autoprefixer 文档传送门

3、 .babelrc

该文件是 es6 解析的一个配置

{
// 制定转码的规则
  "presets": [
  //env 是使用 babel-preset-env 插件将 js 进行转码成 es5,并且设置不转码的 AMD,COMMONJS 的模块文件,制定浏览器的兼容
    ["env", {
      "modules": false,
      "targets": {
        "browsers": ["> 1%", "last 2 versions", "not ie <= 8"]
      }
    }],
    "stage-2"
  ],

"plugins": ["transform-vue-jsx", "transform-runtime"]//①
}

注释:
①、点这里→transform-vue-jsx 文档传送门
点这里→transform-runtime 文档传送门

4、src 内文件

我们开发的代码都存放在 src 目录下,根据需要我们通常会再建一些文件夹。比如 pages 的文件夹,用来存放页面让 components 文件夹专门做好组件的工作;api 文件夹,来封装请求的参数和方法;store 文件夹,使用 vuex 来作为 vue 的状态管理工具,我也常叫它作前端的数据库等。


①、assets 文件:脚手架自动会放入一个图片在里面作为初始页面的 logo。平常我们使用的时候会在里面建立 js,css,img,fonts 等文件夹,作为静态资源调用

②、components 文件夹:用来存放组件,合理地使用组件可以高效地实现复用等功能,从而更好地开发项目。一般情况下比如创建头部组件的时候,我们会新建一个 header 的文件夹,然后再新建一个 header.vue 的文件

③、router 文件夹:该文件夹下有一个叫 index.js 文件,用于实现页面的路由跳转,具体使用请点击→vue-router 传送门

④、App.vue:作为我们的主组件,可通过使用 <router-view/> 开放入口让其他的页面组件得以显示。

⑤、main.js:作为我们的入口文件,主要作用是初始化 vue 实例并使用需要的插件,小型项目省略 router 时可放在该处

注释:具体 vue 的用法可查看vue 官方中文文档传送门

5、其他文件

①、.editorconfig:编辑器的配置文件

②、.gitignore:忽略 git 提交的一个文件,配置之后提交时将不会加载忽略的文件

③、index.html:页面入口,经过编译之后的代码将插入到这来。

④、package.lock.json:锁定安装时的包的版本号,并且需要上传到 git,以保证其他人在 npm install 时大家的依赖能保证一致

⑤、README.md:可此填写项目介绍

⑥、node_modules:根据 package.json 安装时候生成的的依赖(安装包)

三、config 文件夹

├─config 
│ ├─dev.env.js 
│ ├─index.js 
│ ├─prod.env.js 

1、config/dev.env.js

config 内的文件其实是服务于 build 的,大部分是定义一个变量 export 出去。

'use strict'// 采用严格模式
const merge = require('webpack-merge')//①
const prodEnv = require('./prod.env')
//webpack-merge 提供了一个合并函数,它将数组和合并对象创建一个新对象。
// 如果遇到函数,它将执行它们,通过算法运行结果,然后再次将返回的值封装在函数中. 这边将 dev 和 prod 进行合并
module.exports = merge(prodEnv, {
  NODE_ENV: '"development"'
})

注释:①、点这里→webpack-merge 文档传送门

2、config/prod.env.js

当开发是调取 dev.env.js 的开发环境配置,发布时调用 prod.env.js 的生产环境配置

'use strict'
module.exports = {
  NODE_ENV: '"production"'
}

3、config/index.js

'use strict'
const path = require('path')

module.exports = {
dev: {
// 开发环境下面的配置
assetsSubDirectory: 'static',// 子目录,一般存放 css,js,image 等文件
assetsPublicPath: '/',// 根目录
proxyTable: {},// 可利用该属性解决跨域的问题
host: 'localhost', // 地址
port: 8080, // 端口号设置,端口号占用出现问题可在此处修改
autoOpenBrowser: false,// 是否在编译(输入命令行 npm run dev)后打开http://localhost:8080/ 页面,以前配置为 true,近些版本改为 false,个人偏向习惯自动打开页面
errorOverlay: true,// 浏览器错误提示
notifyOnErrors: true,// 跨平台错误提示
poll: false, // 使用文件系统 (file system) 获取文件改动的通知 devServer.watchOptions
devtool: 'cheap-module-eval-source-map',// 增加调试,该属性为原始源代码(仅限行)不可在生产环境中使用
cacheBusting: 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 后打开文件会报错,此处修改为./。如果是上线的文件,可根据文件存放位置进行更改路径
productionSourceMap: true,
devtool: '#source-map',//①
//unit 的 gzip 命令用来压缩文件,gzip 模式下需要压缩的文件的扩展名有 js 和 css
productionGzip: false,
productionGzipExtensions: ['js', 'css'],
bundleAnalyzerReport: process.env.npm_config_report
}
}

注释:①点击→devtool 文档传送门

四、build 文件夹

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

1、build/build.js

该文件作用,即构建生产版本。package.json 中的 scripts 的 build 就是 node build/build.js,输入命令行 npm run build 对该文件进行编译生成生产环境的代码。

'use strict'
require('./check-versions')()//check-versions:调用检查版本的文件。加()代表直接调用该函数
process.env.NODE_ENV = 'production'// 设置当前是生产环境
// 下面定义常量引入插件
const ora = require('ora')//①加载动画
const rm = require('rimraf')//②删除文件
const path = require('path')
const chalk = require('chalk')//③对文案输出的一个彩色设置
const webpack = require('webpack')
const config = require('../config')// 默认读取下面的 index.js 文件
const webpackConfig = require('./webpack.prod.conf')
// 调用 start 的方法实现加载动画,优化用户体验
const spinner = ora('building for production...')
spinner.start()
// 先删除 dist 文件再生成新文件,因为有时候会使用 hash 来命名,删除整个文件可避免冗余
rm(path.join(config.build.assetsRoot, config.build.assetsSubDirectory), err => {
  if (err) throw err
  webpack(webpackConfig, (err, stats) => {
    spinner.stop()
    if (err) throw err
    process.stdout.write(stats.toString({
      colors: true,
      modules: false,
      children: false, // If you are using ts-loader, setting this to true will make TypeScript errors show up during build.
      chunks: false,
      chunkModules: false
    }) + '\n\n')
<span class="hljs-keyword">if</span> (stats.<span class="hljs-title function_">hasErrors</span>()) {
  process.<span class="hljs-title function_">exit</span>(<span class="hljs-number">1</span>)
}

<span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(chalk.<span class="hljs-title function_">cyan</span>(<span class="hljs-string">'  Build complete.\n'</span>))
<span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(chalk.<span class="hljs-title function_">yellow</span>(
  <span class="hljs-string">'  Tip: built files are meant to be served over an HTTP server.\n'</span> +
  <span class="hljs-string">'  Opening index.html over file:// won\'t work.\n'</span>
))

})
})

注释:
①、点这里→ora 文档传送门
②、点这里→chalk 文档传送门
③、点这里→rimraf 文档传送门

2、build/check-version.js

该文件用于检测 node 和 npm 的版本,实现版本依赖

'use strict'
const chalk = require('chalk')
const semver = require('semver')//①对版本进行检查
const packageConfig = require('../package.json')
const shell = require('shelljs')

function exec (cmd) {
// 返回通过 child_process 模块的新建子进程,执行 Unix 系统命令后转成没有空格的字符串
return require('child_process').execSync(cmd).toString().trim()
}

const versionRequirements = [
{
name: 'node',
currentVersion: semver.clean(process.version),// 使用 semver 格式化版本
versionRequirement: packageConfig.engines.node// 获取 package.json 中设置的 node 版本
}
]

if (shell.which('npm')) {
versionRequirements.push({
name: 'npm',
currentVersion: exec('npm --version'),// 自动调用 npm --version 命令,并且把参数返回给 exec 函数,从而获取纯净的版本号
versionRequirement: packageConfig.engines.npm
})
}

module.exports = function () {
const warnings = []
for (let i = 0; i < versionRequirements.length; i++) {
const mod = versionRequirements[i]

<span class="hljs-keyword">if</span> (!semver.<span class="hljs-title function_">satisfies</span>(mod.<span class="hljs-property">currentVersion</span>, mod.<span class="hljs-property">versionRequirement</span>)) {
<span class="hljs-comment">//上面这个判断就是如果版本号不符合package.json文件中指定的版本号,就执行下面错误提示的代码</span>
  warnings.<span class="hljs-title function_">push</span>(mod.<span class="hljs-property">name</span> + <span class="hljs-string">': '</span> +
    chalk.<span class="hljs-title function_">red</span>(mod.<span class="hljs-property">currentVersion</span>) + <span class="hljs-string">' should be '</span> +
    chalk.<span class="hljs-title function_">green</span>(mod.<span class="hljs-property">versionRequirement</span>)
  )
}

}

if (warnings.length) {
console.log('')
console.log(chalk.yellow('To use this template, you must update following to modules:'))
console.log()

<span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> i = <span class="hljs-number">0</span>; i &lt; warnings.<span class="hljs-property">length</span>; i++) {
  <span class="hljs-keyword">const</span> warning = warnings[i]
  <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(<span class="hljs-string">'  '</span> + warning)
}

<span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>()
process.<span class="hljs-title function_">exit</span>(<span class="hljs-number">1</span>)

}
}

注释:
①、点这里→chalk 文档传送门
点这里→semver 文档传送门

3、build/utils.js

utils 是工具的意思,是一个用来处理 css 的文件。

'use strict'
const path = require('path')
const config = require('../config')
const ExtractTextPlugin = require('extract-text-webpack-plugin')
const packageConfig = require('../package.json')
// 导出文件的位置,根据环境判断开发环境和生产环境,为 config 文件中 index.js 文件中定义的 build.assetsSubDirectory 或 dev.assetsSubDirectory
exports.assetsPath = function (_path) {
  const assetsSubDirectory = process.env.NODE_ENV === 'production'
    ? config.build.assetsSubDirectory
    : config.dev.assetsSubDirectory
//Node.js path 模块提供了一些用于处理文件路径的小工具①
  return path.posix.join(assetsSubDirectory, _path)
}

exports.cssLoaders = function (options) {
options = options || {}
// 使用了 css-loader 和 postcssLoader,通过 options.usePostCSS 属性来判断是否使用 postcssLoader 中压缩等方法
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',
//Object.assign 是 es6 语法的浅复制,后两者合并后复制完成赋值
options: Object.assign({}, loaderOptions, {
sourceMap: options.sourceMap
})
})
}

<span class="hljs-keyword">if</span> (options.<span class="hljs-property">extract</span>) {
<span class="hljs-comment">//ExtractTextPlugin可提取出文本,代表首先使用上面处理的loaders,当未能正确引入时使用vue-style-loader</span>
  <span class="hljs-keyword">return</span> <span class="hljs-title class_">ExtractTextPlugin</span>.<span class="hljs-title function_">extract</span>({
    <span class="hljs-attr">use</span>: loaders,
    <span class="hljs-attr">fallback</span>: <span class="hljs-string">'vue-style-loader'</span>
  })
} <span class="hljs-keyword">else</span> {
<span class="hljs-comment">//返回vue-style-loader连接loaders的最终值</span>
  <span class="hljs-keyword">return</span> [<span class="hljs-string">'vue-style-loader'</span>].<span class="hljs-title function_">concat</span>(loaders)
}

}
return {
css: generateLoaders(),// 需要 css-loader 和 vue-style-loader
postcss: generateLoaders(),// 需要 css-loader 和 postcssLoader 和 vue-style-loader
less: generateLoaders('less'),// 需要 less-loader 和 vue-style-loader
sass: generateLoaders('sass', { indentedSyntax: true }),// 需要 sass-loader 和 vue-style-loader
scss: generateLoaders('sass'),// 需要 sass-loader 和 vue-style-loader
stylus: generateLoaders('stylus'),// 需要 stylus-loader 和 vue-style-loader
styl: generateLoaders('stylus')// 需要 stylus-loader 和 vue-style-loader
}
}
exports.styleLoaders = function (options) {
const output = []
const loaders = exports.cssLoaders(options)
// 将各种 css,less,sass 等综合在一起得出结果输出 output
for (const extension in loaders) {
const loader = loaders[extension]
output.push({
test: new RegExp('\.' + extension + '$'),
use: loader
})
}

return output
}

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.<span class="hljs-title function_">notify</span>({
  <span class="hljs-attr">title</span>: packageConfig.<span class="hljs-property">name</span>,
  <span class="hljs-attr">message</span>: severity + <span class="hljs-string">': '</span> + error.<span class="hljs-property">name</span>,
  <span class="hljs-attr">subtitle</span>: filename || <span class="hljs-string">''</span>,
  <span class="hljs-attr">icon</span>: path.<span class="hljs-title function_">join</span>(__dirname, <span class="hljs-string">'logo.png'</span>)
})

}
}

注释:
①、path.posix:提供对路径方法的 POSIX(可移植性操作系统接口)特定实现的访问,即可跨平台,区别于 win32。
path.join:用于连接路径,会正确使用当前系统的路径分隔符,Unix 系统是 "/",Windows 系统是 ""
点击→path 用法传送门

4、vue-loader.conf.js

该文件的主要作用就是处理.vue 文件,解析这个文件中的每个语言块(template、script、style), 转换成 js 可用的 js 模块。

'use strict'
const utils = require('./utils')
const config = require('../config')
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
  }),
  cssSourceMap: sourceMapEnabled,
  cacheBusting: config.dev.cacheBusting,
   // 在模版编译过程中,编译器可以将某些属性,如 src 路径,转换为 require 调用,以便目标资源可以由 webpack 处理.
  transformToRequire: {
    video: ['src', 'poster'],
    source: 'src',
    img: 'src',
    image: 'xlink:href'
  }
}

5、webpack.base.conf.js

webpack.base.conf.js 是开发和生产共同使用提出来的基础配置文件,主要实现配制入口,配置输出环境,配置模块 resolve 和插件等

'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 = {
//path.join 将路径片段进行拼接,而 path.resolve 将以 / 开始的路径片段作为根目录,在此之前的路径将会被丢弃
//path.join('/a', '/b') // 'a/b',path.resolve('/a', '/b') // '/b'
context: path.resolve(__dirname, '../'),
// 配置入口,默认为单页面所以只有 app 一个入口
entry: {
app: './src/main.js'
},
// 配置出口,默认是 /dist 作为目标文件夹的路径
output: {
path: config.build.assetsRoot,// 路径
filename: '[name].js',// 文件名
publicPath: process.env.NODE_ENV === 'production'
? config.build.assetsPublicPath
: config.dev.assetsPublicPath// 公共存放路径
},
resolve: {
// 自动的扩展后缀,比如一个 js 文件,则引用时书写可不要写.js
extensions: ['.js', '.vue', '.json'],
// 创建路径的别名,比如增加 'components': resolve('src/components') 等
alias: {
'vue$': 'vue/dist/vue.esm.js',
'@': resolve('src'),
}
},
// 使用插件配置相应文件的处理方法
module: {
rules: [
// 使用 vue-loader 将 vue 文件转化成 js 的模块①
{
test: /.vue$/,
loader: 'vue-loader',
options: vueLoaderConfig
},
//js 文件需要通过 babel-loader 进行编译成 es5 文件以及压缩等操作②
{
test: /.js$/,
loader: 'babel-loader',
include: [resolve('src'), resolve('test'), resolve('node_modules/webpack-dev-server/client')]
},
// 图片、音像、字体都使用 url-loader 进行处理,超过 10000 会编译成 base64③
{
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]')
}
}
]
},
// 以下选项是 Node.js 全局变量或模块,这里主要是防止 webpack 注入一些 Node.js 的东西到 vue 中
node:
setImmediate: false,
dgram: 'empty',
fs: 'empty',
net: 'empty',
tls: 'empty',
child_process: 'empty'
}
}

注释:
①、点击→vue-loader 文档传送门
②、点击→babel-loader 文档传送门

6、webpack.dev.conf.js

'use strict'
const utils = require('./utils')
const webpack = require('webpack')
const config = require('../config')
// 通过 webpack-merge 实现 webpack.dev.conf.js 对 wepack.base.config.js 的继承
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')// 查看空闲端口位置,默认情况下搜索 8000 这个端口②
const HOST = process.env.HOST//③processs 为 node 的一个全局对象获取当前程序的环境变量,即 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, // 增强调试,上文有提及
// 此处的配置都是在 config 的 index.js 中设定好了
devServer: {//④
clientLogLevel: 'warning',// 控制台显示的选项有 none, error, warning 或者 info
// 当使用 HTML5 History API 时,任意的 404 响应都可能需要被替代为 index.html
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,// warning 和 error 都要显示
publicPath: config.dev.assetsPublicPath,
proxy: config.dev.proxyTable,// 接口代理
quiet: true, // 控制台是否禁止打印警告和错误, 若用 FriendlyErrorsPlugin 此处为 true
watchOptions: {
poll: config.dev.poll,//// 文件系统检测改动
}
},
plugins: [
new webpack.DefinePlugin({
'process.env': require('../config/dev.env')
}),
new webpack.HotModuleReplacementPlugin(),//⑤模块热替换插件,修改模块时不需要刷新页面
new webpack.NamedModulesPlugin(), // 显示文件的正确名字
new webpack.NoEmitOnErrorsPlugin(),// 当 webpack 编译错误的时候,来中端打包进程,防止错误代码打包到文件中
// https://github.com/ampedandwired/html-webpack-plugin
// 该插件可自动生成一个 html5 文件或使用模板文件将编译好的代码注入进去⑥
new HtmlWebpackPlugin({
filename: 'index.html',
template: 'index.html',
inject: true
}),
new CopyWebpackPlugin([// 复制插件
{
from: path.resolve(__dirname, '../static'),
to: config.dev.assetsSubDirectory,
ignore: ['.*']// 忽略.* 的文件
}
])
]
})

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: {
messages: [Your application is running here: http://<span class="hljs-subst">${devWebpackConfig.devServer.host}</span>:<span class="hljs-subst">${port}</span>],
},
onErrors: config.dev.notifyOnErrors
? utils.createNotifierCallback()
: undefined
}))
resolve(devWebpackConfig)
}
})
})

注释:
①、点击→friendly-errors-webpack-plugin 文档传送门
②、点击→process 文档传送门
③、点击→babel-loader 文档传送门
④、点击→devtool 文档传送门
⑤、点击→webpack 的 HotModuleReplacementPlugin 文档传送门
⑥、点击→html-webpack-plugin 文档传送门

7、webpack.prod.conf.js

'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 env = require('../config/prod.env')

const webpackConfig = merge(baseWebpackConfig, {
module: {
// 调用 utils.styleLoaders 的方法
rules: utils.styleLoaders({
sourceMap: config.build.productionSourceMap,// 开启调试的模式。默认为 true
extract: true,
usePostCSS: true
})
},
devtool: config.build.productionSourceMap ? config.build.devtool : false,
output: {
path: config.build.assetsRoot,
filename: utils.assetsPath('js/[name].[chunkhash].js'),
chunkFilename: utils.assetsPath('js/[id].[chunkhash].js')
},
plugins: [
new webpack.DefinePlugin({
'process.env': env
}),
new UglifyJsPlugin({
uglifyOptions: {
compress: {// 压缩
warnings: false// 警告:true 保留警告,false 不保留
}
},
sourceMap: config.build.productionSourceMap,
parallel: true
}),
new ExtractTextPlugin({// 抽取文本。比如打包之后的 index 页面有 style 插入,就是这个插件抽取出来的,减少请求
filename: utils.assetsPath('css/[name].[contenthash].css'),
allChunks: true,
}),

<span class="hljs-keyword">new</span> <span class="hljs-title class_">OptimizeCSSPlugin</span>({<span class="hljs-comment">//优化css的插件</span>
  <span class="hljs-attr">cssProcessorOptions</span>: config.<span class="hljs-property">build</span>.<span class="hljs-property">productionSourceMap</span>
    ? { <span class="hljs-attr">safe</span>: <span class="hljs-literal">true</span>, <span class="hljs-attr">map</span>: { <span class="hljs-attr">inline</span>: <span class="hljs-literal">false</span> } }
    : { <span class="hljs-attr">safe</span>: <span class="hljs-literal">true</span> }
}),

<span class="hljs-keyword">new</span> <span class="hljs-title class_">HtmlWebpackPlugin</span>({<span class="hljs-comment">//html打包</span>
  <span class="hljs-attr">filename</span>: config.<span class="hljs-property">build</span>.<span class="hljs-property">index</span>,
  <span class="hljs-attr">template</span>: <span class="hljs-string">'index.html'</span>,
  <span class="hljs-attr">inject</span>: <span class="hljs-literal">true</span>,
  <span class="hljs-attr">minify</span>: {<span class="hljs-comment">//压缩</span>
    <span class="hljs-attr">removeComments</span>: <span class="hljs-literal">true</span>,<span class="hljs-comment">//删除注释</span>
    <span class="hljs-attr">collapseWhitespace</span>: <span class="hljs-literal">true</span>,<span class="hljs-comment">//删除空格</span>
    <span class="hljs-attr">removeAttributeQuotes</span>: <span class="hljs-literal">true</span><span class="hljs-comment">//删除属性的引号   </span>
  },
 
  <span class="hljs-attr">chunksSortMode</span>: <span class="hljs-string">'dependency'</span><span class="hljs-comment">//模块排序,按照我们需要的顺序排序</span>
}),

<span class="hljs-keyword">new</span> webpack.<span class="hljs-title class_">HashedModuleIdsPlugin</span>(),
<span class="hljs-keyword">new</span> webpack.<span class="hljs-property">optimize</span>.<span class="hljs-title class_">ModuleConcatenationPlugin</span>(),
<span class="hljs-keyword">new</span> webpack.<span class="hljs-property">optimize</span>.<span class="hljs-title class_">CommonsChunkPlugin</span>({<span class="hljs-comment">//抽取公共的模块</span>
  <span class="hljs-attr">name</span>: <span class="hljs-string">'vendor'</span>,
  minChunks (<span class="hljs-variable language_">module</span>) {   
    <span class="hljs-keyword">return</span> (
      <span class="hljs-variable language_">module</span>.<span class="hljs-property">resource</span> &amp;&amp;
      <span class="hljs-regexp">/\.js$/</span>.<span class="hljs-title function_">test</span>(<span class="hljs-variable language_">module</span>.<span class="hljs-property">resource</span>) &amp;&amp;
      <span class="hljs-variable language_">module</span>.<span class="hljs-property">resource</span>.<span class="hljs-title function_">indexOf</span>(
        path.<span class="hljs-title function_">join</span>(__dirname, <span class="hljs-string">'../node_modules'</span>)
      ) === <span class="hljs-number">0</span>
    )
  }
}),
<span class="hljs-keyword">new</span> webpack.<span class="hljs-property">optimize</span>.<span class="hljs-title class_">CommonsChunkPlugin</span>({
  <span class="hljs-attr">name</span>: <span class="hljs-string">'manifest'</span>,
  <span class="hljs-attr">minChunks</span>: <span class="hljs-title class_">Infinity</span>
}),
<span class="hljs-keyword">new</span> webpack.<span class="hljs-property">optimize</span>.<span class="hljs-title class_">CommonsChunkPlugin</span>({
  <span class="hljs-attr">name</span>: <span class="hljs-string">'app'</span>,
  <span class="hljs-attr">async</span>: <span class="hljs-string">'vendor-async'</span>,
  <span class="hljs-attr">children</span>: <span class="hljs-literal">true</span>,
  <span class="hljs-attr">minChunks</span>: <span class="hljs-number">3</span>
}),
<span class="hljs-keyword">new</span> <span class="hljs-title class_">CopyWebpackPlugin</span>([<span class="hljs-comment">//复制,比如打包完之后需要把打包的文件复制到dist里面</span>
  {
    <span class="hljs-attr">from</span>: path.<span class="hljs-title function_">resolve</span>(__dirname, <span class="hljs-string">'../static'</span>),
    <span class="hljs-attr">to</span>: config.<span class="hljs-property">build</span>.<span class="hljs-property">assetsSubDirectory</span>,
    <span class="hljs-attr">ignore</span>: [<span class="hljs-string">'.*'</span>]
  }
])

]
})

if (config.build.productionGzip) {
const 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) {
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
webpackConfig.plugins.push(new BundleAnalyzerPlugin())
}

module.exports = webpackConfig

注释:webpack.prod.conf.js 详细内容

五、结语

第一篇博文总在想要写点什么,就根据自己的经验加查了下文档写了这么一篇工具类的文章,由于有些插件有些用法会重复,所以按照文章先后,写过用法或者给过链接的插件,在后面的文章就省略了,有时间的建议从头开始,如果单独看某章节的话遇到不懂的语法或插件可全文查找,也可以点击更多安装包传送门进行查找阅读。本文将 vue 本身自带的英文注释删除了,但英文注释非常有用可以仔细阅读,希望对大家学习 vue 和 webpack 都有所帮助。