Last updated on
如何编写 unified 插件 (2) remark
回顾
前面提到了用 visit
方法来修改节点树,从而编写一个 remark 插件。本文将从之前所述的另一种方法,来阐述一下 micromark 生态以及用这一个模块来编写插件的方法。
以下为 Unified 中处理 markdown 的一个整个流程,一个 processor 中包含 parser, transformer(s), compiler.
对于使用下面的语句来进行 markdown to HTML 的转换的过程
- 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 的角度,更加细粒度地修改语法树。同时,在阅读其源码中,也发现了 toMarkdown
和 fromMarkdown
的区分,使其能够从 AST 恢复到 markdown 的原始样貌。
调用关系
如果参考 remark-gfm 插件,会发现这里面引用的关系有点过于复杂。所以还是从一个稍微简单一点的 remark-math 中来看这几位之间的关系吧。
- remark-math
- 引用了 mdast-util-math 中的
mathFromMarkDown
和mathToMarkdown
方法 - 引用了 micromark-extension-math 的
math
- 将这些程序分别加入到 remark 程序中的
fromMarkdownExtensions
,toMarkdownExtensions
,micromarkExtensions
中
- 引用了 mdast-util-math 中的
- mdast-util-math
- fromMarkdown 返回了一个对象,其中包含了
enter
和exit
对象enter
包含了进入某类节点的处理函数,过程中会指定对应语法树节点的一些属性exit
包含了退出某类节点的处理函数,当退出该节点时,在函数的成员方法中可以获取到节点内容的值
- toMarkdown 返回一个对象,其中包含了
unsafe
和handlers
属性unsafe
包含了转换回 markdown 所需的字符和所属的阶段handlers
包含了转换回 markdown 的函数处理
- 类型说明文件中,指定了新规则的数据类型
- fromMarkdown 返回了一个对象,其中包含了
- micromark-extension-math
- 指定了从 markdown 转换为 AST 的函数
- 指定了从 AST 转换为 HTML 的函数
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
这部分的导出结构一般为
TODO: 暂时还不知道这是干啥用的。
remark
在 wooorm 维护的插件中,通常是将上述声明的 micromark 插件和 mdast-util 都 push 到插件列表中。
TODO: 这部分也不是很懂,待完善
如何编写 remark 插件
这里给出一个使用 micromark 生态编写的插件:remark-callout
,可以与上一篇文章中所提到的的插件 @widcardw/remark-callouts
进行一下对比。
- https://github.com/micromark/micromark#architecture
- https://github.com/micromark/micromark/blob/main/packages/micromark/dev/lib/constructs.js
- https://github.com/micromark/micromark-extension-math/blob/main/dev/lib/math-flow.js
- https://github.com/micromark/micromark-extension-gfm-strikethrough/blob/main/dev/lib/syntax.js
- https://github.com/syntax-tree/mdast-util-math/blob/main/index.d.ts
- https://github.com/micromark/common-markup-state-machine/tree/main/build
- https://github.com/micromark/micromark/blob/main/packages/micromark/dev/lib/constructs.js
Footnotes
-
这位作者是 rk-terence,在此表示对该作者的感谢。博客原文:编写 remark 插件实现自定义扩展语法 ↩
#remark
#markdown
#unified
#mdx