likes
comments
collection
share

markdown文件如何在前端框架中肆意运行(一)

作者站长头像
站长
· 阅读数 39

我正在参加「掘金·启航计划」

第三方库通常有对应文档,尤其组件库文档会附带组件效果以及 demo 代码,对使用者十分友好;查看库源码发现使用 md 文件编写,今天通过解析 element-ui 组件库来看看 md 文件是如何一步步被处理的

处理流程

markdown文件如何在前端框架中肆意运行(一)

示例

md文件(效果预览) markdown文件如何在前端框架中肆意运行(一)

分析

  1. 处理过程通过自定义 loader 实现,目录结构如下
md-loader
    config.js
    containers.js
    fence.js
    index.js
    util.js
  1. 此篇先讲解 md 转 html 过程,依赖库如下
    • markdown-it
      • 解析:md 字符串为 tokens
      • 渲染:针对不同 type 的 token 调用不同的 render 转换 html
    • markdown-it-chain
      • 依赖 markdown-it 后,通过链式 api 进行配置设置,获取 markdown-it 实例
    • markdown-it-anchor
      • 进行标题锚点生成,方便配合全局搜索快速定位标题锚点
    • markdown-it-container
      • 定义自定义容器格式

逐行解析

  1. index.js 中 md.render 会按照 config.js 中定义的部分规则、默认规则组成的规则链,逐步解析 md(省略部分为 html 转 vue 过程)
const md = require('./config');

module.exports = function(source) {
    // md 转化 html
  const content = md.render(source);
}
  1. conifg.js 对 markdown-it 进行自定义配置
/**
 * markdown-it-chain 实例化配置后,通过链式 api 配置设置选项、插件
 * 通过 toMd 使用上述配置创建一个 markdown-it 实例
 * 注:直接使用 markdown-it 也能达到 markdown-it-chain 效果
 */
const Config = require('markdown-it-chain');
const anchorPlugin = require('markdown-it-anchor');
const slugify = require('transliteration').slugify;
const containers = require('./containers');
const overWriteFenceRule = require('./fence');

const config = new Config();
// 1.设置选项
config.options
  .html(true)
  .end()
  .plugin('anchor')
  // 2.标签锚点插件 --- 针对 md 中标题增加锚点
  .use(anchorPlugin, [
    {
      level: 2,
      slugify: slugify, // 中文转拼音
      permalink: true,
      permalinkBefore: true
    }
  ])
  .end()
  // 3.自定义容器插件
  .plugin('containers')
  .use(containers)
  .end();

const md = config.toMd();
// 4.复写代码块渲染规则 -- 针对 type 为 fence 进行处理
overWriteFenceRule(md);

module.exports = md;
  1. fence.js 覆盖渲染函数(type = fence)
// 覆盖默认的 fence 渲染策略
module.exports = md => {
  const defaultRender = md.renderer.rules.fence;
  md.renderer.rules.fence = (tokens, idx, options, env, self) => {
    // type:fence 的渲染处理规则
    const token = tokens[idx];
    // 判断该 fence 是否在 :::demo 内
    const prevToken = tokens[idx - 1];
    const isInDemoContainer = prevToken && prevToken.nesting === 1 && prevToken.info.trim().match(/^demo\s*(.*)$/);
    // 这里的 html 就是在 md 中 ```html  ``` 解析出来的(可以任意定义)
    if (token.info === 'html' && isInDemoContainer) {
      // 使用 v-pre 会被保留当前以及子标签按原样渲染;路由切换时,会获取 code 对应的 dom,使用 highlight.js 进行代码高亮
      return `<template slot="highlight"><pre v-pre><code class="html">${md.utils.escapeHtml(token.content)}</code></pre></template>`;
    }
    return defaultRender(tokens, idx, options, env, self);
  };
};
  1. containers.js 使用 markdown-it-container 默认解析 ::: 标记,通过 validate 钩子来确保自定义标记;此外也定义对应的渲染函数
const mdContainer = require('markdown-it-container');

module.exports = md => {
  // 自定义容器实现
  md.use(mdContainer, 'demo', {
    // 解析到 :::    ::: 就会触发 validate 函数
    // params 就是第一个 ::: 与 ``` 间的内容,就是例子中的【 demo 使用`type`、`plain`、`round`和`circle`属性来定义 Button 的样式。】
    validate(params) {
      return params.trim().match(/^demo\s*(.*)$/);
    },
    /**
     * validate 函数返回 true,会触发 render
     * tokens 是通过 markdown-it 规则链解析 md 产生的
     * idx 是对应容器 opening、closing tokens 的下标 --- 所以 render 会触发两次
     */
    render(tokens, idx) {
      const m = tokens[idx].info.trim().match(/^demo\s*(.*)$/);
      // nesting `1` 容器打开标记、`-1` 容器关闭标记(标记就是 :::)
      if (tokens[idx].nesting === 1) {
        // 获取 demo-block 默认插槽内容
        const description = m && m.length > 1 ? m[1] : '';
        // type fence:代码块
        const content = tokens[idx + 1].type === 'fence' ? tokens[idx + 1].content : '';
        // 使用 demo-block 组件,<!--element-demo  :element-demo--> 作为 html 转 vue 的标记
        return `<demo-block>
        ${description ? `<div>${md.render(description)}</div>` : ''}
        <!--element-demo: ${content}:element-demo-->
        `;
      }
      return '</demo-block>';
    }
  });
};

md 转换 html 结果

在转换过程中将组件描述、组件运行占位注释、组件代码展示都放在 demo-block 全局组件内,方便后期将其转换为 vue 可执行代码 markdown文件如何在前端框架中肆意运行(一)

结尾

截止到此,md 转 html 过程已经结束;发现 markdown-it 很灵活,每个环节、标记都可以自定义;欢迎大家评论交流,后面会继续html 转换为具体前端框架可运行代码的文章

希望看完的朋友可以点个喜欢/关注,您的支持是对我最大的鼓励