18
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Go4Advent Calendar 2019

Day 13

Goで快適な開発環境を手に入れるためにホットリロードツールを開発している話

Last updated at Posted at 2019-12-13

この投稿は、 Go4 Advent Calendar 2019の 13日目のものになります。

はじめに

例えばWebアプリケーションを開発する際などに、ソースコードの変更を検知して自動で再ビルドをしアプリケーションを起動する、所謂ホットリロードを行う機能はだいたいのメジャーな言語で何かしらの形で提供されていると思います。

Goにもいくつかのライブラリが存在していて、おそらくメジャーなものといえば freshrealize の2つだと思います。
僕自身もfreshには数年ほどお世話になっており、実際にプロジェクトの開発でも使用しています。
しかしながら、fresh を使用していく中でこのライブラリがいくつかの問題を抱えていることもわかりました。

  • メンテナンスがされていない
  • mod=vendor のような仕組みに対応しきれていない (https://github.com/gravityblast/fresh/issues/99)
  • 監視ファイル(ディレクトリ)の細かい設定ができない
  • 起動時の最初のbuildに失敗すると監視が動かない

realize もこのうちいくつかの同様な問題を抱えており、特に監視ファイルの柔軟な設定ができないのが比較的大きいプロジェクトを効率よく開発していく上でストレスになりました。

また、ローカルの開発環境を Dockerdocker-compose で構築することは最近では一般的な手段の一つだと思います。その際 MacDocker を利用する場合は、 Docker for Mac を利用することが多いと思われますが、処理が遅くホットリロードを行うと応答しなくなることも稀によくあり、これもまた開発効率を落とす要因であることに気づきました。

さきほど紹介したライブラリは、メンテナンスされていないこともありこれらの問題に対する解決をライブラリ側に望むことはほぼ不可能だと考えています。
そのためこれらの代替手段を探していましたが、なかなか見つからなかったため今回は実装することにしました。

本記事は僕が開発しているホットリロードツール fresher についての紹介記事になります。
アドベントカレンダー投稿日にリリースが間に合わずまだ開発中ではありますが、https://github.com/kanataxa/fresher というレポジトリで公開しています。
realize ほど多機能でないシンプルなホットリロードを提供するライブラリですが、 fresh が抱えている課題を解決した better-fresh を目指しています。

※開発途中のため仕様変更が入る場合があります、ご了承ください

fresher(開発中)が満たす機能について

後発である fresher を多くのユーザに使ってもらうためには、 今まで存在していた従来のツールと比較した際に優位な点を示すこと、そして学習コストを低くし乗り換えをしやすくする必要があると考えました。

そこで、 fresher では以下のような機能を提供します。

YAMLベースの設定ファイルによる機能のカスタマイズ

fresher では細やかな設定をしてくためにユーザ側に設定ファイルを書くこと要求します。
もし手っ取り早く go run main.go をホットリロードで起動したい場合は以下のような設定を書いて fresher -c your/conf/path.yml を実行するだけです。

path:
  - .

この設定ファイルをユーザがカスタマイズすることで適用するプロジェクトへより最適化できる機能を提供することを目指しています。

ホットリロード

fresher の一番核となる機能です。
fresher では, https://github.com/fsnotify/fsnotify を用いてファイルの監視をしています。
fresher はファイルの作成も検知しなければならないため、監視対象のファイルではなくディレクトリ丸ごとを監視します。
そして fsnotify によって通知されたファイルが監視対象であった場合に、再度 go build し、ビルドしたバイナリを実行することでホットリロードを実現しています。

go run を使用していない理由は、後述のビルドする環境と実行環境を分けるような機能を提供する都合と、go run 内で作成されたバイナリを実行するプロセスが別で起動してしまうため、別途そのプロセスを kill しなければならないためです。

監視ファイルの設定に柔軟性を持たせる

一番最初に述べたように fresh は監視するファイルやディレクトリの指定に柔軟性がないと考えています。
例えば、特定のディレクトリの配下の特定のファイルパターンを無視したいときなど、これを指定する方法がありませんでした。
そこで fresher ではより柔軟に監視ファイルの設定をできるようにします。
具体的には下記の仕様をサポートします。

  1. ディレクトリごとに監視/無視するファイルやディレクトリを指定できるようにする
  2. グローバルに無視するファイルやディレクトリを指定できるようにする
  3. パターンマッチングを用いて指定できるようにする

まずは以下のディレクトリ構成と yml ファイルをみてください

bash-3.2$ tree ./
./
├── config.yml
├── main.go
├── controller
│   ├── controller.go
│   └── const.go
├── model
│   ├── message.go
│   └── message_test.go
└── pkg
    ├── config.go
    ├── const.go
    ├── pkg.go
    └── utils
        ├── utils.go
        └── utils_test.go

path:
  - .
  - name: controller
    exclude:
      - const.go
  - model
  - name: pkg
    include:
      - utils*
      - config.go
exclude:
  - vendor
  - '*_test.go'

path というフィールドでディレクトリを指定し includeexclude によってそのディレクトリ配下にあるファイルやディレクトリの監視/無視の設定をします。もし include/exclude を特に指定する必要がない場合は - dir_name のような記述も可能になっています。

また path と同じレベルにある exclude によってグローバルに無視するファイルやディレクトリの設定ができます。

基本的に以下のようなルールで設定ファイルから監視対象のファイルを決定します。

  1. path に記述されたディレクトリを起点に再帰的に監視対象のファイルかどうかを判断する
  2. include が指定されている場合は、再帰的に見るディレクトリや監視対象とするファイルを include で指定されたものと一致するもののみにする. もしくは exclude に設定されているファイルと一致した場合は監視対象から除外する。
  3. include/exclude はファイルかディレクトリかを区別せず filepath.Match を用いて一致したものを監視/除外する。

クロスコンパイル可能

Goはクロスコンパイル可能な言語です。そこで fresher でもクロスコンパイルをサポートします。
env というフィールドを用いて GOOSGOARCH を指定できるようにします。
もしユーザが fresher を実行している環境と別の環境のバイナリをビルドしたい場合はこのフィールドを設定することでビルドが可能になります。
また argoutput, target を指定することでビルド対象やバイナリの出力先、ビルド時の引数も指定することが可能です。

build:
  env:
    - GOOS=linux
  target: main.go
  output: /tmp/app
  arg:
    - -v

ビルド前後でのフックの提供

fresher では build の直前と直後にユーザが任意の操作をできるような機能を提供しています。これは realize が提供していた scripts に似たような機能です。
build 内の before/after で実行する操作を設定することができます。 async でその実行処理の完了を待つかどうかを設定することが可能です。

build:
  before:
    - echo Before Build hook
  after:
    - docker cp /tmp/app container:/tmp/app
    - name: docker
      arg:
        - exec
        - container
        - /tmp/app
      async: true

ホスト上でビルドし、Dockerで実行する(開発中)

例えば、MacDocker を使用する際に Docker For Mac 使うことがあるかと思いますが、非常に動作が遅く重いです。
この場合、ホットリロードを Docker 側で行おうとすると、ホスト上で数秒でビルドできるようなソースコードですらビルドにものすごく時間がかかる場合があります。これは開発サイクルに大きな支障をきたしストレスの原因になります。

そこで、 fresher ではホスト上でビルドしDocker側で実行できるような仕組みを提供することでより快適な開発環境をユーザに提供します。

※この仕組みは現在開発中です。

おわりに

この fresher は、ユーザの求める機能をなるべくシンプルでより使いやすく提供するということを目指しながら開発しています。
また、Go のホットリロードツールの新たな選択肢の一つになればよいなと考えています。

まだ開発途中ではあるものの、おおよその機能は開発完了で設定ファイルの書き方をよりブラッシュアップしていくという状態で、年内~年始あたりにv0.0.1 をリリースできればよいなあと考えています。
fresh が提供していた基本的なホットリロードの機能はすでに実装されていて、fresh 相当のツールとしては利用可能だと思います。

特に監視ファイルの細かい指定ができないことは fresh を使用していて課題に感じていたので、もし同様の思いを抱えているがいたらぜひ使っていただけると嬉しいです。

また、Docker For Mac などを用いて開発していて再ビルドのストレスに悩まされている方、まだ開発中ですが、なるべく早く(遅くても年内には)この問題への解決策の一つを fresher が提供する予定なので提供した際には良ければ利用してみてください。

そして、もし使用するうえで要望や問題があった場合はIssueを立ててください、日本語で大丈夫です。なるべく早いメンテナンスをします。

18
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
18
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?