首页 > 其他分享 >【webpack4系列】设计可维护的webpack4.x+vue构建配置(终极篇)

【webpack4系列】设计可维护的webpack4.x+vue构建配置(终极篇)

时间:2024-09-15 21:52:49浏览次数:1  
标签:webpack4 const plugin require loader webpack vue error 终极

构建配置包设计

构建配置管理的可选方案:

  • 通过多个配置文件管理不同环境的构建,webpack --config 参数进行控制
  • 将构建配置设计成一个库,比如:xxx-webpack
  • 抽成一个工具进行管理,比如:create-vue-app
  • 将所有的配置放在一个文件,通过 --env 参数控制分支选择

通过多个配置文件管理不同环境的 webpack 配置

  • 动态配置项:config.js
  • 通用功能:utils.js
  • 基础配置:webpack.base.js
  • 开发环境:webpack.dev.js
  • 生产环境:webpack.prod.js
  • 预编译配置:webpack.dll.js

抽离成一个 npm 包统一管理(省略)

  • 规范:Git commit日志、README、ESLint 规范、Semver 规范
  • 质量:冒烟测试、单元测试、测试覆盖率和 CI

什么是semver 规范?

概念:语义化的版本控制(Semantic Versioning),简称语义化版本,英文缩写为 SemVer

优势:

  • 避免出现循环依赖
  • 依赖冲突减少

语义化版本(Semantic Versioning)规范格式

  • 主版本号: 做了不兼容的 API 修改(进行不向下兼容的修改)
  • 次版本号: 做了向下兼容的功能性增加(API 保持向下兼容的新增及修改)
  • 修订号: 做了向下兼容的问题修正(修复问题但不影响 API)

通过 webpack-merge 组合配置

const merge = require("webpack-merge")
// 省略其他代码
module.exports = merge(baseConfig, devConfig);

功能模块设计

  • 基础配置:webpack.base.js
    • 资源解析
      • 解析ES6
      • 解析vue
      • 解析css
      • 解析less
      • 解析scss
      • 解析图片
      • 解析字体
      • 解析媒体
    • 样式增强
      • CSS前缀补齐
      • CSS px转成rem等
    • 目录清理
    • 忽略打包内容
    • 命令行信息显示优化
    • 错误捕获和处理
    • CSS提取成一个单独的文件
  • 开发配置:webpack.dev.js
    • 代码热更新
      • css热更新
      • js热更新
    • devServer配置
    • sourcemap
    • 样式压缩(可略)
    • 资源拷贝(可略)
  • 生产配置:webpack.prod.js
    • 代码压缩
    • 文件指纹
    • Tree Shaking(webpack4自带)
    • Scope Hositing(webpack4自带)
    • 速度优化(基础包CDN等)
    • 体积优化(代码分割)
    • 资源拷贝(可略)
    • 构建报告(可略)
    • 构建速度(可略)
  • 预编译配置:webpack.dll.js
    • 基础库:vue、element-ui等
    • 其它库:axios、vue-router等
  • 通用功能:utils.js
    • CSS加载器
    • 资源路径
    • 环境变量
    • eslint检测配置
    • ...
  • 动态配置项:config.js
    • dev: 开发动态配置项
    • build:生产动态配置项

目录结构设计

  • app-build 放置配置文件
  • app-dll 放预编译后的文件
  • src(或者lib)放置源代码
  • test 放置测试代码(可省略)

结构如下:

+ |- /app-build
    + |- config.js
    + |- utils.js
    + |- webpack.dev.js
    + |- webpack.prod.js
    + |- webpack.base.js
    + |- webpack.dll.js
+ |- /app-dll
    + |- dll.library.min.js
    + |- dll.vendors.min.js
    + |- manifest.library.json
    + |- manifest.vendors.json
+ |- /test
+ |- /src
+ |- .env.development
+ |- .env.production
+ |- .eslintignore
+ |- .eslinrc.js
+ |- .prettierrc
+ |- package.json
+ |- README.md

构建配置插件

安装webpack、webpack-cli

npm i webpack@4 [email protected] -D

关联HTML插件html-webpack-plugin

webpack4.x对应的html-webpack-plugin@4

npm install html-webpack-plugin@4 -D 

webpack示例配置:

const HtmlWebpackPlugin = require('html-webpack-plugin')

module.exports = {
  plugins: [
    new HtmlWebpackPlugin({
      template: './src/index.html'
    }),
  ]
}

解析ES6

webpack4.x安装@babel/core,@babel/preset-env,babel-loader@8

npm i babel-loader@8 @babel/core @babel/preset-env core-js@3 -D

在根路径下新建一个.babelrc文件,增加ES6的babel preset配置,代码如下:

{
  "preset": ["@babel/preset-env"]
}

webpack示例配置:

module: {
    rules: [
      {
        test: /\.js$/,
        use: 'babel-loader'
      } 
    ]
}

解析vue、JSX

安装插件:

npm i @vue/babel-preset-jsx @vue/babel-helper-vue-jsx-merge-props vue-loader@15 vue-style-loader@3 vue-template-compiler -D
npm i vue@2 -S

在.babelrc文件中添加JSX相关配置:

{
  "presets": [["@babel/preset-env"], "@vue/babel-preset-jsx"]
}

解析CSS、Less和Sass

解析CSS

解析css,需要安装style-loader和css-loader。

其中webpack4.x安装style-loader1.x、css-loader4.x:

npm i css-loader@4 style-loader@1 -D

rules配置:

{
    test: /.css$/,
    use: ['style-loader', 'css-loader']
}

解析Less

解析less,需要安装less、less-loader。

其中webpack4.x建议安装less-loader@6([email protected]也支持webpack4.x)

npm i less less-loader@6 -D

版本参考:https://github.com/webpack-contrib/less-loader/blob/v6.2.0/package.json

rules配置如下:

{
    test: /.less$/,
    use: ['style-loader', 'css-loader', 'less-loader']
}

解析sass

安装sass、sass-loader、sass-resources-loader(剔除掉node-sass,深度依赖node版本):

npm i [email protected] [email protected] [email protected] -D

注意:node-sass可以解析/deep/、::v-deep,sass只能解析::v-deep,如果剔除掉node-sass,需要把相关语法升级。像/deep/、::v-deep、:deep()这些是Vue.js框架中用于穿透样式作用域的特定选择器。

以下是对/deep/::v-deep:deep() 的对比说明:

选择器 说明 应用示例
/deep/ 是一个用于穿透组件样式作用域的旧版选择器。在某些Vue版本中被移除,不建议使用。 在Vue 2.x中,用于穿透样式作用域,如:.parent /deep/ .child { ... }
::v-deep 是Vue中的一个内置伪选择器,用于访问子组件的样式。它只适用于scoped样式中,并将样式深入到子组件作用域中。 在Vue组件的<style scoped>标签中使用,如:.parent ::v-deep .child { ... }
:deep() 是/deep/的替代品,也是一个伪类选择器。适用于全局样式和嵌套组件中的样式,用于穿透样式作用域。 可用于Vue组件的<style>标签中,无论是否带有scoped属性,如:.parent :deep(.child) { ... }

提取CSS

如果需要单独把 CSS 文件分离出来,我们需要使用 mini-css-extract-plugin 插件。

注:v4 版本之后才开始使用 mini-css-extract-plugin,之前的版本是使用 extract-text-webpack-plugin。

安装mini-css-extract-plugin插件:

npm i mini-css-extract-plugin -D

解析图片和字体

资源解析:解析图片

解析图片,可以安装file-loader,其中file-loader最新版本为6.2.0,支持webpack4.x。

npm i file-loader -D

版本参考:https://github.com/webpack-contrib/file-loader/blob/v6.2.0/package.json

rules配置如下:

{
    test: /.(png|jpe?g|gif)$/,
    use: 'file-loader'
}

资源解析:解析字体

rules配置如下:

{
    test: /.(woff|woff2|eot|otf|ttf)$/,
    use: 'file-loader'
},

css参考样式:

@font-face {
  font-family: 'SourceHeavy';
  src: url('./images/SourceHeavy.otf') format('truetype');
}

.search-text {
  font-size: 20px;
  color: #f00;
  font-family: 'SourceHeavy';
}

资源解析:使用url-loader

url-loader 也可以处理图⽚和字体,可以设置较⼩资源⾃动 base64,其中url-loader内部实现也是使用的file-loader。

目前url-loader最新版本为4.1.1,支持webpack4.x.

npm i url-loader -D

版本参考:https://github.com/webpack-contrib/url-loader/blob/master/package.json

rules配置(把之前关于图片的file-loader配置替换):

 {
    test: /.(png|jpe?g|gif)$/,
    use: [{ loader: 'url-loader', options: { limit: 10240 } }],
 }

热更新:webpack-dev-server

  • webpack-dev-server不刷新浏览器
  • webpack-dev-server不输出⽂件,⽽是放在内存中
  • 使⽤ HotModuleReplacementPlugin插件
npm i webpack-dev-server@3 -D

package.json示例配置:

"scripts": {
    "dev": "webpack-dev-server --open"
}

其中open是构建完成之后,自动开启浏览器。

文件指纹策略:chunkhash、contenthash和hash

注:文件指纹只能用于生产环境。

文件指纹如何生成

  • Hash:和整个项⽬的构建相关,只要项⽬⽂件有修改,整个项⽬构建的hash值就会更改
  • Chunkhash:和webpack 打包的chunk 有关,不同的entry 会⽣成不同的chunkhash值
  • Contenthash:根据⽂件内容来定义hash ,⽂件内容不变,则contenthash不变

文件指纹设置

  • JS文件:设置output的filename,使⽤[chunkhash]。
output: {
    path: path.join(__dirname, 'dist'),
    filename: '[name]_[chunkhash:8].js'
  }
  • CSS文件:设置MiniCssExtractPlugin的filename,使⽤[contenthash]
new MiniCssExtractPlugin({
  filename: "[name]_[contenthash:8].css"
}),

HTML 、CSS和JavaScript代码压缩

JS压缩

webpack4及以后使用内置optimization,配合自定义压缩插件terser-webpack-plugin使用

npm i terser-webpack-plugin@4 -D

配置示例:

optimization: {
    minimize: true,
    minimizer: [
      new TerserPlugin({
        // 过滤掉以".min.js"结尾的文件.
        exclude: /\.min\.js$/i,
        // Enable multi-process parallel running and set number of concurrent runs.
        parallel: true,
        // Enable file caching. Default path to cache directory: node_modules/.cache/terser-webpack-plugin.
        cache: true,
        terserOptions: {
          // 开启变量名混淆
          mangle: true,
          compress: {
            unused: true,
            // 移除所有debugger
            drop_debugger: true,
            // 移除所有console
            drop_console: true,
            pure_funcs: [
              // 移除指定的指令,如console, alert等
              "console.log",
              "console.error",
              "console.dir"
            ]
          },
          format: {
            // 删除注释
            comments: true
          }
        },
        // 是否将注释剥离到单独的文件中
        extractComments: false
      })
    ]
  }

CSS压缩

需要安装optimize-css-assets-webpack-plugin,同时使⽤cssnano

说明:optimize-css-assets-webpack-plugin插件目前官网最新版本5.0.8,使用的webpack为^4.44.1。

npm i optimize-css-assets-webpack-plugin@5 cssnano@4 -D

插件配置地址:https://github.com/NMFR/optimize-css-assets-webpack-plugin/blob/master/package.json

const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin')

module.exports = {
  // 其他省略
  mode: 'production',
  plugins: [
    new OptimizeCssAssetsPlugin({
      assetNameRegExp: /\.css$/g,
      cssProcessor: require('cssnano'),
    }),
  ],
}

另外官网推荐了另外一个CSS样式插件:css-minimizer-webpack-plugin。如果在开发环境提取的CSS需要压缩,建议使用css-minimizer-webpack-plugin插件,测试压缩速度比optimize-css-assets-webpack-plugin快。

安装:

npm i [email protected] -D

示例配置:

const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");

module.exports = {
    optimization: {
        minimize: true,
        minimizer: [
          new CssMinimizerPlugin({
            test: /\.css$/i
          })
        ]
      }
}

HTML压缩

安装html-webpack-plugin,并设置压缩参数。

其中webpack4.x对应的html-webpack-plugin@4。

npm i html-webpack-plugin@4 -D

webpack示例配置:

const HtmlWebpackPlugin = require('html-webpack-plugin')

module.exports = {
  
  plugins: [
    new HtmlWebpackPlugin({
      template: path.join(__dirname, 'src/index.html'),
      filename: 'index.html',
      chunks: ['index'],
      inject: true,
      minify: {
        html5: true,
        collapseWhitespace: true,
        preserveLineBreaks: false,
        minifyCSS: true,
        minifyJS: true,
        removeComments: false,
      },
    }),

    new HtmlWebpackPlugin({
      template: path.join(__dirname, 'src/search.html'),
      filename: 'search.html',
      chunks: ['search'],
      inject: true,
      minify: {
        html5: true,
        collapseWhitespace: true,
        preserveLineBreaks: false,
        minifyCSS: true,
        minifyJS: true,
        removeComments: false,
      },
    })
  ]
}

自动清理构建目录产物

webpack4.x使用clean-webpack-plugin@3版本:

npm i clean-webpack-plugin@3 -D

webpack配置:

const { CleanWebpackPlugin }  = require('clean-webpack-plugin')

plugins: [
    new CleanWebpackPlugin(),
 ]

PostCSS插件autoprefixer自动补齐CSS3前缀

需要安装postcss-loader、postcss、autoprefixer插件。

其中webpack4.x需要安装postcss-loader@4。

npm i postcss-loader@4 postcss@8 autoprefixer -D

示例配置如下:

module.exports = {
  module: {
    rules: [
      {
        test: /.css$/,
        use: [
          'css-loader',
          'less-loader',
          {
            loader: 'postcss-loader',
            options: {
              postcssOptions: {
                plugins: [
                  [
                    'autoprefixer',
                    {
                      overrideBrowserslist: ['last 2 version', '>1%', 'ios 7']
                    }
                  ]
                ]
              }
            }
          }
        ]
      }
    ]
  }
}

静态资源内联

资源内联的意义:

  • 代码层⾯:
    • ⻚⾯框架的初始化脚本
    • 上报相关打点
    • css 内联避免⻚⾯闪动
  • 请求层⾯:减少 HTTP ⽹络请求数
    • ⼩图⽚或者字体内联 (url-loader)

安装[email protected]版本

npm i [email protected] -D
  • raw-loader 内联 html
<%= require('raw-loader!./meta.html') %>
  • raw-loader 内联 JS
<%= require('raw-loader!babel-loader!../../node_modules/lib-flexible/flexible.js') %>

示例,例如我们抽离meta通用的代码为一个meta.html,以及flexible.js插件都内联带html页面中。
meta.html示例代码:

<meta charset="UTF-8">
<meta name="viewport" content="viewport-fit=cover,width=device-width,initial-scale=1,user-scalable=no">
<meta name="format-detection" content="telephone=no">
<meta name="keywords" content="keywords content">
<meta name="name" itemprop="name" content="name content">
<meta name="apple-mobile-web-app-capable" content="no">
<meta http-equiv="X-UA-Compatible" content="IE=Edge,chrome=1">

使用sourcemap

作⽤:通过source map定位到源代码

sourcemap参考文章:http://www.ruanyifeng.com/blog/2013/01/javascript_source_map.html

source map 关键字:

  • eval: 使⽤eval包裹模块代码
  • source map: 产⽣.map⽂件
  • cheap: 不包含列信息
  • inline: 将.map作为DataURI嵌⼊,不单独⽣成.map⽂件
  • module:包含loader的sourcemap

source map 类型:
image

一般开发环境配置:

module.exports = {
  // 其他代码省略
  devtool: "source-map"
};

生产环境配置:

module.exports = {
  // 其他代码省略
  devtool: "none"
};

Tree Shaking(摇树优化)的使用和原理分析

基础介绍

一个模块可能有多个⽅法,只要其中的某个⽅法使⽤到了,则整个⽂件都会被打到
bundle ⾥⾯去,tree shaking 就是只把⽤到的⽅法打⼊ bundle ,没⽤到的⽅法会在
uglify 阶段被擦除掉。

uglify阶段:将 JavaScript代码进行压缩、混淆,并去除一些不必要的代码,从而减小文件体积。

webpack4及以上默认内置了,当mode为production情况下默认开启。进行tree shaking条件是必须是 ES6 的语法,CJS 的⽅式不⽀持

DCE (Dead code elimination)

DCE 解释就是死代码消除的意思。

  • 代码不会被执⾏,不可到达
  • 代码执⾏的结果不会被⽤到
  • 代码只会影响死变量(只写不读)
    示例:
if (false) {
    console.log('这段代码永远不会执行’);
}

如上所示代码,在uglify 阶段就会删除⽆⽤代码。

Tree-shaking 原理

利⽤ ES6 模块的特点:

  • 只能作为模块顶层的语句出现
  • import 的模块名只能是字符串常量
  • import binding 是 immutable的(import引用的模块是不能修改的)

注:使用mode为production与none 来验证tree-shaking。

Scope Hoisting使用和原理分析

背景:构建后的代码存在⼤量闭包代码

如图所示:
image

这样会导致什么问题?

  • ⼤量作⽤域包裹代码,导致体积增⼤(模块越多越明显)
  • 运⾏代码时创建的函数作⽤域变多,内存开销变⼤

模块转换分析

示例:我们编写了一个模块,代码如下

import { helloworld } from "./helloworld";
import "../../common";

document.write(helloworld());

我们把webpack4中的mode设置为none,看下编译结果,webpack会把编写的模块转换成模块初始化函数,代码如下:

/* 0 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {

"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony import */ var _helloworld__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(1);
/* harmony import */ var _common__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(2);


document.write(Object(_helloworld__WEBPACK_IMPORTED_MODULE_0__["helloworld"])());

/***/ })

结果说明:

  • 被 webpack 转换后的模块会带上⼀层包裹
  • import 会被转换成 __webpack_require

当然上面两个import导入的模块编译为如下代码:

/* 1 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {

"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "helloworld", function() { return helloworld; });
function helloworld() {
  return 'Hello webpack';
}

/***/ }),
/* 2 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {

"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "common", function() { return common; });
function common() {
  return "common module";
}

/***/ })

进⼀步分析 webpack 的模块机制

(function (modules) {
  // webpackBootstrap
  // The module cache
  var installedModules = {};

  // The require function
  function __webpack_require__(moduleId) {
    // Check if module is in cache
    if (installedModules[moduleId]) {
      return installedModules[moduleId].exports;
    }
    // Create a new module (and put it into the cache)
    var module = (installedModules[moduleId] = {
      i: moduleId,
      l: false,
      exports: {}
    });

    // Execute the module function
    modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);

    // Flag the module as loaded
    module.l = true;

    // Return the exports of the module
    return module.exports;
  }
  return __webpack_require__(0);
})([
  /* 0 */
  function (module, __webpack_exports__, __webpack_require__) {
    // 省略代码
  },
  /* 1 */
  function (module, __webpack_exports__, __webpack_require__) {
    // 省略代码
  },
  /* 2 */
  function (module, __webpack_exports__, __webpack_require__) {
    // 省略代码
  }
  /******/
]);

上述代码分析:

  • 打包出来的是⼀个 IIFE (匿名闭包)
  • modules 是⼀个数组,每⼀项是⼀个模块初始化函数
  • __webpack_require ⽤来加载模块,返回 module.exports
  • 通过 webpack_require(0) 启动程序

scope hoisting 原理

原理:将所有模块的代码按照引⽤顺序放在⼀个函数作⽤域⾥,然后适当的重命名⼀
些变量以防⽌变量名冲突。

优点:通过scope hoisting可以减少函数声明代码和内存开销。

优化前代码

/* 1 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {

"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "helloworld", function() { return helloworld; });
function helloworld() {
  return 'Hello webpack';
}

/***/ }),
/* 2 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {

"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "common", function() { return common; });
function common() {
  return "common module";
}

/***/ })

优化后代码

(function(module, __webpack_exports__, __webpack_require__) {

    "use strict";
    // ESM COMPAT FLAG
    __webpack_require__.r(__webpack_exports__);
    
    // CONCATENATED MODULE: ./src/index/helloworld.js
    function helloworld() {
      return 'Hello webpack';
    }
    // CONCATENATED MODULE: ./common/index.js
    function common() {
      return "common module";
    }
    // CONCATENATED MODULE: ./src/index/index.js
    
    
    document.write(helloworld());

})

scope hoisting 使⽤

webpack mode 为 production 默认开启,必须是 ES6 语法,CJS 不⽀持。

由于mode为production来验证的话,默认会被压缩,我们可以设置为none,然后添加ModuleConcatenationPlugin来验证,示例代码:

const webpack = require("webpack");

module.exports = {
  // 其他代码省略
  mode: "none",
  plugins: [
    new webpack.optimize.ModuleConcatenationPlugin()
  ]
};

注:webpack4及以上mode为production的时候,默认内置了ModuleConcatenationPlugin

代码分割和动态import

代码分割的意义

对于⼤的 Web 应⽤来讲,将所有的代码都放在⼀个⽂件中显然是不够有效的,特别是当你的
某些代码块是在某些特殊的时候才会被使⽤到。webpack 有⼀个功能就是将你的代码库分割成
chunks(语块),当代码运⾏到需要它们的时候再进⾏加载。

适⽤的场景:

  • 抽离相同代码到⼀个共享块
  • 脚本懒加载,使得初始下载的代码更⼩

懒加载 JS 脚本的⽅式

  • CommonJS:require.ensure
  • ES6:动态 import(⽬前还没有原⽣⽀持,需要 babel 转换)

如何使⽤动态 import?

安装 babel 插件

npm i @babel/plugin-syntax-dynamic-import -D

ES6:动态 import(⽬前还没有原⽣⽀持,需要 babel 转换),在babelrc中添加:

{
  "plugins": ["@babel/plugin-syntax-dynamic-import"]
}

代码分割的效果如图所示:
image

上面编译的圈红的,如果是动态加载的,那会生成一个以[number]_[chunkhash].js生成的文件名。

在webpack中使用ESLint

行内优秀的eslint规范

eslint-config-airbnb:默认导出包含大多数ESLint规则,包括ECMAScript 6+和React。它需要eslint, eslint-plugin-import, eslint-plugin-react, eslint-plugin-react-hooks, eslint-plugin-jsx-a11y。请注意,它不会启用我们的React Hooks规则。

当然如果不需要React,那么可以参考使用eslint-config-airbnb-base。

以使用eslint-config-airbnb-base为例:

npm i eslint@7 babel-eslint@10 eslint-webpack-plugin@2 eslint-config-airbnb-base eslint-plugin-import -D

其中使用eslint-webpack-plugin替换eslint-loader.

eslint-webpack-plugin 3.0 which works only with webpack 5. For the webpack 4, see the 2.x branch.

eslint-webpack-plugin详细参考地址:https://github.com/webpack-contrib/eslint-webpack-plugin

示例代码:

const ESLintPlugin = require("eslint-webpack-plugin");

module.exports = {
  mode: "production",
  plugins: [
    new ESLintPlugin({
      fix: true, // 启用ESLint自动修复功能
      extensions: ["js", "jsx"],
      context: path.join(__dirname, "src"), // 文件根目录
      exclude: ["/node_modules/"], // 指定要排除的文件/目录
      cache: true // 缓存
    })
  ]
};

再安装如下插件:

npm i eslint-import-resolver-webpack [email protected] [email protected] -D
  • eslint-import-resolver-webpack:获取webpack配置的一些参数,共享给配置规则,让其正确识别import路径。
  • eslint-plugin-vue:帮助我们检测.vue文件中 <template><script> 中的js代码
  • vue-eslint-parser:eslint-plugin-vue 插件依赖 vue-eslint-parser解析器

整体示例代码:

const webpackBaseConfig = require('./app-build/webpack.base.js');

module.exports = {
  root: true,
  parser: 'vue-eslint-parser',
  parserOptions: {
    parser: 'babel-eslint'
  },
  env: {
    node: true,
    browser: true,
    es6: true
  },
  plugins: ['vue'],
  extends: [
    'plugin:vue/essential',
    'airbnb-base'
  ],
  settings: {
    'import/resolver': {
      webpack: {
        config : {
          resolve: webpackBaseConfig.resolve
        }
      }
    }
  },
  rules: {
 
  }
};

优化构建时命令行的显示日志

webpack构建统计信息 stats

image

如何优化命令⾏的构建⽇志

1、使⽤ friendly-errors-webpack-plugin

  • success: 构建成功的⽇志提示
  • warning: 构建警告的⽇志提示
  • error: 构建报错的⽇志提示

2、stats 设置成 errors-only

安装friendly-errors-webpack-plugin:

npm i friendly-errors-webpack-plugin -D

示例配置:

const FriendlyErrorsWebpackPlugin = require("friendly-errors-webpack-plugin");

module.exports = {
  plugins: [
    new FriendlyErrorsWebpackPlugin()
  ],
  stats: "errors-only"
};

构建异常和中断处理

如何判断构建是否成功?

  • 在 CI/CD 的 pipline 或者发布系统需要知道当前构建状态
  • 每次构建完成后输⼊ echo $? 获取错误码

webpack4 之前的版本构建失败不会抛出错误码 (error code)

Node.js 中的 process.exit 规范

  • 0 表示成功完成,回调函数中,err 为 null
  • ⾮ 0 表示执⾏失败,回调函数中,err 不为 null,err.code 就是传给 exit 的数字

如何主动捕获并处理构建错误?

  • compiler 在每次构建结束后会触发 done 这个 hook
  • process.exit 主动处理构建报错

在配置中可以添加如下代码,进行中断处理,例如错误上报等。

module.exports = {
  plugins: [
    function errorPlugin() {
      this.hooks.done.tap("done", (stats) => {
        if (stats.compilation.errors && stats.compilation.errors.length && process.argv.indexOf("--watch") == -1) {
          console.log("build error");
          process.exit(1); // 1表示错误码并退出
        }
      });
    }
  ]
};

速度分析:使用 speed-measure-webpack-plugin

使用speed-measure-webpack-plugin插件。

官网地址:https://github.com/stephencookdev/speed-measure-webpack-plugin#readme

安装:

npm i speed-measure-webpack-plugin -D

使用:

const SpeedMeasurePlugin = require("speed-measure-webpack-plugin");
const smp = new SpeedMeasurePlugin();

module.exports = smp.wrap({
  // 其他省略
  plugins: [
    new MyPlugin(), new MyOtherPlugin()
  ]
});

速度分析插件作用

  • 分析整个打包总耗时
  • 每个插件和loader的耗时情况

体积分析:使用webpack-bundle-analyzer

安装:

npm i webpack-bundle-analyzer -D

示例:

const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;

module.exports = {
  plugins: [
    new BundleAnalyzerPlugin()
  ]
}

可以分析哪些问题?

  • 依赖的第三方模块文件大小
  • 业务里面的组件代码大小

多进程/多实例构建

资源并行解析可选方案

  • parallel-webpack
  • HappyPack
  • thread-loader

使用 HappyPack 解析资源

原理:每次 webapck 解析一个模块,HappyPack 会将它及它的依赖分配给 worker 线程中。

安装:

npm i happypack -D

使用示例:

const HappyPack = require('happypack');
 
exports.module = {
  rules: [
    {
      test: /.js$/,
      // 1) replace your original list of loaders with "happypack/loader":
      // loaders: [ 'babel-loader?presets[]=es2015' ],
      use: 'happypack/loader',
      include: [ /* ... */ ],
      exclude: [ /* ... */ ]
    }
  ]
};
 
exports.plugins = [
  // 2) create the plugin:
  new HappyPack({
    // 3) re-add the loaders you replaced above in #1:
    loaders: [ 'babel-loader?presets[]=es2015' ]
  })
];

使用 thread-loader 解析资源

由于webpack4.x目前只能安装[email protected]版本,3.0.0以后的版本需要webpack5.x。

原理:每次 webpack 解析一个模块,thread-loader 会将它及它的依赖分配给 worker 线程中

npm i [email protected] -D

配置:

module: {
    rules: [
      {
        test: /.js$/,
        use: [
          {
            loader: "thread-loader",
            options: {
              workers: 3
            }
          },
          "babel-loader"
        ]
      }
    ]
  }

多进程并行压缩代码:terser-webpack-plugin 开启 parallel 参数

webpack4.x及以上建议使用terser-webpack-plugin插件

注:Using Webpack v4, you have to install terser-webpack-plugin v4.

安装:

npm i terser-webpack-plugin@4 -D

配置示例:

const TerserPlugin = require("terser-webpack-plugin");

module.exports = {
  optimization: {
    minimizer: [
      new TerserPlugin({
        parallel: true
      })
    ]
  }
};

进一步分包:预编译资源模块

方法:使用DLLPlugin进行分包,DllReferencePlugin对 manifest.json 引用

  • DllPlugin:负责抽离第三方库,形成第三方动态库dll。
  • DllReferencePlugin:负责引用第三方库。

使用 DLLPlugin 进行分包

新建一个webpack.dll.js:

const path = require("path");
const webpack = require("webpack");

module.exports = {
  entry: {
    library: ["vue/dist/vue.esm.js", "element-ui"]
  },
  output: {
    filename: "[name].dll.js",
    path: path.join(__dirname, "build/library"),
    library: "[name]_[hash:8]", // 保持与webpack.DllPlugin中name一致
    libraryTarget: 'window'
  },
   resolve: {
    alias: {
      vue$: "vue/dist/vue.esm.js"
    }
  },
  plugins: [
    new webpack.DllPlugin({
      name: "[name]_[hash:8]", // 保持与output.library中名称一致
      path: path.join(__dirname, "build/library/[name].json")
    })
  ]
};

在package.json中添加命令:

"scripts": {
    "dll": "webpack --config webpack.dll.js"
}

最后执行npm run dll,结果在工程根目录下有如下文件:

  • build
    • library.dll.js
    • library.json

使用 DllReferencePlugin 引用 manifest.json

在webpack.prod.js中插件中配置如下:

plugins: [
    new webpack.DllReferencePlugin({
      manifest: require("./build/library/library.json")
    })
]

当执行npm run build 后其实index.html页面中没有引入library.dll.js文件,我们可以通过安装add-asset-html-webpack-plugin插件,webpack4.x版本使用add-asset-html-webpack-plugin@3

npm i add-asset-html-webpack-plugin@3 -D

在webpack.prod.js中插件中配置如下:

plugins: [
    new webpack.DllReferencePlugin({
      manifest: require("./build/library/library.json")
    }),
    new AddAssetHtmlPlugin({
      filepath: path.resolve("./build/library", "library.dll.js")
    })
]

作用就是把build/library/library.dll.js拷贝到编译后的dist文件夹下,并且通过script标签引入到index.html中。

最终页面生成的效果:

<!DOCTYPE html>
<html lang="en">
<meta charset="utf-8">
<head>
  <title>Document</title>
  <link href="search_42937580.css" rel="stylesheet">
</head>
<body>
  <div id="root"></div>
  <script src="library.dll.js"></script>
  <script src="search_c1f12d25.js"></script>
</body>
</html>

add-asset-html-webpack-plugin参考地址:https://www.npmjs.com/package/add-asset-html-webpack-plugin/v/3.2.2?activeTab=versions

充分利用缓存提升二次构建速度

目的:提升二次构建速度。

缓存思路:

  • babel-loader 开启缓存
  • terser-webpack-plugin 开启缓存
  • 使用 cache-loader 或者 hard-source-webpack-plugin

开启了对应方式的缓存,会在node_modules目录下的cache文件夹看到缓存的内容,如下结构:

  • node_modules
    • .cache
      • babel-loader
      • hard-source
      • terser-webpack-plugin

1、babel-loader 开启缓存

rules: [
  {
    test: /.js$/,
    use: ["babel-loader?cacheDirectory=true"
    ]
  }
]

如果是使用的HappyPack,配置如下:

new HappyPack({
    loaders: ["babel-loader?cacheDirectory=true"]
})

2、terser-webpack-plugin 开启缓存

optimization: {
    minimizer: [
      new TerserPlugin({
        parallel: true,
        cache: true
      })
    ]
  }

3、hard-source-webpack-plugin开启缓存
安装:

npm i hard-source-webpack-plugin -D

配置:

const HardSourceWebpackPlugin = require('hard-source-webpack-plugin');

module.exports = {
    plugins: [
         new HardSourceWebpackPlugin()
    ]
}

在webpack4.x中有时会报错。

缩小构建目标与减少文件搜索范围

缩小构建目标

目的:尽可能的少构建模块

比如 babel-loader 不解析 node_modules

 rules: [
  {
    test: /.js$/,
    include: [path.resolve(__dirname, "src")],
    use: [
        "babel-loader"
    ]
  }

当然也可以使用exclude,来缩小构建范围。

减少文件搜索范围

  • 优化 resolve.modules 配置(减少模块搜索层级)
  • 优化 resolve.mainFields 配置
  • 优化 resolve.extensions 配置
  • 合理使用 alias

示例代码:

resolve: {
    alias: {
      "@": path.resolve(__dirname, "src"),
      react: path.resolve(__dirname, "./node_modules/react/umd/react.production.min.js"),
      "react-dom": path.resolve(__dirname, "./node_modules/react-dom/umd/react-dom.production.min.js")
      extensions: [".js"],
      mainFields: ["main"]
  },

擦除无用的CSS

无用的 CSS 如何删除掉?

  • PurifyCSS: 遍历代码,识别已经用到的 CSS class
  • uncss: HTML 需要通过 jsdom加载,所有的样式通过PostCSS解析,通过document.querySelector 来识别在 html 文件里面不存在的选择器

在 webpack 中如何使用 PurifyCSS?
PurifyCSS官网已经不再维护了,使用 purgecss-webpack-plugin这个插件和 mini-css-extract-plugin 配合使用。

安装purgecss-webpack-plugin插件:

npm i purgecss-webpack-plugin@4 glob@7 -D

配置:

const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const PurgeCSSPlugin = require('purgecss-webpack-plugin')

const PATHS = {
  src: path.join(__dirname, 'src')
}

module.exports = {
 // 其他省略
  module: {
    rules: [
      {
        test: /\.css$/,
        use: [
          MiniCssExtractPlugin.loader,
          "css-loader"
        ]
      }
    ]
  },
  plugins: [
    new MiniCssExtractPlugin({
      filename: "[name].css",
    }),
    new PurgeCSSPlugin({
      paths: glob.sync(`${PATHS.src}/**/*`,  { nodir: true }),
    }),
  ]
}

配置其它插件

  • webpack-merge:合并webpack配置项
npm i webpack-merge -D
  • copy-webpack-plugin:拷贝资源
npm i @copy-webpack-plugin6 -D
  • git-revision-webpack-plugin:获取git信息
npm i git-revision-webpack-plugin -D
  • progress-bar-webpack-plugin:编译进度
npm i progress-bar-webpack-plugin -D
  • cross-env:设置node环境变量
npm i cross-env -D
  • portfinder:查找机器端口
npm i portfinder -D

示例:

module.exports = new Promise((resolve, reject) => {
  portfinder.setBasePort(process.env.PORT || config.dev.port);
  portfinder.getPort(function (err, port) {
    //
    // `port` is guaranteed to be a free port
    // in this scope.
    //
    if (err) {
      reject(err);
    } else {
      process.env.PORT = port;
      devConfig.devServer.port = port;
  
      resolve(merge(baseConfig, devConfig));
    }
  });
});
  • node-notifier:用 Node.js 发送跨平台本机通知
npm i node-notifier -D

示例:

const notifier = require('node-notifier');
// String
notifier.notify('Message');

// Object
notifier.notify({
  title: 'My notification',
  message: 'Hello, there!'
});
  • prettier:代码格式化插件
npm i prettier -D

实现源码

/app-build/

config.js

const path = require("path");

module.exports = {
  // 开发基础配置
  dev: {
    // 静态资源存放的文件夹
    assetsSubDirectory: "static",
    // 静态资源引用路径
    assetsPublicPath: "/",
    proxy: {},
    // can be overwritten by process.env.HOST
    host: "localhost",
    // can be overwritten by process.env.PORT, if port is in use, a free one will be determined
    port: 8081,
    // css source map
    cssSourceMap: false,
    // source map类型
    devtool: "cheap-source-map",
    // 是否自动打开浏览器
    open: true,
    // 是否添加git信息
    gitRevision: true,
    // 是否启用eslint
    useEslint: true,
    // 开启eslint后,检测任何出错是否导致编译失败,true=是,false=否
    esLintFailOnError: false,
  },

  // 生产基础配置
  build: {
    // 打包后的文件存放的地方
    assetsRoot: path.resolve(__dirname, "../dist"),
    // 静态资源存放的文件夹
    assetsSubDirectory: "static",
    // 静态资源引用路径
    assetsPublicPath: "./",
    // 是否开启source map
    productionSourceMap: false,
    // source map类型
    devtool: "cheap-source-map",
    // 是否开启打包后的分析报告
    bundleAnalyzerReport: false,
    // 测量webpack构建速度
    isSpeedMeasure: false,
    // 是否添加git信息
    gitRevision: true,
    // 清除多余不用的css样式
    purgeCSS: true,
    // 是否启用eslint
    useEslint: true,
    // 开启eslint后,检测任何出错是否导致编译失败,true=是,false=否
    esLintFailOnError: true
  }
};

app-build/utils.js

const path = require("path");
const { merge } = require("webpack-merge");
const fs = require("fs");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const GitRevisionPlugin = require("git-revision-webpack-plugin");
const ESLintPlugin = require("eslint-webpack-plugin");
const config = require("./config");

// 生成css加载器
exports.cssLoaders = function (options) {
  // css loader
  const cssLoader = {
    loader: "css-loader",
    options: {
      sourceMap: options.sourceMap,
      modules: {
        compileType: 'icss'
      }
    }
  };

  // postcss loader
  const postcssLoader = {
    loader: "postcss-loader",
    options: {
      sourceMap: options.sourceMap,
      postcssOptions: {
        plugins: [
          [
            "autoprefixer",
            {
              overrideBrowserslist: ["last 2 version", ">1%", "ios 7"]
            }
          ]
        ]
      }
    }
  };

  /**
   * 
   * @param { Sting } loader  loader名称
   * @param { Object } loaderOptions loader配置项
   * @param { Object } injectOptions 匹配 css-loader或者postcss-loader等基础loader,把loaderOptions配置项注入其中
   * @returns 返回loader数组
   */

  function generateLoaders(loader, loaderOptions) {
    const loaders = options.usePostCSS
      ? [cssLoader, postcssLoader]
      : [cssLoader];

    if (loader) {
      loaders.push({
        loader: `${loader}-loader`,
        options: {
          ...loaderOptions,
          sourceMap: options.sourceMap
        }
      });
    }

    // 如果需要提前css文件,就使用MiniCssExtractPlugin.loader
    if (options.extract) {
      return [
        {
          loader: MiniCssExtractPlugin.loader,
          options: {
            publicPath: "../../"
          }
        },
        ...loaders
      ];
    }
    return ["style-loader", ...loaders];
  }

  return [
    {
      test: /\.(sa|sc|c)ss$/,
      use: generateLoaders("sass")
    },
    {
      test: /.less$/,
      use: generateLoaders("less")
    }
  ];
};

// 设置资源路径
exports.assetsPath = function (_path) {
  const assetsSubDirectory = process.env.NODE_ENV === "production"
    ? config.build.assetsSubDirectory
    : config.dev.assetsSubDirectory;
  // 在 Windows 平台上,路径分隔符是 \(也可以使用 /),而在其他平台上是 /
  // path.join 即会按照当前操作系统进行给定路径分隔符,而 path.posix.join 则始终是 /
  return path.posix.join(assetsSubDirectory, _path);
};

// 获取绝对路径
exports.resolve = function (dir) {
  return path.join(__dirname, "..", dir);
};

// 获取config文件中的环境属性名称
exports.getEnvName = function () {
  const env = process.env.NODE_ENV;
  let configEnvName = "";
  if (env === "development") {
    configEnvName = "dev";
  }
  if (env === "production") {
    configEnvName = "build";
  }
  return configEnvName;
};


// 获取环境变量
exports.getEnvironment = function () {
  const envName = exports.getEnvName();

  // 环境变量中是否需要添加git信息
  let gitEnv = {};
  if (config[envName].gitRevision) {
    const gitRevision = new GitRevisionPlugin();
    gitEnv = {
      GITVERSION: JSON.stringify(gitRevision.version()),
      GITBRANCH: JSON.stringify(gitRevision.branch())
    };
  }

  // 本地环境文件解析
  let localEnv = {};
  const callbackData = fs.readFileSync(
    path.join(__dirname, "..", `.env.${process.env.NODE_ENV}`)
  );
  const result = callbackData.toString();
  if (result) {
    localEnv = result.replace(/[\r\n]/g, ";").split(";").reduce((pre, cur) => {
      const [key, value] = cur.split("=");
      if (key) {
        pre[key] = JSON.stringify(value);
        process.env[key] = value;
      }
      return pre;
    }, {});
  }

  return merge({}, gitEnv, localEnv);
};

/** 创建eslint检测插件
 * @param { Boolean } failOnError  如果出现任何错误,将导致模块构建失败 true=是 false=否
 * @returns plugin Object
 */
exports.createEsLintPlugin = function(failOnError) {
  // eslint-webpack-plugin详细参考: https://www.npmjs.com/package/eslint-webpack-plugin
  return new ESLintPlugin({
    // 自动修复
    fix: true, 
    // 指定应检查的扩展
    extensions: ["js", "jsx","vue"], 
    // 指定检测的目录
    context: exports.resolve("src"), 
    // 指定要排除的文件/目录
    exclude: ["/node_modules/"], 
    // 默认情况下启用缓存以减少执行时间
    cache: false,
    // 默认值true,将始终发出发现的错误,禁用设置为false
    emitError: true,
    // 默认值true,将始终发出找到的警告,以禁用设置为false
    emitWarning: true,
    // 默认值true,如果出现任何错误,将导致模块构建失败,禁用设置为false
    failOnError: failOnError,
    // 默认值false,如果出现任何警告,将导致模块构建失败,那么设置为true
    failOnWarning: false,
    /** 将错误的输出写入文件,例如用于报告 Jenkins CI 的 checkstyle xml 文件。
     * outputReport = boolean | { filePath?: string | undefined, formatter?: function | undefined}
     *  默认值false
     * 是绝对路径或相对于 webpack 配置的路径。
     */
    outputReport: false
  })
}

webpack.base.js

const path = require("path");
const webpack = require("webpack");
const VueLoaderPlugin = require("vue-loader/lib/plugin");
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const FriendlyErrorsWebpackPlugin = require("friendly-errors-webpack-plugin");
const config = require("./config");
const utils = require("./utils");

module.exports = {
  context: path.resolve(__dirname, "../"),
  entry: {
    app: "./src/main.js"
  },
  output: {
    path: config.build.assetsRoot,
    filename: "[name].js"
  },
  resolve: {
    extensions: [".js", ".vue", ".json"],
    mainFields: ["main"],
    alias: {
      vue$: "vue/dist/vue.esm.js",
      "@": utils.resolve("src")
    }
  },
  module: {
    rules: [
      {
        test: /\.vue$/,
        loader: "vue-loader"
      },
      {
        test: /\.js$/,
        include: [utils.resolve("src")],
        use: [
          {
            loader: "thread-loader",
            options: {
              workers: 3
            }
          },
          "babel-loader"
        ]
      },
      {
        test: /\.(png|jpe?g|gif|svg)$/,
        use: [
          {
            loader: "url-loader",
            options: {
              esModule: false,
              limit: 10000,
              name: utils.assetsPath("img/[name].[hash:8].[ext]")
            }
          }
        ]
      },
      {
        test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)$/,
        loader: "url-loader",
        options: {
          esModule: false,
          limit: 10000,
          name: utils.assetsPath("media/[name].[hash:8].[ext]")
        }
      },
      {
        test: /\.(woff|woff2|eot|otf|ttf)$/,
        use: [
          {
            loader: "url-loader",
            options: {
              esModule: false,
              limit: 10000,
              name: utils.assetsPath("fonts/[name].[hash:8].[ext]")
            }
          }
        ]
      }
    ]
  },
  plugins: [
    // 忽略moment插件非本地语言的其他代码
    new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/),

    new VueLoaderPlugin(),
    // 抽离css文件
    new MiniCssExtractPlugin({
      filename: utils.assetsPath("css/[name]_[contenthash:8].css")
    }),

    // 优化构建日志插件
    new FriendlyErrorsWebpackPlugin(),

    function errorPlugin() {
      this.hooks.done.tap("done", (stats) => {
        if (
          stats.compilation.errors
          && stats.compilation.errors.length
          && process.argv.indexOf("--watch") === -1
        ) {
          // 1表示错误码并退出
          process.exit(1); 
        }
      });
    },
    // 清理构建目录
    new CleanWebpackPlugin(),

    // eslint配置
    ...(config[utils.getEnvName()].useEslint ? [utils.createEsLintPlugin(config[utils.getEnvName()].esLintFailOnError)] : [])
  ],
  stats: "errors-only"
};

webpack.dev.js

// 设置环境
process.env.NODE_ENV = "development";

const path = require("path");
const { merge } = require("webpack-merge");
const utils = require("./utils");
const config = require("./config");
const webpack = require("webpack");
const portfinder = require("portfinder");
const baseConfig = require("./webpack.base");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const CopyWebpackPlugin = require("copy-webpack-plugin");
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
const FriendlyErrorsWebpackPlugin = require("friendly-errors-webpack-plugin");
const notifier = require("node-notifier");

const env = utils.getEnvironment();

const HOST = process.env.HOST;
const PORT = process.env.PORT && Number(process.env.PORT);

const devConfig = {
  mode: "development",
  output: {
    publicPath: config.dev.assetsPublicPath
  },
  devServer: {
    headers: {"Access-Control-Allow-Origin": "*"},
    historyApiFallback: {
      rewrites: [
        {
          from: /.*/,
          to: path.posix.join(config.dev.assetsPublicPath, "index.html")
        }
      ]
    },
    static: {
      directory: config.build.assetsRoot,
      publicPath: "/"
    },
    hot: true,
    compress: true,
    client: {
      // 关闭webpack-dev-server客户端的日志输出
      logging: "none",
      overlay: {
        // compilation errors
        errors: true,
        // compilation warnings
        warnings: false,
        // unhandled runtime errors
        runtimeErrors: false
      },
      // 显示打包进度
      progress: true
    },
    host: HOST || config.dev.host,
    port: PORT || config.dev.port,
    proxy: config.dev.proxy,
    open: config.dev.open
  },
  devtool: config.dev.devtool,
  // stats构建日志输出配置几种配置说明
  // 1. false: 什么都不输出
  // 2. errors-only: 只在发生错误时输出
  // 3. minimal: 只在发生错误或有新的编译时输出
  // 4. none: 没有输出
  // 5. normal: 标准输出
  // 6. verbose: 全部输出
  stats: "errors-only",
  module: {
    rules: utils.cssLoaders({
      sourceMap: config.dev.cssSourceMap,
      extract: true,
      usePostCSS: true
    })
  },
  optimization: {
    minimize: true,
    minimizer: [
      new CssMinimizerPlugin({
        test: /\.css$/i
      })
    ]
  },
  plugins: [
    // 注册全局变量
    new webpack.DefinePlugin({
      "process.env": env
    }),
    // 热更新插件,结合webpack-dev-server使用
    new webpack.HotModuleReplacementPlugin(),

    // 页面模块
    new HtmlWebpackPlugin({
      template: "index.html",
      filename: 'index.html',
      inject: true
    }),

    // 将static文件夹下的静态资源复制到dist/static文件夹下
    new CopyWebpackPlugin({
      patterns: [
        {
          from: path.resolve(__dirname, '../static'),
          to: config.dev.assetsSubDirectory,
          globOptions: {
            ignore: ['.*'],
          },
        },
      ],
    }),
  ]
};

module.exports = new Promise((resolve, reject) => {
  portfinder.setBasePort(process.env.PORT || config.dev.port);
  portfinder.getPort(function (err, port) {
    //
    // `port` is guaranteed to be a free port
    // in this scope.
    //
    if (err) {
      reject(err);
    } else {
      process.env.PORT = port;
      devConfig.devServer.port = port;
      devConfig.plugins.push(
        new FriendlyErrorsWebpackPlugin({
          compilationSuccessInfo: {
            messages: [
              `You application is running here http://${devConfig.devServer.host}:${port}`
            ]
          },
          one rrors: (severity, errors) => {
            if (severity !== "error") {
              return;
            }
            const error = errors[0];
            notifier.notify({
              title: "Webpack error",
              message: severity + ": " + error.name,
              subtitle: error.file || "",
              icon: path.join(__dirname, "icon.png")
            });
          }
        })
      );

      resolve(merge(baseConfig, devConfig));
    }
  });
});

webpack.dll.js

const path = require("path");
const webpack = require("webpack");
const utils = require("./utils");
const ProgressBarPlugin = require("progress-bar-webpack-plugin");
const TerserPlugin = require("terser-webpack-plugin");
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
const { BundleAnalyzerPlugin } = require("webpack-bundle-analyzer");

module.exports = {
  mode: "production",
  entry: {
    library: ["vue/dist/vue.esm.js", "element-ui"],
    vendors: ["axios", "vue-router/dist/vue-router.common.js"]
  },
  output: {
    filename: "dll.[name].min.js",
    path: utils.resolve("./app-dll"),
    library: "[name]", // 保持与webpack.DllPlugin中name一致
    libraryTarget: 'window'
  },
  resolve: {
    alias: {
      vue$: "vue/dist/vue.esm.js"
    }
  },
  plugins: [
    new ProgressBarPlugin(),
    // 清理构建目录
    new CleanWebpackPlugin(),

    new webpack.DllPlugin({
      name: "[name]", // 保持与output.library中名称一致
      path: utils.resolve("./app-dll/manifest.[name].json")
    }),

    new BundleAnalyzerPlugin()
  ],
  optimization: {
    minimize: true,
    minimizer: [
      new TerserPlugin({
        terserOptions: {
          format: {
            // 删除注释
            comments: true
          }
        },
        // 是否将注释剥离到单独的文件中
        extractComments: false
      })
    ]
  }
};

webpack.prod.js

// 设置环境
process.env.NODE_ENV = "production";

const { merge } = require("webpack-merge");
const webpack = require("webpack");
const path = require("path");
const fs = require("fs");
const glob = require("glob");
const ProgressBarPlugin = require("progress-bar-webpack-plugin");
const OptimizeCssAssetsPlugin = require("optimize-css-assets-webpack-plugin");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const CopyWebpackPlugin = require("copy-webpack-plugin");
const TerserPlugin = require("terser-webpack-plugin");
const PurgeCSSPlugin = require("purgecss-webpack-plugin");
const AddAssetHtmlPlugin = require("add-asset-html-webpack-plugin");
const SpeedMeasurePlugin = require("speed-measure-webpack-plugin");
const { BundleAnalyzerPlugin } = require("webpack-bundle-analyzer");
const cssnano = require("cssnano");
const config = require("./config");
const utils = require("./utils");
const baseConfig = require("./webpack.base.js");
const webpackDll = require("./webpack.dll.js");

const env = utils.getEnvironment();

const prodConfig = {
  mode: "production",
  output: {
    filename: utils.assetsPath("js/[name].[chunkhash:8].js"),
    publicPath: config.build.assetsPublicPath
  },
  module: {
    rules: utils.cssLoaders({
      sourceMap: config.build.productionSourceMap,
      extract: true,
      usePostCSS: true
    })
  },
  plugins: [
    new ProgressBarPlugin(),
    new webpack.DefinePlugin({
      "process.env": env
    }),
    new OptimizeCssAssetsPlugin({
      assetNameRegExp: /\.css$/g,
      cssProcessor: cssnano
    }),

    // 页面模块
    new HtmlWebpackPlugin({
      template: "index.html",
      filename: `${config.build.assetsRoot}/index.html`,
      inject: true,
      minify: {
        html5: true,
        collapseWhitespace: true,
        preserveLineBreaks: false,
        minifyCSS: true,
        minifyJS: true,
        removeComments: false
      }
    }),

    // 将static文件夹下的静态资源复制到dist/static文件夹下
    // new CopyWebpackPlugin({
    //   patterns: [
    //     {
    //       from: path.resolve(__dirname, '../static'),
    //       to: config.build.assetsSubDirectory,
    //       globOptions: {
    //         ignore: ['.*'],
    //       },
    //     },
    //   ],
    // }),

    // 生成git版本信息
    function gitVersionPlugin() {
      this.hooks.done.tap("done", () => {
        if (!fs.existsSync(config.build.assetsRoot)) {
          fs.mkdirSync(config.build.assetsRoot);
        }

        const verisionJson = JSON.stringify(
          { version: env.GITVERSION, branch: env.GITBRANCH },
          null,
          2
        );

        fs.writeFile(
          path.join(config.build.assetsRoot, "version.json"),
          verisionJson,
          (err) => {
            if (err) {
              console.log("版本JSON创建失败");
            } else {
              console.log("版本JSON创建成功");
            }
          }
        );
      });
    }
  ],
  optimization: {
    minimize: true,
    minimizer: [
      new TerserPlugin({
        // 过滤掉以".min.js"结尾的文件.
        exclude: /\.min\.js$/i,
        // Enable multi-process parallel running and set number of concurrent runs.
        parallel: true,
        // Enable file caching. Default path to cache directory: node_modules/.cache/terser-webpack-plugin.
        cache: true,
        terserOptions: {
          // 开启变量名混淆
          mangle: true,
          compress: {
            unused: true,
            // 移除所有debugger
            drop_debugger: true,
            // 移除所有console
            drop_console: true,
            pure_funcs: [
              // 移除指定的指令,如console, alert等
              "console.log",
              "console.error",
              "console.dir"
            ]
          },
          format: {
            // 删除注释
            comments: true
          }
        },
        // 是否将注释剥离到单独的文件中
        extractComments: false
      })
    ],
    splitChunks: {
      minSize: 0,
      cacheGroups: {
        commons: {
          name: "commons",
          chunks: "all",
          minChunks: 3
        }
      }
    }
  },
  devtool: config.build.productionSourceMap ? config.build.devtool : false,
  performance: {
    // 用于控制Webpack在资源的大小超过限制的时候,做出提示
    hints: false
  }
};

// 通过webpack.dll.js中的entry获取包名,然后引用manifest.json和dll.js
Object.keys(webpackDll.entry).forEach((name) => {
  // 引用 manifest.json
  prodConfig.plugins.push(
    new webpack.DllReferencePlugin({
      manifest: require(`../app-dll/manifest.${name}.json`)
    })
  );

  // Add a JavaScript or CSS asset to the HTML generated by html-webpack-plugin
  prodConfig.plugins.push(
    new AddAssetHtmlPlugin([
      {
        filepath: utils.resolve(`./app-dll/dll.${name}.min.js`),
        outputPath: "./static/app-dll",
        publicPath: "./static/app-dll"
      }
    ])
  );
});

// 是否启用剔除多余不用的CSS样式
if(config.build.purgeCSS) {
  const purgeCSSPlugin = new PurgeCSSPlugin({
    paths: glob.sync(`${utils.resolve("src")}/**/*`, { nodir: true }),
    only: ["app"],
    // safelist 参考地址:https://purgecss.com/safelisting.html
    // standard: selectors width el such as el-button
    // deep: selectors width el as well as their children such as el-table th
    // greedy:selectors that have any part contain el such as button.el-button
    safelist: {
      standard: [
        /^el-/,
        /-(leave|enter|appear)(|-(to|from|active))$/,
        /^(?!(|.*?:)cursor-move).+-move$/,
        /^router-link(|-exact)-active$/,
        /data-v-.*/,
        "html",
        "body"
      ],
      deep: [/^el-/],
      greedy: [/^el-/]
    }
  });

  prodConfig.plugins.push(purgeCSSPlugin)
}

// 是否开启打包后的分析报告
if (config.build.bundleAnalyzerReport) {
  prodConfig.plugins.push(new BundleAnalyzerPlugin());
}

let webpackConfig = merge(baseConfig, prodConfig);
// 是否需要测量webpack构建速度
if (config.build.isSpeedMeasure) {
  const smp = new SpeedMeasurePlugin();
  webpackConfig = smp.wrap(webpackConfig);
}

module.exports = webpackConfig;

.babelrc

{
  "presets": [["@babel/preset-env"], "@vue/babel-preset-jsx"],
  "plugins": ["@babel/plugin-syntax-dynamic-import"],
  "compact": false
}

.env.development

NODE_ENV=development
BASE_API=/base

.env.production

NODE_ENV=production
BASE_API=/base

.eslintrc

const webpackBaseConfig = require('./app-build/webpack.base.js');

module.exports = {
  /** 根目录标识
   * 标识当前配置文件为eslint的根配置文件,停止在父级目录中查找
   */
  root: true,
  /** 解析器
   * ESLint 默认使用Espree作为其解析器
   * 解析器必须是本地安装的一个 npm 模块。即必须按照在本地的node_module中。
   * 解析器是用于解析js代码的,会对js进行一些语法分析,语义分析什么的,才能判断语句符不符合规范。
   * 解析器有很多,但兼容eslint的解析器有以下几个:
   * Espree:默认解析器,一个从Esprima中分离出来的解析器,做了一些优化
   * Esprima:js标准解析器,是一个用来从字符串中解析js代码的工具
   * Babel-ESLint:一个对Babel解析器的包装,使其能够与ESLint兼容。如果我们的代码需要经过babel转化,则对应使用这个解析器
   * 由于解析器只有一个,用了「vue-eslint-parser」就不能用「babel-eslint」。
   * 所以「vue-eslint-parser」的做法是,在解析器选项中,再传入一个解析器选项parser。从而在内部处理「babel-eslint」,检测<script>中的js代码
   */
  parser: 'vue-eslint-parser',
  /** 解析器选项
   * http://eslint.cn/docs/user-guide/configuring#specifying-parser-options
   * 有些解析器支持一些特定的选项。你可以使用 parserOptions 来指定不同解析器的选项。
   * 注意,在使用自定义解析器时,为了让 ESLint 在处理非 ECMAScript 5 特性时正常工作,配置属性 parserOptions 仍然是必须的。
   * 解析器会被传入 parserOptions,但是不一定会使用它们来决定功能特性的开关。。
   *
   */
  parserOptions: {
    parser: 'babel-eslint'
  },
  /** 运行环境
   * http://eslint.cn/docs/user-guide/configuring#specifying-environments
   * 常见的运行环境:
   * browser - 浏览器环境中的全局变量。
   * node - Node.js 全局变量和 Node.js 作用域。
   * commonjs - CommonJS 全局变量和 CommonJS 作用域 (仅为使用 Browserify/WebPack 写的只支持浏览器的代码)。
   * es6 - 启用除了 modules 以外的所有 ECMAScript 6 特性(该选项会自动设置 ecmaVersion 解析器选项为 6)。
   * worker - Web Workers 全局变量。
   * amd - 将 require() 和 define() 定义为像 amd 一样的全局变量。
   * serviceworker - Service Worker 全局变量。
   */
  env: {
    node: true,
    browser: true,
    es6: true
  },
  /** 插件
   * http://eslint.cn/docs/user-guide/configuring#configuring-plugins
   * 1、注意插件名忽略了「eslint-plugin-」前缀,所以在package.json中,对应的项目名是「eslint-plugin-vue」
   * 2、插件的作用类似于解析器,用以扩展解析器的功能,用于检测非常规的js代码。也可能会新增一些特定的规则。
   * 3、如 eslint-plugin-vue,是为了帮助我们检测.vue文件中 <template> 和 <script> 中的js代码
   * 4、eslint-plugin-vue 插件依赖 「vue-eslint-parser」解析器。
   */
  plugins: ['vue'],
  /** 规则继承
   * http://eslint.cn/docs/user-guide/configuring#extending-configuration-files
   * 可继承的方式有以下几种:
   * eslint内置推荐规则,就只有一个,即「eslint:recommended」
   * 可共享的配置, 是一个 npm 包,它输出一个配置对象。即通过npm安装到node_module中
   * 可共享的配置可以省略包名的前缀 eslint-config-,即实际设置安装的包名是 eslint-config-airbnb-base
   * 从插件中获取的规则,书写规则为 「plugin:插件包名/配置名」,其中插件包名也是可以忽略「eslint-plugin-」前缀。如'plugin:vue/essential'
   * 从配置文件中继承,即继承另外的一个配置文件,如'./node_modules/coding-standard/eslintDefaults.js'
   */
  extends: [
    'plugin:vue/essential',
    /**
     * 有两种eslint规范:
     *  一种是自带了react插件的「eslint-config-airbnb」,
     *  一种是基础款「eslint-config-airbnb-base」
     *    在使用airbnb-base的时候,需要安装「eslint-plugin-import」
     * airbnb-base 包括了ES6的语法检测,需要依赖 「eslint-plugin-import」
     * airbnb-base 地址:https://github.com/airbnb/javascript
     */
    'airbnb-base'
  ],
  /** 规则共享参数
   * http://eslint.cn/docs/user-guide/configuring#adding-shared-settings
   * ESLint 支持在配置文件添加共享设置。
   * 你可以添加 settings 对象到配置文件,它将提供给每一个将被执行的规则。
   * 如果你想添加的自定义规则而且使它们可以访问到相同的信息,这将会很有用,并且很容易配置。
   */
  settings: {
    //  注意,「import/resolver」并不是eslint规则项,与rules中的「import/extensions」不同,它不是规则项.
    // 这里只是一个参数名,叫「import/resolver」,会传递给每个规则项。
    // settings并没有具体的书写规则,「import/」只是import模块自己起的名字,原则上,它直接命名为「resolver」也可以,不是强制设置的。
    // 因为「import」插件很多规则项都用的这个配置项,所以并没有通过rules设置,而是通过settings共享。
    // 具体使用方法可参考:https://github.com/benmosher/eslint-plugin-import
    'import/resolver': {
      /**
       * 这里传入webpack并不是import插件能识别webpack,而且通过npm安装了「eslint-import-resolver-webpack」
       * import」插件通过「eslint-import-resolver-」+「webpack」找到该插件并使用,就能解析webpack配置项,使用里面的参数。
       * 主要是使用以下这些参数,共享给import规则,让其正确识别import路径
       * extensions: [".js", ".vue", ".json"],
       * alias: {
       *  vue$: "vue/dist/vue.esm.js",
       * "@": utils.resolve("src")
       * }
       * eslint-import-resolver-webpack参考地址:https://www.npmjs.com/package/eslint-import-resolver-webpack
       */
      webpack: {
        config : {
          resolve: webpackBaseConfig.resolve
        }
      }
    }
  },
  /** 全局变量
   * http://eslint.cn/docs/user-guide/configuring#specifying-globals
   * 当访问当前源文件内未定义的变量时,no-undef 规则将发出警告。
   * 如果你想在一个源文件里使用全局变量,推荐你在 ESLint 中定义这些全局变量,这样 ESLint 就不会发出警告了。你可以使用注释或在配置文件中定义全局变量。
   * key值就是额外添加的全局变量
   * value值用于标识该变量能否被重写,类似于const的作用。writable为允许变量被重写, readonly不允许被重写。
   * 注意:要启用no-global-assign规则来禁止对只读的全局变量进行修改。
   */
  globals: {
    // var1: "writable" // 例如定义var1这个全局变量,且这个变量可以被重写
    // var2: "readonly" // 例如定义var2这个全局变量,但是这个变量不可以被重写
  },
  /** 自定义规则
   * http://eslint.cn/docs/user-guide/configuring#configuring-rules
   * "off" 或者0 关闭规则
   * "warn" 或者1 将规则打开为警告(不影响退出代码)
   * "error" 或者2 将规则打开为错误(触发时退出代码为1)
   * 示例:如:'no-restricted-syntax': 0, // 表示关闭该规则
   * 如果某项规则,有额外的选项,可以通过数组进行传递,而数组的第一位必须是错误级别。
   * 如 'semi': ['error', 'never'], never就是额外的配置项
   */
  rules: {
    // eslint规则参考:https://zh-hans.eslint.org/docs/latest/rules/
    'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
    'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
    'linebreak-style': 'off', // 取消换行符\n或\r\n的验证
    // airbnb-base规则参考:
    'prefer-const': 'error', // 2.1 要求声明后永远不会重新赋值的变量使用const
    'no-const-assign': 'error', // 2.1 禁止重新分配变量const
    'no-var': 'error', // 2.2 要求使用let或const而不是var
    'no-new-object': 'error', // 3.1 禁止使用Object构造函数
    'object-shorthand': 'error', // 3.2 使用对象方法的简写方式
    'quote-props': 'error', // 3.6 Only quote properties that are invalid identifiers
    'no-prototype-builtins': 'error', // 3.7 不要直接使用 Object.prototype 的方法,使用类似Object.prototype.hasOwnProperty.call(object, key)
    'prefer-object-spread': 'error', // 3.8 使用对象扩展而不是 Object.assign
    'no-array-constructor': 'error', // 4.1 禁止使用Array构造函数,使用字面量值创建数组
    'array-callback-return': 'error', // 4.7 在数组方法回调中使用 return 语句,如果是单一声明语句的情况,可省略return
    'prefer-destructuring': 'error', // 5.1 数组和对象解构
    quotes: ['error', 'single'], // 6.1 字符串使用单引号
    'prefer-template': 'error', // 6.3 使用模板而非字符串连接
    'template-curly-spacing': 'error', // 6.3 模板字符串中的嵌入表达式周围不能使用空格
    'no-eval': 'error', // 6.4 禁止使用eval()
    'no-useless-escape': 'error', // 6.5 禁止不必要的转义
    'func-style': 'error', // 7.1 使用命名函数表达式而不是函数声明
    'func-names': 'error', // 7.1 函数表达式必须有名字
    'wrap-iife': ['error', 'any'], // 7.2 用圆括号包裹自执行匿名函数,outside、inside、any
    'no-loop-func': 'error', // 7.3 不要在非函数代码块(if、while等循环语句中)中声明函数
    'prefer-rest-params': 'error', // 7.6 使用剩余运算符rest而不是 arguments
    'default-param-last': 'error', // 7.9 把默认参数赋值放在最后
    'no-new-func': 'error', // 7.10 禁止使用Function构造函数
    'space-before-function-paren': 'error', // 7.11 函数名称或关键字与左括号之间需要加空格
    'space-before-blocks': 'error', // 7.11 在块级作用域之前需要加空格
    'no-param-reassign': 'error', // 7.12 禁止对函数参数再赋值
    'prefer-spread': 'error', // 7.14 使用扩展运算符而非.apply
    'function-paren-newline': 'error', // 7.15 在函数括号内强制使用一致的换行符
    'prefer-arrow-callback': 'error', // 8.1 使用箭头函数作为回调
    'arrow-spacing': 'error', // 8.1 箭头函数中强制调整箭头前后的间距一致
    'arrow-parens': 'error', // 8.2 箭头函数中强制使用圆括号
    'arrow-body-style': 'error', // 8.2 如果函数体只包含一条没有副作用的返回表达式的语句,可以省略花括号并使用隐式的return, 否则保留花括号并使用return语句
    'no-confusing-arrow': 'error', // 8.5 避免箭头函数(=>)和比较操作符(<=, >=)混淆使用
    'implicit-arrow-linebreak': 'error', // 8.6 使用隐式返回强制箭头函数体的位置
    'no-useless-constructor': 'error', // 9.5 如果未指定默认构造函数,则类具有默认构造函数。不需要空构造函数或仅委托给父类的构造函数。
    'class-methods-use-this': 'error', // 9.7 除非外部库或框架要求使用特定的非静态方法,否则类方法应使用此方法或将其制成静态方法。
    'no-duplicate-imports': 'error', // 10.4 禁止重复导入
    'import/no-mutable-exports': 'error', // 10.5 禁止导出可变的引用
    'import/prefer-default-export': 'error', // 10.6 当模块只有一个导出时,更喜欢使用默认导出而不是命名导出。
    'import/first': 'error', // 10.7 强制将导入放在顶层
    'object-curly-newline': 'error', // 10.8 多行import应该缩进,就像多行数组和对象字面量
    'import/no-webpack-loader-syntax': 'error', // 10.9 禁止使用 Webpack loader 语法
    'import/extensions': [
      'error',
      'always',
      {
        js: 'never',
        vue: 'never'
      }
    ], // 10.10 文件扩展名
    'no-iterator': 'error', // 11.1 禁止使用迭代器
    'no-restricted-syntax': 'error', // 11.1 禁止指定的语法
    'generator-star-spacing': ['error', { before: false, after: true }], // 11.2 强制 generator 函数中 * 号周围使用一致的空格
    'dot-notation': 'error', // 12.1 使用点号访问属性
    'prefer-exponentiation-operator': 'error', // 12.3 使用指数运算符 ** 替代 Math.pow
    'no-undef': 'error', // 13.1 禁止使用未定义的变量
    'one-var': 'error', // 13.2 每个变量都用一个 const 或 let
    'no-multi-assign': 'error', // 13.5 变量不要进行链式赋值
    'no-plusplus': 'error', // 13.6 禁止使用一元操作符 ++ 和 --
    'operator-linebreak': 'error', // 13.7 在赋值的时候避免在 = 前/后换行。如果你的赋值语句超出 max-len, 那就用小括号把这个值包起来再换行。
    'no-unused-vars': 'error', // 13.8 不允许未使用的变量
    'no-use-before-define': 'error', // 14.5 禁止变量、类和函数在定义之前使用
    eqeqeq: 'error', // 15.1 使用 === 和 !== 代替 == 和 !=
    'no-case-declarations': 'error', // 15.5 在case和default分句里用大括号创建一块包含语法声明的区域
    'no-nested-ternary': 'error', // 15.6 避免嵌套三元表达式
    'no-unneeded-ternary': 'error', // 15.7 避免不必要的三元表达式
    'no-mixed-operators': 'error', // 15.8 用圆括号来包裹混合操作符
    'nonblock-statement-body-position': 'error', // 16.1 用大括号包裹多行代码块
    'brace-style': 'error', // 16.2 if表达式的else和if的关闭大括号在一行
    'no-else-return': 'error', // 16.3 如果if语句有一个return语句,else就不用写了
    'spaced-comment': 'error', // 18.3 注释前加空格
    indent: ['error', 'tab'], // 19.1 使用设置为 tab键的缩进
    'keyword-spacing': 'error', // 19.3 在控制语句(if, while 等)的圆括号前空一格。在函数调用和定义时,参数列表和函数名之间不空格
    'space-infix-ops': 'error', // 19.4 用空格来隔开操作符
    'eol-last': 'error', // 19.5 文件结尾带单个换行符
    'newline-per-chained-call': 'error', // 19.6 方法链中每次调用后需要换行符
    'no-whitespace-before-property': 'error', // 19.6 禁止属性前有空白
    'padded-blocks': 'error', // 19.8 不要用空行填充块
    'no-multiple-empty-lines': ['error', { max: 2 }], // 19.9 不要使用多个空行来填充代码(默认最大连续空行数为2)
    'space-in-parens': 'error', // 19.10 圆括号内不要加空格
    'array-bracket-spacing': ['error', 'never'], // 19.11 never(默认值)不允许数组括号内有空格
    'object-curly-spacing': 'error', // 19.12 大括号内添加空格(强制在大括号内保持一致的间距)
    'max-len': ['error', { code: 200 }], // 19.13 限制一行的最大长度
    'block-spacing': 'error', // 19.14 作为语句的花括号内也要加空格 —— { 后和 } 前都需要空格
    'comma-spacing': 'error', // 19.15 禁止在逗号前使用空格,并要在逗号后使用空格
    'computed-property-spacing': 'error', // 19.16 在计算属性括号内强制使用间距
    'func-call-spacing': 'error', // 19.17 避免在函数及其调用之间使用空格
    'key-spacing': 'error', // 19.18 在对象属性中强制键和值之间的一致间距
    'no-trailing-spaces': 'error', // 19.19 禁止行尾空格(空格、制表符和其他 Unicode 空格字符)
    'comma-style': 'error', // 20.1 不要前置逗号
    'comma-dangle': ['error', 'never'], // 20.2 附加尾随逗号,"never"(默认值)不允许尾随逗号;"always"需要尾随逗号
    semi: 'error', // 21.1 语句强制分号结尾
    'no-new-wrappers': 'error', // 22.2 禁止使用new创建String, Number, and Boolean实例
    radix: 'error', // 22.3 使用parseInt时始终指定基数
    'id-length': ['error', { properties: 'never' }], // 23.1 变量名长度,避免使用单个字母命名,让你的命名可描述   { properties: 'never' }表示不检查属性名长度
    camelcase: 'error', // 23.2 命名对象、函数和实例时使用驼峰大小写
    'new-cap': 'error', // 23.3 构造函数首字母大写
    'no-underscore-dangle': 'error', // 23.4 不要使用尾随下划线或前导下划线
    'no-restricted-globals': 'error' // 29.1 禁止指定的全局变量
  }
};

.prettierrc

{
  "printWidth": 200,
  "tabWidth": 2,
  "semi": true,
  "endOfLine": "auto",
  "trailingComma": "none"
}

index.html

<!DOCTYPE html>
<html lang="en">

<head>
  <title>webpack4.x+vue2.x脚手架</title>
  <%= require('raw-loader!./meta.html') %>
</head>

<body>
  <div id="root-app"></div>
  <script type="text/javascript" src="./static/index.js"></script>
</body>

</html>

meta.html

<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0,maximum-scale=1.0,user-scalable=no">
<meta http-equiv="Pragma" content="no-cache">
<meta http-equiv="Cache-control" content="no-cache">
<meta http-equiv="Expires" content="0">
<meta http-equiv="Cache" content="no-cache">

package.json

{
  "name": "itrus-webpack4-vue-cli",
  "version": "1.0.0",
  "description": "webpack4.x-vue脚手架",
  "main": "src/main.js",
  "scripts": {
    "dev": "webpack-dev-server --progress --config app-build/webpack.dev.js",
    "build": "webpack --config app-build/webpack.prod.js",
    "dll": "webpack --config app-build/webpack.dll.js",
    "lint": "cross-env NODE_ENV=development eslint --ext .js,.vue src --fix"
  },
  "repository": {
    "type": "git",
    "url": "https://git.itrus.com.cn/RDCenter/FDD/webengineering/itrus-webpack4-vue-cli.git"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "axios": "^1.5.0",
    "element-ui": "^2.15.14",
    "vue": "^2.7.14",
    "vue-router": "^3.6.5",
    "vuex": "^3.6.2"
  },
  "devDependencies": {
    "@babel/core": "^7.22.15",
    "@babel/plugin-syntax-dynamic-import": "^7.8.3",
    "@babel/preset-env": "^7.22.15",
    "@vue/babel-helper-vue-jsx-merge-props": "^1.4.0",
    "@vue/babel-preset-jsx": "^1.4.0",
    "add-asset-html-webpack-plugin": "^3.2.2",
    "autoprefixer": "^10.4.15",
    "babel-eslint": "^10.1.0",
    "babel-loader": "^8.3.0",
    "clean-webpack-plugin": "^3.0.0",
    "copy-webpack-plugin": "^6.4.1",
    "core-js": "^3.26.1",
    "cross-env": "^7.0.3",
    "css-loader": "^4.3.0",
    "css-minimizer-webpack-plugin": "^1.3.0",
    "cssnano": "^4.1.11",
    "eslint": "^7.32.0",
    "eslint-config-airbnb-base": "^15.0.0",
    "eslint-import-resolver-webpack": "^0.13.7",
    "eslint-plugin-import": "^2.28.1",
    "eslint-plugin-vue": "^7.18.0",
    "eslint-webpack-plugin": "^2.7.0",
    "file-loader": "^6.2.0",
    "friendly-errors-webpack-plugin": "^1.7.0",
    "git-revision-webpack-plugin": "^3.0.6",
    "glob": "^7.2.3",
    "html-webpack-plugin": "^4.5.2",
    "less": "^4.2.0",
    "less-loader": "^6.2.0",
    "mini-css-extract-plugin": "^1.6.2",
    "node-notifier": "^10.0.1",
    "optimize-css-assets-webpack-plugin": "^5.0.8",
    "portfinder": "^1.0.32",
    "postcss": "^8.4.29",
    "postcss-loader": "^4.3.0",
    "prettier": "^2.8.8",
    "progress-bar-webpack-plugin": "^2.1.0",
    "purgecss-webpack-plugin": "^4.1.3",
    "raw-loader": "^0.5.1",
    "sass": "1.32.13",
    "sass-loader": "^7.3.1",
    "sass-resources-loader": "^2.2.4",
    "speed-measure-webpack-plugin": "^1.5.0",
    "style-loader": "^1.3.0",
    "terser-webpack-plugin": "^4.2.3",
    "thread-loader": "^3.0.0",
    "url-loader": "^4.1.1",
    "vue-eslint-parser": "^7.11.0",
    "vue-loader": "^15.10.2",
    "vue-style-loader": "^3.0.1",
    "vue-template-compiler": "^2.7.14",
    "webpack": "^4.47.0",
    "webpack-bundle-analyzer": "^4.9.1",
    "webpack-cli": "^4.10.0",
    "webpack-dev-server": "^4.15.1",
    "webpack-merge": "^5.9.0"
  },
  "eslintIgnore": [
    "/app-build",
    "/app-dll"
  ]
}

标签:webpack4,const,plugin,require,loader,webpack,vue,error,终极
From: https://www.cnblogs.com/moqiutao/p/18415729

相关文章

  • 基于Node.js+vue基于开源工作流的自来水业扩报装系统(开题+程序+论文) 计算机毕业设计
    本系统(程序+源码+数据库+调试部署+开发环境)带文档lw万字以上,文末可获取源码系统程序文件列表开题报告内容研究背景随着城市化进程的加速和居民生活质量的提升,自来水业作为城市基础设施的重要组成部分,其服务质量与效率直接关系到民众的生活品质与城市的发展速度。然而,传统......
  • 基于Node.js+vue智慧医疗系统(开题+程序+论文) 计算机毕业设计
    本系统(程序+源码+数据库+调试部署+开发环境)带文档lw万字以上,文末可获取源码系统程序文件列表开题报告内容研究背景随着科技的飞速发展,医疗领域正经历着前所未有的变革。传统医疗模式在面对日益增长的医疗需求、资源分配不均以及患者个性化服务要求时显得力不从心。智慧医......
  • 基于Node.js+vue基于Springboot的手机电商网站(开题+程序+论文) 计算机毕业设计
    本系统(程序+源码+数据库+调试部署+开发环境)带文档lw万字以上,文末可获取源码系统程序文件列表开题报告内容研究背景随着互联网技术的飞速发展,电子商务已成为人们日常生活中不可或缺的一部分,特别是在移动互联网的普及下,手机电商以其便捷性、实时性和广泛覆盖性迅速崛起。传......
  • 基于Node.js+vue基于Vue的社区拼购商城(开题+程序+论文) 计算机毕业设计
    本系统(程序+源码+数据库+调试部署+开发环境)带文档lw万字以上,文末可获取源码系统程序文件列表开题报告内容研究背景随着互联网技术的飞速发展,电子商务已成为人们日常生活中不可或缺的一部分。其中,社区拼购作为一种新兴的购物模式,凭借其价格优势、社交互动性和便捷性,迅速赢......
  • 【webpack4系列】编写可维护的webpack构建配置(四)
    构建配置包设计构建配置管理的可选方案:通过多个配置文件管理不同环境的构建,webpack--config参数进行控制将构建配置设计成一个库,比如:xxx-webpack抽成一个工具进行管理,比如:create-vue-app将所有的配置放在一个文件,通过--env参数控制分支选择通过多个配置文件管理不同......
  • 【webpack4系列】webpack构建速度和体积优化策略(五)
    目录速度分析:使用speed-measure-webpack-plugin体积分析:使用webpack-bundle-analyzer使用高版本的webpack和Node.js多进程/多实例构建资源并行解析可选方案使用HappyPack解析资源使用thread-loader解析资源多进程并行压缩代码方法一:使用parallel-uglify-plugin插件方法......
  • 【webpack4系列】webpack进阶用法(三)
    自动清理构建目录产物webpack4.x使用clean-webpack-plugin@3版本:npmiclean-webpack-plugin@3-Dwebpack配置:const{CleanWebpackPlugin}=require('clean-webpack-plugin')plugins:[newCleanWebpackPlugin(),]PostCSS插件autoprefixer自动补齐CSS3前缀需......
  • 【webpack4系列】webpack初识与构建工具发展(一)
    为什么需要构建工具?转换ES6语法转换JSXCSS前缀补全/预处理器压缩混淆图片压缩前端构建演变之路ant+YUIToolgruntgulp、fis3webpack、rollup、parcel为什么选择webpack?社区⽣态丰富配置灵活和插件化扩展官⽅方更新迭代速度快初识webpack,简单的例子入手......
  • 简易化解vue3中store存储与使用的响应式问题
    今日在写网站的时候遇见一个大坑,在使用vue3渲染数据的时候发现,在先取store后,又有了修改store中的数据时出现的没有预想的响应式更改那么我有了一下步骤从存储段设置log,探查是否真正存储到了数据(因为网页版的vue插件有一些bug,不能全靠它来观察数据变化)发现存储没有问......
  • 基于Node.js+vue基于SpingBoot的剧本杀管理系统(开题+程序+论文) 计算机毕业设计
    本系统(程序+源码+数据库+调试部署+开发环境)带文档lw万字以上,文末可获取源码系统程序文件列表开题报告内容研究背景近年来,随着桌游文化的兴起与线上娱乐的蓬勃发展,剧本杀作为一种集角色扮演、逻辑推理与社交互动于一体的新型娱乐方式,迅速在年轻人中走红。然而,传统的剧本杀......