这是用户在 2024-5-23 19:04 为 https://deno.com/blog/document-javascript-package 保存的双语快照页面,由 沉浸式翻译 提供双语支持。了解如何保存?
Skip to main content
跳到主要内容
How to document your JavaScript package

How to document your JavaScript package
如何记录 JavaScript 包

Creating and publishing open source packages is a great way to contribute to the ecosystem and community. You made something cool and want people to use it. But simply publishing your module to a registry and crossing your fingers won’t get users. Helping your users become successful with your package means not only writing concise, descriptive documentation, but also ensuring your users can access the documentation within their workflows (e.g. in VSCode) to save them time.
创建和发布开源包是为生态系统和社区做出贡献的好方法。你做了一些很酷的东西,并希望人们使用它。但是,简单地将模块发布到注册表并交叉手指不会获得用户。帮助用户成功使用您的软件包不仅意味着编写简洁的描述性文档,还意味着确保用户可以在其工作流程(例如在 VSCode 中)访问文档以节省他们的时间。

Thanks to JSDoc it’s easy to write documentation that is coupled with your code and can be consumed by users in a variety of formats. When combined with a modern publishing flow like JSR, you can easily create comprehensive documentation for your package that not only fits within your workflow, but also integrates directly in the tools your users consume your package with. This blog post aims to cover best practices when writing JSDoc-style comments to get your users up and running as quickly as possible:
多亏了 JSDoc,可以轻松编写与代码相结合的文档,并且可以由用户以各种格式使用。当与 JSR 等现代发布流程结合使用时,您可以轻松地为包创建全面的文档,这些文档不仅适合您的工作流程,而且直接集成到用户使用包的工具中。这篇博文旨在介绍编写 JSDoc 样式注释的最佳实践,以使用户尽快启动并运行:

Why JSDoc? 为什么选择 JSDoc?

While a good README answers “why should I use your package?”, good documentation should answer “how can I use your package?”. Users browsing your documentation have a problem they need to solve, and your documentation should provide them with the answer in the fewest clicks and keyboard taps.
一个好的自述文件回答的是“我为什么要使用你的软件包?”,而好的文档应该回答“我如何使用你的软件包?”。浏览文档的用户有他们需要解决的问题,您的文档应该以最少的点击和键盘点击为他们提供答案。

JSDoc is a great way to write reference documentation that is coupled with the code itself and can be consumed by users in a variety of formats, such as HTML, markdown, JSON, or in their IDE or text editor. Here is a quick diagram of an example JSDoc-style comment and how it appears as documentation in various mediums:
JSDoc 是编写参考文档的好方法,它与代码本身相结合,可供用户以各种格式使用,例如 HTML、Markdown、JSON,或者在他们的 IDE 或文本编辑器中使用。下面是一个示例 JSDoc 样式注释的快速图表,以及它在各种媒体中如何作为文档显示:

JSDoc diagram on JSR

When you write JSDoc-style comments in your code and publish to JSR, it will appear formatted on your package’s documentation page on JSR, VSCode tooltips and auto-complete, and in `deno doc` output.
当您在代码中编写 JSDoc 样式的注释并发布到 JSR 时,它将显示在包的文档页面上的 JSR、VSCode 工具提示和自动完成以及“deno doc”输出中。

Writing good JSDoc can improve the success of your package. Before we dive into some best practices, here’s a brief high-level introduction to JSDoc.
编写好的 JSDoc 可以提高包的成功率。在我们深入探讨一些最佳实践之前,这里有一个简短的 JSDoc 高级介绍。

A brief intro to JSDoc
JSDoc 简介

JSDoc turns your comments in your code into a documentation object that can be rendered and displayed in a variety of formats.
JSDoc 将代码中的注释转换为文档对象,该对象可以以各种格式呈现和显示。

JSDoc comments are any block comments that begin with /** and end with */ that precede a block of code. Here’s an example:
JSDoc 注释是以 /** 代码块开头和结尾 */ 的任何块注释。下面是一个示例:

/** Adds two values and returns the sum. */
function sum(value1, value2) {
  return value1 + value2;
}

This JSDoc will then appear as a tooltip in your IDE:
然后,此 JSDoc 将作为工具提示显示在 IDE 中:

Example of JSDoc with sum function

JSDoc comments can span multiple lines. Each line should start with * and should be indented by one space.
JSDoc 注释可以跨越多行。每行应以 开头 * ,并应缩进一个空格。

/**
 * 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;
}

The first paragraph of a JSDoc comment is the most important. It is a summary of the symbol and is shown in tooltips, auto-completions in your editor, and is indexed by search. The first paragraph should be a concise description of the symbol, and should be written in a way that helps users quickly understand what this function does.
JSDoc 注释的第一段是最重要的。它是符号的摘要,显示在工具提示、编辑器中的自动完成中,并通过搜索编制索引。第一段应该是对符号的简明描述,并且应该以帮助用户快速理解此函数的作用的方式编写。

For example, don’t write:
例如,不要写:

/**
 * 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, "_");
}

Instead, concisely describe what the function does:
相反,简明扼要地描述该函数的作用:

/**
 * Replaces all spaces in a string with underscores.
 */
function replaceSpacesWithUnderscores(value) {
  return value.replace(/ /g, "_");
}

Additional information like the implementation details, caveats, or examples should be added in subsequent paragraphs. Because JSDoc supports markdown, you can even use headings to separate different sections.
其他信息,如实现细节、注意事项或示例,应在后续段落中添加。由于 JSDoc 支持 Markdown,因此您甚至可以使用标题来分隔不同的部分。

Simple and concise summaries help users quickly filter through a list of symbols during auto-complete and find the one they need. Once they’ve found the symbol, they can read through the rest to learn about the details.
简单明了的摘要可帮助用户在自动完成期间快速筛选符号列表并找到所需的符号。找到符号后,他们可以通读其余部分以了解详细信息。

Provide good type information
提供良好的类型信息

After the succinct descriptive summary, it’s important to provide good type information for the symbols you are exposing in your package. This serves two main purposes:
在简洁的描述性摘要之后,为要在包中公开的符号提供良好的类型信息非常重要。这有两个主要目的:

  1. It allows auto-completion on parameters and return values in your editor, because the editor knows the types of the parameters and return values.
    它允许在编辑器中自动完成参数和返回值,因为编辑器知道参数和返回值的类型。
  2. It helps users quickly filter through the list of functions to find the one they need. For instance, if they are looking for a function that combines two strings, they can filter out functions that don’t take two strings as parameters.
    它可以帮助用户快速过滤功能列表以找到他们需要的功能。例如,如果他们正在寻找组合两个字符串的函数,他们可以过滤掉不将两个字符串作为参数的函数。

Here, we’ll use TypeScript to add type information. TypeScript, one of the fastest growing programming languages, is a strongly typed language built on JavaScript that enhances code quality and maintainability, while improving developer productivity.
在这里,我们将使用 TypeScript 来添加类型信息。TypeScript 是发展最快的编程语言之一,是一种基于 JavaScript 构建的强类型语言,可提高代码质量和可维护性,同时提高开发人员的工作效率。

/**
 * Adds two values and returns the sum.
 */
export function sum(value1: number, value2: number): number {
  return value1 + value2;
}

In your editor, you’ll see the type information for the parameters and return value when you hover over the function:
在编辑器中,将鼠标悬停在函数上时,将看到参数和返回值的类型信息:

Getting type information for parameters and return value in VSCode on hover

When a user types sum( in their editor, they’ll see the type information for the parameters:
当用户在其编辑器中键入 sum( 内容时,他们将看到参数的类型信息:

Getting type information in the parameters on VSCode

On the return value, you can immediately get completion for methods on the returned number:
在返回值上,您可以立即获得返回 number 的方法的完成:

Getting completion options for number type in VSCode

Tags, tags, tags 标签,标签,标签

JSDoc supports a variety of tags that can be used to provide additional information about your symbols, such as @param for parameters, @returns for the return value, or @typeParam for type parameters. Here’s an example of a function with type information and tags:
JSDoc 支持各种标记,这些标记可用于提供有关符号的其他信息,例如 @param 参数、 @returns 返回值或 @typeParam 类型参数。下面是具有类型信息和标记的函数示例:

/**
 * 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;

In your editor, you’ll see the type information for the parameters and return value, as well as the additional information provided by the tags:

Seeing param info in the hover on VSCode

On JSR, the tags are rendered in HTML. Here’s an example of the @param and @return tags in the JSDoc of the move function in deno_std/fs:

Seeing param and return value information on JSR

Add examples to JSDoc

Examples are another great way to help users quickly understand how to use your library. This is especially useful for functions that have complex behavior or many parameters. Examples can be added to your JSDoc comments using the @example tag:

/**
 * 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;

The best examples are concise and demonstrate the most common use cases of your function. They should be easy to understand and can be copied and pasted into a project.

You can even provide multiple examples if there are multiple use cases worth mentioning. Here’s an example of how multiple examples appear on JSR from the move function of deno_std/fs:

/**
 * (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.
 */

Note the text immediately following @example serves as the title, and the text beneath the example becomes its description on JSR:

How examples appear on JSR

But what should I document?

You should document every symbol your package exports, including functions, classes, interfaces, and type aliases.

This extends beyond just one JSDoc comment per symbol. For classes and interfaces for example, you should document the symbol itself, each method or property on it, including constructors. Here’s an example of an Oak interface with JSDoc comments on its properties:

/** 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;
}

This is how the JSDoc comments on properties appear on JSR:

How property documentation appears on JSR

If your package consists of multiple modules, adding a JSDoc comment to the top of each module file with the @module tag will be helpful. This module comment should include a description and examples of how to use its exported symbols.

Here’s an example of @module in Oak’s application.ts file:

/**
 * 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
 */

In JSR, the first paragraph becomes the description beneath the modules on the main docs page of your package:

Oak module description

Note the main doc page only includes the first paragraph. The subsequent JSDoc comment appears when you click through to the module page:

Oak's application module description

Use markdown for a better documentation experience

Using markdown in JSDoc lets you organize your documentation in a more readable and engaging way. This can help you create documentation that is easier to understand, and allows you to link to external resources or other parts of your documentation using links.

Some useful markdown features you can use in your JSDoc comments include:

  • # my heading for section headings
  • - hello world for bullet points
  • **important** for emphasis
  • _noteworthy_ for italics
  • > quote for block quotes
  • [foo](https://example.com) for links
  • `console.log("foo")` for inline code snippets

On JSR, you can also use [!IMPORTANT] to highlight important information in your documentation that you want to draw attention to.

// 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
 */

This module-level JSDoc comment will appear at the top level in JSR as such:

Markdown in JSDoc example

Sometimes, your documentation refers to another symbol within your package. To make it easy for your users to navigate throughout your docs, you can link within your documentation using the @link , @linkcode , and @linkplain tags. These tags accept a name paths or URL, from which it generates an HTML anchor element. Here’s an example:

/** 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;

In VSCode, the hover tooltip now includes clickable links that will take you directly to the code where that symbol is defined:

Example of linkcode in VSCode hover tooltip

Here’s an example of how @linkcode appears in JSR. In the JSDoc for Oak’s serve function, it references Application, which is becomes a clickable link on JSR:

Example of linkcode in JSR

You can also reference built-in JavaScript objects, like ArrayBuffer, and JSR will automatically link to the relevant MDN documentation.

Keep JSDoc up-to-date with code changes

One benefit about using JSDoc is that writing documentation in the comments happens at the same time as writing the code. That means anytime we need to make a change to a function, interface, or module, we can make the necessary change to the JSDoc with minimal context switching costs.

But how does one make sure that the docs in the comments are updated? Starting with docs, á la docs-driven-development, can help you spec out and reason about the requirements before writing a single line of code. Sometimes, this means catching potential problems earlier and saving time from having to re-write code. (For a more macro-level approach to this, check out “Readme-driven development”.)

If you’ve included code examples in your documentation, you can type check them from the command line with deno test --doc. This is a helpful tool to ensure that your examples within your documentation are up-to-date and working.

For example, building on our sum function from earlier, let’s add an example with a code snippet:

/**
 * 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;
}

Then, we can check the code in this example block by running deno test --doc :

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

Whoops! After we fix the type error in the code in our documentation:

deno test --doc
Check file:///Users/sum.ts$8-13.ts

ok | 0 passed | 0 failed (0ms)

This offers a quick way to type check your code examples in your documentation before publishing.

Audit your JSDoc

If you’re publishing to JSR, it will handle all of the formatting and generation of documentation based off your JSDoc-style comments. However, if you’re interested in using your tooling to audit or test what the JSDoc comment output looks like, here are some suggestions.

  • deno doc <file>: This Deno command will print the JSDoc documentation for each of thefile’s exported members. This command also accepts an--htmlflag that generates a static site with documentation, and a --json flag to generate JSON output that you can display yourself.
  • deno doc --lint`: This command will check for problems, such as missing return type or missing JSDoc comment on a public type. These lints help you write better documentation and catch potential issues before you publish.
  • deno test --doc`: We mentioned this command earlier in this post, but this allows you to easily type check your documentation examples.
  • jsdoc <directory>: JSDoc’s own CLI can generate a static documentation site with its default template, and offers a variety of configuration option flags. If the default template is a bit boring, there are others such as docdash, which provides hierarchical navigation and syntax highlighting.

What’s next?

Writing good JSDocs for your JavaScript package is critical to its success. Let’s recap the best practices:

  • Write a concise summary: The first paragraph of your JSDoc comment should be a concise description of the symbol that helps users quickly understand what it does.
  • Provide good type information: Type information helps users quickly filter through the list of functions and find the one they need.
  • Use tags: Tags like @param, @returns, and @typeParam provide more information about specific parts of your function or class.
  • Add examples: Examples help users quickly understand how to use your library.
  • Document everything: Document every symbol you are exposing in your package, including whole modules if you expose multiple modules.
  • Link internally: Use @link, @linkcode, and @linkplain to link to other parts of your documentation to help users navigate your docs.
  • Test your documentation: Use deno test --doc to type check your documentation examples before publishing, and deno doc --lint to check for issues in your JSDoc comments.

By following these best practices, you can create comprehensive documentation for your package that helps users get up and running with your package as quickly as possible.

🚨️ Read more about JSR 🚨️