【万字】手把手搭建Webpack5 + React18 + TS脚手架

714 阅读9分钟

本文内容覆盖以下方面:

  • 介绍npm包的发布流程。
  • 教你如何搭建一个简单的webpack5项目。
  • 配置一个ts + react18项目脚手架。
  • 如何分析并优化webpack配置。
  • 代码规范化,提交规范化
  • 手写loader、plugin等。

脚手架代码已经提交到github,有需要可以下载学习:react-webpack-staging

1. 发布一个npm包

1.1 初始化npm

npm init

image.png

1.2 安装依赖

将镜像源更改为淘宝镜像。然后,在全局和开发环境都安装webpack

npm config set registry https://registry.npm.taobao.org
sudo npm i -g webpack webpack-cli
npm i -D webpack webpack-cli

1.3 写一个简单demo

const heloword = () => {
    console.log('Hello World!')
}

heloword();

exports.heloword = heloword;

运行下看看效果 image.png

1.4 创建一个账号

npm adduser

发现镜像不能直接发布 image.png

所以还是需要切换到官方地址

npm config set registry https://registry.npmjs.org

输入相关信息,npm会给你发送验证邮件,输入邮件中的验证码就完成登录了。

image.png

这里管理npm源也可使用nrm。

sudo npm install -g nrm
nrm use npm # 切换为npm官方
nrm use taobao # 切换为淘宝镜像

1.5 发布第一个版本

执行下面命令就可以发布

npm publish

image.png

我们到npm官网搜一下 www.npmjs.com/search?q=re…

确实发布了,很好! image.png

1.6 引用自己的包

const { heloword } = require('react-webpack-staging');

heloword();

可以验证是没有问题的

image.png

1.7 编译

  • 执行编译命令:
webpack ./index.js

image.png

  • 发现报错了,没有指定环境变量。所以加上环境变量
webpack --mode=development ./index.js

image.png

  • 把命令放在scripts中,则变成这样:
"scripts": {
    "build": "webpack --config=webpack.config.js"
}

image.png

  • 可以看看编译出来的产物是这样: image.png

1.8 其他

设置上传到npm的白名单

"files": [
  "dist"
],

2. webpack配置

2.1 基本配置

我们将上述过程调整下目录结构,并把配置统一放在webpack.config.js中。

webpack.config.js

const path = require('path')

module.exports = {
  mode: 'development',
  entry: path.resolve(__dirname, './src/index.js'),
  output: {
    filename: 'main.js',
    path: path.resolve(__dirname, './dist')
  }
}

package.json

"scripts": {
    "build": "webpack --config=webpack.config.js"
}

重新运行命令,并得到产出物

npm run build

image.png

2.2 html

2.2.1 html模版中引用JS脚本

我们需要在html模版中引用JS脚本,为了让每次生成的脚步不一样让html识别js不一样时重新加载需要给每次生成的js脚步加上hash码。

脚本hash配置

output: {
    filename: '[name].[hash:8].js',
    path: path.resolve(__dirname, './dist')
},

html插件安装

sudo npm i -D html-webpack-plugin

创建html文件 public/index.html

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="utf-8"> 
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <title>webpack react脚手架</title>
    </head>
<body></body>
</html>

html插件配置

const HtmlWebpackPlugin = require('html-webpack-plugin')
plugins:[
    new HtmlWebpackPlugin({
      template:path.resolve(__dirname, './public/index.html')
    })
]

image.png

打开这个文件并查看控制台,可以看到这样的效果

image.png

2.2.2 清理之前打包的文件

我们发现上面每次执行都生成了一个重复的配置文件,所以可以安装下面这个插件来保证只保留最新的js文件。

sudo npm i clean-webpack-plugin -D

配置

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

plugins:[
    new CleanWebpackPlugin(),
]

验证,编译后发现文件确实只有最新的了

image.png

2.2.3 多入口

用多个HtmlWebpackPlugin配置每个页码所用的脚本即可:

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

module.exports = {
  mode: 'development',
  entry: {
    page1: path.resolve(__dirname, './src/page1.js'),
    page2: path.resolve(__dirname, './src/page2.js')
  },
  output: {
    filename: '[name].[hash:8].js',
    path: path.resolve(__dirname, './dist')
  },
  plugins:[
    new CleanWebpackPlugin(),
    new HtmlWebpackPlugin({
      template: path.resolve(__dirname, './public/page1.html'),
      filename: 'page1.html',
      chunks: ['page1']
    }),
    new HtmlWebpackPlugin({
      template: path.resolve(__dirname, './public/page2.html'),
      filename: 'page2.html',
      chunks: ['page2']
    })
  ]
}

image.png

2.3 css

2.3.1 加载css/less文件

安装相关的loader

npm i -D style-loader css-loader less less-loader

配置

module.exports = {
  ...
  module: {
    rules: [{
      test: /\.css$/,// css后缀文件
      use: ['style-loader', 'css-loader']// 从右向左解析原则
    }, {
      test: /\.less$/,// css后缀文件
      use: ['style-loader', 'css-loader', 'less-loader']// 从右向左解析原则
    }]
  }
}

编译

image.png

验证

打开page1.html可以看到效果: image.png

2.3.2 拆分CSS

npm i -D mini-css-extract-plugin

配置

...
plugins:[
    ...
    new MiniCssExtractPlugin({
      filename: '[name].[hash].css',
      chunkFilename: '[id].css'
    })
],
module: {
    ...
    rules: [{
      test: /\.less$/,// css后缀文件
      use: [MiniCssExtractPlugin.loader, 'css-loader', 'less-loader']// 从右向左解析原则
    }]
}

需要注意的是,要去掉'style-loader'会与mini-css-extract-plugin冲突。 ReferenceError: document is not defined image.png

看下编译结果 image.png

2.3.3 添加浏览器前缀

npm i -D autoprefixer postcss-preset-env

配置

module: {
    rules: [{
      test: /\.less$/,// css后缀文件
      use: [
        ...
        {
          loader:'postcss-loader',
          // 配置参数
          options:{
             postcssOptions:{
                 // 添加前缀
                 plugins:[
                     require('autoprefixer'),
                     require('postcss-preset-env')
                 ]
             }
          }
        }
       ]
    }]
}

当然也可以加到postcss.config.js文件,配置如下:

module.exports = {
    plugins: [require('autoprefixer')]   
}

这里为了减少配置文件,不使用这种方式。

编译后发现没有生效,查看文档发现需要配置兼容的浏览器环境

{
  loader:'postcss-loader',
  // 配置参数
  options:{
    postcssOptions:{
      // 添加前缀
        plugins:[
          require('autoprefixer')({
            overrideBrowserslist: [
              "last 2 version",
              "> 1%",
              "iOS >= 7",
              "Android > 4.1",
              "Firefox > 20"
            ]
          }),
          require('postcss-preset-env')
        ]
     }
  }
}, 

当然也可以在package.json中加browserslist,也可以用browserslistrc配置文件。这两种都不推荐。

验证下效果:

image.png

2.4 文件加载

文件、图片、字体、媒体文件等。

module.exports = {
  module: {
    rules: [{
      test: /\.(jpe?g|png|gif)$/i, //图片文件
      use: [
        {
          loader: 'url-loader',
          options: {
            limit: 10240,
            fallback: {
              loader: 'file-loader',
              options: {
                  name: 'img/[name].[hash:8].[ext]'
              }
            }
          }
        }
      ]
    },
    {
      test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/, //媒体文件
      use: [
        {
          loader: 'url-loader',
          options: {
            limit: 10240,
            fallback: {
              loader: 'file-loader',
              options: {
                name: 'media/[name].[hash:8].[ext]'
              }
            }
          }
        }
      ]
    },
    {
      test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/i, // 字体
      use: [
        {
          loader: 'url-loader',
          options: {
            limit: 10240,
            fallback: {
              loader: 'file-loader',
              options: {
                name: 'fonts/[name].[hash:8].[ext]'
              }
            }
          }
        }
      ]
    }]
  }
}

TODO 验证

2.5 babel转义js

npm i -D url-loader file-loader
module.exports = {
  module: {
    rules: [{
      test:/\.js$/, 
      use:{ 
        loader:'babel-loader', 
        options:{ 
          presets:['@babel/preset-env'] 
        } 
      }, 
      exclude: /node_modules/ 
    }]
  }
}

babel-loader只会将 ES6/7/8语法转换为ES5语法,但是对新api并不会转换 例如(promise、Generator、Set、Maps、Proxy等)
此时我们需要借助babel-polyfill来帮助我们转换

npm i -D @babel/polyfill
module.exports = {
  entry: {
    page1: ["@babel/polyfill", path.resolve(__dirname, './src/page1.js')],
    page2: ["@babel/polyfill", path.resolve(__dirname, './src/page2.js')]
  }
}

2.6 开发服务器

安装插件webpack-dev-server

npm i -D webpack-dev-server

配置

webpack.config.js

const Webpack = require('webpack') 
module.exports = { 
  ...
  devServer: { 
    port: 3000, 
    hot: true, 
    contentBase: '../dist' 
  }, 
  plugins: [ 
    new Webpack.HotModuleReplacementPlugin() 
  ] 
}

pageckage.json

"scripts": {
    "build": "webpack --config=webpack.config.js",
    "dev": "webpack-dev-server --config=webpack.config.js --open"
},

验证下,保存后重新刷新

image.png

3. react项目

3.1 基本配置

安装react依赖

tnpm i --save react react-dom

配置@babel/preset-react解析react语法。

webpack.config.js

module.exports = {
  ...
  module: {
    rules: [{ 
      test:/\.js$/, 
      use:{ 
        loader:'babel-loader', 
        options:{  
          presets:[
            '@babel/preset-react', // 支持react
            '@babel/preset-env'
          ] 
        }
      }, 
      exclude: /node_modules/ 
    },
  ]}
}

改造下入口文件page2.js

import './page2.less';
import React from 'react';
import ReactDOM from 'react-dom';
import Container from './components/Container.js'

ReactDOM.render(
    <Container />,
    document.getElementById('root')
);

改造下组件components/Container.js

import React from 'react';

const Component = () => {
  return <div class="container">
        <div class="box"></div>
        <div class="box"></div>
        <div class="box"></div>
    </div>
}

export default Component;

验证下能跑起来。

3.2 区分环境

安装依赖

npm i -D webpack-merge mini-css-extract-plugin css-minimizer-webpack-plugin

先分一下文件:

image.png

基本配置webpack.base.js。

  • 注意,为了使修改文件仅对部分文件生效,对其中的js使用chunkhash,内容使用contenthash。
  • 用环境变量process.argv判断是什么环境,从而决定是否做一些压缩等处理。
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
const MiniCssExtractPlugin = require('mini-css-extract-plugin');

const devMode = process.argv.indexOf('--mode=production') === -1;

module.exports = {
  ...
  output: {
    filename: '[name].[chunkhash:8].js',
    path: path.resolve(__dirname, '../dist')
  },
  plugins:[
    ...
    new MiniCssExtractPlugin({
      filename: devMode ? '[name].css' : '[name].[contenthash].css',
      chunkFilename: devMode ? '[id].css' : '[id].[contenthash].css'
    })
  ],
  module: {
    rules: [
    ...
    {
      test: /\.(jpe?g|png|gif)$/i, //图片文件
      use: [
        {
          loader: 'url-loader',
          options: {
            limit: 10240,
            fallback: {
              loader: 'file-loader',
              options: {
                  name: 'img/[name].[contenthash:8].[ext]'
              }
            }
          }
        }
      ]
    }
  ]}
}

webpack.dev.js

const path = require('path');

const BaseConfig = require('./webpack.base.js')
const WebpackMerge = require('webpack-merge')

module.exports = WebpackMerge.merge(BaseConfig,{
  mode: 'development',
  devtool: 'eval-cheap-module-source-map',
  devServer: {
    port: 3000,
    hot: true,
    static: {
      directory: path.join(__dirname, '../dist'),
    },
    compress: true
  }
});

webpack.dev.js

const BaseConfig = require('./webpack.base.js')
const WebpackMerge = require('webpack-merge')

const CssMinimizerPlugin = require('css-minimizer-webpack-plugin')

module.exports = WebpackMerge.merge(BaseConfig, {
  mode: 'production',
  devtool: 'eval-cheap-module-source-map',
  optimization: {
    minimizer: [
      new CssMinimizerPlugin({})
    ],
    splitChunks:{
      chunks: 'all',
      cacheGroups: {
        libs: {
          name: "chunk-libs",
          test: /[\\/]node_modules[\\/]/,
          priority: 10,
          chunks: "initial" // 只打包初始时依赖的第三方
        }
      }
    }
  }
})

这里顺便介绍下devtool的配置。

开发过程中或者打包后的代码都是webpack处理后的代码,如果进行调试肯定希望看到源代码,而不是编译后的代码, source map就是用来做源码映射的,

不同的映射模式会明显影响到构建和重新构建的速度, devtool选项就是webpack提供的选择源码映射方式的配置。

devtool的命名规则为 ^(inline-|hidden-|eval-)?(nosources-)?(cheap-(module-)?)?source-map$

关键字描述
inline代码内通过 dataUrl 形式引入 SourceMap
hidden生成 SourceMap 文件,但不使用
evaleval(...) 形式执行代码,通过 dataUrl 形式引入 SourceMap
nosources不生成 SourceMap
cheap只需要定位到行信息,不需要列信息
module展示源代码中的错误位置

开发环境推荐:eval-cheap-module-source-map

  • 本地开发首次打包慢点没关系,因为 eval 缓存的原因, 热更新会很快
  • 开发中,我们每行代码不会写的太长,只需要定位到行就行,所以加上 cheap
  • 我们希望能够找到源代码的错误,而不是打包后的,所以需要加上 module

后续还会介绍如何进行优化。

3.3 TS配置

npm i -D typescript ts-loader @babel/preset-typescript @babel/plugin-transform-runtime
  • 文件后缀名都改成tsx

webpack.base.js image.png

  • 初始化ts配置并修改配置 tsc --init

image.png

  • 解决类型报错

此时文件内容还会报类型错误,安装下面类型即可

npm i -D @types/react-dom @types/react
  • 修改扩展名extensions

image.png

3.4 支持注解

npm i @babel/plugin-proposal-decorators -D

开启ts注解配置

{
    "experimentalDecorators": true, 
}

TODO 验证

3.5 支持热更新

目的:修改tsx文件,能够带状态刷新(hooks除外)

npm i @pmmmwh/react-refresh-webpack-plugin react-refresh -D

webpack.dev.js

const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin');

module.exports = WebpackMerge.merge(BaseConfig,{
  plugins: [
    new ReactRefreshWebpackPlugin(), // 添加热更新插件
  ]
});

TODO 验证

4. 配置优化

4.1 打包性能分析

安装以下 webpack 插件,帮助我们分析优化效率:

npm i -D progress-bar-webpack-plugin speed-measure-webpack-plugin webpack-bundle-analyzer chalk@4.x
  • 进度条

webpack.prod.js

module.exports = WebpackMerge.merge(BaseConfig, {
  ...
  plugins: [// 进度条
    new ProgressBarPlugin({
      format: `  :msg [:bar] ${chalk.green.bold(":percent")} (:elapsed s)`,
    })
  ]
}

包含内容、进度条、进度百分比、消耗时间,进度条效果如下: image.png

  • 编译速度
const SpeedMeasurePlugin = require("speed-measure-webpack-plugin");
const smp = new SpeedMeasurePlugin();
module.exports = smp.wrap({
  // ...webpack config...
});

包含各工具的构建耗时,效果如下: image.png

  • 包体积分析
const BundleAnalyzerPlugin = require("webpack-bundle-analyzer").BundleAnalyzerPlugin;

module.exports = {
  //...
  plugins: [
    // 打包体积分析
    new BundleAnalyzerPlugin(),
  ]
};

运行npm run build后能看到会自动打开http://127.0.0.1:8888/展示包的分布 image.png

4.2 文件缓存

webpack5 开箱即用的持久缓存是比 dll 更优的解决方案。

配置

module.exports = {
  mode: 'development',
  cache: {
    type: "filesystem", // 使用文件缓存
  }
}

可见首次构建耗时提升到了3792ms。但随后的编译耗时从1920ms降到了576ms。效果十分明显

image.png image.png

4.3 其他尝试

  • include/exclude

image.png

配置文件范围,有略微提升,不是很稳定。目前项目文件较少,此效果可以忽略。

image.png

  • asset/resource

image.png 使用 webpack 资源模块 (asset module) 代替旧的 assets loader(如  file-loader/url-loader/raw-loader  等),减少 loader 配置数量。

目前项目文件较少,此效果可以忽略。

  • thread-loader
npm i -D thread-loader 

image.png

目前项目文件较少,此效果可以忽略。

4.4 terser/gzip等优化体积

优化前 image.png

  • 使用terser-webpack-plugin压缩

webpack.prod.js

const TerserPlugin = require("terser-webpack-plugin");
{
  mode: 'production',
  optimization: {
    ...
    minimizer: [
      new CssMinimizerPlugin({}),
      new TerserPlugin({
        parallel: 4,
        terserOptions: {
          parse: {
            ecma: 8,
          },
          compress: {
            ecma: 5,
            warnings: false,
            comparisons: false,
            inline: 2,
          },
          mangle: {
            safari10: true,
          },
          output: {
            ecma: 5,
            comments: false,
            ascii_only: true,
          },
        },
      }),
    ]
  }
}

减少200k image.png

  • gzip压缩
npm i compression-webpack-plugin glob -D
const BaseConfig = require('./webpack.base.js')
const WebpackMerge = require('webpack-merge')

const CompressionPlugin  = require('compression-webpack-plugin');

const config = WebpackMerge.merge(BaseConfig, {
  mode: 'production',
  plugins: [new CompressionPlugin({
    test: /.(js|css)$/, // 只生成css,js压缩文件
    filename: '[path][base].gz', // 文件命名
    algorithm: 'gzip', // 压缩格式,默认是gzip
    test: /.(js|css)$/, // 只生成css,js压缩文件
    threshold: 10240, // 只有大小大于该值的资源会被处理。默认值是 10k
    minRatio: 0.8 // 压缩率,默认值是 0.8
  })],
}

可见又缩小到之前的1/3 image.png

4.5 代码分离

代码分离能够把代码分离到不同的 bundle 中,然后可以按需加载或并行加载这些文件。代码分离可以用于获取更小的 bundle,以及控制资源加载优先级,可以缩短页面加载时间。

  • 抽离重复代码

SplitChunksPlugin 插件开箱即用,可以将公共的依赖模块提取到已有的入口 chunk 中,或者提取到一个新生成的 chunk。

webpack 将根据以下条件自动拆分 chunks:

新的 chunk 可以被共享,或者模块来自于 node_modules 文件夹; 新的 chunk 体积大于 20kb(在进行 min+gz 之前的体积); 当按需加载 chunks 时,并行请求的最大数量小于或等于 30; 当加载初始化页面时,并发请求的最大数量小于或等于 30; 通过 splitChunks 把 react 等公共库抽离出来,不重复引入占用体积。

此配置已有,略

  • CSS 文件分离

MiniCssExtractPlugin 插件将 CSS 提取到单独的文件中,为每个包含 CSS 的 JS 文件创建一个 CSS 文件,并且支持 CSS 和 SourceMaps 的按需加载。

此配置已有,略

  • 代码分割第三方包和公共模块

一般第三方包的代码变化频率比较小,可以单独把node_modules中的代码单独打包, 当第三包代码没变化时,对应chunkhash值也不会变化,可以有效利用浏览器缓存,还有公共的模块也可以提取出来,避免重复打包加大代码整体体积

webpack.prod.js

{
    splitChunks:{
      chunks: 'all',
      cacheGroups: {
        vendors: { // 提取node_modules代码
          test: /[\\/]node_modules[\\/]/,
          name: 'vendors', // 提取文件命名为vendors,js后缀和chunkhash会自动加
          minChunks: 1, // 只要使用一次就提取出来
          chunks: 'initial', // 只提取初始化就能获取到的模块,不管异步的
          minSize: 0, // 提取代码体积大于0就提取出来
          priority: 1, // 提取优先级为1
        },
        commons: { // 提取页面公共代码
          name: 'commons', // 提取文件命名为commons
          minChunks: 2, // 只要使用两次就提取出来
          chunks: 'initial', // 只提取初始化就能获取到的模块,不管异步的
          minSize: 0, // 提取代码体积大于0就提取出来
        }
      } 
    }
}

4.6 配置alias别名

webpack支持设置别名alias,设置别名可以让后续引用的地方减少路径的复杂度。

webpack.base.js

module.exports = {
  resolve: {
    alias: {
      '@': path.join(__dirname, '../src')
    }
  },
}

tsconfig.json

{
  "compilerOptions": {
    // ...
    "baseUrl": ".",
    "paths": {
      "@/*": [
        "src/*"
      ]
    }
  }
}

配置修改完成后,在项目中使用  @/xxx.xx,就会指向项目中src/xxx.xx,js/ts文件和css文件中都可以用。

4.7 配置包查找范围

使用requireimport引入模块

  • 相对或者绝对路径
  • node核心模块
  • 当前目录下node_modules
  • 父级文件夹查找node_modules
  • 系统node全局模块

这样查找不但耗时、而且当前没有引用的包因为其他上级范围存在,那么发布之后会报错。

webpack.base.js

const path = require('path')
module.exports = {
  // ...
  resolve: {
     // ...
     modules: [path.resolve(__dirname, '../node_modules')], // 查找第三方模块只在本项目的node_modules中查找
  },
}

4.8 tree-shaking css

默认js是开启摇树功能,能去除无用代码。对于css可以这样做:

npm i purgecss-webpack-plugin glob-all -D

webpack.prod.js

const { PurgeCSSPlugin } = require('purgecss-webpack-plugin');
const config = WebpackMerge.merge(BaseConfig, {
  mode: 'production',
  plugins: [
    // 清理无用css
    new PurgeCSSPlugin({
      // 检测src下所有tsx文件和public下index.html中使用的类名和id和标签名称
      // 只打包这些文件中用到的样式
      paths: globAll.sync([
        `${path.join(__dirname, '../src')}/**/*.tsx`,
        path.join(__dirname, '../public/index.html')
      ]),
    })
  ]
}

4.9 资源懒加载

webpack默认支持资源懒加载,只需要引入资源使用import语法来引入资源,webpack打包的时候就会自动打包为单独的资源文件,等使用到的时候动态加载。


import React, { lazy, Suspense, useState } from 'react'
const SomeLazyComponent = lazy(() => import('@/SomeLazyComponent')) // 使用import语法配合react的Lazy动态引入资源

function App() {
  const [ show, setShow ] = useState(false)
  
  // 点击事件中动态引入css, 设置show为true
  const onClick = () => {
    import('./app.css')
    setShow(true)
  }
  
  return (
    <>
      <h2 onClick={onClick}>展示</h2>
      {/* show为true时加载LazyDemo组件 */}
      { show && <Suspense fallback={null}><SomeLazyComponent /></Suspense> }
    </>
  )
}
export default App 

5. 编码规范

5.1 eslint/prettier

  • eslint
npm i eslint -g

通过eslint初始化一些配置

eslint --init

image.png

  • stylelint
npm install -D stylelint stylelint-order stylelint-config-standard stylelint-order stylelint-config-prettier stylelint-prettier
  • prettier npm i prettier -g
npm i prettier -g

.prettierrc.js

module.exports = {
    printWidth: 100, // 代码宽度建议不超过100字符
    tabWidth: 2, // tab缩进2个空格
    semi: true, // 末尾分号
    singleQuote: true, // 单引号
    jsxSingleQuote: true, // jsx中使用单引号
    trailingComma: 'es5', // 尾随逗号
    arrowParens: 'avoid', // 箭头函数仅在必要时使用()
    htmlWhitespaceSensitivity: 'css', // html空格敏感度
}

然后安装vscode插件。把自动格式化打开。

image.png

5.2 husky/commit 规范

安装依赖

npm install --D husky @commitlint/cli

添加git提交的hook

npx husky add .husky/commit-msg 'npx --no -- commitlint --edit $1'

添加配置文件 commitlint.config.js

module.exports = {
  extends: ['@commitlint/config-conventional'],
  rules: {
    'body-leading-blank': [1, 'always'],
    'footer-leading-blank': [1, 'always'],
    'header-max-length': [2, 'always', 72],
    'scope-case': [2, 'always', 'lower-case'],
    'subject-case': [2, 'never', ['start-case', 'pascal-case', 'upper-case']],
    'subject-empty': [2, 'never'],
    'subject-full-stop': [2, 'never', '.'],
    'type-case': [2, 'always', 'lower-case'],
    'type-empty': [2, 'never'],
    'type-enum': [
      2,
      'always',
      [
        'build',
        'chore',
        'ci',
        'docs',
        'feat',
        'fix',
        'perf',
        'refactor',
        'revert',
        'style',
        'test',
      ],
    ],
  },
};

验证

image.png