vuepress-plugin-demo-container 之 containers.js

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第3天,点击查看活动详情

上一篇文章介绍了 vuepress-plugin-demo-container的整体工作流程,本篇来详细介绍如何识别自定义代码块并构建HTML字符串

enhanceAppFiles

前文说到 enhanceAppFiles 是用来增加一些应用级别的配置,在本插件中,就是用来注册一个包裹示例代码的组件:

import DemoBlock from './DemoBlock.vue'
export default ({ Vue }) => {
  Vue.component('DemoBlock', DemoBlock)
}

展开、关闭、复制源代码都是依托于该组件实现,该组件的源代码可点击 DemoBlock.vue 查看

containers.js 详解

该文件就是用来识别自定义代码块的文件,首先列出文件源码:

const mdContainer = require('markdown-it-container');

// options 就是在config.js中plugins参数的配置
module.exports = options => {

  const {
    component = 'demo-block'
  } = options;

  const componentName = component
    .replace(/^\S/, s => s.toLowerCase())
    .replace(/([A-Z])/g, "-$1").toLowerCase();
    
  return md => {
    md.use(mdContainer, 'demo',
      {
       // 验证:::之后是否为 demo
        validate(params) {
          return params.trim().match(/^demo\s*(.*)$/);
        },
        render(tokens, idx) {
          const m = tokens[idx].info.trim().match(/^demo\s*(.*)$/);
          if (tokens[idx].nesting === 1) {
            const description = m && m.length > 1 ? m[1] : '';
            const content = tokens[idx + 1].type === 'fence' ? tokens[idx + 1].content : '';
            const encodeOptionsStr = encodeURI(JSON.stringify(options));
            return `<${componentName} :options="JSON.parse(decodeURI('${encodeOptionsStr}'))">
            <template slot="demo"><!--pre-render-demo:${content}:pre-render-demo--></template>
            ${description ? `<div slot="description">${md.render(description).html}</div>` : ''}
            <template slot="source">`
          }
          return `</template></${componentName}>`;
        }
      });
  };
}

代码并不长,我们一点一点来看。

options.vuepress/config.jsplugins 参数的配置,如下所示:

  plugins: [
    [
      require('../../src'),
      {
        component: 'DemoBlock',
        locales: [
  ...
        ]
      }
    ]
  ],

componentlocales 都会被传入到options中。在对options做解构的同时,给component属性赋予默认值demo-block。在 plugins参数中没有传入component属性的情况下,该属性就是 demo-block

  const componentName = component
    .replace(/^\S/, s => s.toLowerCase())
    .replace(/([A-Z])/g, "-$1").toLowerCase();

该处的代码是为了实现 DemoBlockdemo-block 的转换

render函数解析

下面,就是最为重要的渲染流程。首先来了解一下 markdown 转换为 HTML 的流程

markdown解析流程.png
总体可以概括为两步:

  1. markdown 经过词法解析得到 token
  2. token 渲染为 HTML

几个相关的token属性为:

  1. nesting:嵌套层级,1 对应 HTML 中的开始标签,-1 对应 HTML 中的结束标签,0表示中间值
  2. info:::: 后的信息
  3. type:token 类型,fence 表示代码块
    以下面代码块为例,将其转为token
::: demo 此处放置代码示例的描述信息,支持 `Markdown` 语法,**描述信息只支持单行**
`` `html
<template>
  <div class="red-center-text">
      <p>{{ message }}</p>
      <input v-model="message" placeholder="Input something..."/>
  </div>
</template>
<script>
export default {
  data() {
    return {
      message: 'Hello Vue'
    }
  }
}
</script>
<style>
.red-center-text { 
  color: #ff7875;
  text-align: center;
}
</style>
`` `
:::

转化后得到的token为:

[
  Token {
    type: 'container_demo_open',
    tag: 'div',
    attrs: null,
    map: [ 2, 26 ],
    nesting: 1,
    level: 0,
    children: null,
    content: '',
    markup: ':::',
    info: ' demo 此处放置代码示例的描述信息,支持 `Markdown` 语法,**描述信息只支持单行**',
    meta: null,
    block: true,
    hidden: false
  },
  Token {
    type: 'fence',
    tag: 'code',
    attrs: null,
    map: [ 3, 26 ],
    nesting: 0,
    level: 1,
    children: null,
    content: '<template>\n' +
      '  <div class="red-center-text">\n' +
      '      <p>{{ message }}</p>\n' +
      '      <input v-model="message" placeholder="Input something..."/>\n' +
      '  </div>\n' +
      '</template>\n' +
      '<script>\n' +
      'export default {\n' +
      '  data() {\n' +
      '    return {\n' +
      "      message: 'Hello Vue'\n" +
      '    }\n' +
      '  }\n' +
      '}\n' +
      '</script>\n' +
      '<style>\n' +
      '.red-center-text { \n' +
      '  color: #ff7875;\n' +
      '  text-align: center;\n' +
      '}\n' +
      '</style>\n',
    markup: '```',
    info: 'html',
    meta: null,
    block: true,
    hidden: false
  },
  Token {
    type: 'container_demo_close',
    tag: 'div',
    attrs: null,
    map: null,
    nesting: -1,
    level: 0,
    children: null,
    content: '',
    markup: ':::',
    info: '',
    meta: null,
    block: true,
    hidden: false
  }
]

render函数过程

  1. 遍历token数组。如果 nesting = 1 就构建HTML开标签。标签名就是上文解构出的 componentName
  2. 找到fencetoken,取出content属性,该属性就是代码块中的所有代码。并将代码以注释的形式放在插槽中,将其传入到DemoBlock.vue 中。
  3. 如果 nesting = -1,构建 HTML 闭合标签。
  4. 返回整个字符串

总结

这一步的主要作用就是识别 :::demo 代码块,构建HTML字符串。下一步就是处理HTML字符串,例如,将 vue template 转化为 render 函数、合并script、style等。

vuepress-plugin-demo-container源码解析系列

  1. 组件库中的示例代码是如何展示的呢?(一)
© 版权声明
THE END
喜欢就支持一下吧
点赞9 分享
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

取消
昵称表情代码图片

    暂无评论内容