在前端的平常工做中,常常會出現「當執行一種操做以前(以後)須要同時執行另外一種操做」的狀況,好比咱們但願在每次git commit以前都運行eslint代碼檢查、npm install以前檢查項目依賴等。做爲經典的狀況,各種工具均可以讓咱們在特定的動做發生時觸發自定義腳本,這個功能就叫鉤子hooks
。javascript
平常常常用到的工具備npm
、git
、webpack
,其中的hooks
用法咱們分別介紹一下。前端
其中webpack
的hooks是webpack
爲開發者提供的運行時事件鉤子,咱們能利用它來編寫plugins
,這個就不在這裏說了,未來會單獨寫一篇關於寫plugin
的文章(立個flag╮(╯▽╰)╭)。java
目前提到npm hooks,有兩個不一樣概念的操做。node
一般意義下的監聽npm各種操做的鉤子是經過配置package.json
文件中的scripts
字段來實現的。python
而npm hooks
則是npm提供的命令行操做,目的是爲了訂閱你須要的npm package發生的特定改動,好比能夠訂閱尤大(誤)的新動態等。固然,這項功能須要你本身提供一個域名,而且須要有npm帳號且購買服務,因此就很少討論了╮(╯▽╰)╭,具體參見npm-hook官方文檔。webpack
使用方法很簡單,在項目的package.json
中的scripts
字段加入"hook": "script"
鍵值對便可。hook名稱由npm提供,script就是可以運行的shell語句。下面列幾個經常使用的hook:git
npm install
以前執行npm install
以後執行npm start
以前執行npm start
以後執行從以上的例子中能夠看出,hooks的命名是pre[op]
爲操做以前的鉤子,[op]
或post[op]
爲操做以後的鉤子。還有不少其餘的鉤子,具體能夠查閱npm script官方文檔。github
因爲歷史緣由,publish相關的鉤子會比較特殊,具體緣由和修改後的樣子都在文檔裏了,很少介紹。web
好比項目npm install
以前依賴一個全局的npm包,用戶須要先npm install -g package
,這時就能夠把該操做寫到preinstall
裏:shell
package.json
... "scripts": { "preinstall": "npm install -g package" ... },
固然,shell的部分能夠寫全部的能夠執行的shell語句。若是須要的操做比較多,也能夠寫shell腳本,而後執行該腳本。對不少前端來講,直接開搞shell腳本比較困難,也能夠寫node、python等腳本,而後用node script.js
的方式執行也是ok的。
git hooks基本跟上面介紹的npm script hooks差很少,也是配置相應的pre-[op]
、post-[op]
之類的鉤子。
git經過項目根目錄下的.git
目錄中的內容來標記一個git庫並記錄相關信息,這點應該是衆所周知了。
git hooks的配置就在.git/hooks
目錄下,以無後綴的腳本文件的形式存在,文件名稱便是鉤子名稱,文件內容是shell腳本,你能夠自行添加可執行內容。通常在剛npm init
的hooks文件夾中全都是[hook].sample
示例文件,須要複製並更名爲該hook名稱才能夠正常使用。
根據git版本不一樣,可用的hooks也不一樣,舉下跟commit相關的hook例子:
git commit -s
那個),默認信息被建立以後運行還有不少基於其餘操做的鉤子,都因工做流程不一樣而有所不一樣,具體能夠查閱Git 鉤子官方文檔。
鉤子腳本能夠按照職責不一樣接收不一樣的參數並進行修改。好比commit-msg
鉤子,鉤子接收一個參數,是存有當前提交信息的臨時文件的路徑,參數能夠在shell腳本中以$1
的形式進行調用,有了這個咱們就能夠修改文件中的提交信息了。
舉個例子,當你想在每次commit以前用檢查代碼規範與否,就能夠直接在pre-commit
腳本最後添加npm run lint
(這裏看本身相關配置,不必定是這句)。
可是,git hooks的設計思路是在每臺終端以及服務器端提供不一樣的定製方案。說人話,就是由於git是一種分佈式的系統,它保證了全部終端的版本都是相同的,但hooks在服務器端和我的終端的配置可能不同,因此hooks的配置不能跟隨git提交。
例如gerrit
提供的用於修改commit message的commit-msg
鉤子就須要在git clone
的同時從遠程服務器下載到本地來替換,代碼以下:
git clone ssh://kinice@gerrit.company.com:29418/All-Projects && scp -p -P 29418 kinice@gerrit.company.com:hooks/commit-msg All-Projects/.git/hooks/
這固然是一種好方式。但有一種狀況,當咱們沒有其餘的能夠存儲腳本的第三方服務器,又但願將hooks同步給全部終端,該怎麼辦呢?
爲了解決上面的問題,有不少大神寫了第三方工具來實現hooks同步。對於前端來講,能夠在npm安裝的第三方工具備不少,例如husky
、yorky
、git-hooks
等。yorkie
是Vue做者尤雨溪fork了husky
並作了一些修改的工具,改善了一些使用體驗,因此如今只介紹一下yorkie。
*注:git-hooks
跟前兩種工具的思路不一樣,感興趣能夠了解一下:git-hooks。
yorkie
$ npm install yorkie --save-dev
// package.json { "gitHooks": { "pre-commit": "npm test", "commit-msg": "npm test", "...": "..." } }
簡單到看完配置就懂了吧,直接在package.json
中增長gitHooks
這一項,並直接把想執行的shell語句寫在裏面便可。
yorkie
的實現原理在安裝過yorkie以後,比對一下安裝以前的hook文件,會發現yorkie直接重寫了全部的hooks。因此咱們把/.git/hooks/pre-commit
的核心代碼貼出來看看yorkie作了什麼:
# 下面部分都是用來檢測有沒有hook和設置全局變量的 has_hook_script () { [ -f package.json ] && cat package.json | grep -q "\"$1\"[[:space:]]*:" } cd "." has_hook_script pre-commit || exit 0 export PATH="$PATH:/usr/local/bin:/usr/local" export GIT_PARAMS="$*" # 重點是這裏,運行了runner.js node "./node_modules/yorkie/src/runner.js" pre-commit || { echo echo "pre-commit hook failed (add --no-verify to bypass)" exit 1 }
忽略上面那些檢查是否存在hook腳本的代碼,腳本在最後執行了一個runner.js
:
// Node fs 和 path 模塊 const fs = require('fs') const path = require('path') // 用execa庫運行腳本 const execa = require('execa') // 當前腳本文件的執行路徑(不是文件所在路徑) const cwd = process.cwd() // 讀取代碼中的package.json const pkg = fs.readFileSync(path.join(cwd, 'package.json')) // 將package.json重的hooks字段取出來 const hooks = JSON.parse(pkg).gitHooks // 沒有hook則退出 if (!hooks) { process.exit(0) } // 這裏的process.argv[2]就是在hooks腳本里傳過來的hook名稱,如pre-commit const hook = process.argv[2] const command = hooks[hook] // 不是當前hook則退出 if (!command) { process.exit(0) } console.log(` > running ${hook} hook: ${command}`) // 使用execa.shellSync運行命令 try { execa.shellSync(command, { stdio: 'inherit' }) } catch (e) { process.exit(1) }
關於對runner.js
的解析,我寫到了註釋中,應該都能看得懂。即經過(npm install
時改寫hooks --> 將hooks改成運行本身的runner --> runner依賴package.json
)的方式,實現了將hooks信息保存在package.json中並能夠經過git共享給全部項目成員。
俗話說,懶惰是人類進步的動力,但願能夠用這些東西,作到一鍵完成全部手工重複任務,提升咱們的工做效率,把時間用在更有意義的事情上。