Go1.16引入新的//go:embed指令,可以在编译时嵌入文件和目录,并对其进行访问。通过它,真正做到部署时只有一个二进制文件。
背景:2021-02-16,Go Team正式发布了Go1.16。该版本包含下面的一些重要变化:
- embed 包和 //go:embed 指令
- 增加对 macOS ARM64 的支持
- 默认启用 Module
- io/fs 包
- 弃用io/ioutil
最后,还有许多其他改进和错误修复,包括构建速度提高了 20-25%,linux/amd64上内存使用量减少了 5-15%。有关更改的完整列表以及有关上述改进的更多信息,请参阅 Go 1.16 发行说明。
01 基本使用
基本思路是,在代码中添加特殊注释,Go将知道其包含的一个或多个文件。
Go源文件在引入“embed”包后,可以使用//go:embed指令在编译时,从包目录或者子目录的文件读取内容来初始化
string,[]byte,FS类型的变量。如,可以用下面的三种方式嵌入名为hello.txt文件,然后在运行时打印其内容。
目录结构:
1
2
3
|
.
├── main.go
└── hello.txt
|
1
2
3
4
5
6
7
8
|
import (
_ "embed"
"fmt"
)
//go:embed hello.txt
var s string
fmt.Print(s)
|
1
2
3
4
5
6
7
8
|
import (
_ "embed"
"fmt"
)
//go:embed hello.txt
var b []byte
fmt.Print(string(b))
|
1
2
3
4
5
6
7
8
9
|
import (
_ "embed"
"fmt"
)
//go:embed hello.txt
var f embed.FS
data, _ := f.ReadFile("hello.txt")
fmt.Print(string(data))
|
02 可能使用方案
版本信息
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
package main
import (
_ "embed"
"fmt"
"strings"
)
var (
Version string = strings.TrimSpace(version)
//go:embed version.txt
version string
)
func main() {
fmt.Printf("Version %q\n", Version)
}
|
对于更复杂的示例,我们甚至可以根据是否将某个构建标记传递给go工具来有条件地包含版本信息。
1
2
3
4
5
6
|
// version_dev.go
// +build !prod
package main
var version string = "dev"
|
1
2
3
4
5
6
7
8
9
10
11
|
// version_prod.go
// +build prod
package main
import (
_ "embed"
)
//go:embed version.txt
var version string
|
1
2
3
4
5
|
$ go run .
Version "dev"
$ go run -tags prod .
Version "0.0.1"
|
Quine
1
2
3
4
5
6
7
8
9
10
11
12
13
|
package main
import (
_ "embed"
"fmt"
)
//go:embed quine.go
var src string
func main() {
fmt.Print(src)
}
|
当运行时,就可以将自己打印出来。
Web资源文件
可以网站所需要的所有静态文件或者模板包含在一个可执行文件中,甚至可以通过命令行参数,在读取磁盘文件和读取嵌入文件之间进行切换。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
|
package main
import (
"embed"
"io/fs"
"log"
"net/http"
"os"
)
func main() {
useOS := len(os.Args) > 1 && os.Args[1] == "live"
http.Handle("/", http.FileServer(getFileSystem(useOS)))
http.ListenAndServe(":8888", nil)
}
//go:embed static
var embededFiles embed.FS
func getFileSystem(useOS bool) http.FileSystem {
if useOS {
log.Print("using live mode")
return http.FS(os.DirFS("static"))
}
log.Print("using embed mode")
fsys, err := fs.Sub(embededFiles, "static")
if err != nil {
panic(err)
}
return http.FS(fsys)
}
|
03 注意事项
关于嵌入有些地方需要注意,首先必须要将包导入到任何使用embed命令的文件中。如没有导入包时:
1
2
3
4
5
6
7
8
9
10
11
12
|
package main
import (
"fmt"
)
//go:embed file.txt
var s string
func main() {
fmt.Print(s)
}
|
1
2
|
$ go run mian.go
main.go:7:3: //go:embed only allowed in Go files that import "embed"
|
其次,embed命令只能在包级别变量使用,不能用在函数或方法中。
更多:使用规则,详见文档:https://golang.org/pkg/embed/
相关代码在https://github.com/bytedaring/embed
04 参考资源
- Go 1.16 Release Notes
- embed Doc
- How to Use //go:embed
- Managing Go installations
- 来了来了!Go1.16 重磅发布