概要
- Go には embed という機能があり、ビルドするときファイルを埋め込むことができます
- embed で静的ファイル(HTML/CSS/JS)を埋め込む際に dist をどう管理するか悩みました
- 解決策の1つとして、go generate を活用する方法を紹介します
背景
go:embed でフロントエンドアプリの dist を埋め込みたい時がありました。
次のようなディレクトリ構造です。
$ tree . ├── go.mod ├── go.sum ├── main.go └── web # web 配下はフロントエンドアプリ ├── node_modules ├── package.json ├── package-lock.json ├── src ├── dist └── main.go # dist を埋め込みたい
web というディレクトリ配下に package.json や src があります。
ここにフロントエンドアプリのソースが置かれ、同じ階層の web/dist へビルドした成果物が吐き出されます。 そして web/main.go で Goのバイナリに埋め込みます。
package web import ( "embed" ) //go:embed all:dist/* var Dist embed.FS // フロントエンドアプリの dist を格納
このとき次のような課題がありました。
課題: dist の管理方法
go:embed でファイルを埋め込むには、埋め込みたいファイルが存在している必要があります。
ここで言えば web/dist にファイルがないと embed で埋め込み出来ません。
... はい、当たり前です。
当たり前のことなのですが、ビルドする際に手順ができてしまいます。
- cd web && npm run build
- go build
この手順が「暗黙的だ」と課題を感じてました。
意識していないと npm run build の実行を忘れてしまう、ということです。
解決策
上記の課題の解決策として、ありがちなのはコマンドをまとめることでしょうか。
例えば、Makefile や go-task が有用だと思います。
それでも良いのですが、go generate を活用する方法もあるのに気づきました。
go generate で npm run build を実行する
go generate はコードを生成する機能です。 例えば OpenAPI や Protobuf からコードを生成する時使いますよね。
そんな go generate ですが embed する際も活用できそうだと気づきました。
つまり、埋め込むファイルの生成処理や取得処理を go generate で行う、ということです。
ここではフロントエンドアプリの dist を埋め込みたいので、次のような記述を追加します。
package web import ( "embed" ) // 追加 //go:generate npm install //go:generate npm run build //go:embed all:dist/* var Dist embed.FS
go generate からなるコメントを追加しました。
この状態で go generate ./... を実行するとフロントエンドアプリのビルド処理が行われ、
web/dist へビルド成果物が置かれます。
終わりに
結局のところビルド手順は減ってません。npm run build の代わりに go generate を実行する手順が加わっただけです。 しかし「Go のエコシステムの中に入れられた」と言いますか、より Go ライクな方法なのではないかと思ってます。