告别代码质量隐患:Husky 生态工具链在前端工程化中的实战应用

在现代前端团队开发中,代码质量控制和提交规范已成为项目可持续发展的基石。一个结构良好、记录清晰的代码库不仅能提升开发效率,还能降低维护成本,使团队协作更加顺畅。

为实现这一目标,成熟的前端团队通常采用双管齐下的策略:在代码质量和风格统一方面,ESLint 与 Prettier 的组合已成为行业标准;而在提交规范化方面,husky、lint-staged、commitlint、commitizen 以及 cz-git 等工具的协同使用,则构建了一道坚实的质量防线。

这套完整的前端工程化配置不仅能在提交前自动检查和修复代码问题,还能引导开发者遵循统一的提交信息格式,从而打造一个条理分明的项目历史记录。本文将深入探讨这些工具如何协同工作,以及如何在你的项目中构建这套自动化工程体系,让规范成为团队的无形资产而非额外负担。

概念

git hook

git hook也就是常说的Git 钩子,是Git 在特定重要动作发生时触发自定义脚本的机制,分为客户端和服务器端钩子。客户端钩子由诸如提交和合并这样的操作所调用,而服务器端钩子作用于诸如接收被推送的提交这样的联网操作。客户端钩子通常包括了提交工作流钩子、电子邮件工作流钩子和其它钩子。这些钩子通常存储在项目的 .git/hooks目录下,我们需要关注的主要是提交工作流钩子。提交工作流钩子主要包括了以下四种:

  • pre-commit:该钩子在提交信息前运行。 它用于检查即将提交的快照。如果该钩子以非零值退出,Git 将放弃此次提交,你可以利用该钩子,来检查代码风格是否一致。
  • prepare-commit-msg:该钩子在启动提交信息编辑器之前,默认信息被创建之后运行。 它允许你编辑提交者所看到的默认信息。
  • commit-msg:它接收 一个参数 ,该参数是 包含当前提交信息的临时文件路径 。钩子脚本可以通过读取该文件获取提交信息内容。如果该钩子脚本以 非零状态码退出 ,Git 将 中止此次提交 ,从而阻止不符合规范的提交被写入仓库。因此, 常用于 校验提交信息的格式或内容,以及根据项目需求进行其他验证(例如检查提交是否关联 issue)。
  • post-commit:该钩子一般用于通知之类的事情。

在上面的钩子中,我们重点关注 pre-commitcommit-msg 钩子。

husky

husky是常见的 git hook 工具,使用 husky 可以挂载 Git 钩子,当我们本地进行 git commitgit push 等操作前,能够执行其它一些操作,比如进行 ESLint 检查,如果不通过,就不允许 commitpush

Lint-staged

pre-commit hook 中,一般来说都是对当前要 commit 的文件进行校验、格式化等,而不是对全局文件校验,因此在脚本中我们需要知道当前在 Git 暂存区的文件有哪些,而 Git 本身也没有向 pre-commit 脚本传递相关参数,Lint-staged这个包为我们解决了这个问题。简单说就是当我们触发 pre-commit hook 中的脚本命令后,配合 Lint-staged 的配置可以只检查暂存区的文件从而避免我们每次检查都把整个项目的代码都检查一遍的尴尬情况。其次,Lint-staged允许指定不同类型后缀文件执行不同指令的操作,并且可以按步骤再额外执行一些其它 shell 指令。它通过运行git diff --staged --diff-filter=ACMR --name-only -z命令获取暂存区文件信息

commitlint

执行 git commit -m 'xxx' 时,用来检查 'xxx' 是否满足固定格式的工具。 为什么使用 commitlint:团队中规范了 commit 规范可以更清晰的查看每一次代码提交记录,还可以根据自定义的规则,自动生成 changeLog 文件。

commitizen

基于 Node.js 的 git commit 命令行工具,辅助生成标准化规范化的 commit message,是一个平台,具体职能交给适配器。

什么是adapter(适配器)

更换 commitizen 命令行工具的交互方式 插件。像 cz-emojicz-emoji-chinesecz-conventional-changelogcz-customizable,这些都是适配器,至于选择哪个,看个人喜好选择。本文选择cz-git

配置步骤

安装所有依赖

npm install --save-dev husky lint-staged @commitlint/cli @commitlint/config-conventional commitizen cz-git

配置husky

  • 初始化npx husky init,执行完之后项目根目录会多一个 .husky 文件夹并创建了一个pre-commit脚本,并且在package.json文件添加了prepare脚本用来

配置lint-staged

  • 首先明确一下,lint-staged 仅仅是文件过滤器,不会帮你格式化任何东西,所以没有代码规则配置文件,需要自己配置一下,对特定的文件进行特定的格式化

  • 配置文件可以有很多种方式,package.json文件里直接配置或者使用单独的配置文件(可参考官方文档),这里使用单独的配置文件.lintstagedrc.json,在项目根目录下创建

    json 复制代码
    {
      "*.{js,jsx,ts,tsx,vue}": ["eslint --fix", "prettier --write"],
      "*.{css,scss,less,html,md,json}": ["prettier --write"]
    }
  • package.json增加如下命令

    json 复制代码
    {
      "scripts": {
        "lint:lint-staged": "lint-staged",
      }
    }
  • 修改刚才husky初始化自动创建的pre-commit脚本npm run lint:lint-staged,至此在执行``git commit -m "xxx"时就会触发lint:lint-staged,如果 lint:lint-staged 命令失败,提交将自动中止。而lint:lint-staged`则会根据刚才的配置文件对特定文件进行特定的格式化

配置commitlint

  • package.json 添加 config 字段,为 commitizen 指定 cz-git 适配器

    json 复制代码
    {
      "config": {
        "commitizen": {
          "path": "./node_modules/cz-git"
        }
      }
    }
  • 创建 commitlint.config.js 文件来配置 lint 规则

    cz-git 有多种模板,这里使用中英文对照模版,可参考官方

    js 复制代码
    // commitlint.config.cjs这里要使用cjs的后缀文件名,告诉vite以commonjs规范来处理 
    module.exports = {
      // 继承的规则
      extends: ["@commitlint/config-conventional"],
      // 自定义规则
      rules: {
        // @see https://commitlint.js.org/#/reference-rules
    
        // 提交类型枚举,git提交type必须是以下类型
        "type-enum": [
          2,
          "always",
          [
            "feat", // 新增功能
            "fix", // 修复缺陷
            "docs", // 文档变更
            "style", // 代码格式(不影响功能,例如空格、分号等格式修正)
            "refactor", // 代码重构(不包括 bug 修复、功能新增)
            "perf", // 性能优化
            "test", // 添加疏漏测试或已有测试改动
            "build", // 构建流程、外部依赖变更(如升级 npm 包、修改 webpack 配置等)
            "ci", // 修改 CI 配置、脚本
            "revert", // 回滚 commit
            "chore", // 对构建过程或辅助工具和库的更改(不影响源文件、测试用例)
            "wip", // 对构建过程或辅助工具和库的更改(不影响源文件、测试用例)
          ],
        ],
        "subject-case": [0], // subject大小写不做校验
      },
    
      prompt: {
        messages: {
          type: "选择你要提交的类型 :",
          scope: "选择一个提交范围(可选):",
          customScope: "请输入自定义的提交范围 :",
          subject: "填写简短精炼的变更描述 :\n",
          body: '填写更加详细的变更描述(可选)。使用 "|" 换行 :\n',
          breaking: '列举非兼容性重大的变更(可选)。使用 "|" 换行 :\n',
          footerPrefixesSelect: "选择关联issue前缀(可选):",
          customFooterPrefix: "输入自定义issue前缀 :",
          footer: "列举关联issue (可选) 例如: #31, #I3244 :\n",
          generatingByAI: "正在通过 AI 生成你的提交简短描述...",
          generatedSelectByAI: "选择一个 AI 生成的简短描述:",
          confirmCommit: "是否提交或修改commit ?",
        },
        // prettier-ignore
        types: [
          { value: "feat",     name: "特性:     ✨  新增功能", emoji: ":sparkles:" },
          { value: "fix",      name: "修复:     🐛  修复缺陷", emoji: ":bug:" },
          { value: "docs",     name: "文档:     📝  文档变更(更新README文件,或者注释)", emoji: ":memo:" },
          { value: "style",    name: "格式:     🌈  代码格式(空格、格式化、缺失的分号等)", emoji: ":lipstick:" },
          { value: "refactor", name: "重构:     🔄  代码重构(不修复错误也不添加特性的代码更改)", emoji: ":recycle:" },
          { value: "perf",     name: "性能:     🚀  性能优化", emoji: ":zap:" },
          { value: "test",     name: "测试:     🧪  添加疏漏测试或已有测试改动", emoji: ":white_check_mark:"},
          { value: "build",    name: "构建:     📦️  构建流程、外部依赖变更(如升级 npm 包、修改 vite 配置等)", emoji: ":package:"},
          { value: "ci",       name: "集成:     ⚙️   修改 CI 配置、脚本",  emoji: ":ferris_wheel:"},
          { value: "revert",   name: "回退:     ↩️   回滚 commit",emoji: ":rewind:"},
          { value: "chore",    name: "其他:     🛠️   对构建过程或辅助工具和库的更改(不影响源文件、测试用例)", emoji: ":hammer:"},
          { value: "wip",      name: "开发中:   🚧  开发阶段临时提交", emoji: ":construction:"},
        ],
        useEmoji: true,
        emojiAlign: "center",
        useAI: false,
        aiNumber: 1,
        themeColorCode: "",
        scopes: [],
        allowCustomScopes: true,
        allowEmptyScopes: true,
        customScopesAlign: "bottom",
        customScopesAlias: "custom",
        emptyScopesAlias: "empty",
        upperCaseSubject: false,
        markBreakingChangeMode: false,
        allowBreakingChanges: ["feat", "fix"],
        breaklineNumber: 100,
        breaklineChar: "|",
        skipQuestions: [],
        issuePrefixes: [{ value: "closed", name: "closed:   ISSUES has been processed" }],
        customIssuePrefixAlign: "top",
        emptyIssuePrefixAlias: "skip",
        customIssuePrefixAlias: "custom",
        allowCustomIssuePrefix: true,
        allowEmptyIssuePrefix: true,
        confirmColorize: true,
        maxHeaderLength: Infinity,
        maxSubjectLength: Infinity,
        minSubjectLength: 0,
        scopeOverrides: undefined,
        defaultBody: "",
        defaultIssues: "",
        defaultScope: "",
        defaultSubject: "",
      },
    };
  • 在husky文件夹下添加commit-msg钩子脚本npx --no -- commitlint --edit ${1}

最后配置便捷脚本

package.json添加以下脚本,方便利用上述工具进行提交。提交代码时执行npm run commit即可

json 复制代码
{
  "scripts": {
    "commit": "git add . && git-cz",
  }
}

一图总结

相关推荐
marzdata_lily9 分钟前
从零到上线!7天搭建高并发体育比分网站全记录(附Java+Vue开源代码)
前端·后端
图扑软件12 分钟前
智慧城市新基建!图扑智慧路灯,点亮未来城市生活!
大数据·javascript·人工智能·智慧城市·数字孪生·可视化·智慧路灯
小君22 分钟前
让 Cursor 更加聪明
前端·人工智能·后端
顾林海32 分钟前
Flutter Dart 异常处理全面解析
android·前端·flutter
很萌很帅的恶魔神ww1 小时前
HarmonyOS Next之组件之自定义弹窗(CustomDialog)
javascript
残轩1 小时前
JavaScript/TypeScript异步任务并发实用指南
前端·javascript·typescript
AR71 小时前
unplugin-vue-router 的基本使用
javascript
用户88442839014251 小时前
xterm + socket.io 实现 Web Terminal
前端
Cutey9161 小时前
前端如何实现文件上传进度条
javascript·vue.js·面试
helloYaJing1 小时前
代码封装:超时重传方法
前端