yikegaya’s blog

仕事関連(Webエンジニア)と資産運用について書いてます

DockerとGinでGo+WebAssemblyプロジェクトの雛形を作った

GoでWebassemblyを試してくてdockerとHTTPフレームワークGinで開発環境を作りました。

フォルダ構成

.
├── Dockerfile
├── docker-compose.yaml
├── go.mod
├── go.sum
├── index.html
├── server/
│   └── main.go
└── webassembly/
    ├── js/
    └── main.go

Dockerfile

FROM golang:1.21-bullseye

COPY ./ /app

WORKDIR /app

RUN cp "$(go env GOROOT)/misc/wasm/wasm_exec.js" ./webassembly/js

RUN cd /app/webassembly && GOARCH=wasm GOOS=js go build -o main.wasm

CMD ["go","run","/app/server/main.go"]

解説

  • RUN cp “$(go env GOROOT)/misc/wasm/wasm_exec.js” ./webassembly/jsコマンドでGoに標準で入っているWebAssemblyバインディングスクリプトを所定のパスにコピーします。これはWebAssemblyの実行に必要になります。
  • RUN cd /app/webassembly && GOARCH=wasm GOOS=js go build -o main.wasmコマンドでWebAssemblyのコードをbuildしてmain.wasmファイルを作成します。これは後ほどAssemblyバインディングスクリプトと合わせてフロントで読み込ませます。

docker-compose.yaml

version: '3'
services:
  go-webassembly-app:
    build:
      context: ./
    volumes:
      - ./server:/app/server
      - ./go.mod:/app/go.mod
      - ./go.sum:/app/go.sum
      - ./index.html:/app/index.html
      - webassembly_go_module_data:/go
    ports:
      # ローカルのポート被らないように
      # 4321は適当
      - "4321:80"
volumes:
  webassembly_go_module_data:

解説

  • volumeにDockerfile内でコピーしたバインディングスクリプトとbuildしたwasmファイルを含めてしまうとホストの情報で上書きされて消えてしまうので外しています。
  • ポートはローカルで3000とか4000とか3100とか他のサービスで色々使ってたので一応被らなそうなポートにしてます。

index.html

<!DOCTYPE html>
<html>
<head>
    <title>Title</title>
</head>
<body>
    <h1>WebAssembly Demo</h1>
    <script src="/js/wasm_exec.js"></script>
    <script>
        const go = new Go();
        WebAssembly.instantiateStreaming(fetch("/wasm/main.wasm"), go.importObject)
            .then(result => {
                go.run(result.instance);
            })
            .catch(err => {
                console.error(err);
            });
    </script>
</body>
</html>

解説

バインディングスクリプトwasm_exec.jsを読み込ませその中にあるGoコンストラクタのインスタンスにwasmファイルを読み込ませます。これでGoのコードをブラウザで実行できます。 このhtmlと読み込んでいるjs、wasmのパスはGinで指定する必要がありますが後述します

server/main.go

package main

import (
    "github.com/gin-gonic/gin"
)

func main() {
    r := gin.Default()

    r.Static("/js", "./webassembly/js")
    r.Static("/wasm", "./webassembly")
    r.StaticFile("/", "./index.html")

    r.Run(":80")
}

解説

必要最低限のGinのコードです。htmlだけでなくjsとwasmのファイルも指定する必要があります。

webassembly/main.go

package main

import (
    "fmt"
    "syscall/js"
)

func main() {
    fmt.Println("fmt Println!")
    js.Global().Get("console").Call("log", "js.Global Call!")
    select {}
}
  • ブラウザのコンソールに適当な文字列を出力するWebAssemblyの最低限のコードです。
  • fmtで出力した文字とjs.Globalから出力した文字両方がブラウザに表示されます。

終わり

とりあえず最小構成でWebAssemblyのプログラムを実行する環境を作りました。

これをベースに画像/動画処理などもっと凝ったプログラムを書いてWebAssemblyを試していきたいと思います。