【elementui源码解析】如何实现自动渲染md文档-第四篇

news2024/10/5 16:19:16

目录

 1.前言

2.md-loader - index.js

1)md.render()

2)定义变量

3)while 

stripTemplate 

stripScript 

genInlineComponentText 

4)pageScript

5)return

6)demo-block

3.总结 


所有章节:

  • 【elementui源码解析】如何实现自动渲染md文档-第一篇
  • 【elementui源码解析】如何实现自动渲染md文档-第二篇
  • 【elementui源码解析】如何实现自动渲染md文档-第三篇

 1.前言

前面我们分析了md-loader中的config.js、containers.js和fence.js这三个文件的作用,今天我们来看md-loader的index.js文件,前面几个文件都在这个文件里得到应用,这也是elementui实现渲染md文件最重要的文件。如果还没阅读前几篇,可以点击下面链接查看前几篇。​​​​​​​

2.md-loader - index.js

图1和图2就是index.js的源代码。

1-6行,引入了util中的几个方法,我们在下面用到的时候再介绍。然后就是引入了之前在config.js中定义的md解析器。

图1

图2

1)md.render()

我们还是以之前的md文档为例,如图3。

图3 

图4

第9行 const content = md.render(source); 输出的内容如下。输出格式有点混乱,我将其格式化了一下在图4下面的代码框里。

可以看见 id="input-shu-ru-kuang", 这就是之前cofig.js 里的 slugify 的作用。之后在elementui的锚点的时候就是通过这个id来定位到对应的位置。

然后可以看见 <demo-block></demo-block>,这是之前在 container.js 里定义的。我们在下面会介绍这个elementui自定义的全局的组件。然后第一个div里的内容就是description(结合图5看),然后就是这段注释的内容, <!--element-demo: <div>111test</div> :element-demo-->, 这里在下面会有用处。最后可以看见一段插槽,这就是 fence.js 覆盖的渲染策略,返回了md文档的代码部分(结合图6看)。

    <h2 id="input-shu-ru-kuang">
      <a class="header-anchor" href="#input-shu-ru-kuang">¶</a> Input 输入框
    </h2>
    <p>通过鼠标或键盘输入字符</p>
    <h3 id="jin-yong-zhuang-tai">
      <a class="header-anchor" href="#jin-yong-zhuang-tai">¶</a> 禁用状态
    </h3>
    <demo-block>
      <div>
        <p>通过 <code>disabled</code> 属性指定是否禁用 input 组件</p>
      </div>
      <!--element-demo: <div>111test</div> :element-demo-->
      <template slot="highlight">
        <pre v-pre>
          <code class="html">&lt;div&gt;111test&lt;/div&gt;
          </code>
        </pre>
      </template>
    </demo-block>

图5 

图6

2)定义变量

代码11-22行如下。

我们直接看最后两行,commentstart就是获取了在 md.render() 返回的字符串中 <!--element-demo: 这一段字符串的起始位置,commentEnd获取了 :element-demo--> 这一串字符串的起始位置。

  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);

3)while 

这个while函数的条件是在render函数返回的字符串中找到了 <!--element-demo:  和 :element-demo-->。这是必须的,为什么呢,因为这里面包含的就是我们要在页面上执行的demo的例子,可能我这里因为例子比较简单,不太直观,假设 <!--element-demo: :element-demo--> 里面包含的字符串是如下代码,这里面包含的是一个el-input实例,md文档里必须要有这个才会在页面上显示这个例子,如果没有这个demo例子,是不会执行渲染的。

<el-input
  placeholder="请输入内容"
  v-model="input"
  :disabled="true">
</el-input>

<script>
export default {
  data() {
    return {
      input: ''
    }
  }
}
</script>
while (commentStart !== -1 && commentEnd !== -1) {
    // 截取 <!--element-demo: :element-demo--> 之前的内容
    output.push(content.slice(start, commentStart)); 

    // <!--element-demo: :element-demo--> 之间的内容
    const commentContent = content.slice(commentStart + startTagLen, commentEnd); 
    // 获取template标签里的内容
    const html = stripTemplate(commentContent);
    // 获取Script标签里的内容
    const script = stripScript(commentContent);
    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);
}

我们看第一行,它是截取了<!--element-demo:  之前的代码片段。相当于把要渲染的实例之前的文字部分全部截取出去了,然后push到outpush里。

然后第二行 commentContent, 它是获取了<!--element-demo:   :element-demo--> 之间的代码片段, 接下来,调用了stripTemplate方法、stripScript方法和genInlineComponentText方法。

stripTemplate 

这个函数的功能是去除传入内容中的<script>和<style>标签及其内容,返回处理后的字符串。如注释写的那样,“编写例子时不一定有 template,所以采取的方案是剔除其他的内容”。首先使用trim()方法去除字符串两端的空格,然后通过正则表达式替换掉<script>和<style>标签及其内容,最后再次使用trim()方法去除可能产生的两端空格。如果处理后的字符串为空,则直接返回原字符串。

图7

stripScript 

这个函数用于从给定的content中提取<script>标签内的内容。它通过正则表达式匹配<script>标签及其内的所有内容,然后返回匹配结果的第二项(即<script>标签内的内容),如果没匹配到则返回空字符串。

图8

genInlineComponentText 

这段代码有点长,就直接放代码片段。这段代码有点难理解,但最主要的就是compileTemplate。它的作用是将Vue模板语法转换为可执行的JavaScript渲染函数。也就是我们将代码的template里的html代码交给compileTemplate处理,然后返回给我们处理后的可渲染的html代码。

然后tips和errors就是处理一些错误。

下面的script处理了传入进来的script标签里的代码,对传入的script进行处理,如果存在内容,则替换开头的export defaultconst democomponentExport =,以适应内联组件的定义方式(也就是上面的filename:inline-component)。若脚本为空,则赋予默认的空对象{}作为导出内容。

最后将模板的渲染函数、静态渲染函数及整合后的脚本导出内容包装在一个立即执行的函数表达式中,确保组件的定义是独立且不会污染全局命名空间。

最重要的这个函数返回的是立即执行表达式去渲染我们的md文档中写的template和script,这也就是为什么我们在md文档里写例子,在页面上就能执行这个组件的渲染。

function genInlineComponentText(template, script) {
  // https://github.com/vuejs/vue-loader/blob/423b8341ab368c2117931e909e2da9af74503635/lib/loaders/templateLoader.js#L46
  const finalOptions = {
    source: `<div>${template}</div>`,
    filename: 'inline-component', // TODO:这里有待调整
    compiler
  };

  // const { compileTemplate } = require('@vue/component-compiler-utils');
  const compiled = compileTemplate(finalOptions);
  // tips
  if (compiled.tips && compiled.tips.length) {
    compiled.tips.forEach(tip => {
      console.warn(tip);
    });
  }
  // errors
  if (compiled.errors && compiled.errors.length) {
    console.error(
      `\n  Error compiling template:\n${pad(compiled.source)}\n` +
        compiled.errors.map(e => `  - ${e}`).join('\n') +
        '\n'
    );
  }
  let demoComponentContent = `
    ${compiled.code}
  `;
  // todo: 这里采用了硬编码有待改进
  script = script.trim();
  if (script) {
    script = script.replace(/export\s+default/, 'const democomponentExport =');
  } else {
    script = 'const democomponentExport = {}';
  }
  demoComponentContent = `(function() {
    ${demoComponentContent}
    ${script}
    return {
      render,
      staticRenderFns,
      ...democomponentExport
    }
  })()`;
  return demoComponentContent;
}

回到while函数。demoComponentName定义了组件的名字,然后放入slot="source"的插槽。然后定义了componenetsString,定义了组件的名字和其对应的内容,在下面会渲染。然后就是将id、start、commentStart和commentEnd全部更新,去找下一个demo例子。

4)pageScript

可以看见这个if就是如过当我们上面的while函数执行了,那么componentsString肯定是有值的,那么就将生成一个默认的Vue组件脚本结构,包含这些组件,并将其赋值给pageScript。

如果没有componenetsString,那么会检查Markdown内容content的开头是否直接包含了<script>标签。如果是,则认为Markdown中直接定义了组件,并尝试提取这部分内容作为pageScript。这在elementui中的某些情况是适用的,比如color.md,这里面就没有直接使用demo例子,而是在md文档中使用了script标签,如图9。可以看见我搜索:::demo,是并没有的,而在开头就有一段script脚本,具体作用大家可以去看color.md源码。

  // 仅允许在 demo 不存在时,才可以在 Markdown 中写 script 标签
  // todo: 优化这段逻辑
  let pageScript = '';
  if (componenetsString) {
    pageScript = `<script>
      export default {
        name: 'component-doc',
        components: {
          ${componenetsString}
        }
      }
    </script>`;
  } else if (content.indexOf('<script>') === 0) { // 硬编码,有待改善
    start = content.indexOf('</script>') + '</script>'.length;
    pageScript = content.slice(0, start);
  }

 图9

5)return

最后return这个内容大家可能就一下子清晰了,返回的内容是template模版和script,这不就是一个vue文件所需要的吗,只不过template是渲染了当前页面所有demo的html,pagescript是定义了当前页面的所有组件。

图10

6)demo-block

我们最后简单看下demo-block组件。主要看下它的html结构。

可以看见 

  • <slot name="source"></slot>, 
  • <div class="description" v-if="$slots.default"> <slot></slot> </div>
  • <div class="highlight"> <slot name="highlight"></slot> </div>
<template>
  <div
    class="demo-block"
    :class="[blockClass, { hover: hovering }]"
    @mouseenter="hovering = true"
    @mouseleave="hovering = false"
  >
    <div class="source">
      <slot name="source"></slot>
    </div>
    <div class="meta" ref="meta">
      <div class="description" v-if="$slots.default">
        <slot></slot>
      </div>
      <div class="highlight">
        <slot name="highlight"></slot>
      </div>
    </div>
    <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>

我们结合elementui的demo来看一下。我用对应颜色标记了代码和渲染的部分,然后大家结合上面代码观察,再想一想之前篇章中介绍的返回的内容,大家应该一下就清晰了。

3.总结 

我们的elementui-md渲染部分到这里就结束了,其实关于elementui的源码还有很多地方值得学习的,大家可以自己下去看看elementui源码,比如其中关于tpl文件的应用,关于国际化的应用、关于scss文件的处理等等。以后会慢慢攥写更多文章,希望大家多多支持。一起进步~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1827205.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

React@16.x(29)useRef

目录 1&#xff0c;介绍2&#xff0c;和 React.createRef() 的区别3&#xff0c;计时器的问题 目前来说&#xff0c;因为函数组件每次触发更新时&#xff0c;都会重新运行。无法像类组件一样让一些内容保持不变。 所以才出现了各种 HOOK 函数&#xff1a;useState&#xff0c;u…

CCAA质量管理【学习笔记】​​ 备考知识点笔记(二)

第三节 GB/T19001-2016 标准正文 本节为ISO9001:2015 标准条款的正文内容&#xff0c;各条款中的术语参照上节内容理解时&#xff0c;会很轻松。本节不再一一对各条款讲解。 引 言 0.1 总 则 采用质量管理体系是组织的一项战略决策&#xff0c;能够帮助其提高整体绩效…

C++11移动语义

前言 之前我们已经知道了在类里开辟数组后&#xff0c;每一次传值返回和拷贝是&#xff0c;都会生成一个临时变量 class Arr { public://构造Arr() {/*具体实现*/ };//拷贝Arr(const Arr& ar) {/*具体实现*/ };//重载Arr operator(const Arr& ar) { /*具体实现*/Arr …

北方工业大学24计算机考研情况,学硕专硕都是国家线复试!

北方工业大学&#xff08;North China University of Technology&#xff0c;NCUT&#xff09;&#xff0c;简称“北方工大”&#xff0c;位于北京市&#xff0c;为一所以工为主、文理兼融&#xff0c;具有学士、硕士、博士培养层次的多科性高等学府&#xff0c;是中华人民共和…

自动化数据驱动?最全接口自动化测试yaml数据驱动实战

前言 我们在做自动化测试的时候&#xff0c;通常会把配置信息和测试数据存储到特定的文件中&#xff0c;以实现数据和脚本的分离&#xff0c;从而提高代码的易读性和可维护性&#xff0c;便于后期优化。 而配置文件的形式更是多种多样&#xff0c;比如&#xff1a;ini、yaml、…

Android 工程副总裁卸任

Android 工程副总裁卸任 Android工程副总裁Dave Burke宣布&#xff0c;他将辞去领导Android工程的职位&#xff0c;将重心转向“AI/生物”项目。不过&#xff0c;他并没有离开Alphabet&#xff0c;目前仍将担任Android系统开发顾问的角色。 Burke参与了Android系统的多个关键…

Vue45-分析脚手架结构

一、脚手架项目结构一览 二、src、public文件夹外的文件 2-1、babel.config.js文件 详细的配置规格&#xff1a;babel官网。 2-2、package.json包的说明书 build命令&#xff1a;代码写完了&#xff0c;最后使用build命名构建整个工程&#xff0c;将其变成浏览器能够运行的项…

【Ardiuno】实验使用ESP32单片机根据光线变化控制LED小灯开关(图文)

今天小飞鱼继续来实验ESP32的开发&#xff0c;这里使用关敏电阻来配合ESP32做一个我们平常接触比较多的根据光线变化开关灯的实验。当白天时有太阳光&#xff0c;则把小灯关闭&#xff1b;当光线不好或者黑天时&#xff0c;自动打开小灯。 int value;void setup() {pinMode(34…

虚拟机上安装centos7

目录 1&#xff0c;下载centos镜像2&#xff0c;在VMware中新建虚拟机3&#xff0c;为新创建的虚拟机挂载镜像4&#xff0c;安装centos75&#xff0c;配置网络 1&#xff0c;下载centos镜像 直接下载地址 https://mirrors.tuna.tsinghua.edu.cn/centos-vault/7.8.2003/isos/x8…

定个小目标之刷LeetCode热题(20)

这题与上一题有一点不同&#xff0c;上一题是判断链表是否存在环&#xff0c;这题是寻找入环的第一个节点&#xff0c;有一个规则是这样的&#xff0c;在存在环的情况下&#xff0c;运用快慢指针判断是否有环结束时&#xff0c;把快指针指向头结点&#xff0c;慢指针不变&#…

【Linux】进程控制1——进程创建和进程终止

1.进程创建 1.1.再谈fork 在linux中fork函数时非常重要的函数&#xff0c;它从已存在进程中创建一个新进程。新进程为子进程&#xff0c;而原进程为父进程。 #include <unistd.h> pid_t fork(void);//pid_t为整形 返回值&#xff1a;子进程中的fork()返回0&#xff…

为什么电源滤波器中的电容器太大

所有 AC-DC 转换器&#xff0c;无论是线性电源还是具有某种开关元件&#xff0c;都需要一种机制来获取交流侧的变化功率并在直流侧产生恒定功率。通常&#xff0c;大滤波电容器用于在交流功率高于直流负载所需时吸收和存储能量&#xff0c;并在交流功率低于所需时向负载提供能量…

排序算法、堆排序、大顶堆、小顶堆、手写快排-215. 数组中的第K个最大元素、2336. 无限集中的最小数字

目录 215. 数组中的第K个最大元素 题目链接及描述 题目分析 堆排序分析 堆排序代码编写 快排分析 快排代码编写 2336、无限集中的最小数字 题目链接及描述 题目分析 代码编写 215. 数组中的第K个最大元素 题目链接及描述 215. 数组中的第K个最大元素 - 力扣&#…

哈工大-公差基础-配合(互换性和测量基础)-3

基轴制的情况&#xff1a; 三种配合类别的选择&#xff1a; 公差带是由标准公差和基本偏差组成的。 公差带的选用&#xff1a; 未注公差的标准&#xff1a; 练习&#xff1a; 计算过程&#xff1a;

Zookeeper: 配置参数解读

Zookeeper中的配置文件zoo.cfg中参数含义解读如下&#xff1a; tickTime&#xff1a;通信心跳时间&#xff0c;Zookeeper服务器与客户端心跳时间&#xff0c;单位毫秒。 initLimit: LF初始通信时限 Leader和Follower初始连接时能容忍的最多心跳数。 syncLimit: LF同步通信时…

枚举算法01

限制&#xff1a;升序-局部-表征新加的数大于前面一个 从前往后依次枚举每个位置上的数是几 我们排列时候 是从1 到 n 实际上比前面的数大 递归时 1.把搜索问题顺序变成搜索树 2 如何把树转化为

Python教程:超详细1小时学会Python,太简单了!

1.Hello world 安装完Python之后&#xff0c;打开IDLE(Python GUI) &#xff0c;该程序是Python语言解释器,你写的语句能够立即运行。 我们写下一句著名的程序语句&#xff1a; 并按回车&#xff0c;你就能看到这句被K&R引入到程序世界的名言。 在解释器中选择"File…

MyBatis使用Demo

文章目录 01、Mybatis 意义02、Mybatis 快速入门04、Mapper 代理开发05、Mybatis 配置文件07、查询所有&结果映射08、查询-查看详情09、查询-条件查询10、查询-动态条件查询多条件动态查询单条件动态查询 11、添加&修改功能添加功能修改功能 12、删除功能删除一个批量删…

港风归来‖王晶监制首部民俗电影《民间憋宝传说》定档6月18日

随着暑期档的临近&#xff0c;本月即将上映一部备受期待的电影《民间憋宝传说》&#xff0c;本片被视为香港著名导演王晶的强势回归&#xff0c;重新捍卫属于他的“商业片之王”的宝座&#xff0c;无疑为这部电影增添了浓厚的情感色彩与期待值。 一&#xff1a;港风再现 王晶&…