Socket.IO 发版流程详解:从 RELEASING 文档看 socket.io 的版本发布、npm Trusted Publishing 与客户端 Bundle 同步
本文围绕 packages/socket.io/RELEASING.md 中定义的 6 步发版流程展开,结合 monorepo 的 workspaces 结构、client-dist/ 静态资源的服务机制以及 .github/workflows/publish.yml 中的 CI 配置,完整解析 socket.io 是如何通过 Git tag 触发 GitHub Actions,并借助 npm trusted publishing 安全发布到 npm 的。读完后你将掌握一套可复用的 monorepo 版本发布方案:手动同步版本号与 changelog、用 tag 约定驱动 CI 自动发布、以及服务端包如何内嵌客户端浏览器 bundle。
一、发版流程总览:6 步完成一次发布
packages/socket.io 是 Socket.IO 的 Node.js 服务端主包(当前版本为 4.8.3,见 packages/socket.io/package.json),同时它也是 monorepo 中唯一“打包分发”的包:npm 包内不仅包含服务端编译产物,还内嵌了客户端浏览器 bundle。packages/socket.io/RELEASING.md 定义的发布流程共 6 步:
- 更新
package.json中的版本号; - 用
conventional-changelog -p angular更新CHANGELOG.md; - 把 client 项目产出的 bundle 拷贝到
client-dist/; - 提交
package.json、CHANGELOG.md、client-dist/三处变更; - 创建 tag
socket.io@x.y.z并推送,由 CI workflow 自动发布到 npm; - 在 GitHub 仓库创建 Release 页面。
前 4 步是维护者手动完成的内容准备,第 5、6 步分别对应自动化发布与面向用户的公告。整个流程没有任何“手动执行 npm publish”的环节——发布权完全交给 CI,这也是后文重点解释的部分。
1. 版本号与 CHANGELOG 的更新
package.json 的 version 字段是发布的唯一事实来源。以当前仓库为例,packages/socket.io/package.json 与 packages/socket.io-client/package.json 的版本均为 4.8.3——服务端包与客户端包保持锁步(lockstep)版本,这是第 3 步“拷贝 bundle”能够成立的前提:socket.io 服务端内嵌的客户端 bundle 必须来自同一版本的 socket.io-client。
changelog 通过 conventional-changelog -p angular 生成,即基于 conventional commits 规范并使用 angular 预设(feat/fix/perf 等类型映射到 Major/Minor/Patch)。生成结果写入 packages/socket.io/CHANGELOG.md,其顶部是一张版本与发布日期的索引表,例如:
| Version | Release date |
|----------------------------------|---------------|
| 4.8.3 (2025-12-23) | December 2025 |
| 4.8.2 (2025-12-22) | December 2025 |
| 4.8.1 (2024-10-25) | October 2024 |
这意味着日常开发中提交的 commit message 规范(feat:、fix: 等)直接决定了 changelog 的内容与版本号应升哪个层级。
2. 与客户端包的对应关系
socket.io 发布前必须同步 socket.io-client 的构建产物,这一步的细节见下节。值得注意的是 monorepo 的完整工作区清单:根 package.json 声明了 11 个 workspace(engine.io、engine.io-client、socket.io-parser、socket.io-adapter 等),但 RELEASING.md 只覆盖 socket.io 这一个包。从源码结构看,其余包的发布依赖同一个 tag 触发机制(tag 命名 <包名>@<版本>,如 socket.io-client@x.y.z),packages/socket.io-client/RELEASING.md 额外要求:更新 support/package.esm.json 中的版本号、执行 npm run compile 与 npm run build、提交 dist/ 产物,最后还需把 bundle 同步到独立的 CDN 仓库供 cdn.socket.io 使用。
二、client-dist/:服务端包为什么内嵌客户端 bundle
RELEASING.md 第 3 步“Copy the bundles from the client project to client-dist/”看似简单,却揭示了 socket.io 的一个核心设计:服务端默认帮浏览器分发客户端 JS,因此 socket.io npm 包必须携带预构建的客户端 bundle。
当前仓库的 packages/socket.io/client-dist/ 目录包含 4 个 bundle(外加 source map):
socket.io.js/socket.io.min.js:UMD 格式,可直接用<script>标签引入,全局变量名为io;socket.io.esm.min.js:ES Module 版本;socket.io.msgpack.min.js:带 MessagePack 序列化支持的压缩版。
1. serveClient:bundle 的实际消费路径
服务端通过 serveClient 选项决定是否为客户端分发这些静态文件。从 packages/socket.io/lib/index.ts 可以看到实现细节:
- 构造函数中
this.serveClient(false !== opts.serveClient)(index.ts#L325)——默认开启,只有显式传serveClient: false才关闭; - 开启时,静态文件路由指向
path.join(__dirname, "../client-dist/", filename)(index.ts#L577),也就是包内client-dist/目录; - 若
serveClient为 false,则不注册静态文件处理器(测试用例见 packages/socket.io/test/server-attachment.ts#L154:new Server(srv, { serveClient: false }))。
这正是 RELEASING.md 第 3 步存在的原因:每次发版前必须把 socket.io-client 新构建的 bundle 拷贝进 client-dist/,否则用户 npm install socket.io 后,服务端通过 /socket.io/socket.io.js 等路径分发的将仍是旧版本客户端。而 packages/socket.io/package.json 的 files 字段确认了 client-dist/ 会随 npm 包一起发布:
"files": [
"dist/",
"client-dist/",
"wrapper.mjs",
"!**/*.tsbuildinfo"
]
2. 客户端 bundle 从哪里来
socket.io-client 的 build 脚本(见 packages/socket.io-client/package.json#L59)用 Rollup 串行执行三个配置,产出上述文件:
rollup -c support/rollup.config.umd.js \
&& rollup -c support/rollup.config.esm.js \
&& rollup -c support/rollup.config.umd.msgpack.js
以 packages/socket.io-client/support/rollup.config.umd.js 为例,它定义了两个输出:
devBundle:输入./build/esm-debug/browser-entrypoint.js,输出./dist/socket.io.js(UMD,全局名io,带 sourcemap);prodBundle:输入./build/esm/browser-entrypoint.js,输出./dist/socket.io.min.js,经 Terser 压缩。
两个 bundle 都会写入版本 banner,其中的版本号直接读取 ../package.json 的 version 字段:
const version = require("../package.json").version;
const banner = `/*!
* Socket.IO v${version}
* ...
*/`;
这解释了为什么客户端发版(packages/socket.io-client/RELEASING.md)必须先更新两个 package.json(主包 + support/package.esm.json)再执行 npm run build——bundle 内嵌的版本号、files 声明的 dist/ 目录(packages/socket.io-client/package.json#L13-L16)都以版本同步为前提。
3. ESM 互操作:wrapper.mjs
socket.io 是 CommonJS 包("type": "commonjs"),但对 ESM 导入提供了 packages/socket.io/wrapper.mjs 作为垫片:
import io from "./dist/index.js";
export const {Server, Namespace, Socket} = io;
package.json 的 exports 字段将 import 条件指向 ./wrapper.mjs、require 指向 ./dist/index.js(packages/socket.io/package.json#L28-L33)。该 wrapper 同样在 files 声明中,是发布产物的一部分。
三、tag 约定与 CI 自动发布
RELEASING.md 第 5 步是整套流程的自动化核心:
Create the tag
socket.io@x.y.zand push it to the GitHub repository. The workflow.github/workflows/publish.ymlwill safely publish the package to npm using trusted publishing.
tag 采用 <包名>@<版本> 格式(例如 socket.io@4.8.3),推送后触发 .github/workflows/publish.yml。该 workflow 的关键配置如下:
on:
push:
tags:
# expected format: <package>@<version> (example: socket.io@1.2.3)
- '**@*'
jobs:
publish:
runs-on: ubuntu-latest
permissions:
contents: read
id-token: write # 生成 OIDC token,用于 npm trusted publishing
steps:
- name: Checkout repository
uses: actions/checkout@v6
- name: Use Node.js 26
uses: actions/setup-node@v6
with:
node-version: 26
registry-url: 'https://registry.npmjs.org'
cache: npm
- name: Install dependencies
run: npm ci
- name: Compile each package
run: npm run compile --workspaces --if-present
- name: Publish package
run: npm stage publish --workspace=${GITHUB_REF_NAME%@*} --access public
逐行解析:
- 触发条件
**@*:任何形如xxx@yyy的 tag 都会触发,一个 workflow 文件服务 monorepo 中所有包;tag 中的@前的部分就是目标包名; permissions: id-token: write:这是 npm trusted publishing(OIDC)的必要权限,使 Actions 能向 npm 换取临时身份,全程无需在仓库中保存NPM_TOKEN;contents: read说明发布流程只需要读代码,最小权限;npm run compile --workspaces --if-present:在 monorepo 根目录对每个声明了compile脚本的 workspace 执行编译。对socket.io而言,packages/socket.io/package.json#L47 定义"compile": "rimraf ./dist && tsc";对socket.io-client则是清理build/后做 CJS + ESM 双份 tsc 编译并执行postcompile.sh(packages/socket.io-client/package.json#L54)。之所以要编译,是因为dist/产物不入库——CI 从源码现场构建,保证发布的字节码与 tag 指向的提交完全一致;npm stage publish --workspace=${GITHUB_REF_NAME%@*} --access public:${GITHUB_REF_NAME%@*}是 bash 参数展开,剥去 tag 名@及之后的部分,即从socket.io@4.8.3提取出socket.io,作为--workspace参数只发布这一个包。前缀npm stage publish表明这里使用的是 npm staged publishing(工作流文件头部注释也引用了 npm 的 trusted publishing 与 staged publishing 官方文档):包先被发布到暂存区,只有当该版本的所有产物(tarball、元数据等)都就位后才会对外可见,避免了传统npm publish可能出现的“半成品版本”;- 注意该步骤没有指定版本号——npm 以 tag 触发时包内
package.json的version字段为准。这就是 RELEASING.md 第 1 步(先改package.json再打 tag)与第 5 步 tag 名必须一致的原因:如果socket.io@4.8.3这个 tag 指向的提交里package.json仍写着4.8.2,发布结果会是一个版本名与 tag 不符的包,因此“版本号更新 → 提交 → 打 tag”的顺序不可颠倒。
适用前提与限制
- 该流程依赖仓库是 monorepo(npm workspaces),
--workspaces --if-present是根级命令;单包仓库需要相应简化; - trusted publishing 要求预先在 npm 控制台为 GitHub 仓库配置 publisher(仓库、workflow 文件、环境绑定),本仓库中看不到这部分配置(它在 npm 侧),因此文中“会自动发布”的结论以 RELEASING.md 的官方说明为准;
- CI 使用 Node.js 26 执行发布,而包自身的
engines声明(如 packages/socket.io/package.json#L81-L83 的node >=10.2.0)面向的是最终用户,两者不冲突但含义不同。
四、第 4 步提交的三个文件及其一致性
第 4 步要求提交 package.json、CHANGELOG.md、client-dist/ 三个路径的变更,三者构成一个自洽的版本快照:
| 提交内容 | 作用 | 一致性要求 |
|---|---|---|
package.json |
npm 元数据:name、version、files、exports、dependencies |
version 必须与将创建的 tag 中版本一致 |
CHANGELOG.md |
面向用户的变更说明,由 conventional-changelog 生成 | 新版本的 changelog 小节(如 # 4.8.3 (2025-12-23))需与 version 对应 |
client-dist/ |
服务端分发的客户端 bundle | 必须来自同版本的 socket.io-client 构建产物,bundle banner 中的版本号应一致 |
三者任一不同步都会产生问题:package.json 与 tag 不一致会导致发布版本错位;CHANGELOG.md 缺更新则 Release 说明不完整;client-dist/ 落后则出现“服务端 4.8.3 分发 4.8.2 客户端 bundle”的版本漂移,且由于 serveClient 默认开启(packages/socket.io/lib/index.ts#L325),这种漂移会静默影响所有依赖服务端分发客户端的项目。
五、第 6 步:创建 GitHub Release
流程的最后一步是在仓库的 Releases 页面手动创建 Release,向用户公告新版本及其变更内容。这一步不触发任何自动化,纯粹是发布流程的“面向人”的收尾;它与第 5 步的 tag 天然关联——Release 通常锚定在 socket.io@x.y.z 这个 tag 上,正文内容一般直接取自本次更新生成的 CHANGELOG.md 小节。
至此,一次完整发版的闭环是:规范提交(conventional commits)→ 手动升版本、生成 changelog、同步 client bundle → 提交三文件 → 打 <包名>@<版本> tag → CI 编译 + npm stage publish 经 trusted publishing 发布 → 创建 Release 公告。整个过程发布密钥零落地(OIDC 换取临时身份)、发布内容可追溯到具体 tag 指向的提交,是典型的 monorepo 安全发布范式。
六、可复用的发版要点清单
结合本文对 packages/socket.io/RELEASING.md 与配套 CI/源码的分析,可提炼出通用要点:
- tag 命名即路由:
<包名>@<版本>格式让单个 publish workflow 服务所有包,${GITHUB_REF_NAME%@*}提取包名,避免为每个包维护独立 workflow; - 产物不入库、CI 现编译:
dist/、build/均不在提交范围(files字段只约束 tarball 内容),prepack/CI 中的compile脚本保证发布的产物来自 tag 指向的确切提交(如 packages/socket.io/package.json#L53 的"prepack": "npm run compile"); - 多文件版本同步点:主包有“
package.json+ 内嵌 bundle”两个同步点(对应第 3 步),客户端包另有support/package.esm.json这个 ESM 子包的版本副本,遗漏任一都会造成包内版本不一致; - changelog 自动化:
conventional-changelog -p angular将 commit 规范转化为发布说明,维护者无需手写; - 安全发布:trusted publishing + staged publishing 组合,CI 仅持
id-token: write权限即可完成npm stage publish,仓库无需持久化任何 npm 凭据。
若需进一步了解各环节的实现,可重点阅读:发版文档 packages/socket.io/RELEASING.md 与 packages/socket.io-client/RELEASING.md、发布工作流 .github/workflows/publish.yml、服务端静态分发逻辑 packages/socket.io/lib/index.ts(serveClient 相关段落约 L325、L541-L577)、客户端 bundle 构建 packages/socket.io-client/support/rollup.config.umd.js,以及 ESM 互操作垫片 packages/socket.io/wrapper.mjs。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0623
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00