Featured image of post 如何把 Go 模块文档发布到 pkg.go.dev

如何把 Go 模块文档发布到 pkg.go.dev

推个 Go 库到 GitHub 不等于发布——pkg.go.dev 才是 Go 生态的官方文档站。本文讲清发布流程、文档规范与版本管理

写在前面

Go 生态里没有 npm registry / Maven Central 这种"集中仓库"——Go 模块直接通过 git 仓库地址拉取

1
go get github.com/lingcoder/go-fsm

但有一个东西扮演了"官方文档站"的角色——pkg.go.dev。它不存代码,只存文档——从你的 Go 源码自动生成。

如果你写了一个 Go 库想让人用,go get 能拉到不代表你"发布了"——pkg.go.dev 上有完整文档、版本列表、示例代码、依赖图,才是 Go 生态意义上的"发布"。

本文用我自己的 go-fsm 作为例子,把发布流程、文档规范、版本管理讲清楚。


一、pkg.go.dev 是什么

它是 Google 维护的 Go 模块文档中心:

  • 自动从 GitHub / GitLab 拉取公开 Go 模块
  • 解析源码生成 HTML 文档
  • 显示版本列表、license、依赖图、被引用数
  • 支持搜索、跳转、链接到源码

你不需要"提交"什么——只要模块能被 go get,pkg.go.dev 就会自动收录。


二、最小可发布的 Go 模块

1. 仓库结构

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
go-fsm/
├── go.mod
├── go.sum
├── LICENSE
├── README.md
├── doc.go          # 包级文档
├── fsm.go          # 主代码
├── fsm_test.go
└── examples/
    └── basic/
        └── main.go

2. go.mod

1
2
3
module github.com/lingcoder/go-fsm

go 1.21

module必须是仓库的 import path——pkg.go.dev 通过这个找到对应的 git 仓库。

3. doc.go:包级文档

 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
// Package fsm provides a lightweight, type-safe finite state machine
// implementation for Go applications.
//
// # Quick Start
//
// Define states and events:
//
//     const (
//         StatePending   = fsm.State("pending")
//         StateApproved  = fsm.State("approved")
//         StateRejected  = fsm.State("rejected")
//     )
//
//     m := fsm.New(StatePending).
//         On(EventApprove).From(StatePending).To(StateApproved).
//         On(EventReject).From(StatePending).To(StateRejected).
//         Build()
//
// # Features
//
//   - Type-safe state and event definitions
//   - Hooks before / after transitions
//   - Concurrent-safe by default
//   - Zero dependencies
package fsm

doc.go 里的 package 注释会成为 pkg.go.dev 主页的描述——它是用户对你库的第一印象

4. README.md

pkg.go.dev 会同时展示 README 与包级 doc——README 显示在首页 Overview 区,包级 doc 显示在 Documentation 区。两者各司其职:README 面向 GitHub / pkg.go.dev 访客做项目介绍,doc.go 面向 godoc / IDE 跳转做 API 文档。建议把 GitHub 风格的徽章、功能 GIF、安装命令放 README,把 API 用法和 example 放 doc.go


三、文档规范:让 godoc 漂亮

pkg.go.dev 的文档完全从 Go 源码注释生成——注释规范决定文档质量

1. 每个导出符号都要有注释

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
// State represents a state in the finite state machine.
type State string

// Event represents a transition trigger event.
type Event string

// FSM is a finite state machine instance.
type FSM struct {
    current State
    // ...
}

// New creates a new FSM with the given initial state.
func New(initial State) *FSM {
    return &FSM{current: initial}
}

注释必须以符号名开头——这是 godoc 的强约定。

2. 多行注释

1
2
3
4
5
6
7
8
9
// Trigger fires an event and transitions the FSM.
//
// If no transition is defined for the current state and event,
// it returns an ErrNoTransition error.
//
// Trigger is safe for concurrent use.
func (m *FSM) Trigger(e Event) error {
    // ...
}

空行分段,每段一个意思

3. 列表

1
2
3
4
5
// Available hooks:
//
//   - BeforeTransition: called before state changes
//   - AfterTransition:  called after state changes
//   - OnError:          called when transition fails

注意——列表项前要有 4 空格缩进,pkg.go.dev 才会渲染成 bullet。

4. 可执行示例

example_*.go 文件里的 Example* 函数会自动出现在 pkg.go.dev

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
// example_test.go
package fsm_test

import (
    "fmt"
    "github.com/lingcoder/go-fsm"
)

func ExampleFSM_Trigger() {
    m := fsm.New("pending").
        On("approve").From("pending").To("approved").
        Build()

    m.Trigger("approve")
    fmt.Println(m.State())
    // Output: approved
}

// Output: ... 是 Go 测试框架的语法——它既验证示例正确性,也是文档

5. 标题(Go 1.19+)

1
2
3
4
5
6
7
// # Configuration
//
// The FSM can be configured via options...

// # Performance
//
// The FSM is optimized for...

# 在注释里被识别为标题——pkg.go.dev 会渲染成大标题、生成目录。


四、版本管理:发布正式版本

1. 用 git tag 发布

1
2
git tag v1.0.0
git push origin v1.0.0

tag 必须是 vX.Y.Z 格式——Go modules 强制语义化版本。

2. 让 pkg.go.dev 收录新版本

打 tag 后,第一次访问 https://pkg.go.dev/github.com/lingcoder/go-fsm@v1.0.0 会触发proxy.golang.org 抓取——几秒后页面就有了。

或者用:

1
go get github.com/lingcoder/go-fsm@v1.0.0

也会触发同样的抓取。

3. v2+ 的特殊规则

重要:v2 及以上版本要在 module path 里加版本号:

1
2
3
v0.x.x / v1.x.x  → module path: github.com/lingcoder/go-fsm
v2.x.x           → module path: github.com/lingcoder/go-fsm/v2
v3.x.x           → module path: github.com/lingcoder/go-fsm/v3

发布 v2 时:

1
2
3
4
# go.mod
module github.com/lingcoder/go-fsm/v2

go 1.21

并打 tag v2.0.0。这是 Go 1.13 之后的强制规则——强迫"breaking change 必显式"


五、license 文件:影响排名

pkg.go.dev 会显示模块的 license——有 license 的模块在搜索结果里更靠前。最常用:

  • MIT —— 最宽松,工业首选
  • Apache 2.0 —— 带专利授权条款,大公司喜欢
  • BSD-3-Clause —— 简单、宽松

把对应的 LICENSE 文件放仓库根目录就行——pkg.go.dev 自动识别。

没 license 的代码 法律上等于"All rights reserved" ——别人不能用。开源前一定要加。


六、CI 集成:自动化发布

GitHub Actions 自动测试 + 发版:

 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
# .github/workflows/release.yml
name: release

on:
  push:
    tags: ['v*']

jobs:
  release:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-go@v4
        with:
          go-version: '1.21'

      - name: Test
        run: go test ./...

      - name: Vet
        run: go vet ./...

      - name: Trigger pkg.go.dev fetch
        run: |
          curl -sSf "https://proxy.golang.org/github.com/${{ github.repository }}/@v/${{ github.ref_name }}.info"

最后一步主动告诉 proxy.golang.org 拉取——比等用户访问快


七、提高搜索排名

pkg.go.dev 的搜索排名考虑:

  1. 被 import 数量——其他 Go 模块依赖你的越多,排名越高
  2. license 是否存在
  3. 文档完整度——所有导出符号都有注释
  4. 版本稳定——是否打过 v1+ tag
  5. README / doc.go 内容——关键词是否覆盖搜索词

策略:

  • 好搜的名字(关键词命中度高)
  • 早发 v1.0.0——pkg.go.dev 把 0.x 版本视为不稳定
  • 写好 doc.go——是搜索引擎的主要食粮
  • 写 example——示例丰富的库更受欢迎

八、常见问题

1. 推了 tag 但 pkg.go.dev 没显示

主动触发抓取:

1
curl "https://proxy.golang.org/github.com/your/repo/@v/v1.0.0.info"

或访问一次 https://pkg.go.dev/github.com/your/repo@v1.0.0

2. README 里的图片不显示

pkg.go.dev 会渲染 README,但不加载相对路径的本地图片——![logo](docs/logo.png) 这种引用在 pkg.go.dev 上看不到。要让图片正常显示:

  • 用 GitHub raw 的绝对 URL:![logo](https://raw.githubusercontent.com/owner/repo/main/docs/logo.png)
  • 或者把图片放在 GitHub Pages / 图床上引用

API 文档建议同时维护 README(项目介绍)和 doc.go(API 用法)——两者会分别在 Overview 区和 Documentation 区展示,互不替代。

3. 私有仓库怎么办

私有 module 不会在 pkg.go.dev 出现。要内部 doc:

  • 自托管 godoc -http=:6060
  • 公司内部用 pkgsite 自建

4. 删除已发布版本

不能删除。一旦 tag 发布到 proxy.golang.org,会被永久缓存——这是 Go modules 的"checksum 不可变"承诺。

发现版本有 bug:

  • 标记 retract(go.mod 里加 retract v1.0.5
  • 立刻发新版本 v1.0.6 修复
1
2
3
4
5
6
// go.mod
module github.com/lingcoder/go-fsm

go 1.21

retract v1.0.5  // bug, use v1.0.6+

retracted 版本仍能解析,但用户用 go get 时会得到警告。

5. examples 怎么组织

  • 简单示例 → example_test.go(pkg.go.dev 显示)
  • 完整 demo → examples/ 目录(GitHub 显示但 pkg.go.dev 不显示——所以 doc.go 里指向)

九、一份发布前 Checklist

发版前对照检查:

  • go.mod 的 module path 正确
  • doc.go 写好包级文档
  • 所有导出符号有注释(用 golint / revive 检查)
  • LICENSE 文件存在
  • README.md 包含安装、quickstart、license
  • 至少 3 个 Example 函数
  • 单元测试覆盖关键路径
  • CI 通过
  • 打 v1.0.0+ tag(或符合 v2+ 规则)
  • 推 tag 后访问 pkg.go.dev 验证

小结

把全文压一句:

Go 模块发布的『正式仪式』是 git tag + pkg.go.dev——但真正决定库能否被人用的是 doc.go 和 Example 函数的质量。

工程要点:

  • 注释以符号名开头,每个导出符号都有
  • doc.go 是包的脸面——花时间写
  • Example 函数比一万字文档有效
  • v1.0.0 是分水岭——别永远停在 0.x
  • v2+ 必改 module path

把这套做对,你的 Go 库就不只是"能 go get"——它有文档、有版本、有声誉,是 Go 生态的"正式公民"。

使用 Hugo 构建
主题 StackJimmy 设计