创建和发布开源包是为生态系统和社区做出贡献的好方法。你做了一些很酷的东西并希望人们使用它。但仅仅将模块发布到注册表并祈祷是不会获得用户的。帮助您的用户成功使用您的包不仅意味着编写简洁的描述性文档,还意味着确保您的用户可以在其工作流程中(例如在 VSCode 中)访问文档以节省时间。
借助JSDoc,可以轻松编写与代码相结合的文档,并可供用户以各种格式使用。当与JSR等现代发布流程结合使用时,您可以轻松地为包创建全面的文档,该文档不仅适合您的工作流程,而且还可以直接集成到用户使用包的工具中。这篇博文旨在介绍编写 JSDoc 风格的注释时的最佳实践,以使您的用户尽快启动并运行:
为什么选择 JSDoc?
虽然一个好的自述文件会回答"我为什么 应该使用你的包?",但好的文档应该回答"我如何使用你的包?"。浏览您的文档的用户有一个需要解决的问题,您的文档应该通过最少的点击和键盘敲击为他们提供答案。
JSDoc是编写与代码本身结合的参考文档的好方法,用户可以以各种格式使用,例如 HTML、Markdown、JSON,或者在 IDE 或文本编辑器中使用。下面是 JSDoc 风格注释示例的快速图表,以及它如何在各种媒体中显示为文档:

当您在代码中编写 JSDoc 风格的注释并发布到 JSR 时,它将在 JSR 包的文档页面、VSCode 工具提示和自动完成以及"deno doc"输出中显示格式化内容。
编写好的 JSDoc 可以提高包的成功率。在我们深入探讨一些最佳实践之前,先对 JSDoc 进行简要的高级介绍。
JSDoc 简介
JSDoc 将代码中的注释转换为可以以多种格式呈现和显示的文档对象。
JSDoc 注释是任何以 代码块之前的开头/**
和结尾的块注释。*/
这是一个例子:
php
/** Adds two values and returns the sum. */
function sum(value1, value2) {
return value1 + value2;
}
然后,此 JSDoc 将作为工具提示出现在您的 IDE 中:

JSDoc 注释可以跨越多行。每行应以一个空格开头*
并缩进一个空格。
sql
/**
* Adds two values and returns the sum.
*
* NOTE: JavaScript math uses IEEE 754 floating point arithmetic, so there may
* be some rounding errors when adding two numbers.
*/
function sum(value1, value2) {
return value1 + value2;
}
JSDoc 注释的第一段是最重要的。 它是符号的摘要,显示在工具提示中、编辑器中的自动完成中,并通过搜索建立索引。第一段应该是对符号的简洁描述,并且应该以帮助用户快速理解该函数的作用的方式编写。
例如,不要写:
javascript
/**
* This function takes a string in the first and returns a string. It looks for
* all the spaces in the input string using a regexp, and then replaces them one
* by one with an underscore. The function then returns the modified string.
*/
function replaceSpacesWithUnderscores(value) {
return value.replace(/ /g, "_");
}
相反,简洁地描述该函数的作用:
javascript
/**
* Replaces all spaces in a string with underscores.
*/
function replaceSpacesWithUnderscores(value) {
return value.replace(/ /g, "_");
}
应在后续段落中添加其他信息,例如实现细节、警告或示例。由于 JSDoc 支持 markdown,您甚至可以使用标题来分隔不同的部分。
简单明了的摘要可帮助用户在自动完成过程中快速筛选符号列表并找到他们需要的符号。一旦找到该符号,他们就可以通读其余部分以了解详细信息。
提供良好的类型信息
在简洁的描述性摘要之后,为您在包中公开的符号提供良好的类型信息非常重要。这有两个主要目的:
- 它允许在编辑器中自动完成参数和返回值,因为编辑器知道参数和返回值的类型。
- 它可以帮助用户快速筛选功能列表,找到他们需要的功能。例如,如果他们正在寻找组合两个字符串的函数,他们可以过滤掉不将两个字符串作为参数的函数。
在这里,我们将使用 TypeScript 添加类型信息。 TypeScript 是增长最快的编程语言之一,是一种基于 JavaScript 构建的强类型语言,可增强代码质量和可维护性,同时提高开发人员的工作效率。
typescript
/**
* Adds two values and returns the sum.
*/
export function sum(value1: number, value2: number): number {
return value1 + value2;
}
在编辑器中,当您将鼠标悬停在函数上时,您将看到参数和返回值的类型信息:

当用户sum(
在编辑器中键入时,他们将看到参数的类型信息:

在返回值上,您可以立即完成返回的方法number
:

标签,标签,标签
JSDoc 支持多种标记,可用于提供有关符号的附加信息,例如@param
参数、@returns
返回值或@typeParam
类型参数。以下是带有类型信息和标签的函数示例:
php
/**
* Find a substring in a string and return the index of the first occurrence.
*
* @param value The string that will be searched for the needle.
* @param needle The substring to search for in the string.
* @returns The index of the first occurrence of the needle in the value, or -1 if the needle is not found.
*/
declare function find(value: string, needle: string): number;
在编辑器中,您将看到参数和返回值的类型信息,以及标签提供的附加信息:

在 JSR 上,标签以 HTML 形式呈现。以下是中函数的 JSDoc 中的 @param
和 标签的示例:@return
move``deno_std/fs

将示例添加到 JSDoc
示例是帮助用户快速了解如何使用您的库的另一种好方法。这对于具有复杂行为或许多参数的函数特别有用。可以使用以下标签将示例添加到 JSDoc 注释中 @example
:
ini
/**
* Find a substring in a string and return the index of the first occurrence.
*
* @example Find a substring in a string
* ```ts
* const value = "hello world";
* const needle = "world";
* const index = find(value, needle); // 6
* ```
*
* @example Find a substring in a string that doesn't exist
* ```ts
* const value = "hello world";
* const needle = "foo";
* const index = find(value, needle); // -1
* ```
*/
declare function find(value: string, needle: string): number;
最好的示例简洁明了,展示了函数最常见的用例。它们应该易于理解并且可以复制并粘贴到项目中。
如果有多个用例值得一提,您甚至可以提供多个示例。下面是一个示例,说明 JSR 上的多个示例如何出现在 的函数中move``deno_std/fs
:
markdown
/**
* (truncated for brevity)
* @example Basic usage
* ```ts
* import { move } from "@std/fs/move";
*
* await move("./foo", "./bar");
* ```
*
* This will move the file or directory at `./foo` to `./bar` without
* overwriting.
*
* @example Overwriting
* ```ts
* import { move } from "@std/fs/move";
*
* await move("./foo", "./bar", { overwrite: true });
* ```
*
* This will move the file or directory at `./foo` to `./bar`, overwriting
* `./bar` if it already exists.
*/
请注意,紧随其后的文本@example
用作标题,示例下方的文本成为其在 JSR 上的描述:

但我应该记录什么?
您应该记录包导出的每个符号,包括函数、类、接口和类型别名。
这超出了每个符号只有一个 JSDoc 注释的范围。例如,对于类和接口,您应该记录符号本身、其上的每个方法或属性,包括构造函数。以下是 Oak 接口的示例 ,其属性带有 JSDoc 注释:
css
/** Base interface for application listening options. */
export interface ListenOptionsBase {
/** The port to listen on. If not specified, defaults to `0`, which allows the
* operating system to determine the value. */
port?: number;
/** A literal IP address or host name that can be resolved to an IP address.
* If not specified, defaults to `0.0.0.0`.
*
* __Note about `0.0.0.0`__ While listening `0.0.0.0` works on all platforms,
* the browsers on Windows don't work with the address `0.0.0.0`.
* You should show the message like `server running on localhost:8080` instead of
* `server running on 0.0.0.0:8080` if your program supports Windows. */
hostname?: string;
secure?: false;
/** An optional abort signal which can be used to close the listener. */
signal?: AbortSignal;
}

如果您的包由多个模块 组成,那么在每个模块文件的顶部添加 JSDoc 注释和@module
标签将会很有帮助。该模块注释应包括如何使用其导出符号的描述和示例。
这是Oak文件 @module
中的一个示例:application.ts
ini
/**
* Contains the core concept of oak, the middleware application. Typical usage
* is the creation of an application instance, registration of middleware, and
* then starting to listen for requests.
*
* # Example
*
* ```ts
* import { Application } from "jsr:@oak/oak@14/application";
*
* const app = new Application();
* app.use((ctx) => {
* ctx.response.body = "hello world!";
* });
*
* app.listen({ port: 8080 });
* ```
*
* @module
*/
在 JSR 中,第一段成为 包主文档页面上模块下方的描述:

请注意,主文档页面仅包含第一段。当您单击 模块页面时,会出现后续的 JSDoc 注释:

使用 Markdown 获得更好的文档体验
在 JSDoc 中使用 markdown 可以让您以更具可读性和吸引力的方式组织文档。这可以帮助您创建更易于理解的文档,并允许您使用链接链接到外部资源或文档的其他部分。
您可以在 JSDoc 注释中使用的一些有用的 Markdown 功能包括:
# my heading
对于章节标题- hello world
对于要点**important**
为了强调_noteworthy_
对于斜体> quote
对于块引用[foo](https://example.com)
对于链接console.log("foo")
对于内联代码片段
在 JSR 上,您还可以用来[!IMPORTANT]
突出显示文档中您想要引起注意的重要信息。
php
// Copyright 2018-2024 the oak authors. All rights reserved. MIT license.
/** Middleware that converts the oak specific context to a Fetch API standard
* {@linkcode Request} and {@linkcode Response} along with a modified context
* providing some of the oak functionality. This is intended to make it easier
* to adapt code to work with oak.
*
* There are two functions which will "wrap" a handler that operates off a
* Fetch API request and response and return an oak middleware. The
* {@linkcode serve} is designed for using with the {@linkcode Application}
* `.use()` method, while {@linkcode route} is designed for using with the
* {@linkcode Router}.
*
* > [!IMPORTANT]
* > This is not intended for advanced use cases that are supported by oak,
* > like integrated cookie management, web sockets and server sent events.
* >
* > Also, these are designed to be very deterministic request/response handlers
* > versus a more nuanced middleware stack which allows advanced control.
* > Therefore there is no `next()`.
* >
* > For these advanced use cases, create middleware without the wrapper.
*
* @module
*/
此模块级 JSDoc 注释将出现在 JSR 的顶层,如下所示:

内部链接到文档的其他部分
有时,您的文档引用了包中的另一个符号。为了使您的用户能够轻松地浏览整个文档,您可以 使用、和标签在文档 中 进行链接 。这些标记接受名称路径或 URL,从中生成 HTML 锚元素。这是一个例子:@link``@linkcode``@linkplain
php
/** Options to use when styling text with the {@linkcode print} function. */
export interface StyleOptions {
/** The color to print the message in. */
color: "black" | "red" | "green";
/** Whether to print the message in bold. */
bold: boolean;
/** Whether to print the message in italic. */
italic: boolean;
}
/**
* A function that prints a message to the terminal with the given options.
*
* Note that on some versions of Windows, {@linkcode StyleOptions.color} may not
* be supported in combination with {@linkcode StyleOptions.bold}.
*/
declare function print(message: string, options: StyleOptions): void;
在 VSCode 中,悬停工具提示现在包含可单击的链接,这些链接将直接带您到定义该符号的代码:

@linkcode
下面是JSR 中显示方式的示例。在 Oak 函数的 JSDoc 中 serve
,它引用了Application
,它成为 JSR 上的可点击链接:

您还可以引用内置的 JavaScript 对象,例如ArrayBuffer
,JSR 会自动链接到 相关的 MDN 文档。
使 JSDoc 保持最新的代码更改
使用 JSDoc 的好处之一是在注释中编写文档与编写代码同时进行。这意味着每当我们需要对函数、接口或模块进行更改时,我们都可以以最小的上下文切换成本对 JSDoc 进行必要的更改。
但是如何确保评论中的文档得到更新呢?从文档开始,即文档驱动的开发,可以帮助您在编写一行代码之前规范和推理需求。有时,这意味着更早发现潜在问题并节省重新编写代码的时间。 (有关更宏观的方法,请查看 "自述文件驱动的开发".)
如果您在文档中包含了代码示例,则可以在命令行中使用 键入检查它们 deno test --doc
。这是一个有用的工具,可确保文档中的示例是最新的且有效。
例如,在sum
之前的函数的基础上,我们添加一个带有代码片段的示例:
bash
/**
* Adds two values and returns the sum.
*
* @example
* ```ts
* import { sum } from "jsr:@deno/sum";
* const finalValue = sum(1, "this is a string"); // 3
* ```
*/
export function sum(value1: number, value2: number): number {
return value1 + value2;
}
然后,我们可以通过运行以下命令来检查此示例块中的代码deno test --doc
:
bash
deno test --doc
Check file:///Users/sum.ts$8-13.ts
error: TS2345 [ERROR]: Argument of type 'string' is not assignable to parameter of type 'number'.
const finalValue = sum(1, "this is a string");
~~~~~~~
at file:///Users/main.ts$8-13.ts:2:27
哎呀!在我们修复文档中代码中的类型错误后:
bash
deno test --doc
Check file:///Users/sum.ts$8-13.ts
ok | 0 passed | 0 failed (0ms)
这提供了一种在发布之前对文档中的代码示例进行类型检查的快速方法。
审核你的 JSDoc
如果您要发布到 JSR,它将根据您的 JSDoc 风格注释处理所有格式设置和文档生成。但是,如果您有兴趣使用工具来审核或测试 JSDoc 注释输出的样子,这里有一些建议。
deno doc <file>
file
:此 Deno 命令将打印每个导出成员的JSDoc 文档。此命令还接受一个--html
用于生成带有文档的静态站点的标志,以及一个--json
用于生成您可以自己显示的 JSON 输出的标志。deno doc --lint
`:此命令将检查问题,例如缺少返回类型或缺少公共类型的 JSDoc 注释。这些 lint 可以帮助您编写更好的文档并在发布之前发现潜在的问题。deno test --doc
`:我们在本文前面提到过此命令,但这使您可以轻松地键入检查文档示例。jsdoc <directory>
:JSDoc自己的CLI 可以使用其默认模板生成静态文档站点,并提供各种配置选项标志。如果默认模板有点无聊,还有其他模板,例如 docdash,它提供分层导航和语法突出显示。
下一步是什么?
为 JavaScript 包编写良好的 JSDoc 对其成功至关重要。让我们回顾一下最佳实践:
- 编写简洁的摘要:JSDoc 注释的第一段应该是符号的简洁描述,以帮助用户快速理解它的作用。
- 提供良好的类型信息:类型信息可帮助用户快速筛选函数列表并找到他们需要的函数。
- 使用标签 :诸如
@param
、@returns
、 之类的标签@typeParam
提供有关函数或类的特定部分的更多信息。 - 添加示例:示例可以帮助用户快速了解如何使用您的库。
- 记录所有内容:记录您在包中公开的每个符号,如果公开多个模块,则包括整个模块。
- 内部链接 :使用
@link
、@linkcode
和@linkplain
链接到文档的其他部分,以帮助用户导航您的文档。 - 测试您的文档 :用于
deno test --doc
在发布之前对您的文档示例进行类型检查,并deno doc --lint
检查 JSDoc 注释中的问题。
通过遵循这些最佳实践,您可以为您的软件包创建全面的文档,帮助用户尽快启动并运行您的软件包。