markdown-it 插件如何写(二)

简介: 「这是我参与2022首次更文挑战的第4天,活动详情查看:2022首次更文挑战」。

0.png


「这是我参与2022首次更文挑战的第4天,活动详情查看:2022首次更文挑战」。


前言


《一篇带你用 VuePress + Github Pages 搭建博客》中,我们使用 VuePress 搭建了一个博客,最终的效果查看:TypeScript 中文文档


在搭建博客的过程中,我们出于实际的需求,在《VuePress 博客优化之拓展 Markdown 语法》中讲解了如何写一个 markdown-it插件,又在 《markdown-it 原理解析》中讲解了 markdown-it的执行原理,本篇我们将讲解具体的实战代码,帮助大家更好的写插件。


Parse


markdown-it的渲染过程分为两部分,ParseRender,如果我们要实现新的 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.rulermd.core.ruler可以自定义其中的规则。


然后是 .before,查看 Ruler 相关的 API,还有 afteratdisableenable等方法,这是因为规则是按照顺序执行的,某一规则的改变可能会影响其他规则。


接着是 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,还有 startLineendLinesilent,而具体这其中的代码怎么写,其实最好的方式就是参考这些已经实现的代码。


实例讲解


接下来我们以解析 @ 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``startLineendLine 等数据:


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);
})
复制代码


这是打印的结果:


5.png


其中 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 这行字符的结束位置,通过 posmax,我们可以截取出这行字符串:


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
})
复制代码


打印结果为:


6.png


在代码里我们加入了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;
      }
})
复制代码


至此,就实现了预期的效果:


7.png


系列文章


博客搭建系列是我至今写的唯一一个偏实战的系列教程,预计 20 篇左右,讲解如何使用 VuePress 搭建、优化博客,并部署到 GitHub、Gitee、私有服务器等平台。全系列文章地址:github.com/mqyqingfeng…


微信:「mqyqingfeng」,加我进冴羽唯一的读者群。


如果有错误或者不严谨的地方,请务必给予指正,十分感谢。如果喜欢或者有所启发,欢迎 star,对作者也是一种鼓励。



目录
相关文章
使用markdown-it对markdown进行一个实时解析
# 引言 大家应该都接触过markdown笔记吧,我们常常见到很多能写文章的网页提供了一个实时的markdown解析的功能----即我们在一侧写下markdown语法,右边能够实时解析,并将解析后的markdown语法渲染到右侧页面上。非常炫酷实用,那么这是如何实现的呢?今天我们来一起探究一下。 # 开始 首先,我们这个演示学习在Vue3+TypeScript下使用的,所以我们预先建议准备一个Vue3+Typescript工程。 对于markdown的解析,我们可以使用`markdown-it`来进行解析。 比如,我们在左侧设置一个输入框,接收用户markdown输入,我们通过`ma
|
5月前
|
机器学习/深度学习 自然语言处理 并行计算
提升长序列建模效率:Mamba+交叉注意力架构完整指南
本文探讨了Mamba架构中交叉注意力机制的集成方法,Mamba是一种基于选择性状态空间模型的新型序列建模架构,擅长处理长序列。通过引入交叉注意力,Mamba增强了多模态信息融合和条件生成能力。文章从理论基础、技术实现、性能分析及应用场景等方面,详细阐述了该混合架构的特点与前景,同时分析了其在计算效率、训练稳定性等方面的挑战,并展望了未来优化方向,如动态路由机制和多模态扩展,为高效序列建模提供了新思路。
395 1
提升长序列建模效率:Mamba+交叉注意力架构完整指南
|
7月前
|
人工智能 Cloud Native 安全
AI 网关代理 LLMs 最佳实践
云原生 AI 网关其实并不是一个新的独立的产品,而是属于云原生 API 网关产品内的一部分功能,基于 AI 的场景,设计了更贴合 AI 业务的 AI API 及各个功能。同时也具备云原生 API 网关本身提供的各个通用能力。
357 15
|
9月前
|
机器学习/深度学习 存储 人工智能
2025年阿里云GPU服务器的租赁价格与选型指南
随着AI、深度学习等领域的发展,GPU服务器成为企业及科研机构的核心算力选择。阿里云提供多种GPU实例类型(如NVIDIA V100、A100等),涵盖计算型、共享型和弹性裸金属等,满足不同场景需求。本文详解2025年阿里云GPU服务器的核心配置、价格策略及适用场景,帮助用户优化选型与成本控制,实现高效智能计算。
|
缓存 前端开发 JavaScript
Vite 打包优化:全面解析与实践
Vite 作为新一代前端构建工具,以其快速开发体验和高效打包能力著称。然而,在实际项目开发中,为了进一步提升性能和用户体验,我们仍需对 Vite 打包进行优化。本文将深入探讨 Vite 打包优化策略,涵盖代码拆分、资源压缩、缓存利用、构建配置等多个方面,并提供实践案例和最佳实践建议,帮助开发者充分释放 Vite 的潜力。
2837 1
|
消息中间件 分布式计算 DataWorks
DataWorks产品使用合集之如何使用Python和阿里云SDK读取OSS中的文件
DataWorks作为一站式的数据开发与治理平台,提供了从数据采集、清洗、开发、调度、服务化、质量监控到安全管理的全套解决方案,帮助企业构建高效、规范、安全的大数据处理体系。以下是对DataWorks产品使用合集的概述,涵盖数据处理的各个环节。
|
UED
禁止手机侧滑返回上一个页面的三种方法
禁止手机侧滑返回上一个页面的三种方法
1174 0
|
缓存 负载均衡 前端开发
构建高性能 Java Web 应用程序
【4月更文挑战第19天】构建高性能 Java Web 应用涉及数据库优化(合理设计、查询优化、性能调优)、缓存策略(服务器端缓存、HTTP 缓存)、代码优化(避免冗余查询、减少对象创建、有效使用线程)、异步处理(增强并发能力)、负载均衡(分发请求、提升可靠性)、性能测试与监控(发现瓶颈、实时问题)、前端优化(减少加载时间、优化资源)、服务器配置(硬件资源、系统优化)以及代码压缩和资源合并。综合运用这些技术,能显著提升应用性能和用户体验。
259 8
|
弹性计算 运维 监控
多云基础设施的统一纳管与运维实践分享
CloudOps云上运维系列课程第五节由阿里云弹性计算技术专家朱士松主讲《多云基础设施的统一纳管与运维实践》,点击下方链接进入【CloudOps云上运维】课程专题页即可观看课程回放,还可了解最新课程资讯。
多云基础设施的统一纳管与运维实践分享