npm-package
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
Chinesenpm Package Development (Bun-First)
npm包开发(优先使用Bun)
Build and publish npm packages using Bun as the primary runtime and toolchain, producing output that works everywhere npm packages are consumed.
使用Bun作为主要运行时和工具链构建并发布npm包,生成的输出可在所有支持npm包的环境中使用。
When to Use This Skill
适用场景
Use when:
- Creating a new npm library package from scratch
- Setting up build/test/lint tooling for an existing package
- Fixing CJS/ESM interop, exports map, or TypeScript declaration issues
- Publishing a package to npm
- Reviewing or improving package configuration
Do NOT use when:
- Building an npx-executable CLI tool (use the skill)
npx-cli - Building an application (not a published package)
- Working in a monorepo (this skill targets single-package repos)
适用于以下情况:
- 从零开始创建新的npm库包
- 为现有包配置构建/测试/代码检查工具
- 修复CJS/ESM互操作、导出映射或TypeScript声明问题
- 将包发布到npm
- 审查或优化包配置
不适用于以下情况:
- 构建可执行的npx CLI工具(请使用技能)
npx-cli - 构建应用程序(而非可发布的包)
- 工作在单体仓库中(本技能针对单包仓库)
Toolchain
工具链
| Concern | Tool | Why |
|---|---|---|
| Runtime / package manager | Bun | Fast install, run, transpile |
| Bundler | Bunup | Bun-native, dual output, .d.ts generation |
| Type declarations | Bunup (via tsc) | Integrated with build |
| TypeScript | | Maximum correctness for published code |
| Formatting + basic linting | Biome v2 | 10-25x faster than ESLint, single tool |
| Type-aware linting | ESLint + typescript-eslint | 40+ type-aware rules Biome can't do |
| Testing | Vitest | Test isolation, mature mocking, coverage |
| Versioning | Changesets | File-based, explicit, monorepo-ready |
| Publishing | | Trusted Publishing / OIDC |
| 关注点 | 工具 | 原因 |
|---|---|---|
| 运行时 / 包管理器 | Bun | 安装、运行、转译速度快 |
| 打包工具 | Bunup | Bun原生支持,双输出格式,自动生成.d.ts文件 |
| 类型声明 | Bunup(基于tsc) | 与构建流程集成 |
| TypeScript | | 确保发布代码的最大正确性 |
| 格式化 + 基础代码检查 | Biome v2 | 速度比ESLint快10-25倍,单一工具完成多任务 |
| 类型感知代码检查 | ESLint + typescript-eslint | 提供40+ Biome不支持的类型感知规则 |
| 测试 | Vitest | 测试隔离,成熟的Mock功能,覆盖率统计 |
| 版本管理 | Changesets | 基于文件的显式版本管理,支持单体仓库 |
| 发布 | | 可信发布 / OIDC认证 |
Scaffolding a New Package
搭建新包脚手架
Run the scaffold script to generate a complete project:
bash
bun run <skill-path>/scripts/scaffold.ts ./my-package \
--name my-package \
--description "What this package does" \
--author "Your Name" \
--license MITOptions:
- — Generate dual CJS/ESM output (default: ESM-only)
--dual - — Skip ESLint, use Biome only
--no-eslint
Then install dependencies:
bash
cd my-package
bun install
bun add -d bunup typescript vitest @vitest/coverage-v8 @biomejs/biome @changesets/cli
bun add -d eslint typescript-eslint # unless --no-eslint运行脚手架脚本生成完整项目:
bash
bun run <skill-path>/scripts/scaffold.ts ./my-package \
--name my-package \
--description "What this package does" \
--author "Your Name" \
--license MIT可选参数:
- — 生成CJS/ESM双格式输出(默认:仅ESM)
--dual - — 跳过ESLint,仅使用Biome
--no-eslint
然后安装依赖:
bash
cd my-package
bun install
bun add -d bunup typescript vitest @vitest/coverage-v8 @biomejs/biome @changesets/cli
bun add -d eslint typescript-eslint # 若使用--no-eslint则无需执行Project Structure
项目结构
my-package/
├── src/
│ ├── index.ts # Package entry point — all public API exports here
│ └── index.test.ts # Tests co-located with source
├── dist/ # Built output (gitignored, included in published tarball)
├── .changeset/
│ └── config.json
├── package.json
├── tsconfig.json
├── bunup.config.ts
├── biome.json
├── eslint.config.ts # Type-aware rules only
├── vitest.config.ts
├── .gitignore
├── README.md
└── LICENSEmy-package/
├── src/
│ ├── index.ts # 包入口文件 — 所有公开API均在此导出
│ └── index.test.ts # 测试文件与源码同目录
├── dist/ # 构建输出目录(已加入.gitignore,会包含在发布包中)
├── .changeset/
│ └── config.json
├── package.json
├── tsconfig.json
├── bunup.config.ts
├── biome.json
├── eslint.config.ts # 仅包含类型感知规则
├── vitest.config.ts
├── .gitignore
├── README.md
└── LICENSECritical Configuration Details
关键配置细节
Read these reference docs before modifying any configuration. They contain the reasoning behind each decision and the sharp edges that cause subtle breakage:
- reference/esm-cjs-guide.md — map configuration, dual package hazard,
exports, common mistakesmodule-sync - reference/strict-typescript.md — tsconfig rationale, Biome rules, ESLint type-aware rules, Vitest config
- reference/publishing-workflow.md — Changesets, field, Trusted Publishing, CI pipeline
files
修改任何配置前,请阅读以下参考文档。这些文档包含每个决策的理由以及可能导致细微bug的注意事项:
- reference/esm-cjs-guide.md — 映射配置、双包风险、
exports、常见错误module-sync - reference/strict-typescript.md — tsconfig配置理由、Biome规则、ESLint类型感知规则、Vitest配置
- reference/publishing-workflow.md — Changesets使用、字段、可信发布、CI流水线
files
Key Rules (Non-Negotiable)
关键规则(不可违反)
These are the rules that, when violated, cause the most common and painful bugs in published packages. Follow these without exception.
以下规则若被违反,会导致发布包出现最常见且难以排查的bug,请严格遵守。
Package Configuration
包配置
-
Always usein package.json. ESM-only is the correct default.
"type": "module"works in all supported Node.js versions.require(esm) -
Always usefield, not
exports.mainis legacy.maingives precise control over what consumers can access.exports -
must be the first condition in every exports block. TypeScript silently fails to resolve types if it isn't.
types -
Always export. Many tools need access to the package.json and
"./package.json": "./package.json"encapsulates completely.exports -
Usein package.json. Whitelist approach prevents shipping secrets. Never use
files: ["dist"]..npmignore -
Runbefore every publish. Verify the tarball contains exactly what you intend.
npm pack --dry-run
-
始终在package.json中使用。ESM-only是正确的默认选择。所有受支持的Node.js版本都支持
"type": "module"。require(esm) -
始终使用字段,而非
exports。main是遗留字段。main可精确控制消费者能访问的内容。exports -
必须是每个exports块中的第一个条件。如果不是,TypeScript会静默无法解析类型。
types -
始终导出。许多工具需要访问package.json,
"./package.json": "./package.json"会完全封装包的可访问路径。exports -
在package.json中使用。白名单方式可防止泄露敏感信息。永远不要使用
files: ["dist"]。.npmignore -
每次发布前运行。验证发布包中是否仅包含你想要的内容。
npm pack --dry-run
TypeScript
TypeScript
-
Usefor published packages. Not
module: "nodenext". Code satisfying nodenext works everywhere; the reverse is not true."bundler" -
is non-negotiable. Without it, your .d.ts files can contain types that error for consumers using strict mode.
strict: true -
Enable. Catches real runtime bugs from unguarded array/object access.
noUncheckedIndexedAccess -
Ship. Enables "Go to Definition" to reach original source for consumers.
declarationMap: true -
Do not use path aliases () in published packages. tsc does not rewrite them in emitted code. Consumers can't resolve them.
paths
-
发布包使用。不要使用
module: "nodenext"。符合nodenext规范的代码可在所有环境运行;反之则不行。"bundler" -
是必须的。如果不开启,你的.d.ts文件可能包含在严格模式下会报错的类型,导致消费者出现问题。
strict: true -
启用。捕获数组/对象无防护访问导致的真实运行时bug。
noUncheckedIndexedAccess -
发布时开启。允许消费者通过“跳转到定义”功能访问原始源码。
declarationMap: true -
发布包中不要使用路径别名()。tsc不会在输出代码中重写路径别名,消费者无法解析它们。
paths
Code Quality
代码质量
-
is banned. Use
anyand narrow. Suppress withunknownonly when genuinely unavoidable, and always include the reason.// biome-ignore suspicious/noExplicitAny: <reason> -
Prefer named exports over default exports. Default exports behave differently across CJS/ESM boundaries.
-
Always usefor type-only imports. Enforced by both
import typeand Biome'sverbatimModuleSyntaxrule.useImportType
-
禁止使用类型。使用
any并进行类型收窄。只有在真正无法避免时,才用unknown抑制,并始终注明原因。// biome-ignore suspicious/noExplicitAny: <reason> -
优先使用命名导出而非默认导出。默认导出在CJS/ESM边界的行为不一致。
-
类型导入始终使用。
import type和Biome的verbatimModuleSyntax规则会强制要求这一点。useImportType
Build
构建
-
Build with Bunup using(or
format: ['esm']for dual). Bunup handles .d.ts generation, external detection, and correct file extensions.['esm', 'cjs'] -
Setto
engines.nodein package.json. This documents the minimum supported Node.js version (first LTS with stable>=20.19.0).require(esm)
-
使用Bunup进行构建,设置(若需双格式则用
format: ['esm'])。Bunup会处理.d.ts文件生成、外部依赖检测和正确的文件扩展名。['esm', 'cjs'] -
在package.json中设置为
engines.node。这会记录最低支持的Node.js版本(首个支持稳定>=20.19.0的LTS版本)。require(esm)
Testing
测试
-
Use Vitest, not bun:test. bun:test lacks test isolation — module mocks leak between files. Vitest runs each test file in its own worker.
-
Set coverage thresholds (branches, functions, lines, statements all ≥ 80%). Enforced in vitest.config.ts.
-
使用Vitest,而非bun:test。bun:test缺乏测试隔离——模块Mock会在文件间泄漏。Vitest会在独立的工作进程中运行每个测试文件。
-
设置覆盖率阈值(分支、函数、行、语句覆盖率均≥80%)。在vitest.config.ts中强制执行。
Development Workflow
开发工作流
bash
undefinedbash
undefinedWrite code and tests
编写代码和测试
bun run test:watch # Vitest watch mode
bun run test:watch # Vitest监听模式
Check everything
检查所有内容
bun run lint # Biome + ESLint
bun run typecheck # tsc --noEmit
bun run test # Vitest run
bun run lint # Biome + ESLint代码检查
bun run typecheck # tsc --noEmit 类型检查
bun run test # 运行Vitest测试
Build
构建
bun run build # Bunup → dist/
bun run build # Bunup 构建到dist/
Prepare release
准备发布
bunx changeset # Create changeset describing changes
bunx changeset version # Bump version, update CHANGELOG
bunx changeset # 创建描述变更的changeset
bunx changeset version # 升级版本,更新CHANGELOG
Publish
发布
bun run release # Build + npm publish --provenance
undefinedbun run release # 构建 + npm publish --provenance
undefinedAdding Subpath Exports
添加子路径导出
When the package needs to expose multiple entry points:
- Add the source file:
src/utils.ts - Add to bunup.config.ts entry:
entry: ['src/index.ts', 'src/utils.ts'] - Add to package.json exports:
json
{
"exports": {
".": {
"types": "./dist/index.d.ts",
"default": "./dist/index.js"
},
"./utils": {
"types": "./dist/utils.d.ts",
"default": "./dist/utils.js"
},
"./package.json": "./package.json"
}
}Reminder: Adding or removing export paths is a semver-major change.
当包需要暴露多个入口点时:
- 添加源码文件:
src/utils.ts - 在bunup.config.ts的entry中添加:
entry: ['src/index.ts', 'src/utils.ts'] - 在package.json的exports中添加:
json
{
"exports": {
".": {
"types": "./dist/index.d.ts",
"default": "./dist/index.js"
},
"./utils": {
"types": "./dist/utils.d.ts",
"default": "./dist/utils.js"
},
"./package.json": "./package.json"
}
}注意: 添加或移除导出路径属于语义化版本的重大变更(semver-major)。
Switching to Dual CJS/ESM Output
切换到CJS/ESM双格式输出
If consumers require CJS support for Node.js < 20.19.0:
- Update bunup.config.ts:
format: ['esm', 'cjs'] - Update package.json exports to include ,
module-sync, andimportconditionsrequire - See reference/esm-cjs-guide.md for the exact exports map structure
若消费者需要支持Node.js < 20.19.0的CJS格式:
- 更新bunup.config.ts:
format: ['esm', 'cjs'] - 更新package.json的exports,包含、
module-sync和import条件require - 查看reference/esm-cjs-guide.md获取精确的exports映射结构
Bun-Specific Gotchas
Bun特定注意事项
- does not generate .d.ts files. Use Bunup (which delegates to tsc) or run
bun buildseparately.tsc --emitDeclarationOnly - CJS output is experimental. Always use
bun buildfor npm-publishable CJS.target: "node"produces Bun-specific wrappers.target: "bun" - does not downlevel syntax. Modern ES2022+ syntax ships as-is. If targeting older runtimes, additional transpilation is needed.
bun build - does not support
bun publish. Use--provenancefor provenance signing.npm publish - uses
bun publish, notNPM_CONFIG_TOKEN. CI pipelines may need adjustment.NODE_AUTH_TOKEN
- 不会生成.d.ts文件。使用Bunup(它会委托给tsc)或单独运行
bun build。tsc --emitDeclarationOnly - 的CJS输出是实验性的。发布到npm的CJS格式始终使用
bun build。target: "node"会生成Bun特定的包装器。target: "bun" - 不会降级语法。现代ES2022+语法会原样发布。若需要支持旧版运行时,需额外转译。
bun build - 不支持
bun publish。使用--provenance进行来源签名。npm publish - 使用
bun publish,而非NPM_CONFIG_TOKEN。CI流水线可能需要调整配置。NODE_AUTH_TOKEN