package.json 对比 go.mod:版本字段去哪了?
如果你从 JavaScript 转到 Go,有一件事会让你感到惊讶。
在 package.json 文件中,版本号就在最顶部。你可以直接读取它,可以在 pull request 中修改它,也可以搜索它。它是一个实实在在存在于代码中的事实。
现在打开一个 go.mod 文件。版本号并不在那里。
这不是一个错误,而是一种选择。
Go 不会为你的模块使用版本字段。相反,Go 使用 git tags。
在 Go 中设置版本,你需要这样做:
- git tag v1.2.3
- git push origin v1.2.3
git tag 是唯一的真理来源(single source of truth)。当有人运行 go get 时,Go 会查看你的仓库标签以找到正确的 commit。
这种设计有一个巨大的优势:版本永远不会指向错误的代码。在 npm 中,发布的代码和标记的源码可能会产生偏差。而在 Go 中,它们是同一回事,因为版本就是指向一个 commit 的指针。
然而,这会在多个方面改变你的工作流:
- 查找版本需要使用命令。你必须运行
git describe --tags,而不是查看文件。 - 版本升级(Version bumps)不会出现在代码审查中。推送 tag 不是代码变更,因此它不会出现在 pull request 中。
- 本地构建使用伪版本(pseudo-versions)。在你为 commit 打上标签之前,你看到的是一串很长的数字和哈希值,而不是像
v1.2.3这样整洁的版本号。 - 主版本号会改变你的导入路径(import path)。在 npm 中,你只需更改版本号但保持包名不变。在 Go 中,v2 及以上版本需要更改路径(例如
/v2)。这允许程序同时使用同一个库的 v1 和 v2 版本而不会发生冲突。
大多数其他生态系统都将版本保存在文件中:
- Node: package.json
- Rust: Cargo.toml
- Python: pyproject.toml
- Java: pom.xml
- .NET: .csproj
Go 与众不同。它仅依赖 git tags。
如果你希望你的 Go 二进制文件显示整洁的版本号,可以在构建过程中使用 ldflags 进行注入。许多开发者还会使用像 goreleaser 这样的工具来自动化 tag 和发布流程。
这两种模型代表了不同的优先级:
- 基于文件的版本易于阅读和审查。风险在于文件可能会与实际代码脱节。
- 基于标签的版本无法伪造。代价是它们更难查看和管理。
Go 选择标签模型是为了确保清单(manifest)始终与代码保持一致。
对于那些发布 Go 代码的开发者:在日常工作中,这种仅基于 Tag 的模型体验如何?你会改变它吗?
来源:https://dev.to/dalirnet/packagejson-vs-gomod-where-did-the-version-field-go-3301