前端自動化工做流中的hooks

在前端的平常工做中,常常會出現「當執行一種操做以前(以後)須要同時執行另外一種操做」的狀況,好比咱們但願在每次git commit以前都運行eslint代碼檢查、npm install以前檢查項目依賴等。做爲經典的狀況,各種工具均可以讓咱們在特定的動做發生時觸發自定義腳本,這個功能就叫鉤子hooksjavascript

平常常常用到的工具備npmgitwebpack,其中的hooks用法咱們分別介紹一下。前端

其中webpack的hooks是webpack爲開發者提供的運行時事件鉤子,咱們能利用它來編寫plugins,這個就不在這裏說了,未來會單獨寫一篇關於寫plugin的文章(立個flag╮(╯▽╰)╭)。java

npm hooks —— 監聽npm操做 / 訂閱npm源修改獲取通知

目前提到npm hooks,有兩個不一樣概念的操做。node

一般意義下的監聽npm各種操做的鉤子是經過配置package.json文件中的scripts字段來實現的。python

npm hooks則是npm提供的命令行操做,目的是爲了訂閱你須要的npm package發生的特定改動,好比能夠訂閱尤大(誤)的新動態等。固然,這項功能須要你本身提供一個域名,而且須要有npm帳號且購買服務,因此就很少討論了╮(╯▽╰)╭,具體參見npm-hook官方文檔webpack

npm script hooks

使用方法很簡單,在項目的package.json中的scripts字段加入"hook": "script"鍵值對便可。hook名稱由npm提供,script就是可以運行的shell語句。下面列幾個經常使用的hook:git

  • preinstall: 在npm install以前執行
  • install, postinstall: 在npm install以後執行
  • prestart: 在npm start以前執行
  • start, poststart: 在npm start以後執行
  • etc.

從以上的例子中能夠看出,hooks的命名是pre[op]爲操做以前的鉤子,[op]post[op]爲操做以後的鉤子。還有不少其餘的鉤子,具體能夠查閱npm script官方文檔github

因爲歷史緣由,publish相關的鉤子會比較特殊,具體緣由和修改後的樣子都在文檔裏了,很少介紹。web

e.g.

好比項目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 —— 監聽git操做

介紹

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例子:

  • pre-commit: 在commit以前運行
  • post-commit: 在commit以後運行
  • prepare-commit-msg: 在啓動提交信息編輯器以前(git commit -s那個),默認信息被建立以後運行
  • commit-msg: 生成基本commit-msg時觸發,能夠用來在提交經過前驗證項目狀態或提交信息

還有不少基於其餘操做的鉤子,都因工做流程不一樣而有所不一樣,具體能夠查閱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同步給全部終端,該怎麼辦呢?

用更簡單的方式使用git hooks

爲了解決上面的問題,有不少大神寫了第三方工具來實現hooks同步。對於前端來講,能夠在npm安裝的第三方工具備不少,例如huskyyorkygit-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共享給全部項目成員。

END

俗話說,懶惰是人類進步的動力,但願能夠用這些東西,作到一鍵完成全部手工重複任務,提升咱們的工做效率,把時間用在更有意義的事情上。

相關文章
相關標籤/搜索