webpack 的运行流程 npm run dev (webpack-dev-server --inline --progress --config build/webpack.dev.conf.js) 在 配置了一些参数 调用 npm run build(node build/build.js) 引入了配置文件,如 npm run dev 流程中的配置文件 调用 webpack.prod.con…

vue2.5 webpack3.6 开启了单元测试框架jest 开启了Eslint检查


webpack 的运行流程

  • npm run dev (webpack-dev-server --inline --progress --config build/webpack.dev.conf.js)
  1. webpack.base.conf.js配置了一些参数
module.exports = {
context:""
// 配置入口,默认为单页面所以只有app一个入口
entry:{}
// 配置出口
output:{}
// 自动的扩展后缀,创建路径的别名
resolve:{}
// 使用插件配置相应文件的处理方法,设置哪些文件请求哪些解析器
module{}
}
  1. 调用webpack.dev.conf.js
const baseWebpackConfig = require('./webpack.base.conf')
const devWebpackConfig = merge(baseWebpackConfig, {
  // 一些规则工具
  module:{},
  // 选择一种 source map 格式来增强调试过程
  devtool:"",
  // 新增配置项
  devServer:{},
  // 一些插件
  plugins{}
})
module.exports = new Promise((resolve, reject) => {
  // 缓存端口号
  portfinder.basePort = process.env.PORT || config.dev.port
  // 查找端口号
  portfinder.getPort((err, port) => {
    if (err) {
      reject(err)
    } else {
      // 设置端口
      process.env.PORT = port
      // 端口被占用时就重新设置env的端口
      devWebpackConfig.devServer.port = port

      // 友好地输出信息
      devWebpackConfig.plugins.push(new FriendlyErrorsPlugin({
        compilationSuccessInfo: {
          messages: [`Your application is running here: http://${config.dev.host}:${port}`],
        },
        onErrors: config.dev.notifyOnErrors
        ? utils.createNotifierCallback()
        : undefined
      }))

      resolve(devWebpackConfig)
    }
  })
})
  • npm run build(node build/build.js)
  1. 引入了配置文件,如 npm run dev 流程中的配置文件
  2. 调用 webpack.prod.conf.js,插件会比 webpack.dev.conf.js 多一点,同时也进行了是否开启 gzip 的操作
const baseWebpackConfig = require('./webpack.base.conf')
const devWebpackConfig = merge(baseWebpackConfig, {
  // 一些规则工具
  module:{},
  // 选择一种 source map 格式来增强调试过程
  devtool:"",
  // 新增配置项
  devServer:{},
  // 一些插件
  plugins{}
})
if (config.build.productionGzip) {
  const CompressionWebpackPlugin = require('compression-webpack-plugin')

  webpackConfig.plugins.push(
    // 提供带 Content-Encoding 编码的压缩版的资源

    new CompressionWebpackPlugin({
      // 目标资源名称。
      // [file] 会被替换成原始资源名称。
      // [path] 会被替换成原始资源路径
      // [query] 会被替换成原始查询字符串
      asset: '[path].gz[query]',
      algorithm: 'gzip',
      // 处理匹配到的资源
      test: new RegExp(
        '\\.(' +
        config.build.productionGzipExtensions.join('|') +
        ')$'
      ),
      // 只处理比这个值大的资源
      threshold: 10240,
      // 只有压缩率比这个值小的资源才会被处理
      minRatio: 0.8
    })
  )
}
  1. 执行build.js,每次执行都先要删了原来的 build 文件
const webpackConfig = require('./webpack.prod.conf');
// 先删除dist文件再生成新文件,因为有时候会使用hash来命名,删除整个文件可避免冗余
rm(path.join(config.build.assetsRoot, config.build.assetsSubDirectory), (err) => {
  if (err) throw err;
  webpack(webpackConfig, function(err, stats) {
    spinner.stop();
    if (err) throw err;
    process.stdout.write(
      stats.toString({
        colors: true,
        modules: false,
        children: false,
        chunks: false,
        chunkModules: false
      }) + '\n\n'
    );

    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"));
  });
});

package.json

{
  // 项目名称,不能以._开头,不能包含大写字母,不能与现有项目重复
  "name": "vue-item",
  // 版本号:大版本.次要版本.小版本
  "version": "1.0.0",
  // 项目描述
  "description": "A Vue.js project",
  // 作者名字
  "author": "dirkhe1051931999 <1051931999@qq.com>",
  // 是否私有
  "private": true,
  // 控制台运行脚本的缩写
  "scripts": {
    // webpack-dev-server 启动http静态文件服务器,不会刷新浏览器
    // 1. webpack-dev-server --inline 前端代码变动的时,刷新浏览器,hash和chunkhash都可以使用
    // 2. webpack-dev-server --hot    前端代码变动的时,不刷新浏览器,只把变化的部分替换掉,只能使用hash,如果使用chunkhash会报错
    // 3. webpack-dev-server  --inline --hot 前端代码变动的时,重新加载改变的部分,HRM失败则刷新页面
    // --progress 显示打包进度
    "dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js",
    "start": "npm run dev",
    "unit": "jest test/unit/specs --coverage",
    "test": "npm run unit",
    "lint": "eslint --ext .js,.vue src test/unit/specs",
    "build": "node build/build.js"
  },
  // 项目依赖库:生产环境使用的框架,发布之后还依赖的,比如vue,jq等
  "dependencies": {
    "vue": "^2.5.2",
    "vue-resource": "^1.3.4",
    "vue-router": "^3.0.1",
    "vuex": "^3.0.1"
  },
  // 开发依赖库:只是在开发环境依赖的,比如babel-core babel等
  "devDependencies": {
    //作为postcss插件用来解析css补充前缀,比如display: flex会补充为display:-webkit-box;
    "autoprefixer": "^7.1.2",
    // babel核心,把js代码解析成AST(抽象语法树)
    "babel-core": "^6.22.1",
    // 是一个Babel parser的包装器,这个包装器使得Babel parser可以和ESLint协调工作
    "babel-eslint": "^7.1.1",
    // babel-loader加载器来编译所有的js代码,而无需手动引入babel-core
    "babel-loader": "^7.1.1",
    // 当你使用 generators/async 函数时,自动引入 babel-runtime/regenerator (使用 regenerator 运行时而不会污染当前环境) 。
    // 自动引入 babel-runtime/core-js 并映射 ES6 静态方法和内置插件(实现polyfill的功能且无全局污染,但是实例方法无法正常使用,如 “foobar”.includes(“foo”) )。
    // 移除内联的 Babel helper 并使用模块 babel-runtime/helpers 代替(提取babel转换语法的代码)。
    // {
    //   "plugins": [
    //     ["transform-runtime", {
    //       "helpers": true, // default
    //       "polyfill": true, // default
    //       "regenerator": true, // defalut
    //       "moduleName": "babel-runtime" // default
    //     }]
    //   ]
    // }
    "babel-plugin-transform-runtime": "^6.22.0",
    // 仅仅转换 新版的语法 如:箭头函数,并不转换新版api 如:Array.include
    "babel-preset-env": "^1.3.2",
    // stage-x分别代表的是stage-0、stage-1、stage-2、stage-3进入EMAC标准之前的4个阶段
    "babel-preset-stage-2": "^6.22.0",
    // 模块改写require命令,为它加上一个钩子。此后,每当使用require加载.js、.jsx、.es和.es6后缀名的文件,就会先用Babel进行转码。
    "babel-register": "^6.22.0",
    // 来在命令行输出不同颜色文字
    "chalk": "^2.0.1",
    // 拷贝资源和文件
    "copy-webpack-plugin": "^4.0.1",
    // webpack先用css-loader据诶系后缀为css的文件,再使用style-loader,生成最终解析完的css代码的style标签,放入head中
    "css-loader": "^0.28.0",
    // 一些开启语法检测的插件
    "eslint": "^3.19.0",
    "eslint-friendly-formatter": "^3.0.0",
    "eslint-loader": "^1.7.1",
    "eslint-plugin-html": "^3.0.0",
    "eslint-config-standard": "^10.2.1",
    "eslint-plugin-promise": "^3.4.0",
    "eslint-plugin-standard": "^3.0.1",
    "eslint-plugin-import": "^2.7.0",
    "eslint-plugin-node": "^5.2.0",
    "eventsource-polyfill": "^0.9.6",
    //将一个以上的包里面的文本提取到单独文件中
    "extract-text-webpack-plugin": "^3.0.0",
    // 打包压缩文件,与url-loader用法类似
    "file-loader": "^1.1.4",
    // 识别某些类别的webpack错误和清理,聚合和优先排序,以提供更好的开发经验
    "friendly-errors-webpack-plugin": "^1.6.1",
    // 简化了HTML文件的创建,引入了外部资源,创建html的入口文件,可通过此项进行多页面的配置
    "html-webpack-plugin": "^2.30.1",
    // 可视化webpack输出文件的大小
    "webpack-bundle-analyzer": "^2.9.0",
    "babel-jest": "^21.0.2",
    "jest": "^21.2.0",
    "vue-jest": "^1.0.2",
    // 支持使用node发送跨平台的本地通知
    "node-notifier": "^5.1.2",
    //可以消耗本地文件、节点模块或web_modules
    "postcss-import": "^11.0.0",
    //用来兼容css的插件
    "postcss-loader": "^2.0.8",
    //用来对特定的版本号做判断的
    "semver": "^5.3.0",
    // 使用它来消除shell脚本在UNIX上的依赖性,同时仍然保留其熟悉和强大的命令,即可执行Unix系统命令
    "shelljs": "^0.7.6",
    // 压缩提取出的css,并解决ExtractTextPlugin分离出的js重复问题(多个文件引入同一css文件)
    "optimize-css-assets-webpack-plugin": "^3.2.0",
    // 实现node.js 命令行环境的 loading效果, 和显示各种状态的图标等
    "ora": "^1.2.0",
    // 节点的UNIX命令RM—RF,强制删除文件或者目录的命令
    "rimraf": "^2.6.0",
    // 压缩文件,可将图片转化为base64
    "url-loader": "^0.5.8",
    // 编译.vue的文件,打包成浏览器认识的js代码
    "vue-loader": "^13.3.0",
    // 检查这个组件是否已经具有服务器内联(server-inlined)的 CSS - 如果没有,CSS 将通过 <style> 标签动态注入。
    "vue-style-loader": "^3.0.1",
    // 这个包可以用来预编译VUE模板到渲染函数,以避免运行时编译开销和CSP限制
    "vue-template-compiler": "^2.5.2",
    // 是用来查看端口是否被占用
    "portfinder": "^1.0.13",
    "webpack": "^3.6.0",
    //提供一个提供实时重载的开发服务器
    "webpack-dev-server": "^2.9.1",
    // 将数组和合并对象创建一个新对象
    "webpack-merge": "^4.1.0"
  },
  // 自动化测试之前端js单元测试框架jest
  "jest": {
    "moduleFileExtensions": ["js", "json", "vue"],
    "moduleNameMapper": {
      "^@/(.*)$": "<rootDir>/src/$1"
    },
    "transform": {
      "^.+\\.js$": "<rootDir>/node_modules/babel-jest",
      ".*\\.(vue)$": "<rootDir>/node_modules/vue-jest"
    },
    "setupFiles": ["<rootDir>/test/unit/setup"],
    "mapCoverage": true,
    "coverageDirectory": "<rootDir>/test/unit/coverage",
    "collectCoverageFrom": ["src/**/*.{js,vue}", "!src/main.js", "!src/router/index.js", "!**/node_modules/**"]
  },
  //engines是引擎,指定node和npm版本
  "engines": {
    "node": ">= 4.0.0",
    "npm": ">= 3.0.0"
  },
  //限制了浏览器或者客户端需要什么版本才可运行
  "browserslist": ["> 1%", "last 2 versions", "not ie <= 8"]
}

.babelrc

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

{
  // 制定转码的规则
  "presets": [
    // env是使用babel-preset-env插件将js进行转码成es5
    // 设置不转码的AMD,common的模块文件
    ["env", {
      "modules": false
    }],
    "stage-2"
  ],
  "plugins": ["transform-runtime"],
  "env": {
    "test": {
      "presets": ["env", "stage-2"]    }
  }
}

.editorconfig

编辑器的配置文件

root = true

[*]
charset = utf-8
indent_style = space
indent_size = 2
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true

.postcssrc.js

.postcssrc.js 文件其实是 postcss-loader 包的一个配置,里面写进去需要使用到的插件

module.exports = {
  plugins: {
    // to edit target browsers: use "browserslist" field in package.json
    'postcss-import': {},
    autoprefixer: {}
  }
};

config/dev.env.js

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

// 合并函数
const merge = require('webpack-merge');
const prodEnv = require('./prod.env');
// 可以将数据和对象合并,最终合并成为一个对象,如果有数组,那么对象的key就是数组的索引
module.exports = merge(prodEnv, {
  NODE_ENV: '"development"'
});

config/index.js

在 webpack 压缩的时候配置的参数文件

const path = require('path');

module.exports = {
  // 开发环境下面的配置
  dev: {
    // 子目录,一般存放css,js,image等文件
    assetsSubDirectory: 'static',
    // 根目录
    assetsPublicPath: '/',
    // 可利用该属性解决跨域的问题
    proxyTable: {},

    // 地址
    host: 'localhost',
    // 端口
    port: 8080,
    // 是否在浏览器中自动打开页面
    autoOpenBrowser: false,
    // 浏览器错误提示
    errorOverlay: true,
    // 跨平台错误提示
    notifyOnErrors: true,
    // 使用文件系统(file system)获取文件改动的通知devServer.watchOptions
    poll: false,

    // Eslit的配置
    useEslint: false,
    showEslintErrorsInOverlay: false,
    /**
     * Source Maps
     */
    // 增加调试,该属性为原始源代码(仅限行)不可在生产环境中使用
    devtool: 'eval-source-map',
    // 使缓存失效
    cacheBusting: true,
    // 记录压缩前后的位置信息记录,当产生错误时直接定位到未压缩前的位置
    cssSourceMap: false
  },
  // 生产环境下面的配置
  build: {
    // index编译后生成的位置和名字
    index: path.resolve(__dirname, '../dist/index.html'),

    // 编译后存放生成环境代码的位置
    assetsRoot: path.resolve(__dirname, '../dist'),
    // js,css,images存放文件夹名
    assetsSubDirectory: 'static',
    // 发布的根目录
    assetsPublicPath: '/',
    // 打包后要不要的.map文件
    productionSourceMap: true,
    // 选择一种 source map 格式来增强调试过程。不同的值会明显影响到构建(build)和重新构建(rebuild)的速度
    devtool: '#source-map',

    // unit的gzip命令用来压缩文件,gzip模式下需要压缩的文件的扩展名有js和css
    productionGzip: false,
    productionGzipExtensions: ['js', 'css'],
    // 在打包的时候输入npm run build --report,就可以在打包的同时查看每个打包生成的js,里面的库的构成情况,
    // 方便我们进行相关的删减,比如有的库太大了,是否可以自己实现,有的库是否有必要,可否删除之类。
    bundleAnalyzerReport: process.env.npm_config_report
  }
};

config/prod.env.js

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

// 当开发时调取dev.env.js的开发环境配置,发布时调用prod.env.js的生产环境配置
module.exports = {
  NODE_ENV: '"production"'
};

build/build.js

构建生产版本

// 执行检查版本,有问题会报警告
require('./check-versions')();
// 设置当前是生产环境
process.env.NODE_ENV = 'production';
// 实现node.js 命令行环境的 loading效果, 和显示各种状态的图标等
const ora = require('ora');
// 节点的UNIX命令RM—RF,强制删除文件或者目录的命令
const rm = require('rimraf');
const path = require('path');
// 来在命令行输出不同颜色文字
const chalk = require('chalk');
const webpack = require('webpack');
// 默认读取下面的index.js文件
const config = require('../config');
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, function(err, stats) {
    spinner.stop();
    if (err) throw err;
    process.stdout.write(
      stats.toString({
        colors: true,
        modules: false,
        children: false,
        chunks: false,
        chunkModules: false
      }) + '\n\n'
    );

    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"));
  });
});

build/check-versions.js

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

// 来在命令行输出不同颜色文字
const chalk = require('chalk');
// 用来对特定的版本号做判断的
const semver = require('semver');
// 导入package.json文件
const packageConfig = require('../package.json');
// 使用它来消除shell脚本在UNIX上的依赖性,同时仍然保留其熟悉和强大的命令,即可执行Unix系统命令
const shell = require('shelljs');

function exec(cmd) {
  // 通过child_process模块的新建子进程
  // 进而执行 Unix 系统命令后转成没有空格的字符串
  return require('child_process')
    .execSync(cmd)
    .toString()
    .trim();
}
// 把格式化的版本缓存下来
const versionRequirements = [
  {
    name: 'node',
    //使用semver格式化版本
    currentVersion: semver.clean(process.version),
    // 获取package.json中设置的node版本
    versionRequirement: packageConfig.engines.node
  }
];
// 判定npm命令是否可用
if (shell.which('npm')) {
  // 自动调用npm --version命令,并且把参数返回给exec函数,从而获取纯净的版本号
  versionRequirements.push({
    name: 'npm',
    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];
    // 如果版本号不符合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);
  }
};

build/utils.js

主要有 1.cssLoaders: 用于加载.vue 文件中的样式和 2.styleLoaders:对.vue 文件之外的 css 文件或 css 预处理器文件进行处理

'use strict';
const path = require('path');
// 默认读取下面的index.js文件
const config = require('../config');
// 将一个以上的包里面的文本提取到单独文件中
const ExtractTextPlugin = require('extract-text-webpack-plugin');
// 导入package.json文件
const pkg = require('../package.json');
// 根据环境判断开发环境和生产环境,进而返回是生产环境的静态资源目录,还是开发环境的静态资源目录
exports.assetsPath = function(_path) {
  const assetsSubDirectory = process.env.NODE_ENV === 'production' ? config.build.assetsSubDirectory : config.dev.assetsSubDirectory;
  // path.posix,提供对路径方法的可移植性操作系统接口,特定实现的访问
  // path.join:用于连接路径,会正确使用当前系统的路径分隔符,Unix系统是"/",Windows系统是""
  return path.posix.join(assetsSubDirectory, _path);
};
// 用于加载.vue文件中的样式
exports.cssLoaders = function(options) {
  options = options || {};
  // cssloader
  const cssLoader = {
    loader: 'css-loader',
    // loader的选项配置
    options: {
      //生成环境下压缩文件
      minimize: process.env.NODE_ENV === 'production',
      // 根据参数是否生成sourceMap文件
      sourceMap: options.sourceMap
    }
  };
  // postcssLoader
  var postcssLoader = {
    loader: 'postcss-loader',
    options: {
      // 根据参数是否生成sourceMap文件
      sourceMap: options.sourceMap
    }
  };

  // generate loader string to be used with extract text plugin
  function generateLoaders(loader, loaderOptions) {
    // 选择默认loader
    const loaders = options.usePostCSS ? [cssLoader, postcssLoader] : [cssLoader];
    // 如果有css预处理器文件(如less、sass、stylus等)
    if (loader) {
      loaders.push({
        // 使用该预处理器对应的加载器
        loader: loader + '-loader',
        // 将css加载器初始配置与该预处理器的特殊配置进行合并
        options: Object.assign({}, loaderOptions, {
          // 根据参数是否生成sourceMap文件
          sourceMap: options.sourceMap
        })
      });
    }

    // 是否将入口文件(main.js)中引入的css文件一起打包进该文件的css中
    // 根据传入的options.extract的值进行判断(一般在生产环境中会去单独打包)
    if (options.extract) {
      return ExtractTextPlugin.extract({
        use: loaders,
        fallback: 'vue-style-loader'
      });
    } else {
      return ['vue-style-loader'].concat(loaders);
    }
  }

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

// 对.vue文件之外的css文件或css预处理器文件进行处理
exports.styleLoaders = function(options) {
  const output = [];
  const loaders = exports.cssLoaders(options);
  // 遍历cssLoaders返回出来的所有文件类型
  for (const extension in loaders) {
    const loader = loaders[extension];
    // 对每一个文件类型用其相对应的加载器进行处理
    output.push({
      test: new RegExp('\\.' + extension + '$'),
      use: loader
    });
  }
  return output;
};
// 是一个跨平台系统通知的页面,当遇到错误时,它能用系统原生的推送方式给你推送信息
// 就是编辑器右上角的警告通知
exports.createNotifierCallback = function() {
  const notifier = require('node-notifier');

  return (severity, errors) => {
    if (severity !== 'error') {
      return;
    }
    const error = errors[0];

    const filename = error.file.split('!').pop();
    notifier.notify({
      title: pkg.name,
      message: severity + ': ' + error.name,
      subtitle: filename || '',
      // 这个右上角警告的那个图片
      icon: path.join(__dirname, 'logo.png')
    });
  };
};

build/vue-loader.conf.js

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

// 获取到公共方法
const utils = require('./utils');
// 获取到配置文件,默认访问index
const config = require('../config');
// 获取到当前环境
const isProduction = process.env.NODE_ENV === 'production';
// 打包后要不要的.map文件
const sourceMapEnabled = isProduction ? config.build.productionSourceMap : config.dev.cssSourceMap;

module.exports = {
  // 处理项目中的css文件,生产环境和测试环境默认是打开sourceMap
  loaders: utils.cssLoaders({
    sourceMap: sourceMapEnabled,
    // 提取样式到单独文件只有在生产环境中才需要
    extract: isProduction
  }),
  cssSourceMap: sourceMapEnabled,
  // 在模版编译过程中,编译器可以将某些属性,如 src 路径,转换为require调用,以便目标资源可以由 webpack 处理
  transformToRequire: {
    video: 'src',
    source: 'src',
    img: 'src',
    image: 'xlink:href'
  }
};

build/webpack.base.conf.js

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

const path = require('path');
// 获取到公共方法
const utils = require('./utils');
// 获取到配置文件,默认访问index
const config = require('../config');
// 获取到当前环境
const vueLoaderConfig = require('./vue-loader.conf');
//拼接出绝对路径
function resolve(dir) {
  return path.join(__dirname, '..', dir);
}

module.exports = {
  // 而path.resolve将以/开始的路径片段作为根目录,在此之前的路径将会被丢弃
  // path.join('/a', '/b')
  // path.resolve('/a', '/b')
  // 等于'/b'
  context: path.resolve(__dirname, '../'),
  // 配置入口,默认为单页面所以只有app一个入口
  entry: {
    app: './src/main.js'
  },
  // 配置出口,
  output: {
    // 默认是/dist作为目标文件夹的路径
    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'],
    // 创建路径的别名
    alias: {
      vue$: 'vue/dist/vue.esm.js',
      '@': resolve('src')
    }
  },
  module: {
    // 使用插件配置相应文件的处理方法
    rules: [
      // ESlint的报错提示
      ...(config.dev.useEslint
        ? [
            {
              test: /\.(js|vue)$/,
              loader: 'eslint-loader',
              enforce: 'pre',
              include: [resolve('src'), resolve('test')],
              options: {
                formatter: require('eslint-friendly-formatter'),
                emitWarning: !config.dev.showEslintErrorsInOverlay
              }
            }
          ]
        : []),
      // 使用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')]
      },
      // 图片、音像、字体都使用url-loader进行处理,超过10000b会编译成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]')
        }
      }
    ]
  }
};

build/webpack.dev.conf.js

// 引入公共模块
const utils = require('./utils');
// 导入webpack
const webpack = require('webpack');
// 导入配置
const config = require('../config');
// 可以将数据和对象合并,最终合并成为一个对象,如果有数组,那么对象的key就是数组的索引
const merge = require('webpack-merge');
// 引入基础文件
const baseWebpackConfig = require('./webpack.base.conf');
// 简化了HTML文件的创建,引入了外部资源,创建html的入口文件,可通过此项进行多页面的配置
const HtmlWebpackPlugin = require('html-webpack-plugin');
// 识别某些类别的webpack错误和清理,聚合和优先排序,美化webpack的错误信息和日志的插件
const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin');
// 是用来查看端口是否被占用
const portfinder = require('portfinder');

const devWebpackConfig = merge(baseWebpackConfig, {
  module: {
    // 规则是工具utils中处理出来的styleLoaders,生成了css,less,postcss等规则
    rules: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap, usePostCSS: true })
  },
  // 选择一种 source map 格式来增强调试过程。不同的值会明显影响到构建(build)和重新构建(rebuild)的速度
  devtool: config.dev.devtool,

  // 此处的配置都是在config的index.js中设定好了
  devServer: {
    // 控制台显示的选项有none, error, warning 或者 info
    clientLogLevel: 'warning',
    // 当使用 HTML5 History API 时,任意的 404 响应都可能需要被替代为 index.html
    // 比如History.back()
    historyApiFallback: true,
    //热加载
    hot: true,
    host: process.env.HOST || config.dev.host,
    port: process.env.PORT || config.dev.port,
    //调试时自动打开浏览器,这个默认是false
    open: config.dev.autoOpenBrowser,
    // warning 和 error 都要显示,这个默认是true
    overlay: config.dev.errorOverlay
      ? {
          warnings: false,
          errors: true
        }
      : false,
    // 发布的根目录
    publicPath: config.dev.assetsPublicPath,
    // 接口代理
    proxy: config.dev.proxyTable,
    // 控制台是否禁止打印警告和错误,若用FriendlyErrorsPlugin 此处为 true
    quiet: true,
    // 文件系统检测改动
    watchOptions: {
      poll: config.dev.poll
    }
  },
  plugins: [
    // 允许创建一个在编译时可以配置的全局常量
    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',
      inject: true
    })
  ]
});

module.exports = new Promise((resolve, reject) => {
  // 缓存端口号
  portfinder.basePort = process.env.PORT || config.dev.port;
  // 查找端口号
  portfinder.getPort((err, port) => {
    if (err) {
      reject(err);
    } else {
      // 设置端口
      process.env.PORT = port;
      // 端口被占用时就重新设置env的端口
      devWebpackConfig.devServer.port = port;

      // 友好地输出信息
      devWebpackConfig.plugins.push(
        new FriendlyErrorsPlugin({
          compilationSuccessInfo: {
            messages: [`Your application is running here: http://${config.dev.host}:${port}`]
          },
          onErrors: config.dev.notifyOnErrors ? utils.createNotifierCallback() : undefined
        })
      );

      resolve(devWebpackConfig);
    }
  });
});

build/webpack.prod.conf.js

const path = require('path');
// 引入公共方法
const utils = require('./utils');
const webpack = require('webpack');
// 引入配置文件
const config = require('../config');
// 可以将数据和对象合并,最终合并成为一个对象,如果有数组,那么对象的key就是数组的索引
const merge = require('webpack-merge');
// 引入基础文件
const baseWebpackConfig = require('./webpack.base.conf');
// 拷贝资源和文件
const CopyWebpackPlugin = require('copy-webpack-plugin');
// 可自动创建一个index.html文件
const HtmlWebpackPlugin = require('html-webpack-plugin');
// 将一个以上的包里面的文本提取到单独文件中
const ExtractTextPlugin = require('extract-text-webpack-plugin');
// 压缩提取出的css,并解决ExtractTextPlugin分离出的js重复问题(多个文件引入同一css文件)
const OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin');
// 先确定环境
const env = process.env.NODE_ENV === 'testing' ? require('../config/test.env') : require('../config/prod.env');

const webpackConfig = merge(baseWebpackConfig, {
  module: {
    // 调用utils.styleLoaders的方法
    rules: utils.styleLoaders({
      // 打包后要不要的.map文件
      sourceMap: config.build.productionSourceMap,
      extract: true,
      usePostCSS: true
    })
  },
  // 如果要map文件,择一种 source map 格式来增强调试过程
  devtool: config.build.productionSourceMap ? config.build.devtool : false,
  output: {
    // 静态资源的位置
    path: config.build.assetsRoot,
    // js名字
    filename: utils.assetsPath('js/[name].[chunkhash].js'),
    chunkFilename: utils.assetsPath('js/[name].[chunkhash].js')
  },
  plugins: [
    // 许创建一个在编译时可以配置的全局常量
    new webpack.DefinePlugin({
      'process.env': env
    }),
    // 压缩js的,但是不支持es6代码的压缩
    new webpack.optimize.UglifyJsPlugin({
      // uglifyjs是否看到的警告信息
      compress: {
        warnings: false
      },
      // 是否需要map文件,已经在config中设置了
      sourceMap: config.build.productionSourceMap,
      //
      parallel: true
    }),
    // 抽取文本。比如打包之后的index页面有style插入,就是这个插件抽取出来的,减少请求
    new ExtractTextPlugin({
      filename: utils.assetsPath('css/[name].[contenthash].css'),
      allChunks: false
    }),
    // 优化css的插件
    // 优化、最小化css代码,如果只简单使用extract-text-plugin可能会造成css重复
    new OptimizeCSSPlugin({
      // 打包后要不要的.map文件
      cssProcessorOptions: config.build.productionSourceMap
        ? {
            // 避免 cssnano 重新计算 z-index
            safe: true,
            // map文件的行内样式
            map: {
              inline: false
            }
          }
        : {
            // 避免 cssnano 重新计算 z-index
            safe: true
          }
    }),
    // html打包
    new HtmlWebpackPlugin({
      // 判断是哪个环境
      filename:
        process.env.NODE_ENV === 'testing'
          ? 'index.html'
          : // 访问build后的index.html
            config.build.index,
      template: 'index.html',
      // 向template或者templateContent中注入所有静态资源,不同的配置值注入的位置不经相同
      inject: true,
      //压缩
      minify: {
        //删除注释
        removeComments: true,
        //删除空格
        collapseWhitespace: true,
        //删除属性的引号
        removeAttributeQuotes: true
      },
      // 模块排序,按照我们需要的顺序排序
      chunksSortMode: 'dependency'
    }),
    // 是用于保持模块引用的 module id 不变
    new webpack.HashedModuleIdsPlugin(),
    // 过去 webpack 打包时的一个取舍是将 bundle 中各个模块单独打包成闭包。这些打包函数使你的 JavaScript 在浏览器中处理的更慢
    // 可以提升(hoist)或者预编译所有模块到一个闭包中,提升你的代码在浏览器中的执行速度。
    new webpack.optimize.ModuleConcatenationPlugin(),
    // 通过将公共模块拆出来,最终合成的文件能够在最开始的时候加载一次,便存到缓存中供后续使用。
    // 这个带来页面速度上的提升,因为浏览器会迅速将公共的代码从缓存中取出来,
    // 而不是每次访问一个新页面时,再去加载一个更大的文件。
    new webpack.optimize.CommonsChunkPlugin({
      // 可以是已经存在的chunk(一般指入口文件)对应的name,那么就会把公共模块代码合并到这个chunk上
      // 否则,会创建名字为name的commons chunk进行合并
      name: 'vendor',
      // filename:指定commons chunk的文件名
      // 既可以是数字,也可以是函数,还可以是Infinity
      minChunks: function(module) {
        // any required modules inside node_modules are extracted to vendor
        // 数字:模块被多少个chunk公共引用才被抽取出来成为commons chunk
        // 函数:接受 (module, count) 两个参数,返回一个布尔值,你可以在函数内进行你规定好的逻辑来决定某个模块是否提取成为commons chunk
        // Infinity:只有当入口文件(entry chunks) >= 3 才生效,用来在第三方库中分离自定义的公共模块
        // 从node_modules抽取到需要的module,生成vendor.js
        return module.resource && /\.js$/.test(module.resource) && module.resource.indexOf(path.join(__dirname, '../node_modules')) === 0;
      }
    }),
    /**
      new webpack.optimize.CommonsChunkPlugin({
        name: ['vendor', 'runtime'],
        filename: '[name].js',
        minChunks: Infinity
      }),
      等同于
      new webpack.optimize.CommonsChunkPlugin({
          name: 'vendor',
          filename: '[name].js'
      }),
      new webpack.optimize.CommonsChunkPlugin({
          name: 'runtime',
          filename: '[name].js',
          chunks: ['vendor']
      }),
      new webpack.optimize.CommonsChunkPlugin({
        name: 'common',
        filename: '[name].js',
        chunks: ['first', 'second'] //从first.js和second.js中抽取commons chunk
      }),
      查看dist目录下,新增了一个common.js的文件
     */
    new webpack.optimize.CommonsChunkPlugin({
      name: 'manifest',
      // 用来在第三方库中分离自定义的公共模块,生成manifest.js
      minChunks: Infinity
    }),
    // 这两个属性主要是在code split(代码分割)和异步加载当中应用
    new webpack.optimize.CommonsChunkPlugin({
      // 进行code split出来的childrend的chunks,生成app.js
      name: 'app',
      async: 'vendor-async',
      // 为true所有公共 chunk 的子模块都会被选择
      children: true,
      minChunks: 3
    }),

    // 如打包完之后需要把打包的文件复制到dist里面
    new CopyWebpackPlugin([
      {
        from: path.resolve(__dirname, '../static'),
        to: config.build.assetsSubDirectory,
        ignore: ['.*']
      }
    ])
  ]
});

// 是否开启GZIP

if (config.build.productionGzip) {
  const CompressionWebpackPlugin = require('compression-webpack-plugin');

  webpackConfig.plugins.push(
    // 提供带 Content-Encoding 编码的压缩版的资源

    new CompressionWebpackPlugin({
      // 目标资源名称。
      // [file] 会被替换成原始资源名称。
      // [path] 会被替换成原始资源路径
      // [query] 会被替换成原始查询字符串
      asset: '[path].gz[query]',
      algorithm: 'gzip',
      // 处理匹配到的资源
      test: new RegExp('\\.(' + config.build.productionGzipExtensions.join('|') + ')$'),
      // 只处理比这个值大的资源
      threshold: 10240,
      // 只有压缩率比这个值小的资源才会被处理
      minRatio: 0.8
    })
  );
}

// 在打包的时候输入npm run build --report,就可以在打包的同时查看每个打包生成的js,里面的库的构成情况
if (config.build.bundleAnalyzerReport) {
  webpackConfig.plugins.push(new BundleAnalyzerPlugin());
}

module.exports = webpackConfig;

暂无评论