socket.io-client 发布流程详解:从版本号管理到 npm 可信发布与 CDN 分发
【免费下载链接】socket.ioBidirectional and low-latency communication for every platform项目地址: https://gitcode.com/gh_mirrors/so/socket.io
本文以 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 给出的完整发布步骤为:
- 更新
package.json中的版本号; - 更新
support/package.esm.json中的版本号; - 执行
conventional-changelog -p angular更新CHANGELOG.md; - 执行
npm run compile编译 TypeScript 源码; - 执行
npm run build生成浏览器用的 bundle; - 提交
package.json、support/package.esm.json、CHANGELOG.md和dist/目录的变更; - 创建形如
socket.io-client@x.y.z的 tag 并推送,由 CI 工作流 .github/workflows/publish.yml 通过 Trusted Publishing 安全地把包发布到 npm; - 创建一个 GitHub Release;
- 把 bundle 拷贝到官方 CDN 仓库,使其可以在
cdn.socket.io/域名下被直接引用。
下面结合仓库中的真实文件,逐步展开每个环节的实现细节。
为什么要维护两个版本号:package.json与support/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.js,require条件下指向./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 Fixes、Features、Dependencies等分组,并附带 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.1、ws@~8.18.3),帮助应用方判断是否需要升级。
编译阶段:npm run compile到底做了什么
第四步的npm run compile对应 package.json 中的脚本定义:
"compile": "rimraf ./build && tsc && tsc -p tsconfig.esm.json && ./postcompile.sh"它依次做四件事:
rimraf ./build:清理旧产物,保证可重现构建;tsc:按 tsconfig.json 将lib/编译为 CommonJS,outDir为build/cjs/,target为es2018(注释注明对应 Node.js 10,与包声明的"engines": { "node": ">=10.0.0" }一致),declaration: true会产出.d.ts;tsc -p tsconfig.esm.json:按 tsconfig.esm.json 再以module: "esnext"编译一遍到build/esm/;./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.json的exports中: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.js 从build/esm/index.js打包出dist/socket.io.esm.min.js(format: "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.json的files字段为["dist/", "build/"],说明 npm 包最终同时携带 Node 产物(build/cjs、build/esm、build/esm-debug)与浏览器 bundle(dist/),这也解释了为什么文档第 6 步要求把dist/目录一并提交进仓库——bundle 是预先构建好入库的,tag 触发发布时 CI 只需重新编译校验即可。
提交与打 tag:触发自动发布的钥匙
完成前六步并提交后,第 7 步是创建 tagsocket.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"模式。
工作流的执行步骤依次是:
actions/checkout@v6检出代码;actions/setup-node@v6安装 Node.js 26 并指向registry.npmjs.org(注意 ci-socket.io-client.yml 中日常 CI 用 Node.js 24,发布用 26);npm ci按 lockfile 精确安装依赖;npm run compile --workspaces --if-present编译全部工作区——因为 package.json 根部的workspaces列出了socket.io-client及其依赖链(engine.io-parser、engine.io-client、socket.io-parser等);- 最终执行:
npm stage publish --workspace=${GITHUB_REF_NAME%@*} --access public${GITHUB_REF_NAME%@*}是 Bash 参数展开,去掉 tag 名中第一个@及其之后的部分:tagsocket.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-parser、engine.io-client、socket.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 中逐一对照验证。
【免费下载链接】socket.ioBidirectional and low-latency communication for every platform项目地址: https://gitcode.com/gh_mirrors/so/socket.io
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考