Desde Go 1.16, la directiva //go:embed permite incluir ficheros, imágenes, plantillas y cualquier otro asset dentro del binario en tiempo de compilación. El resultado es un ejecutable completamente autocontenido que no depende de rutas relativas ni de la presencia de ficheros en el sistema donde se ejecuta. go generate complementa esto automatizando la generación de código repetitivo antes de compilar.
Incrustar un fichero de texto
package main
import (
_ "embed"
"fmt"
)
//go:embed version.txt
var version string
func main() {
fmt.Println("Versión:", version) // contenido de version.txt en tiempo de compilación
}
La directiva debe ir en la línea inmediatamente anterior a la variable. La variable puede ser string, []byte o embed.FS.
Incrustar múltiples ficheros con embed.FS
embed.FS es el tipo adecuado cuando necesitas varios ficheros. Admite globs y subdirectorios:
package main
import (
"embed"
"fmt"
"io/fs"
)
//go:embed templates/*.html
var templateFS embed.FS
//go:embed assets/css assets/js assets/img
var assetsFS embed.FS
func main() {
// Listar ficheros embebidos
fs.WalkDir(templateFS, ".", func(path string, d fs.DirEntry, err error) error {
if !d.IsDir() {
fmt.Println(path)
}
return nil
})
// Leer un fichero concreto
data, _ := templateFS.ReadFile("templates/index.html")
fmt.Println(string(data))
}
Servidor web self-contained
Combinando embed.FS con http.FileServer obtienes un servidor que distribuye sus propios assets sin depender de ningún directorio externo:
package main
import (
"embed"
"html/template"
"io/fs"
"net/http"
)
//go:embed web/static
var staticFS embed.FS
//go:embed web/templates
var templateFS embed.FS
func main() {
// Servir ficheros estáticos desde el FS embebido
staticSub, _ := fs.Sub(staticFS, "web/static")
http.Handle("/static/", http.StripPrefix("/static/",
http.FileServer(http.FS(staticSub))))
// Cargar templates embebidos
tmpl := template.Must(template.ParseFS(templateFS, "web/templates/*.html"))
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
tmpl.ExecuteTemplate(w, "index.html", map[string]any{
"Titulo": "App Go embebida",
})
})
http.ListenAndServe(":8080", nil)
}
Embeber configuración
import (
_ "embed"
"encoding/json"
)
//go:embed config/defaults.json
var configDefaults []byte
type Config struct {
Puerto int `json:"puerto"`
LogLevel string `json:"log_level"`
Debug bool `json:"debug"`
}
func CargarConfigDefaults() (*Config, error) {
var cfg Config
if err := json.Unmarshal(configDefaults, &cfg); err != nil {
return nil, err
}
return &cfg, nil
}
go generate: automatizar la generación de código
go generate busca comentarios con la directiva //go:generate y ejecuta los comandos indicados. Es un paso previo a la compilación que automatiza la generación de código repetitivo:
//go:generate stringer -type=Estado
//go:generate mockgen -destination=mocks/servicio_mock.go -package=mocks . Servicio
//go:generate protoc --go_out=. --go-grpc_out=. proto/api.proto
type Estado int
const (
Pendiente Estado = iota
Procesando
Completado
Error
)
// Tras ejecutar: go generate ./...
// Se genera automáticamente estado_string.go con:
// func (s Estado) String() string { ... }
stringer: generar String() automáticamente
// Instalar la herramienta
// go install golang.org/x/tools/cmd/stringer@latest
//go:generate stringer -type=Direccion
type Direccion int
const (
Norte Direccion = iota
Sur
Este
Oeste
)
// Genera direction_string.go con:
// func (i Direccion) String() string {
// return [...]string{"Norte", "Sur", "Este", "Oeste"}[i]
// }
La combinación de go:embed y go generate permite construir aplicaciones que se distribuyen como un único binario sin instaladores, sin gestionar rutas de datos y con el código generado siempre actualizado antes de cada compilación.
