🦜

GoとSvelteKitの組み合わせ

2021/06/29に公開

あらかじめ必要なもの

  • npm(6.14以降)
  • Go(1.16以降)

プロジェクトの開始

go mod init sample
npm init svelte@next frontend
// Choice "Svelte app template" is "Skelton Project".
// Choice "Use TypeScript" is No.
// Choice "ESLint" is No.
// Choice "Prettier" is No.
cd frontend
npm install
npm i -D @sveltejs/adapter-static@next

開発モードとリリースモード

development.go

// +build !release

package main

import (
	"log"
	"net/http"
	"net/http/httputil"
	"net/url"
)

func init() {
	u, err := url.Parse("http://localhost:3000/")
	if err != nil {
		log.Fatal(err)
	}
	http.Handle("/", httputil.NewSingleHostReverseProxy(u))
}

release.go

// +build release

package main

import (
	"embed"
	"io/fs"
	"log"
	"net/http"
)

//go:generate sh -c "cd frontend; npm run build"
//go:embed frontend/build/*
var content embed.FS

func init() {
	pub, err := fs.Sub(content, "frontend/build")
	if err != nil {
		log.Fatal(err)
	}
	http.Handle("/", http.FileServer(http.FS(pub)))
}

main.go

package main

import (
	"context"
	"flag"
	"fmt"
	"log"
	"net"
	"net/http"
	"os"
	"os/signal"
)

func logger(h http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		log.Print(r.RequestURI)
		h.ServeHTTP(w, r)
	})
}

func health(w http.ResponseWriter, r *http.Request) {}

func main() {
	port := 8080
	flag.IntVar(&port, "p", 8080, "http listen port")
	flag.Parse()
	log.SetFlags(log.Lshortfile)
	ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt)
	defer stop()
	l, err := net.Listen("tcp", fmt.Sprintf(":%d", port))
	if err != nil {
		log.Fatal(err)
	}
	defer l.Close()
	log.Println("listen:", l.Addr())
	http.HandleFunc("/api/health", health)
	server := &http.Server{Handler: logger(http.DefaultServeMux)}
	go func() {
		if err := server.Serve(l); err != nil {
			log.Fatal(err)
		}
	}()
	<-ctx.Done()
	server.Shutdown(ctx)
}

以下の様にsvelteの構成ファイルにstatic-adapterを追加します。

frontend/svelte.config.js

import adapter from "@sveltejs/adapter-static";
/** @type {import('@sveltejs/kit').Config} */
const config = {
  kit: {
    adapter: adapter({
      fallback: "index.html",
    }),
  },
};
export default config;

SvelteKitの新しいバージョンでは開発やビルドにviteコマンドを使うようになりました。
npm install --global viteをして以下の設定ファイルを追加しましょう。

frontend/vite.config.js

import { sveltekit } from "@sveltejs/kit/vite";

/** @type {import('vite').UserConfig} */
const config = {
  plugins: [sveltekit()],
};

開発モード

バックエンドサーバーを起動します。

go run . -p 8080

別のターミナルにてnpm開発サーバーを起動します(localhost:3000)。

cd frontend; npm run dev

http://localhost:8080/ を開きます。

バックエンドサーバーのAPI以外のアクセスはlocalhost:3000に中継します。
この場合、npmの開発サーバーのホットリロード機能が機能します。

リリースモード

go generate -tags release ./...
go build -tags release -o build/sample .

以上の操作でSvelteKitの静的ファイル生成アダプターによる生成物をバイナリに埋め込んだ実行ファイルが出力されます。

所感

  • Svelteのコンポーネントの作成と利用ができる
  • 2つのモードのビルドは双方ともに爆速なので開発体験が良い
  • 全てのリソースはバイナリにバンドルされるためデプロイが非常にシンプル
  • フロントエンドの開発体験を損なうことなく開発できる
  • SvelteKitはレイアウトテンプレートやルーティングの機能もあるため、それなりにリッチなSPAでも構築できる
  • APIを除くリソースは静的コンテンツに落とし込まれるのでCDNとの相性が良い
  • Svelteの1コンポーネント1ESモジュールという作りは快適かつデバッグしやすい

Discussion