如何发布 Node 包到 npm?
npm 发包的核心流程 10 年没变,但 撤销规则 和 版本工具 换了 3 轮。
- 登录:
npm login+ 2FA OTP(2023 年起强制) - files:
package.json白名单 +.npmignore兜底 - 版本号:
npm version自动 bump,禁手改 - 撤销:24h 后禁止 unpublish
- 废弃:
npm deprecate比 unpublish 安全 - 工具:changesets 取代 standard-version
登录认证:起点仍然是 npm login
2016 年这节的 npm adduser 命令今天已经被标为 npm login 的别名,二者效果完全一样,直接用 npm login 就行:
npm login
Username: YOUR_USER_NAME
Password: YOUR_PASSWORD
Email: YOUR_EMAIL@domain.com
# One-time Password (OTP) ← 2023 年起强制
登录成功后凭据存 ~/.npmrc,验证当前身份:
npm whoami
注意:2023 年起 npm 给所有账户强制开启 2FA,登录和首次发包都需要 OTP(一次性密码)。这不是可选项,npm 会在第一次 npm publish 之前强制引导你配置。
files 字段控制发布范围
默认行为:npm 读 .npmignore,没有就 fallback 到 .gitignore。但 .gitignore 通常会忽略 dist/、lib/ 这些应该发出去的目录——继续 fallback 下去就 publish 不出能用的包。
正确做法是在 package.json 用 files 字段白名单声明:
{
"files": ["dist", "lib", "es"]
}
files 数组支持 minimatch glob,lib/**/*.js 也合法。node_modules/ 默认不打包,要发依赖请用 bundledDependencies。
.npmignore 写法跟 .gitignore 一致,但优先级高于 files——写在 .npmignore 里的文件即使在 files 里声明了也会被排除。
调试技巧:拿不准到底发了什么文件,用 npm pack --dry-run 列出 tarball 内容,比反复 publish + 安装调试快得多。
版本号:永远用 npm version,不要手改
x.y.z 三段语义见 SemVer,npm 提供自动 bump:
npm version patch # z++
npm version minor # y++ && z=0
npm version major # x++ && y=0 && z=0
每条命令自动做三件事:改 package.json 的 version、commit(如果项目是 git 仓库)、打 tag。不要在编辑器里手改版本号——跳过 git commit 会导致 tag 跟代码对不上。
最佳实践:版本号自动化搭配 conventional commits 提交规范,让 commit message 驱动版本号。
2016 年这篇推的 standard-version 今天仍然能用,但生态已经被两个新工具接棒:
- changesets:monorepo 首选,Vite、Vue、Next.js 在用
- release-please:Google 出品,单仓单包也好用
standard-version 的问题在于它只看 commit message,对 monorepo 里多个包各自独立发版的场景不友好。新项目直接上 changesets,老项目维持 standard-version 也不必急着迁移。
撤销 vs 废弃:理解 24 小时规则
这是 2026 年最容易踩坑的一节。npm 的 unpublish 规则经过几次收紧,现在严格执行:
| 时段 | 是否可 unpublish |
|---|---|
| 发布后 24 小时内 | 允许,npm unpublish pkg@version --force |
| 超过 24 小时 | 禁止,必须联系 npm support |
| 已存在其他依赖 | 永远禁止(避免破坏下游) |
撤销后再发同名包会进入 24 小时冷却:
npm ERR! code E403
npm ERR! xxx cannot be republished until 24 hours have passed.
最佳实践:绝大多数情况下你不需要 unpublish。发布错了用 npm deprecate:
npm deprecate YOUR_PKG@"< 0.2.3" "critical bug fixed in v0.2.3"
npm deprecate YOUR_PKG@"*" "this package has been renamed, use new-pkg instead"
deprecate 不删除任何东西,只是给安装者打一条警告。这是撤销的替代品,不是临时方案——npm 自己也推这个姿势。
重命名包
重命名的本质是废弃 + 发布新包,不要尝试迁移历史版本。推荐用 pkg-rename 一条命令搞定:
npx pkg-rename old-package-name --publish
它会:1) 拿到旧包最新版本,2) npm deprecate 旧包全部历史版本,3) npm publish 新包。
注意:发布到 @your-org 命名空间时必须加 --access public,否则 npm 默认当私有包发,免费账户会直接失败。
维护者管理
个人名下挂公司包是个长期隐 患——员工离职后包的管理就成了孤儿:
npm owner ls pkg-name # 列出维护者
npm owner add user pkg-name # 添加
npm owner rm user pkg-name # 删除
公司项目应该尽早转到组织名下,或者把多个维护者加进 owner 列表,避免单点故障。
常用命令速查
| 命令 | 用途 |
|---|---|
npm whoami | 当前登录用户 |
npm pack --dry-run | 预览 tarball,不实际打包 |
npm deprecate pkg@"<1.0.0" "msg" | 标记旧版本为废弃 |
npm owner ls pkg | 列出维护者 |
npm outdated | 检查依赖更新 |
npm home pkg | 打开包主页 |
npm repo pkg | 打开包仓库 |
npm version patch | bump 版本号 |
npm home 和 npm repo 这两个不用先安装包就能用,写 README 链接时省事。
References
- npm package.json — files —— npm 官方文档
- npm unpublish policy —— npm 撤销发布最新规则
- npm deprecate —— npm 官方文档
- changesets —— 版本号 + CHANGELOG 自动化
- release-please —— Google 出品的发版自动化
- SemVer 中文 —— 语义化版本规范