Usando arquivos estáticos, mas nem tanto

Desde a versão 1.16 o Go tem suporte nativo para “embutir” arquivos estáticos no momento da compilação. Isto é excelente para quem gosta de distribuir suas aplicações em um único executável “autossuficiente”. O chato é ter que ficar recompilando tudo a cada mudança nesses arquivos durante o desenvolvimento.

Por aqui, resolvi esse problema usando a compilação condicional. Segue um exemplo usando o fiber.

main.go

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
package main

import (
    "github.com/gofiber/fiber/v2"
    "github.com/gofiber/fiber/v2/middleware/filesystem"
)

func main() {
    app := fiber.New()

    app.Use(filesystem.New(filesystem.Config{Root:getStaticDir()}))

    app.Listen(":8000")
}

Temos um arquivo main.go chamando uma função que retorna um http.Filesystem para ser mapeado pelo middleware. Esta função está declarada duas vezes (o que daria erro na compilação). Uma está em staticProd.go e outra no arquivo staticDev.go.

Adicionando um comentário no início de cada arquivo, conseguimos fazer com que o compilador use apenas uma delas.

staticProd.go

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
//go:build production
package main

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

//go:embed public
var embeds embed.FS

func getStaticDir() http.FileSystem {
    static, _ := fs.Sub(embeds, "public")

    return http.FS(static)
}

Veja que no arquivo staticDev.go temos um comentário parecido, mas com uma negação. Indicando que o arquivo é para compilar apenas na “ausência” da tag “production”.

staticDev.go

1
2
3
4
5
6
7
8
//go:build !production
package main

import "net/http"

func getStaticDir() http.FileSystem {
    return http.Dir("./public")
}

O mesmo diretório “public” será usado nas duas situações, a diferença é que no primeiro caso os arquivos serão “embutidos”, enquanto no segundo caso serão sempre lidos do disco. Ah sim! Pra quem tá pensando em performance, é claro que o embutido vai ser bem mais rápido.

Num ambiente de desenvolvimento, basta executar o projeto com go run *.go e teremos o diretório indicado do filesystem mapeado pelo middleware. Assim, os arquivos em disco sempre serão lidos e refletirão as mudanças sem precisar recompilar ou mesmo reiniciar o processo.

Para compilar a versão de produção, com os arquivos embutidos, use go build -tags production *.go.