4
头图

foreword

In "An article that takes you to build a blog with VuePress + Github Pages" , we used VuePress to build a blog. Check the final effect: TypeScript Chinese document .

In the process of building the blog, we have, for practical needs, in "VuePress blog optimization of expansion Markdown syntax" in explaining how to write a markdown-it plug-in, but also in "Markdown-IT principles to resolve" are explained markdown-it In this article, we will explain the specific actual code to help you write plug-ins better.

renderer

markdown-it is divided into two parts, Parse and Render . If we want to change the rendering effect, such as wrapping a layer of div on the outer layer, or modify the attributes of HTML elements, add class etc., we can Render process.

The way to Render can be found in the official document

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 has some built-in default rules , you can modify them directly. For details and rendering methods, you can check the source code renderer.js, which is listed directly here:

Example 1

If we look at the rendering results of code blocks in VuePress, we will find that each code block is wrapped with a layer of extra-class class name div :

image.png

In fact, this is the result of VuePress modifying the rendering rules, see VuePress source code :

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-->`
  }
}

We can see that the processing of the token is very cleverly avoided here, and the rendered result is directly used, wrapping a layer of div in the outer layer.

Example 2

Similar to the way of VuePress, we can also use replace to replace some content after getting the default rendering content. For example, in "VuePress Blog Optimization: Extended Markdown Syntax" , we customized a code block The syntax is to modify the rendered content 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}`
  }
})

Example three

But it is not always possible to do this. Sometimes it is necessary to deal with tokens. Here we refer to the example in the design guideline . When rendering an image, if the link matches /^https?:\/\/(www\.)?vimeo.com\/(\d+)($|\/)/ , we will Rendered as a iframe , the others remain the default rendering:

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);
};

The function parameters passed in by rules.image can be viewed in the source code of 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;
};

We can see the parameters passed in by rules, where tokens refers to the list of tokens, and idx refers to the index of the token to be rendered, so the target token tokens[index]

Then we used tokens.attrIndex , which methods provided by tokens to view official API , or directly view Token source code .

Let's explain some of the methods used in this example. Let's start with the token. Let's take an example to see the token generated by the markdown syntax of ![video link]([https://www.vimeo.com/123)](https://www.vimeo.com/123))

{
    "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"
}

It can be seen that the token has an attr , which indicates the attributes of the img tag to be rendered. token.attrIndex obtains the attribute index through the name, and then obtains the specific attribute value token.attrs[aIndex][1]

Example 4

Markdown-it is also from design criteria provided by the official example, the link to add to all 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);
};

Maybe you will wonder why there is rules.link_open ? This is not in the default rules, can it be used directly?

It's really possible. In fact, the link_open here and the previous image , fence etc. are all token types, so as long as they are the token types, what types of tokens are there? Is there any specific documentation?

Regarding this issue, markdown-it also has issues raised:
image.png

The author means, no, if you want to write a plug-in, you go to the source code...

That's it. In fact, in our actual development, if you want to know a certain token type, you can actually print out the token and take a look. The official Live Demo provides a debug mode to view the token:

image.png

Of course, for the requirements in this example, the author also provides the markdown-it-for-inline plugin to simplify code writing:

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';
              }
            });

About markdown-it-for-inline will be introduced in future articles.

series of articles

The blog building series is the only series of practical tutorials I have written so far. It is expected to be about 20 articles, explaining how to use VuePress to build and optimize blogs, and deploy them to GitHub, Gitee, private servers and other platforms. Full series of article address: https://github.com/mqyqingfeng/Blog

WeChat: "mqyqingfeng", add me to the only readership of Xianyu.

If there are any mistakes or inaccuracies, please be sure to correct me, thank you very much. If you like or have inspiration, welcome to star, which is also an encouragement to the author.


冴羽
9.3k 声望6.3k 粉丝

17 年开始写前端文章,至今 6 个系列,上百篇文章,全网千万阅读