誰動了我的程式碼!?

榮頂發表於2022-02-14

前言

公司一個老專案,沒有做程式碼提交前的校驗,我拿到後,想著老專案嘛也沒時間幫它弄這些,反正就是改一點點小東西;儘量跟著它的程式碼風格寫,寫完提交就行;

直到某一天,又有一個人加入了進來。好傢伙,直接幹出事了。
很多個檔案一起提交的,然後 commit-msg 不規範,程式碼縮排也不規範,換行符也不一致,裡面還有很多沒用的程式碼,可讀性極差;

當你某一天 fetch 程式碼時,發現很多檔案是這樣的 ? 你是不是很崩潰?
但問題終歸是得去解決的;治理汙水的最好方法就是從能汙水產生的地方開始整治。我來幫它加上吧。?

下面我將帶大家一起,先通過husky+eslint完成一個最簡單的程式碼規範校驗,然後逐步優化,最後通過husky+eslint+lint-stage+commitlint+prettier實現一個強有力的限制。最後再配合commitizen,通過commitlint-config-cz+cz-customizable實現自定義的提交模板和限制規則來實現團隊最終的專案提交限制規約

eslint+prettier

這裡我為該專案適配了一套 eslit 規則,按照這套規則提交的程式碼不會有衝突。
eslint 和 prettier 大家平時專案應該都用到過,應該都很熟悉了,這裡將不佔用篇幅。(想看的可以私聊我,我可以單獨出一篇 eslint 和 prettier 主題的文章)

? 在這篇文章中,主要講解如何在團隊協同工作時,在 git 提交程式碼更改前,對不規範的程式碼和提交資訊進行校驗,修復,並限制不規範的提交。

husky

首先要介紹的是husky,搞工程化,我們肯定都少不了 husky,它能很方便的幫我們阻擋小可愛們的進攻,不,是為我們的專案新增git hooks

具體方法

首先我們將 husky 安裝到開發依賴中

npm i husky -D
# or
yarn add husky -D

注意

目前我所安裝的版本是husky@7.0.4,由於husky@6.0.0 後做了breaking change,所以在6.0.0版本之前的那種設定鉤子的方法已經不適用了,這裡我們只介紹最新的方式

安裝完後,我們需要在當前專案中建立一個.husky目錄並指定該目錄為 git hooks 所在的目錄。

使用以下命令快速建立 ?

#--no-install 參數列示強制npx使用專案中node_modules目錄下的husky依賴包
npx --no-install husky install

為了讓其他人在此專案中安裝依賴後也能自動建立.husky目錄並指定該目錄為 git hooks 所在的目錄,我們需要在 package.json 裡面新增一條指令碼"prepare": "husky install"

使用以下命令快速新增 ?

npm set-script prepare "husky install"
prepare 指令碼會在 npm i或者其他yarn or yarn add 之後自動執行。也就是說當我們安裝依賴後會自動執行 husky install 命令,從而自動建立.husky目錄並指定該目錄為 git hooks 所在的目錄。

使用以下命令快速建立 ?

npx --no-instal husky add .husky/pre-commit "npm run [你要執行的命令]"

完成後可以看到.husky目錄下新增了一個pre-commit檔案,其中的內容為 ?

這裡我用的是npm run lint,這樣我們就可以配合 Eslint 的程式碼校驗,來限制不規範程式碼的提交了


可以看到,不符合 Eslint 校驗規則的程式碼是沒法提交的;

當然,這裡的報錯問題只是由於縮排不規範引起的,類似這種的問題還有引號,句尾分號,換行符等等...都可以通過 eslint 的引數 --fix來自動修復,這樣就可以在提交前,先將能實現自動修復的簡單程式碼風格問題後 commit。複雜的情況還是要自己去手動處理的。

說到換行符,這裡我們需要了解的是:在 Windows 上預設的是回車換行(Carriage Return Line Feed, CRLF),然而,在 Linux/MacOS 上則是換行(Line Feed, LF)。

我們可以試一下將原先換行符為crlf的檔案格式化為換行符為lf後,執行git add .的情況。

可以看到最終 LF 換行符還是被 CRLF 轉化了;

如果你們不會跨平臺協作(都在 Mac/Linux,或者都在 Windows 上協同),只需要在當前專案中通過git config core.autocrlf false來阻止這種情況的發生。

為了保險起見,你需要新建一個.gitattributes檔案(主要用於定義每種檔案的屬性,以方便 git 幫我們統一管理。),設定 eol(end of line)為指定的換行符(lf/crlf),這裡我把所有檔案*.*的換行符都設定為了 LF,並且將一些非文字檔案進行了標記(排除它們),你也可以對每一種檔案型別分別設定對應的屬性 => *.js eol=lf*.ts eol=lf...

*.* eol=lf
*.jpg -text
*.png -text
  ...
# 或者?

*.js eol=lf
*.ts eol=lf
*.json eol=lf
  ...

檔案內容如下 ?

image.png
這樣,我們不管在什麼平臺上開發,檔案換行符都統一為 LF。

可以看到使用.gitattributes配置檔案後執行git add,所有不是指定換行符的檔案都會被自動更換為你指定的換行符,例如我這裡指定了lf,那麼 git add . 後,不是以 lf 換行的檔案都會被轉換為 lf ,並在終端輸出warning: CRLF will be replaced by LF in xxxx/filename,如圖 ?

.gitattributes有很多用處,具體可以檢視 ?gitattributes

? 到這裡,一個最簡單的程式碼風格限制方法就已經實現了。

既然做了,就肯定要做一套完整的,且好用的。下面我們來繼續完善其他功能 ~


lint-staged

什麼是lint-staged?顧名思義,藉助這個工具只是用來檢查 git 暫存區檔案的,就是在你git add file1,2,3... 後的暫存區檔案中執行 lint 的一個工具。

每次提交一兩個檔案,卻都要 lint 所有檔案話,是很沒有必要的,我們只對需要提交的程式碼進行 lint,這樣可以減少很多沒必要的時間開銷。(如果你每次修改一個檔案,都要去 lint 所有檔案,這個工具對你來說就沒有什麼意義了,husky 就管夠 ?)

使用方法

我們將.husky/pre-commit中之前寫程式碼改為 ?

#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
- npm run lint
+ npx --no-install lint-staged

然後在 package.json 中新增以下程式碼,lint-staged物件中採用鍵值對的方式進行配置,鍵名是你想處理的單個檔案或一個檔案型別,多個型別可以寫在{}中,用逗號分隔;鍵值是一個陣列,陣列中為 lint 時需要執行的命令組。

{
  "lint-staged": {
    "*.{ts,js,vue}": ["eslint", "echo '沒問題!'"]
  }
}

新增玩上述程式碼後,我們通過測試,將兩個檔案的縮排改為不符合規範的情況,然後將其中一個檔案暫存後,我們執行git commit後發現終端的報錯中,只有一個檔案的 lint 報錯資訊,另一個檔案的報錯並沒有出現。

當所有暫存區程式碼都符合規範時 ?,才會通過校驗執行提交。

✔ Preparing lint-staged...
✔ Hiding unstaged changes to partially staged files...
✔ Running tasks for staged files...
✔ Restoring unstaged changes to partially staged files...
✔ Cleaning up temporary files...

commitizen

Commitizen 是一個撰寫符合上面 Commit Message 標準的一款工具。通過它可以實現互動式撰寫規範的 Commit Message。

如果只在本倉庫使用 ?

npm install commitizen -D

如果你想全域性都用 commitizen 來幫你做 commit

npm install commitizen -g

安裝完成後,一般我們都採用符合 Angular 的 Commit message 格式的提交規範(當然也可以自定義,後面會講到~),執行以下命令生成符合 Angular 提交規範格式的 Commit message。

如果你專案用的是 npm ?

# 如果你專案用的是npm
npx --no-install commitizen init cz-conventional-changelog --save-dev --save-exact

如果你專案用的是 yarn ?

# 如果你專案用的是yarn
npx --no-install commitizen init cz-conventional-changelog --save-dev --save-exact

執行了上述命令後,它將為你專案安裝 cz-conventional-changelog 介面卡模組,把 config.commitizen 的金鑰新增到檔案的根目錄新增到 package.json

可以在package.json 中看到,自動的新增了以下內容 ?

{
  ...
  "config": {
    "commitizen": {
      "path": "./node_modules/cz-conventional-changelog"
    }
  }
  ...
}

完成後,通過命令yarn cz,你如果是全域性安裝的 commitizen,那你直接 git cz,都可以通過以下互動式的撰寫 commit messag 然後提交

限制 commitlint

由於 commitizen 並不是強制使用的,仍然可以通過git commit來提交,所以我們必須在不管是通過cz還是git commit提交前,都要對 commit messag 進行一次校驗,不符合規範的情況下是不允許進行 commit 的

首先我們需要安裝commitlint,commitlint/config-conventional

yarn add @commitlint/cli @commitlint/config-conventional -D

使用以下命令快速建立 git hooks 的 commit-msg 鉤子 ?
這樣每次 commit 的時候都會由 commitlint 對 commit message 進行一次檢驗

npx --no-instal husky add .husky/commit-msg 'npx --no-install commitlint --edit "$1"'

然後我們建立一個 commitlint 配置檔案到專案根目錄 ?

echo "module.exports = {extends: ['@commitlint/config-conventional']}" > commitlint.config.js

以上將會在專案目錄中生成 commitlint.config.js,程式碼如下,他將繼承@commitlint/config-conventional中的 Commit message 規範。("feat", "fix", "perf", "refactor"...)

module.exports = {
  extends: ["@commitlint/config-conventional"],
};

然後我們在終端進行測試

echo 'feat: bar' | npx --no-install commitlint

如果你執行上面這個行命令後出現了以上這種報錯。

將檔案更改問 UTF-8 的格式即可解決;這個問題目前已經在 ? Issues中,有不少人遇到了(我也是 ?)。
最簡單的方法就是,把檔案用記事本開啟,選擇另存為,然後在彈窗的右下角更改字元編碼為 UTF-8 (Windows 使用者執行echo "xxx" > xx.js時,檔案編碼可能為 UTF-16 LE),更改好後,把原來的commitlint.config.js檔案替換掉即可。

解決以上問題後,我們再測試一下,可以看到,不符合規範的 commit-msg 是會導致報錯的,也就 commit 不了了,說明我們的 commitlint 已經生效了~ ???

到此,commit-msg 的校驗也已經完成 ✔

如果,你想自定義 commitlint 的互動文字(不用 feat,fix...,很多人都喜歡在 commit message 前面加一個 emoji 表情符號),當然也可以。

我們需要安裝 cz-customizable來實現自定義 commit message 規則,以及安裝對應的commitlint-config-cz來配套校驗(直接從自定義的檔案裡讀取規則)

執行以下命令 ?

yarn add commitlint-config-cz  cz-customizable -D

在專案根目錄,建立一個.cz-config.js檔案,並複製cz-config-EXAMPLE.js 中的內容到其中。然後改成你自己想要的規則即可。

當然,你也可以用我寫好的:

module.exports = {
  types: [
    { value: "feat", name: "feat: 一個新的特性" },
    { value: "fix", name: "fix: 修復一個Bug" },
    { value: "docs", name: "docs: 變更的只有文件" },
    { value: "style", name: "style: 程式碼風格,格式修復" },
    { value: "refactor", name: "refactor: 程式碼重構,注意和feat、fix區分開" },
    { value: "perf", name: "perf: 碼優化,改善效能" },
    { value: "test", name: "test: 測試" },
    { alue: "chore", name: "chore: 變更構建流程或輔助工具" },
    { value: "revert", name: "revert: 程式碼回退" },
    { value: "init", name: "init: 專案初始化" },
    { value: "build", name: "build: 變更專案構建或外部依賴" },
    { value: "WIP", name: "WIP: 進行中的工作" },
  ],
  scopes: [],
  allowTicketNumber: false,
  isTicketNumberRequired: false,
  ticketNumberPrefix: "TICKET-",
  ticketNumberRegExp: "\\d{1,5}",
  // it needs to match the value for field type. Eg.: 'fix'
  /*
  scopeOverrides: {
    fix: [
      {name: 'merge'},
      {name: 'style'},
      {name: 'e2eTest'},
      {name: 'unitTest'}
    ]
  },
  */
  // override the messages, defaults are as follows
  messages: {
    type: "選擇一種你的提交型別:",
    scope: "選擇一個scope (可選):",
    // used if allowCustomScopes is true
    customScope: "Denote the SCOPE of this change:",
    subject: "簡短說明(最多40個字):",
    body: '長說明,使用"|"換行(可選):\n',
    breaking: "非相容性說明 (可選):\n",
    footer: "關聯關閉的issue,例如:#12, #34(可選):\n",
    confirmCommit: "確定提交?",
  },
  allowCustomScopes: true,
  allowBreakingChanges: ["feat", "fix"],
  // skip any questions you want
  skipQuestions: ["scope", "body", "breaking"],
  // limit subject length
  subjectLimit: 100,
  // breaklineChar: '|', // It is supported for fields body and footer.
  // footerPrefix : 'ISSUES CLOSED:'
  // askForBreakingChangeFirst : true, // default is false
};

建立完.cz-config.js檔案後,我們需要回到 package.json 檔案中,將 config.commitizen.path 更改為"node_modules/cz-customizable",如果你的.cz-config.js檔案在專案根目錄下,那麼可以不配置下面這條,commitlint-config-cz 會自動在專案根目錄下尋找: .cz-config.js.config/cz-config.js

...
{
  "config": {
    "commitizen": {
      "path": "node_modules/cz-customizable"
    },
    // 如果你的.cz-config.js檔案在專案根目錄下,那麼可以不配置下面這條,commitlint-config-cz會自動在專案根目錄下尋找: .cz-config.js 或 .config/cz-config.js
    "cz-customizable": {
      "config": "你的檔案路徑/xxxconfig.js"
    }
  }
}
...

關於commitlint-config-cz更高階的用法可以檢視 ?commitlint-config-cz

最後我們將之前建立過的commitlint.config.js中的程式碼進行更改 ?

module.exports = {
- extends: ["@commitlint/config-conventional"],
+ extends: ["cz"],
};

或者你也可以在commitlint.config.js中手動新增自定義的規則 ?,他將覆蓋 extends 中的規則

module.exports = {
  extends: ["@commitlint/config-conventional","cz"],
  rules: {
    "type-enum": [
      2,
      "always",
      [
        "init",
        "build",
        "ci",
        "chore",
        "docs",
        "feat",
        "fix",
        "perf",
        "refactor",
        "revert",
        "style",
        "test",
      ],
    ],
  },
};

到這裡,自定義的 commit message 的校驗也 ok 了 ✅

最後

提醒:專案的程式碼風格和規則要和團隊一起制定哦 ~

至此,在團隊協同的專案中,不符合規範的提交就被扼殺在搖籃裡面了。我們大家不管是從書寫程式碼還是提交程式碼最好都要規範哦~ 不給自己惹麻煩的同時,也不會給他人或公司帶來麻煩。這就是本篇的全部內容啦~如果對你有幫助,記得點贊鼓勵 ~

我是榮頂,一個面向快樂程式設計的前端開發 ?
如果你也非常熱愛前端相關技術!掃描二維碼~ 加入前端超人技術交流群 ?

回覆 [加群],將拉你進學習交流群,與其他前端愛好者共同進步!
回覆 [書籍],獲取大量前端pdf書籍。
朋友圈不定期舉行送書活動。一起加油,衝!

相關文章