如果手上只有一段代码或一个项目,第一步通常会让人困惑:怎样才算一个 npm 包?需要把源码放到哪里?npm pack 和 npm publish 又有什么区别?
先从这些基础概念开始,再按实际操作顺序走一遍完整发布流程。文中的 @your-name/my-tool、GitHub 地址和版本号都要换成自己的内容。
npm 包是什么
npm 包可以理解为一个准备好给别人安装的项目目录。目录中至少要有 package.json,它会告诉 npm:
- 包叫什么、当前是什么版本。
- 从哪个文件开始运行或导入。
- 安装时还需要哪些依赖。
- 哪些文件应该交给使用者。
一个常见的包目录如下:
my-tool/
├─ package.json
├─ README.md
├─ LICENSE
├─ src/
│ └─ index.ts
└─ dist/
├─ index.js
└─ index.d.ts这里的 src/ 存放源码,dist/ 存放构建后交付给使用者的文件。普通 JavaScript 项目也可以直接使用源码文件;TypeScript 和需要编译的项目通常会先生成 JavaScript。
安装一个 npm 包时,npm 会下载它的文件和运行依赖,并把它放进项目的 node_modules:
npm install @your-name/my-tool
npm registry 是存放和分发这些包的服务。公开发布后,其他人可以通过包名和版本安装同一份内容。
为什么要发布成 npm 包
适合发布的内容通常有这些:
- 会在多个项目中重复使用的函数、组件或配置。
- 希望提供给其他开发者使用的 SDK、插件或工具库。
- 希望用户安装后直接在终端执行的 CLI。
- 需要通过版本号维护和分发的公共模块。
完整的网站、桌面应用或服务端项目通常有自己的部署方式。只有其中需要复用或单独安装的部分,才需要整理成 npm 包。
npm 包有哪些形式
从使用方式看,常见形式有三种:
| 形式 | 用户怎样使用 | 需要配置的入口 |
|---|---|---|
| 普通库 | 在代码中 import 或 require | exports 或 main |
| CLI | 安装后在终端运行命令 | bin |
| 同时提供两种方式 | 既能导入,也能运行命令 | exports 和 bin |
从发布范围看,又可以分为:
| 形式 | 示例 | 适合什么情况 |
|---|---|---|
| 非 scoped 公共包 | my-tool | 名称简单,但要占用全局唯一包名 |
| scoped 公共包 | @your-name/my-tool | 归在个人或组织名下,仍可供所有人安装 |
| restricted 包 | @your-company/my-tool | 只给有权限的用户或团队使用 |
本地 .tgz | my-tool-0.1.0.tgz | 测试、内部传递,暂时不上传 registry |
本文主要讲公开包。第一次发布时,使用个人 scope 往往更容易管理名称。
一段代码怎样变成 npm 包
先确定“包目录”。独立工具通常直接使用项目根目录;大型项目可以把要发布的部分放在 cli/、packages/my-tool/ 或单独生成的发布目录中。package.json 所在的目录就是包的根目录。
如果目录里还没有 package.json,可以先运行:
cd my-tool
npm init -y
然后完成下面几件事:
- 整理要提供给用户的代码。
- TypeScript 等源码先构建成可运行的 JavaScript。
- 在
package.json中填写包名、版本、入口和依赖。 - 添加 README、LICENSE,并限制发布文件。
- 使用
npm pack生成.tgz。 - 安装这份
.tgz做本地测试。 - 使用
npm publish上传到 registry。
最小流程可以记成:
代码
→ package.json 和入口
→ 构建产物
→ npm pack
→ 本地安装测试
→ npm publish
npm pack 只在本地生成压缩包,不会公开任何内容:
npm pack
输出通常类似:
your-name-my-tool-0.1.0.tgz
这份 .tgz 就是等待检查和发布的 npm 包。理解这条流程后,再开始确定正式包名和发布账号。
1. 确定包名和命名空间
npm 包有两种常见命名方式:
my-tool 普通包
@your-name/my-tool scoped 包
@your-name 叫作 scope,可以使用 npm 用户名或组织名。它相当于一层命名空间,适合管理一组相关的包,例如:
@your-name/core
@your-name/cli
@your-name/config
包名尽量使用小写字母和连字符,名称要能说明用途,也要避开他人的商标和相似包名。npm 的包名建议
可以先查一下名称是否已经存在:
npm view @your-name/my-tool
包名、GitHub 仓库名和 CLI 命令名可以不同。比如 npm 包叫 @your-name/my-tool,仓库叫 my-tool-repo,安装后的命令仍然可以叫 my-tool。
2. 准备 npm 账号和 2FA
到 npmjs.com 创建账号,验证邮箱,然后在账号设置中启用双重认证(2FA)。恢复码要另外保存,别放进项目目录或 Git 仓库。npm 2FA 指南
回到终端,确认 npm 使用官方 registry:
npm config get registry
正常结果是:
https://registry.npmjs.org/
然后登录并确认身份:
npm login --registry=https://registry.npmjs.org/
npm whoami --registry=https://registry.npmjs.org/
npm whoami 应该输出自己的 npm 用户名。如果项目里有 .npmrc,也要检查其中有没有其他 registry。不要把 token 提交到 Git。
3. 完善 package.json
下面是一份适合公开 CLI 包的简化示例:
{
"name": "@your-name/my-tool",
"version": "0.1.0",
"description": "A small command-line tool for processing files.",
"type": "module",
"bin": {
"my-tool": "./dist/cli.js"
},
"files": [
"dist/",
"README.md",
"LICENSE"
],
"engines": {
"node": ">=20"
},
"license": "MIT",
"repository": {
"type": "git",
"url": "git+https://github.com/your-name/my-tool.git"
},
"homepage": "https://github.com/your-name/my-tool#readme",
"bugs": {
"url": "https://github.com/your-name/my-tool/issues"
},
"publishConfig": {
"registry": "https://registry.npmjs.org/",
"access": "public"
}
}先看几个容易写错的字段:
name:npm 上显示和安装时使用的名称。version:本次发布的版本,同一个版本不能重复发布。files:允许进入 npm 包的文件,建议使用白名单。bin:CLI 的入口。普通库不需要这个字段。engines:项目实际支持的 Node.js 版本,不要直接照抄示例。repository、homepage、bugs:源码、主页和问题反馈地址。publishConfig:固定发布目标和访问级别,减少发错 registry 的机会。
如果是供代码导入的普通库,可以去掉 bin,改为配置入口:
{
"type": "module",
"exports": "./dist/index.js",
"types": "./dist/index.d.ts"
}
只有生成了 index.d.ts,才填写 types。运行时需要的包放进 dependencies,构建和测试工具放进 devDependencies。npm package.json 文档
private: true 应该怎么用
"private": true 会直接阻止 npm publish。npm private 字段说明
开发阶段可以先加上它,防止误发:
{
"private": true
}
如果直接从当前目录发布,最终验收前要删掉这个字段。删掉以后需要重新打包和检查,因为压缩包里的 package.json 也发生了变化。
有些项目会保留根目录的 private: true,再把准备发布的文件复制到单独目录。发布目录使用另一份精简的 package.json,其中不带 private: true。后面的 pack:cli 就属于这种做法。
GitHub 地址什么时候填
GitHub 仓库并非发布 npm 包的前置条件。不过,开源项目最好先创建仓库并推送代码,再填写 repository、homepage 和 bugs。这样可以直接验证链接,npm 包页面也不会出现空地址。
4. 添加 LICENSE 和 README
开源项目要选择许可证,并在包目录放一份完整的 LICENSE 文件。package.json 中的 license 要与它一致。MIT、Apache-2.0、GPL 等许可证的要求不同,选择前可以查看 Choose a License。
README 不用写得很长,至少包含:
- 这个包解决什么问题。
- 安装命令。
- 一个能直接复制的使用示例。
- 常用参数或配置。
- 支持的 Node.js 版本。
- 问题反馈地址和许可证。
CLI 包最好加上 --help 示例;普通库要写清楚怎样 import。README 放在发布包的根目录,npm 才会在包页面显示它。npm README 说明
5. 控制要发布的文件
npm 发布的是打包后的文件,不会直接照搬 Git 仓库页面。最简单的控制方式是在 package.json 中使用 files:
{
"files": [
"dist/",
"README.md",
"LICENSE"
]
}
这样比维护一长串排除规则更容易检查。.npmignore 也能排除文件,但最终结果仍然要以 npm pack 生成的内容为准。npm 文件包含规则
重点留意这些内容:
.env、token、私钥和证书。- 数据库备份、日志和真实用户数据。
- 测试文件、旧构建产物和其他
.tgz。 - 打包进 JavaScript 或 source map 的内部地址和敏感值。
6. 运行测试并生成 .tgz
先把项目本身检查一遍。下面是常见命令,按项目实际提供的脚本执行:
npm ci
npm run lint
npm run check
npm test
npm run build
npm audit
使用 pnpm 的项目可以改成:
pnpm install --frozen-lockfile
pnpm lint
pnpm check
pnpm test
pnpm build
pnpm audit
项目没有 check 或 build 脚本时不需要硬加。至少要保证测试通过,并重新生成准备发布的 dist/。
接着预览 npm 会收进去的文件:
npm pack --dry-run
确认文件列表和体积,再生成压缩包:
npm pack
运行后会得到类似这样的文件:
your-name-my-tool-0.1.0.tgz
.tgz 就是 npm 包的压缩文件。npm publish 最后上传的内容也会按这套规则打包。npm pack 文档
pack 是什么
pack:cli 只是项目自定义的脚本名,例如:
{
"scripts": {
"pack:cli": "node scripts/pack-cli.mjs"
}
}
它常用于“主项目里附带一个 CLI”的情况。一个比较稳妥的流程是:
构建 CLI
→ 清空临时发布目录
→ 只复制 CLI 运行需要的文件
→ 生成精简的 package.json
→ 在发布目录执行 npm pack
主项目可以继续保留 private: true。脚本生成的发布目录只包含 dist、README、LICENSE 和发布用的 package.json,可以减少把网站源码、配置或环境文件一起发出去的机会。
独立的小型 npm 包直接使用 files 和 npm pack 就够了。Monorepo 如果使用 pnpm 的 workspace: 依赖,应通过对应 workspace 的打包流程生成包,并检查 .tgz 内的依赖版本。pnpm workspace 发布说明
7. 人工检查 .tgz
不要只看 npm pack 打印的摘要。先列出压缩包中的全部文件:
tar -tzf ./your-name-my-tool-0.1.0.tgz
再把它解压到一个空目录:
mkdir npm-package-review
tar -xzf ./your-name-my-tool-0.1.0.tgz -C npm-package-review
打开 npm-package-review/package/package.json,逐项确认:
- 包名和版本正确。
- 没有
private: true。 bin、exports、types指向的文件都存在。dependencies中没有本地路径。- README 和 LICENSE 已包含。
- 文件列表中没有密钥、环境文件、日志和无关源码。
还可以检查 JSON 能否正常解析:
node -e "JSON.parse(require('node:fs').readFileSync('./npm-package-review/package/package.json', 'utf8')); console.log('package.json OK')"
修改了源码、构建结果或 package.json,就重新生成并检查 .tgz。
8. 在空目录模拟安装
到仓库外面新建一个空目录,然后安装刚才检查过的 .tgz:
npm init -y
npm install "C:/path/to/your-name-my-tool-0.1.0.tgz"
CLI 可以这样测试:
./node_modules/.bin/my-tool --help
./node_modules/.bin/my-tool --version
Windows PowerShell 也可以运行:
./node_modules/.bin/my-tool.cmd --help
普通库可以测试导入:
node --input-type=module -e "import('@your-name/my-tool').then(console.log)"
最后照着 README 完整跑一次主要功能。这个步骤能发现缺少运行依赖、入口写错、文件漏打包等问题。
9. 确认本地代码已经推送
正式发布前,确认本地没有漏提交的改动:
git status
git fetch origin
git status -sb
查看当前提交和远程分支:
git rev-parse HEAD
git rev-parse origin/main
两个提交号应该一致。如果使用其他分支,把 origin/main 换成实际分支。
.tgz 和 dist/ 是否提交到 Git,要看项目约定。更重要的是保存好这次发布对应的源码提交。代码有变化时,重新执行测试、打包和空目录安装。
10. 正式发布
先做最后一次确认:
npm whoami --registry=https://registry.npmjs.org/
npm view @your-name/my-tool@0.1.0 version --registry=https://registry.npmjs.org/
首次发布时,第二条通常会返回找不到包或版本。注意区分名称不存在、网络错误和权限错误。
建议直接发布已经检查和安装过的 .tgz:
npm publish ./your-name-my-tool-0.1.0.tgz --access public --registry=https://registry.npmjs.org/
公开的 scoped 包要加 --access public。普通非 scoped 包也可以保留这个参数,命令更统一。npm scoped 包发布指南
终端会要求完成 2FA 验证。发布成功后,同一个包名和版本号不能再次使用;修复内容时要更新版本号。npm publish 文档
11. 发布后再安装一次
查看线上信息:
npm view @your-name/my-tool@0.1.0
然后在另一个空目录安装线上版本:
npm init -y
npm install @your-name/my-tool@0.1.0 --registry=https://registry.npmjs.org/
重复运行 README 中的主要示例,并打开 npm 包页面检查 README、LICENSE 和 GitHub 链接。
确认没有问题后,可以给对应的 Git 提交加版本标签:
git tag v0.1.0
git push origin v0.1.0
发布前检查清单
- npm 账号、邮箱和 2FA 已设置好。
- 包名、scope、版本号和 registry 正确。
-
package.json的入口、依赖和仓库地址正确。 - README 与 LICENSE 已放进包目录。
- 测试和构建通过。
-
.tgz已人工解压检查。 - 在仓库外安装并运行过同一份
.tgz。 - 本地源码已推送到对应远程提交。
- 公开 scoped 包使用了
--access public。 - 发布后已从 npm 安装线上版本。
喜欢的话,留下你的评论吧~