markdown-it 插件如何写(一)

冴羽
• 阅读 941

前言

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

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

renderer

markdown-it的渲染过程分为两部分,ParseRender,如果我们要更改渲染的效果,就比如在外层包裹一层 div,或者修改 HTML 元素的属性、添加 class 等,就可以从 Render 过程入手。

markdown-it 的官方文档里就可以找到自定义 Render 渲染规则的方式:

Instance of Renderer. Use it to modify output look. Or to add rendering rules for new token types, generated by plugins.

var md = require('markdown-it')();

function myToken(tokens, idx, options, env, self) {
  //...
  return result;
};

md.renderer.rules['my_token'] = myToken

markdown-it 内置了一些默认的 rules,你可以直接修改它们,具体有哪些以及渲染方式可以查看 renderer.js 的源码,这里直接列出来:

  • code_inline
  • code_block
  • fence
  • image
  • hardbreak
  • softbreak
  • text
  • html_block
  • html_inline

实例一

如果我们查看 VuePress 中代码块的渲染结果,我们会发现每一个代码块外层都包了一层带 extra-class类名的 div:

markdown-it 插件如何写(一)

其实,这就是 VuePress 修改了渲染规则产生的,查看 VuePress 源码

module.exports = md => {
  const fence = md.renderer.rules.fence
  md.renderer.rules.fence = (...args) => {
    const [tokens, idx] = args
    const token = tokens[idx]
    const rawCode = fence(...args)
    return `<!--beforebegin--><div class="language-${token.info.trim()} extra-class">` +
    `<!--afterbegin-->${rawCode}<!--beforeend--></div><!--afterend-->`
  }
}

我们可以看到这里非常巧妙的避开了处理 token,直接使用渲染后的结果,在外层包了一层 div。

实例二

类似于 VuePress 的这种方式,我们也可以在获取默认渲染内容后,再使用 replace 替换掉一些内容,比如在《VuePress 博客优化之拓展 Markdown 语法》这篇中,我们自定义了一个代码块语法,就是在 rules.fence中修改了渲染的内容:

md.use(function(md) {
  const fence = md.renderer.rules.fence
  md.renderer.rules.fence = (...args) => {
    let rawCode = fence(...args);
    rawCode = rawCode.replace(/<span class="token comment">\/\/ try-link https:\/\/(.*)<\/span>\n/ig, '<a href="$1" class="try-button" target="_blank">Try</a>');
    return `${rawCode}`
  }
})

实例三

但不可能总是可以这么取巧,有的时候就是需要处理 token,这里我们参考 markdown-it 官方提供的设计准则中的例子,当渲染一个图片的时候,如果链接匹配 /^https?:\/\/(www\.)?vimeo.com\/(\d+)($|\/)/,我们就将其渲染为一个 iframe ,其他的则保持默认渲染方式:

var md = require('markdown-it')();

var defaultRender = md.renderer.rules.image,
    vimeoRE       = /^https?:\/\/(www\.)?vimeo.com\/(\d+)($|\/)/;

md.renderer.rules.image = function (tokens, idx, options, env, self) {
  var token = tokens[idx],
      aIndex = token.attrIndex('src');

  if (vimeoRE.test(token.attrs[aIndex][1])) {

    var id = token.attrs[aIndex][1].match(vimeoRE)[2];

    return '<div class="embed-responsive embed-responsive-16by9">\n' +
           '  <iframe class="embed-responsive-item" src="//player.vimeo.com/video/' + id + '"></iframe>\n' +
           '</div>\n';
  }

  // pass token to default renderer.
  return defaultRender(tokens, idx, options, env, self);
};

rules.image 传入的函数参数可以查看 renderer.js 的源码:

Renderer.prototype.render = function (tokens, options, env) {
  var i, len, type,
      result = '',
      rules = this.rules;

  for (i = 0, len = tokens.length; i < len; i++) {
    type = tokens[i].type;

    if (type === 'inline') {
      result += this.renderInline(tokens[i].children, options, env);
    } else if (typeof rules[type] !== 'undefined') {
      result += rules[tokens[i].type](tokens, i, options, env, this);
    } else {
      result += this.renderToken(tokens, i, options, env);
    }
  }

  return result;
};

我们可以看到 rules 传入的参数,其中 tokens 就是指 tokens 列表,idx 则是指要渲染的 token 索引,所以在代码里才可以通过 tokens[index]获取目标 token。

然后我们又使用了 tokens.attrIndex,tokens 提供了哪些方法可以查看官方 API,或者直接查看 Token 源码

我们解释一下这个示例里用到的一些方法,先从 token 开始说起,我们举个例子,看下![video link]([https://www.vimeo.com/123)](https://www.vimeo.com/123))这句 markdown 语法产生的 token(这里进行了简化):

{
    "type": "image",
    "tag": "img",
    "attrs": [
        [
            "src",
            "https://www.vimeo.com/123"
        ],
        [
            "alt",
            ""
        ]
    ],
    "children": [
        {
            "type": "text",
            "tag": "",
            "attrs": null,
            "children": null,
            "content": "video link",

        }
    ],
    "content": "video link"
}

可以看到 token 是有一个 attr 属性的,表明了要渲染的 img 标签的属性有哪些,token.attrIndex 就是通过名字获取属性索引,然后再通过 token.attrs[aIndex][1]获取具体的属性值。

实例四

同样是来自 markdown-it 官方提供的设计准则中的例子,给所有的链接添加 target="_blank"

// Remember old renderer, if overridden, or proxy to default renderer
var defaultRender = md.renderer.rules.link_open || function(tokens, idx, options, env, self) {
  return self.renderToken(tokens, idx, options);
};

md.renderer.rules.link_open = function (tokens, idx, options, env, self) {
  // If you are sure other plugins can't add `target` - drop check below
  var aIndex = tokens[idx].attrIndex('target');

  if (aIndex < 0) {
    tokens[idx].attrPush(['target', '_blank']); // add new attribute
  } else {
    tokens[idx].attrs[aIndex][1] = '_blank';    // replace value of existing attr
  }

  // pass token to default renderer.
  return defaultRender(tokens, idx, options, env, self);
};

也许你会奇怪,为什么会有 rules.link_open?这个并不在默认规则里呀,可以直接使用吗?

还真是可以的,其实这里的 link_open 和之前的 imagefence 等都是 token 的 type,所以只要是 token 的 type 就可以,那 token 有哪些 type 呢?有具体的说明文档吗?

关于这个问题, markdown-it 也有 issues 里提出: markdown-it 插件如何写(一)

作者的意思就是,没有,如果你想写插件,你就去看源码……

那成吧,其实在我们的实际开发中,如果你想要知道某一个 token type,其实完全可以打印出 token 看一下,官方的 Live Demo 提供了 debug 模式用于查看 token:

markdown-it 插件如何写(一)

当然就这个例子里的需求,作者还提供了 markdown-it-for-inline 插件用于简化代码书写:

var iterator = require('markdown-it-for-inline');

var md = require('markdown-it')()
            .use(iterator, 'url_new_win', 'link_open', function (tokens, idx) {
              var aIndex = tokens[idx].attrIndex('target');

              if (aIndex < 0) {
                tokens[idx].attrPush(['target', '_blank']);
              } else {
                tokens[idx].attrs[aIndex][1] = '_blank';
              }
            });

关于 markdown-it-for-inline就在以后的文章里介绍了。

系列文章

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

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

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

点赞
收藏
评论区
推荐文章
冴羽 冴羽
2年前
有的时候我觉得我不会 Markdown
前言在中,我们使用VuePress搭建了一个博客,最终的效果查看:。在优化博客的过程中,因为需要写markdownit插件,翻了下markdown的,突然发现对Markdown还远不够了解:软换行(Softlinebreaks)换行符不在行内代码或HTML标签内,前面没有两个或以上的空格,将解析为软换行(Softlinebr
冴羽 冴羽
2年前
从零实现一个 VuePress 插件
前言在中,我们使用VuePress搭建了一个博客,最终的效果查看:。但在搭建VuePress博客的过程中,也并不是所有的插件都能满足需求,所以本篇我们以实现一个代码复制插件为例,教大家如何从零实现一个VuePress插件。本地开发开发插件第一个要解决的问题就是如何本地开发,我们查看VuePress1.0官方文档的「」章节,并没有找到解决
冴羽 冴羽
2年前
markdown-it 插件如何写(二)
前言在中,我们使用VuePress搭建了一个博客,最终的效果查看:。在搭建博客的过程中,我们出于实际的需求,在中讲解了如何写一个markdownit插件,又在中讲解了markdownit的执行原理,本篇我们将讲解具体的实战代码,帮助大家更好的写插件。Parsemarkdownit的渲染过程分为两部分,Parse和Render,如果我们要实现
冴羽 冴羽
1年前
VuePress 博客优化之增加 Vssue 评论功能
前言在中,我们使用VuePress搭建了一个博客,最终的效果查看:。本篇讲讲如何使用Vssue快速的实现评论功能。主题内置因为我用的是vuepressthemereco主题,主题内置评论插件@vuepressreco/vuepressplugincomments,可以根据自己的喜好选择Valine或者Vssue。那我们来介绍下Vss
冴羽 冴羽
2年前
VuePress 博客优化之 last updated 最后更新时间如何设置
前言在中,我们使用VuePress搭建了一个博客,但是浏览最终搭建的站点:,我们会发现,在每篇文章的底部,并没有像VuePress官方文档那样,出现最后更新的时间:这篇我们来探究下如何实现最后更新时间。官方自带查阅VuePress的,我们可以知道,VuePress自带显示最后更新时间的插件,在默认主题下,无需安装本插件,因为VuePre
冴羽 冴羽
2年前
markdown-it 插件如何写(三)
前言在中,我们使用VuePress搭建了一个博客,最终的效果查看:。在搭建博客的过程中,我们出于实际的需求,在中讲解了如何写一个markdownit插件,又在中讲解了markdownit的执行原理,本篇我们将讲解具体的实战代码,帮助大家更好的写插件。markdownitinlinemarkdownit的作者提供了markdownitinin
冴羽 冴羽
1年前
VuePress 博客优化之增加 Valine 评论功能
前言在中,我们使用VuePress搭建了一个博客,最终的效果查看:。本篇讲讲如何使用Valine快速的实现评论功能。主题内置因为我用的是vuepressthemereco主题,主题内置评论插件@vuepressreco/vuepressplugincomments,可以根据自己的喜好选择Valine或者Vssue。本篇讲讲使用Val
冴羽 冴羽
1年前
VuePress 博客之 SEO 优化(三)标题、链接优化
前言在中,我们使用VuePress搭建了一个博客,最终的效果查看:。本篇讲讲SEO中的一些细节优化。1.设置全局的title、description、keywordsjavascript//config.jsmodule.exportstitle:"title",description:'description',
冴羽 冴羽
2年前
搭建 VuePress 博客,你可能会用到的一些插件
前言在中,我们使用VuePress搭建了一个博客,最终的效果查看:。为了丰富站点的功能,我们可以直接使用一些现有的插件,本篇我们讲讲一些常用的插件。1.公告栏弹窗插件地址:安装:bashyarnadd@vuepressreco/vuepresspluginbulletinpopoverD使用:javascriptplugins:注意事项:查
冴羽 冴羽
2年前
Markdown-it 原理解析
前言在中,我们使用VuePress搭建了一个博客,最终的效果查看:。在搭建博客的过程中,我们出于实际的需求,在中讲解了如何写一个markdownit插件,本篇我们将深入markdownit的源码,讲解markdownit的执行原理,旨在让大家对markdownit有更加深入的理解。介绍引用的介绍:Markdownparserdoner
冴羽
冴羽
Lv1
男 · 淘宝 · 前端工程师
GitHub 26K Star 的博客: https://github.com/mqyqingfeng/Blog
文章
32
粉丝
15
获赞
67