使用 @effect/docgen 为 Effect 项目自动生成 API 文档:安装配置、JSDoc 约定与源码实现解析
2026/9/15 11:16:45 网站建设 项目流程

使用 @effect/docgen 为 Effect 项目自动生成 API 文档:安装配置、JSDoc 约定与源码实现解析

【免费下载链接】t3code项目地址: https://gitcode.com/GitHub_Trending/t3/t3code

@effect/docgen 是 Effect 生态中一个"固执己见"(opinionated)的文档生成器,它读取 TypeScript 源码中的 JSDoc 注释,自动产出面向 GitHub Pages 的 Markdown API 文档站点,并内置示例代码的类型检查与运行校验。本文以仓库中的 .repos/effect-smol/packages/tools/docgen/README.md 为主体,结合 docgen 的源码实现,完整讲解其安装、零配置使用、docgen.json配置、JSDoc 标签约定、CLI 参数以及"解析—校验—生成"的底层流水线,帮助你在自己的 Effect 项目中一键生成高质量、可持续维护的 API 文档。

项目定位:为什么需要一个专门的文档生成器

@effect/docgen面向 Effect 项目设计,核心思路是把"写文档"这件事沉淀为一套可强制的工程规范:

  • 注释即文档:通过标准的 JSDoc 标签(@category@example@since@deprecated@internal@ignore)描述每个模块导出,生成器负责排版输出;
  • 示例即测试@example中的代码片段会被tsc类型检查,还能用tsx实际运行,配合 Node.js 内置assert模块实现"文档即测试";
  • 规范可强制执行:通过enforceDescriptions/enforceExamples/enforceVersion开关,让 CI 在缺少描述、示例或版本标注时直接失败。

它借鉴了 docs-ts 可以看到其自身就是 Effect 风格代码:CLI 用effect/unstable/cli构建,配置加载用 Effect 的Config/ConfigProvider,整个程序跑在Effect运行时上。

安装与快速上手

环境要求

[!WARNING] 使用@effect/docgen需要Node.js v18 或以上

这一点在源码中有双重印证:package.json 声明了"engines": { "node": ">=18.0.0" },且bin入口直接指向需要原生process与子进程能力的脚本(bin.ts)。

安装

rc版本安装为开发依赖:

npm install -D @effect/docgen@rc

作为 peer 依赖,项目还需要 TypeScript 与 tsx(用于运行示例):

  • typescript:>=5.8.2 <7.0.0
  • tsx:>=4.19.3 <5.0.0

三步完成接入

  1. (可选)创建docgen.json配置文件,并让编辑器获得 JSON Schema 校验支持:
{ "$schema": "node_modules/@effect/docgen/schema.json" }
  1. package.json中添加脚本
{ "scripts": { "docgen": "docgen" } }
  1. 运行
npm run docgen

零配置默认行为

默认情况下 docgen 无需任何配置即可工作:src目录搜索 TypeScript 文件,向docs目录输出生成的 Markdown 文档(README 原文:"By default, docgen will search for files in thesrcdirectory and will output generated files into adocsdirectory")。srcDir/outDir的默认值在 Configuration.ts 的ConfigurationSchema中有明确声明(default: "src"default: "docs")。

生成的文档站点包含哪些文件

从 Core.ts 的getMarkdownwriteMarkdown可以看到,每次运行会生成/维护四类产物:

产物路径(相对 outDir)说明
首页index.mdJekyll front matter(title: Homenav_order: 1
模块索引modules/index.mdtitle: Moduleshas_children: true
GitHub Pages 配置_config.yml写入remote_themesearch_enabledaux_links
模块文档modules/<路径>.md每个源码模块一个文件,含目录(TOC)与分类导出版块

其中_config.yml是"智能合并"的:若目标文件已存在,resolveConfigYML 会保留用户已有内容,只替换remote_theme:search_enabled:和 Auxiliary Links 三处;若不存在则生成完整默认文件。此外,写入前会先删除 outDir 下所有旧的**/*.ts.md文件,避免残留脏文档。

支持的 JSDoc 标签

docgen 通过doctrine解析 JSDoc(见 Parser.ts 的parseComment),支持的标签如下:

标签说明默认值
@category在生成的文档中为相关模块导出分组'utils'
@example为源码提供使用示例。所有示例都会用tsc做类型检查;还可以用tsx实际运行,并可用 Node.js 的assert模块做即时测试
@since标注某段源码最近一次更新的库版本
@deprecated标记弃用,生成的文档中对应模块或函数名会显示为删除线false
@internal阻止 docgen 为标注的代码块生成文档;若tsconfig.jsonstripInternaltrue,TypeScript 也不会为其输出声明
@ignore阻止 docgen 为标注的代码块生成文档

标签行为背后的源码逻辑

  • @internal@ignore的处理:在 Parser.ts 中,shouldIgnore(doc)直接检查doc.tags中是否存在"internal""ignore",命中即跳过该导出。注意二者语义差异:@internal还附带 TypeScript 编译器stripInternal的配合(README 明确说明),而@ignore纯粹是 docgen 层面的排除。
  • @deprecated的渲染Doc模型保存deprecated标签数组(Domain.ts),Printer 据此在名称上渲染删除线。
  • @example的测试能力:示例代码块会被抽取成独立.ts文件先tsc类型检查、再(可选)tsx执行,执行环境中可直接import assert from "node:assert"做断言——即 README 所述"on-the-fly testing"。

docgen.json 配置详解

docgen默认是零配置 CLI 工具,需要自定义行为时,在项目根目录创建docgen.json。该文件由 Effect Schema 定义并校验(Configuration.ts 的ConfigurationSchema),对应的 JSON Schema 文件位于 schema.json,注意其中additionalProperties: false,多余的字段会被判定为非法配置。

docgen.json遵循以下 TypeScript 接口(README 原文):

interface Config { readonly projectHomepage?: string readonly srcLink?: string readonly srcDir?: string readonly outDir?: string readonly theme?: string readonly enableSearch?: boolean readonly enforceDescriptions?: boolean readonly enforceExamples?: boolean readonly enforceVersion?: boolean readonly tscExecutable?: string readonly exclude?: ReadonlyArray<string> readonly parseCompilerOptions?: string | Record<string, unknown> readonly examplesCompilerOptions?: string | Record<string, unknown> }

参数说明与默认值

参数说明默认值
projectHomepage在生成文档的 Auxiliary Links(右上角导航区)中链接到项目主页package.json中的homepage字段
srcLink链接到项目源码{projectHomepage}/blob/main/src/
srcDirdocgen 搜索待解析 TypeScript 文件的目录'src'
outDirdocgen 输出 Markdown 文档的目录'docs'
theme写入生成_config.yml的 GitHub Pages 主题'mikearnaldi/just-the-docs'
enableSearch是否在生成_config.yml中开启搜索true
enforceDescriptions是否强制要求每个模块导出都有描述false
enforceExamples是否强制要求每个模块导出都有@example(注意:模块级文档不强制)false
enforceVersion是否强制要求每个模块导出都有@sincetrue
tscExecutabledocgen 以编程方式调用编译器时使用的 TypeScript 编译器可执行文件路径'tsc'
excludeglob 字符串数组,指定排除在文档之外的源码文件[]
parseCompilerOptions解析源码用的 tsconfig 选项(或 tsconfig 文件路径){}
examplesCompilerOptions示例代码用的 tsconfig 选项(或 tsconfig 文件路径){}

两个值得注意的细节:

  1. enforceVersion默认开启:也就是说,除非显式设置"enforceVersion": false,所有导出的函数、类、常量、接口、类型别名、命名空间都必须带@since标签,否则 docgen 会报错终止。这与 Checker.ts 中checkEntry的逻辑一致:@since缺失即生成 "Missing@sincetag" 错误,并附带@babel/code-frame定位的源码片段。
  2. parseCompilerOptionsexamplesCompilerOptions可为字符串:传入字符串时被视为 tsconfig 文件路径,docgen 用tsconfck解析(readTSConfig),从中提取compilerOptions;传入对象时直接作为编译选项使用(Configuration.ts 的resolveCompilerOptions)。

配置优先级:CLI > 环境变量 > docgen.json > 默认值

从 Configuration.ts 的configProviderLayer可以看出完整的取值链条:

  1. CLI 标志优先(loadConfiguration中先处理命令行参数);
  2. 其次读环境变量:以DOCGEN_为前缀、常量大小写命名(如DOCGEN_ENABLE_SEARCH=false),对应ConfigProvider.fromEnv({ env }).pipe(ConfigProvider.nested("DOCGEN"), ConfigProvider.constantCase)
  3. 再读docgen.json文件
  4. 最后落到内置默认值enableSearch: trueenforceDescriptions: falseenforceExamples: falseenforceVersion: true等)。

其中projectNameprojectHomepage必须来自package.json(Schema 要求namehomepage均为字符串,缺失会直接报错),srcLink未配置时由 homepage 推导。

官方示例配置

{ "exclude": ["src/internal/**/*.ts"], "parseCompilerOptions": { "noEmit": true, "strict": true, "skipLibCheck": true, "moduleResolution": "Bundler", "target": "ES2022", "lib": ["ES2022", "DOM"], "paths": { "@effect/<project-name>": ["./src/index.js"], "@effect/<project-name>/test/*": ["./test/*.js"], "@effect/<project-name>/examples/*": ["./examples/*.js"], "@effect/<project-name>/*": ["./src/*.js"] } }, "examplesCompilerOptions": { "noEmit": true, "strict": true, "skipLibCheck": true, "moduleResolution": "Bundler", "target": "ES2022", "lib": ["ES2022", "DOM"], "paths": { "@effect/<project-name>": ["../../src/index.js"], "@effect/<project-name>/test/*": ["../../test/*.js"], "@effect/<project-name>/examples/*": ["../../examples/*.js"], "@effect/<project-name>/*": ["../../src/*.js"] } } }

对该配置的实操解读:

  • exclude: ["src/internal/**/*.ts"]@internal标签相互配合,是 Effect 生态的标准做法——内部实现既不出现在文档中,也不参与解析;
  • parseCompilerOptions.paths@effect/<project-name>映射到本地./src/*.js,保证解析阶段能正确解析模块间引用;
  • examplesCompilerOptions.paths使用../../相对路径,是因为示例代码会被抽取到outDir/examples/下执行(见下文),需要从该目录回指项目根部的src
  • 未显式设置的runExamples保持默认false,即示例只做类型检查、不实际运行。

CLI 命令行参数

docgen 的 CLI 由effect/unstable/cli构建(CLI.ts),所有配置项都暴露为命令行标志,用法形如docgen --src ./lib --out ./api-docs

标志对应配置说明
--homepage <url>projectHomepage项目主页链接(显示在生成文档的 Auxiliary Links)
--srcLink <url>srcLink项目源码链接
--src <dir>srcDir搜索待解析 TS 文件的目录(必须存在)
--out <dir>outDir输出 Markdown 的目录
--theme <theme>theme生成文档使用的 Jekyll 主题
--disable-search/--enable-searchenableSearch是否在生成文档中启用搜索
--enforce-descriptionsenforceDescriptions强制要求每个模块导出有描述
--enforce-examplesenforceExamples强制要求每个模块导出有@example(模块级文档不强制)
--no-enforce-version/--enforce-versionenforceVersion是否强制要求@since标签
--run-examplesrunExamples是否实际执行源码中发现的示例
--exclude <glob>excludeglob 数组,排除指定文件(可多次传入)
--parse-tsconfig-file <file>parseCompilerOptions解析源码使用的 tsconfig 文件路径
--parse-compiler-options <json>parseCompilerOptions解析源码使用的编译器选项(JSON 字符串)
--examples-tsconfig-file <file>examplesCompilerOptions示例使用的 tsconfig 文件路径
--examples-compiler-options <json>examplesCompilerOptions示例使用的编译器选项(JSON 字符串)

需要注意的约束(CLI.ts 的loadConfiguration中明确校验):

  • --parse-tsconfig-file--parse-compiler-options不能同时使用,否则抛出InvalidValue错误;
  • --examples-tsconfig-file--examples-compiler-options同理;
  • 内联 JSON 选项会先经Schema.decodeUnknownEffect校验,必须是合法的 JSON 对象,否则报错并提示expected: a JSON record

示例代码的类型检查与运行:文档即测试

@example与描述文本中的代码围栏(code fence)会被 docgen 抽取、类型检查并(可选)运行,这是它区别于普通文档工具的核心能力。完整流水线位于 Core.ts 的typeCheckAndRunExamples

  1. 抽取extractFencedCode用正则匹配```~~~两种围栏;只有语言标记以ts/typescript开头、且不包含skip-type-checking元数据的代码块才会被抽取(支持```ts title="..."这类带元数据的围栏)。若发现未闭合的围栏会输出警告。
  2. 落盘:示例写入outDir/examples/,文件命名规则为<模块路径>-<kind>-<导出名>-<序号>.ts(kind 包括moduleclassinterfacetypealiasconstantfunctionnamespaceexport以及类的method/staticmethod),同时生成一个聚合入口index.ts(逐行import所有示例)和examples/tsconfig.json(内容来自examplesCompilerOptions)。
  3. 类型检查:用tsc --noEmit --project <outDir>/examples/tsconfig.json检查全部示例;非零退出码即报错,错误信息中会携带tsc的 stdout。
  4. 运行:仅当runExamplestrue(配置或--run-examples)时,用tsx --tsconfig ... index.ts执行所有示例,执行失败同样导致 docgen 失败。
  5. 清理:每次运行前后都会删除outDir/examples目录,保证示例永远从最新源码重新生成(目录中的文件标记为可覆写isOverwriteable: true)。

在 Windows 上,工具会自动改用.cmd变体(tsc.cmd/tsx.cmd)并以 shell 模式执行。

如果想跳过某段代码的类型检查,可在围栏元数据中加入skip-type-checking

```ts skip-type-checking // 这段代码不会被 docgen 抽取与类型检查 ```

工作原理解析:解析 → 校验 → 生成的流水线

docgen 的入口程序定义在 Core.ts 的program中,整体分为两个并发 Fiber,最后Fiber.joinAll汇合:

读取源码文件(glob src/**/*.ts + exclude) │ ▼ 解析为模块模型(Parser:ts-morph AST + doctrine JSDoc) │ ┌────┴─────────────────────────────┐ ▼ ▼ 校验模块(Checker) 生成 Markdown(Printer) │ ├─ 强制 @since ├─ 每个模块一个 .md │ ├─ 强制描述(可选) ├─ 首页 / 模块索引 / _config.yml │ └─ 强制示例(可选) └─ 清理旧 *.ts.md ▼ 类型检查并(可选)运行示例

解析层(Parser.ts)

基于ts-morphProject+doctrine的 JSDoc 解析,构建出 Domain.ts 中的Module模型。每个模块模型包含classesinterfacesfunctionstypeAliasesconstantsexportsnamespaces七类成员,解析规则值得了解:

  • 函数:既支持export function声明,也支持export const fn = () => ...箭头函数形式的导出变量;签名统一渲染为declare const <name>: <类型>
  • 常量:导出的、非函数类型的变量声明(如export const x = 1);
  • :导出类的实例方法、静态方法(均取第一个重载的 JSDoc)与实例属性,属性签名保留readonly与可选标记;
  • 命名空间:支持嵌套命名空间,文档中通过前缀-命名空间名的方式区分配平后的重名导出(extractPrefixedNestedNamespaces);
  • 显式导出:包括命名导出(export { x } from ...,支持前置 JSDoc 注释)与export * from .../export * as ns from ...(后者会被自动注入一段描述:"Re-exports all named exports from the ... module");
  • 模块级文档:取源码文件第一个语句前的/** ... */注释作为模块描述;
  • 解析选项createProject会把parseCompilerOptions合并进默认的{ strict: true, moduleResolution: "node" },再交给 ts-morph 建立工程。

校验层(Checker.ts)

checkModules遍历所有模块,对每个导出调用checkEntry按配置检查:

  • enforceDescriptions→ 缺少描述报错;
  • enforceExamplesdoc.examples.length === 0报错;
  • enforceVersion(默认开启)→ 缺少@since报错。

错误信息使用@babel/code-frame输出带行列定位的源码片段。类的方法、静态方法、属性以及嵌套命名空间中的成员会递归校验,但方法/属性级不强制@sinceenforceVersion: false)。

生成层(Printer.ts + markdown-toc)

  • 每个模块生成docs/modules/<路径>.md,front matter 中写入nav_order
  • @effect/markdown-toc生成 TOC,插入到<!-- toc -->占位符处,产出"Exports Grouped by Category"目录;
  • 最终内容经prettier格式化后写盘。

常见问题

Q:重载函数能否分别记录每个重载的文档?

A:不能。docgen 在生成输出时只使用函数第一个重载的文档。这一行为在 Parser.ts 的getFunctionDeclarationJSDocs中得到印证:fd.getOverloads()非空时,直接取第一个重载的 JSDoc。因此请把完整的用法说明写在第一个重载上,或在实现上补充模块级文档。

更多参考

  • 配置 Schema:schema.json
  • 配置加载与默认值:Configuration.ts
  • 主流水线:Core.ts
  • JSDoc 解析:Parser.ts
  • 规范校验:Checker.ts
  • 数据模型:Domain.ts
  • 命令行入口:CLI.ts

许可证

MIT License。

【免费下载链接】t3code项目地址: https://gitcode.com/GitHub_Trending/t3/t3code

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询