前言
最近看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