# 在 Go 当中嵌入父目录中的文件

- Link: https://www.ixiqin.com/2022/10/02/in-the-embedded-in-the-parent-directory-of-the-file/
- Published: 2022-10-02T06:00:00+08:00
- Author: 白宦成

文章目录

 [Toggle](https://www.ixiqin.com/2022/10/02/in-the-embedded-in-the-parent-directory-of-the-file/#)

 * [为什么会需要嵌入父目录中的文件？](https://www.ixiqin.com/2022/10/02/in-the-embedded-in-the-parent-directory-of-the-file/#%e4%b8%ba%e4%bb%80%e4%b9%88%e4%bc%9a%e9%9c%80%e8%a6%81%e5%b5%8c%e5%85%a5%e7%88%b6%e7%9b%ae%e5%bd%95%e4%b8%ad%e7%9a%84%e6%96%87%e4%bb%b6%ef%bc%9f)
 * [错误的尝试1：使用 .. 来引入](https://www.ixiqin.com/2022/10/02/in-the-embedded-in-the-parent-directory-of-the-file/#%e9%94%99%e8%af%af%e7%9a%84%e5%b0%9d%e8%af%951%ef%bc%9a%e4%bd%bf%e7%94%a8_%e6%9d%a5%e5%bc%95%e5%85%a5)
 * [错误的尝试2: 使用 go:generate 来执行复制](https://www.ixiqin.com/2022/10/02/in-the-embedded-in-the-parent-directory-of-the-file/#%e9%94%99%e8%af%af%e7%9a%84%e5%b0%9d%e8%af%952_%e4%bd%bf%e7%94%a8_go_generate_%e6%9d%a5%e6%89%a7%e8%a1%8c%e5%a4%8d%e5%88%b6)
 * [正确的做法](https://www.ixiqin.com/2022/10/02/in-the-embedded-in-the-parent-directory-of-the-file/#%e6%ad%a3%e7%a1%ae%e7%9a%84%e5%81%9a%e6%b3%95)
 * [参考阅读](https://www.ixiqin.com/2022/10/02/in-the-embedded-in-the-parent-directory-of-the-file/#%e5%8f%82%e8%80%83%e9%98%85%e8%af%bb)

自 Go 1.16 版本开始，Go 提供了将二进制文件打包进入到 Binary 文件当中的机制:``//go:
embed`。不过，我看到的示例大多数都是嵌入当前文件夹下的子文件夹的示例。并没有嵌入
父一级文件夹的示例。于是，我便开始研究起来。

## 为什么会需要嵌入父目录中的文件？

这是因为不同的项目的构建规则不同。一些小型项目可能只有一个 `main.go` 或同级目录下
几个其他的 go source 文件即可，但对于更大型的项目，合理的项目拆分是有助于帮助提升
项目的可维护性的。

以我为例，我的目录结构如下：biz 目录下是我的核心逻辑，也是我日常写代码的地方；conf
则放置了各种配置，比如各种 API Key，ral 则是网络访问层，比如我比较喜欢用 gin 来做
底层的网络访问层；script 放置了开发所需要的各种脚本文件；static 则放置了前端所需要
的 JS / CSS 文件。在开发一些偏内容向的页面时，我会习惯使用 Server Side Render，所以
我在 biz 目录下还有一个 template 目录。而主要的逻辑则放在 handler。

    ```php
    ├── biz
    │   ├── dal
    │   ├── handler
    │   ├── logic
    │   ├── service
    │   └── template
    ├── conf
    ├── ral
    │   └── gin
    ├── script
    └── static
        └── public
    Code language: PHP (php)
    ```

在日常开发时，一个非常常见的操作是在 handler 里处理基本的逻辑，并将 template 中的
模板渲染出来，并返回给用户，这个时候就需要在 handler 里使用上一级目录的文件了。

## 错误的尝试1：使用 `..` 来引入

作为文件系统的常规配置，我自然知道可以通过 `..` 来代表上一级目录，因此我试着使用
如下的语法来引入文件

    ```shcb-line-numbers
    //go:embed ../template/*
    Code language: JSON / JSON with Comments (json)
    ```

结果报了个错误 `invalid pattern syntax`。看得出来，是因为我的语法出现了问题。

通过搜索，我找到了这个 [Github issue](https://github.com/golang/go/issues/46056)，
才得知，由于避免跨模块的访问，go embed 阻止了 `..` 语法的使用

> Because embed.Files implements fs.FS, it cannot provide access to files with names
> beginning with .., so files in parent directories are also disallowed entirely,
> even when the parent directory named by .. does happen to be in the same module.
> https://go.googlesource.com/proposal/+/master/design/draft-embed.md

## 错误的尝试2: 使用 `go:generate` 来执行复制

在上面的 issue 当中，我注意到还有另外一个方式，可以实现类似的效果。可以借助 `go:
generate`来实现在执行 `go generate` 方法时，复制文件到本地，这样就可以实现在 Build
二进制文件之前，把文件复制到本地，即规避了跨模块的问题，也避免了将 template 文件
放在本地，污染代码目录的问题。

    ```javascript
    //go:generate cp -r ../../assets ./local-asset-dir
    //go:embed local-asset-dir
    var assetFs embed.FS
    Code language: JavaScript (javascript)
    ```

这个方法在运行的时候没有效果，不过在其他人可能是有效果的（我猜是因为我开发时很少
直接用 go generate，都是直接 go run 了)。不过，这个方法也有个问题，因为是写在代码
本身当中的，很有可能后续因为某些原因，这些代码被误删除或修改了，导致整个系统的运行
不正常。而散落在各文件的描述也会使得修复十分复杂。

## 正确的做法

在看这个 issue 的最后，我注意到了正确的用法，在[另外一个 issue ](https://github.com/golang/go/issues/41191#issuecomment-686621090)
当中，开发者 tomasf 提到了，我们应该在 asset 目录下创建一个 embed.go 来完成 embedFs
的建立，并在其他文件直接引入这个文件，完成定义。

> You can create a embed.go file in your assets directory and make the assets available
> as it’s own package to the rest of your program.
> tomasf

这个说法一出，豁然开朗！尝试一下，果然有效。我在 template 目录下创建了一个 `embed.
go` 文件，并添加了如下代码。

    ```wp-block-preformatted
    package templateimport "embed"//go:embed *var TemplateFs embed.FS
    ```

并在另外一个文件当中使用`template.TemplateFs.ReadFile("index.tmpl")` 来完成模板文件
的引用。这样既不违背 golang 的跨模块，也不会使得代码不可维护，非常好。

## 参考阅读

 * https://go.googlesource.com/proposal/+/master/design/draft-embed.md
 * https://github.com/golang/go/issues/46056
 * https://github.com/golang/go/issues/41191#issuecomment-686621090
 * https://blog.carlmjohnson.net/post/2016-11-27-how-to-use-go-generate/
 * https://blog.carlmjohnson.net/post/2021/how-to-use-go-embed/

## Comments

### By: 果果爸

- Link: https://www.ixiqin.com/2022/10/02/in-the-embedded-in-the-parent-directory-of-the-file/#comment-11220
- Published: 2025-02-06T09:41:34+08:00

不错 确实是个比较优雅的办法
