首页
/ socket.io-client 发布流程详解:从版本号管理到 npm 可信发布与 CDN 分发

socket.io-client 发布流程详解:从版本号管理到 npm 可信发布与 CDN 分发

2026-09-04 21:08:48作者:滕妙奇

本文以 socket.io 官方仓库中的发布说明文档 packages/socket.io-client/RELEASING.md 为主线,逐步骤拆解 socket.io-client 客户端包的完整发布流程:双 package.json 版本号管理、Changelog 生成、TypeScript 编译与 Rollup 打包、Git tag 触发,以及基于 npm Trusted Publishing 的自动化发布和 CDN 产物同步。读完后你将理解一个真实生产级 monorepo 中客户端库"从改版本号到用户可在 npm / CDN 上使用"的全链路机制,并能对照源码验证每一步的实际实现。

发布流程总览:九步走

官方文档 RELEASING.md 给出的完整发布步骤为:

  1. 更新 package.json 中的版本号;
  2. 更新 support/package.esm.json 中的版本号;
  3. 执行 conventional-changelog -p angular 更新 CHANGELOG.md
  4. 执行 npm run compile 编译 TypeScript 源码;
  5. 执行 npm run build 生成浏览器用的 bundle;
  6. 提交 package.jsonsupport/package.esm.jsonCHANGELOG.mddist/ 目录的变更;
  7. 创建形如 socket.io-client@x.y.z 的 tag 并推送,由 CI 工作流 .github/workflows/publish.yml 通过 Trusted Publishing 安全地把包发布到 npm;
  8. 创建一个 GitHub Release;
  9. 把 bundle 拷贝到官方 CDN 仓库,使其可以在 cdn.socket.io/ 域名下被直接引用。

下面结合仓库中的真实文件,逐步展开每个环节的实现细节。

为什么要维护两个版本号:package.jsonsupport/package.esm.json

第一步和第二步看似重复,实则服务于不同产物。当前 packages/socket.io-client/package.json 的版本为 4.8.3,而 support/package.esm.json 内容极简:

{
  "name": "socket.io-client",
  "version": "4.8.3",
  "type": "module"
}

原因在于主 package.json 声明了 "type": "commonjs"(第 17 行),这决定了 Node.js 把包内 .js 文件按 CJS 解析。而客户端同时需要发布 ESM 产物——exports 字段显示,import 条件下 default 指向 ./build/esm/index.jsrequire 条件下指向 ./build/cjs/index.js,两者各自携带独立的 .d.ts 类型声明。若 build/esm/ 目录下的 .js 仍落在一个 "type": "commonjs" 的包根之下,ESM 语义就会被破坏。

postcompile.sh 脚本第 3 行给出了答案:

cp ./support/package.esm.json ./build/esm/package.json

即在编译后向 build/esm/package.json 复制一份带 "type": "module" 标记的子包描述文件,让 ESM 产物被 Node 正确识别。因此两个文件中的版本号必须保持一致——它们最终都出现在发布物中(前者是 npm 包元信息,后者是 ESM 产物的模块标记),这正是文档把它们列为两个独立步骤、并要求在步骤 6 中一并提交的原因。

用 Conventional Changelog 生成变更日志

第三步要求用 conventional-changelog -p angular 更新 CHANGELOG.md。当前文件呈现出该工具生成的典型格式:

  • 顶部是一张版本汇总表,列为 版本 / 发布日期 / UMD 压缩包大小(min+gzip),例如最新的 4.8.3 对应 14.4 KB
  • 每个版本一节,包含按 angular 提交类型归类的 Bug FixesFeaturesDependencies 等分组,并附带 commit 短哈希与 issue 链接,如 4.8.2 版本中的:
* **bundle** do not mangle the "_placeholder" attribute (bis)
* drain queue before emitting "connect"

采用 angular 预设意味着维护者的 commit message 遵循 Conventional Commits 规范(fix:feat:chore: 等前缀加 scope),变更日志才能被自动聚合。这对用户也有实际价值:Changelog 直接记录了依赖变更(如 engine.io-client@~6.6.1ws@~8.18.3),帮助应用方判断是否需要升级。

编译阶段:npm run compile 到底做了什么

第四步的 npm run compile 对应 package.json 中的脚本定义:

"compile": "rimraf ./build && tsc && tsc -p tsconfig.esm.json && ./postcompile.sh"

它依次做四件事:

  1. rimraf ./build:清理旧产物,保证可重现构建;
  2. tsc:按 tsconfig.jsonlib/ 编译为 CommonJS,outDirbuild/cjs/targetes2018(注释注明对应 Node.js 10,与包声明的 "engines": { "node": ">=10.0.0" } 一致),declaration: true 会产出 .d.ts
  3. tsc -p tsconfig.esm.json:按 tsconfig.esm.json 再以 module: "esnext" 编译一遍到 build/esm/
  4. ./postcompile.sh:做三项后处理。

postcompile.sh 的完整逻辑值得逐行看:

cp ./support/package.esm.json ./build/esm/package.json

cp -r ./build/esm/ ./build/esm-debug/

if [[ "$OSTYPE" == "darwin"* ]]; then
    sed -i '' -e '/debug(/d' ./build/esm/*.js
else
    sed -i -e '/debug(/d' ./build/esm/*.js
fi

# for backward compatibility with `const socket = require("socket.io-client")(...)`
echo -e '\nmodule.exports = lookup;' >> ./build/cjs/index.js
  • 复制 ESM 标记文件:即前述的 build/esm/package.json
  • 派生出 build/esm-debug/:从 build/esm/ 整目录复制一份,之后再从 build/esm/*.js 中用 sed 删除所有 debug( 调用行。于是 build/esm/ 成为剥离了 debug 包依赖的精简版(浏览器入口走这条路径),而 build/esm-debug/ 保留了调试日志。这一设计直接反映在主 package.jsonexports 中:import + node 条件指向 ./build/esm-debug/index.js,浏览器 default 指向 ./build/esm/index.js
  • CJS 尾部追加 module.exports = lookup;:脚本中的注释写明这是为了向后兼容 const socket = require("socket.io-client")(...) 这种直接调用默认导出的旧写法。

值得注意的是 prepack 脚本:"prepack": "npm run compile"。即使发布流程由 CI 执行,npm pack/publish 前也会自动重新编译一次,构成一道安全网。

构建浏览器 Bundle:npm run build 的三路输出

第五步 npm run build 执行三条 Rollup 命令(见 package.json 第 59 行):

"build": "rollup -c support/rollup.config.umd.js && rollup -c support/rollup.config.esm.js && rollup -c support/rollup.config.umd.msgpack.js"

UMD 双产物(开发版 + 压缩版)

support/rollup.config.umd.js 导出两个配置对象,分别产出:

产物 输入 后处理
dist/socket.io.js build/esm-debug/browser-entrypoint.js(保留 debug) Babel(@babel/preset-env + object-assign / classes 转换),带 sourcemap
dist/socket.io.min.js build/esm/browser-entrypoint.js(已去 debug) 额外经 Terser 压缩,mangle 时按正则 /^_/ 处理属性名并保留 _placeholder

两者都以 umd 格式输出、全局名为 io(因此页面上可用 io(url) 创建连接),文件头带有 banner:

/*!
 * Socket.IO v4.8.3
 * (c) 2014-2026 Guillermo Rauch
 * Released under the MIT License.
 */

其中版本号直接取自 package.json(配置第 6 行 require("../package.json").version)——再次印证了发布前必须先改版本号,否则 bundle banner 也会带错版本。reserved: ["_placeholder"] 这个细节并非偶然:CHANGELOG 4.8.2 条目专门记录了一次 "do not mangle the '_placeholder' attribute (bis)" 的修复,说明压缩保留规则是踩过坑后写死的。

ESM 压缩产物

support/rollup.config.esm.jsbuild/esm/index.js 打包出 dist/socket.io.esm.min.jsformat: "esm" + Terser),供现代打包器直接消费 ESM 版本。

MsgPack 变体

support/rollup.config.umd.msgpack.js 复用 UMD 压缩配置的 output,仅改输出文件名并用 @rollup/plugin-alias 做一次关键替换:

alias({
  entries: [
    {
      find: "socket.io-parser",
      replacement: "socket.io-msgpack-parser",
    },
  ],
})

即把标准 JSON 解析器 socket.io-parser 在打包时静态替换为 MsgPack 解析器,产出 dist/socket.io.msgpack.min.js,为需要二进制紧凑传输的用户提供免配置的 MsgPack 客户端。

发布物范围

package.jsonfiles 字段为 ["dist/", "build/"],说明 npm 包最终同时携带 Node 产物(build/cjsbuild/esmbuild/esm-debug)与浏览器 bundle(dist/),这也解释了为什么文档第 6 步要求把 dist/ 目录一并提交进仓库——bundle 是预先构建好入库的,tag 触发发布时 CI 只需重新编译校验即可。

提交与打 tag:触发自动发布的钥匙

完成前六步并提交后,第 7 步是创建 tag socket.io-client@x.y.z 并推送。这里的 <package>@<version> 格式不是约定俗成,而是发布工作流的硬契约:

.github/workflows/publish.yml 的触发条件为:

on:
  push:
    tags:
      # expected format: <package>@<version> (example: socket.io@1.2.3)
      - '**@*'

工作流的权限声明是关键:

permissions:
  contents: read
  id-token: write

文件开头的注释点明其依赖的两大 npm 机制:trusted publishing(可信发布,凭 GitHub 工作流身份签发 OIDC token 换取 npm 发布权)与 staged publishing(暂存发布,新注册包名的自动审核期)。因此整个仓库不需要也不存储任何 NPM_TOKEN,安全性高于传统的"塞一个 npm 密钥进 secret"模式。

工作流的执行步骤依次是:

  1. actions/checkout@v6 检出代码;
  2. actions/setup-node@v6 安装 Node.js 26 并指向 registry.npmjs.org(注意 ci-socket.io-client.yml 中日常 CI 用 Node.js 24,发布用 26);
  3. npm ci 按 lockfile 精确安装依赖;
  4. npm run compile --workspaces --if-present 编译全部工作区——因为 package.json 根部的 workspaces 列出了 socket.io-client 及其依赖链(engine.io-parserengine.io-clientsocket.io-parser 等);
  5. 最终执行:
npm stage publish --workspace=${GITHUB_REF_NAME%@*} --access public

${GITHUB_REF_NAME%@*} 是 Bash 参数展开,去掉 tag 名中第一个 @ 及其之后的部分:tag socket.io-client@4.8.4 → workspace 名 socket.io-client,再配合 npm stage publish 完成带暂存发布语义的正式 publish。也就是说,tag 名本身编码了"发哪个包、发什么版本"的全部信息,这是把多包 monorepo 的发布编排压缩到一条 shell 命令里的巧妙设计。

收尾两步:GitHub Release 与 CDN 同步

第 8 步是在官方 GitHub 仓库创建一个 Release,附上该版本的变更摘要——这也是 CHANGELOG.md 中各版本锚点(如 #483-2025-12-23)对应的用户可见入口。

第 9 步则面向 <script src> 直连场景:把 dist/ 下的 bundle 同步到独立的官方 CDN 仓库,使用户可以直接引用形如 https://cdn.socket.io/4.8.3/socket.io.min.js 的地址,无需经过 npm。这一步是纯文件分发,与 npm 发布相互独立——即使 npm 侧包处于 staged 审核期,CDN 产物依然可用。

对照 CI 理解本地验证

发布前置条件隐含在 CI 中:ci-socket.io-client.yml 展示了改动 packages/socket.io-client/** 等路径时触发的验证链——先依次编译上游依赖(engine.io-parserengine.io-clientsocket.io-parser),再编译本包,然后跑 npm test(默认 Node 端 mocha 用例,test:node 脚本使用 tsx 直接加载 test/index.ts);push 事件下还会以 BROWSERS=1 追加浏览器端测试。发布者在本地执行步骤 4、5 前跑通 npm test,等价于提前复现了 CI 的编译链路。

小结

socket.io-client 的发布流程可以用一条数据流概括:

改版本号(package.json + support/package.esm.json)
  → conventional-changelog -p angular 更新 CHANGELOG.md
  → npm run compile(rimraf + 双 tsc + postcompile.sh:esm 标记 / esm-debug / 去 debug / cjs 兼容)
  → npm run build(UMD 双产物 + ESM min + MsgPack 变体)
  → 提交元信息与 dist/
  → push tag socket.io-client@x.y.z
  → publish.yml:Node 26 + npm ci + 全工作区编译 + npm stage publish(可信发布,无 token)
  → GitHub Release + CDN 仓库同步

其工程要点值得借鉴:版本号在元信息与构建产物中多处出现,靠脚本而非人工保证一致性(banner、esm 子包均由版本号派生);发布凭证零落库(OIDC 可信发布);tag 命名即发布指令<package>@<version> 被 shell 展开直接解析出 workspace);npm 包与浏览器 bundle 双通道分发build/ 面向 Node,dist/ 面向打包器与 CDN)。以上全部细节均可在仓库的 packages/socket.io-client 目录及 .github/workflows 中逐一对照验证。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.12 K
2.72 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
528
590
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
904
1.82 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
854
1.34 K
docsdocs
暂无描述
Markdown
889
5.78 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.52 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.33 K
1.45 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
982
503
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
540
384