
До Go 1.16 чтобы встроить файл в наш бинарник на Go, например, какие-то шаблоны, html файлы, если это веб сервер или даже README.md, нам приходилось либо саморучно затаскивать их в наш код, либо пользоваться сторонними пакетами. Первый способ не гибкий, в нем можно ошибиться, так как нужно все делать вручную. Второй способ получше, но это дополнительные зависимости, которых может не оказаться в вашей среде и главное дополнительные шаги при сборке приложения.
Начиная с Go 1.16 нашу проблему директива //go:embed path_pattern
Условия использования директивы
директива должна предшествовать строке, содержащей объявление переменной, в которую будет помещен файл. Между директивой и объявлением переменной допускаются только пустые строки и комментарии
паттерн пути к файлу или директории не должен начинаться с / и иметь в себе . или ..
паттерн должен соответствовать хотя бы одному файлу или не пустой директории. В противном случае сборка не состоится
симлинки запрещено использовать в паттерне
паттерн может принимать только файлы или директории внутри модуля, но не во вне
чтобы получить все файлы в директории нужно использовать *
Встраиваем файл
Директива //go:embed позволяет нам встроить файл как строку string, так и как слайс []byte. Это можно сделать так:
package main
import (
_ "embed"
"fmt"
)
//go:embed README.md
var readme string
//go:embed image.png
var image []byte
func main() {
fmt.Println(readme)
}
В данном примере файлы располагаются следующим образом:
.
├── README.md
├── image.png
└── main.go
Теперь содержимое файла README.md лежит в переменной readme, а содержимое image.png в переменной image. При этом это обычные переменные, которые мы можем менять в ходе выполнения нашей программы.
Встраиваем директорию
Мы поняли как встраивать один файл, но что делать, если у нас директория с несколькими html файлами, а еще директория с изображениями. Как нам встроить это все в наш бинарник?
На этот раз наш пакет будет выглядит так:
.
├── README.md
├── image.png
├── main.go
└── www
├── html
│ ├── about.html
│ └── index.html
└── images
├── image.jpg
└── avatar.jpg
А код, которые встраивает в себя всю директорию www следующий:
package main
import (
"embed"
"fmt"
)
//go:embed README.md
var readme string
//go:embed www
var www embed.FS
func main() {
fmt.Println(readme)
entries, err := www.ReadDir("www/html")
if err != nil {
fmt.Fatal(err)
}
for _, entry := range entries {
fmt.Println(entry.Name())
}
}
При этом embed.FS реализует интерфейс fs.FS, что очень удобно для абстрагирования в коде откуда на самом деле он читает файлы. Несмотря на это, для embed.FS есть ряд ограничений:
- это строго read-only структура, так что можно свободно передавать ее в горутины
- паттерн заканчивающийся на /* встраивает все файлы даже те, которые начинаются на . и на _
Важно отметить, что встраивание одного файла дважды будет честным, то есть, если мы делаем что-то подобное:
//go:embed test.jpg
var image []byte
//go:embed test.jpg
var image2 []byte
то размер файла увеличится на 2 размера файла test.jpg. При этом для встраивания через embed.FS это не так.