前言
最近看 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.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
这样的代码。
具体代码就不详细分析了,个别 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) 定义了激活的规定以及选项的组合。能够是 commonmark
、zero
& 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