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:
- code_inline
- code_block
- fence
- image
- hardbreak
- softbreak
- text
- html_block
- html_inline
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
:
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:
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:
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.
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。