当前位置: 首页 > Web前端 > vue.js

如何编写markdown-it插件(二)

时间:2023-04-01 11:56:17 vue.js

前言在《一篇带你用 VuePress + Github Pages 搭建博客》中,我们使用VuePress搭建了一个博客。查看最终效果:TypeScript中文文档。在搭建博客的过程中,我们针对实际需要在《VuePress 博客优化之拓展 Markdown 语法》中讲解了如何编写markdown-it插件,在《markdown-it 原理解析》中讲解了markdown-it的执行原理。在这篇文章中,我们将详细讲解实战代码,帮助大家更好的编写插件。Parsemarkdown-it的渲染过程分为两部分,Parse和Render。如果我们想实现一个新的markdown语法,比如我们想把@header解析为

header

,我们可以从Parse过程开始。在markdown-it的官方文档中,可以找到自定义解析规则的方法,即通过Ruler类:varmd=require('markdown-it')();md.block.ruler.before('paragraph','my_rule',functionreplace(state){//...});这句话的意思是在markdown-it解析块的一套规则中的段落规则前插入一条名为my_rule的自定义规则,我们慢慢解释。第一个是md.block.ruler。另外还有md.inline.ruler和md.core.ruler来自定义规则。然后.before,查看Ruler相关的API,有after、at、disable、enable等方法,这是因为规则是按顺序执行的,一个规则的改变可能会影响到其他规则。然后是段落,我怎么知道在哪个规则之前或之后插入?这个需要你看源码,没有文档告诉你这个。。。如果是md.block就查parse_block.js,如果是md.inline就查parse_inline.js,如果是md.core,检查parse_core.js,我们以md.block为例,我们可以看到这些规则写在源代码中:var_rules=[//First2params-rulename&source.辅助数组-规则列表,//可以由此终止。['table',require('./rules_block/table'),['paragraph','reference']],['code',require('./rules_block/code')],['fence',require('./rules_block/fence'),['段落','参考','blockquote','列表']],['blockquote',require('./rules_block/blockquote'),['段落','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'),['段落','参考','blockquote']],['标题',require('./rules_block/heading'),['段落','参考','blockquote']],['lheading',require('./rules_block/lheading')],['段落',require('./rules_block/paragraph')]];最后是functionreplace(state),这里函数的参数其实不仅仅是state,我们看任意一个具体规则的解析代码,比如heading.js:module.exports=functionheading(state,startLine,endLine,silent){varch,level,tmp,token,pos=state.bMarks[startLine]+state.tShift[startLine],max=state.eMarks[startLine];//...};可以看到除了state还有startLine,endLine,silent,具体代码怎么写,其实最好的办法就是参考这些已经实现的代码实例解释下面我们以解析@header为

header

为例来解释其中涉及的代码,也就是要渲染的内容:varmd=window.markdownit();//md.block。ruler.before(...)varresult=md.render(`@headercontentTwo`);console.log(result);正常情况下它的渲染结果是:

@headercontentTwo

现在期望的渲染结果是:

header

contentTwo

下面我们看看如何实现,先看代码header.js的:md.block.ruler.before('paragraph','@header',function(state,startLine,endLine,silent){varch,level,tmp,token,pos=state.bMarks[startLine]+state.tShift[startLine],max=state.eMarks[startLine];//...})parse过程是根据换行符逐行扫描的,所以每一行的内容都会执行我们自定义的函数for匹配。该函数支持传入四个参数。其中state记录了各种状态数据,startLine表示本次的起始行号,endLine表示总的结束行号。我们打印state`startLine,endLine`和其他数据:md.block.ruler.before('paragraph','@header',function(state,startLine,endLine,silent){varch,level,tmp,token,pos=state.bMarks[startLine]+state.tShift[startLine],max=state.eMarks[startLine];console.log(JSON.parse(JSON.stringify(state)),startLine,endLine);})这是whatprints结果:状态的内容被简化显示:{"src":"@header\ncontentTwo\n","md":{...},"env":{...},"令牌”:[...],“bMarks”:[0、9、20],“eMarks”:[8、19、20],“tShift”:[0、0、0],“行”:0}state这些字段的具体含义可以在state_block.js文件中查看,其中:bMarks表示每行的起始位置eMarks表示每行的结束位置tShift表示每行第一个非空格字符的位置我们看一下pos的计算逻辑是state.bMarks[startLine]+state.tShift[startLine],其中startLine为0,所以pos=0+0=0再看tmax的计算逻辑是state.eMarks[startLine],所以max=8从这里可以看出pos其实是这行字符的起始位置,也是max这行字符的结束位置。通过pos和max,我们可以截取这行字符串:md.block.ruler.before('paragraph','@header',function(state,startLine,endLine,silent){varch,level,tmp,token,pos=state.bMarks[startLine]+state.tShift[startLine],max=state.eMarks[startLine];console.log(JSON.parse(JSON.stringify(state)),startLine,endLine);lettext=state.src.substring(pos,max);控制台日志(文本);state.line=startLine+1;returntrue})打印出来的结果是:在代码中我们添加了state.line=startLine+1;并返回true,这是进入下一行的遍历如果每次都能把判断用的字符串取出来,那么就可以进行正则匹配了。如果匹配,我们将自定义令牌。其余的逻辑非常简单。我们直接给出最终代码:md.block.ruler。before('paragraph','myplugin',function(state,startLine,endLine){varch,level,tmp,token,pos=state.bMarks[startLine]+state.tShift[startLine],max=state.eMarks[startLine];ch=state.src.charCodeAt(pos);if(ch!==0x40/*@*/||pos>=max){returnfalse;}让文本=state.src.substring(pos,max);letrg=/^@\s(.*)/;letmatch=text.match(rg);if(match&&match.length){letresult=match[1];token=state.push('heading_open','h1',1);token.markup='@';token.map=[startLine,state.line];token=state.push('inline','',0);token.content=结果;token.map=[startLine,state.line];token.children=[];token=state.push('heading_close','h1',-1);token.markup='@';state.line=起始线+1;返回真;}})至此,达到了预期的效果:博客搭建系列文章系列是我目前为止唯一写的实用系列教程。预计20篇左右,讲解如何使用VuePress搭建和优化博客,并部署到GitHub、Gitee、私服等平台全系列文章地址:https://github.com/mqyqingfeng/博客微信:“mqyqingfeng”,加我到世优唯一的读者群。如有错误或不准确的地方,请务必指正,万分感谢。如果你喜欢或者有启发,欢迎star,这也是对作者的鼓励。