foreword
In "An article that takes you to build a blog with VuePress + Github Pages" , we used VuePress to build a blog, and the final effect can be viewed: 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.
Parse
markdown-it
is divided into two parts, Parse
and Render
. If we want to implement the new markdown syntax, for example, if we want to parse @ header
as <h1>header</h1>
, we can start with the Parse
process.
The way to customize parse rules can be found in the official document Ruler
class:
var md = require('markdown-it')();
md.block.ruler.before('paragraph', 'my_rule', function replace(state) {
//...
});
What this means is means markdown-it
a set of rules to resolve the block, the paragraph
before inserting a rule named my_rule
custom rules, we slowly explained.
The first is md.block.ruler
, in addition, there are md.inline.ruler
, md.core.ruler
can customize the rules.
Then there is .before
, check the API related to Ruler after
, at
, disable
, enable
and other methods, this is because the change of other rules may affect the execution of other rules.
Then paragraph
, how do I know which rule to insert before or after it? This requires you to look at the source code, and there is no documentation to tell you this...
If md.block
, see parse_block.js , if it is md.inline
, see parse_inline.js , if it is md.core
, see parse_core.js , we md.block
for example, you can see the source code to write these rules:
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') ]
];
The last is function replace(state)
, the parameters of the function here are not only state
, we look at the parse code of any specific rule, such as 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];
// ...
};
It can be seen that in addition to state
, there are also startLine
, endLine
, silent
, and how to write the code in it, in fact, the best way is to refer to these already implemented codes.
Example explanation
Next, let's take parsing @ header
as <h1>header</h1>
as an example to explain the code involved. This is the content to be rendered:
var md = window.markdownit();
// md.block.ruler.before(...)
var result = md.render(`@ header
contentTwo
`);
console.log(result);
Normally its rendering result is:
<p>@ header
contentTwo</p>
The expected rendered result is now:
<h1>header</h1>
<p>contentTwo</p>
Let's take a look at how to implement it, first refer to the code of 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];
//...
})
The process of parse is scanned line by line based on newline characters, so the content of each line will be matched by executing our custom function. The function supports four parameters. Among them, state
records various status data, and startLine
represents this time starting line number, while endLine
represents the total ending line number.
We print data such as state
`startLine ,
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);
})
Here is the printed result:
Among state
, the content of 061f29758d3f06 is simplified and displayed:
{
"src": "@ header\ncontentTwo\n",
"md": {...},
"env": {...},
"tokens": [...],
"bMarks": [0, 9, 20],
"eMarks": [8, 19, 20],
"tShift": [0, 0, 0],
"line": 0
}
state
the specific meaning of these fields can be viewed state_block.js file, of which:
- bMarks indicates the starting position of each line
- eMarks indicate where each line ends
- tShift indicates the position of the first non-space character on each line
Let's see pos
the calculation logic of state.bMarks[startLine] + state.tShift[startLine]
is 061f29758d403f, of which startLine
is 0, so pos = 0 + 0 = 0
Look at max
calculation logic is state.eMarks[startLine]
, so max = 8
It can also be seen from this that pos
is actually the initial position of this line of characters, and the end position of max
pos
and max
, we can intercept this line of strings:
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
})
The print result is:
In the code we added state.line = startLine + 1;
and return true
, this is to enter the traversal of the next line.
If we can take out the string used for judgment each time, then we can perform regular matching. If it matches, we can customize the tokens. The rest of the logic is very simple. We directly give the final code:
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;
}
})
So far, the expected effect has been achieved:
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 articles 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) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。