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

Markdown-it原理分析

时间:2023-03-31 22:19:20 vue.js

前言在《一篇带你用 VuePress + Github Pages 搭建博客》中,我们使用VuePress搭建了一个博客,最终效果可以查看:TypeScript中文文档。在搭建博客的过程中,出于实际需要,我们在《VuePress 博客优化之拓展 Markdown 语法》中讲解了如何编写一个markdown-it插件。在本文中,我们将深入markdown-it的源码,讲解markdown-it的执行原理。让大家对markdown-it有更深入的了解。介绍参考了markdown-itGithub仓库的介绍:Markdownparserdoneright。快速且易于扩展。可见markdown-it是一个markdown解析器,易于扩展。其demo地址为:https://markdown-it.github.io/markdown-它具有以下优点:遵循CommonMark规范,增加了语法扩展和语法糖(如URL自动识别和打印特殊处理)可配置语法,你可以添加新规则或替换现有规则快速默认安全社区有很多插件或其他包使用//installnpminstallmarkdown-it--save//node.js,“经典”方式:varMarkdownIt=require('markdown-it'),md=newMarkdownIt();varresult=md.render('#markdown-itrulezz!');//没有AMD的浏览器,在脚本加载时添加到“窗口”//注意,“markdownit”中没有破折号。varmd=window.markdownit();varresult=md.render('#markdown-itrulezz!');源码分析再看一下markdown-it的入口代码,可以发现代码逻辑清晰://...varRenderer=require('./renderer');varParserCore=require('./parser_core');varParserBlock=require('./parser_block');varParserInline=require('./parser_inline');functionMarkdownIt(presetName,options){//...this.inline=newParserInline();this.block=newParserBlock();this.core=newParserCore();this.renderer=newRenderer();//...}MarkdownIt.prototype.parse=函数(src,env){//...varstate=newthis.core.State(src,this,env);这个.core.process(状态);返回state.tokens;};MarkdownIt.prototype.render=function(src,env){env=env||{};返回this.renderer.render(this.parse(src,env),this.options,env);};从render方法也可以看出,它的渲染分为两个过程:Parse:将Markdown文件解析成TokensRender:遍历Tokens生成HTML和Babel很像,只不过Babel把它转成了抽象语法树(AST),而markdown-it没有选择使用AST,主要是遵循KISS(KeepItSimple,Stupid)原则Tokens是什么样子的?我们在demo页面试一下:可以看出#header生成的Token的格式为(注:为了方便展示,这里做了简化):[{"type":"heading_open","tag":"h1"},{"type":"inline","tag":"","children":[{"type":"text","tag":"","content":"header"}]},{"type":"heading_close","tag":"h1"}]具体Token中各字段含义参见TokenClass。通过这个简单的Tokens例子也可以看出Tokens和AST的区别:Tokens只是一个简单的数组。开始标签和结束标签是分开的。Parse查看parse方法相关的代码://...varParserCore=require('./parser_core');functionMarkdownIt(presetName,options){//...this.core=newParserCore();//...}MarkdownIt.prototype.parse=function(src,env){//...varstate=newthis.core.State(src,this,env);这个.core.process(状态);返回state.tokens;};可以看到具体的执行代码,应该是写在.在/parse_core中查看parse_core.js的代码:var_rules=[['normalize',require('./rules_core/normalize')],['block',require('./rules_core/block')],['inline',require('./rules_core/inline')],['linkify',require('./rules_core/linkify')],['replacements',require('./rules_core/replacements')],['smartquotes',require('./rules_core/smartquotes')]];functionCore(){//...}Core.prototype.process=function(state){//...for(i=0,l=rules.length;i”,即“\n”;在Windows系统中,每行的结尾是“”,即“\r\n”;在Mac系统中,每行以“”结束。一个直接的后果就是如果在Windows中打开Unix/Mac系统下的文件,所有的文字都会变成一行;而如果在Unix/Mac下打开Windows中的文件,每行符号的末尾可能会多出一个^M。\r\n被\n替换的原因其实是遵循规范:一个行结束是一个换行符(U+000A),一个回车符(U+000D)后面没有一个换行符,或者一个回车符和一个以下换行符。其中U+000A表示换行(LF),U+000D表示回车(CR)。除了替换回车符外,源代码还替换了空字符。在常规模式中,\0表示匹配NULL(U+0000)字符。根据WIKI的解释:空字符(Nullcharacter)也叫终止符,缩写为NUL,是一个值为0的控制字符。空字符包含在很多字符编码中,包括ISO/IEC646(ASCII),C0控制码、通用字符集、Unicode和EBCDIC等。几乎所有的主流编程语言都包含空字符。这个字符的本义类似于NOPCommand,当发送到打印机或终端时,设备不需要做任何事情(但有些设备会打印错误或显示空白)。我们用\uFFFD替换空字符。在Unicode中,\uFFFD代表替换字符:之所以要这样替换,其实是为了遵循规范。我们参考CommonMark规范2.3章节:出于安全原因,Unicode字符U+0000必须替换为替换字符(U+FFFD)。我们来测试一下这个效果:md.render('foo\u0000bar'),'

foo\uFFFDbar

\n'效果如下,你会发现没有可见的空字符被替换后加上替换字符,显示为:2.blockblock这条规则的作用是寻找区块,生成代币,那么什么是区块?什么是内联?我们也可以在CommonMark规范的块和内联章节中找到答案:我们可以将文档视为一系列块——结构元素,如段落、块引用、列表、标题、规则和代码块。一些块(如块引用和列表项)包含其他块;其他(如标题和段落)包含内嵌内容——文本、链接、强调文本、图像、代码跨度等。翻译是:我们认为文档由一组块组成,结构元素如段落、引号、列表、标题、代码块等。一些块(如引号和列表)可以包含其他块,而另一些(如标题和段落)可以包含内联内容,例如文本、链接、强调文本、图像、代码片段等。当然,在markdown-it中会被识别为block的,可以查看parser_block.js,里面也定义了一些识别解析规则:这些规则我挑几个不常见的规则:coderules用于识别Indentedcodeblocks(4个空格填充),markdown中:fence规则用于标识Fenced代码块,markdown中:hr规则用于标识换行符,markdown中:reference规则用于标识引用链接,markdown中:html_block用于标识markdown中的HTML块元素标签,例如div。lheading用于标识Setext标题。在markdown中:3.inlineinline规则是解析markdown中的inline,然后生成token。block先执行的原因是block可以包含inline。解析规则可以在parser_inline.js中找到:下面我挑几个不常见的规则:换行规则用于标识\n,将\n替换为hardbreak类型tokenbackticks规则用于标识反引号:使用实体规则处理HTML实体,例如{`ˉ`"等:4.linkify自动识别链接5.替换将(c)`(C)替换为?,将??????????替换为???,替换!!!!!替换为!!!`,等等:6.Smartquotes为了打印方便对直引号进行了处理:Render渲染过程其实比较简单,查看renderer.js文件,可以看到一些内置默认渲染规则:default_rules.code_inlinedefault_rules.code_blockdefault_rules.fencedefault_rules.imagedefault_rules.hardbreakdefault_rules.softbreakdefault_rules.textdefault_rules.html_blockdefault_rules.html_inline其实这些名字也是token的类型,在遍历token的时候,根据令牌,规则是ex根据_inline规则执行。其实很简单:default_rules.code_inline=function(tokens,idx,options,env,slf){vartoken=tokens[idx];返回'??'+escapeHtml(tokens[idx].content)+'';};CustomRules至此,我们对markdown-it的渲染原理有了一个简单的了解,无论是Parse还是Render过程中的Rules,markdown-it都可以提供自定义这些Rules的方式,这也是写法的关键markdown-it插件。我们会在后续的系列文章中谈到。博客搭建系列是我目前为止唯一写的实用系列教程,讲解如何使用VuePress搭建博客。并部署到GitHub、Gitee、个人服务器等平台。一会带你用VuePress+GitHubPages搭建博客一会教你如何在GitHub和Gitee之间同步代码还是不知道如何使用GitHubActions?看看这个Gitee是如何自动部署Pages的?仍然使用GitHubActions!一个前端足够用的Linux命令一个足够简单的NginxLocation配置说明从购买服务器到部署博客代码的详细教程从域名购买到备案到解析的详细教程VuePress博客优化最后更新最后更新时间如何设置VuePress博客优化添加统计功能VuePress博客优化启用HTTPSVuePress博客优化启用Gzip压缩从头开始实现一个VuePress插件微信:“mqyqingfeng”,加我到SaeYu的唯一读者群。如有错误或不准确的地方,请务必指正,万分感谢。如果你喜欢或者有启发,欢迎star,这也是对作者的鼓励。