前言
在 《一篇帶你用 VuePress + Github Pages 搭建部落格》中,我們使用 VuePress 搭建了一個部落格,最終的效果檢視:TypeScript 中文文件。
在搭建部落格的過程中,我們出於實際的需求,在《VuePress 部落格優化之擴充 Markdown 語法》中講解了如何寫一個 markdown-it
外掛,又在 《markdown-it 原理解析》中講解了 markdown-it
的執行原理,本篇我們將講解具體的實戰程式碼,幫助大家更好的寫外掛。
renderer
markdown-it
的渲染過程分為兩部分,Parse
和 Render
,如果我們要更改渲染的效果,就比如在外層包裹一層 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
:
其實,這就是 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
和之前的 image
、fence
等都是 token 的 type,所以只要是 token 的 type 就可以,那 token 有哪些 type 呢?有具體的說明文件嗎?
關於這個問題, markdown-it
也有 issues 裡提出:
作者的意思就是,沒有,如果你想寫外掛,你就去看原始碼……
那成吧,其實在我們的實際開發中,如果你想要知道某一個 token type,其實完全可以列印出 token 看一下,官方的 Live Demo 提供了 debug 模式用於檢視 token:
當然就這個例子裡的需求,作者還提供了 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,對作者也是一種鼓勵。