Go 内嵌网页

参考项目:D:\code\go-homework

记住这条链路:前端 build → Go embed → fs.Sub → HTTP 提供网页 → go build → 运行程序、浏览器访问。

1. 目录结构

myapp/
├── go.mod
├── main.go
└── frontend/
    ├── package.json
    ├── src/
    └── dist/          # pnpm build 生成
        ├── index.html
        └── assets/

2. 前端调用同源接口

fetch('/api/hello')

生产环境不要写死 http://localhost:8080。网页由哪个地址打开,相对路径就请求那个地址。

开发时前端和 Go 分别启动,用 Vite 代理:

// vite.config.ts:合并到现有配置中
server: {
  proxy: {
    '/api': 'http://localhost:8080',
  },
},

3. Go 嵌入并提供网页

下面使用标准库,方便直接复用。现有项目用 Gin 时原理相同。

package main

import (
    "embed"
    "io/fs"
    "log"
    "net/http"
)

// 路径相对于当前 Go 文件,编译前必须存在构建产物。
//go:embed frontend/dist
var assets embed.FS

func main() {
    // 去掉 frontend/dist 前缀,让 index.html 成为文件系统根目录下的文件。
    frontend, err := fs.Sub(assets, "frontend/dist")
    if err != nil {
        log.Fatal(err)
    }

    mux := http.NewServeMux()

    // API 和网页由同一个服务提供。
    mux.HandleFunc("/api/hello", func(w http.ResponseWriter, r *http.Request) {
        w.Header().Set("Content-Type", "application/json")
        w.Write([]byte(`{"message":"hello"}`))
    })
    mux.HandleFunc("/api/", http.NotFound)

    // 从嵌入资源读取网页,不依赖运行目录下的 dist 文件夹。
    mux.Handle("/", http.FileServer(http.FS(frontend)))

    log.Println("打开 http://localhost:8080")
    log.Fatal(http.ListenAndServe("127.0.0.1:8080", mux))
}

这个例子只监听本机。部署到服务器、需要其他机器访问时,按需改为 :8080

前端使用 Hash 路由(如 /#/todos)可以直接配合上述代码。使用 History 路由(如 /todos)时,需要额外为页面路由返回 index.html;不要把不存在的 API、JS、CSS 也返回成首页。

4. 按顺序构建

首次创建项目时,在根目录执行一次 go mod init example.com/myapp

每次发布,在项目根目录执行:

cd frontend
pnpm install
pnpm build
cd ..
go build -ldflags="-s -w" -o myapp.exe .

必须先构建前端,再编译 Go。 Go 编译时会把当时的 dist 内容装进程序;前端改完后,必须重新执行这两步。

5. 运行和交付

.\myapp.exe

浏览器打开 http://localhost:8080

网页资源随可执行文件交付,运行时不需要 Node.js 或单独的 dist 目录。数据库、上传文件、运行配置等仍按业务需求单独处理。

对照现有项目

通用示例 go-homework 中的位置
frontend/dist static/fe-project/dist
embedfs.Sub main.go
HTTP 提供网页 routers/index.gofrontendHandler
先前端、后 Go 的构建顺序 taskfile.ymltask build

一句话记忆:embed 装进去,fs.Sub 去前缀,HTTP 送出来;先打前端,再编 Go。

优化

对你这个 Gin 项目,最贴合的是 gin-contrib/static,配合 Go 自带的 embed 使用。

方案 做什么 适用场景
gin-contrib/static 把嵌入资源接到 Gin,省去自己写静态文件读取逻辑 你现在这种“运行 exe,浏览器访问”
Wails 整合前端构建、Go 编译、资源嵌入和桌面窗口 希望网页变成独立桌面软件
go.rice 通过生成 Go 代码或向二进制追加资源完成打包 需要这类特殊资源打包方式

gin-contrib/static,核心代码可以简化为:

import (
    "embed"

    "github.com/gin-contrib/static"
    "github.com/gin-gonic/gin"
)

//go:embed static/fe-project/dist
var assets embed.FS

func main() {
    r := gin.Default()

    frontend, err := static.EmbedFolder(
        assets, "static/fe-project/dist",
    )
    if err != nil {
        panic(err)
    }

    r.Use(static.Serve("/", frontend))

    // 在这里注册 /api 路由

    if err := r.Run(":8080"); err != nil {
        panic(err)
    }
}

安装:

go get github.com/gin-contrib/static

构建仍然是 pnpm build,再 go build。这个库负责提供静态网页,真正把网页装进可执行文件的仍然是 embed。History 路由的首页兜底需要另外处理。官方示例

你现在可以用 embed + gin-contrib/static;如果只是想少记代码,也可以把现有实现保存成项目模板,无需增加依赖。

posted @ 2026-09-05 23:56  丁少华  阅读(4)  评论(0)    收藏  举报