如何打造規範的開源專案workflow

逸傑發表於2018-08-02

前言

一般開源專案正常的開發流程是:

  • 初始階段
    • 建立倉庫
    • git clone repository 到本地
    • 新增修改程式碼
    • 提交commit message
    • push到 github 倉庫
  • 迭代階段
    • 切換開發分支
    • 開發新功能
    • 完善測試用例
    • 日常修復bug
    • 文件的完善
  • 釋出階段
    • 打tag
    • npm publish

表面看起來沒有什麼毛病,但是要做一個規範的多人協作的可維護的開源專案,開發流程上還是有不少細節的點需要我們來打磨。

那在多人協作的開發過程中到底會碰到哪些無法統一的行為?我們該如何解決這些問題?

  • 程式碼檢查
  • 提交資訊和修改日誌
  • 版本迭代
  • 執行落地

程式碼檢查

問題

這是一個老生常談的問題,語句結尾要不要分號?縮排是空格還是tab?是2個還是4個?

解決步驟

目前業界比較統一的做法是使用eslint

作為專案owner,建議全域性安裝eslint

npm install eslint -g
複製程式碼

之後就可以在本地的專案倉庫中,執行命令:

eslint --init
複製程式碼

可以有三種選擇:

  • 選擇一個流行的樣式規範
  • 自定義的程式碼習慣
  • 檢查你專案之前的程式碼樣式

常規的前端專案建議選用Standard規範(雖然不是真正的規範,但是屬於業內預設的規範吧),初始化如下:

{
   "devDependencies": {
        "eslint": "^4.19.1",
        "eslint-plugin-import": "^2.13.0",
        "eslint-config-standard": "^11.0.0",
        "eslint-plugin-standard": "^3.1.0",
        "eslint-plugin-promise": "^3.8.0",
    }
}
複製程式碼

React專案建議選用Airbnb規範,初始化後的package.json如下:

{
    "devDependencies": {
        "eslint": "^4.19.1",
        "eslint-config-airbnb": "^17.0.0",
        "eslint-plugin-import": "^2.13.0",
        "eslint-plugin-react": "^7.10.0",
        "eslint-plugin-jsx-a11y": "^6.1.1"
    }
}
複製程式碼

如果需要單獨定義一些規則,可以在根目錄下新建一個.eslintrc檔案,見具體配置

如果需要忽略一些檔案,可以在根目錄下新建一個.eslintignore檔案,見具體配置

最後在package.json中加上命令

{
    "scripts": {
        "lint": "eslint --ext .jsx,.js ."
    }
}
複製程式碼

這樣專案成員只需要操作以下步驟

  • vscode中安裝eslint擴充套件(以後使用vscode開啟專案都會檢測專案中的.eslintrc檔案進行實時lint提示)
  • cd project && npm install

衍生問題1

如果是之前的老專案接入了eslint,同時加入了git hook來強制執行,會導致之前的錯誤沒修復完無法提交,那有沒有辦法只檢測當前修改的檔案呢?

解決方案

目前社群比較成熟的方案是lint-staged

首先在專案中安裝lint-stagedhusky

husky是用來實現繫結git hooks的一個庫,後面會提到

npm install -D lint-staged husky
複製程式碼

然後在專案中的package.json中新增

{
  "scripts": {
    "precommit": "lint-staged"
  },
  "lint-staged": {
    "*.js": ["eslint --fix", "git add"]
  }
}
複製程式碼

以上操作在我們正常的開發流程中做了什麼呢?

git add .之後,git commit之前,會觸發precommit鉤子,執行lint-staged,然後lint-staged會只檢查經過暫存區的檔案,根據它的配置項執行命令。

例如上面的配置,就是針對所有修改過的以js為字尾的檔案,依次執行eslint --fix,git add兩個命令,幫你自動修復掉eslint的錯誤,再將修改新增到暫存區,然後一起commit到工作區。

衍生問題2

eslint只是作為程式碼檢測的工具,並沒有規範開發時的行為,那可不可以在編輯器上進行規範呢?

解決方案

主流方案是使用EditorConfig

首先在專案根目錄新建一個.editorconfig檔案,根據EditorConfig支援的大部分屬性自行定義

然後再給編輯器安裝EditorConfig的外掛,就可以使用了,外掛安裝可見官方下載

提交資訊和修改日誌

問題

  • commit message

    在實際開發的過程中,其實大家對自己的修改內容並沒有很好的界定,也沒有統一的格式化,導致了很多時候我們無法根據commit message來快速定位修改內容。

  • changelog

    其實一個專案新增功能,修復bug等資訊都需要記錄下來,便於區別不同版本的新特性,然後手動維護記錄通常容易忘記,導致這一部分經常沒有持續的更新。

解決步驟

目前社群使用最廣的方案是Commitizenconventional-changelog配合使用。

顧名思義

  • commitizen就是用來規範commit message
  • conventional-changelog可以根據格式化的commit message自動生成CHANGLOG.md

commitizen

有兩種開發方式:

  • 全域性安裝(牆裂推薦):

    npm install commitizen -g
    複製程式碼

    然後安裝commitizenadpater,比如cz-conventional-changelog(AngularJS的提交慣例)

    npm install -g cz-conventional-changelog
    複製程式碼

    然後你就可以使用git cz來替換git commit命令來進行程式碼提交了。

    如何打造規範的開源專案workflow

  • 專案安裝:

    讓你的專案讓別人更友好的接入commit規範,你不可能強制要求別人全域性安裝一個工具,就只能在開發依賴裡新增

    作為專案owner,預設已經全域性安裝了commitizen,然後執行:

    commitizen init cz-conventional-changelog --save-dev --save-exact
    複製程式碼

    以上命令做了三件事:

    • 在本地安裝cz-conventional-changelog模組

    • 儲存到package.jsondevDependencies

    • package.json的根層級新增了config.commitizen

      "config": {
        "commitizen": {
        	"path": "cz-conventional-changelog"
        }
      }
      複製程式碼

    為了保證其他貢獻者的倉庫中也能使用同一版本的commitizen,最好在倉庫安裝開發依賴

    npm install -D commitizen
    複製程式碼

    然後在專案的package.json中的scripts里加上

    {
      "scripts": {
        "cz": "git-cz"
      }
    }
    複製程式碼

    這裡需要注意一點,如果你在scripts指令碼中定義了{"commit": "git-cz"}的話,precommit鉤子會在真正commit之前執行兩次,見husky issue,所以自定義另外一個名稱就好了。

    然後專案contributors就可以通過npm run cz來愉快的提交格式化的commit message了。

changelog

還是國際慣例,全域性安裝

npm install -g conventional-changelog-cli
複製程式碼

切換到專案目錄後,執行

conventional-changelog -p angular -i CHANGELOG.md -s -r 0
複製程式碼

就可以根據commit message 自動生成一份CHANGELOG.md檔案

再配合上package.json裡的scripts

{
    "scripts": {
        "changelog": "conventional-changelog -p angular -i CHANGELOG.md -s -r 0"
    }
}
複製程式碼

只需要執行npm run changelog就行了,是不是很簡單?

但是該在什麼時機執行上面這條命令呢?

根據文件推薦的工作流,在修改package.json中的版本之後執行最合適。

不過在下面的版本控制會將工作流改成用npm version來實現。

{
    "scripts": {
        "version": "npm run changelog && git add CHANGELOG.md"
    }
}
複製程式碼

版本迭代

問題

傳統版本迭代步驟

  • package.json中修改遞增version

  • git add -A

  • git commit -m "update version"

  • git push

  • git tag <tag version>

  • git push --tag

  • npm publish

流程過於繁冗,很容易遺漏打tag那一步。

解決步驟

使用npm version命令,從文件上我們可以看到其依據semver支援了大部分alias:

npm version [<newversion> | major | minor | patch | premajor | preminor | prepatch | prerelease | from-git]
複製程式碼

例:初始版本為1.0.0

npm version prepatch //預備補丁版本號 v1.0.1-0

npm version prerelease //預釋出版本號 v1.0.1-1

npm version patch //補丁版本號 v1.0.2

npm version preminor //預備次版本號 v1.1.0-0

npm version minor //次版本號 v1.1.0

npm version premajor //預備主版本號 v2.0.0-0

npm version major //主版本號 v2.0.0

當在倉庫中執行npm version時,會自動提交git commit並打上git tag

當使用-m引數時,就可以自定義釋出版本的資訊,其中%s可以用來代替當前版本號

npm version patch -m "upgrade to %s for reasons"
複製程式碼

這樣以後版本迭代只需要以下步驟

  • npm version patch | minor | major | ...etc
  • git push
  • git push --tag
  • npm publish

衍生問題

如何釋出beta,rc,alpha版本呢?如果釋出了,應該如何安裝?

解決方案

首先我們要理解這些版本的含義

  • alpha:內部測試版本
  • beta: 公開測試版本
  • rc: 候選版本(Release Candidate)

然後將package.jsonversion改成x.x.x-beta

配合npm publish --tag <tag>,我們可以釋出對應的dist-tag

舉個例子:

使用npm publish --tag beta釋出後,然後就可以使用npm install <pkg>@beta安裝對應版本的包。

我們可以通過npm dist-tag ls <pkg>來檢視包的dist-tag

{
    latest: 1.0.1, // 這就是npm publish預設釋出的tag
    beta: 1.0.1-beta
}
複製程式碼

當我們的beta版本穩定後,可以使用npm dist-tag add x.x.x-beta latest設定為穩定版本。

執行落地

問題

正常工作流我們分為兩塊:

  • 開發流程

    在開發流程中,我們需要保障

    • 程式碼lint通過
    • 單元測試通過
    • 規範的提交資訊(optional)
  • 釋出流程

    在釋出流程中,我們需要保障

    • 打包通過
    • 生成CHANGELOG
    • 規範的版本號迭代

解決方案

根據上述兩種流程,我們可以使用git hooknpm hook

開發流程

在上面已經介紹過,我們選用husky作為git hook的實現工具

npm install -D husky
複製程式碼

程式碼lint和單元測試,我們可以放到precommit中執行,修改鉤子執行的命令

{
    "scripts": {
    	"test": "jest",
        "precommit": "lint-staged && npm run test"
    }
}
複製程式碼

commit message lint的解決方案:commitlint

npm install -D @commitlint/config-conventional @commitlint/cli
複製程式碼

然後在package.json中配置鉤子

{
    "scripts": {
        "commitmsg": "commitlint -E GIT_PARAMS"
    },
    "commitlint": {
        "extends": ["@commitlint/config-conventional"],
        "rules": {
            "subject-case": [0]
        }
    }
}
複製程式碼

這樣我們在git commit執行之前驗證了eslint是否通過,測試用例是否通過,commit message是否符合規範。

當然也可以在commit後面新增引數--no-verify來跳過commit message lint

釋出流程

  • bump version

    修改package.json

    {
        "scripts": {
            "version": "npm run changelog && git add CHANGELOG.md",
            "postversion": "git push && git push --tags"
        }
    }
    複製程式碼

    然後執行npm version patch | minor | major

  • npm publish

    新增prepublishOnly hook

    {
        "scripts": {
            "prepublishOnly ": "npm run build"
        }
    }
    複製程式碼

    然後執行npm publish

執行npm version命令時會依次執行npm script中的preversionversionpostversion三個鉤子,具體詳情見文件

prepublishOnly只會在npm publish之前執行,prepareprepublish都會在npm publish和不帶引數的npm install時執行,見npm script文件

結尾

至此,我們已經基本打造了一個比較完善的開源專案workflow。

對於專案開發者來說,只需要做以下幾點改變:

  • 編輯器安裝eslinteditorconifg外掛
  • 使用git cz提交資訊
  • 使用npm version patch | minor | patch來進行版本迭代

是不是更簡單方便了呢?

歡迎大家拍磚,廣納良言!

參考文獻

相關文章