前言
一般開源專案正常的開發流程是:
- 初始階段
- 建立倉庫
- 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-staged
和husky
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等資訊都需要記錄下來,便於區別不同版本的新特性,然後手動維護記錄通常容易忘記,導致這一部分經常沒有持續的更新。
解決步驟
目前社群使用最廣的方案是Commitizen和conventional-changelog配合使用。
顧名思義
commitizen
就是用來規範commit message
conventional-changelog
可以根據格式化的commit message
來自動生成CHANGLOG.md
commitizen
有兩種開發方式:
-
全域性安裝(牆裂推薦):
npm install commitizen -g 複製程式碼
然後安裝
commitizen
的adpater
,比如cz-conventional-changelog
(AngularJS的提交慣例)npm install -g cz-conventional-changelog 複製程式碼
然後你就可以使用
git cz
來替換git commit
命令來進行程式碼提交了。 -
專案安裝:
讓你的專案讓別人更友好的接入commit規範,你不可能強制要求別人全域性安裝一個工具,就只能在開發依賴裡新增
作為專案owner,預設已經全域性安裝了
commitizen
,然後執行:commitizen init cz-conventional-changelog --save-dev --save-exact 複製程式碼
以上命令做了三件事:
-
在本地安裝
cz-conventional-changelog
模組 -
儲存到
package.json
的devDependencies
中 -
在
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.json
的version
改成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 hook
和npm 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
中的preversion
,version
,postversion
三個鉤子,具體詳情見文件
prepublishOnly
只會在npm publish
之前執行,prepare
和prepublish
都會在npm publish
和不帶引數的npm install
時執行,見npm script文件
結尾
至此,我們已經基本打造了一個比較完善的開源專案workflow。
對於專案開發者來說,只需要做以下幾點改變:
- 編輯器安裝
eslint
和editorconifg
外掛 - 使用
git cz
提交資訊 - 使用
npm version patch | minor | patch
來進行版本迭代
是不是更簡單方便了呢?
歡迎大家拍磚,廣納良言!