打造自己的 npm 包实战指南

📌 一句话定义

npm 包不只是"发布一个库"------它是前端工程化的最小交付单元,是团队知识沉淀的载体,更是你技术影响力的名片。

基本信息 内容
包管理器 npm / pnpm / yarn / bun
注册表 npmjs.com(公共)/ Verdaccio / GitHub Packages / Nexus(私有)
模块格式 ESM(推荐)/ CJS / UMD / Dual Package
构建工具 tsup / unbuild / Rollup / Vite Library Mode
类型支持 TypeScript + .d.ts 自动生成
测试框架 Vitest / Jest
CI/CD GitHub Actions / Changesets
当前 Node LTS v22.x(2026-09)

一、为什么要造自己的轮子?

1.1 不是重复造轮子,而是"精准造齿轮"

复制代码
❌ "我要做一个比 lodash 更好的工具库"     → 大概率失败
✅ "我们团队每次新建项目都要配 ESLint"    → 值得封装
✅ "这个正则校验逻辑在 5 个项目里复制粘贴" → 必须抽包
✅ "这套组件设计规范需要强制执行"          → 适合做包

1.2 造包的六大真实收益

收益 说明
消除重复 DRY 原则的物理实现,改一处生效 N 处
强制解耦 倒逼你设计清晰的 API 边界
版本可控 语义化版本让升级可预测、可回滚
知识沉淀 把"只有老王知道"变成"任何人都能用"
质量保障 独立测试 + CI = 比业务代码更高的置信度
个人品牌 npm 下载量是最客观的技术影响力指标

1.3 什么时候不该造包?

  • 逻辑只在一个项目中使用 → 用 monorepo workspace
  • 现有成熟方案已满足需求 → 直接用,别造
  • 团队 < 3 人且无复用场景 → 过度工程化
  • 你无法承诺维护 → 不维护的包比没有包更危险

二、npm 包的核心概念

2.1 包的本质

复制代码
一个 npm 包 = 
  package.json(元数据)
  + 入口文件(代码)
  + 类型声明(可选)
  + README(文档)
  + LICENSE(许可证)

2.2 关键术语速查

术语 含义
Registry 包的存储仓库(npmjs.com / 私有源)
Scope @myorg/utils 中的 @myorg,命名空间
Tag latest / next / beta,指向特定版本的别名
SemVer MAJOR.MINOR.PATCH 语义化版本号
Peer Dependency 期望宿主环境提供的依赖
Optional Dependency 安装失败也不报错的依赖
Bin 包提供的 CLI 命令
Exports Map 条件导出,控制不同环境的入口
Side Effects 标记包是否有副作用,影响 Tree Shaking

2.3 模块格式全景

格式 语法 适用环境 Tree Shaking 备注
ESM import/export 浏览器 + Node 18+ ✅ 原生支持 2026 年首选
CJS require/module.exports Node.js 旧版 兼容层
UMD 工厂函数 <script> 标签 CDN 场景
Dual ESM + CJS 同时提供 最大兼容性 ✅ ESM 侧 推荐方案

三、技术选型决策树

复制代码
你要做什么类型的包?
│
├── 纯工具函数 / 算法
│   └── tsup + Vitest + ESM/CJS Dual
│
├── React/Vue/Svelte 组件库
│   └── tsup/unbuild + Storybook + Playwright
│
├── CLI 工具
│   └── tsup + Commander/Oclif + bin 字段
│
├── Vite/Webpack 插件
│   └── unbuild + 对应框架 Plugin API
│
├── Monorepo 内部共享包
│   └── 无需构建,直接 TS 引用 + workspace:*
│
└── 同构库(Node + Browser)
    └── tsup + Exports Map + 条件导入

3.1 构建工具对比(2026)

工具 速度 易用性 灵活性 适用场景
tsup ⚡⚡⚡ ⭐⭐⭐⭐⭐ ⭐⭐⭐ 通用库(首选)
unbuild ⚡⚡⚡ ⭐⭐⭐⭐ ⭐⭐⭐⭐ UnJS 生态 / 高级场景
Rollup ⚡⚡ ⭐⭐⭐ ⭐⭐⭐⭐⭐ 极致定制
Vite Lib ⚡⚡⚡ ⭐⭐⭐⭐ ⭐⭐⭐ 组件库
pkgroll ⚡⚡⚡ ⭐⭐⭐⭐⭐ ⭐⭐ 零配置极简
Bun build ⚡⚡⚡⚡ ⭐⭐⭐ ⭐⭐ Bun 生态

💡 2026 推荐默认选择tsup。它基于 esbuild,开箱即用,覆盖 90% 的场景。

四、从零搭建:项目脚手架

4.1 手动初始化(推荐理解原理)

bash 复制代码
# 创建项目目录
mkdir my-awesome-utils && cd my-awesome-utils

# 初始化 package.json
npm init -y

# 安装开发依赖
npm install -D typescript tsup vitest @types/node

# 创建源码目录
mkdir src
touch src/index.ts

4.2 使用脚手架(快速启动)

bash 复制代码
# 方式一:create-ts-lib(轻量)
npx create-ts-lib my-awesome-utils

# 方式二:UnJS starter
npx giget unjs/template my-awesome-utils

# 方式三:Tsup template
npx degit egoist/tsup-template my-awesome-utils

4.3 推荐项目结构

复制代码
my-awesome-utils/
├── src/
│   ├── index.ts          # 主入口(导出所有公共 API)
│   ├── string.ts         # 字符串工具
│   ├── array.ts          # 数组工具
│   ├── types.ts          # 公共类型定义
│   └── internal/         # 内部实现(不导出)
│       └── helpers.ts
├── tests/
│   ├── string.test.ts
│   └── array.test.ts
├── docs/                 # 文档 / 示例
├── dist/                 # 构建产物(gitignore)
├── package.json
├── tsconfig.json
├── tsup.config.ts
├── vitest.config.ts
├── .gitignore
├── .npmrc
├── LICENSE
└── README.md

五、package.json 深度解析

5.1 完整模板(Dual Package)

// jsonc

json 复制代码
{
  "name": "@myorg/awesome-utils",
  "version": "1.0.0",
  "description": "一套经过实战检验的工具函数集",
  "keywords": ["utils", "typescript", "helpers"],
  "license": "MIT",
  "author": "Your Name <you@example.com>",
  "repository": {
    "type": "git",
    "url": "https://github.com/myorg/awesome-utils.git"
  },
  "bugs": "https://github.com/myorg/awesome-utils/issues",
  "homepage": "https://github.com/myorg/awesome-utils#readme",

  // ⭐ 核心:条件导出映射
  "exports": {
    ".": {
      "import": {
        "types": "./dist/index.d.mts",
        "default": "./dist/index.mjs"
      },
      "require": {
        "types": "./dist/index.d.cts",
        "default": "./dist/index.cjs"
      }
    },
    "./string": {
      "import": {
        "types": "./dist/string.d.mts",
        "default": "./dist/string.mjs"
      },
      "require": {
        "types": "./dist/string.d.cts",
        "default": "./dist/string.cjs"
      }
    }
  },

  // 兼容旧版解析器
  "main": "./dist/index.cjs",
  "module": "./dist/index.mjs",
  "types": "./dist/index.d.ts",

  // ⭐ Tree Shaking 关键
  "sideEffects": false,

  // 发布时包含的文件
  "files": ["dist", "README.md", "LICENSE"],

  // 脚本
  "scripts": {
    "build": "tsup",
    "dev": "tsup --watch",
    "test": "vitest run",
    "test:watch": "vitest",
    "lint": "eslint src/",
    "typecheck": "tsc --noEmit",
    "prepublishOnly": "npm run build && npm run test"
  },

  // 运行时依赖(尽量少)
  "dependencies": {},

  // 开发依赖
  "devDependencies": {
    "typescript": "^5.7.0",
    "tsup": "^8.4.0",
    "vitest": "^3.0.0",
    "@types/node": "^22.0.0"
  },

  // 引擎要求
  "engines": {
    "node": ">=18.0.0"
  }
}

5.2 Exports Map 详解

// jsonc

json 复制代码
// 最简形式(仅 ESM)
"exports": "./dist/index.mjs"

// 双格式
"exports": {
  ".": {
    "import": "./dist/index.mjs",
    "require": "./dist/index.cjs"
  }
}

// 带类型声明(推荐!TypeScript 5.x+ 要求 types 在最前)
"exports": {
  ".": {
    "import": {
      "types": "./dist/index.d.mts",
      "default": "./dist/index.mjs"
    },
    "require": {
      "types": "./dist/index.d.cts",
      "default": "./dist/index.cjs"
    }
  }
}

// 子路径导出
"exports": {
  ".": { ... },
  "./string": { ... },
  "./array": { ... },
  "./package.json": "./package.json"
}

5.3 sideEffects 的重要性

json 复制代码
// ✅ 纯函数库,无副作用 → 允许 Tree Shaking
"sideEffects": false

// ⚠️ 有副作用的文件需要显式声明
"sideEffects": ["./polyfill.ts", "*.css"]

不设 sideEffects: false,打包工具会保守地保留所有代码,用户装了你的包却摇不掉无用代码。

六、代码编写与模块化

6.1 设计原则

复制代码
1. 单一职责:一个函数只做一件事
2. 纯函数优先:相同输入 → 相同输出,无副作用
3. 最小 API 表面积:只导出必要的东西
4. 类型即文档:TypeScript 类型是最好的 API 文档
5. 零依赖目标:每多一个 dependency 就多一份风险

6.2 代码示例

typescript 复制代码
// src/string.ts

/**
 * 将字符串转为 kebab-case
 * @example toKebabCase('helloWorld') // 'hello-world'
 */
export function toKebabCase(str: string): string {
  return str
    .replace(/([a-z])([A-Z])/g, '$1-$2')
    .replace(/[\s_]+/g, '-')
    .toLowerCase();
}

/**
 * 安全截断字符串,不会截断 emoji
 */
export function truncate(str: string, maxLength: number, suffix = '...'): string {
  if ([...str].length <= maxLength) return str;
  return [...str].slice(0, maxLength - 1).join('') + suffix;
}
typescript 复制代码
// src/index.ts --- 统一导出
export { toKebabCase, truncate } from './string';
export { unique, chunk, groupBy } from './array';
export type { DeepPartial, Nullable } from './types';

6.3 避免的常见错误

typescript 复制代码
// ❌ 不要导出内部实现
export function _internalHelper() { ... }

// ❌ 不要用 default export(不利于 Tree Shaking 和重命名)
export default function utils() { ... }

// ❌ 不要在顶层执行副作用
console.log('loaded!');  // 破坏 sideEffects: false
fetch('/api/init');      // 同上

// ✅ 用命名导出
export function toKebabCase() { ... }

// ✅ 副作用封装为显式函数
export function init() {
  console.log('loaded!');
}

七、构建系统配置

7.1 tsup 配置(推荐)

typescript 复制代码
// tsup.config.ts
import { defineConfig } from 'tsup';

export default defineConfig({
  entry: ['src/index.ts', 'src/string.ts', 'src/array.ts'],
  
  // 输出格式
  format: ['esm', 'cjs'],
  
  // 生成类型声明
  dts: true,
  
  // 清理 dist 目录
  clean: true,
  
  // 代码分割(多入口时)
  splitting: true,
  
  // 压缩(生产环境)
  minify: false,  // 库通常不压缩,方便调试
  
  // 外部化依赖(不打入 bundle)
  external: [],
  
  // 目标环境
  target: 'node18',
  
  // sourcemap
  sourcemap: true,
});

7.2 构建命令

bash 复制代码
# 一次性构建
npm run build

# 监听模式(开发时)
npm run dev

# 验证产物
ls -la dist/
# dist/index.mjs      ← ESM
# dist/index.cjs      ← CJS
# dist/index.d.ts     ← 类型声明
# dist/index.d.mts    ← ESM 类型
# dist/index.d.cts    ← CJS 类型

7.3 验证构建产物

bash 复制代码
# 检查 ESM 是否能正常导入
node --input-type=module -e "import { toKebabCase } from './dist/index.mjs'; console.log(toKebabCase('helloWorld'));"

# 检查 CJS 是否能正常 require
node -e "const { toKebabCase } = require('./dist/index.cjs'); console.log(toKebabCase('helloWorld'));"

# 检查类型声明
npx attw --pack .   # Are The Types Wrong? 工具

💡 attw(Are The Types Wrong)是 2025-2026 年必备的类型验证工具,能检测出 90% 的类型声明问题。

八、类型声明生成

8.1 tsconfig.json 配置

json 复制代码
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "ESNext",
    "moduleResolution": "bundler",
    "declaration": true,
    "declarationMap": true,
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "outDir": "dist",
    "rootDir": "src",
    
    // ⭐ 确保类型正确解析
    "resolveJsonModule": true,
    "isolatedModules": true,
    "verbatimModuleSyntax": true
  },
  "include": ["src/**/*.ts"],
  "exclude": ["tests", "dist", "node_modules"]
}

8.2 类型导出的最佳实践

typescript 复制代码
// ✅ 导出有用的类型
export interface Config {
  timeout: number;
  retries: number;
}

// ✅ 导出工具类型
export type DeepPartial<T> = {
  [P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P];
};

// ❌ 不要导出内部类型
interface InternalState { ... }  // 不 export

// ✅ 用 type-only export 避免运行时开销
export type { Config, DeepPartial } from './types';

九、测试策略

9.1 Vitest 配置

typescript 复制代码
// vitest.config.ts
import { defineConfig } from 'vitest/config';

export default defineConfig({
  test: {
    globals: true,
    coverage: {
      provider: 'v8',
      reporter: ['text', 'html', 'lcov'],
      include: ['src/**/*.ts'],
      exclude: ['src/**/*.test.ts', 'src/internal/**'],
    },
  },
});

9.2 测试用例编写

typescript 复制代码
// tests/string.test.ts
import { describe, it, expect } from 'vitest';
import { toKebabCase, truncate } from '../src/string';

describe('toKebabCase', () => {
  it('转换驼峰命名', () => {
    expect(toKebabCase('helloWorld')).toBe('hello-world');
  });

  it('处理连续大写', () => {
    expect(toKebabCase('XMLParser')).toBe('xml-parser');
  });

  it('处理空字符串', () => {
    expect(toKebabCase('')).toBe('');
  });

  it('处理已有连字符', () => {
    expect(toKebabCase('already-kebab')).toBe('already-kebab');
  });
});

describe('truncate', () => {
  it('短字符串不截断', () => {
    expect(truncate('hi', 10)).toBe('hi');
  });

  it('正确截断并添加后缀', () => {
    expect(truncate('hello world', 6)).toBe('hello...');
  });

  it('正确处理 emoji', () => {
    expect(truncate('🎉🎊🎈🎁', 3)).toBe('🎉🎊...');
  });
});

9.3 测试覆盖率目标

复制代码
行覆盖率 ≥ 90%
分支覆盖率 ≥ 85%
每个导出函数至少 3 个测试用例(正常 / 边界 / 异常)

十、文档与示例

10.1 README 模板

markdown 复制代码
# @myorg/awesome-utils

> 一句话描述你的包解决什么问题

[![npm version](https://img.shields.io/npm/v/@myorg/awesome-utils)](...)
[![downloads](https://img.shields.io/npm/dm/@myorg/awesome-utils)](...)
[![license](https://img.shields.io/npm/l/@myorg/awesome-utils)](...)

## 安装

\`\`\`bash
npm install @myorg/awesome-utils
\`\`\`

## 快速开始

\`\`\`typescript
import { toKebabCase } from '@myorg/awesome-utils';

console.log(toKebabCase('helloWorld')); // 'hello-world'
\`\`\`

## API 参考

### toKebabCase(str: string): string
将字符串转为 kebab-case。

| 参数 | 类型 | 说明 |
|------|------|------|
| str | string | 输入字符串 |

**返回值**: `string`

**示例**:
\`\`\`ts
toKebabCase('helloWorld')   // 'hello-world'
toKebabCase('XMLParser')    // 'xml-parser'
\`\`\`

## License

MIT

10.2 文档工具选择

工具 适用场景
README.md 小型库,够用
Typedoc 从 TS 注释自动生成 API 文档
VitePress 中型库,需要指南 + API
Nextra Next.js 风格文档站
Storybook 组件库

十一、版本管理与 Changelog

11.1 语义化版本(SemVer)

复制代码
MAJOR.MINOR.PATCH

MAJOR: 不兼容的 API 变更(删除/重命名导出、改变参数签名)
MINOR: 向后兼容的新功能(新增导出、新增可选参数)
PATCH: 向后兼容的 Bug 修复

11.2 Changesets(推荐)

bash 复制代码
npm install -D @changesets/cli
npx changeset init

工作流

bash 复制代码
# 1. 修改代码后,记录变更
npx changeset add
# → 选择 affected packages
# → 选择版本类型 (major/minor/patch)
# → 写变更描述

# 2. 发布前,自动计算版本号 + 更新 changelog
npx changeset version

# 3. 发布
npx changeset publish

11.3 Conventional Commits

复制代码
feat: 新增 truncate 函数          → MINOR
fix: 修复 toKebabCase 空串崩溃    → PATCH
breaking: 移除 deprecated 方法    → MAJOR
docs: 更新 README                 → 不发版
chore: 升级依赖                    → 不发版

十二、发布流程

12.1 发布前 Checklist

复制代码
□ 所有测试通过
□ 构建成功,产物验证无误
□ 类型声明正确(attw 通过)
□ README 包含安装、用法、API
□ LICENSE 文件存在
□ package.json 字段完整(name/version/description/exports/files/license)
□ .npmignore 或 files 字段排除了不必要文件
□ Changelog 已更新
□ Git tag 已打

12.2 发布命令

bash 复制代码
# 登录 npm
npm login

# 发布公共包
npm publish --access public

# 发布 scoped 包(首次需要 --access public)
npm publish --access public

# 发布 beta 版本
npm publish --tag beta

# 干跑(不真正发布,检查内容)
npm publish --dry-run

12.3 自动化发布(GitHub Actions)

yaml 复制代码
# .github/workflows/release.yml
name: Release

on:
  push:
    branches: [main]

concurrency: ${{ github.workflow }}-${{ github.ref }}

jobs:
  release:
    runs-on: ubuntu-latest
    permissions:
      contents: write
      pull-requests: write
      id-token: write  # npm provenance
    
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 22
          registry-url: 'https://registry.npmjs.org'
      
      - run: npm ci
      - run: npm run build
      - run: npm run test
      
      - name: Create Release PR or Publish
        uses: changesets/action@v1
        with:
          publish: npm run release
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}

12.4 npm Provenance(2025+ 安全特性)

yaml 复制代码
# 在 CI 中启用 provenance
- run: npm publish --provenance --access public

Provenance 为包提供供应链证明,用户在 npmjs.com 上可以看到"此包由 GitHub Actions 构建"的可信标识。

十三、私有包与企业内部实践

13.1 私有 Registry 选型

方案 部署方式 适用规模 特点
Verdaccio 自建 Docker 中小团队 轻量、缓存上游
GitHub Packages SaaS GitHub 团队 与 GH 集成好
Nexus OSS 自建 中大型 多格式支持
Artifactory 自建/SaaS 大型企业 企业级治理
Cloudsmith SaaS 任意 免费额度慷慨

13.2 Verdaccio 快速部署

bash 复制代码
# Docker 一键启动
docker run -d --name verdaccio \
  -p 4873:4873 \
  -v verdaccio-data:/verdaccio/conf \
  verdaccio/verdaccio:6

# 设置私有源
npm set registry http://localhost:4873

# 发布私有包
npm publish --registry http://localhost:4873

13.3 .npmrc 配置

ini 复制代码
# 全局使用公共源
registry=https://registry.npmjs.org/

# scoped 包走私有源
@myorg:registry=https://packages.mycompany.com/
//packages.mycompany.com/:_authToken=${NPM_TOKEN}

# 或者 Verdaccio
@myorg:registry=http://localhost:4873/

13.4 Monorepo 内部包(不发布到 Registry)

jsonc 复制代码
// packages/shared/package.json
{
  "name": "@myorg/shared",
  "private": true,  // ⭐ 防止意外发布
  "version": "0.0.0",
  "main": "./src/index.ts"  // 直接引用源码
}
jsonc 复制代码
// apps/web/package.json
{
  "dependencies": {
    "@myorg/shared": "workspace:*"  // pnpm/yarn workspace 协议
  }
}

十四、实际应用场景

14.1 团队 ESLint/Prettier 配置包

typescript 复制代码
// @myorg/eslint-config/index.ts
import type { Linter } from 'eslint';

const config: Linter.Config[] = [
  {
    rules: {
      'no-console': 'warn',
      '@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
    },
  },
];

export default config;
bash 复制代码
# 其他项目一行接入
npm install -D @myorg/eslint-config

14.2 UI 组件库

typescript 复制代码
// @myorg/ui/Button.tsx
export interface ButtonProps {
  variant: 'primary' | 'secondary' | 'ghost';
  size?: 'sm' | 'md' | 'lg';
  loading?: boolean;
  children: React.ReactNode;
}

export function Button({ variant, size = 'md', loading, children }: ButtonProps) {
  // ...
}

14.3 API Client SDK

typescript 复制代码
// @myorg/api-client
export class ApiClient {
  constructor(private baseUrl: string, private token: string) {}
  
  async getUsers(): Promise<User[]> { ... }
  async createUser(data: CreateUserInput): Promise<User> { ... }
}

// 自动生成自 OpenAPI Spec

14.4 CLI 工具

jsonc 复制代码
// package.json
{
  "bin": {
    "my-cli": "./dist/cli.mjs"
  }
}
typescript 复制代码
// src/cli.ts
#!/usr/bin/env node
import { Command } from 'commander';

const program = new Command();
program
  .command('init')
  .description('初始化项目')
  .action(async () => { /* ... */ });

program.parse();

14.5 Vite 插件

typescript 复制代码
// @myorg/vite-plugin-svg
import type { Plugin } from 'vite';

export default function svgPlugin(options?: Options): Plugin {
  return {
    name: 'vite-plugin-svg',
    transform(code, id) {
      if (!id.endsWith('.svg')) return null;
      // 将 SVG 转为组件
    },
  };
}

14.6 共享工具函数库

复制代码
@myorg/utils
├── string.ts    → toKebabCase, truncate, capitalize
├── array.ts     → unique, chunk, groupBy, flatten
├── date.ts      → formatDate, parseDate, isExpired
├── validation.ts → isEmail, isPhone, isIdCard
└── crypto.ts    → hash, encrypt, generateId

14.7 场景选择矩阵

场景 推荐方案 是否需要发布
跨项目工具函数 npm 包 ✅ 公共/私有
团队规范配置 npm 包 ✅ 私有
UI 组件库 npm 包 ✅ 公共/私有
API SDK npm 包 ✅ 私有
单项目内模块 Monorepo workspace
临时实验代码 Git gist / 本地
CLI 工具 npm 包 + bin ✅ 公共
框架插件 npm 包 ✅ 公共

十五、发布后的运营与维护

15.1 监控指标

指标 工具 关注点
下载量 npmjs.com / npmtrends 增长趋势
依赖方 npmjs.com "Dependents" 谁在用你
安全漏洞 npm audit / Snyk 及时修复
Issue 响应 GitHub < 48h 首次回复
Bundle Size bundlephobia.com 体积变化
类型质量 arethetypeswrong.github.io 类型正确性

15.2 废弃与迁移

bash 复制代码
# 废弃某个版本
npm deprecate @myorg/old-pkg@"<2.0.0" "请迁移到 @myorg/new-pkg"

# 在 README 顶部加迁移指引
# ⚠️ DEPRECATED: 请使用 @myorg/new-pkg 替代

15.3 长期维护承诺

复制代码
✅ 设置 Dependabot / Renovate 自动更新依赖
✅ 定期运行 npm audit
✅ 保持 CI 绿色
✅ 及时回复 Issue(至少确认收到)
✅ 重大变更前发 RFC / Discussion
❌ 不要悄无声息地删包
❌ 不要在不通知的情况下发 breaking change

十六、踩坑指南与最佳实践

16.1 高频踩坑清单

症状 解决
类型丢失 用户安装后无类型提示 检查 exports map 中 types 字段位置(必须在 default 之前)
CJS/ESM 混用 ERR_REQUIRE_ESM 使用 tsup dual format + attw 验证
peerDependency 缺失 用户报运行时错误 明确声明 peerDependencies + peerDependenciesMeta
文件过大 包体积 10MB+ 检查 files 字段,排除 tests/docs/src
循环依赖 构建警告 / 运行时报错 重构模块结构,提取公共层
postinstall 脚本 用户安装失败 避免 postinstall,改用 optionalDependencies
Git 大文件 npm publish 超时 .npmignore 排除 .git、coverage、examples
Scoped 包名冲突 403 Forbidden 确认 scope 已在 npm 创建
Windows 路径 构建产物路径错误 始终使用 path.posix.join
sourcemap 泄露源码 dist 中包含原始代码 确认 sourcemap 不包含 sourcesContent

16.2 黄金法则

复制代码
1. 永远不要 default export
2. 永远设置 sideEffects: false
3. 永远用 attw 验证类型
4. 永远用 --dry-run 先检查再发布
5. 永远写测试再发布
6. 永远不在库里放 console.log
7. 永远用 SemVer
8. 永远写 CHANGELOG
9. 依赖越少越好
10. README 是你最重要的营销材料

十七、2026 年趋势与新工具

17.1 ESM-Only 成为主流

越来越多的包放弃 CJS,仅发布 ESM:

  • Node 22 LTS 完全支持 ESM
  • Deno / Bun 原生 ESM
  • Vite / Next.js / Nuxt 默认 ESM

💡 如果你的用户都是现代环境,大胆 ESM-only。否则仍建议 Dual Package。

17.2 npm Provenance & SBOM

供应链安全成为标配:

  • CI 发布自动附带 provenance
  • Software Bill of Materials 逐步强制
  • 企业采购要求 SBOM 合规

17.3 AI 辅助包开发

复制代码
• Cursor / Copilot 自动生成测试用例
• AI 审查 API 设计的合理性
• 自动生成 Typedoc 文档
• AI 分析 bundle size 优化建议

17.4 新兴构建工具

工具 亮点
Rolldown Rust 写的 Rollup 替代品,速度 10x
Oxc Rust 写的 TypeScript 编译器
Bun build 内置 bundler,极快
pkgroll 零配置,自动推断入口

17.5 WebMCP 与 npm 包

随着 Chrome WebMCP 的推进,未来 npm 包可以直接注册为 AI Agent 可调用的工具:

typescript 复制代码
// 未来的可能性
document.modelContext?.registerTool({
  name: 'format_date',
  description: '格式化日期',
  inputSchema: { ... },
  execute: ({ date, format }) => formatDate(date, format)
});

总结

复制代码
┌───────────────────────────────────────────────────────┐
│                                                       │
│   造 npm 包的本质:                                     │
│   把"经验"变成"可安装的代码"                              │
│                                                       │
│   • 对个人:最好的技术简历                                │
│   • 对团队:最高效的知识复用                              │
│   • 对社区:开源精神的最小实践                            │
│                                                       │
│   记住:                                               │
│   好的 npm 包 ≠ 复杂的 npm 包                           │
│   好的 npm 包 = 解决一个明确问题的最小可靠单元              │
│                                                       │
│   今天就开始你的第一个包吧 🚀                             │
│                                                       │
└───────────────────────────────────────────────────────┘

参考资源

资源 链接
npm 官方文档 https://docs.npmjs.com/
tsup 文档 https://tsup.egoist.dev/
unbuild 文档 https://github.com/unjs/unbuild
Changesets https://github.com/changesets/changesets
Are The Types Wrong https://arethetypeswrong.github.io/
Bundlephobia https://bundlephobia.com/
SemVer 规范 https://semver.org/
Conventional Commits https://www.conventionalcommits.org/
Verdaccio https://verdaccio.org/
npm Provenance https://docs.npmjs.com/generating-provenance-statements
相关推荐
然我42 分钟前
从 Service 到生命周期:Agent Runtime 的插件内核
前端·javascript·agent
Moriarty1236661 小时前
【前端技巧】实现卡片页面容器全屏
前端·css
mmsx1 小时前
osmdroid 地图实战 02|在线底图接入:从 URL 模板到生产级瓦片源工厂(谷歌 + 天地图双案例)
前端·前端框架
用户307140958481 小时前
零依赖实现「形切形」:我用 6200 行原生 Canvas 写了个网页设计工具
前端·ai编程·canvas
兄弟加油,别颓废了。1 小时前
bugku题目
开发语言·前端·python
西西小飞龙2 小时前
npm vs pnpm
前端·npm·node.js
天天进步20152 小时前
Pixelle-Video 源码解析 #17:TTS 语音生成:Edge-TTS、Index-TTS 如何接入?
前端·数据库
晴天162 小时前
Tsup:TypeScript库打包的“零配置极速方案”
前端·javascript·typescript