webpack详情3

editorconfig

在项目中引入 editorconfig 是为了在多人协作开发中保持代码的风格和一致性。不同的开发者使用不 同的编辑器或IDE ,可能会有不同的缩进(比如有的人喜欢 4 个空格,有的喜欢 2 个空格)、换行符、编码 格式等。甚至相同的编辑器因为开发者自定义配置的不同也会导致不同风格的代码,这会导致代码的可 读性降低,增加代码冲突的可能性,降低了代码的可维护性。
EditorConfig 使不同编辑器可以保持同样的配置。因此,我们得以无需在每次编写新代码时,再
依靠 Prettier 来按照团队约定格式化一遍(出现保存时格式化突然改变的情况) 。当然这需要在
你的 IDE 上安装了必要的 EditorConfig 插件或扩展。
安装 EditorConfig for VS Code
目前主流的编辑器或者 IDE 基本上都有对应的 EditorConfig 插件,有的是内置支持的(比如,
WebStorm 不需要独立安装 EditorConfig 的插件),有的需要独立安装。

需要注意的是,不同的插件对 EditorConfig 属性的支持度不一样,笔者使用的是 VS Code

{
"recommendations": ["editorconfig.editorconfig"]
}
为什么要做这个操作? 假如哪天项目新来一个协同开发的同学,当他拉取取项目,用 vscode
开项目的时候,编辑器就会自动提醒他安装这个插件,并将相关的配置做设定。下面的 eslint
prettier 插件也是类似

新建 .editorconfig

在根目录新建 .editorconfig 文件:

# https://editorconfig.org
root = true # 设置为true表示根目录,控制配置文件 .editorconfig 是否生效的字段
[*] # 匹配全部文件,匹配除了 `/` 路径分隔符之外的任意字符串
charset = utf-8 # 设置字符编码,取值为 latin1,utf-8,utf-8-bom,
utf-16be 和 utf-16le,当然 utf-8-bom 不推荐使用
end_of_line = lf # 设置使用的换行符,取值为 lf,cr 或者 crlf
indent_size = 2 # 设置缩进的大小,即缩进的列数,当 indexstyle 取值 tab
时,indentsize 会使用 tab_width 的值
indent_style = space # 缩进风格,可选space|tab
insert_final_newline = true # 设为true表示使文件以一个空白行结尾
trim_trailing_whitespace = true # 删除一行中的前后空格
[*.md] # 匹配全部 .md 文件
trim_trailing_whitespace = false
上面的配置可以规范本项目中文件的缩进风格,和缩进空格数等,会覆盖 vscode 的配置,来达到不同 编辑器中代码默认行为一致的作用。
VS Code EditorConfig 目前支持下列属性:
  • indent_style
  • indent_size
  • tab_width
  • end_of_line (on save)
  • insert_final_newline (on save)
  • trim_trailing_whitespace (on save)

prettier

每个人写代码的风格习惯不一样,比如代码换行,结尾是否带分号,单双引号,缩进等,而且不能只靠口头规范来约束,项目紧急的时候可能会不太注意代码格式,这时候需要有工具来帮我们自动格式化代 码,而prettier就是帮我们做这件事的。

安装 VS Code 插件和 prettier

安装 prettier
pnpm i prettier -D

 新建 .prettierrc.js

在根目录下新建 .prettierrc.js .prettierignore 文件:

// .prettierrc.js
module.exports = {
tabWidth: 2, // 一个tab代表几个空格数,默认就是2
useTabs: false, // 是否启用tab取代空格符缩进,.editorconfig设置空格缩进,所以设置为
false
printWidth: 100, // 一行的字符数,如果超过会进行换行
semi: false, // 行尾是否使用分号,默认为true
singleQuote: true, // 字符串是否使用单引号
trailingComma: 'all', // 对象或数组末尾是否添加逗号 none| es5| all
jsxSingleQuote: true, // 在jsx里是否使用单引号,你看着办
bracketSpacing: true, // 对象大括号直接是否有空格,默认为true,效果:{ foo: bar }
arrowParens: 'avoid' // 箭头函数如果只有一个参数则省略括号
}
// .prettierignore
node_modules
dist
env
.gitignore
pnpm-lock.yaml
README.md
src/assets/*
.vscode
public
.github
.husky

配置 .vscode/settings.json

配置前两步后,虽然已经配置 prettier 格式化规则,但还需要让 vscode 来支持保存后触发格式化, 在项目根目录新建 .vscode 文件夹,内部新建 settings.json 文件配置文件,代码如下:
{
"search.exclude": {
"/node_modules": true,
"dist": true,
"pnpm-lock.yaml": true
},
"files.autoSave": "onFocusChange",
"editor.formatOnSave": true,
"editor.formatOnType": true,
"editor.defaultFormatter": "esbenp.prettier-vscode",
"[javascript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[javascriptreact]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[typescript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[typescriptreact]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[json]": {
"editor.defaultFormatter": "vscode.json-language-features"
},
"[html]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[markdown]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"javascript.validate.enable": false,
}
  • 若配置 "prettier.requireConfig": true 则要求根目录下有 Prettier 的配置文件,它会覆
    Prettier 扩展 中的默认配置, 否则保存时不会自动格式化
  • 若配置 "prettier.requireConfig": false 则不要求根目录下有配置文件,若有的话也会被感
    知到并以配置文件的内容为准。
这些代码的作用是:在编辑后保存 [xxx] 文件时,自动应用 Prettier 进行格式化,以确保代码
风格的一致性。
先配置了忽略哪些文件不进行格式化,又添加了保存代码后触发格式化代码配置,以及各类型文件格式化使用的格式。这一步配置完成后,修改项目代码,把格式打乱,点击保存时就会看到编辑器自动把代 码格式规范化了。
若设置需要配置文件,则必须要求根目录下有配置文件 .prettierrc.js .editorconfig 中的一个
或者两个同时存在,否则代码保存不会进行格式化。
可能你会对上面 .editorconfig 文件作为 Prettier 的配置文件感到疑惑, vscode Prettier
件中有关配置文件有这样的一段描述,

  • 可以看出Prettier插件获取配置文件有一个优先级: .prettierrc > .editorconfig > vscode认配置
  • 上面的前两者并不是说 .prettierrc .editorconfig 同时存在时,后者的配置内容就被忽
    略,实际的表现:
  • .prettierrc.js .editorconfig 同时存在时,二者内容会进行合并,若配置项有冲突,这
    .prettierrc 的优先级更高。

脚本命令检查和修复格式

package.json "scripts" 中加入以下脚本命令:

"lint:prettier": "prettier --write --loglevel warn \"src/**/*.
{js,ts,json,tsx,css,less,scss,stylus,html,md}\""

 这段代码是一个脚本命令,用于运行 Prettier 工具来格式化指定目录下的文件。具体解释如下:

  • "--write" : 表示将格式化后的结果直接写回原文件中,而不是输出到控制台。
  • "--loglevel warn" : 表示只输出警告级别的日志信息。
  •  "src/**/*.{js,ts,json,tsx,css,less,scss,stylus,html,md}" : 是要格式化的文件的路径,
    这里指定了在 src 目录下,所有扩展名为
    .js .ts .json .tsx .css .less .scss .stylus .html .md 的文件。
这个脚本命令的作用是:运行 Prettier 工具来格式化指定目录下的文件,并将格式化后的结果直接
写回原文件中。同时,只输出警告级别的日志信息。
现在可以测试一下,将 .prettierrc.js 中的 tabWidth 值修改为 4 (缩进为 4 个空格),然后运行
pnpm run lint:prettier ,随便打开一个 src 下的文件,你就会发现缩进从 2 变成 4 了。

安装 stylelint 插件和依赖

pnpm i stylelint stylelint-config-css-modules stylelint-config-prettier
stylelint-config-standard stylelint-order -D

新建 .stylelintrc.js .stylelintignore

// @see: https://stylelint.io
module.exports = {
extends: [
'stylelint-config-standard',
'stylelint-config-prettier'
// "stylelint-config-css-modules",
// "stylelint-config-recess-order" // 配置stylelint css属性书写顺序插件,
],
plugins: ['stylelint-order'],
rules: {
/**
* indentation: null, // 指定缩进空格
"no-descending-specificity": null, // 禁止在具有较高优先级的选择器后出现被其覆盖的
较低优先级的选择器
"function-url-quotes": "always", // 要求或禁止 URL 的引号 "always(必须加上引
号)"|"never(没有引号)"
"string-quotes": "double", // 指定字符串使用单引号或双引号
"unit-case": null, // 指定单位的大小写 "lower(全小写)"|"upper(全大写)"
"color-hex-case": "lower", // 指定 16 进制颜色的大小写 "lower(全小写)"|"upper(全
大写)"
"color-hex-length": "long", // 指定 16 进制颜色的简写或扩写 "short(16进制简
写)"|"long(16进制扩写)"
"rule-empty-line-before": "never", // 要求或禁止在规则之前的空行 "always(规则之前
必须始终有一个空行)"|"never(规则前绝不能有空行)"|"always-multi-line(多行规则之前必须始终有
一个空行)"|"never-multi-line(多行规则之前绝不能有空行。)"
"font-family-no-missing-generic-family-keyword": null, // 禁止在字体族名称列表
中缺少通用字体族关键字
"block-opening-brace-space-before": "always", // 要求在块的开大括号之前必须有一
个空格或不能有空白符 "always(大括号前必须始终有一个空格)"|"never(左大括号之前绝不能有空
格)"|"always-single-line(在单行块中的左大括号之前必须始终有一个空格)"|"never-singleline(在单行块中的左大括号之前绝不能有空格)"|"always-multi-line(在多行块中,左大括号之前必须
始终有一个空格)"|"never-multi-line(多行块中的左大括号之前绝不能有空格)"
"property-no-unknown": null, // 禁止未知的属性(true 为不允许)
"no-empty-source": null, // 禁止空源码
"declaration-block-trailing-semicolon": null, // 要求或不允许在声明块中使用尾随
分号 string:"always(必须始终有一个尾随分号)"|"never(不得有尾随分号)"
"selector-class-pattern": null, // 强制选择器类名的格式
"value-no-vendor-prefix": null, // 关闭 vendor-prefix(为了解决多行省略 -
webkit-box)
"at-rule-no-unknown": null,
"selector-pseudo-class-no-unknown": [
true,
{
ignorePseudoClasses: ["global", "v-deep", "deep"]
}
]
}
*/
'selector-class-pattern': [
// 命名规范 -
'^([a-z][a-z0-9]*)(-[a-z0-9]+)*$',
{
message: 'Expected class selector to be kebab-case'
}
],
'string-quotes': 'double', // 单引号
'at-rule-empty-line-before': null,
'at-rule-no-unknown': null,
'at-rule-name-case': 'lower', // 指定@规则名的大小写
'length-zero-no-unit': true, // 禁止零长度的单位(可自动修复)
'shorthand-property-no-redundant-values': true, // 简写属性
'number-leading-zero': 'always', // 小数不带0
'declaration-block-no-duplicate-properties': true, // 禁止声明快重复属性
'no-descending-specificity': true, // 禁止在具有较高优先级的选择器后出现被其覆盖的较
低优先级的选择器。
'selector-max-id': null, // 限制一个选择器中 ID 选择器的数量
'max-nesting-depth': 10,
'declaration-block-single-line-max-declarations': 1,
'block-opening-brace-space-before': 'always',
// 'selector-max-type': [0, { ignore: ['child', 'descendant', 'compounded']
}],
indentation: [
2,
{
// 指定缩进 warning 提醒
severity: 'warning'
}
],
'order/order': ['custom-properties', 'dollar-variables', 'declarations',
'rules', 'at-rules'],
'order/properties-order': [
// 规则顺序
'position',
'top',
'right',
'bottom',
'left',
'z-index',
'display',
'float',
'width',
'height',
'max-width',
'max-height',
'min-width',
'min-height',
'padding',
'padding-top',
'padding-right',
'padding-bottom',
'padding-left',
'margin',
'margin-top',
'margin-right',
'margin-bottom',
'margin-left',
'margin-collapse',
'margin-top-collapse',
'margin-right-collapse',
'margin-bottom-collapse',
'margin-left-collapse',
'overflow',
'overflow-x',
'overflow-y',
'clip',
'clear',
'font',
'font-family',
'font-size',
'font-smoothing',
'osx-font-smoothing',
'font-style',
'font-weight',
'line-height',
'letter-spacing',
'word-spacing',
'color',
'text-align',
'text-decoration',
'text-indent',
'text-overflow',
'text-rendering',
'text-size-adjust',
'text-shadow',
'text-transform',
'word-break',
'word-wrap',
'white-space',
'vertical-align',
'list-style',
'list-style-type',
'list-style-position',
'list-style-image',
'pointer-events',
'cursor',
'background',
'background-color',
'border',
'border-radius',
'content',
'outline',
'outline-offset',
'opacity',
'filter',
'visibility',
'size',
'transform'
]
}
}

 .stylelintignore 文件:

*.js
*.tsx
*.ts
*.json
*.png
*.eot
*.ttf
*.woff
src/styles/antd-overrides.less
node_modules
dist
env
.gitignore
pnpm-lock.yaml
README.md
src/assets/*
.vscode
public
.github
.husky

配置 .vscode/settings.json

最后记得在 .vscode/settings.json 中加入:
{
// ...
"editor.codeActionsOnSave": {
"source.fixAll.stylelint": true
},
"stylelint.validate": [
"css",
"less",
"sass",
"stylus",
"postcss"
]
}

安装 eslint 插件和包

安装 eslint
pnpm i eslint eslint - config - airbnb eslint - config - standard eslint - friendly
formatter eslint - plugin - import eslint - plugin - jsx - a11y eslint - plugin - node eslint
plugin - promise eslint - plugin - react - hooks eslint - plugin - react @ typescript
eslint / eslint - plugin @ typescript - eslint / parser eslint - plugin - prettier eslint
config - prettier - D

新建 .eslintrc.js

在根目录新建一个 .eslintrc.js 文件:

module.exports = {
env: {
browser: true,
es2021: true,
node: true
},
extends: [
'airbnb-base',
'eslint:recommended',
'plugin:import/recommended',
'plugin:@typescript-eslint/recommended',
'prettier',
'plugin:prettier/recommended'
],
parser: '@typescript-eslint/parser',
parserOptions: {
ecmaFeatures: {
jsx: true
},
ecmaVersion: 'latest',
sourceType: 'module'
},
plugins: ['react', '@typescript-eslint'],
rules: {
// eslint (http://eslint.cn/docs/rules)
'react/jsx-filename-extension': ['error', { extensions: ['.js', '.jsx',
'.ts', '.tsx'] }],
'class-methods-use-this': 'off',
'no-param-reassign': 'off',
'no-unused-expressions': 'off',
'no-plusplus': 0,
'no-restricted-syntax': 0,
'consistent-return': 0,
'@typescript-eslint/ban-types': 'off',
// "import/no-extraneous-dependencies": "off",
'@typescript-eslint/no-non-null-assertion': 'off',
'import/no-unresolved': 'off',
'import/prefer-default-export': 'off', // 关闭默认使用 export default 方式导出
'import/no-extraneous-dependencies': ['error', { devDependencies: true }],
'@typescript-eslint/no-use-before-define': 0,
'no-use-before-define': 0,
'@typescript-eslint/no-var-requires': 0,
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/no-namespace': 'off', // 禁止使用自定义 TypeScript 模块和命名
空间。
'no-shadow': 'off',
// "@typescript-eslint/no-var-requires": "off"
'import/extensions': [
'error',
'ignorePackages',
{
'': 'never',
js: 'never',
jsx: 'never',
ts: 'never',
tsx: 'never'
}
]
// "no-var": "error", // 要求使用 let 或 const 而不是 var
// "no-multiple-empty-lines": ["error", { max: 1 }], // 不允许多个空行
// "no-use-before-define": "off", // 禁止在 函数/类/变量 定义之前使用它们
// "prefer-const": "off", // 此规则旨在标记使用 let 关键字声明但在初始分配后从未重新分
配的变量,要求使用 const
// "no-irregular-whitespace": "off", // 禁止不规则的空白
// // typeScript (https://typescript-eslint.io/rules)
// "@typescript-eslint/no-unused-vars": "error", // 禁止定义未使用的变量
// "@typescript-eslint/no-inferrable-types": "off", // 可以轻松推断的显式类型可能
会增加不必要的冗长
// "@typescript-eslint/no-namespace": "off", // 禁止使用自定义 TypeScript 模块和
命名空间。
// "@typescript-eslint/no-explicit-any": "off", // 禁止使用 any 类型
// "@typescript-eslint/ban-ts-ignore": "off", // 禁止使用 @ts-ignore
// "@typescript-eslint/ban-types": "off", // 禁止使用特定类型
// "@typescript-eslint/explicit-function-return-type": "off", // 不允许对初始化
为数字、字符串或布尔值的变量或参数进行显式类型声明
// "@typescript-eslint/no-var-requires": "off", // 不允许在 import 语句中使用
require 语句
// "@typescript-eslint/no-empty-function": "off", // 禁止空函数
// "@typescript-eslint/no-use-before-define": "off", // 禁止在变量定义之前使用它
们
// "@typescript-eslint/ban-ts-comment": "off", // 禁止 @ts-<directive> 使用注释
或要求在指令后进行描述
// "@typescript-eslint/no-non-null-assertion": "off", // 不允许使用后缀运算符的非
空断言(!)
// "@typescript-eslint/explicit-module-boundary-types": "off", // 要求导出函数
和类的公共类方法的显式返回和参数类型
// // react (https://github.com/jsx-eslint/eslint-plugin-react)
// "react-hooks/rules-of-hooks": "error",
// "react-hooks/exhaustive-deps": "off"
},
settings: {
'import/extensions': ['.js', '.jsx', '.ts', '.tsx'],
'import/parsers': {
'@typescript-eslint/parser': ['.ts', '.tsx']
},
'import/resolver': {
node: {
paths: ['src'],
extensions: ['.js', '.jsx', '.ts', '.tsx'],
moduleDirectory: ['node_modules', 'src/']
}
},
react: {
version: 'detect'
}
}
}
有时候,我们需要在代码中忽略 ESLint 的某些规则检查,此时我们可以通过加入代码注释的方式解
决:可以指定整个文件、某一行、某一区块开启 / 关闭 某些或全部规则检查;
/* eslint-disable */ --禁用全部规则 放在文件顶部则整个文件范围都不检查
/* eslint-disable no-alert, no-console */ --禁用某些规则
// eslint-disable-line --当前行上禁用规则
// eslint-disable-next-line --下一行上禁用规则

新建 .eslintignore

在根目录新建一个 .eslintignore 文件:

node_modules
dist
env
.gitignore
pnpm-lock.yaml
README.md
src/assets/*
.vscode
public
.github
.husky
.eslintignore 用于指定 ESLint 工具在检查代码时要忽略的文件和目录。具体来说, .eslintignore
文件中列出的文件和目录将被 ESLint 忽略,不会对其进行代码检查和报告。这个文件中的每一行都是一 个文件或目录的模式,支持使用通配符( * )和正则表达式来匹配多个文件或目录。
通常情况下, .eslintignore 文件中会列出一些不需要检查的文件或目录,比如第三方库、测试 代码、构建输出等,以减少 ESLint 的检查范围,提高代码检查的效率。

添加eslint语法检测脚本

前面的 eslint 报错和警告都是我们用眼睛看到的,有时候需要通过脚本执行能检测出来,在
package.json scripts 中新增:
// --fix:此项指示 ESLint 尝试修复尽可能多的问题。这些修复是对实际文件本身进行的,只有剩余的未
修复的问题才会被输出。
"lint:eslint": "eslint --fix --ext .js,.ts,.tsx ./src",
现在执行 pnpm run lint:eslint ,控制台将会爆出一系列 warning.
除此之外再解决一个问题就是 eslint 报的警告:
React version not specified in eslint-plugin-react settings
需要告诉 eslint 使用的 react 版本 ,在 .eslintrc.js rules 平级添加 settings 配置,让
eslint 自己检测 react 版本,对应 issuse
settings: {
"react": {
"version": "detect"
}
}
再执行 pnpm run lint:eslint 就不会报这个未设置 react 版本的警告了。

eslint prettier 冲突

安装两个依赖:
pnpm i eslint-config-prettier eslint-plugin-prettier -D

 .eslintrc.js extends 中加入:

module.exports = {
// ...
extends: [
// ...
'plugin:prettier/recommended', // <==== 增加一行
],
// ...
}
最后再配置一下 .vscode/settings.json
{
// ...
"eslint.enable": true,
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true,
},
}

husky + lint-statged

使用lint-staged优化eslint检测速度

在上面配置的 eslint 会检测 src 文件下所有的 .ts, .tsx 文件,虽然功能可以实现,但是当项目文件
多的时候,检测的文件会很多,需要的时间也会越来越长,但其实只需要检测提交到暂存区,就是 git add 添加的文件,不在暂存区的文件不用再次检测,而 lint-staged 就是来帮我们做这件事情的。
package.json 添加 lint-staged 配置
        
"lint-staged": {
"src/**/*.{ts,tsx}": [
"pnpm run lint:eslint",
"pnpm run lint:prettier"
]
},
因为要检测 git 暂存区代码,所以如果你的项目还没有使用 git 来做版本控制,需要执行 git init
初始化一下 git
git init

初始化 git 完成后就可以进行测试了,先提交一下没有语法问题的 App.tsx

git add src/App.tsx  

src/App.tsx 提交到暂存区后,执行 npx lint-staged ,会顺利通过检测。

假如我们现在把 package.json 中的 "lint:eslint" 改一下,加一个 --max-warnings=0 ,表示允
许最多 0 个警告,就是只要出现警告就会报错:
"lint:eslint" : "eslint --fix --ext .js,.ts,.tsx ./src --max-warnings=0" ,

 然后在 App.tsx 中加入一个未使用的变量:

// ...
const a = 1
// ...
然后执行:
git add src / App . tsx
npx lint - staged

 你就会发现控制台出现了 warning,这就是 lint-staged 的作用。

使用tsc检测类型和报错

需要注意的是,执行 tsc 命令可能会生成一个编译后的产物文件,需想要避免这个问题,需要在
tsconfig.json 中加入 "noEmit": true
{
"compilerOptions": {
"target": "es2016", // 编译输出的ECMAScript目标版本为ES2016
"esModuleInterop": true, // 允许默认导入与CommonJS模块操作
"module": "commonjs", // 使用CommonJS模块系统
"forceConsistentCasingInFileNames": true, // 强制文件名大小写一致
"strict": true, // 启用所有严格的类型检查选项
"skipLibCheck": true, // 跳过所有声明文件(.d.ts)的类型检查
"resolveJsonModule": true, // 允许从.json文件中导入数据
"allowJs": false, // 不允许在项目中包含JavaScript文件
"noEmit": true, // 不生成输出文件(例如.js文件)
"noImplicitAny": false, // 在表达式和声明上有隐含的any类型时,不发出警告
"experimentalDecorators": true, // 启用实验性的装饰器
"baseUrl": ".", // 用于解析非相对模块名称的基目录
"paths": {
"@/*": ["./src/*"] // 将路径别名“@”映射到“./src/”
},
"typeRoots": ["./typings/*.d.ts", "node_modules/@types"], // 指定类型声明文件的
查找路径
"jsx": "react-jsx" // react18这里改成react-jsx,就不需要在tsx文件中手动引入React了
},
"include": ["./src/*", "./src/**/*.ts", "./src/**/*.tsx", "./typings/*.d.ts"],
// 指定需要包含的文件和目录
"exclude": ["node_modules", "dist"] // 指定需要排除的文件和目录
}
在项目中使用了 ts ,但一些类型问题,现在配置的 eslint 是检测不出来的,需要使用 ts 提供的 tsc
工具进行检测。
index.tsx 定义了函数 hello ,参数 name string 类型,当调用传 number 类型参数时,页面有了明显的ts 报错,但此时提交 index.tsx 文件到暂存区后执行 npx lint-staged
发现没有检测到报错,所以需要配置下 tsc 来检测类型,在 package.json 添加脚本命令
"pre-check" : "tsc && npx lint-staged"
执行 pnpm run pre-check ,发现已经可以检测出类型报错了。

配置 husky

为了避免把不规范的代码提交到远程仓库,一般会在 git 提交代码时对代码语法进行检测,只有检测通过 时才能被提交,git 提供了一系列的 githooks ,而我们需要其中的 pre-commit 钩子,它会在 git commit 把代码提交到本地仓库之前执行,可以在这个阶段检测代码,如果检测不通过就退出命令行进程 停止 commit

代码提交前husky检测语法

husky 就是可以监听 githooks 的工具,可以借助它来完成这件事情。

安装husky

npx husky install

配置huskypre-commit钩子

生成 .husky 配置文件夹(如果项目中没有初始化 git ,需要先执行 git init

npx husky install 

会在项目根目录生成 .husky 文件夹,生成文件成功后,需要让 husky 支持监听 pre-commit 钩子,监听到后执行上面定义的 pnpm run pre-check 语法检测。
npx husky add .husky/pre-commit 'pnpm run pre-check'

 会在 .husky 目录下生成 pre-commit 文件,里面可以看到我们设置的 npm run pre-check 命令。

然后提交代码进行测试

git add .
git commit -m "feat: add code validate"
会发现监听 pre-commit 钩子执行了 pnpm run pre-check , 使用 eslint 检测了 git 暂存区的两个文
件,并且发现了 index.tsx 的警告,退出了命令行,没有执行 git commit 把暂存区代码提交到本地仓库

Commit 信息的 Linter - Commitlint

Commitlint 是个 npm 包,它使用 commit conventions 规范来检查 commit 的信息是否符合我们约
定好的提交规范。
通过配置 commitlint.config.js Commitlint 可以知道要使用哪些规则规范 commit 信息,并输
出相对的提示供使用者作为修改的依据。
使用 Commitlint 规范项目的 commit ,可以让所有人的代码提交保持一致的格式,这样做会有下列好处:
  • 容易检索:利用定义的关键字可以轻松地找到想要找的 commit
  • 自动输出 Changelog :固定的讯息格式可以藉由 changelog 工具 的帮助输出 `Changelog

安装Commitlint

首先安装 Commitlint

pnpm i @commitlint/cli -D 

使用Commitlint

安装完成后,由于 Commitlint 的配置档是必要的,因此要建立配置档 commitlint.config.js

module.exports = {
rules: {
'header-min-length': [2, 'always', 10],
},
};
配置属性 rules 可以设定规则,规则列表请参考 Commitlint 的官方页面 。范例中设定讯息标头的最小长度要大于10
接着执行 commitlint
> echo 'foo' | npx commitlint
⧗ input: foo
✖ header must not be shorter than 10 characters, current length is 3 [headermin-length]
✖ found 1 problems, 0 warnings
ⓘ Get help: https://github.com/conventional-changelog/commitlint/#what-iscommitlint
message 信息为 foo 时,由于长度只有 3 ,因此 Commitlint 会视为违规而输出错误讯息 。

配置规则包        

为了节省配置规则的时间, Commitlint 可以使用预先配置的规则包来设定多项规则。使用前须要先安装:
pnpm i @commitlint/config-conventional -D
这里使用 @commitlint/config-conventional Commitlint 提供的规则包。安装完成后,要在配
置中设定使用规则包:
module.exports = {
extends: ['@commitlint/config-conventional'],
// ...
};
这样一来 Commitlint 就会将 @commitlint/config-conventional 所配置的规则 都纳入并对讯息做相应
的检查。

使用 Husky Commitlint 注册 Git Hooks

到目前为止,我们都必须自己去手动调用 Commitlint 才能作用,使用起来的步骤较原本多,也不直
观,容易被忽略。
        
接下来我们使用 Husky Commitlint 融入 Git flow 中,让其更加的易用。使用 husky add 将指
令加入 Git hooks
npx husky add .husky/commit-msg 'npx --no-install commitlint --edit "$1"'

 修改完后,要重新注册 Git hooks

npx husky install 

这会触发相关的初始化工作。完成设定后,当你输入指令 git commit ,在完成编辑讯息后会启动
Commitlint 检查讯息。

Commitizen

为了避免写出不符规范的 commit message 而提交失败, Commitizen 使用问答的方式,让使用者在完成问答时就可以边写出符合规范的信息,以减少来回的次数。
Commitizen 是个指令式的工具,使用 Commitizen commit 代码时会启动设定的 adapter ,使用
adapter 提供的问题一一询问开发者,每个问题都会确认一部分的 commit message ,到最后将所有的回答组合起来,变成一个完整并符合规范的 commit message

cz-git

 指定提交文字规范,一款工程性更强、高度自定义、标准输出格式的 commitizen 适配器:

pnpm i commitizen cz-git -D -g

配置 package.json

"config": {
"commitizen": {
"path": "node_modules/cz-git"
}
}

 配置 commitlint.config.js 文件

// @see: https://cz-git.qbenben.com/zh/guide
/** @type {import('cz-git').UserConfig} */
module.exports = {
ignores: [commit => commit.includes('init')],
extends: ['@commitlint/config-conventional'],
rules: {
// @see: https://commitlint.js.org/#/reference-rules
'body-leading-blank': [2, 'always'],
'footer-leading-blank': [1, 'always'],
'header-max-length': [2, 'always', 108],
'subject-empty': [2, 'never'],
'type-empty': [2, 'never'],
'subject-case': [0],
'type-enum': [
2,
'always',
[
'feat',
'fix',
'docs',
'style',
'refactor',
'perf',
'test',
'build',
'ci',
'chore',
'revert',
'wip',
'workflow',
'types',
'release'
]
]
},
prompt: {
messages: {
type: "Select the type of change that you're committing:",
scope: 'Denote the SCOPE of this change (optional):',
customScope: 'Denote the SCOPE of this change:',
subject: 'Write a SHORT, IMPERATIVE tense description of the change:\n',
body: 'Provide a LONGER description of the change (optional). Use "|" to
break new line:\n',
breaking: 'List any BREAKING CHANGES (optional). Use "|" to break new
line:\n',
footerPrefixsSelect: 'Select the ISSUES type of changeList by this change
(optional):',
customFooterPrefixs: 'Input ISSUES prefix:',
footer: 'List any ISSUES by this change. E.g.: #31, #34:\n',
confirmCommit: 'Are you sure you want to proceed with the commit above?'
// 中文版
// type: "选择你要提交的类型 :",
// scope: "选择一个提交范围(可选):",
// customScope: "请输入自定义的提交范围 :",
// subject: "填写简短精炼的变更描述 :\n",
// body: '填写更加详细的变更描述(可选)。使用 "|" 换行 :\n',
// breaking: '列举非兼容性重大的变更(可选)。使用 "|" 换行 :\n',
// footerPrefixsSelect: "选择关联issue前缀(可选):",
// customFooterPrefixs: "输入自定义issue前缀 :",
// footer: "列举关联issue (可选) 例如: #31, #I3244 :\n",
// confirmCommit: "是否提交或修改commit ?"
},
types: [
{
value: 'feat',
name: 'feat: 🚀 A new feature',
emoji: '🚀'
},
{
value: 'fix',
name: 'fix: 🧩 A bug fix',
emoji: '🧩'
},
{
value: 'docs',
name: 'docs: 📚 Documentation only changes',
emoji: '📚'
},
{
value: 'style',
name: 'style: 🎨 Changes that do not affect the meaning of the code',
emoji: '🎨'
},
{
value: 'refactor',
name: 'refactor: ♻️ A code change that neither fixes a bug nor adds a
feature',
emoji: '♻️'
},
{
value: 'perf',
name: 'perf: ⚡️ A code change that improves performance',
emoji: '⚡️'
},
{
value: 'test',
name: 'test: ✅ Adding missing tests or correcting existing tests',
emoji: '✅'
},
{
value: 'build',
name: 'build: 📦 Changes that affect the build system or external
dependencies',
emoji: '📦'
},
{
value: 'ci',
name: 'ci: 🎡 Changes to our CI configuration files and scripts',
emoji: '🎡'
},
{
value: 'chore',
name: "chore: 🔨 Other changes that don't modify src or test files",
emoji: '🔨'
},
{
value: 'revert',
name: 'revert: ⏪️ Reverts a previous commit',
emoji: '⏪️'
}
// 中文版
// { value: "特性", name: "特性: 🚀 新增功能", emoji: "🚀" },
// { value: "修复", name: "修复: 🧩 修复缺陷", emoji: "🧩" },
// { value: "文档", name: "文档: 📚 文档变更", emoji: "📚" },
// { value: "格式", name: "格式: 🎨 代码格式(不影响功能,例如空格、分号等格式修
正)", emoji: "🎨" },
// { value: "重构", name: "重构: ♻️ 代码重构(不包括 bug 修复、功能新增)",
emoji: "♻️" },
// { value: "性能", name: "性能: ⚡️ 性能优化", emoji: "⚡️" },
// { value: "测试", name: "测试: ✅ 添加疏漏测试或已有测试改动", emoji: "✅"
},
// { value: "构建", name: "构建: 📦 构建流程、外部依赖变更(如升级 npm 包、修改
webpack 配置等)", emoji: "📦" },
// { value: "集成", name: "集成: 🎡 修改 CI 配置、脚本", emoji: "🎡" },
// { value: "回退", name: "回退: ⏪️ 回滚 commit", emoji: "⏪️" },
// { value: "其他", name: "其他: 🔨 对构建过程或辅助工具和库的更改(不影响源文
件、测试用例)", emoji: "🔨" }
],
useEmoji: true,
themeColorCode: '',
scopes: [],
allowCustomScopes: true,
allowEmptyScopes: true,
customScopesAlign: 'bottom',
customScopesAlias: 'custom',
emptyScopesAlias: 'empty',
upperCaseSubject: false,
allowBreakingChanges: ['feat', 'fix'],
breaklineNumber: 100,
breaklineChar: '|',
skipQuestions: [],
issuePrefixs: [{ value: 'closed', name: 'closed: ISSUES has been processed'
}],
customIssuePrefixsAlign: 'top',
emptyIssuePrefixsAlias: 'skip',
customIssuePrefixsAlias: 'custom',
allowCustomIssuePrefixs: true,
allowEmptyIssuePrefixs: true,
confirmColorize: true,
maxHeaderLength: Infinity,
maxSubjectLength: Infinity,
minSubjectLength: 0,
scopeOverrides: undefined,
defaultBody: '',
defaultIssues: '',
defaultScope: '',
defaultSubject: ''
}
}
然后测试:
git add .
git-cz

一键提交

我们还可以通过一个script来集成之前所有的这些步骤:

"scripts": {
// ...
"commit": "git pull && git add -A && git-cz && git push",
}
现在,我们只需要执行 pnpm run commit 即可完成代码的质量检测、 style format 、代码提交规范
等一系列流程了。
  • 19
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值