快速在你的项目里加入 husky、eslint、stylelint、lint-staged、commitlint

2,273 阅读7分钟

前言

既然点进来这篇文章相信大家都知道代码风格校验的重要性,这里不再过多赘述,按照本文的步骤,你可以在你的现有项目中快速地加入 husky、eslint、stylelint、lint-staged、commitlint一系列代码校验工具,为编写高质量代码打下坚实的基础。

Eslint

安装

pnpm install eslint --save-dev

添加或修改.eslintrc.js文件

module.exports = {
  root: true, // 声明这是根配置文件,不再向上查找
  env: { browser: true, es2023: true },
  // 使用的规则集,可以是一个字符串(预设)或自定义规则的对象
  extends: 'eslint:recommended',
  // plugins 可以提供额外的规则配置,如vue,这些规则需要在 rules 中进行配置。
  plugins: ['vue'],
  // 自定义规则
  rules: {
    'no-console': 'off', // 允许使用console
    'indent': ['error', 2], // 两个空格的缩进
    // 更多规则...
  },
  // 默认使用espree,可以选择其他解析器,例如使用ts
  parser: "@typescript-eslint/parser",
  parserOptions: {
    "parser": "@typescript-eslint/parser",
    "sourceType": "module",
    "project": "./tsconfig.json" // 指定tsconfig.json文件路径
  },
  //定义全局变量
  globals: {
    "wx": "readonly"
  },
  // 配置哪些文件或目录应该被 ESLint 忽略
  ignorePatterns: ["node_modules/", "build/", "dist/"]
}

以上配置可以根据自己需要来自由修改,规则集推荐使用 eslint-config-mature ,该规范包集eslint、stylelint、prettier规范于一身,集各大厂百家之长,免去安装各种包的繁杂操作,用起来很方便。

pnpm install eslint-config-mature eslint-plugin-import --save-dev

eslint 和 prettier 两者可以配合使用,即 使用 prettier 做格式化, eslint 做代码校验。

pnpm install prettier eslint-plugin-prettier --save-dev

更新.eslintrc.js文件

module.exports = {
  // 使用的规则集,可以是一个字符串(预设)或自定义规则的对象,优先级由低到高
  extends: [
    'plugin:react/recommended', // 优先你的react或者Vue规则,优先级最低
    'eslint-config-mature/prettier', // 主要用于格式化模板,优先级应更低
    'eslint-config-mature',
    'eslint-config-mature/ts', // 如果使用ts
  ],
}

Stylelint

安装相关包,如果使用 scss 的话需要额外安装 stylelint-scss postcss-scss

pnpm install stylelint stylelint-scss postcss-scss stylelint-config-recess-order --save-dev

.stylelintrc.js 配置

module.exports = {
  extends: [
    'stylelint-config-recess-order', // 属性排序
    'eslint-config-mature/stylelint/style', // 使用 eslint-config-mature 包的规则
    'eslint-config-mature/stylelint/style-scss',
  ],
  ignoreFiles: [
    '**/*.js',
    '**/*.cjs',
    '**/*.jsx',
    '**/*.tsx',
    '**/*.ts',
    "node_modules/",
    "dist/",
    "public/",
    "docs/",
  ],
  overrides: [
    {
      files: ["**/*.scss"],
      customSyntax: "postcss-scss", // 处理.scss文件时使用postcss-scss语法解析器
    },
  ],
  rules: {
    // 自定义覆盖规则
  },
};


手动lint

package.json 的 scripts 里添加

    "lint": "yarn lint:eslint && yarn lint:stylelint",
    "lint:eslint": "eslint -c .eslintrc.cjs --ext .ts,.tsx,.js src --fix --report-unused-disable-directives --max-warnings 0",
    "lint:stylelint": "stylelint src/**/*.{html,css,scss} --fix --max-warnings 0",

运行 npm run lint 即可进行代码检查,如果你不想自动修复问题,可以把 --fix 去掉

强烈推荐安装 eslint 和 stylelint 的 vscode 插件,可以实时提示并支持保存自动修复等功能。

husky

每次手动去运行命令检查太麻烦了,而且也很考验小伙伴的自觉性。

husky 是一个 Git 钩子(Git hooks)工具,它可以让你在 Git 事件发生时执行脚本,进行代码格式化、测试等操作。

常见的钩子

  • pre-commit:在执行 Git commit 命令之前触发,用于在提交代码前进行代码检查、格式化、测试等操作。
  • commit-msg:在提交消息(commit message)被创建后,但提交操作尚未完成之前触发,用于校验提交消息的格式和内容。
  • pre-push:在执行 Git push 命令之前触发,用于在推送代码前进行额外检查、测试等操作。

具体的使用步骤如下:

  1. 安装 husky
pnpm add  husky --save-dev
  1. 启用git 钩子 输入以下命令
pnpm pkg set scripts.prepare="husky install"

安装成功后会在 package.json 文件中 script 中生成命令

注意!如未自动生成需手动添加,将以下内容粘贴到 package.json 文件中

// package.json
{
  "scripts": {
    "prepare": "husky install"
  }
}
  1. 执行如下代码,创建.husky目录,
pnpm run prepare

执行成功后,项目中会生成一个 .husky 目录

  1. 添加命令到pre-commit 钩子

给 pre-commit 钩子添加 npx lint-staged 命令

npx husky add .husky/pre-commit "npx lint-staged"

lint-staged下面有详细介绍

lint-staged

lint-staged 可以让你在 Git 暂存(staged)区域中的文件上运行脚本,通常用于在提交前对代码进行格式化、静态检查等操作。

可以在项目中使用 lint-staged 配合 husky 钩子来执行针对暂存文件的脚本。

安装

pnpm add lint-staged --save-dev

在 package.json 文件中添加以下配置:

  "lint-staged": {
    "src/**/*.{js,jsx,ts,tsx}": [
      "eslint --max-warnings 0"
    ],
    "src/**/*.{vue,less,postcss,css,scss}": [
      "stylelint --max-warnings 0"
    ]
  }

src/**/*.{js,ts,vue,tsx}为校验暂存区、指定目录下的文件类型,可以根据自己需要配置。

现在,在代码提交时就会自动执行 npx lint-staged 命令校验代码。

注意: 没有 --fix 参数,某些场景下自动修复会导致代码莫名其妙的bug,如果自动修复了你可能都不知道就合了

Commitizen 交互式提交

Commitizen 是一个用于规范化提交信息的工具,它能够帮助项目团队创建一致、易读的 Git 提交消息。通过使用 Commitizen,你可以确保提交信息按照预定义的规范格式化,方便后续查看和管理项目历史记录。

使用步骤:

  1. 运行以下命令,安装 Commitizen 和 Commitizen 适配器,比如 cz-conventional-changelog
pnpm add commitizen cz-conventional-changelog -D 
  1. 安装完成后,在 package.json 中添加一个 config.commitizen 的字段,并设置它的值为 cz-conventional-changelog
"config": {
  "commitizen": {
    "path": "cz-conventional-changelog"
  }
}
  1. 在 package.json 中的 scripts 字段中添加一个 commit 的命令。 示例如下:
"scripts": {
  "commit": "git-cz"
}

执行 npm run commit 就可以进行交互式提交了。

还可以使用其他更强大的适配器,如cz-git:

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

然后添加自定义配置(可选,使用默认)

两种配置方式

方式一: (推荐) cz-git 与 commitlint 进行联动给予校验信息,所以可以编写于 commitlint 配置文件之中。
例如: (⇒ 配置模板)

// .commitlintrc.js
/** @type {import('cz-git').UserConfig} */
module.exports = {
  rule: {
    ...
  },
  prompt: {
    useEmoji: true
    //option...
  }
}

方式二:  在 package.json 下 config.commitizen 下添加自定义配置,但过量的配置项会导致 package.json 臃肿,适合简单自定义。例如:

{
  "scripts": {
    "commit": "git-cz"
  },
  "config": {
    "commitizen": {
      "path": "node_modules/cz-git",
      "useEmoji": true
    }
  }
}

自用配置参考:

/** @type {import('cz-git').UserConfig} */
module.exports = {

  // 使用 @commitlint/config-conventional规则
  extends: ['@commitlint/config-conventional'],

  // 定义验证规则
  rules: {

    // header最大94字符
    'header-max-length': [2, 'always', 72],

    // subject不能为空
    'subject-empty': [2, 'never'],

    // type的类型必须在指定范围内
    'type-enum': [
      2,
      'always',
      ['build', 'docs', 'feat', 'fix', 'perf', 'refactor', 'revert', 'style', 'test']
    ],

    // type不能为空
    "type-empty": [2, 'never'],

    // type必须小写
    "type-case": [2, "always", 'lowerCase'],

    // scope 不能为空
    "scope-empty": [0, "never"],

    // scope 必须小写
    "scope-case": [2, "always", "lowerCase"],

    // scope 不限制类型
    "scope-enum": [2, "always", []],

    // 自定义规则 提交信息需携带需求相关信息
    'ticket-empty': [2, 'never'],
  },

  plugins: [
    {
      rules: {
        'ticket-empty': (parsed) => {
          // 定义以 --(story|task|bug)=[0-9]{7} 结尾的正则表达式
          const regex = /--(story|task|bug)=\d{7}$/;

          if (regex.test(parsed.subject)) {
            return [true];
          } else {
            return [false, '提交信息必须包含 7位数 tapd id: --(story|task|bug)=[0-9]{7}'];
          }
        },
      },
    },
  ],
 
  prompt: {
    messages: {
      type: '请选择提交信息的类型:',
      scope: '自定义本次修改的范围:',
      subject: '本次提交的简要描述:',
      body: '当前提交详细描述, 使用 "|" 来进行换行处理:',
      footerPrefixesSelect: '选择本次提交关联的任务类型',
      footer: '输入关联的 7 位数 tapd id:',
      confirmCommit: '是否确定以上提交信息?'
    },
    // 修改范围,根据项目情况自定义,可选
    scopes: [
      { name: 'publicclass' },
      { name: 'realquestion' },
      { name: 'shared' },
    ],
    types: [
      { value: 'feat', name: 'feat: 新增功能' },
      { value: 'fix', name: 'fix: 修复缺陷' },
      { value: 'docs', name: 'docs: 文档更新' },
      { value: 'style', name: 'style: 修改了代码格式,如空格、分号等' },
      { value: 'refactor', name: 'refactor: 重构代码' },
      { value: 'perf', name: 'perf: 优化代码性能' },
      { value: 'test', name: 'test: 新增、删除、修改测试用例(test目录下文件修改)' },
      { value: 'build', name: 'build: 构建流程、外部依赖变更(如升级 npm 包、修改 webpack 配置等' },
      { value: 'revert', name: 'revert: 将某功能还原到之前的逻辑' },
    ],
    allowCustomScopes: false,
    footerPrefix: "TAPD的任务ID:\n",
    // limit subject length
    subjectLimit: 50,
    useEmoji: false,
    emojiAlign: 'center',
    useAI: false,
    aiNumber: 1,
    themeColorCode: '',
    allowCustomScopes: true,
    allowEmptyScopes: false,
    customScopesAlign: 'bottom',
    enableMultipleScopes: true, // 允许范围多选
    scopeEnumSeparator: '&',
    customScopesAlias: '自定义范围',
    // emptyScopesAlias: '跳过',
    upperCaseSubject: false,
    markBreakingChangeMode: false,
    allowBreakingChanges: [],
    breaklineNumber: 100,
    breaklineChar: '|',
    skipQuestions: [
      'breaking',
      // 'footerPrefixesSelect',
      // 'footerPrefix',
    ],
    issuePrefixes: [
      { value: '--story=', name: 'story:   tapd需求id' },
      { value: '--task=', name: 'task:   tapd任务id' },
      { value: '--bug=', name: 'bug:   tapd缺陷id' },
    ],
    customIssuePrefixAlign: 'top',
    emptyIssuePrefixAlias: '跳过',
    allowCustomIssuePrefix: false,
    allowEmptyIssuePrefix: false,
    confirmColorize: true,
    maxHeaderLength: Infinity,
    maxSubjectLength: 94,
    minSubjectLength: 5,
    scopeOverrides: undefined,
    defaultBody: '',
    defaultIssues: '',
    defaultScope: '',
    defaultSubject: '',
    // 格式化最终提交信息
    formatMessageCB: (messageMod) => {
      return messageMod.defaultMessage.replace(/\n/g, '').replace(/(--story=|--task=|--bug=)\s+/g, '  $1');
    }
  }

};

commitlint

Commitizen是用来创建规范化提交的,如果项目成员没有使用 npm run commit 来提交,而是直接使用 git commit 的话还是有可能生成不规范提交的,所以还需要对最终的提交格式做一下校验,接下来添加提交格式校验,安装:

pnpm add commitlint @commitlint/config-conventional -D

添加 commit-msg 钩子

npx husky add .husky/commit-msg "npx commitlint --config .commitlintrc.cjs --color --edit "$1""

如果你全局安装commitlint的话,可以不使用 npx 命令

创建配置文件 .commitlintrc.cjs

/**
  feat:新增功能
  fix:修复bug
  docs:文档更新
  style: 代码格式修改
  refactor: 重构代码
  test: 测试用例修改
  build: 构建系统或包依赖修改
  ci: CI/CD 配置修改
  chore: 其他杂项修改
  revert: 回滚到上一版本
  perf: 性能优化
 */

module.exports = {

  // 使用 @commitlint/config-conventional规则
  extends: ['@commitlint/config-conventional'],

  // 定义验证规则
  rules: {

    // header最大94字符
    'header-max-length': [0, 'always', 94],

    // subject不能为空
    'subject-empty': [2, 'never'],

    // type的类型必须在指定范围内
    'type-enum': [
      2,
      'always',
      ['build', 'ci', 'chore', 'docs', 'feat', 'fix', 'perf', 'refactor', 'revert', 'style', 'test']
    ],

    // type不能为空
    "type-empty": [2, 'never'],

    // type必须小写
    "type-case": [2, "always", 'lowerCase'],

    // scope 不能为空
    "scope-empty": [0, "never"],

    // scope 必须小写
    "scope-case": [2, "always", "lowerCase"],

    // scope 不限制类型
    "scope-enum": [2, "always", []]

  }

};

一般建议保持 scope 强制和小写校验,但允许为空。这样可以鼓励使用 scope 来细分提交分类,但不强制要求。给予一定灵活性。

当 scope 非必填时,可以在提交时按需要填写,比如:

feat(home): add new parser
feat: add new feature

第一条指定了 scope,第二条未指定也能通过校验。

以上只是最基础的配置,可以根据团队实际情况自行更改,例如在提交信息时带上需求编号等。

另外提交类型中的 style 是一个通用的概念:

commit message 中的style表示的是不影响代码逻辑的修改,比如:

  • 代码空格
  • 格式化
  • 缺失分号
  • 代码注释

等与程序逻辑无关的修改。

另外一些文档也将style解释为代码风格的修改。

所以更准确的理解是"风格修改",而不是具体指CSS。

相比之下,如果是修改CSS样式表,建议使用下面两种类型:

  • feat:新增样式特性
  • fix:修复样式问题

如果修改的只是空格、格式化等与逻辑无关的变化,才使用style类型。当然,style也可以具体表示CSS修改,需要看团队自己的约定。

其他自定义校验

除了使用一些现成的npm包外,我们也可以自己写一些规则, husky 提供了钩子,我们在钩子里添加要执行的逻辑即可。

自定义 husky 校验-校验分支名

在校验代码格式之前,我们还想先看一下分支名是否符合要求,而不是让大家可以随心所欲,团队合作的项目尤其推荐哦。

假设我们要求的分支名格式为 feat_name_summary

在.husky 目录下新建目录custom,加入文件 branch-check.sh,文件内容为:

// .husky/custom/branch-check.sh

#!/bin/bash

# 获取当前所在的 Git 分支名
branch_name=$(git symbolic-ref --short HEAD)

# 分支名规则
pattern="^(fix|feat)_[a-z]{2,}_([a-zA-Z0-9]+)$"

# 正则不匹配就错误提示
if [[ ! $branch_name =~ $pattern ]]; then
  echo "Error: Invalid branch name. Branch name must match '(fix|feat)_(名字缩写如lyl等)_(具体改动如addcoupon)' format."
  exit 1
fi

分支名规则根据实际情况自行修改。

然后在pre-commit中加入这个脚本,在校验代码之前

#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"

# 校验分支
. "$(dirname -- "$0")/custom/branch-check.sh"

# 校验代码
npx lint-staged

完成后,以后再提交时就会校验一下分支是否符合规范

自定义 husky 校验-校验文件注释

为了项目更好的可读性和可维护性,在文件开头写一下注释,告诉别人这个文件是干嘛的是很有必要的,减少小伙伴的阅读和维护成本。

看下效果:

/**
 * @description 封装表单相关行为
 */

// balabala

当然,还有自动生成文件注释的插件,还会额外带上什么 author 、 updateTime 之类的大家感兴趣可以自己研究。

那具体怎么搞?

依旧在 .husky 下新建文件 custom/desc-check.sh ,注意,我们只校验src目录下的文件

#!/bin/bash

# 获取 git 暂存区中 src 目录下所有 js、ts 和 tsx 文件(不包括 .d.ts 文件)
files=$(git diff --cached --name-only --diff-filter=ACM | grep -E '^src/.*\.(js|ts|tsx)$' || true | grep -vE '\.d\.ts$' || true)


# 遍历文件列表
for file in $files; do
  # 检查文件的前 10 行中是否包含针对整个文件的 @description 注释
  if ! head -n 10 "$file" | awk '/\/\*\*/,/\*\//{if(/@description/){found=1;exit}}END{exit !found}'; then
    # 如果不存在,则输出错误信息并退出脚本
    echo "Error: $file 必须在文件开头包含指定的jsdoc注释属性 @description xxx"
    exit 1
  fi
done

然后在pre-commit中加入这个脚本,在校验代码之前

#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"

# 校验分支
. "$(dirname -- "$0")/custom/branch-check.sh"

# 校验文件注释
. "$(dirname -- "$0")/custom/desc-check.sh"

# 校验代码
npx lint-staged

大家还可以自己尝试编写其他校验。

总结

至此,你的项目就配置好了完整的校验流程,赶快试试吧!!!