做者:嵇智html
markdown-it
是一個 parser。它接收一些字符串,而且通過內部的 rule 函數處理以後,調用 render 以後輸出 HTML 字符串。既然是接受字符串,那麼以下所見node
// node.js
var md = require('markdown-it')();
var result = md.render('# markdown-it rulezz!');
// output "<h1>markdown-it rulezz!</h1>"
// browser
var md = require('markdown-it')();
var result = md.render('# markdown-it rulezz!');
// output "<h1>markdown-it rulezz!</h1>"
複製代碼
輸入必定格式的字符串給 md,輸出 HTML 字符串,只要將其 append 到 DOM Tree 中,便可完成渲染。簡單易懂。git
那麼內部的 parse 機制究竟是怎樣的呢?先從入口文件 markdown-it/lib/index.js
入手。github
function MarkdownIt(presetName, options) {
if (!(this instanceof MarkdownIt)) {
return new MarkdownIt(presetName, options);
}
if (!options) {
if (!utils.isString(presetName)) {
options = presetName || {};
presetName = 'default';
}
}
this.inline = new ParserInline();
this.block = new ParserBlock();
this.core = new ParserCore();
this.renderer = new Renderer();
this.linkify = new LinkifyIt();
this.validateLink = validateLink;
this.normalizeLink = normalizeLink;
this.normalizeLinkText = normalizeLinkText;
this.utils = utils;
this.helpers = utils.assign({}, helpers);
this.options = {};
this.configure(presetName);
if (options) { this.set(options); }
}
複製代碼
MarkdownIt 支持傳入兩個參數 presetName 和 options.markdown
首先,構造函數內部先對調用方是否用了 new 操做符來調用 MarkdownIt 作了兼容。而且對 presetName 與 options 作了缺省值的一些操做。先來看下 presetName。架構
// 構造函數調用 configure
this.configure(presetName)
var config = {
'default': require('./presets/default'),
zero: require('./presets/zero'),
commonmark: require('./presets/commonmark')
};
MarkdownIt.prototype.configure = function (presets) {
var self = this, presetName;
if (utils.isString(presets)) {
presetName = presets;
presets = config[presetName];
if (!presets) { throw new Error('Wrong `markdown-it` preset "' + presetName + '", check name'); }
}
if (!presets) { throw new Error('Wrong `markdown-it` preset, can\'t be empty'); }
if (presets.options) { self.set(presets.options); }
if (presets.components) {
Object.keys(presets.components).forEach(function (name) {
if (presets.components[name].rules) {
self[name].ruler.enableOnly(presets.components[name].rules);
}
if (presets.components[name].rules2) {
self[name].ruler2.enableOnly(presets.components[name].rules2);
}
});
}
return this;
}
複製代碼
configure 方法接收 presets 做爲參數,內部會對其作一些適配的工做。當 presets 爲 commonmark | default | zero
的時候,就賦值爲不一樣對象。這三種配置對象位於 lib/presets
下面,就拿 default
來講吧。app
module.exports = {
options: {
html: false, // Enable HTML tags in source
xhtmlOut: false, // Use '/' to close single tags (<br />)
breaks: false, // Convert '\n' in paragraphs into <br>
langPrefix: 'language-', // CSS language prefix for fenced blocks
linkify: false, // autoconvert URL-like texts to links
// Enable some language-neutral replacements + quotes beautification
typographer: false,
// Double + single quotes replacement pairs, when typographer enabled,
// and smartquotes on. Could be either a String or an Array.
//
// For example, you can use '«»„「' for Russian, '„「‚‘' for German,
// and ['«\xA0', '\xA0»', '‹\xA0', '\xA0›'] for French (including nbsp).
quotes: '\u201c\u201d\u2018\u2019', /* 「」‘’ */
// Highlighter function. Should return escaped HTML,
// or '' if the source string is not changed and should be escaped externaly.
// If result starts with <pre... internal wrapper is skipped.
//
// function (/*str, lang*/) { return ''; }
//
highlight: null,
maxNesting: 100 // Internal protection, recursion limit
},
components: {
core: {},
block: {},
inline: {}
}
};
複製代碼
導出了擁有 options 與 components 屬性的對象。接下來就走到 configure 內部判斷 options 與 components 的邏輯來。components 下面的頂級屬性 core & block & inline 分別對應瞭如下三個實例,其目的是爲了禁用它們內部的一些 rule 函數,讓 Markdown 的 parse 更快,更純粹。這也體現出 MarkdownIt 的靈活性以及高度定製的特性。函數
this.inline = new ParserInline();
this.block = new ParserBlock();
this.core = new ParserCore();
this.renderer = new Renderer();
複製代碼
這四個類,是整個 MarkdownIt 的靈魂。它們貫穿 MarkdownIt 的 parse、tokenize、render 的全流程,這四個類,我會在接下來的系列來一一分析。post
接着初始化跟 url 相關的三個功能函數ui
this.validateLink = validateLink;
this.normalizeLink = normalizeLink;
this.normalizeLinkText = normalizeLinkText;
複製代碼
主要是用來校驗 url 的合法以及 decode 與 encode,感興趣的同窗,能夠研究內部用到的 mdurl 以及 punycode 的邏輯。
最後部分就是一些 util 與 helpers 函數的掛載以及對 options 的配置。
從上面來看,整個初始化工做的邏輯很是清晰、明瞭,並且源碼內部有大量的用法介紹,對開發者是很是的友好。
大體瞭解了構造函數的邏輯以後,咱們看下 MarkdownIt 原型上的一些方法。
MarkdownIt.prototype.set = function (options) {
utils.assign(this.options, options);
return this;
};
複製代碼
MarkdownIt.prototype.configure = function (presets) {
var self = this, presetName;
if (utils.isString(presets)) {
presetName = presets;
presets = config[presetName];
if (!presets) { throw new Error('Wrong `markdown-it` preset "' + presetName + '", check name'); }
}
... // 省略部分代碼
return this;
};
複製代碼
MarkdownIt.prototype.enable = function (list, ignoreInvalid) {
var result = [];
if (!Array.isArray(list)) { list = [ list ]; }
[ 'core', 'block', 'inline' ].forEach(function (chain) {
result = result.concat(this[chain].ruler.enable(list, true));
}, this);
... // 省略部分代碼
return this;
};
複製代碼
MarkdownIt.prototype.disable = function (list, ignoreInvalid) {
var result = [];
if (!Array.isArray(list)) { list = [ list ]; }
[ 'core', 'block', 'inline' ].forEach(function (chain) {
result = result.concat(this[chain].ruler.disable(list, true));
}, this);
... // 省略部分代碼
return this;
};
複製代碼
// plugin 是一個函數,會將後面的參數傳入 plugin 而且執行。
MarkdownIt.prototype.use = function (plugin /*, params, ... */) {
var args = [ this ].concat(Array.prototype.slice.call(arguments, 1));
plugin.apply(plugin, args);
return this;
};
複製代碼
// 1.,輸入必須是字符串
// 2. State 是 CoreParser 的狀態管理類,保存了 md 單例、src 編譯字符串、tokens 詞法單元等重要信息。
// 3.每種 Parser(InlineParser & BlockParser) 都有對應的狀態管理類,內部的實現有必定區別。
MarkdownIt.prototype.parse = function (src, env) {
if (typeof src !== 'string') {
throw new Error('Input data should be a String');
}
var state = new this.core.State(src, this, env);
this.core.process(state);
return state.tokens;
};
複製代碼
// md 實例上是有 renderer 屬性,能夠理解爲渲染器,對外暴露一些特定的 API,接收 tokens 來生成 HTML 字符串。
MarkdownIt.prototype.render = function (src, env) {
env = env || {};
return this.renderer.render(this.parse(src, env), this.options, env);
};
複製代碼
// md 實例上的 renderer,能夠理解爲渲染器,對外暴露一些特定的 API,接收 tokens 來生成 HTML 字符串。
MarkdownIt.prototype.parseInline = function (src, env) {
var state = new this.core.State(src, this, env);
state.inlineMode = true;
this.core.process(state);
return state.tokens;
};
複製代碼
MarkdownIt.prototype.renderInline = function (src, env) {
env = env || {};
return this.renderer.render(this.parseInline(src, env), this.options, env);
};
複製代碼
從總體來看,MarkdownIt 的流程以下圖:
隨着一步步對 MarkdownIt 的剖析以後,是否是驚歎於 Markdown 的架構設計,至少對於我來講,不少信息經過代碼的組織以及變量命名就已經傳達給我了,這也體現了閱讀優秀源碼的好處。
下一篇文章,將講解 Ruler 以及 Token 這兩個基礎類。