跳至主要内容

【Other】使用 husky + lint-staged 來規範 commit file

本篇會介紹常見的統一程式碼風格的套件,並且使用 husky + lint-staged 來讓每次 commit 都會自動執行格式化程式碼的動作。

套件介紹

ESLint

ESLint 是一個 JavaScript 的 Linter,可以用來檢查程式碼是否符合 JavaScript 的規範,並且可以自訂規則。

npm init @eslint/config

上面的指令會透過問答的方式來產生 .eslintrc.js 設定檔,大部分會用到的像是使用什麼框架,或是有沒有用 typescript 都有考慮進去。



提示

如果是使用建置套件,像是 Vite 或 Next.js 都已經有內建設定好的 ESLint 可以使用,就不用再另外安裝。

ESLint 的設定檔主要有幾個項目:

  • Environments:專案是在哪個環境做執行,較常會設定 node: true 允許使用 module.exports
  • Parser Options:eslint 解析器的設定。
  • Plugins:引入的第三方套件。
  • Rules:客製化 ESLint 規則。
  • Extends:擴充其他設定檔。
  • Globals:定義全域變數,避免 eslint 報錯,像是 window 或 document。
  • Overrides:針對特定檔案的設定。

比較常會客製化的是 Rules,可以參考 ESLint Rules

如果特定檔案不想使用 eslint,可以新增 .eslintignore 檔案,並在裡面寫上不想要使用 eslint 的檔案路徑。

Prettier

Prettier 是一個程式碼格式化的工具,可以幫助我們統一程式碼的風格,並且可以自訂規則。

npm install prettier --save-dev --save-exact

--save-exact(shorcut : -E) 會固定安裝當前版本,避免未來安裝其他套件時,因為版本不同而造成的錯誤。

接著新增 .prettierrc.json 檔案,並在裡面寫上 prettier 的設定。

.prettierrc.json
{
"printWidth": 80,
"tabWidth": 2,
"useTabs": false,
"semi": true,
"singleQuote": false
}

詳細內容可以參考 Prettier Options

提供一下我個人用的 prettier 設定

.prettierrc.json
{
"semi": true,
"tabWidth": 2,
"singleQuote": false,
"bracketSameLine": false,
"bracketSpacing": true,
"trailingComma": "es5",
"printWidth": 80,
"jsxSingleQuote": false,
"arrowParens": "always"
}

Stylelint

Stylelint 是一個 CSS 的 Linter,可以用來檢查程式碼是否符合 CSS 的規範,並且可以自訂規則。

npm init stylelint

上面的指令會產生 .stylelintrc.json 設定檔,並且預設使用 stylelint-config-standard 規則。

如果使用 SASS 可以另外安裝 styledlint-config-standard-scss ,並放入 .stylelintrc.json 的 extends 中。

.stylelintrc.json
{
"extends": ["stylelint-config-standard", "stylelint-config-standard-scss"]
}

stylelint 跟 ESLint 一樣有幾個屬性可以做設定

  • Plugins:引入的第三方套件。
  • Rules:客製化 stylelint 規則。
  • Extends:擴充其他設定檔。

比較常會客製化的是 Rules,可以參考 stylelint Rules

這邊推薦一個 plugin 是 stylelint-order,可以設定 CSS 屬性的順序

npm install stylelint-order --save-dev

使用方法:

.stylelintrc.json
{
"plugin": ["stylelint-order"],
"rules":{
"order/properties-order":[
// 設定屬性排序
"position",
"display",
"width",
...
]
}
}

這邊提供我常用的設定順序,可以依照個人習慣做更動。

JavaScript + TypeScript ESLint 設定

如果專案有使用 JS + TS 的話,記得不要把 plugin:@typescript-eslint/recommended 放在 extends 裡面,不然 JS 會吃不到 ESLint 的規則。

解決方式是把 TS 的 ESLint 放在 Overrides 裡面另外設定 .ts 檔案的規則。

eslintrc.js
module.exports = {
extends: ["eslint:recommended", "plugin:react/recommended"],
overrids: [
{
files: ["*.ts", "*.tsx"],
extends: ["plugin:@typescript-eslint/recommended"],
},
],
};

eslint 與 prettier 衝突

有些屬性在 eslint 跟 prettier 都有,但是預設的規則不一樣,會造成其中一個修改後,另一個就會報錯。解決方法是下載 eslint-config-prettier

npm install eslint-config-prettier --save-dev

並且在 .eslintrc.js 檔案中的 extends 中加入 prettier,記得加在最後面,這樣 prettier 的規則就會覆蓋 eslint 的規則。

.eslintrc.js
{
"extends": [
"standard-with-typescript",
"plugin:react/recommended",
"prettier"
]
}

Vscode 設定

在 Vscode 這三個都有各自的套件可以安裝

並且在全域 setting.json,或專案底下 .vscode/settings.json 中加入以下設定,可以讓 Vscode 在儲存時自動修正 eslint、prettier、stylelint 的錯誤。

{
"formatOnSave": true, // 儲存時自動格式化
"editor.defaultFormatter": "esbenp.prettier-vscode", // 設定 prettier 為預設格式化工具
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true, // 儲存時自動修正 eslint 的錯誤
"source.fixAll.stylelint": true // 儲存時自動修正 stylelint 的錯誤
}
}

husky (v8) + lint-staged

講了這麼多規範套件,但是每次都要手動執行指令,或是在 vscode 設定自動修正才能進行格式化。在多人協作時,總是有可能會漏掉,這時候就可以使用 husky + lint-staged 來幫助我們。

  • hustky:可以在 git 的 hook 中執行指令,像是 commit、push、pull 等等。
  • lint-staged:可以在 git commit 時,只對 staged 的檔案做指定的動作。

所以 husky + lint-staged 做的就是在 commit 時,執行 husky 的 script 指令,裡面設定去跑 lint-staged 的指令,lint-staged 就會去執行設定的 eslint、prettier、stylelint 的指令。

安裝

首先先下載套件:

npx husky-init && npm install lint-staged --save-dev
信息

在下指令前記得先下 git init 初始化 git,不然會報錯。

下載完後會有一個 .husky 的資料夾,裡面會有 husky.sh 執行檔跟預設的 pre-commit 的檔案,預設是執行 npm test。



並且會在 package.json 中加入 husky install 的 script。

{
"scripts": {
"prepare": "husky install"
}
}

這個 script 會在 npm install 的時候執行,用意是當其他人下載專案包時可以自動執行 husky install,載入 .husky.sh 的執行檔。

信息

如果專案資料夾跟 .git 資料夾不在同一層(像是 monorepo),可以參考 Project Not in Git Root Directory

主要是直接改 package.json 裡 script 的 prepare 指令

"prepare": "cd ../ && husky install <ProjectFolderName>/.husky"

這樣的用意是先 cd 到有 .git 資料夾的地方,再執行 husky install,並且指定 husky 的路徑。

設定指令

先把 .hsuky/pre-commit 裡的 npm test 改成呼叫 lint-staged 的指令。

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


npx lint-staged

接著在 package.json 裡加入 lint-staged 要執行的格式化指令。

package.json
{
"lint-staged": {
"**/*.{ts,tsx,js,jsx,css,scss,json,md}": ["prettier --write"],
"**/*.{js,jsx,ts,tsx}": ["eslint --fix"],
"**/*.{css,scss}": ["stylelint --fix"]
}
}

可以針對特定的檔案格式進行格式化,設定完後就可以在 commit 時自動執行格式化的動作。

這邊簡單做一個範例,可以看到原本很糟的程式碼在 commit 時就會自動修正成設定好的規範。是不是很方便呢!



migrate to v9

husky 在 2024/01/25 發布了 v9 版本,設定有變動記錄一下。

原本 prepare script 是 husky install,現在改成只要 husky 就可以了。

{
"scripts": {
- "prepare": "husky install"
+ "prepare": "husky"
}
}

而在 git hook 的檔案裡會有預設的指定環境跟執行檔的路徑,v9 版本就不需要了。因為他把執行指令放到 .husky/_ 裡面,然後把 git config core.hookPath 指向為 .husky/_ (原本是指向 .husky)。

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

如果是新專案要安裝的話,可以直接下指令:

npm install -D husky && npx husky init

踩雷經驗

使用 Vscode Source Control 發 commit 時,lint-staged 不執行

有些會習慣使用 Vscode 內建的 commit 按鈕來操作,一樣可以進行 lint-staged,只是過程會顯示在 Vscode 的 output 裡



然後因為 Vscode 是使用 default 的 node version 去跑 lint-staged,我在使用的時候就遇到 lint-staged 最新版本只能使用 node 14 以上的版本,不然會報錯。但我明明下 node -v 是超過 14 的版本,後來才發現我的 nvm default node version 是使用 14,記得下指令修改 default node version 才不會找不到報錯原因。

nvm alias default {{ > 14 的版本號 }}

手動加 pre-commit 檔案,但是 husky 不執行

如果你手動新增 pre-commit 檔案,可能會遇到 The '.husky/pre-commit' hook was ignored because it's not set as executable. 的錯誤訊息,這是因為手動新增的話,檔案沒有執行權限,可以透過下面指令加上執行權限。

chmod +x .husky/pre-commit

或是刪除 pre-commit 檔案,使用 husky 的 add 指令:

npx husky add .husky/pre-commit

這樣 husky 就會自動幫你新增有權限的 pre-commit 檔案。

信息

在 husky 的原始碼可以看到 husky 在 set 檔案的地方有加上權限 {mode: 0o0755},這代表的是 755 的權限,權限說明可以看 chmod

有興趣了解 husky 原理的可以參考 husky 源码浅析 ,寫的超級詳細的!

參考資料