Skip to main content

如何发布 Node 包到 npm?

· 5 min read

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.jsonfiles 字段白名单声明:

{
"files": ["dist", "lib", "es"]
}

files 数组支持 minimatch globlib/**/*.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 今天仍然能用,但生态已经被两个新工具接棒:

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 patchbump 版本号

npm homenpm repo 这两个不用先安装包就能用,写 README 链接时省事。


References

  1. npm package.json — files —— npm 官方文档
  2. npm unpublish policy —— npm 撤销发布最新规则
  3. npm deprecate —— npm 官方文档
  4. changesets —— 版本号 + CHANGELOG 自动化
  5. release-please —— Google 出品的发版自动化
  6. SemVer 中文 —— 语义化版本规范