npm-package

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

npm 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
    npx-cli
    skill)
  • 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

工具链

ConcernToolWhy
Runtime / package managerBunFast install, run, transpile
BundlerBunupBun-native, dual output, .d.ts generation
Type declarationsBunup (via tsc)Integrated with build
TypeScript
module: "nodenext"
,
strict: true
+ extras
Maximum correctness for published code
Formatting + basic lintingBiome v210-25x faster than ESLint, single tool
Type-aware lintingESLint + typescript-eslint40+ type-aware rules Biome can't do
TestingVitestTest isolation, mature mocking, coverage
VersioningChangesetsFile-based, explicit, monorepo-ready
Publishing
npm publish --provenance
Trusted Publishing / OIDC
关注点工具原因
运行时 / 包管理器Bun安装、运行、转译速度快
打包工具BunupBun原生支持,双输出格式,自动生成.d.ts文件
类型声明Bunup(基于tsc)与构建流程集成
TypeScript
module: "nodenext"
,
strict: true
+ 额外配置
确保发布代码的最大正确性
格式化 + 基础代码检查Biome v2速度比ESLint快10-25倍,单一工具完成多任务
类型感知代码检查ESLint + typescript-eslint提供40+ Biome不支持的类型感知规则
测试Vitest测试隔离,成熟的Mock功能,覆盖率统计
版本管理Changesets基于文件的显式版本管理,支持单体仓库
发布
npm publish --provenance
可信发布 / 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 MIT
Options:
  • --dual
    — Generate dual CJS/ESM output (default: ESM-only)
  • --no-eslint
    — Skip ESLint, use Biome only
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
可选参数:
  • --dual
    — 生成CJS/ESM双格式输出(默认:仅ESM)
  • --no-eslint
    — 跳过ESLint,仅使用Biome
然后安装依赖:
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
└── LICENSE
my-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
└── LICENSE

Critical 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
    exports
    map configuration, dual package hazard,
    module-sync
    , common mistakes
  • reference/strict-typescript.md — tsconfig rationale, Biome rules, ESLint type-aware rules, Vitest config
  • reference/publishing-workflow.md — Changesets,
    files
    field, Trusted Publishing, CI pipeline
修改任何配置前,请阅读以下参考文档。这些文档包含每个决策的理由以及可能导致细微bug的注意事项:
  • reference/esm-cjs-guide.md
    exports
    映射配置、双包风险、
    module-sync
    、常见错误
  • reference/strict-typescript.md — tsconfig配置理由、Biome规则、ESLint类型感知规则、Vitest配置
  • reference/publishing-workflow.md — Changesets使用、
    files
    字段、可信发布、CI流水线

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

包配置

  1. Always use
    "type": "module"
    in package.json.
    ESM-only is the correct default.
    require(esm)
    works in all supported Node.js versions.
  2. Always use
    exports
    field, not
    main
    .
    main
    is legacy.
    exports
    gives precise control over what consumers can access.
  3. types
    must be the first condition
    in every exports block. TypeScript silently fails to resolve types if it isn't.
  4. Always export
    "./package.json": "./package.json"
    .
    Many tools need access to the package.json and
    exports
    encapsulates completely.
  5. Use
    files: ["dist"]
    in package.json.
    Whitelist approach prevents shipping secrets. Never use
    .npmignore
    .
  6. Run
    npm pack --dry-run
    before every publish.
    Verify the tarball contains exactly what you intend.
  1. 始终在package.json中使用
    "type": "module"
    。ESM-only是正确的默认选择。所有受支持的Node.js版本都支持
    require(esm)
  2. 始终使用
    exports
    字段,而非
    main
    main
    是遗留字段。
    exports
    可精确控制消费者能访问的内容。
  3. types
    必须是每个exports块中的第一个条件
    。如果不是,TypeScript会静默无法解析类型。
  4. 始终导出
    "./package.json": "./package.json"
    。许多工具需要访问package.json,
    exports
    会完全封装包的可访问路径。
  5. 在package.json中使用
    files: ["dist"]
    。白名单方式可防止泄露敏感信息。永远不要使用
    .npmignore
  6. 每次发布前运行
    npm pack --dry-run
    。验证发布包中是否仅包含你想要的内容。

TypeScript

TypeScript

  1. Use
    module: "nodenext"
    for published packages.
    Not
    "bundler"
    . Code satisfying nodenext works everywhere; the reverse is not true.
  2. strict: true
    is non-negotiable.
    Without it, your .d.ts files can contain types that error for consumers using strict mode.
  3. Enable
    noUncheckedIndexedAccess
    .
    Catches real runtime bugs from unguarded array/object access.
  4. Ship
    declarationMap: true
    .
    Enables "Go to Definition" to reach original source for consumers.
  5. Do not use path aliases (
    paths
    ) in published packages.
    tsc does not rewrite them in emitted code. Consumers can't resolve them.
  1. 发布包使用
    module: "nodenext"
    。不要使用
    "bundler"
    。符合nodenext规范的代码可在所有环境运行;反之则不行。
  2. strict: true
    是必须的
    。如果不开启,你的.d.ts文件可能包含在严格模式下会报错的类型,导致消费者出现问题。
  3. 启用
    noUncheckedIndexedAccess
    。捕获数组/对象无防护访问导致的真实运行时bug。
  4. 发布时开启
    declarationMap: true
    。允许消费者通过“跳转到定义”功能访问原始源码。
  5. 发布包中不要使用路径别名(
    paths
    。tsc不会在输出代码中重写路径别名,消费者无法解析它们。

Code Quality

代码质量

  1. any
    is banned.
    Use
    unknown
    and narrow. Suppress with
    // biome-ignore suspicious/noExplicitAny: <reason>
    only when genuinely unavoidable, and always include the reason.
  2. Prefer named exports over default exports. Default exports behave differently across CJS/ESM boundaries.
  3. Always use
    import type
    for type-only imports.
    Enforced by both
    verbatimModuleSyntax
    and Biome's
    useImportType
    rule.
  1. 禁止使用
    any
    类型
    。使用
    unknown
    并进行类型收窄。只有在真正无法避免时,才用
    // biome-ignore suspicious/noExplicitAny: <reason>
    抑制,并始终注明原因。
  2. 优先使用命名导出而非默认导出。默认导出在CJS/ESM边界的行为不一致。
  3. 类型导入始终使用
    import type
    verbatimModuleSyntax
    和Biome的
    useImportType
    规则会强制要求这一点。

Build

构建

  1. Build with Bunup using
    format: ['esm']
    (or
    ['esm', 'cjs']
    for dual). Bunup handles .d.ts generation, external detection, and correct file extensions.
  2. Set
    engines.node
    to
    >=20.19.0
    in package.json. This documents the minimum supported Node.js version (first LTS with stable
    require(esm)
    ).
  1. 使用Bunup进行构建,设置
    format: ['esm']
    (若需双格式则用
    ['esm', 'cjs']
    )。Bunup会处理.d.ts文件生成、外部依赖检测和正确的文件扩展名。
  2. 在package.json中设置
    engines.node
    >=20.19.0
    。这会记录最低支持的Node.js版本(首个支持稳定
    require(esm)
    的LTS版本)。

Testing

测试

  1. Use Vitest, not bun:test. bun:test lacks test isolation — module mocks leak between files. Vitest runs each test file in its own worker.
  2. Set coverage thresholds (branches, functions, lines, statements all ≥ 80%). Enforced in vitest.config.ts.
  1. 使用Vitest,而非bun:test。bun:test缺乏测试隔离——模块Mock会在文件间泄漏。Vitest会在独立的工作进程中运行每个测试文件。
  2. 设置覆盖率阈值(分支、函数、行、语句覆盖率均≥80%)。在vitest.config.ts中强制执行。

Development Workflow

开发工作流

bash
undefined
bash
undefined

Write 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
undefined
bun run release # 构建 + npm publish --provenance
undefined

Adding Subpath Exports

添加子路径导出

When the package needs to expose multiple entry points:
  1. Add the source file:
    src/utils.ts
  2. Add to bunup.config.ts entry:
    entry: ['src/index.ts', 'src/utils.ts']
  3. 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.
当包需要暴露多个入口点时:
  1. 添加源码文件:
    src/utils.ts
  2. 在bunup.config.ts的entry中添加:
    entry: ['src/index.ts', 'src/utils.ts']
  3. 在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:
  1. Update bunup.config.ts:
    format: ['esm', 'cjs']
  2. Update package.json exports to include
    module-sync
    ,
    import
    , and
    require
    conditions
  3. See reference/esm-cjs-guide.md for the exact exports map structure
若消费者需要支持Node.js < 20.19.0的CJS格式:
  1. 更新bunup.config.ts:
    format: ['esm', 'cjs']
  2. 更新package.json的exports,包含
    module-sync
    import
    require
    条件
  3. 查看reference/esm-cjs-guide.md获取精确的exports映射结构

Bun-Specific Gotchas

Bun特定注意事项

  • bun build
    does not generate .d.ts files.
    Use Bunup (which delegates to tsc) or run
    tsc --emitDeclarationOnly
    separately.
  • bun build
    CJS output is experimental.
    Always use
    target: "node"
    for npm-publishable CJS.
    target: "bun"
    produces Bun-specific wrappers.
  • bun build
    does not downlevel syntax.
    Modern ES2022+ syntax ships as-is. If targeting older runtimes, additional transpilation is needed.
  • bun publish
    does not support
    --provenance
    .
    Use
    npm publish
    for provenance signing.
  • bun publish
    uses
    NPM_CONFIG_TOKEN
    , not
    NODE_AUTH_TOKEN
    . CI pipelines may need adjustment.
  • bun build
    不会生成.d.ts文件
    。使用Bunup(它会委托给tsc)或单独运行
    tsc --emitDeclarationOnly
  • bun build
    的CJS输出是实验性的
    。发布到npm的CJS格式始终使用
    target: "node"
    target: "bun"
    会生成Bun特定的包装器。
  • bun build
    不会降级语法
    。现代ES2022+语法会原样发布。若需要支持旧版运行时,需额外转译。
  • bun publish
    不支持
    --provenance
    。使用
    npm publish
    进行来源签名。
  • bun publish
    使用
    NPM_CONFIG_TOKEN
    ,而非
    NODE_AUTH_TOKEN
    。CI流水线可能需要调整配置。