首页 / Node.js 教程 / 发布自己的 npm 包

Node.js 教程

发布自己的 npm 包

本教程共 76 篇 · 第 74 篇 · 更新于 2026-07-25 · 约 6 分钟阅读

Node.jsnpm发包exports双包

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.jsrequire./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)一次生成双格式

实际上,手动维护两份源码容易遗漏。推荐用 Rollupesbuild 做构建:

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 至少包含:

  1. 一句话描述:这个包是干什么的
  2. 安装命令:npm install my-utils
  3. 使用示例: copy-paste 就能跑
  4. API 文档:每个函数的参数和返回值
  5. 协议: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 +1Bug 修复,向下兼容1.0.01.0.1
MINOR +1新功能,向下兼容1.0.11.1.0
MAJOR +1破坏性变更1.1.02.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 versionnpm 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 不允许删除,这是为了防止「供应链投毒」。