ゆるふわ技術日誌

エンジニア見習いの悪戦苦闘日記

モノレポ on husky戦略

2020年は毎月2本技術的なアウトプットするぞ、と言いつつ1月から打ち破ってしまったので罪滅ぼしに最近業務でやったことについて備忘録的に書いておこうと思います。

モノレポを採用しているプロダクトにhuskyを導入したという話です。

※前置きが長いので、いらない人は本題のところまで読み飛ばしてくださいませ。


モノレポ

モノリシックレポジトリの略。

詳細な定義は知らないので、実際には違うかもしれないが(ていうか多分違う。)この記事におけるモノレポは「一つのGitリポジトリを用いて、一つのプロダクトのフロントエンド/バックエンド等のコードを管理するGit戦略」ということにして進めたいと思います。

husky

github.com

こいつのこと。これは一言で言うと、JS開発者間でGitフックの設定を揃えるための便利ツールとでも言えば良いのだろうか。

package.jsonに依存関係として定義しておくと、npm installnpm ciを使って依存ライブラリのダウンロードをした際に、Gitフックを設定してくれるという感じの挙動をします。あとはpackage.jsonや設定ファイルにgit commit時やgit push時にやらせたいことを列挙しておけば、自動的に設定に宣言したことを実行してくれます。

まぁJSを使って何かしらの開発をしていれば、npm installは間違いなくやるだろうし、そのタイミングを使ってGitフックが設定されるので実質的にGitフックの設定を強制することができるという素敵OSSってわけです。

これを使ってcommit前にPrettierやなんちゃらLintとかをかけておけば、誤ってコードフォーマットを忘れたコードがGitリポジトリ上に上がって、コードレビューで「フォーマッタかけてください」みたいな不毛なやりとりを産むこともないので素晴らしいのです。

ちなみに

ちなみに、huskyはGitフックを設定してくれるだけなので、lint-stagedというツールと組み合わせることで、ステージに上がっているファイルを対象に必要となるフォーマッタやLintをかけるということができます。どのファイルに対してどのフォーマッタを適用するかみたいなのを指定できるので超良い。超おすすめです。

んで本題。

基本的にここまでで書いたことをやりたかったら、一番早いのはlint-stagedのREAMDEに書いてある

$ mrm lint-staged

というのをプロジェクト直下で叩いてやるとインストールされているフォーマッタ等をみて良い感じにしてくれるので誰でもできると思います。

ところが、僕がメンバーとして関わっているプロダクトのリポジトリはモノレポ構成を取っており、1つのGitレポジトリの下にWebフロントエンド(ElmとTypeSript)やらスマートフォンアプリ(React Native製なのでTypeScript)、ほかにもサーバーサイドのコード等も入っているというような状況です。

このような状況において、huskyを導入するにはちょっと工夫をする必要があります。というのもGitフックはGitレポジトリ単位でしか設定できないので、たとえばWebフロントエンドのディレクトリ配下でhuskyを設定してしまうとスマートフォンアプリのディレクトリではhuskyを使うことができません。(できません、というか上書きで設定されるので最後にインストールしたものだけが生き残ってしまう)

huskyのREADMEにはlernaを使うように書いています。lernaはモノレポでの開発を支援するツールで、複数のnpm packageをスマートに管理してくれるものらしいです。(雑な理解)

ユースケースを見た所、npm packageの開発をモノレポを使って行う際に便利な機能を提供しているツールのようで、今回のようなケースには向かないと判断し別の方法を使ってなんとかhuskyを使う方法を模索しました。

そこで今回はGitリポジトリ直下にhusky用のディレクトリを作成し、そこで一括管理するという方法をとりました。

husky-example/
├── _husky
├── server
├── sp-client
└── web-client

こんな感じ。

_huskyディレクトリのしたのpackage.jsonのdevDependenciesにhuskyを追加し、設定を書きます。

今回Linterやformatterのツール自体はそれぞれのコードのあるディレクトリのpackage.jsonに書きたかったため、こんな感じで相対パスで指定するようにしました。

{
  "name": "husky",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "husky": "^4.2.1",
    "lint-staged": "^10.0.6"
  },
  "husky": {
    "hooks": {
      "pre-commit": "lint-staged"
    }
  },
  "lint-staged": {
    "../sp-client/app/**/*.{ts,tsx}": "../sp-client/node_modules/.bin/tslint -p ../sp-client --fix"
  }
}

少々冗長な感じは否めないですが、まぁ仕方なし。。。

そしてもう一点やっておくとよいかもしれないのが、Lint対象のディレクトリにあるpackage.jsonpostinstallに_huskyディレクトリ以下でnpm ciを走らせるスクリプトを書くことです。

具体的にはこんな感じ。

{
  "name": "sp-client",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "postinstall": "cd ../_husky && npm ci"
  },
以下省略

このようにしておくことで、sp-client以下でnpm inpm ciをやったとき、一緒に_huskyディレクトリ以下でnpm ciが実行され、Gitフックが設定されます。

ここまで設定しておけば、普段の開発では意識することなくcommit時のlintが走ります。

おわりに

ということを先日仕込んでみました。まだマージされてから日が浅いので何か問題が起こったら、考え直そうと思っていますが、今のところは順調に動いています。

ここまでドヤ顔で記事を書きましたが、husky用のディレクトリを切ったらいいのではとか、postinstallを書いたらいいのではとかは全て開発メンバーのアイデアです。。。

それではまた。