Last updated on

如何编写 unified 插件 (2) remark

接着上一篇,讲一讲 unified 生态,以及如何使用 enter & exit 方式来编写 remark 插件

回顾

前面提到了用 visit 方法来修改节点树,从而编写一个 remark 插件。本文将从之前所述的另一种方法,来阐述一下 micromark 生态以及用这一个模块来编写插件的方法。

以下为 Unified 中处理 markdown 的一个整个流程,一个 processor 中包含 parser, transformer(s), compiler.

| ........................ process ........................... |
| .......... parse ... | ... run ... | ... stringify ..........|
+--------+ +----------+
Input ->- | Parser | ->- Syntax Tree ->- | Compiler | ->- Output
+--------+ | +----------+
X
|
+--------------+
| Transformers |
+--------------+

对于使用下面的语句来进行 markdown to HTML 的转换的过程

unified
.use(remarkParse)
.use(remarkPlugin) // remark 插件
.use(remarkRehype)
.use(rehypePlugin) // rehype 插件
.use(rehypeStringify)
.processSync('# Hello world')
  • remarkParse 充当 parser 的身份,将文本转换为 AST
  • rehypeStringify 充当 compiler 的身份,将 AST 转换为字符串
  • 其他插件均可认为是 transformer

从 RK 处得到的帮助

我在 Bing 上搜索 remark-wikilink,偶然中发现了一篇最近几个月前才写的博客1。这篇文章介绍了一些 unified 的生态,也为我理解这一系列内容提供了一些帮助。在阅读了 Ta 编写的 remark-callout 插件后,感觉这位作者对这一生态是比较了解的,故通过邮件,得到了以下参考:

  • Mdast is the AST of markdown, write a parser plugin that generates mdast.
  • You should know micromark. Creating a micromark extension is of great help.
  • After the micromark plugin generates tokens, mdast-util can transform tokens into your customized mdast nodes by adding extra attributes to your nodes.
  • Micromark common state machine may be helpful when you are implementing your own state machine.

Unified 生态

完整的生态建议查看 unified 官方 README

由 unified 主导的组织有

  • remark 能够对 markdown 和 mdast 进行监测和修改,主导了 remark 插件
  • syntax-tree 为语法分析树奠定基础,定义了 unist,并衍生出 mdast, hast, nlcst, esast 等。这个系列还包含了 mdast-util 和 hast-util
  • rehype 能够对 HTML 和 hast 进行监测和修改,主导了 rehype 插件
  • retext 对英文自然语言进行一些分析
  • micromark 最小的通用 markdown 编译器,是 unified 新的基础

在 micromark 出现后,我们依然可以使用 unist-util-visit 来对语法树进行修改,但是使用基于 micromark 的插件也拥有其优势。在代码结构上,虽然看起来更加复杂了,但是能够从 enter & exit 的角度,更加细粒度地修改语法树。同时,在阅读其源码中,也发现了 toMarkdownfromMarkdown 的区分,使其能够从 AST 恢复到 markdown 的原始样貌。

调用关系

如果参考 remark-gfm 插件,会发现这里面引用的关系有点过于复杂。所以还是从一个稍微简单一点的 remark-math 中来看这几位之间的关系吧。

  • remark-math
    • 引用了 mdast-util-math 中的 mathFromMarkDownmathToMarkdown 方法
    • 引用了 micromark-extension-math 的 math
    • 将这些程序分别加入到 remark 程序中的 fromMarkdownExtensions, toMarkdownExtensions, micromarkExtensions
  • mdast-util-math
    • fromMarkdown 返回了一个对象,其中包含了 enterexit 对象
      • enter 包含了进入某类节点的处理函数,过程中会指定对应语法树节点的一些属性
      • exit 包含了退出某类节点的处理函数,当退出该节点时,在函数的成员方法中可以获取到节点内容的值
    • toMarkdown 返回一个对象,其中包含了 unsafehandlers 属性
      • unsafe 包含了转换回 markdown 所需的字符和所属的阶段
      • handlers 包含了转换回 markdown 的函数处理
    • index.d.ts 类型说明文件中,指定了新规则的数据类型
  • micromark-extension-math
    • syntax.js 指定了从 markdown 转换为 AST 的函数
    • html.js 指定了从 AST 转换为 HTML 的函数
    • index.d.ts 指定了新的 TokenTypeMap

Micromark2

Micromark 可以提供直接从 markdown 转换为 HTML 的功能,也可以获取每个 token 的详细信息,做细粒度的操作。作者说 micromark 是一个比较新的库,而且整个包很小,在 Common Mark 标准和包大小的层面上拥有优势。

Remark 是建立在 micromark 和 AST 上的,remark 支持所有 micromark 的扩展,因此有很多基于 micromark 的插件,都会包装到 remark 上。

Micromark 的插件包含 Syntax Extension 和 Html Extension 两类,前者指定了 markdown 如何解析,后者指定了最终编译为什么样的 HTML.

编写 remark 插件的其他思路 · remark-directive

同时,micromark 的主页建议我们,对于一些有特殊要求的,建议通过 remark-directive 插件来实现,从而避免从头开始写一个插件。

remark-directive 是通过识别 :name[attribute] 的格式,然后自定义的编写一个非常简单的,通过 visit 来修改语法树的 remark 插件,来满足需求。例如我们需要将 :ico[vscode-icons:file-type-js-official] 翻译为 iconify 的图标

关于如何使用 visit 来编写插件,remark-directive 插件的主页有编写指南,也可以参考笔者的上一篇博客。

如果读者的需求比较简单,则本文阅读到此处即可。但我相信依然有硬着头皮继续研究的开发者,想要一探到底,那么我们继续。

create a micromark extension 部分,需要有以下的预备知识

  • 相当熟悉 JavaScript
  • 阅读 unified 的 README 到 API 部分前,以便能够更好地理解 micromark 适用于哪里
  • 阅读 micromark#Architecture 部分,以理解 micromark 如何运作
  • 阅读 micromark#Extending-markdown 部分,以确定扩展 markdown 的语法是否有意义

mdast-util

这部分的导出结构一般为

export function fromMarkdown() {
return {
enter: { ... },
exit: { ... },
}
}
export function toMarkdown() {
return {
unsafe: [ ... ],
handlers: { ... },
}
}

TODO: 暂时还不知道这是干啥用的。

remark

在 wooorm 维护的插件中,通常是将上述声明的 micromark 插件和 mdast-util 都 push 到插件列表中。

TODO: 这部分也不是很懂,待完善

如何编写 remark 插件

这里给出一个使用 micromark 生态编写的插件:remark-callout,可以与上一篇文章中所提到的的插件 @widcardw/remark-callouts 进行一下对比。

Footnotes

  1. 这位作者是 rk-terence,在此表示对该作者的感谢。博客原文:编写 remark 插件实现自定义扩展语法

  2. 该部分信息来自 https://github.com/micromark/micromark#micromark

#remark #markdown #unified #mdx