Skip to content

0x02c-如何开发一个markdown-it组件

大约去年,拼拼凑凑写过几个markdown-it插件,差不多忘干净了。
还得再次翻阅文档理解一下:

说明

markdown-it内置了一些默认渲染规则(可以通过打印console.log(Object.keys(md.renderer.rules)) 来看到):

  'code_inline',
  'code_block',
  'fence',
  'image',
  'hardbreak',
  'softbreak',
  'text',
  'html_block',
  'html_inline'
  'code_inline',
  'code_block',
  'fence',
  'image',
  'hardbreak',
  'softbreak',
  'text',
  'html_block',
  'html_inline'

对于此数组中未明确列出的任何元素,将应用其默认规则。

简单理解就是,markdown-it解析文档的时候,将markdown文档的内容分成若干部分(和ast解析不一样)。每个部分会给与一个标识token。

例如:

greed is `good`
greed is `good`

会被渲染成

<p>greed is <code>good</code></p>
<p>greed is <code>good</code></p>

中间处理成token list 如下:

  {
    "type": "paragraph_open",
    "tag": "p",
    "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": "greed is ",
        "markup": "",
        "info": "",
        "meta": null,
        "block": false,
        "hidden": false
      },
      {
        "type": "code_inline",
        "tag": "code",
        "attrs": null,
        "map": null,
        "nesting": 0,
        "level": 0,
        "children": null,
        "content": "good",
        "markup": "`",
        "info": "",
        "meta": null,
        "block": false,
        "hidden": false
      }
    ],
    "content": "greed is `good`",
    "markup": "",
    "info": "",
    "meta": null,
    "block": true,
    "hidden": false
  },
  {
    "type": "paragraph_close",
    "tag": "p",
    "attrs": null,
    "map": null,
    "nesting": -1,
    "level": 0,
    "children": null,
    "content": "",
    "markup": "",
    "info": "",
    "meta": null,
    "block": true,
    "hidden": false
  }
]
  {
    "type": "paragraph_open",
    "tag": "p",
    "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": "greed is ",
        "markup": "",
        "info": "",
        "meta": null,
        "block": false,
        "hidden": false
      },
      {
        "type": "code_inline",
        "tag": "code",
        "attrs": null,
        "map": null,
        "nesting": 0,
        "level": 0,
        "children": null,
        "content": "good",
        "markup": "`",
        "info": "",
        "meta": null,
        "block": false,
        "hidden": false
      }
    ],
    "content": "greed is `good`",
    "markup": "",
    "info": "",
    "meta": null,
    "block": true,
    "hidden": false
  },
  {
    "type": "paragraph_close",
    "tag": "p",
    "attrs": null,
    "map": null,
    "nesting": -1,
    "level": 0,
    "children": null,
    "content": "",
    "markup": "",
    "info": "",
    "meta": null,
    "block": true,
    "hidden": false
  }
]

画个草图描述下:

示例1. 官方示例

给每个ul标签增加一个名为lorem_ipsum的class:

通过在线debug,可以看到看到ul开标签对应的type为 bulle_list_open,由于这个标签没有默认渲染规则里,因此默认渲染的时会使用Renderer.prototype.renderToken来渲染。("For example the rule bullet_list_open is not defined, so when markdown-it tries to parse a list to HTML it defaults to ua generic renderer called Renderer.prototype.renderToken")

js
// 开发插件的最佳实践是保存元素的默认渲染器,并对规则做最小的改动,而不是重新发明轮子:
const proxy = (tokens, idx, options, env, self) => self.renderToken(tokens, idx, options);
const defaultBulletListOpenRenderer = md.renderer.rules.bullet_list_open || proxy;

// 增加新的rules:
md.renderer.rules.bullet_list_open = function(tokens, idx, options, env, self) {
    // tokens: 正在解析的所有的token列表
    // idx: tokens 中当前 token 的 key 对应的数字
    // options: 创建新的 markdown-it 对象时定义的选项(在我们的例子中为 {})
    // env ???
    // self: renderer自身的引用

    // attrJoin 通过空格将值连接到现有属性。如果不存在则创建新属性。
    tokens[idx].attrJoin("class", "lorem_ipsum")
    // Make your changes here ...
    // ... then render it using the existing logic
    return defaultBulletListOpenRenderer(tokens, idx, options, env, self)
};
// 开发插件的最佳实践是保存元素的默认渲染器,并对规则做最小的改动,而不是重新发明轮子:
const proxy = (tokens, idx, options, env, self) => self.renderToken(tokens, idx, options);
const defaultBulletListOpenRenderer = md.renderer.rules.bullet_list_open || proxy;

// 增加新的rules:
md.renderer.rules.bullet_list_open = function(tokens, idx, options, env, self) {
    // tokens: 正在解析的所有的token列表
    // idx: tokens 中当前 token 的 key 对应的数字
    // options: 创建新的 markdown-it 对象时定义的选项(在我们的例子中为 {})
    // env ???
    // self: renderer自身的引用

    // attrJoin 通过空格将值连接到现有属性。如果不存在则创建新属性。
    tokens[idx].attrJoin("class", "lorem_ipsum")
    // Make your changes here ...
    // ... then render it using the existing logic
    return defaultBulletListOpenRenderer(tokens, idx, options, env, self)
};

相关名词

  • nesting
    • { nesting: 1} is an opening tag: <ul>
    • { nesting: -1} is a closing tag: </ul>
    • { nesting: 0} is a self-closing tag: <br />

更多见API文档: markdown-it 14.1.0 API documentation

参考文档