【TS】day28-boss-publish

作者:mario 发布时间: 2026-09-01 阅读量:4 评论数:0

TypeScript 第四周 BOSS 战 — typed-utils 发布准备 + 第 1 个月毕业总结

三周前你手写了 15+ 个工具类型(第 2 周 BOSS 战),本周给它们装上了生产级配置:严格 tsconfig(Day 22)、双格式模块产物(Day 23)、声明文件(Day 24)、类型测试(Day 26)、工具链门禁(Day 27)。今天是毕业典礼:审计 package.json → 构建双格式产物 → npm pack 演练 → 完成"随时可发布"的完整包。然后是重头戏——第 1 个月(TypeScript 深入)的完整复盘:四个星期、28 天、从一个 let 到一条完整类型工程流水线的全部路径。


目录


一、战斗目标与验收清单

今日目标:把 typed-utils 推到"npm publish 只差一个账号"的状态
(真的发布是可选任务——重点是"具备随时发布的能力")
  • [ ] package.json 审计通过(本包元信息 + 双格式入口 + files 白名单)
  • [ ] 构建产出 dist/(ESM + CJS + .d.ts 三件套)
  • [ ] npm pack --dry-run 产物清单干净(无源码泄漏、无多余文件)
  • [ ] 本地 link 验证(另一个项目里真的能用)
  • [ ] 四个周的掌握度自评 + 薄弱点重修计划
  • [ ] 毕业博客:《TypeScript 深入:一个月从语法到工程的完整路径》

二、第一步:package.json 全面审计

2.1 身份信息(用户在 npm 搜索页看到的)

{
  // ===== 身份五要素 =====
  "name": "typed-utils",                    // ⚠ 先去 npmjs.com 查重!
  "version": "0.1.0",                       // 0.x = API 未稳定的约定
  "description": "类型安全的工具类型库:深度只读、路径访问、元组操作等 15+ 工具类型",
  //             ↑ 一句话说清"是什么、给谁用"(搜索权重的一部分)
  "keywords": ["typescript", "utility-types", "type-challenges", "types"],
  "license": "MIT",
  "author": "你的名字 <you@example.com>",

  // ===== 仓库与问题追踪 =====
  "repository": {
    "type": "git",
    "url": "git+https://github.com/you/typed-utils.git"
  },
  "bugs": "https://github.com/you/typed-utils/issues"
}

2.2 入口配置(本周知识的集大成)

{
  // ===== 类型入口(Day 24) =====
  "types": "./dist/index.d.ts",

  // ===== 双格式入口(Day 23) =====
  "exports": {
    ".": {
      "types": "./dist/index.d.ts",     // ⚠ 永远第一位(条件匹配顺序)
      "import": "./dist/index.mjs",     // ESM 用户走这里
      "require": "./dist/index.cjs"     // CJS 用户走这里
    }
  },

  // ===== 老字段兜底(旧工具链的用户) =====
  "main": "./dist/index.cjs",
  "module": "./dist/index.mjs",

  // ===== 发布白名单(Day 23 的 exports 是导入白名单,这是包内容白名单) =====
  "files": ["dist"],

  // ===== 模块声明 =====
  "type": "module",     // .js/.mjs 默认 ESM;.cjs 显式 CJS
  "sideEffects": false  // 纯类型+纯函数 → 允许 tree-shaking 全力摇树
}

2.3 依赖分类审计

{
  // typed-utils 是纯类型库 —— dependencies 应该是空的!
  "dependencies": {},           // ⚠ 运行时零依赖 = 类型库的美德

  "devDependencies": {
    "typescript": "^5.6.0",
    "tsd": "^0.31.0",
    "vitest": "^2.0.0",
    "eslint": "^9.0.0",
    "typescript-eslint": "^8.0.0",
    "prettier": "^3.0.0",
    "husky": "^9.0.0",
    "lint-staged": "^15.0.0"
  }
  // 审计原则:
  // - dependencies:用户运行时【必需】的(发布后会被一起安装)
  // - devDependencies:构建/测试用的(用户不装)
  // - ⚠ 类型库最常见的事故:把 typescript 放进 dependencies
  //   → 用户项目被强塞一份 TS(版本可能与他们项目冲突)
}

2.4 工程脚本全景

{
  "scripts": {
    "typecheck": "tsc -p tsconfig.json --noEmit",
    "lint": "eslint .",
    "format": "prettier --write .",
    "test": "vitest run",
    "test:types": "tsd",
    "build": "npm run build:esm && npm run build:cjs",
    "build:esm": "tsc -p tsconfig.build.json --module ESNext --outDir dist/esm",
    "build:cjs": "tsc -p tsconfig.build.json --module CommonJS --outDir dist/cjs",
    "prepack": "npm run build",     // ⭐ npm pack/publish 前自动构建(见第四节)
    "prepare": "husky"
  }
}

三、第三步:构建双格式产物

3.1 为什么类型库也要双格式

类型库的产物 = .d.ts(类型)+ 少量运行时代码(如果你写了函数工具)

- 纯类型(type/interface 导出)→ 编译后只剩 .d.ts,js 文件是空壳
- 但只要有一个运行时函数 → ESM/CJS 双格式的需求就真实存在
- 双格式的成本很低(两条 tsc 命令),收益是全部用户通吃

3.2 构建配置(tsconfig.build.json,Day 22 的伏笔)

// tsconfig.build.json
{
  "extends": "./tsconfig.base.json",
  "compilerOptions": {
    "rootDir": "./src",
    "declaration": true,          // 产出 .d.ts —— 类型库的产品本体
    "declarationMap": true,       // .d.ts.map:用户可以"跳转到源码"
    "sourceMap": true,
    "module": "ESNext"            // 先出 ESM(命令行可覆盖)
  },
  "include": ["src"]              // 排除 tests —— 老坑点重申
}

3.3 入口文件的模块约定

// src/index.ts —— 公开 API 的唯一出口
export type { Flatten, DeepReadonly, Get, Last, Union } from "./types/tuple.js";
export type { HubTopic, PayloadOf } from "./types/topic.js";
//       ↑ 纯类型导出显式用 type(Day 23 的 isolatedModules 规范)

// 有运行时函数则正常导出:
// export { createValidator } from "./runtime/validator.js";

3.4 产物结构核验

dist/
├── esm/                # ESM 产物
│   ├── index.js        #(空壳或运行时实现)
│   └── index.d.ts
├── cjs/                # CJS 产物
│   ├── index.js
│   └── index.d.ts
├── index.mjs           # 复制自 esm/index.js
├── index.cjs           # 复制自 cjs/index.js
└── index.d.ts          # 复制自 esm/index.d.ts

// 简单方案:构建后用几个 copy 命令整理入口(package.json 指向根级文件)
// 进阶方案:tsup 一步到位(esm/cjs/dts 三合一)—— 了解即可,先掌握手动原理

四、第三步:npm pack 演习

4.1 npm pack --dry-run:发布彩排

npm pack --dry-run
# 输出产物清单(这就是即将进入 .tgz 的全部内容):

# npm notice
# npm notice 📦  typed-utils@0.1.0
# npm notice === Tarball Contents ===
# npm notice 1.1kB  dist/index.mjs
# npm notice 0.8kB  dist/index.cjs
# npm notice 3.2kB  dist/index.d.ts
# npm notice 1.5kB  LICENSE
# npm notice 2.0kB  README.md
# npm notice === Tarball Details ===
# npm notice name:          typed-utils
# npm notice version:       0.1.0
# npm notice total files:   5

4.2 清单审计要点

✅ 必须在包里的:
- dist/ 全部产物
- README.md(npm 页面的门面 —— 没有它的包等于没有说明书)
- LICENSE

❌ 绝不能泄漏的(files 白名单没写它们就不会进):
- src/ 源码          → 检查清单里有没有 src/
- tests/ 测试文件
- tsconfig*.json
- .eslintrc / .prettierrc
- node_modules(npm 默认排除,但 files 写错可能误伤)

⚠ 特殊文件永远包含(白名单拦不住):
- package.json、README、LICENSE / main 入口文件

4.3 prepack:自动化构建

{
  "scripts": {
    "prepack": "npm run build"
    // npm 在 pack/publish 前自动执行 —— 忘记构建导致"发布旧产物"的经典事故
    // 从此根除(CI 缓存场景要留意它的执行成本)
  }
}
# 在 typed-utils 目录:
npm link
# 全局注册了一个软链接

# 在另一个测试项目(比如 playground/):
npm link typed-utils
// playground/test.ts —— 完整模拟用户视角:
import type { Flatten, DeepReadonly } from "typed-utils";

type A = Flatten<readonly [1, 2, 3]>;      // 1 | 2 | 3?
type B = DeepReadonly<{ a: { b: 1 } }>;    // 嵌套 readonly?

// 验收三问:
// 1. 有没有类型提示(exports.types 链路通了吗)
// 2. 提示的内容对不对(.d.ts 的内容正确吗)
// 3. import type 与普通 import 都正常吗(Day 23 知识的真实验证)

五、第四步:发布前终检

发布前 60 秒 checklist(真实按这个顺序走一遍):

□ git status 干净(所有变更已提交)
□ npm run typecheck 通过
□ npm test 通过(vitest + tsd 全绿)
□ npm run lint 无 error(warning 有交代)
□ npm run build 成功,dist 产物齐全
□ npm pack --dry-run 清单审计(上节的 ❌ 项为零)
□ npm link 验证:下游项目提示正常
□ version 遵循 semver(首个版本 0.1.0)
□ README 的安装/导入示例【亲手复制跑通】
□ CHANGELOG.md 记录了 0.1.0(哪怕只有一行)

全部打勾 = 具备发布能力 ✅
(真的 npm publish 是可选项:npm login + npm publish —— 发了记得可以
 npm unpublish 包名@版本 在 72 小时内撤回)

六、第 1 个月毕业总结:四周全景复盘

6.1 四周路径图

第 1 个月:TypeScript 深入(Day 1-28)

第 1 周 类型系统基础(Day 1-7)
  基础类型 → interface/type → 联合/交叉 → 类型收窄/判别联合
  → 枚举/字面量 → keyof/typeof/索引访问 → BOSS:智能工厂类型建模

第 2 周 泛型(Day 8-14)
  泛型函数 → 约束 → 泛型接口/类 → 内置工具类型
  → 条件类型/infer → 映射类型/模板字面量 → BOSS:typed-utils 诞生 ⭐

第 3 周 类型体操(Day 15-21)
  入门方法论 → 元组 → 递归 → 字符串 → 对象 → 协变逆变
  → BOSS:30+ 题通关(Permutation)
  → 产出:四大套路口诀 + Equal/Expect 判题体系

第 4 周 工程化(Day 22-28)★ 本周
  tsconfig → 模块系统 → 声明文件 → 装饰器
  → 类型测试 → 工具链 → BOSS:typed-utils 毕业为可发布包

6.2 四周掌握度自评表

能力域 自评内容 分数(1-5) 薄弱则重修
类型语法 收窄/判别联合/枚举选型 Day 4-5
泛型思维 约束/推断/工具类型手写 Day 8-11
类型体操 medium 独立解题 Day 16-19 对应天
变型理论 协变逆变推导 Day 20
工程配置 tsconfig 每个开关的意义 Day 22
模块系统 互操作报错 30 秒定位 Day 23
声明文件 给任意 JS 库补类型 Day 24
装饰器 四类签名 + 工厂 + 元数据 Day 25
类型测试 tsd 三断言的运用 Day 26
工具链 从零搭规范三件套 Day 27

规则:低于 3 分的能力域 → 下周抽一天重修对应 Day(文档都在,重做练习即可)。

6.3 关键里程碑验证(自问自答)

Q1:为什么泛型比 any 强?
A1:any 是"失忆",泛型是"占位待填"——类型信息全程流动(Day 8 心法)

Q2:分配律什么时候触发?
A2:裸类型参数 + 条件类型(T extends U 时 T 拆开判断)——包裹 [T] 即阻止(Day 21 Permutation)

Q3:函数参数为什么逆变?
A3:漏斗规则——宽进窄出才安全:接收 Animal 的函数才能安全处理 Dog(Day 20)

Q4:import type 解决什么问题?
A4:单文件编译器不用猜"该不该擦除这条 import"(Day 23)

Q5:为什么类型库需要类型测试?
A5:类型回归是编译期事故,运行时测试零发现(Day 26 的 readonly 案例)

每题都能不假思索答出 = 这一个月真的学到手了。

七、毕业博客写作指南

标题:《TypeScript 深入:一个月从语法到工程的完整路径》

推荐大纲

## 一、起点与终点
- Day 1 的我:分不清 interface 和 type
- Day 28 的我:拥有一个带完整质量流水线的可发布类型库

## 二、四周爬坡路径(每周一图)
- 第 1 周:从"标注"到"收窄"——类型开始参与逻辑
- 第 2 周:从"重复"到"工厂"——泛型让类型可复用(typed-utils 诞生)
- 第 3 周:从"使用"到"创造"——30+ 道体操题的方法论
- 第 4 周:从"个人"到"工程"——配置/模块/测试/工具链

## 三、五个认知跃迁(月度最值钱的五句话)
1. 类型是逻辑:收窄与判别联合让分支代码自己证明自己
2. 泛型是类型的函数:占位待填,而不是失忆的 any
3. 类型体操的终点是业务:HubTopic 拦截 100 个运行时 bug
4. 配置是防御工事:strict 八开关各守一道防线
5. 质量要流水线化:从保存到 CI 的四级门禁

## 四、踩坑 Top5 与修复
(从四天的"坑点"章节里挑你最痛的五个)

## 五、typed-utils 的诞生记
- 第 2 周的手写 15 个工具类型
- 第 4 周的类型测试 + 双格式构建 + 发布审计
- (附)npm pack 清单截图

## 六、下个月的路线
Canvas 2D + 实时通信(WebSocket/MQTT)——类型能力将全部用于实战

八、常见坑点与最佳实践

坑点 1:发布后才发现 types 没排第一

症状:用户 install 后"找不到声明文件",但你本地明明正常
原因:本地 TS 从 node_modules 源码猜到了类型;用户只拿到 dist
修复:exports.types 永远第一 + npm link 用【空项目】验证(不能靠源码就近猜测)

坑点 2:types 字段指向了不存在的文件

npm pack 不校验 types 指向的文件是否存在!
终检时必须 ls dist/ 逐个核对 exports 里写的每个路径

坑点 3:忘了 prepack 导致发旧产物

症状:npm 上的版本没有最新修复
原因:手动 build 后又改了代码,publish 时 dist 是旧的
修复:prepack 脚本自动构建(2.4 节)—— 让"忘记"在物理上不可能

坑点 4:semver 语义用错

0.x.y 阶段:minor 变化也可以有 breaking(npm 对 0.x 的约定)
1.0.0 之后:必须严格 semver ——
  breaking → major(删导出、改类型形状)
  新功能 → minor
  修复 → patch
类型库特别提醒:类型收紧( narrowing)对下游就是 breaking!
  Flatten 返回从 any 变具体类型 = patch?不,是 major(下游可能依赖了 any 的宽容)

坑点 5:毕业总结变成流水账

❌ 按天罗列"Day 1 学了 X,Day 2 学了 Y"
✅ 按【认知跃迁】组织:"我原来以为 X,四周后理解成 Y"
毕业博客的价值在于记录思维的转变,不是课程的目录

九、自测挑战

挑战 1(基础):字段对号入座

package.json 的以下字段分别解决用户的什么问题?
a. exports(含 types/import/require 三条件)
b. files
c. sideEffects: false
d. dependencies vs devDependencies
e. prepack

挑战 2(进阶):产物排错

用户反馈:import { Flatten } from "typed-utils" 提示
"Module 'typed-utils' has no exported member 'Flatten'"
但你源码里明明导出了。列出 5 个可能的检查点
(提示:从 exports 链路 → .d.ts 内容 → 构建时序 逐环排查)

挑战 3(实验):完整发布演练

1. npm pack --dry-run → 审计清单(本文 4.2 的 ❌ 项为零)
2. npm pack → 解压 .tgz(tar -tzf)核对内容
3. npm link + 空项目验证三问(4.4 节)
4. (可选)npm publish → npm unpublish 撤回演练
全程截图,作为博客素材

挑战 4(论文级):月度毕业答辩

对着 typed-utils 的最终形态,向橡皮鸭做 10 分钟毕业答辩:
1. 这个库解决什么问题?(每个工具类型一个场景)
2. 它的质量防线有几道?各守什么?
3. 如果下周要发 1.0.0,还差什么?
4. 四周里哪个认知跃迁对你影响最大?为什么?

十、总结与知识图谱

第 4 周 BOSS 战(Day 28)
│
├── package.json 审计
│   ├── 身份五要素(name/version/description/keywords/license)
│   ├── 双格式入口(exports:types → import → require)
│   ├── files 发布白名单
│   └── 纯类型库的依赖美德(dependencies 为空)
│
├── 双格式构建
│   ├── declaration: true(.d.ts 是产品本体)
│   ├── ESM + CJS 两条 tsc 命令
│   └── 产物入口整理(index.mjs/.cjs/.d.ts)
│
├── npm pack 演习
│   ├── --dry-run 清单审计(无 src/tests/配置泄漏)
│   ├── prepack 自动构建(消灭"发旧产物")
│   └── npm link 空项目验证(用户视角三问)
│
└── 第 1 个月毕业
    ├── 四周路径:语法 → 泛型 → 体操 → 工程
    ├── 十能力域自评 + 薄弱重修
    ├── 五个认知跃迁(毕业博客的骨架)
    └── 下一站:Canvas 2D + 实时通信(第 2 个月)

一句话总结:第 1 个月的旅程是一条完整的抛物线——从"给变量贴标签"(类型标注)到"给类型写工厂"(泛型体操),再到"给类型配质量流水线"(工程化);typed-utils 是这条抛物线的物证:第 2 周它只有 15 个手写类型,今天它拥有类型测试、双格式产物、提交门禁和一份干净的 pack 清单——这就是"从会写类型到会做类型工程"的距离,而你走完了


下月预告:第 2 个月进入 Canvas 2D + 实时通信——画板(坐标系/路径/贝塞尔)、图片滤镜(像素操作)、流程图编辑器(进阶)、WebSocket/MQTT 实时数据推送。你四周练出的每一个类型(判别联合守卫事件、模板字面量约束 topic、泛型封装消息总线)都将在工业可视化场景里真刀真枪地服役。

评论