构建vue

将vue项目建在 go 项目内的ui文件夹内,例如在ui文件夹内,pnpm build 生成的生产文件内容位于 ui/web/db-compare-ui/dist 文件夹内。

代码

// vue 项目构建的目录及所有内容都使用 embed.FS 内嵌到 go 程序体内
//go:embed ui/web/db-compare-ui/dist/**
//go:embed ui/web/db-compare-ui/dist/assets/**
var vue embed.FS

// 初始化 vue 路由
// h hertz的路由器
// contextPath 访问URL地址的根路径,通常是 /
// fs 已内嵌的 embed.FS 虚拟文件系统
// rootDir vue静态文件在 embed.FS 的根路径,通常是 xxx/xxx/dist 这样的相对路径
func InitVueRouter(h *server.Hertz, contextPath string, fs embed.FS, rootDir string) {
	h.StaticFS(contextPath, &app.FS{
		Root:               rootDir,                // 根目录
		IndexNames:         []string{"index.html"}, // 索引文件
		GenerateIndexPages: true,                   // 生成索引页面
		Compress:           false,                  // 压缩
		AcceptByteRange:    false,                  // 接受字节范围
		PathRewrite:        nil,                    // 路径重写
		PathNotFound: func(ctx context.Context, c *app.RequestContext) {
			path := string(c.Path())
			// 这个函数是路径找不到绘执行这个,例如 /login /home
			// css js 文件会在 Root 文件 里面找
			// 下面匹配路径
			switch {
			case strings.HasSuffix(path, ".js"):
				return
			case strings.HasSuffix(path, ".css"):
				return
			default:
				// 必须有这一步 react vue 项目 不是 '/'路径 刷新后找不到 报 404
				// 上面匹配 js css 可以不要,样式文件 会在 Root 文件 里面找,找不到会执行上面的函数
				data, err := fs.ReadFile(rootDir + "/index.html") // 读取react vue 项目的 index.html
				if err != nil {
					return
				}
				c.Data(200, "text/html; charset=utf-8", data)
			}
		}, // 路径未找到
		CacheDuration:        0,  // 缓存持续时间
		CompressedFileSuffix: "", // 压缩文件后缀
	})
}

func main() {
	h := server.Default(server.WithHostPorts("0.0.0.0:8080"))
	log.Println("Starting server at port 8080")
	// 构建 vue 路由
	InitVueRouter(h, "/", vue, "ui/web/db-compare-ui/dist")
	h.Spin()
}

补充更新

实测发现 h.StaticFS 更多的还是用于本地文件系统,而非内存嵌入的 embed.FS

实际上使用 h.NoRoute 更为理想,即参考的第二个链接。我使用 h.NoRoute 方式进行了改造,新的代码如下:


//go:embed ui/web/db-compare-ui/dist/**
var vueFS embed.FS

func InitVueRouter(h *server.Hertz, contextPath string) {
	path := contextPath
	if path == "" {
		path = "/"
	}
	processVueRouter(h, path, vueFS, "ui/web/db-compare-ui/dist")
}

func processVueRouter(h *server.Hertz, contextPath string, vfs embed.FS, dir string) {
	h.NoRoute(func(ctx context.Context, c *app.RequestContext) {
		path := string(c.Path())
		// 如果设置了 contextPath 的话,需要将该路径去除后得到根目录下的真实路径
		if contextPath != "" && contextPath != "/" {
			path = strings.Replace(path, contextPath, "", -1)
		}
		log.Printf("Request path: %s", path)

		if path == "" || strings.HasSuffix(path, "/") {
			// 对于vue应用通常使用index.html作为入口
			homePage := RootDir + "/index.html"
			data, err := vfs.ReadFile(homePage) // 读取react vueFS 项目的 index.html
			if err != nil {
				log.Printf("Error reading %s: %v", homePage, err)
				return
			}
			c.Data(200, "text/html; charset=utf-8", data)
		} else {
			// 请求的是其他文件
			f := RootDir + path
			data, err := vfs.ReadFile(f) // 读取embed.FS内的文件
			if err != nil {
				log.Printf("Error reading %s: %v", f, err)
				return
			}
			// 获取文件扩展名
			ext := filepath.Ext(path)
			// ext = .js .css 等文件扩展名
			// GetContentType是一个根据文件扩展名返回 http 的 Content-Type 的函数
			contentType := GetContentType(ext)
			if err != nil {
				log.Printf("Error reading %s: %v", ext, err)
				return
			}
			if len(contentType) == 0 {
				// 如果是未识别的 Content-Type 类型 返回默认的 application/octet-stream
				c.Data(200, "application/octet-stream", data)
			} else {
				c.Data(200, contentType, data)
			}
		}
	})
}

参考