发布自己的 npm 包
本教程共 76 篇 · 第 74 篇 · 更新于 2026-07-25 · 约 6 分钟阅读
74. 发布自己的 npm 包
本节目标:package.json exports、ESM+CJS 双包和 npm publish 流程。
写代码给自己用和写代码给别人用,中间差了一个「包」。把工具函数、CLI 脚本或者小型框架打包发布到 npm,既能让社区受益,也能逼你把接口设计得更干净。这节我们走一遍完整的发布流程,重点讲双包发布(同时支持 ESM 和 CommonJS),这是 v24 时代避不开的课题。
初始化包
创建目录并初始化:
mkdir my-utils && cd my-utils
npm init
npm init 会问你包名、版本、描述等问题。包名必须是全局唯一的,发布前先用 npm view <name> 检查是否被占用。
npm view my-utils
# 如果返回 404,说明名字可用
目录结构与核心文件
一个现代 npm 包至少包含这些文件:
my-utils/
├── package.json
├── README.md
├── LICENSE
├── index.js // ESM 入口
├── index.cjs // CommonJS 入口
└── lib/
├── string.js
└── number.js
package.json
这是最关键的配置文件:
{
"name": "my-utils",
"version": "1.0.0",
"description": "A collection of utility functions",
"main": "./index.cjs",
"module": "./index.js",
"type": "module",
"exports": {
".": {
"import": "./index.js",
"require": "./index.cjs"
}
},
"files": [
"index.js",
"index.cjs",
"lib/"
],
"scripts": {
"test": "node --test"
},
"keywords": ["utils", "string", "number"],
"author": "Your Name <you@example.com>",
"license": "MIT",
"engines": {
"node": ">=18.0.0"
}
}
逐行解释重点字段:
"type": "module":本仓库默认 ESM,.js文件按 ESM 解析"main":CommonJS 环境下的入口(require('my-utils'))"module":打包工具(Webpack、Rollup)用的 ESM 入口提示"exports":真正的双包入口。import走./index.js,require走./index.cjs"files":白名单,只有列出的文件会打进 tarball,比.npmignore更可控"engines":声明最低 Node 版本,安装时如果环境不匹配会报警告
Warning如果不写
exports字段,用户用require('my-utils')时会直接读"main"对应的文件。如果你的"main"指向 ESM 文件,CommonJS 项目会直接报错。exports是 Node.js 12.20+ 支持的字段,现代项目务必配置上。
双包实现
lib/string.js(ESM,复用):
export function slugify(str) {
return str
.toLowerCase()
.trim()
.replace(/[^\w\s-]/g, '')
.replace(/[\s_-]+/g, '-')
.replace(/^-+|-+$/g, '')
}
export function truncate(str, len) {
if (str.length <= len) return str
return str.slice(0, len) + '...'
}
index.js(ESM 入口):
export { slugify, truncate } from './lib/string.js'
index.cjs(CommonJS 入口):
const { slugify, truncate } = require('./lib/string.cjs')
module.exports = { slugify, truncate }
等等,lib/string.cjs 从哪里来?你需要为 CommonJS 提供一份转换后的代码。最省事的办法是用 Node.js 内置的 createRequire 做桥接:
// index.cjs
import { createRequire } from 'module'
const require = createRequire(import.meta.url)
// 实际项目中,更常见的做法是准备两份源码
// 或者用构建工具(Rollup/esbuild)一次生成双格式
实际上,手动维护两份源码容易遗漏。推荐用 Rollup 或 esbuild 做构建:
npm install --save-dev rollup
rollup.config.js:
export default {
input: 'src/index.js',
output: [
{ file: 'index.js', format: 'es' },
{ file: 'index.cjs', format: 'cjs' }
]
}
一条命令产出双格式:
npx rollup -c
Tip如果你的包是纯 ESM(不打算支持 CommonJS),可以大胆只提供 ESM,在
package.json里加"type": "module"。但要知道仍有大量老项目用require(),完全抛弃 CommonJS 会损失一部分用户。折中方案是:源码写 ESM,构建时产一份 CJS。
README 与 LICENSE
npm 页面上的信息基本来自 README.md,好好写能大幅提升安装转化率。一个合格的 README 至少包含:
- 一句话描述:这个包是干什么的
- 安装命令:
npm install my-utils - 使用示例: copy-paste 就能跑
- API 文档:每个函数的参数和返回值
- 协议:
MIT最宽松,基本不会吓跑企业用户
# my-utils
Lightweight string and number utilities.
## Install
```bash
npm install my-utils
Usage
import { slugify } from 'my-utils'
slugify('Hello World!') // "hello-world"
API
slugify(str)
Converts a string to URL-friendly slug.
LICENSE 文件放一份 MIT 协议原文,GitHub 新建仓库时可以自动生成。
## 本地测试:npm link
发布前先在本地验证包能不能正常被引用:
```bash
# 在包目录里
cd my-utils
npm link
# 在另一个测试项目里
cd ../test-project
npm link my-utils
node -e "const { slugify } = require('my-utils'); console.log(slugify('A B C'))"
用完 unlink:
cd ../test-project
npm unlink my-utils
cd ../my-utils
npm unlink
发布流程
1. 登录 npm
npm login
npm whoami # 确认登录成功
国内用户如果用了淘宝镜像,发布前切回官方 registry:
npm config set registry https://registry.npmjs.org/
2. 检查将要发布的文件
npm pack
这会生成一个 .tgz 文件,解压看看里面是不是只包含你想发出去的内容。node_modules、测试文件、.env 这些千万别打进去。
3. 正式发布
npm publish
如果是 scoped 包(如 @yourname/utils),第一次发布需要加 --access public,否则默认私有(需要付费):
npm publish --access public
4. 验证
npm view my-utils
或者去 https://www.npmjs.com/package/my-utils 查看页面。
版本管理
npm 采用语义化版本(SemVer):MAJOR.MINOR.PATCH。
| 版本变化 | 含义 | 示例 |
|---|---|---|
| PATCH +1 | Bug 修复,向下兼容 | 1.0.0 → 1.0.1 |
| MINOR +1 | 新功能,向下兼容 | 1.0.1 → 1.1.0 |
| MAJOR +1 | 破坏性变更 | 1.1.0 → 2.0.0 |
不用手动改 package.json,npm 提供了快捷命令:
npm version patch # 1.0.0 -> 1.0.1
npm version minor # 1.0.1 -> 1.1.0
npm version major # 1.1.0 -> 2.0.0
这个命令会自动:修改 package.json 版本号、创建 git commit、打 git tag。然后直接 npm publish 即可。
Tip发版前跑一遍测试,更新
CHANGELOG.md,再执行npm version和npm publish。养成这个习惯,回滚时能清楚知道每个版本改了什么。
弃用与删除
发出去的包原则上不要删,因为可能已经有项目依赖它。如果某个版本有严重问题,用 npm deprecate 标记:
npm deprecate my-utils@1.0.0 "Critical bug in 1.0.0, please upgrade to 1.0.1"
用户安装时会看到警告,但包仍然可用。只有 72 小时内发布的新包可以强制删除:
npm unpublish my-utils@1.0.0
超过 72 小时或下载量高的包,npm 不允许删除,这是为了防止「供应链投毒」。