共计 4752 个字符,预计需要花费 12 分钟才能阅读完成。
前言
在《一篇带你用 VuePress + Github Pages 搭建博客》中,咱们应用 VuePress 搭建了一个博客,最终的成果查看:TypeScript 中文文档。
在搭建博客的过程中,咱们出于理论的需要,在《VuePress 博客优化之拓展 Markdown 语法》中解说了如何写一个 markdown-it
插件,又在《markdown-it 原理解析》中解说了 markdown-it
的执行原理,本篇咱们将解说具体的实战代码,帮忙大家更好的写插件。
Parse
markdown-it
的渲染过程分为两局部,Parse
和 Render
,如果咱们要实现新的 markdown 语法,举个例子,比方咱们心愿解析 @ header
为 <h1>header</h1>
,就能够从 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
为 <h1>header</h1>
为例,解说其中波及的代码,这是要渲染的内容:
var md = window.markdownit();
// md.block.ruler.before(...)
var result = md.render(`@ header
contentTwo
`);
console.log(result);
失常它的渲染后果是:
<p>@ header
contentTwo</p>
当初冀望的渲染后果是:
<h1>header</h1>
<p>contentTwo</p>
咱们来看看如何实现,先参照 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」,加我进冴羽惟一的读者群。
如果有谬误或者不谨严的中央,请务必给予斧正,非常感激。如果喜爱或者有所启发,欢送 star,对作者也是一种激励。