返回

markdown-it源码分析系列六——最终渲染器render函数的规则定义与解析器方法分析

前端

在这个系列的之前几篇文章中,我们分析了 markdown-it 的核心对象 Parser 的类定义与构造函数,分析了 markdown-it 语法规则定义的对象 core,分析了 markdown-it 的 Token 类定义与实例的创建与渲染。我们分析到了 token 已经生成了,如何使用它们来渲染成我们需要的内容呢?而负责这件事的,就是 markdown-it 的 Renderer 对象。本篇将对它的实现进行分析。

Renderer 类与方法

在所有 Parser 编译生成完 tokens 的时候,就传给 Renderer.render 方法了。我们来看下 Renderer 的定义。它位于 lib/renderer.js。

var Renderer = function(rules) {
  this._rules = rules;
};

Renderer 是一个构造函数,它接收一个 rules 参数,该参数是一个对象,其中包含了不同类型的 token 渲染函数。

Renderer.prototype.render = function(tokens, options, env) {
  var html = '';
  var rules = this._rules;

  for (var i = 0, len = tokens.length; i < len; i++) {
    var token = tokens[i];

    // 换行符,跳过。
    if (token.type === 'space') {
      continue;
    }

    // 其他类型,调用对应的渲染规则来生成 HTML。
    html += rules[token.type](token, options, env, this);
  }

  return html;
};

Renderer.prototype.render 方法是 Renderer 的核心方法,它接收 tokens、options 和 env 三个参数,返回渲染后的 HTML 字符串。

  • tokens 是一个包含所有 token 的数组。
  • options 是一个包含各种选项的对象,这些选项可以影响渲染后的 HTML 的样式。
  • env 是一个包含环境变量的对象,这些变量可以影响渲染后的 HTML 的内容。

在 render 方法中,我们首先创建一个空字符串 html 来存储渲染后的 HTML 字符串。然后,我们遍历 tokens 数组,对于每个 token,我们调用对应的渲染规则来生成 HTML 字符串。最后,我们将生成的 HTML 字符串连接成一个字符串并返回。

再来细看 render 方法的具体实现。首先,我们检查 token 的类型。如果是 space 类型,我们直接跳过,因为 space 类型 token 表示换行符,我们不需要在 HTML 中渲染换行符。

if (token.type === 'space') {
  continue;
}

如果不是 space 类型,我们调用对应的渲染规则来生成 HTML 字符串。

html += rules[token.type](token, options, env, this);

其中,rules[token.type] 是一个函数,它接收 token、options、env 和 this 四个参数,返回渲染后的 HTML 字符串。

最后,我们将生成的 HTML 字符串连接成一个字符串并返回。

return html;

渲染规则

渲染规则是用来将 token 渲染成 HTML 字符串的函数。这些渲染规则定义在 default_rules 对象中,该对象位于 lib/renderer_rules.js。

var default_rules = {
  // 代码块
  'code': function(tokens, idx, options, env, self) {
    return highlight(
      tokens[idx].content,
      tokens[idx].info,
      options.highlight
    );
  },

  // 引用
  'blockquote': function(tokens, idx, options, env, self) {
    return '<blockquote>\n' + self.render(tokens[idx].tokens, options, env) + '</blockquote>';
  },

  // 列表
  'list': function(tokens, idx, options, env, self) {
    var bullet = tokens[idx].ordered ? 'ol' : 'ul';
    var html = '<' + bullet + '>\n';

    for (var i = 0, len = tokens[idx].items.length; i < len; i++) {
      html += '<li>' + self.render(tokens[idx].items[i].tokens, options, env) + '</li>\n';
    }

    html += '</' + bullet + '>\n';
    return html;
  },

  // 标题
  'heading': function(tokens, idx, options, env, self) {
    var heading = 'h' + tokens[idx].hLevel;
    return '<' + heading + '>' + self.render(tokens[idx].tokens, options, env) + '</' + heading + '>\n';
  },

  // 链接
  'link': function(tokens, idx, options, env, self) {
    return '<a href="' + self.renderInlineAsText(tokens[idx].href, tokens, options, env) + '"' +
           (tokens[idx].title ? ' title="' + self.renderInlineAsText(tokens[idx].title, tokens, options, env) + '"' : '') +
           '>' + self.renderInlineAsText(tokens[idx].content, tokens, options, env) + '</a>';
  },

  // 强调
  'strong': function(tokens, idx, options, env, self) {
    return '<strong>' + self.renderInlineAsText(tokens[idx].content, tokens, options, env) + '</strong>';
  },

  // 斜体
  'em': function(tokens, idx, options, env, self) {
    return '<em>' + self.renderInlineAsText(tokens[idx].content, tokens, options, env) + '</em>';
  },

  // 删除线
  'del': function(tokens, idx, options, env, self) {
    return '<del>' + self.renderInlineAsText(tokens[idx].content, tokens, options, env) + '</del>';
  },

  // 代码
  'code': function(tokens, idx, options, env, self) {
    return '<code>' + self.renderInlineAsText(tokens[idx].content, tokens, options, env) + '</code>';
  },

  // 引用
  'br': function(tokens, idx, options, env, self) {
    return '<br>';
  },

  // 图片
  'image': function(tokens, idx, options, env, self) {
    return '<img src="' + self.renderInlineAsText(tokens[idx].href, tokens, options, env) + '" alt="' + self.renderInlineAsText(tokens[idx].title, tokens, options, env) + '"';
  },

  // 表格
  'table': function(tokens, idx, options, env, self) {
    var html = '<table>\n<thead>\n';
    for (var i = 0, len = tokens[idx].header.length; i < len; i++) {
      html += '<th>' + self.render(tokens[idx].header[i].tokens, options, env) + '</th>\n';
    }
    html += '</thead>\n<tbody>\n';
    for (var j = 0, len = tokens[idx].rows.length; j < len; j++) {
      html += '<tr>';
      for (var k = 0, len = tokens[idx].rows[j].length; k < len; k++) {
        html += '<td>' + self.render(tokens[idx].rows[j][k].tokens, options, env) + '</td>';
      }
      html += '</tr>\n';
    }
    html += '</tbody>\n</table>';
    return html;
  },

  // 表格行
  'tablerow': function(tokens, idx, options, env, self) {
    var html = '<tr>';
    for (var i = 0, len = tokens[idx].cells.length; i < len; i++) {
      html += '<td>' + self.render(tokens[idx].cells[i].tokens, options, env) + '</td>';
    }
    html += '</tr>';
    return html;
  },

  // 表格头
  'tablecell': function(tokens, idx, options, env, self) {
    var html = '<th>' + self.render(tokens[idx].tokens, options, env) + '</th>';
    return html;
  },

  // 表格数据
  'tablecell': function(tokens, idx, options, env, self) {
    var html = '<td>' + self.render(tokens[idx].tokens, options, env) + '</td>';
    return html