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;
};
render 逻辑很简单,传入 tokens,options,env。遍历所有的 token,根据它的 type 采用不同的渲染 rule 函数。
type 是 inline
Renderer.prototype.renderInline = function (tokens, options, env) {
var type,
result = '',
rules = this.rules;
for (var i = 0, len = tokens.length; i < len; i++) {
type = tokens[i].type;
if (typeof rules[type] !== 'undefined') {
result += rules[type](tokens, i, options, env, this);
} else {
result += this.renderToken(tokens, i, options);
}
}
return result;
};
据我们之前分析的 ParserInline,它的作用是进一步解析 type 为 inline 的 token,并且它的 children 属性上放了编译出来的 token。那么 renderInline 就是用到它的 children 上的 token。如果 renderer.rules存在对应类型的渲染 rule 函数,就会用这个渲染 rule 函数去处理对应 type 的 token,否则都走 render.renderToken 的逻辑。
typeof rules[type] !== 'undefined'
用不同渲染 rule 来处理对应 type 的 token。比如 type 为 fence 的就用 default_rule.fence 来渲染。每个 rule 都很简单,就不细谈。
renderToken
如果都不满足以上条件,就用 renderToken 来渲染 token。
Renderer.prototype.renderToken = function renderToken(tokens, idx, options) {
var nextToken,
result = '',
needLf = false,
token = tokens[idx];
// Tight list paragraphs
if (token.hidden) {
return '';
}
if (token.block && token.nesting !== -1 && idx && tokens[idx - 1].hidden) {
result += '\n';
}
result += (token.nesting === -1 ? '</' : '<') + token.tag;
result += this.renderAttrs(token);
if (token.nesting === 0 && options.xhtmlOut) {
result += ' /';
}
if (token.block) {
needLf = true;
if (token.nesting === 1) {
if (idx + 1 < tokens.length) {
nextToken = tokens[idx + 1];
if (nextToken.type === 'inline' || nextToken.hidden) {
needLf = false;
} else if (nextToken.nesting === -1 && nextToken.tag === token.tag) {
needLf = false;
}
}
}
}
result += needLf ? '>\n' : '>';
return result;
};
Renderer
在所有 Parser 编译生成完 tokens 的时候,就传给 Renderer.render 方法了。我们来看下 Renderer 的定义。它位于
lib/renderer.js
。default_rules
对象存在不同类型的 token 渲染函数。再来细看
render
方法的逻辑,因为 内部使用到这些渲染函数。render
逻辑很简单,传入 tokens,options,env。遍历所有的 token,根据它的 type 采用不同的渲染 rule 函数。type 是 inline
据我们之前分析的 ParserInline,它的作用是进一步解析
type
为inline
的 token,并且它的 children 属性上放了编译出来的 token。那么renderInline
就是用到它的 children 上的 token。如果 renderer.rules存在对应类型的渲染 rule 函数,就会用这个渲染 rule 函数去处理对应 type 的 token,否则都走render.renderToken
的逻辑。typeof rules[type] !== 'undefined'
用不同渲染 rule 来处理对应 type 的 token。比如 type 为 fence 的就用 default_rule.fence 来渲染。每个 rule 都很简单,就不细谈。
renderToken
如果都不满足以上条件,就用
renderToken
来渲染 token。renderToken
的作用就是渲染开标签或者闭合标签,内部还会调用renderAttrs
来生成attributes
。render
的最后,就是输出 HTML 字符串。总结
至此,MarkdownIt 的全部流程已经分析完毕,在整个 parse 抑或是 tokenize、render 的过程中,都是不同的 rule 函数在各司其职,这也就赋予了 MarkdownIt 极大的灵活性和扩展性,你可以去替换 parse rule,也可以去替换 tokenize rule 或者 render rule。来张流程图大致感受一下:
假如我们需要在 markdown 里面支持 emoji 语法呢,甚至还有更多定制化的需求呢?那我们就必须得写插件,你可能并不需要对某个 rule 的所有细节都知根知底,但是你必须对源码的整体流程是特别熟悉,我们将在插件篇讲解一些比较有趣的插件。