介绍 sphinx-js,一种更好的大型 JavaScript 项目文档化方式

到目前为止,还没有很好的工具来文档化大型 JavaScript 项目。 JSDoc 长期以来一直是唯一的竞争者,它具有一些不错的属性

  • 一组定义明确的标签,用于描述常见结构
  • 诸如 Closure Compiler 之类的工具,它与这些标签挂钩

但输出始终是项目中所有内容的字母顺序列表。JSDoc 混淆并展平了您的函数,让新用户推断它们之间的关系,并在脑海中将它们分类成可理解的组。虽然您可以在小型库中使用这种方法,但它在大型库中会失败得很惨,比如 Fathom,它有复杂的全新概念需要解释。对于 Fathom 的手册,我想要的是能够以逻辑方式组织它,用提取的文档穿插解释性文字,并添加完整的章节,这些章节纯粹是概念概述,但又与其他工作相关联。1

Python 世界长期以来一直青睐 Sphinx,这是一种成熟的文档工具,支持多种语言和输出格式,以及一流的索引、词汇表生成、搜索和交叉引用。人们用它写了整本书。通过插件,它支持从 Graphviz 图表到 YouTube 视频的所有内容。但是,它的 JavaScript 支持始终缺乏从代码中提取文档的能力。

现在 sphinx-js 添加了这种能力,让 JavaScript 开发者获得两全其美。

sphinx-js 使用标准的 JSDoc 注释和标签——您无需对源代码进行任何奇怪的操作。(事实上,它将解析和提取委托给 JSDoc 本身,让它能够顺利地应对未来的更改。)您只需要让 Sphinx 在项目根目录中初始化一个 docs 文件夹,将 sphinx-js 作为插件激活,然后就可以使用简单的 reStructuredText 写文档,直到您想调用一些提取的文档,您就使用 sphinx-js 的一个特殊指令,该指令模仿以 Python 为中心的 autodoc 的成熟示例。最简单的示例如下

.. autofunction:: linkDensity

这将找到这个函数……

/**
 * Return the ratio of the inline text length of the links in an element to
 * the inline text length of the entire element.
 *
 * @param {Node} node - The node whose density to measure
 * @throws {EldritchHorrorError|BoredomError} If the expected laws of the
 *     universe change, raise EldritchHorrorError. If we're getting bored of
 *     said laws, raise BoredomError.
 * @returns {Number} A ratio of link length to overall text length: 0..1
 */
function linkDensity(node) {
  ...
}

…并吐出一个格式良好的块,如下所示

(the previous comment block, formatted nicely)

当您想做一些事情,比如添加一系列长示例时,Sphinx 的灵活性就显现出来了。与其在 linkDensity 周围的源代码中杂乱无章,不如将这些额外的材料放到组成手册的 reStructuredText 文件中

.. autofunction:: linkDensity
   
   Anything you type here will be appended to the function's description right
   after its return value. It's a great place for lengthy examples!

还有一种 sphinx-js 指令用于类,无论是 ECMAScript 2015 的糖衣品种还是经典的函数作为构造函数的类型,装饰着 @class。它可以可选地遍历类成员,在遍历过程中进行文档化。您可以控制排序,打开或关闭私有成员,甚至可以通过名称包含或排除特定成员——所有这些都是 Sphinx 对 Python 代码支持的经过深思熟虑的极端情况。以下是一个现实世界的示例,它展示了几个真正的公共方法,同时隐藏了一些框架专用的“朋友”方法

.. autoclass:: Ruleset(rule[, rule, ...])
   :members: against, rules

(Ruleset class with extracted documentation, including member functions)

除了完善的 Python 约定之外,sphinx-js 还支持对同名 JS 实体的引用,这些实体在其他情况下会发生冲突:例如,一个 foo 是一个对象的静态方法,另一个 foo 是同一个对象的实例方法。它使用 JSDoc 的 namepaths 的变体来实现这一点。例如……

  • someObject#foo 是实例方法。
  • someObject.foo 是静态方法。
  • someObject~foo 是一个内部成员,这是第三种可能的重叠类型。

因为 JSDoc 仍然在幕后进行分析,所以我们可以利用它对这些 JS 细微差别的理解。

当然,JS 是一种深度嵌套的语言,所以事情可能很快就会变得非常复杂。谁愿意键入完整的路径来文档化 innerMember

some/file.SomeClass#someInstanceMethod.staticMethod~innerMember

太糟糕了!幸运的是,sphinx-js 使用后缀树索引所有这样的对象路径,所以您可以使用任何唯一地引用对象的的后缀。您可能只需要说 innerMember。或者,如果您的代码库中有两个名为“innerMember”的对象,您可以通过说 staticMethod~innerMember 等来消除歧义,一直向左移动,直到您有一个唯一的命中。这提供了简洁性,并且作为奖励,它让您无需在代码库中移动时修改文档。

凭借 Sphinx 的成熟性和强大功能,以及 JSDoc 无处不在的语法约定和经过验证的分析机制的支持,sphinx-js 是文档化任何大型 JS 项目的绝佳方式。要开始使用,请参阅 自述文件。或者,对于大型示例,请参阅 Fathom 文档。一个特别有用的页面是 规则和规则集参考,它将教程段落与提取的类和函数文档穿插在一起;它的 源代码 可在右上角链接后找到,所有此类页面均如此。

我期待您的成功案例和 错误报告——以及丰富、易于组织的 JS 文档的即将到来的增长!


1JSDoc 有 教程,但它们只是单个 HTML 页面。它们没有特别的能力与其他文档交叉链接,也没有能力调用提取的注释。

关于 Erik Rose

Erik 通过 DXR(Mozilla 代码库的搜索和静态分析)、Fathom(从网页中提取语义)、解析器、新语言和一大堆 Python 库等项目,打破了人类认知和机器执行之间的障碍。

更多 Erik Rose 的文章……


15 条评论

  1. Rudolf Olah

    对此感到非常兴奋。我一直很喜欢在 Python 项目中使用 Sphinx,并且在几乎所有 Django 项目中都引入了它,因为它太有用。JSDoc 还有很多不足之处。在 Ruby 世界中,使用 YARD 非常棒,但它不是 Sphinx。

    Sphinx 的一个优点是它有多种输出格式,我曾经有客户想要 API 的 PDF 文件,能够创建 EPUB 文件也是非常棒的。单页与多页 HTML 构建器/输出格式也非常有用。

    …无论如何,感谢您的出色工作!

    2017 年 7 月 17 日 下午 08:57

    1. Carol Willing

      感谢 Erik 和其他 Mozilla 贡献者。我刚刚在我们的一个 Jupyter 项目中尝试了 sphinx-js。开始使用大约花了 15 分钟。感谢出色的文档和 Fathom 示例。:D

      2017 年 7 月 17 日 下午 19:36

      1. Erik Rose

        感谢您尝试并告知我们!(并且感谢您的 PSF、CPython 和 Jupyter 工作,趁此机会!)

        2017 年 7 月 18 日 上午 06:44

  2. Roy Sutton

    当我开始阅读这篇文章时,我非常兴奋。然后我看到了这一行

    “事实上,它将解析和提取委托给 JSDoc 本身,让它能够顺利地应对未来的更改。”

    JSDoc 很久没有更新了(除非最近发生了变化),并且不支持现代 JavaScript 库。您是否考虑过将 documentation.js 用作解析器后端?

    2017 年 7 月 17 日 下午 14:29

    1. Erik Rose

      我用 ES6 编写了 Fathom,JSDoc 似乎运行良好:类、生成器、扩展运算符、解构赋值、getter 和 setter 等。但是,我很乐意用支持更多内容并得到良好维护的东西来替换它。我们前几天确实考虑了 documentation.js (https://github.com/erikrose/sphinx-js/issues/18),发现它的作者正在经历存在主义的怀疑 (https://macwright.org/2017/06/06/documentation-js.html)。另一个竞争者,它是在 Hacker News 线程中被我注意到的 (https://news.ycombinator.com/item?id=14786554) 是 esdoc (https://github.com/esdoc/esdoc)。它似乎主要是一个人的作品,但它对现代语法功能的支持可能更进一步。您对此有什么想法吗?

      [编辑:公平地说,jsdoc 似乎也是一个人主要的作品。]

      2017 年 7 月 18 日 上午 06:50

      1. Tom MacWright

        您好(这里是 documentation.js 的 Tom)——澄清一下,我的存在主义怀疑是对贡献者而言的,而不是对项目中的代码。我认为 esdoc 的开发者也有同感,我相信 JSDoc 也是如此——他们比我的项目或 esdoc 拥有更多用户,但贡献者却不多。

        这里没有阴影:我认为 sphinx-js 是文档生成的出色且原创的方法。但我想澄清的是,好吧,维护者从其维护者基础中没有得到增长的这种维护者倦怠问题几乎是现代 JavaScript 的暗流,我们唯一能够取得进展甚至生存下去的方法是更多的人参与贡献。所以,好吧,如果有人想贡献或考虑提供帮助,请务必这样做!

        2017 年 7 月 20 日 下午 19:58

        1. Erik Rose

          感谢您的澄清!

          作为几个开源项目的作者,我感同身受。我的一些项目吸引了一些“冠军提交者”,我将他们列为维护者,比如 Bo Bayles 在 more-itertools 上。这是一件令人非常高兴的事。但大多数项目只是按照我能管理的速度进行。

          我犹豫着要不要提建议,但这是一个足够重要的议题,所以我还是想说点什么。我们很多人都有这个问题;它在许多开源社区中是一个越来越突出的主题。我最近的策略是提高我在票据评论上的响应速度,整理好自己的文档,并及时地将提交权限和署名权交予那些我发现其参与能够加速项目发展的人。快速比全面更重要。这似乎有助于建立一个由有能力的提交者组成的社区,他们可以互相支持。你在你的文章中已经涵盖了其中的一部分。

          我怀疑在 JavaScript 世界这个问题更严重,因为在技术上它诱使人们分裂。它甚至缺乏在标准库中找到的最基本的功能。这导致每个人都创建自己的(这个问题你也曾在 Lisps 中见过),导致了即使是基本类型也大量涌现。两个例子是 Map 和 Set 数据类型(它们 *仍然* 只是半成品,缺少诸如交集之类的功能)以及迭代(它是语言中添加的如此之晚的功能,以至于现有的假设数组的代码库将在未来几年内阻碍它的使用)。不兼容的基本类型导致高级库和整个生态系统之间的非互操作性,甚至影响到文档系统,而文档系统又因为试图缓解 JS 中沉默失败和弱类型系统的致命组合而进一步受到困扰。如果我们能够以某种方式建立类似于 Python 世界中存在的“吸引子”,比如 requests 包(每个人都使用它来处理 HTTP 1.x)或 stdlib 中定义的协议(迭代、映射、序列),那将非常棒。我只是不确定该怎么做。

          2017 年 7 月 21 日 下午 1:23

  3. Veera

    看起来很有希望。

    2017 年 7 月 18 日 下午 9:54

  4. Shrike

    如何支持 TypeScript?

    2017 年 7 月 20 日 上午 2:47

    1. Erik Rose

      我很乐意考虑支持 TypeScript 的补丁。或者,如果您仅仅知道一个可以输出可处理的数据格式的 TypeScript 元数据提取器,那也是有价值的信息。将我们使用的提取器设置为可插拔非常简单(参见 https://github.com/erikrose/sphinx-js/issues/17#issuecomment-316668901)。然后支持其他来源就变得像编写一个将它们输出转换为 JSDoc 的 doclet 格式的适配器一样简单。

      2017 年 7 月 20 日 上午 6:57

  5. yahiko

    我非常不愿意安装一些 Python 工具来文档化我的 JS 源代码……它不能完全用 JS/NodeJS 实现吗?

    2017 年 7 月 20 日 上午 4:02

    1. Erik Rose

      说起来容易做起来难。Sphinx 已经拥有 10 年的发展历史,并且非常成熟。因此,我选择编写一个简短的适配器,而不是尝试用 JS 重新创建 360,000 行 Sphinx 代码。Linux 内核团队最近做出了同样的决定,他们采用了 Sphinx 而不是用 C 编写自己的工具。:-)

      2017 年 7 月 20 日 上午 7:03

  6. Axel Rauschmayer

    我的理解是,Sphinx 也规定了 reStructuredText?我更喜欢 Markdown。

    2017 年 7 月 20 日 上午 11:42

    1. Erik Rose

      reStructuredText 拥有比 Markdown 更多的优势,比如它拥有一个单一的、定义明确的语法,可以嵌套任意结构。它还拥有用于扩展语法的“指令”。有 https://sphinx-doc.cn/en/stable/markdown.html,但我不知道它是否支持与 ReST 指令等效的功能,而您需要这种功能来调用 sphinx-js 的功能(或者让任何其他东西超越一个简单的格式化页面)。

      2017 年 7 月 20 日 下午 12:06

    2. Erik Rose

      我仔细研究了一下,你可以在 recommonmark 中使用指令,但是你需要将它们包裹在一个笨重的块中,例如:

      “`eval_rst
      .. autoclass:: recommonmark.transform.AutoStructify
      “`

      更多信息请参见 https://recommonmark.readthedocs.io/en/latest/auto_structify.html#embed-restructuredtext

      2017 年 7 月 20 日 下午 12:14

本文的评论已关闭。