关于markdown:编写markdownit的插件和规则

前言

最近看vuePress源码时发现在应用markdownLoader之余应用了大量的markdown-it插件,除了社区插件(如高亮代码、代码块包裹、emoji等),同时也自行编写了很多自定义插件(如内外链辨别渲染等)。
文章联合源码和本人之前写过的插件来具体解读如何编写一个markdown-it插件。

简介

markdown-it是一个辅助解析markdown的库,能够实现从 # test<h1>test</h1> 的转换,渲染过程和babel相似为Parse -> Transform -> Generate。

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.rulermd.block.ruler & md.inline.ruler中自定义规定,规定的定义方法有beforeafteratdisableenable等。

// @vuepress/markdown代码片段
md.block.ruler.before('fence', 'snippet', function replace(state, startLine, endLine, silent) {
  //...
});

上述代码在md.block.ruler.fence之前退出snippet规定,用作解析 <<< @/filepath 这样的代码。
具体代码就不详细分析了,个别parse阶段用到的状况比拟少,感兴趣的能够自行查看vuePress源码。

Transform

Token

通过[官网在线示例](https://markdown-it.github.io/)拿 # 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, …)排序

更具体的数据模型能够通过token类定义查看。

Renderer

token生成后被传递给renderer,renderer会将所有token传递给每个与token类型雷同的rule规定。
renderer的rule规定在md.renderer.rules[name],是参数雷同的函数。

Rules

代表对token的渲染规定,能够被更新或扩大

用法

根底用法

const MarkdownIt = require('markdown-it');
const md = new MarkdownIt();
const result = md.render('# test');

预设

预设(preset)定义了激活的规定以及选项的组合。能够是 commonmarkzero & default

  • commonmark 严格的 CommonMark 模式
  • default 默认的 GFM 模式, 没有 html、 typographer & autolinker 选项
  • zero 无任何规定
// commonmark 模式
var md = require('markdown-it')('commonmark');

// default 模式
var md = require('markdown-it')();

// 启用所有
var md = require('markdown-it')({
  html: true,
  linkify: true,
  typographer: true
});

选项:

参数 类型 默认值 阐明
html Boolean false 在源码中启用 HTML 标签
xhtmlOut Boolean false 应用 ‘/’ 来闭合单标签 (比方 <br />
这个选项只对齐全的 CommonMark 模式兼容
breaks Boolean false 转换段落里的 ‘\n’ 到 <br />
langPrefix String language- 给围栏代码块的 CSS 语言前缀。对于额定的高亮代码十分有用
linkify Boolean false 将相似 URL 的文本主动转换为链接
typographer Boolean false 启用一些语言中立的替换 + 引号丑化
quotes String \ Array “”‘’ 双引号或单引号或智能引号替换对,当 typographer 启用时
highlight Function function (str, lang) { return ''; } 高亮函数,会返回本义的HTML
或 ” 如果源字符串未更改,则应在内部进行本义
如果后果以 <pre … 结尾,外部包装器则会跳过

实例

  • 重写 md.renderer.rules[name]
  • require(‘markdown-it’)().use(plugin1).use(plugin2, opts, …)

在搭建组件库文档过程中,须要判断是否为http结尾的内部链接,内链间接通过a标签跳转绝对路由,外链则新开窗口关上。

const MarkdownIt = require('markdown-it');
const md = new MarkdownIt({
  html: true,
  highlight,
  ...options
});

const defaultRender = md.renderer.rules.link_open || function(tokens, idx, options, env, self) {
  return self.renderToken(tokens, idx, options);
};
md.renderer.rules.link_open = function(tokens, idx, options, env, self) {
  const hrefAttr = tokens[idx].attrGet('href');

  if (/^https?/.test(hrefAttr)) {
    tokens[idx].attrPush(['target', '_blank']); // add new attribute
  }

  return defaultRender(tokens, idx, options, env, self);
};

plugin有 markdown-it-for-inline、markdown-it-anchor 等,以上例为例,如果你须要增加属性,能够在没有笼罩规定的状况下做一些事件。
接下来用markdown-it-for-inline插件来实现上例一样的性能。

const MarkdownIt = require('markdown-it');
const iterator = require('markdown-it-for-inline');
const md = new MarkdownIt({
  html: true,
  highlight,
  ...options
});

md.use(iterator, 'url_new_win', 'link_open', function (tokens, idx) {
  const hrefAttr = tokens[idx].attrGet('href');

  if (/^https?/.test(hrefAttr)) {
    tokens[idx].attrPush(['target', '_blank']); // add new attribute
  }
});

这比间接渲染器笼罩规定要慢,但写法更简略。

参考文档

markdown-it design principles
markdown-it

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

这个站点使用 Akismet 来减少垃圾评论。了解你的评论数据如何被处理