Depuis la version 1.16 de Golang, il est possible d'intégrer des fichiers statiques dans nos binaires au build. C'est faisable grâce à l'ajout d'un nouveau package nommé embed. Ce package fournit un ensemble pratique d'interfaces, de méthodes pour embarquer des fichiers statiques dans les binaires Go. 🤯

La préhistoire

Avant ça, pendant un long moment, j'utilisais un projet nommé "go-bindata" mais qui est déprécié depuis un long moment.

Bon, c'était du passé, maintenant on peut intégrer facilement un fichier statique directement dans le binaire sans passer par des dépendances autres. 😅

Un peu de doc 🎓

Vous pouvez retrouver toute la documentation du package embed en utilisant la commande :

go doc embed

Cette commande est vraiment à retenir quand on utilise Golang au quotidien, c'est super pratique, la doc n'est pas toujours très exhaustive mais le 3/4 du temps, vous avez de bons exemples.

D'ailleurs en bon complément de la documentation officielle, vous avez cet excellent livre de référence en français de Frédéric G. Marand (FGM pour les intimmes). 👏🏼

Un p'tit exemple 😆

On va se créer un petit projet Go tout simple avec un fichier .txt et .html pour l'exemple.

mkdir embed && cd $_

tree
.
├── static
│   └── index.html
├── example.txt
└── main.go
Exemple de projet Golang (contenu)

Notre code sera socké dans le fichier main.go. Go nous offre 3 façons d'intégrer du contenu dans un binaire :

  • Sur type string
  • Sur type byte
  • Sur type FS

On va explorer chaque type, un par un, histoire de rendre plus visuel la chose! 😜

package main

import (
	_ "embed"
	"fmt"
)

func main() {
    //go:embed example.txt
    var s string
    fmt.Println(s)
}
main.go

Pour spécifier quelle variable va contenir le contenu du fichier statique example.txt, on va utiliser un commentaire uniquement lu par le compilateur Go. Ce commentaire est //go:embed suivit du nom de votre fichier. A noter qu'il ne faut surtout pas qu'il y ait d'espace entre "//" et "go", l'espace transformerait la chose en commentaire standard du langage. 🤗

Ainsi dans notre fichier, on retrouve le contenu suivant :

Hello with embed package! 😜
example.txt

Et donc maintenant si on exectute notre programme :

$ go run main.go
Hello with embed package! 😜

Ok, c'est cool me direz-vous, maintenant voyons plutôt comment embarquer tout un répertoire avec embed.FS. 😎


package main

import (
	"embed"
	"fmt"
	"html/template"
	"os"
)

func main() {
	//go:embed static/*
	var assetData embed.FS
	t, err := template.ParseFS(assetData, "static/index.html")
	if err != nil {
		fmt.Println(err)
	}
	templateData := struct {
		Title string
	}{
		Title: "File inside Go binary",
	}
	t.Execute(os.Stdout, templateData)
}

main.go

embed.FS nous permet d'intégrer une arborescence de fichiers statiques, c'est-à-dire, dans notre cas, d'embarquer le répertoire static dicté par la directive //go:embed static/*.

Ce répertoire, comme vous avez pu le constater plus haut ne possède qu'un fichier : index.html, bien sûr, ça fonctionne avec tout un ensemble. 😜

D'ailleurs ce fichier possède le contenu suivant :

<html>
  <head>
    <title>{{$.Title}}</title>
  </head>
  <body>
    <h1>Go is so cool!!!!</h1>
  </body>
</html>
index.html

Les patterns de la directive ne peuvent pas contenir de "." ou ".." ni commencer par un slash pour déclarer un chemin vers un répertoire.

Pour correspondre à tout ce qui se trouve dans le répertoire courant, il faut utiliser le "*" (comme ici : //go:embed static/* ). 😅

Et donc :

$ go run main.go 
<html>
  <head>
    <title>File inside Go binary</title>
  </head>
  <body>
    <h1>Go is so cool!!!!</h1>
  </body>
</html>