关于 nostics
nostics 是用于 TypeScript 项目的结构化诊断库,面向需要为错误提供稳定代码、修复建议和文档链接的开发者。它支持报告器、序列化,并可在生产构建中移除仅用于报告的诊断。
主要功能
- 01定义带类型的诊断代码
- 02附加修复建议与文档链接
- 03通过报告器输出诊断
- 04序列化诊断以跨进程传递
- 05提供格式化器与 Vite 构建插件
适用场景
- 01为构建和运行时错误建立稳定代码
- 02将诊断记录发送到文件或 URL
- 03在 Vite 开发期间收集诊断
- 04从生产包中移除仅报告的诊断
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ vercel-labs/ nostics --skill nostics安装后在 Claude Code 中输入/nostics确认已加载。
附带文件references/documentation-site.mdreferences/migration.md
详细介绍
nostics
每种错误情况都会转化为带有稳定代码、文档 URL 和可执行修复方案 fix 的类型化 Diagnostic(继承 Error)。可通过 toJSON() 序列化。
Diagnostic:name(代码)、message/why(插值文本)、fix?、docs?、sources?('file:line:column')、cause?、toJSON()。可以抛出它,使用 instanceof Diagnostic 捕获,并通过 toJSON() 跨进程边界传递。
defineDiagnostics
每个代码都会返回一个可调用句柄。调用句柄会创建一个新的 Diagnostic,按顺序触发所有报告器,并返回该对象。可以 throw 返回值来抛出错误(报告器仍会运行,因此抛出的诊断也会被报告)。
import { createConsoleReporter, defineDiagnostics } from 'nostics'
const diagnostics = /*#__PURE__*/ defineDiagnostics({
docsBase: (code) => `https://nuxt.com/e/${code.replace('NUXT_', '').toLowerCase()}`,
reporters: [/*#__PURE__*/ createConsoleReporter()],
codes: {
NUXT_B1001: {
why: 'Could not compile template.',
fix: 'Check the template for syntax errors.',
},
NUXT_B2011: {
why: (p: { src: string }) => `Invalid plugin "${p.src}". src option is required.`,
fix: 'Pass a string path or an object with a `src` to `addPlugin()`.',
},
NUXT_W9001: { why: 'message', docs: false }, // per-code: string overrides docsBase, false opts out
},
})
docsBasestring | (code) => string | undefined:字符串会追加/${code.toLowerCase()};函数会返回完整 URL(或返回undefined以省略)。codes:每个定义都需要why(string | (params) => string,唯一必填字段,会成为Error.message);fix(string | (params) => string)、docs(string | false)和data(object | (params) => object)为可选。每个代码解析后的data都有类型,并会包含在toJSON()中。reporters:每次调用都会触发;可选。它们的options类型会取交集;报告器要求的选项在调用处也会成为必填项。对于代码仅会被throw的目录,请省略此项(或传入[]):抛出的Diagnostic已经包含消息,因此控制台报告器会先打印一次,而未捕获的错误又会显示一次,造成明显重复。如果一类警告仅需报告,而另一类错误需要抛出,请将它们放在不同的目录中,这样前者可以使用报告器,而后者不需要。- 参数推断:
why、fix和data中的参数类型会取交集,并在调用处变为必填。如果why需要{ src },而fix需要{ date },则调用时必须传入{ src, date }。
调用位置
diagnostics.NUXT_B1001() // no params: report only
diagnostics.NUXT_B2011({ src: '/plugins/bad.ts' }) // params first
diagnostics.NUXT_B2011({
src,
cause: originalError,
sources: ['nuxt.config.ts:42:3'],
}) // runtime fields merge in
diagnostics.NUXT_B2011({ src }, { method: 'error' }) // reporter options second
throw diagnostics.NUXT_B2011({ src }) // raise
cause/sources 放在参数对象中;对于构建/配置诊断,sources 尤其重要,因为 JS 堆栈会指向库内部。使用 if (err instanceof Diagnostic) 捕获,然后读取 .name、.message、.fix、.docs 和 .data。
defineProdDiagnostics() 中不包含定义数据,其 .data 为 undefined。因此,开发/生产环境的三元表达式会将 .data 的类型推断为代码的数据类型或 undefined。
报告器
(diagnostic: Diagnostic, options?: Opts) => void。声明必填的 options 类型后,调用处的第二个参数也会变为必填,并具有对应类型。
| 报告器 | 导入 | 描述 |
|---|---|---|
createConsoleReporter(options?) | nostics | console[method](formatter(d))。method 默认为 'warn'('log'|'warn'|'error'),formatter 默认为 formatDiagnostic;两者都通过选项设置,且 method 也可在每次调用时覆盖。 |
createFetchReporter(url) | nostics/reporters/fetch | 将诊断 JSON 发送到该 URL;发送失败会被忽略。 |
createFileReporter(options?) | nostics/reporters/node | 将 NDJSON 追加到本地文件(默认为 .nostics.log)。 |
createDevReporter() | nostics/reporters/dev | 通过 import.meta.hot.send() 将 toJSON() 发送到 Vite 开发服务器。 |
import type { DiagnosticReporter } from 'nostics'
const sentryReporter: DiagnosticReporter = (d) =>
sentry.captureMessage(d.message, { tags: { code: d.name } })
const audited: DiagnosticReporter<{ priority: number }> = (d, o) =>
audit.log({ name: d.name, priority: o.priority })
// → audited makes diagnostics.X({...}, { priority: 1 }) required and type-checked.
格式化器
| 格式化器 | 导入 | 描述 |
|---|---|---|
formatDiagnostic | nostics | 带 Unicode 装饰的纯文本字符串(内置报告器会使用它)。 |
ansiFormatter(colors) | nostics/formatters/ansi | 彩色输出;接受 Colors 接口(red/yellow/cyan/gray/bold/dim,每个都是 (s) => string)。 |
jsonFormatter | nostics/formatters/json | 通过 toJSON() 执行 JSON.stringify(diagnostic)。 |
formatDiagnostic 输出的详情顺序固定为 fix → sources → see,缺失的字段会省略:
[NUXT_B2011] Invalid plugin `/plugins/bad.ts`. src option is required.
├▶ fix: Pass a string path or an object with a `src` to `addPlugin()`.
├▶ sources: nuxt.config.ts:42:3
╰▶ see: https://nuxt.com/e/b2011
Vite 插件(@nostics/unplugin,开发依赖)
@nostics/unplugin/strip-transform(库作者使用,优化构建)和 @nostics/unplugin/dev-server-collector(应用开发者使用,在开发时收集)。两者都基于 unplugin:.vite()、.webpack()、.rollup() 等。
nosticsStrip会为defineDiagnostics()标记/*#__PURE__*/,并为裸的诊断表达式语句添加NODE_ENV守卫,使它们在生产环境中能被摇树优化移除。选项packageName?(默认为'nostics')。抛出、返回和赋值会保留(它们属于行为)。为便于追踪:使用相对导入,直接导出目录,不要使用工厂包装或深层聚合导出。- 该插件是可选的。如果目录已手动添加注释,即使没有构建转换,也会产生相同的生产输出:在
defineDiagnostics(前以及其中的每个报告器工厂调用前添加/*#__PURE__*/(如本文所有示例所示),并为每个仅用于报告的调用点添加开发环境守卫(process.env.NODE_ENV !== 'production' && diagnostics.CODE(p))。始终在源代码中添加这些注释;如果仅用于报告的调用点没有守卫,而你又想在不修改这些调用点的情况下移除它们,则使用该插件。
- 该插件是可选的。如果目录已手动添加注释,即使没有构建转换,也会产生相同的生产输出:在
nosticsCollector通过 Vite WebSocket 监听createDevReporter()诊断,并通过createFileReporter将其写为 NDJSON。仅适用于 Vite。选项logFile?(默认为.nostics.log)、debug?(默认为!!process.env.DEBUG)。
// vite.config.ts
import { nosticsStrip } from '@nostics/unplugin/strip-transform'
import { nosticsCollector } from '@nostics/unplugin/dev-server-collector'
export default defineConfig({
plugins: [nosticsStrip.vite(), nosticsCollector.vite()],
})
// src/diagnostics.ts — pair the collector with createDevReporter()
import { createConsoleReporter, defineDiagnostics } from 'nostics'
import { createDevReporter } from 'nostics/reporters/dev'
export const diagnostics = /*#__PURE__*/ defineDiagnostics({
reporters: [/*#__PURE__*/ createConsoleReporter(), /*#__PURE__*/ createDevReporter()],
codes: {/* ... */},
})
生产构建
- 仅用于报告的诊断(裸的
diagnostics.X())应被移除:nosticsStrip或手动添加的注释会移除它们,随后未使用的目录会被摇树优化。 - 仍然存在的诊断(
throw/return/赋值/参数)会保留,并且每个都会使整个目录保持可访问,因此所有why/fix内容都会被打包。并非每个库都会在生产环境中抛出异常:如果你的库只报告诊断,移除这些诊断就够了,到此为止。
如果库确实会在生产环境中执行 throw,请在定义时使用带 NODE_ENV 三元表达式的 defineProdDiagnostics,以便使用者的打包工具移除开发环境分支(所有目录文本):
import { defineDiagnostics, defineProdDiagnostics } from 'nostics'
export const diagnostics =
process.env.NODE_ENV === 'production'
? /*#__PURE__*/ defineProdDiagnostics({ docsBase })
: /*#__PURE__*/ defineDiagnostics({
docsBase,
reporters: [/* ... */],
codes: {/* text */},
})
访问到的代码会成为实例的 name,docs 仍从 docsBase 派生;如果存在文档 URL,why 会指向该 URL(否则为空),不会打包 why/fix 文本。默认不包含 reporters(因此仍然存在的 throw 不会同时记录日志,然后又作为未捕获错误重新出现);传入 reporters 可保留生产环境遥测。nosticsStrip 会像追踪直接导出的目录一样追踪这个三元表达式。
约定
- 代码是稳定且完全限定的
PREFIX_XNNNN(B表示构建,R表示运行时,C表示配置,D表示弃用)。已发布的代码绝不可重复使用或重新分配。 - 在测试中,断言诊断代码,而不是其消息或格式化文本。检查抛出的
Diagnostic的.name,或检查报告器输出中的[CODE]。 - 始终提供
why;只要已知解决方案,就提供fix(这是对人类和 Agent 最有用的字段)。运行时值应使用参数化模板,而不是在工厂外进行字符串拼接。 why用于诊断,fix用于补救——应拆分二者,避免内容重叠。why只说明问题所在;fix只说明要采取的措施。报告器会同时打印两者,因此重复出现的措辞毫无意义。当一个源句同时包含两者("A hash must start with '#'. Prefix it with '#'.")时,应将其拆成两部分——诊断放入why,补救措施放入fix——而不是把整句放进why,再在fix中重复。fix也接受参数函数((p) => ...),因此应将包含值的补救措施(use "#${p.hash}")移入其中,而不是留在why中。- 重新抛出时传递
cause;当 JS 堆栈无法反映用户的源代码时,传递sources。 - 按领域拆分大型目录(
diagnostics/build.ts、runtime.ts、config.ts),并从index.ts重新导出;每个defineDiagnostics()共享docsBase,且各自使用独立的代码范围。
参考资料
- 将现有库迁移到 nostics(以诊断代码替换
console.warn/console.error/warn()/抛出的Error,且不改变运行时行为):从头到尾遵循references/migration.md。 - 构建错误代码文档站点(页面模板、部署、Agent 优化):
references/documentation-site.md。