tsdown 包校验实践:publint 与 attw 双保险配置详解(基于 airi 仓库实证)
【免费下载链接】airi💖🧸 Self hosted, you-owned Grok Companion, a container of souls of waifu, cyber livings to bring them into our worlds, wishing to achieve Neuro-sama's altitude. Capable of realtime voice chat, Minecraft, Factorio playing. Web / macOS / Windows supported.项目地址: https://gitcode.com/GitHub_Trending/ai/airi
本篇围绕 tsdown 的publint与attw两个包校验选项展开:前者在构建后校验package.json字段(exports、main、module、types)与实际产物文件是否一致,后者验证 TypeScript 类型声明在node10、node16、bundler等多种模块解析策略下是否全部正确。读完本篇,你可以在自己的库中配置出「本地开发不打扰、CI 严格拦截」的发布前类型与打包校验流水线,并理解 airi 这类大型 pnpm monorepo 是如何在多个插件包中落地这套校验的。
为什么需要发布前包校验
TypeScript 库发布后最常见的翻车点有两类:
package.json元数据与实际产物不匹配:exports里声明了某个入口,但 dist 目录里没有对应文件;或者main指向 CJS 文件而实际输出是 ESM。这类问题在npm pack之后或下游安装时才暴露。- 类型声明在不同解析策略下表现不一致:同一个
.d.ts文件,在moduleResolution: node10下能解析,换到node16或bundler下就报"解析不到类型"或"CJS 类型指向了 ESM 实现"。tsdown 通过dts: true生成声明文件,但生成不等于正确,需要工具按多套解析策略逐一复核。
tsdown 把两个社区标准工具 publint(对应包结构校验)与 Are the types wrong?(attw,对应类型解析校验)集成进了构建流程,两者都是可选依赖,按需启用。
安装依赖
两个工具都作为开发依赖按需安装:
# 只装 publint npm install -D publint # 只装 attw npm install -D @arethetypeswrong/core # 两个都要 npm install -D publint @arethetypeswrong/core以 airi 仓库为例,它使用 pnpm catalog 统一版本管理,在 pnpm-workspace.yaml 中声明:
catalog: '@arethetypeswrong/core': ^0.18.5 publint: ^0.3.24 tsdown: ^0.22.14根目录 package.json 则通过"publint": "catalog:"与"@arethetypeswrong/core": "catalog:"引用这两个依赖——这意味着 tsdown 在执行publint/attw校验时,能在 node_modules 中找到已安装的核心包,而不是每次临时下载。
publint:package.json 与实际产物的对齐检查
启用
在 tsdown 配置文件中开启即可,构建完成时 tsdown 会对输出目录执行 publint 检查:
export default defineConfig({ publint: true, })配置项
publint也接受对象形式,用于控制报告级别:
export default defineConfig({ publint: { level: 'error', // 'warning' | 'error' | 'suggestion' }, })level决定哪些级别的诊断会被上报并影响构建退出码:设为'error'时仅错误级别的问题会导致构建失败;设为'suggestion'则连建议级别的问题也会输出。
CLI 方式
不想改配置文件时,可以直接在命令行临时开启:
tsdown --publint根据 CLI 参考 中的标志映射规则,--publint等价于设置publint: true,且 CLI 标志的优先级高于配置文件选项。
airi 仓库中的真实用法
airi 的多个插件包把publint: true直接写进了构建配置。以 plugins/airi-plugin-homeassistant/tsdown.config.ts 为例:
import { defineConfig } from 'tsdown' export default defineConfig([ { entry: ['./src/index.ts'], dts: true, unused: true, publint: true, }, ])plugins/airi-plugin-claude-code/tsdown.config.ts 与 plugins/airi-plugin-bilibili-laplace/tsdown.config.ts 采用了同样的模式(dts: true+publint: true+unused: true)。从源码结构看,这套组合拳是 airi 插件体系的标配:dts负责产出声明文件,publint在产物生成后立即核对exports/main/types字段是否指向真实存在的文件,unused则顺带检查未使用的依赖。这也解释了为什么仓库根目录需要把publint列为 devDependency——没有它,publint: true就无法执行。
attw:多解析策略下的类型正确性验证
启用
export default defineConfig({ attw: true, })配置项
export default defineConfig({ attw: { profile: 'node16', // 'strict' | 'node16' | 'esm-only' level: 'error', // 'warn' | 'error' ignoreRules: ['false-cjs', 'cjs-resolves-to-esm'], }, })三个子项各管一件事:
profile:选择校验严格度档位(见下表);level:'warn'只提示不阻断,'error'则让构建失败;ignoreRules:按问题类型白名单,抑制确认可接受的特定告警。
Profile 档位
| Profile | 说明 |
|---|---|
strict | 要求所有解析策略(node10、node16、bundler)全部通过(默认值) |
node16 | 忽略node10解析失败 |
esm-only | 忽略node10与node16-cjs解析失败 |
选择依据通常来自你的用户群:如果你的库以 ESM 为主、面向现代打包器(Vite、Bundler 生态),esm-only是最宽松的合理档位;如果还要兼容 CJS 消费方(Node 生态 CLI 工具),至少选node16;strict适合追求全兼容的公共库。
ignoreRules 完整清单
attw 会按规则类型报告问题,ignoreRules可以精确抑制其中特定类型。完整规则表如下:
| 规则 | 含义 |
|---|---|
no-resolution | 模块完全无法被解析 |
untyped-resolution | 解析成功但没有附带类型 |
false-cjs | 类型声明标记为 CJS,实际实现是 ESM |
false-esm | 类型声明标记为 ESM,实际实现是 CJS |
cjs-resolves-to-esm | CJS 解析路径指向了一个 ESM 模块 |
fallback-condition | 解析时使用了回退/通配条件 |
cjs-only-exports-default | CJS 模块只导出了 default |
named-exports | 类型与实现的命名导出(named exports)不一致 |
false-export-default | 类型声明了 default 导出但实现中不存在 |
missing-export-equals | CJS 类型缺少export = |
unexpected-module-syntax | 文件使用了意外的模块语法 |
internal-resolution-error | 类型检查内部的解析错误 |
配置示例ignoreRules: ['false-cjs', 'cjs-resolves-to-esm']适合一种常见场景:包用双格式(ESM + CJS)构建,CJS 产物的.d.ts在moduleResolution: node16的 CJS 侧解析时会被误判为 ESM 类型,这两条规则正是为此类误报准备的。
CLI 方式
tsdown --attwCI 集成:本地宽松、CI 严格
tsdown 的 CI 感知机制通过检测CI环境变量判断是否处于 CI 环境(当process.env.CI的值不是0或false(不区分大小写)时视为 CI 开启),多个选项因此接受 CI 感知字符串值:
| 值 | 行为 |
|---|---|
true | 始终启用 |
false | 始终禁用 |
'ci-only' | 仅在 CI 启用,本地禁用 |
'local-only' | 仅本地启用,CI 禁用 |
publint与attw都在支持该机制的选项列表中(同列表的还有dts、report、exports、unused、devtools、failOnWarn)。推荐配置是「本地开发不跑校验保持构建快,CI 上严格拦截」:
export default defineConfig({ publint: 'ci-only', attw: { enabled: 'ci-only', profile: 'node16', level: 'error', }, })注意对象形式下用enabled字段承载 CI 感知值。配套的典型 CI 完整配置:
export default defineConfig({ entry: 'src/index.ts', format: ['esm', 'cjs'], dts: true, failOnWarn: 'ci-only', publint: 'ci-only', attw: 'ci-only', })failOnWarn: 'ci-only'是显式 opt-in 的:本地出现警告不阻断,CI 上任何警告都让构建失败,适合在稳定期收紧质量门禁。
前置条件与相关选项
- 两者都要求项目目录下存在
package.json:publint 校验的对象就是package.json字段,attw 也依赖包元数据确定解析条件,没有它则无法运行。 - 两个工具都是可选依赖:不安装
publint/@arethetypeswrong/core时不要启用对应选项,否则校验步骤无法执行。 - 与 CI Environment 文档配合可了解 CI 检测与
'ci-only'/'local-only'的完整语义。 - 与 Package Exports 配合可了解
exports: true如何自动生成package.json的exports字段——自动生成之后,publint 正是核对这套字段与产物一致性的守门人,两者天然形成「生成—校验」闭环。
小结:把校验放进构建流水线的完整姿势
- 安装依赖:
npm install -D publint @arethetypeswrong/core; - 在
tsdown.config.ts中启用publint: 'ci-only'与attw: { enabled: 'ci-only', profile: 'node16', level: 'error' }; - 遇到可解释的误报时,用
ignoreRules精确抑制,而不是整体放宽level; - 需要临时排查时用 CLI 标志
tsdown --publint/tsdown --attw即时开启; - 参考 airi 仓库的做法,把
dts: true+publint: true+unused: true作为插件类库包的标准构建配置,让校验随每次构建自动执行。
【免费下载链接】airi💖🧸 Self hosted, you-owned Grok Companion, a container of souls of waifu, cyber livings to bring them into our worlds, wishing to achieve Neuro-sama's altitude. Capable of realtime voice chat, Minecraft, Factorio playing. Web / macOS / Windows supported.项目地址: https://gitcode.com/GitHub_Trending/ai/airi
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考