前言
最近看vuePress源码时发现在使用markdownLoader之余使用了大量的 markdown-it
插件,除了社区插件(如高亮代码、代码块包裹、emoji等),同时也自行编写了很多自定义插件(如内外链区分渲染等)。
文章结合源码和自己之前写过的插件来详细解读如何编写一个 markdown-it
插件规则。
简介
markdown-it
是一个辅助解析markdown的库,可以完成从 # test
到
的转换,渲染过程和babel类似为Parse -> Transform -> Generate。test
Parse
source通过3个嵌套的规则链core、block、inline进行解析:
core
core.rule1 (normalize)
...
core.ruleX
block
block.rule1 (blockquote)
...
block.ruleX
inline (applied to each block token with "inline" type)
inline.rule1 (text)
...
inline.ruleX
解析的结果是一个token列表,将传递给renderer以生成html内容。
如果要实现新的markdown语法,可以从Parse过程入手:
可以在 md.core.ruler
、md.block.ruler
、md.inline.ruler
中自定义规则,规则的定义方法有 before
、after
、at
、disable
、enable
等。
// @vuepress/markdown代码片段
md.block.ruler.before('fence', 'snippet', function replace(state, startLine, endLine, silent) {
//...
});
上述代码在 md.block.ruler.fence
之前加入snippet规则,用作解析 <<< @/filepath
这样的代码,它会把其中的文件路径拿出来和 root 路径拼起来,然后读取其中文件内容。
具体代码就不详细分析了,一般parse阶段用到的情况比较少,感兴趣的可以自行查看vuePress源码。
Transform
Token
通过官方在线示例拿 # test
举例,会得到如下结果:
[
{
"type": "heading_open",
"tag": "h1",
"attrs": null,
"map": [
0,
1
],
"nesting": 1,
"level": 0,
"children": null,
"content": "",
"markup": "#",
"info": "",
"meta": null,
"block": true,
"hidden": false
},
{
"type": "inline",
"tag": "",
"attrs": null,
"map": [
0,
1
],
"nesting": 0,
"level": 1,
"children": [
{
"type": "text",
"tag": "",
"attrs": null,
"map": null,
"nesting": 0,
"level": 0,
"children": null,
"content": "test",
"markup": "",
"info": "",
"meta": null,
"block": false,
"hidden": false
}
],
"content": "test",
"markup": "",
"info": "",
"meta": null,
"block": true,
"hidden": false
},
{
"type": "heading_close",
"tag": "h1",
"attrs": null,
"map": null,
"nesting": -1,
"level": 0,
"children": null,
"content": "",
"markup": "#",
"info": "",
"meta": null,
"block": true,
"hidden": false
}
]
使用更底层的数据表示Token,代替传统的AST。区别很简单:
- 是一个简单的数组
- 开始和结束标签是分开的
- 会有一些特殊token (type: "inline") 嵌套token,根据标记顺序(bold, italic, text, ...)排序