likes
comments
collection
share

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

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

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

前言

markdown文件如何在前端框架中肆意运行(二) 基于第一节md 转 html分析后,继续来分析示例(效果预览) html 转换 vue 代码过程,其实只要知道目标代码格式,可转换为任意框架的代码(也需要 md 转 html 的调整配合),这里借 element-ui 了解如何将 html 转换为 vue 代码

现状

md 转化 html 结构如下

  • 首先要了解一下 demo-block 全局组件
  • <!--element-demo :element-demo--> 这块现在看来还没啥用 markdown文件如何在前端框架中肆意运行(二)

demo-block 组件

  • 三个区域
    • 组件运行效果
      • 在此环节基于 <!--element-demo :element-demo--> 注释占位声明成局部组件
    • 组件描述
      • md 转 html 过程中已处理
    • 组件代码展示
      • md 转 html 过程中已处理
<template>
  <div class="demo-block" :class="[blockClass, { hover: hovering }]" @mouseenter="hovering = true" @mouseleave="hovering = false">
    <div class="source">
      <!-- vue 组件实现代码运行区域  -->
      <slot name="source"></slot>
    </div>
    <div class="meta" ref="meta">
      <div class="description" v-if="$slots.default">
        <!-- vue 组件描述 -->
        <slot></slot>
      </div>
      <div class="highlight">
        <!-- vue 组件实现代码 -->
        <slot name="highlight"></slot>
      </div>
    </div>
    <!-- 控制显隐 meta 区域的按钮 -->
    <div class="demo-block-control" ref="control" :class="{ 'is-fixed': fixedControl }" @click="isExpanded = !isExpanded">
      <transition name="arrow-slide">
        <i :class="[iconClass, { hovering: hovering }]"></i>
      </transition>
      <transition name="text-slide">
        <span v-show="hovering">{{ controlText }}</span>
      </transition>
    </div>
  </div>
</template>

注释占位处理

  • 匹配注释占位并提取出单文件组件
  • 单文件组件转化为自执行函数,内容返回含有 render 函数的对象
    • genInlineComponentText 主要是借助 vue-template-compiler 编译 vue 模板的能力
  • 在原有 html 上拼接 source 插槽内容 --- 局部组件名 element-demo${id}
  • componenetsString 负责存储局部声明的所有组件
  • 为原有 html 增加 template 包裹,再拼接增加 script 来达到局部声明并导出
const {
  stripScript,
  stripTemplate,
  genInlineComponentText
} = require('./util');
const md = require('./config');

module.exports = function(source) {
  // md 转化 html
  const content = md.render(source);

  // html 中占位注释标记
  const startTag = '<!--element-demo:';
  const startTagLen = startTag.length;
  const endTag = ':element-demo-->';
  const endTagLen = endTag.length;

  let componenetsString = '';
  let id = 0; // demo 的 id
  let output = []; // 输出的内容
  let start = 0; // 字符串开始位置

  let commentStart = content.indexOf(startTag);
  let commentEnd = content.indexOf(endTag, commentStart + startTagLen);
  // 逐一解析 <!--element-demo :element-demo--> 占位注释内容
  while (commentStart !== -1 && commentEnd !== -1) {
    // 将占位注释内容前的字符串放入内容数组中
    output.push(content.slice(start, commentStart));
    // md 中定义的组件实现代码 -- vue 单文件组件
    const commentContent = content.slice(commentStart + startTagLen, commentEnd);
    // 提取 template 标签以及内部内容
    const html = stripTemplate(commentContent);
    // 提取 script 标签内部内容
    const script = stripScript(commentContent);
    // 组件转换为自执行函数,内容返回含有 render 函数的对象
    let demoComponentContent = genInlineComponentText(html, script);
    const demoComponentName = `element-demo${id}`;
    // 增加组件运行插槽并使用局部组件名
    output.push(`<template slot="source"><${demoComponentName} /></template>`);
    // 定义局部组件声明
    componenetsString += `${JSON.stringify(demoComponentName)}: ${demoComponentContent},`;

    // 重新计算下一次的位置
    id++;
    start = commentEnd + endTagLen;
    commentStart = content.indexOf(startTag, start);
    commentEnd = content.indexOf(endTag, commentStart + startTagLen);
  }

  // 仅允许在 demo 不存在时,才可以在 Markdown 中写 script 标签
  let pageScript = '';
  if (componenetsString) {
    // 未用自定义容器
    pageScript = `<script>
      export default {
        name: 'component-doc',
        components: {
          ${componenetsString}
        }
      }
    </script>`;
  } else if (content.indexOf('<script>') === 0) { // 硬编码,有待改善
    // 没有 html,开头就是 script 标签
    start = content.indexOf('</script>') + '</script>'.length;
    pageScript = content.slice(0, start);
  }
  // 将占位注释内容后的字符串放入内容数组中
  output.push(content.slice(start));
  return `
    <template>
      <section class="content element-doc">
        ${output.join('')}
      </section>
    </template>
    ${pageScript}
  `;
};

最终 vue 代码

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

结尾

截止到此,md-loader 解析完成,其支持在 md 中使用 vue 组件;vuepress、vitepress 也有很多类似的插件,比如:vuepress-plugin-demo-container、vuepress-plugin-demoblock-plus,相信大家阅读文章内容后再了解其内部实现会很容易

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