做者:嵇智html
在全部 Parser 編譯生成完 tokens 的時候,就傳給 Renderer.render 方法了。咱們來看下 Renderer 的定義。它位於 lib/renderer.js
。git
default_rules.code_inline = function (tokens, idx, options, env, slf) { var token = tokens[idx]; return '<code' + slf.renderAttrs(token) + '>' + escapeHtml(tokens[idx].content) + '</code>'; }; default_rules.code_block = function (tokens, idx, options, env, slf) { var token = tokens[idx]; return '<pre' + slf.renderAttrs(token) + '><code>' + escapeHtml(tokens[idx].content) + '</code></pre>\n'; }; default_rules.fence = function (tokens, idx, options, env, slf) { var token = tokens[idx], info = token.info ? unescapeAll(token.info).trim() : '', langName = '', highlighted, i, tmpAttrs, tmpToken; if (info) { langName = info.split(/\s+/g)[0]; } if (options.highlight) { highlighted = options.highlight(token.content, langName) || escapeHtml(token.content); } else { highlighted = escapeHtml(token.content); } if (highlighted.indexOf('<pre') === 0) { return highlighted + '\n'; } if (info) { i = token.attrIndex('class'); tmpAttrs = token.attrs ? token.attrs.slice() : []; if (i < 0) { tmpAttrs.push([ 'class', options.langPrefix + langName ]); } else { tmpAttrs[i][1] += ' ' + options.langPrefix + langName; } tmpToken = { attrs: tmpAttrs }; return '<pre><code' + slf.renderAttrs(tmpToken) + '>' + highlighted + '</code></pre>\n'; } return '<pre><code' + slf.renderAttrs(token) + '>' + highlighted + '</code></pre>\n'; }; default_rules.image = function (tokens, idx, options, env, slf) { var token = tokens[idx]; token.attrs[token.attrIndex('alt')][1] = slf.renderInlineAsText(token.children, options, env); return slf.renderToken(tokens, idx, options); }; default_rules.hardbreak = function (tokens, idx, options /*, env */) { return options.xhtmlOut ? '<br />\n' : '<br>\n'; }; default_rules.softbreak = function (tokens, idx, options /*, env */) { return options.breaks ? (options.xhtmlOut ? '<br />\n' : '<br>\n') : '\n'; }; default_rules.text = function (tokens, idx /*, options, env */) { return escapeHtml(tokens[idx].content); }; default_rules.html_block = function (tokens, idx /*, options, env */) { return tokens[idx].content; }; default_rules.html_inline = function (tokens, idx /*, options, env */) { return tokens[idx].content; }; function Renderer() { this.rules = assign({}, default_rules); } 複製代碼
default_rules
對象存在不一樣類型的 token 渲染函數。github
1. `code_inline` 是渲染 `我是 code_inline ` 語法
2. `fence` 是渲染 ``` js ``` 語法
3. `html_block` 是渲染 HTMl 標籤
複製代碼
再來細看 render
方法的邏輯,由於 內部使用到這些渲染函數。markdown
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 是 inlineoop
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
的邏輯。post
typeof rules[type] !== 'undefined'this
用不一樣渲染 rule 來處理對應 type 的 token。好比 type 爲 fence 的就用 default_rule.fence 來渲染。每一個 rule 都很簡單,就不細談。spa
renderTokenprototype
若是都不知足以上條件,就用 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; }; 複製代碼
renderToken
的做用就是渲染開標籤或者閉合標籤,內部還會調用 renderAttrs
來生成 attributes
。
render
的最後,就是輸出 HTML 字符串。
至此,MarkdownIt 的所有流程已經分析完畢,在整個 parse 抑或是 tokenize、render 的過程當中,都是不一樣的 rule 函數在各司其職,這也就賦予了 MarkdownIt 極大的靈活性和擴展性,你能夠去替換 parse rule,也能夠去替換 tokenize rule 或者 render rule。來張流程圖大體感覺一下:
假如咱們須要在 markdown 裏面支持 emoji 語法呢,甚至還有更多定製化的需求呢?那咱們就必須得寫插件,你可能並不須要對某個 rule 的全部細節都知根知底,可是你必須對源碼的總體流程是特別熟悉,咱們將在插件篇講解一些比較有趣的插件。