markdown-it|markdown-it 插件如何写(二)
前言
在 《一篇带你用 VuePress + Github Pages 搭建博客》中,我们使用 VuePress 搭建了一个博客,最终的效果查看:TypeScript 中文文档。
在搭建博客的过程中,我们出于实际的需求,在《VuePress 博客优化之拓展 Markdown 语法》中讲解了如何写一个 markdown-it
插件,又在 《markdown-it 原理解析》中讲解了 markdown-it
的执行原理,本篇我们将讲解具体的实战代码,帮助大家更好的写插件。
Parse
markdown-it
的渲染过程分为两部分,Parse
和 Render
,如果我们要实现新的 markdown 语法,举个例子,比如我们希望解析 @ header
为 header
,就可以从 Parse
过程入手。
在 markdown-it 的官方文档里可以找到自定义 parse 规则的方式,那就是通过 Ruler
类:
var md = require('markdown-it')();
md.block.ruler.before('paragraph', 'my_rule', function replace(state) {
//...
});
这句话的意思是指在
markdown-it
的解析 block 的一组规则中,在 paragraph
规则前插入一个名为 my_rule
的自定义规则,我们慢慢来解释。首先是
md.block.ruler
,除此之外,还有 md.inline.ruler
、md.core.ruler
可以自定义其中的规则。然后是
.before
,查看 Ruler 相关的 API,还有 after
、at
、disable
、enable
等方法,这是因为规则是按照顺序执行的,某一规则的改变可能会影响其他规则。接着是
paragraph
,我怎么知道插入在哪个规则前面或者后面呢?这就需要你看源码了,并没有文档给你讲这个……如果是
md.block
,查看 parse_block.js,如果是md.inline
,查看 parse_inline.js,如果是 md.core
,查看 parse_core.js,我们以md.block
为例,可以看到源码里写了这些规则:var _rules = [
// First 2 params - rule name & source. Secondary array - list of rules,
// which can be terminated by this one.
[ 'table',require('./rules_block/table'),[ 'paragraph', 'reference' ] ],
[ 'code',require('./rules_block/code') ],
[ 'fence',require('./rules_block/fence'),[ 'paragraph', 'reference', 'blockquote', 'list' ] ],
[ 'blockquote', require('./rules_block/blockquote'), [ 'paragraph', 'reference', 'blockquote', 'list' ] ],
[ 'hr',require('./rules_block/hr'),[ 'paragraph', 'reference', 'blockquote', 'list' ] ],
[ 'list',require('./rules_block/list'),[ 'paragraph', 'reference', 'blockquote' ] ],
[ 'reference',require('./rules_block/reference') ],
[ 'html_block', require('./rules_block/html_block'), [ 'paragraph', 'reference', 'blockquote' ] ],
[ 'heading',require('./rules_block/heading'),[ 'paragraph', 'reference', 'blockquote' ] ],
[ 'lheading',require('./rules_block/lheading') ],
[ 'paragraph',require('./rules_block/paragraph') ]
];
最后是
function replace(state)
,这里函数的参数其实不止有 state
,我们查看任何一个具体规则的 parse 代码,就比如 heading.js
:module.exports = function heading(state, startLine, endLine, silent) {
var ch, level, tmp, token,
pos = state.bMarks[startLine] + state.tShift[startLine],
max = state.eMarks[startLine];
// ...
};
可以看出除了
state
,还有 startLine
、endLine
、silent
,而具体这其中的代码怎么写,其实最好的方式就是参考这些已经实现的代码。实例讲解 接下来我们以解析
@ header
为 header
为例,讲解其中涉及的代码,这是要渲染的内容:var md = window.markdownit();
// md.block.ruler.before(...)var result = md.render(`@ header
contentTwo
`);
console.log(result);
正常它的渲染结果是:
@ header
contentTwo
现在期望的渲染结果是:
headercontentTwo
我们来看看如何实现,先参照 header.js 的代码依葫芦画瓢:
md.block.ruler.before('paragraph','@header',function(state, startLine, endLine, silent){
var ch, level, tmp, token,
pos = state.bMarks[startLine] + state.tShift[startLine],
max = state.eMarks[startLine];
//...
})
parse 的过程是根据换行符逐行扫描的,所以每一行的内容都会执行我们这个自定义函数进行匹配,函数支持传入四个参数,其中,
state
记录了各种状态数据,startLine
表示本次的起始行数,而 endLine
表示总的结束行数。我们打印下
state
`startLine,
endLine` 等数据:md.block.ruler.before('paragraph','@header',function(state, startLine, endLine, silent){
var ch, level, tmp, token,
pos = state.bMarks[startLine] + state.tShift[startLine],
max = state.eMarks[startLine];
console.log(JSON.parse(JSON.stringify(state)), startLine, endLine);
})
这是打印的结果:
文章图片
其中
state
的内容我们简化下展示出来:{
"src": "@ header\ncontentTwo\n",
"md": {...},
"env": {...},
"tokens": [...],
"bMarks": [0, 9, 20],
"eMarks": [8, 19, 20],
"tShift": [0, 0, 0],
"line": 0
}
state
中这些字段的具体含义可以查看 state_block.js 文件,这其中:- bMarks 表示每一行的起始位置
- eMarks 表示每一行的终止位置
- tShift 表示每一行第一个非空格字符的位置
pos
的计算逻辑为 state.bMarks[startLine] + state.tShift[startLine]
,其中 startLine
是 0,所以 pos = 0 + 0 = 0
再看下
max
的计算逻辑为 state.eMarks[startLine]
,所以max = 8
从这也可以看出,其实
pos
就是这行字符的初始位置,max
这行字符的结束位置,通过 pos
和 max
,我们可以截取出这行字符串:md.block.ruler.before('paragraph','@header',function(state, startLine, endLine, silent){
var ch, level, tmp, token,
pos = state.bMarks[startLine] + state.tShift[startLine],
max = state.eMarks[startLine];
console.log(JSON.parse(JSON.stringify(state)), startLine, endLine);
let text = state.src.substring(pos, max);
console.log(text);
state.line = startLine + 1;
return true
})
打印结果为:
文章图片
在代码里我们加入了
state.line = startLine + 1;
和 return true
,这是为了进入到下一行的遍历之中。如果我们能取出每次用于判断的字符串,那我们就可以进行正则匹配,如果匹配,就自定义 tokens,剩下的逻辑很简单,我们直接给出最后的代码:
md.block.ruler.before('paragraph', 'myplugin', function (state,startLine,endLine) {
var ch, level, tmp, token,
pos = state.bMarks[startLine] + state.tShift[startLine],
max = state.eMarks[startLine];
ch= state.src.charCodeAt(pos);
if (ch !== 0x40/*@*/ || pos >= max) { return false;
}let text = state.src.substring(pos, max);
let rg = /^@\s(.*)/;
let match = text.match(rg);
if (match && match.length) {
let result = match[1];
token = state.push('heading_open', 'h1', 1);
token.markup = '@';
token.map = [ startLine, state.line ];
token = state.push('inline', '', 0);
token.content = result;
token.map = [ startLine, state.line ];
token.children = [];
token = state.push('heading_close', 'h1', -1);
token.markup = '@';
state.line = startLine + 1;
return true;
}
})
至此,就实现了预期的效果:
文章图片
系列文章 博客搭建系列是我至今写的唯一一个偏实战的系列教程,预计 20 篇左右,讲解如何使用 VuePress 搭建、优化博客,并部署到 GitHub、Gitee、私有服务器等平台。全系列文章地址:https://github.com/mqyqingfeng/Blog
微信:「mqyqingfeng」,加我进冴羽唯一的读者群。
【markdown-it|markdown-it 插件如何写(二)】如果有错误或者不严谨的地方,请务必给予指正,十分感谢。如果喜欢或者有所启发,欢迎 star,对作者也是一种鼓励。
推荐阅读
- 考研英语阅读终极解决方案——阅读理解如何巧拿高分
- 如何寻找情感问答App的分析切入点
- mybatisplus如何在xml的连表查询中使用queryWrapper
- MybatisPlus使用queryWrapper如何实现复杂查询
- 如何在Mac中的文件选择框中打开系统隐藏文件夹
- 漫画初学者如何学习漫画背景的透视画法(这篇教程请收藏好了!)
- java中如何实现重建二叉树
- Linux下面如何查看tomcat已经使用多少线程
- thinkphp|thinkphp 3.2 如何调用第三方类库
- 2019女表什么牌子好(如何挑选女士手表?)