“This is the third day of my participation in the First Challenge 2022, for more details: First Challenge 2022”.

preface

In “A blog with VuePress + Github Pages”, we build a blog with VuePress and see what the final TypeScript Chinese document looks like.

In the process of building a blog, we explained how to write a markdown-it plug-in in “VuePress Blog Optimization extension Markdown Syntax”, and explained the implementation principle of Markdown-it in “Markdown-IT Principle Analysis”. In this article we will explain the actual code to help you better write plug-ins.

renderer

The Markdown-it rendering process is divided into two parts, Parse and Render. If we want to change the rendering effect, such as wrapping a div layer around it, or changing the attributes of an HTML element, adding classes, etc., we can start with the Render process.

Customizing the Render rule can be found in the official documentation of Markdown-it:

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
Copy the code

Markdown-it comes with some default rules, and you can modify them directly. Renderer.js is available with renderer.js.

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

Instance of a

If we look at the rendering results of VuePress code blocks, we see that each code block is surrounded by a div with an extra-class class name:

In fact, this is VuePress modified the rendering rules generated, check 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]
    constrawCode = fence(... args)return ` <! --beforebegin--><div class="language-${token.info.trim()} extra-class">` +
    ` <! --afterbegin-->${rawCode}<! --beforeend--></div><! --afterend-->`}}Copy the code

We can see that this is a very clever way to avoid handling tokens and use the rendered result directly, wrapping a div layer around it.

Example 2

Similar to VuePress, we can take the default render content and use replace to replace some content. For example, in VuePress blog optimization extending Markdown Syntax, we created a custom code block syntax. It was in rules.fence that modified the render:

md.use(function(md) {
  const fence = md.renderer.rules.fence
  md.renderer.rules.fence = (. args) = > {
    letrawCode = 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}`}})Copy the code

Examples of three

When rendering an image, if the link matches /^ HTTPS? :\/\/(www\.) ? Vimeo.com \ / (\ d +)/($| \ /), we will render it as an iframe, others keep 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' +
           '  + id + '"></iframe>\n' +
           '</div>\n';
  }

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

Rules.image is passed as a function parameter to 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 (typeofrules[type] ! = ='undefined') {
      result += rules[tokens[i].type](tokens, i, options, env, this);
    } else {
      result += this.renderToken(tokens, i, options, env); }}return result;
};
Copy the code

Tokens [index] | Tokens [index] | Tokens [index] | Tokens [index] | Tokens [index] | tokens

Then we used Tokens. AttrIndex. Tokens provides methods to check the official API or directly check the Token source code.

Let’s explain some of the methods used in this example, starting with tokens. Let’s take an example. Look! ([https://www.vimeo.com/123] (https://www.vimeo.com/123)) the markdown syntax of token (simplified) here:

{
    "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"
}
Copy the code

Token. attrIndex is the attribute index by name, and token.attrs[aIndex][1] is the attribute value.

Instances of four

Here’s another example from the design guidelines provided by Markdown-it: Add target=”_blank” to all links:

// 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);
};
Copy the code

You may wonder why there is rules.link_open? This is not in the default rule, can you use it directly?

In fact, link_open, image, fence, etc. are token types, so as long as the token type is ok, what is the token type? Is there any specific documentation?

On this question,markdown-it Also put forward in issues:

The author is saying, no, if you want to write a plugin, you read the source code…

In fact, in our actual development, if you want to know a certain token type, you can print the token and have a look. The official Live Demo provides debug mode to check the token:

Of course, the markdown-it-for-inline plugin is also provided to simplify code writing for this example:

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

Markdown-it-for-inline will be covered in a future article.

series

Blog building series is the only practical series I have written so far. It is estimated to be about 20 articles, explaining how to use VuePress to build and optimize a blog and deploy it to GitHub, Gitee, private server and other platforms. Full article address: github.com/mqyqingfeng…

Wechat: “MQyqingfeng”, add me Into Hu Yu’s only readership group.

If there is any mistake or not precise place, please be sure to give correction, thank you very much. If you like or are inspired by it, welcome star and encourage the author.