Airでソースコードの変更を自動保存したら自動的に再ビルドされるようにしよう！[Go, Docker]

# Airとは何か
Airは、Goで実装したアプリケーションをホットリロードしてくれるツールです。

:::note
### ホットリロードとは
コードの修正を確認したら再ビルドし、オンラインでプレビューをできるようにすることを言います。

ホットは`実行中/アクティブな`という意味で、リロードは`再読み込み`という意味です。
:::

https://github.com/air-verse/air

# インストール

下のコマンドでインストールします。

```
go install github.com/air-verse/air@latest
```

:::note

### `github.com/cosmtrek/air@latest`でのエラー

下でエラー出た方いたりしませんか？

```
go get github.com/cosmtrek/air@latest
```

多くのAirの紹介記事ではこのパッケージになっていますが、名前が変わったのかこちらを実行すると上手くいきません。気をつけてください
:::

GOPATHを繋いでなかったら繋ぐようにしてください。
```
export PATH=$PATH:$(go env GOPATH)/bin
```

# .air.tomlファイルを準備する

Airにホットリロードさせるために必要なファイルを準備します。

下のコマンドで初期化します。
```
air init
```

すると、下のような`.air.toml`が生成されます。

これを作業ディレクトリ上に配置します。

```toml:.air.toml
root = "."
testdata_dir = "testdata"
tmp_dir = "tmp"

[build]
  args_bin = []
  bin = "./tmp/main"
  cmd = "go build -o ./tmp/main ."
  delay = 1000
  exclude_dir = ["assets", "tmp", "vendor", "testdata"]
  exclude_file = []
  exclude_regex = ["_test.go"]
  exclude_unchanged = false
  follow_symlink = false
  full_bin = ""
  include_dir = []
  include_ext = ["go", "tpl", "tmpl", "html"]
  include_file = []
  kill_delay = "0s"
  log = "build-errors.log"
  poll = false
  poll_interval = 0
  post_cmd = []
  pre_cmd = []
  rerun = false
  rerun_delay = 500
  send_interrupt = false
  stop_on_error = false

[color]
  app = ""
  build = "yellow"
  main = "magenta"
  runner = "green"
  watcher = "cyan"

[log]
  main_only = false
  time = false

[misc]
  clean_on_exit = false

[proxy]
  app_port = 0
  enabled = false
  proxy_port = 0

[screen]
  clear_on_rebuild = false
  keep_scroll = true

```

# 実際に使ってみる

今回試しに下のようなmain.goを準備しました。

```go:main.go
package main

import (
	"fmt"
	"net/http"
)

func main() {
	fmt.Println("Hello, World!")
	http.ListenAndServe(":8080", nil)
}

```

下のコマンドで実行してみます。
```zsh:zsh
air -c .air.toml
```

:::note warn

### go.mod file not found in current directory or any parent directory;が出た場合

エラー文の通りにgo.modファイルを作りましょう。

```zsh:zsh
go mod init [パッケージ名]
```

:::

実行がうまくいくと下のように表示されます。

```zsh:zsh
  __    _   ___
 / /\  | | | |_)
/_/--\ |_| |_| \_ v1.60.0, built with Go go1.23.2

watching .
!exclude tmp
building...
running...
Hello, World!
```

ここでmain.goのファイルを下のように修正します。
`Hello, World!`の!を3つに増やしました。

```go:main.go
package main

import (
	"fmt"
	"net/http"
)

func main() {
	fmt.Println("Hello, World!!!")
	http.ListenAndServe(":8080", nil)
}

```

そうすると下のように際ビルドされます。

```zsh:zsh
main.go has changed
building...
running...
Hello, World!!!
```

# dockerファイルを準備する

Airはdockerと一緒に使用されることが多いと思います。

なのでdockerでもAirを使ってホットリロードできるようにしようと思います。

下のようなDockerfileを準備します。

```dockerfile:Dockerfile
FROM golang:1.23-alpine

RUN go install github.com/air-verse/air@latest

WORKDIR /app

COPY . .

RUN go mod download


CMD ["air", "-c", ".air.toml"]
```

加えてdocker-compose用のymlファイルを準備します。

```yml:docker-compose.yml
services:
  server:
    build:
      context: .
      dockerfile: ./docker/go/Dockerfile
    tty: true
    depends_on:
      - db
    ports:
      - 8080:8080
    # airに必要
    volumes:
      - .:/app
```

dockerでホットリロードを使うには、ボリュームのマウントが必要になります。

:::note
### ボリュームのマウントが必要な理由
ボリュームをマウントすることで、自分たちのいじったソースコードを、ホストマシンとdocker engineで共有することができます。
ボリュームのマウントを行わないとdocker engineが私たちのソースコードの変更を見ることができず、dockerコンテナ側のAirがソースコードの変更を確認することができません。
:::

dockerでは起動コマンドに`air -c .air.toml`を指定しているので、`docker compose`コマンドを使用します。

```zsh:zsh
docker compose up --build
```

airがしっかり導入できていれば、下のように表示されます。

```zsh:zsh
server-1  |
server-1  |   __    _   ___
server-1  |  / /\  | | | |_)
server-1  | /_/--\ |_| |_| \_ v1.60.0, built with Go go1.23.2
server-1  |
server-1  | watching .
```

ぜひ活用してみてください!