vue - vue-cli2脚手架搭建build文件夹详解

build.js

构建环境下的配置:
  loading动画
  删除创建目标文件夹
  webpack编译;输出信息

//这是vue-cli脚手架工具的生产环境配置入口 package.json中的"build": "node build/build.js"的直接指向。

// js的严格模式
'use strict'
                     
//检查node+npm的版本,引用./check-versions.js文件
require('./check-versions')()

//设置环境变量为生产环境
process.env.NODE_ENV = 'production'

//导入ora模块 实现loading效果,实际是一个命令行转圈圈动画插件,好看用的
const ora = require('ora')
//导入rimraf模块 以包的形式包装rm -rf命令,用来删除文件和文件夹的,不管文件是否为空
//rimraf插件是用来执行UNIX命令rm和-rf的用来删除文件夹和文件,清空旧的文件
const rm = require('rimraf')
//导入node的path模块,node.js路径模块 连接路径,例子:path.join('/foo', 'bar', 'baz/asdf', 'quux', '..');
const path = require('path')
//导入chalk模块 用来改变文字颜色
const chalk = require('chalk')
//导入webpack模块使用内置插件和webpack方法
const webpack = require('webpack')
//导入config/index.js,commonJs风格,引入文件模块,引入模块分为内置模块与文件模块两种
const config = require('../config')
//导入webpack.prod.conf
const webpackConfig = require('./webpack.prod.conf')

//实现loading的模块
const spinner = ora('building for production...')
//开始动画
spinner.start()

/*
    rm方法删除dist/static文件夹
        path.join是将路径片段以'\'连接成新的路径,任何一个路径片段有错误就会报错
        调用rm方法,第一个参数的结果就是 绝对/工程名/dist/static,表示删除这个路径下面的所有文件
        若删除中有错误则抛出异常并终止程序
        若没有错误则继续执行,构建webpack
        结束动画
        若有异常则抛出
        标准输出流,类似于console.log
*/
rm(path.join(config.build.assetsRoot, config.build.assetsSubDirectory), err => {
  //如果删除的过程中出现错误,就抛出这个错误,同时程序终止
  if (err) throw err
  //没有错误,就执行webpack编译 构建webpack
  webpack(webpackConfig, (err, stats) => {
    //停止动画
    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的版本检查

// js的严格模式
'use strict'

// 导入chalk模块 用来改变文字颜色
const chalk = require('chalk')
// 控制版本
const semver = require('semver')
// 获取版本,commonJs风格,引入文件模块,引入模块分为内置模块与文件模块两种
const packageConfig = require('../package.json')
// shell.js插件,执行unix系统命令
const shell = require('shelljs')

function exec (cmd) {
  // 脚本可以通过child_process模块新建子进程,从而执行Unix系统命令
  // 将cmd参数传递的值转换成前后没有空格的字符串,也就是版本号
  // require('child_process')调用nodejs子进程,
  // execSync同步的exec方法执行command
  return require('child_process').execSync(cmd).toString().trim()
}

//声明常量数组,数组内容为有关node相关信息的对象
const versionRequirements = [
  {
    //对象名称为node
    name: 'node',
    //使用semver插件,把版本信息转换成规定格式
    currentVersion: semver.clean(process.version),
    //规定package.json中engines选项的node版本信息
    versionRequirement: packageConfig.engines.node
  }
]

//which为linux指令,在$path规定的路径下查找符合条件的文件
//shell.which('npm')就是寻找npm命令
if (shell.which('npm')) {
  //将npm添加到versionRequirements                      
  versionRequirements.push({
    name: 'npm',
    调用npm --version命令,并且把参数返回给exec函数获取纯净版本
    //exec()方法就是执行一个命令(在这里是执行npm --version),返回版本信息的函数。
    currentVersion: exec('npm --version'),
    //规定package.json中engines选项的npm版本信息
    versionRequirement: packageConfig.engines.npm
  })
}

module.exports = function () {
  const warnings = []
  for (let i = 0; i < versionRequirements.length; i++) {
    const mod = versionRequirements[i]
    // 如果版本号不符合package.json文件中指定的版本号,就执行warning.push...
    // 当前版本号用红色标识,要求版本号用绿色标识,当前版本不符合要求版本那么就将提示信息添加到wranings
    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)
    }
    //执行失败
    process.exit(1)
  }
}

utils.js

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

'use strict'
// 引入nodejs路径模块
const path = require('path')
// 引入config下的index.js文件
const config = require('../config')
// 一个插件,抽离css样式,防止将样式打包在js中引起样式加载错乱
// 引入extract-text-webpack-plugin插件,用来将css提取到单独的css文件中
// 详情请看(1)
const ExtractTextPlugin = require('extract-text-webpack-plugin')    
const packageConfig = require('../package.json')

// exports其实就是一个对象,用来导出方法的,最终还是使用module.exports,此处导出assetsPath
exports.assetsPath = function (_path) {
  // 如果是生产环境assetsSubDirectory就是'static',否则还是'static'
  const assetsSubDirectory = process.env.NODE_ENV === 'production'
    ? config.build.assetsSubDirectory
    : config.dev.assetsSubDirectory
  // path.join返回绝对路径(在电脑上的实际位置);path.posix.join返回相对路径
  return path.posix.join(assetsSubDirectory, _path)   
  // 此方法返回一个干净的相对根路径              
}

// 下面是导出cssLoaders的相关配置
exports.cssLoaders = function (options) {
  // options如果不为null或者undefined,0,""等等就原样,否则就是{}。在js里面,||运算符,A||B,A如果为真,直接返回A。如果为假,直接返回B(不会判断B是什么类型)
  options = options || {}
   
  // cssLoader的基本配置
  const cssLoader = {
    loader: 'css-loader',
    // options是用来传递参数给loader的
    options: {
      // minimize表示压缩,如果是生产环境就压缩css代码,这里全部压缩
      // minimize: process.env.NODE_ENV === 'production',
      // 是否开启cssmap,默认是false
      sourceMap: options.sourceMap
    }
  }
    
  // 为css自动生成兼容性前缀
  const postcssLoader = {
    loader: 'postcss-loader',
    options: {
      sourceMap: options.sourceMap
    }
  }
  
  // 适配移动端屏幕自适应像素兼容性75px==1rem,这里没有用到
  // const px2remLoader = {
  //   loader: 'px2rem-loader',
  //   options: {
  //     remUnit: 75
  //   }
  // }

  // generate loader string to be used with extract text plugin
  function generateLoaders (loader, loaderOptions) {
    // 将上面的基础cssLoader配置、兼容性前缀配置、自适应配置放在一个数组里面
    // const loaders = options.usePostCSS ? [cssLoader, postcssLoader, px2remLoader] : [cssLoader, px2remLoader]
    const loaders = options.usePostCSS ? [cssLoader, postcssLoader] : [cssLoader]

    // 如果该函数传递了单独的loader就加到这个loaders数组里面,这个loader可能是less,sass之类的
    if (loader) {
      loaders.push({
        // 加载对应的loader
        loader: loader + '-loader',
        // Object.assign是es6的方法,主要用来合并对象的,浅拷贝
        options: Object.assign({}, loaderOptions, {
          sourceMap: options.sourceMap
        })
      })
    }

    // Extract CSS when that option is specified
    // (which is the case during production build)
    // 注意这个extract是自定义的属性,可以定义在options里面,主要作用就是当配置为true就把文件单独提取,
    // false表示不单独提取,这个可以在使用的时候单独配置
    if (options.extract) {
      return ExtractTextPlugin.extract({
        use: loaders,
        fallback: 'vue-style-loader'
      })
    } else {
      return ['vue-style-loader'].concat(loaders)
    }
    // 上面这段代码就是用来返回最终读取和导入loader,来处理对应类型的文件
  }

  // https://vue-loader.vuejs.org/en/configurations/extract-css.html
  return {
    // css对应 vue-style-loader 和 css-loader
    css: generateLoaders(),
    // postcss对应 vue-style-loader 和 css-loader
    postcss: generateLoaders(),
    // less对应 vue-style-loader 和 less-loader
    less: generateLoaders('less'),
    // sass对应 vue-style-loader 和 sass-loader
    sass: generateLoaders('sass', { indentedSyntax: true }),
    // scss对应 vue-style-loader 和 sass-loader
    scss: generateLoaders('sass'),
    // stylus对应 vue-style-loader 和 stylus-loader
    stylus: generateLoaders('stylus'),
    // styl对应 vue-style-loader 和 styl-loader
    styl: generateLoaders('stylus')
  }
}

// Generate loaders for standalone style files (outside of .vue)
// 下面这个主要处理import这种方式导入的文件类型的打包,上面的exports.cssLoaders是为这一步服务的
exports.styleLoaders = function (options) {
  const output = []
  // 下面就是生成的各种css文件的loader对象
  const loaders = exports.cssLoaders(options)
  for (const extension in loaders) {
    // 把每一种文件的laoder都提取出来
    const loader = loaders[extension]
    output.push({
       // 把最终的结果都push到output数组中,大事搞定
      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.notify({
      title: packageConfig.name,
      message: severity + ': ' + error.name,
      subtitle: filename || '',
      icon: path.join(__dirname, 'logo.png')
    })
  }
}

注释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

处理.vue文件的配置文件
'use strict'
const utils = require('./utils')
const config = require('../config')
// 是否为生产环境
const isProduction = process.env.NODE_ENV === 'production'
// 根据环境来获取相应的productionSourceMap或者cssSourceMap
const sourceMapEnabled = isProduction
  ? config.build.productionSourceMap
  : config.dev.cssSourceMap

// 导出
module.exports = {
  // 载入utils中的cssloaders返回配置好的css-loader和vue-style=loader
  loaders: utils.cssLoaders({
    // 是否开始sourceMap 用来调试
    sourceMap: sourceMapEnabled,
    // 是否将单独的css文件(一般为引入的外部文件)进行提取单独打包
    extract: isProduction
  }),
  // 记录压缩的代码,用来找到源码位置
  cssSourceMap: sourceMapEnabled,
  // 是否缓存破坏
  cacheBusting: config.dev.cacheBusting,
  // transformToRequire的作用是在模块编译的过程中,编译器可以将某些属性,比如src转换为require调用
  transformToRequire: {
    video: ['src', 'poster'],
    source: 'src',
    img: 'src',
    image: 'xlink:href'
  }
}

webpack.base.conf.js

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

'use strict'
// 引入node path 中间件 可以获取到 path 路径的一些信息
const path = require('path')
// 引入utils工具模块 utils主要用来处理css-loader和vue-style-loader的
const utils = require('./utils')
// 引入config下面的index文件 主要是配置一些开发环境和生产环境的配置
const config = require('../config')
// 用来解决各种css 文件 sass less stulys 等
const vueLoaderConfig = require('./vue-loader.conf')

// 定义了一个路径函数 返回当前的目录的平行目录下的dir 因为有'..'也就是获取绝对路径,方便对import时引入地址的方便填写
function resolve(dir) {
  return path.join(__dirname, '..', dir)
}

// eslint 的检测规则
const createLintingRule = () => ({
  // 对js和vue 文件进行eslint 检查
  test: /\.(js|vue)$/,
  // 使用eslint-loader
  loader: 'eslint-loader',
  // enforce执行的意思 有两个值 pre post
  // pre是在其他规则执行之前执行 post是在其他规则执行之后执行
  enforce: 'pre',
  // 进行检测的文件目录包括哪些 调用了路径函数
  include: [resolve('src'), resolve('test')],
  options: {
    // 使用第三方的插件进行eslint 检测
    formatter: require('eslint-friendly-formatter'),
    // 是否输出eslint报错信息
    emitWarning: !config.dev.showEslintErrorsInOverlay
  }
})

// webpack的配置,可以理解成是开发环境和正式环境的一些公共配置
module.exports = {
  // webpack 解析时根目录地址如果此文件在跟目录这句话就不用写
  context: path.resolve(__dirname, '../'),
  // 项目的入口文件
  entry: {
    app: './src/main.js'
  },
  // 项目出口文件配置
  output: {
    // 项目buid的出口文件的目录地址,这里指的是(../dist文件目录) 引入的是config.build 下面的配置
    path: config.build.assetsRoot,
    // 文件的名字
    filename: '[name].js',
    // 输出解析文件的目录,url 相对于 HTML 页面(生成的html文件中,css和js等静态文件的url前缀)
    publicPath: process.env.NODE_ENV === 'production'
      ? config.build.assetsPublicPath
      : config.dev.assetsPublicPath
  },
  resolve: {
    // 指定哪些文件在引用时可以省略后缀名
    extensions: ['.js', '.vue', '.json'],
    // 别名,在引用文件时 使用别名代理真实目录 后面再在目录时以别名代替
    alias: {
      'vue$': 'vue/dist/vue.esm.js',
      '@': resolve('src'),
    }
  },
  module: {
    // 转换解析规则
    // 1.test是用来解析所有此后缀名的文件,
    // 2.loader我们用什么npm什么形式的loader去解析
    // 3.include是代表我们解析的文件只包含那些东西
    // 4.options解析文件参数设置 具体看下面的解释
    rules: [
      ...(config.dev.useEslint ? [createLintingRule()] : []),
      {
        test: /\.vue$/,
        loader: 'vue-loader',
        // 对vue的css进行解析
        options: vueLoaderConfig
      },
      {
        // 对js文件使用babel-loader进行解析 主要是解析es6
        test: /\.js$/,
        loader: 'babel-loader',
        include: [resolve('src'), resolve('test'), resolve('node_modules/webpack-dev-server/client')]
      },
      {
        // 对图片使用url-loader解析 这个插件的作用是将一个足够小的文件生成一个64位的DataURL
        // 当limit小于10000进行将图片生成base64
        // name 指的是引入的utils里面的一个方法 将name ,7位哈希 .ext 代表后缀名
        // 传入方法 返回结果是 /static/img/[name].[hash:7].[ext]
        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: {
    // prevent webpack from injecting useless setImmediate polyfill because Vue
    // source contains it (although only uses it if it's native).
    setImmediate: false,
    // prevent webpack from injecting mocks to Node native modules
    // that does not make sense for the client
    // 是否 polyfill 或 mock
    // 源包含它(虽然仅在本地使用)
    // 预防webpack从注入模拟节点到node原生模块
    dgram: 'empty',
    fs: 'empty',
    net: 'empty',
    tls: 'empty',
    child_process: 'empty'
  },
  // webpack不处理下面的库,压缩包的大小,但是依然可以引用
  externals: {
    'AMap': 'AMap',
  }
}

webpack.dev.conf

'use strict'
// 引入当前目录下的utils.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')
// 生成html文件
const HtmlWebpackPlugin = require('html-webpack-plugin')
// 下面这个插件是用来把webpack的错误和日志收集起来,漂亮的展示给用户
const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin')
// 一个自动打开可用端口的包
const portfinder = require('portfinder')

// 当前环境的host
const HOST = process.env.HOST
// //当前环境的port
const PORT = process.env.PORT && Number(process.env.PORT)

// 开发环境的配置
const devWebpackConfig = merge(baseWebpackConfig, {
  module: {
    // loader的配置,创建模块时匹配请求的规则数组,这里调用了utils中的配置模板styleLoaders
    rules: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap, usePostCSS: true })
  },
  // cheap-module-eval-source-map is faster for development
  // //debtool是开发工具选项,用来指定如何生成sourcemap文件,cheap-module-eval-source-map此款soucemap文件性价比最高
  devtool: config.dev.devtool,

  // these devServer options should be customized in /config/index.js
  // webpack服务器配置
  devServer: {
    // 重新加载server时,控制台对一些错误以warning的方式提示
    clientLogLevel: 'warning',
    // 当使用 HTML5 History API 时,任意的 404 响应都可能需要被替代为 index.html
    historyApiFallback: {
      rewrites: [
        { from: /.*/, to: path.posix.join(config.dev.assetsPublicPath, 'index.html') },
      ],
    },
    // 是否启用webpack的模块热替换特性。这个功能主要是用于开发过程中,对生产环境无帮助。效果上就是界面无刷新更新。
    hot: true,
    // 告诉服务器从哪里提供内容。只有在你想要提供静态文件时才需要,这里我们禁用
    contentBase: false, // since we use CopyWebpackPlugin.
    // 一切服务都启用gzip压缩
    compress: true,
    // 指定一个host,默认是localhost。如果有全局host就用全局,否则就用index.js中的设置。
    host: HOST || config.dev.host,
    // 指定端口
    port: PORT || config.dev.port,
    // 是否在浏览器开启本dev server,是否自动打开浏览器
    open: config.dev.autoOpenBrowser,
    // 当有编译器错误时,是否在浏览器中显示全屏覆盖。
    overlay: config.dev.errorOverlay
      ? { warnings: false, errors: true }
      : false,
    // 静态内容的路径,此路径下的打包文件可在浏览器中访问
    publicPath: config.dev.assetsPublicPath,
    // 如果你有单独的后端开发服务器api,并且希望在同域名下发送api请求,那么代理某些URL会很有用。
    // 接口代理
    proxy: config.dev.proxyTable,
    // 启用 quiet 后,除了初始启动信息之外的任何内容都不会被打印到控制台。这也意味着来自 webpack 的错误或警告在控制台不可见
    quiet: true, // necessary for FriendlyErrorsPlugin
    // webpack 使用文件系统(file system)获取文件改动的通知。在某些情况下,不会正常工作。例如,当使用 Network File System (NFS) 时。Vagrant 也有很多问题。在这些情况下使用轮询。
    // 监视文件的选项
    watchOptions: {
      poll: config.dev.poll, //是否使用轮询
    }
  },
  plugins: [
    // DefinePlugin 允许创建一个在编译时可以配置的全局常量。这里生成了一个当前环境的常量
    new webpack.DefinePlugin({
      'process.env': require('../config/dev.env')
    }),
    // 模块热替换插件,修改模块时不需要刷新页面
    new webpack.HotModuleReplacementPlugin(),
    // 当开启 HMR 的时候使用该插件会显示模块的相对路径
    new webpack.NamedModulesPlugin(), // HMR shows correct file names in console on update.
    // 在编译出现错误时,使用 NoEmitOnErrorsPlugin 来跳过输出阶段。这样可以确保输出资源不会包含错误。
    new webpack.NoEmitOnErrorsPlugin(),
    // https://github.com/ampedandwired/html-webpack-plugin
    // 模块HtmlWebpackPlugin
    new HtmlWebpackPlugin({
      filename: 'index.html', //生成的文件的名称
      template: 'index.html', //可以指定模块html文件
      inject: true  //打包后js文件放在body的最后
    }),
    // copy custom static assets
    // 将static的内容拷贝到开发路径,忽略这个文件夹下“.XX”的文件
    // 模块CopyWebpackPlugin  将单个文件或整个文件复制到构建目录
    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 {
      // 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: { // build成功的话会执行者块
          messages: [`Your application is running here: http://${devWebpackConfig.devServer.host}:${port}`],
        },
        onErrors: config.dev.notifyOnErrors // 如果出错就执行这块,其实是utils里面配置好的提示信息
        ? utils.createNotifierCallback()
        : undefined
      }))

      resolve(devWebpackConfig)
    }
  })
})

webpack.prod.conf

'use strict'
const path = require('path')
// utils工具配置文件,主要用来处理css类文件的loader
const utils = require('./utils')
const webpack = require('webpack')
// config目录下的index.js配置文件,主要用来定义了生产和开发环境的相关基础配置
const config = require('../config')
// 下面是webpack的merger插件,主要用来处理配置对象合并的,可以将一个大的配置对象拆分成几个小的,合并,相同的项将覆盖
const merge = require('webpack-merge')
const baseWebpackConfig = require('./webpack.base.conf')
// copy-webpack-plugin使用来复制文件或者文件夹到指定的目录的
const CopyWebpackPlugin = require('copy-webpack-plugin')
// html-webpack-plugin是生成html文件,可以设置模板
const HtmlWebpackPlugin = require('html-webpack-plugin')
// extract-text-webpack-plugin这个插件是用来将bundle中的css等文件产出单独的bundle文件的
const ExtractTextPlugin = require('extract-text-webpack-plugin')
// optimize-css-assets-webpack-plugin插件的作用是压缩css代码的,还能去掉extract-text-webpack-plugin插件抽离文件产生的重复代码,因为同一个css可能在多个模块中出现所以会导致重复代码,换句话说这两个插件是两兄弟
// 详情见(1)
const OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin')
// 压缩js 的插件
const UglifyJsPlugin = require('uglifyjs-webpack-plugin')

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: [
    // http://vuejs.github.io/vue-loader/en/workflow/production.html
    // 下面是利用DefinePlugin插件,定义process.env环境变量为env
    new webpack.DefinePlugin({
      'process.env': env
    }),
    // UglifyJsPlugin插件是专门用来压缩js文件的
    new UglifyJsPlugin({
      uglifyOptions: {
        compress: {
          warnings: false // 禁止压缩时候的警告信息,给用户一种vue高大上没有错误的感觉
        }
      },
       // 压缩后生成map文件
      sourceMap: config.build.productionSourceMap,
      parallel: true
    }),
    // extract css into its own file
    new ExtractTextPlugin({
      // 生成独立的css文件,下面是生成独立css文件的名称
      filename: utils.assetsPath('css/[name].[contenthash].css'),
      // Setting the following option to `false` will not extract CSS from codesplit chunks.
      // Their CSS will instead be inserted dynamically with style-loader when the codesplit chunk has been loaded by webpack.
      // It's currently set to `true` because we are seeing that sourcemaps are included in the codesplit bundle as well when it's `false`, 
      // increasing file size: https://github.com/vuejs-templates/webpack/issues/1110
      allChunks: true,
    }),
    // Compress extracted CSS. We are using this plugin so that possible
    // duplicated CSS from different components can be deduped.
    // 压缩css
    new OptimizeCSSPlugin({
      cssProcessorOptions: config.build.productionSourceMap
        ? { safe: true, map: { inline: false } }
        : { 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
    // 在dist目录生成html文件
    new HtmlWebpackPlugin({
      filename: config.build.index,
      template: 'index.html',
      // 将js文件放到body标签的结尾
      inject: true,
      minify: {
        // 压缩产出后的html页面
        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
      // 分类要插到html页面的模块
      chunksSortMode: 'dependency'
    }),
    // keep module.id stable when vendor modules does not change
    // 该插件会根据模块的相对路径生成一个四位数的hash作为模块id, 建议用于生产环境
    new webpack.HashedModuleIdsPlugin(),
    // enable scope hoisting
    // 去 webpack 打包时的一个取舍是将 bundle 中各个模块单独打包成闭包。这些打包函数使你的 JavaScript 在浏览器中处理的更慢。相比之下,一些工具像 Closure Compiler 和 RollupJS 可以提升(hoist)或者预编译所有模块到一个闭包中,提升你的代码在浏览器中的执行速度。
    new webpack.optimize.ModuleConcatenationPlugin(),
    // split vendor js into its own file
    // 将第三方的包分离出来
    new webpack.optimize.CommonsChunkPlugin({
      // common 模块的名称
      name: 'vendor',
      minChunks (module) {
        // any required modules inside node_modules are extracted to vendor
        // 将所有依赖于node_modules下面文件打包到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
    // 为了避免每次更改项目代码时导致venderchunk的chunkHash改变,我们还会单独生成一个manifestchunk
    new webpack.optimize.CommonsChunkPlugin({
      name: 'manifest',
      minChunks: Infinity
    }),
    // This instance extracts shared chunks from code splitted chunks and bundles them
    // in a separate chunk, similar to the vendor chunk
    // see: https://webpack.js.org/plugins/commons-chunk-plugin/#extra-async-commons-chunk
    // 主要逻辑的js文件
    new webpack.optimize.CommonsChunkPlugin({
      name: 'app',
      async: 'vendor-async',
      children: true,
      minChunks: 3
    }),

    // copy custom static assets
    // 拷贝资源
    new CopyWebpackPlugin([
      {
        from: path.resolve(__dirname, '../static'),
        to: config.build.assetsSubDirectory,
        ignore: ['.*']
      }
    ])
  ]
})

// 开启Gzi压缩打包后的文件
if (config.build.productionGzip) {
  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

注释1

// optimize-css-assets-webpack-plugin插件
// 在生产环境中使用extract-text-webpack-plugin,最好也使用这个插件
// 使用方法如下
// 安装 npm install --save-dev optimize-css-assets-webpack-plugin
// 还要安装 cssnano 这是一个css编译器 npm install --save-dev cssnano 这个vue-cli脚手架并没有使用cssnano
new OptimizeCssAssetsPlugin({
  assetNameRegExp: /\.optimize\.css$/g, // 不写默认是/\.css$/g
  cssProcessor: require('cssnano'), // 编译器选项,不写默认是cssnano,所以使用这个插件不管怎样都要cssnano
  cssProcessorOptions: { discardComments: {removeAll: true } }, // 传递给编译器的参数
  canPrint: true // 是否能够输出信息
})

 

你可能感兴趣的:(vue - vue-cli2脚手架搭建build文件夹详解)