Published on

如何创建并发布一个 NPM 包

Authors
  • avatar
    Name
    Duck
    Twitter

什么是 NPM

npm 是 Node.js 的包管理工具,全称是 Node Package Manager。它主要用来管理 JavaScript 项目的依赖包。简单来说,npm 就像是一个 线上“应用商店”,可以帮你安装、更新和管理各种 JavaScript 库或工具。

npm 由三个不同的部分组成: 什么是 npm
  • 网站(the website) 用于发现包、设置个人资料以及管理 npm 的其他功能。

  • 命令行工具(CLI) 在终端中运行,是大多数开发者与 npm 交互的主要方式。

  • 注册库(the registry) 是一个大型的公共数据库,存储 JavaScript 软件及其相关的元信息。

发布第一个包

本文用 typescript 创建一个摩斯电码解码包为例,记录一下包的创建发布流程。

注册 npm 账号

参考 官方文档

新建包并初始化 package.json

mkdir npm-package-start
cd ./npm-package-start
npm init

然后根据提示填写信息,最终在当前路径生成如下内容:

package.json
{
  "name": "@duckbb/npm-package-start", //这里加上了 scope
  "version": "1.0.0",
  "main": "index.ts", //默认是 index.js,修改成 src/index.ts
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "Duck",
  "license": "ISC",
   "description": "A simple npm package to get started with npm",
  "keywords": [
    "npm",
    "package",
    "start"
  ]
}
  • name 是包的名称,也是导入模块的名称,duckbb是 scope,只能是用户名或组织名,加了 scope 默认是私有包
  • version 版本号
  • main 入口文件
  • keywords 关键词,搜索关键词可以搜索到该包
  • author 作者
  • license 协议

再新建三个文件夹

  • src 包源码
  • tests 存放单元测试代码
  • e2e 端到端测试代码

创建入口文件

从入口文件导出两个模块

src/index.ts
export const add = (a: number, b: number) => a + b
export const subtract = (a: number, b: number) => a - b

初始化 Git

创建 Git 忽略文件,dist 目录将用于存放编译后的代码,不需要同步到代码仓库

.gitignore
node_modules
dist
git init
git add -A
git commit -m "init"

就是这么简单,现在就可以直接发布包了。

发布包

npm login # 登录
npm publish --access public # 发布

因为包名加了 scope,默认是发布到私有包,所以加上 --access public发布到公共包。

成功发布后,就可以在后台看到自己的包了。

npm 包发布成功

编译代码

不过,目前发布的包是 typescript 代码,而所有主流的浏览器和 Node.js 运行时环境都只能直接执行 JavaScript 代码。所以,需要编译代码到 JS 再发布,否则无法正常使用。

一个标准的 TypeScript 包发布流程通常包含以下步骤:

  1. 编写 TypeScript 代码:在 src 目录下编写你的 .ts 文件。

  2. 配置 tsconfig.json:

tsconfig.json
{
    "compilerOptions": {
      "target": "ES2017",               // 编译后的 JS 目标版本,ES2017 支持 async/await 等
      "module": "ESNext",               // 输出模块类型,ESNext 对应 ESM(import/export)
      "declaration": true,              // 生成类型声明文件 (.d.ts)
      "outDir": "dist",                 // 编译输出目录
      "strict": true,                    // 开启严格模式,启用所有严格类型检查选项
    //   "esModuleInterop": true,          // 支持 CommonJS 模块默认导入,兼容 require() 的模块
    //   "skipLibCheck": true,             // 跳过库文件类型检查,提高编译速度
      "moduleResolution": "Node",       // 模块解析策略,Node 风格(支持 node_modules)
      "forceConsistentCasingInFileNames": true // 强制文件名大小写一致,避免跨平台问题
    },
    "include": ["src"]                  // 指定编译器要包含的文件或目录,这里只编译 src 文件夹
}
  1. 添加编译脚本:在 package.json 中添加一个 build 脚本来执行编译命令
npm install --save-dev typescript
package.json
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "build": "tsc"
  },
  1. 编译代码。这会将你的 .ts 文件编译成 .js 文件和 .d.ts 文件,并将它们输出到指定的目录(dist)。
npm run build
  1. 配置 package.json。
package.json
{
  "name": "@duckbb/npm-package-start",
  "version": "1.0.0",
  "main": "dist/index.js",     // 入口 JS 文件,指向编译后的 dist 目录
  "types": "dist/index.d.ts",  // TypeScript 类型声明文件入口,供其他 TS 项目使用
  "type": "module",            // 指明这是 ESM 模块,使用 import/export
  "files": [                   // 发布到 npm 时包含的文件或目录
    "dist"                     // 这里只发布 dist 文件夹,确保包体积小
  ],
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "build": "tsc"
  },
  "author": "Duck",
  "license": "ISC",
  "description": "A simple npm package to get started with npm",
  "keywords": [
    "npm",
    "package",
    "start"
  ],
  "devDependencies": {
    "typescript": "^5.9.2"
  }
}

  1. 最后,运行 npm publish

测试

端到端测试

端到端测试(End-to-End Testing),简称 E2E 测试,是一种软件测试方法,它模拟用户从头到尾使用应用程序的整个流程,以验证整个系统作为一个整体是否能正常运行。最好在发布包之前,在本地进行测试。

npm link # 这个命令会在你的文件系统中创建一个符号链接(symbolic link),让你的项目可以像引用一个正常的 npm 包一样,引用你的本地包。
cd ./e2e # 在 e2e 中测试本地包
npm init -y # 初始化,视为一个独立的子项目
npm link @ducbbb/npm-package-start # 链接到本地包

创建测试文件index.mjs以支持 ESM 语法:

e2e/index.mjs
import { add, subtract } from '@duckbb/npm-package-start'

console.log(add(1, 2))
console.log(subtract(1, 2))

执行,看看能否正常调用:

node index.mjs
output
3
-1

输出结果表明,成功调用本地包。

单元测试

单元测试是指针对代码中最小可测试单元进行验证的测试。它的核心在于测试对象。

  • 测试对象:通常是一个函数、一个方法、一个类或一个组件。

  • 测试范围:专注于测试该单元自身的逻辑,不涉及外部依赖(如数据库、网络请求、文件系统)。为了隔离外部依赖,通常会使用 mocking 或 stubbing 技术。

  • 主要目的:确保代码的每个基本单元都按预期工作,是测试金字塔的基础。

流行的测试框架有 jestvitest,经过对比,这里选择更简洁且对 ESM 支持更好的vitest

npm install -D vitest

创建两个测试文件

add.test.ts
import { expect, test } from 'vitest'
import { add } from '../src/index'

test('adds 1 + 2 to equal 3', () => {
    expect(add(1, 2)).toBe(3)
})

substract.test.ts
import { expect, test } from 'vitest'
import { subtract } from '../src/index'

test('subtracts 1 - 2 to equal -1', () => {
    expect(subtract(1, 2)).toBe(-1)
})

TIP

一般情况下,执行测试的文件名中必须包含 .test..spec.

package.json
  "scripts": {
    "test": "vitest"
  }

最后,运行 npm run test,vitest 将打印测试消息

 ✓ tests/add.test.ts (1 test) 2ms
 ✓ tests/subtract.test.ts (1 test) 2ms

 Test Files  2 passed (2)
      Tests  2 passed (2)
   Start at  09:28:35
   Duration  380ms (transform 85ms, setup 0ms, collect 107ms, tests 4ms, environment 0ms, prepare 243ms)

更新包

git add -A
git commit -m "add vitest"
npm version patch # 版本号+1 并commit ,比如 "1.2.0" => "1.2.1"
npm run build # 编译 ts
npm run e2e # 本地测试
npm run test # 单元测试
npm publish # 第一次已经指定了 --access public,后面就可以不用了

之后,就可以用这个做模板代码,快速开发自己的包了。完整模板代码可查看 https://github.com/duck-codes/npm-package-start

IMPORTANT

实际开发包时,可以使用流行的起步模板,配置会更加完善。建议使用 tsdown 快速开发。

参考