skillhub-018-antfu
150 行
4.0 KiB
Markdown
150 行
4.0 KiB
Markdown
---
|
||
name: library-development
|
||
description: 使用 tsdown 构建和发布 TypeScript 库。适用于创建 npm 包、配置库打包或设置 package.json 导出字段时。
|
||
---
|
||
|
||
# 库开发
|
||
|
||
| 方面 | 选择 |
|
||
|--------|--------|
|
||
| 打包工具 | tsdown |
|
||
| 输出格式 | 仅纯 ESM(无 CJS) |
|
||
| 类型声明 | 通过 tsdown 生成 |
|
||
| 导出 | 通过 tsdown 自动生成 |
|
||
|
||
## tsdown 配置
|
||
|
||
使用 tsdown 并启用以下选项:
|
||
|
||
```ts
|
||
// tsdown.config.ts
|
||
import { defineConfig } from 'tsdown'
|
||
|
||
export default defineConfig({
|
||
entry: ['src/index.ts'],
|
||
format: ['esm'],
|
||
dts: true,
|
||
exports: true,
|
||
})
|
||
```
|
||
|
||
| 选项 | 值 | 用途 |
|
||
|--------|-------|---------|
|
||
| `format` | `['esm']` | 纯 ESM,无 CommonJS |
|
||
| `dts` | `true` | 生成 `.d.ts` 文件 |
|
||
| `exports` | `true` | 自动更新 `package.json` 中的 `exports` 字段 |
|
||
|
||
### 多个入口点
|
||
|
||
```ts
|
||
export default defineConfig({
|
||
entry: [
|
||
'src/index.ts',
|
||
'src/utils.ts',
|
||
],
|
||
format: ['esm'],
|
||
dts: true,
|
||
exports: true,
|
||
})
|
||
```
|
||
|
||
当运行 `tsdown` 时,`exports: true` 选项会自动生成 `package.json` 中的 `exports` 字段。
|
||
|
||
---
|
||
|
||
## API 稳定性
|
||
|
||
对于已发布的库,锁定公开 API 表面,使意外的破坏性变更在代码审查中以差异形式显现。
|
||
|
||
| 工具 | 用途 |
|
||
|------|---------|
|
||
| [`tsnapi`](https://github.com/antfu/tsnapi) | 通过 Vitest 将运行时导出与类型声明快照到已提交的 `.snapshot.js` / `.snapshot.d.ts` 文件中 |
|
||
| [`tsdown-stale-guard`](https://github.com/antfu-collective/tsdown-stale-guard) | 记录构建输入/输出哈希值,以便针对过时构建运行测试时快速失败 |
|
||
|
||
将两者安装为开发依赖。将 `tsdown-stale-guard` 作为 tsdown 插件接入,使每次构建都记录其哈希值:
|
||
|
||
```ts
|
||
// tsdown.config.ts
|
||
import { defineConfig } from 'tsdown'
|
||
import { StaleGuardRecorder } from 'tsdown-stale-guard'
|
||
|
||
export default defineConfig({
|
||
entry: ['src/index.ts'],
|
||
format: ['esm'],
|
||
dts: true,
|
||
exports: true,
|
||
plugins: [
|
||
StaleGuardRecorder(),
|
||
],
|
||
})
|
||
```
|
||
|
||
从 Vitest 测试中快照公开 API——优先使用 Vitest 辅助函数而非 tsdown/Rolldown 插件,以便构建配置专注于构建。每个文件通过一个文件级别的 `beforeAll(guardStaleBuild)` 来确保构建是最新的,从而使已提交的 `dist/` 永远不会与源码脱节:
|
||
|
||
```ts
|
||
// test/api.test.ts(单个包)
|
||
import { beforeAll } from 'vitest'
|
||
import { snapshotApiPerEntry } from 'tsnapi/vitest'
|
||
import { guardStaleBuild } from 'tsdown-stale-guard'
|
||
|
||
beforeAll(async () => {
|
||
await guardStaleBuild()
|
||
})
|
||
|
||
await snapshotApiPerEntry(new URL('..', import.meta.url).pathname)
|
||
```
|
||
|
||
对于 monorepo,将 `describePackagesApiSnapshots()` 包裹在 `describe` 中,使每个包的测试套件共享一个过时构建检测门:
|
||
|
||
```ts
|
||
// test/api.test.ts(monorepo,从根目录运行)
|
||
import { beforeAll, describe } from 'vitest'
|
||
import { describePackagesApiSnapshots } from 'tsnapi/vitest'
|
||
import { guardStaleBuild } from 'tsdown-stale-guard'
|
||
|
||
describe('packages api', async () => {
|
||
beforeAll(async () => {
|
||
await guardStaleBuild()
|
||
})
|
||
|
||
await describePackagesApiSnapshots()
|
||
})
|
||
```
|
||
|
||
### 更新快照
|
||
|
||
在有意的 API 变更之后,一次性重建并更新两个快照:
|
||
|
||
```bash
|
||
nr build # 重新生成 dist/ 和过时构建哈希值
|
||
nr test -u # 更新 .snapshot.js / .snapshot.d.ts 文件
|
||
```
|
||
|
||
---
|
||
|
||
## package.json
|
||
|
||
纯 ESM 库的必填字段:
|
||
|
||
```json
|
||
{
|
||
"type": "module",
|
||
"main": "./dist/index.mjs",
|
||
"module": "./dist/index.mjs",
|
||
"types": "./dist/index.d.mts",
|
||
"files": ["dist"],
|
||
"scripts": {
|
||
"build": "tsdown",
|
||
"prepack": "pnpm build",
|
||
"test": "vitest",
|
||
"release": "bumpp -r"
|
||
}
|
||
}
|
||
```
|
||
|
||
当 `exports: true` 时,`exports` 字段由 tsdown 管理。
|
||
|
||
### prepack 脚本
|
||
|
||
对于每个公开包,在 `scripts` 中添加 `"prepack": "pnpm build"`。这确保在发布前(例如运行 `npm publish` 或 `pnpm publish` 时)自动构建该包。这可以防止意外发布过时或缺失的构建产物。
|