📌 一句话定义
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
> 一句话描述你的包解决什么问题
[](...)
[](...)
[](...)
## 安装
\`\`\`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 |