当前位置: 首页 > news >正文

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

目录

 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 的 idlet 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-inputplaceholder="请输入内容"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#L46const finalOptions = {source: `<div>${template}</div>`,filename: 'inline-component', // TODO:这里有待调整compiler};// const { compileTemplate } = require('@vue/component-compiler-utils');const compiled = compileTemplate(finalOptions);// tipsif (compiled.tips && compiled.tips.length) {compiled.tips.forEach(tip => {console.warn(tip);});}// errorsif (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><divclass="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><divclass="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文件的处理等等。以后会慢慢攥写更多文章,希望大家多多支持。一起进步~

相关文章:

  • 监督学习:从数据中学习预测模型的艺术与科学
  • 《C语言程序设计》考试大纲-硕士研究生入学考试
  • 计网重点面试题-TCP三次握手四次挥手
  • 数据分析-相关性
  • CentOS 7 安装部署Cassandra4.1.5
  • Python基础教程(三十):math模块
  • Windows环境部署MySQL_8.4.0 LTS的部署安装、验证连接以及卸载全过程实操手册
  • 链表中环的入口节点
  • JAVA大型医院绩效考核系统源码:​医院绩效考核实施的难点痛点
  • STL——函数对象,谓词
  • VMware虚拟机三种网络模式设置 - Bridged(桥接模式)
  • 王老吉“杀疯啦”?传统品牌如何创新营销“破圈”而出
  • 《计算机英语》 Unit 3 Software Engineering 软件工程
  • vue实现不预览PDF的情况下打印pdf文件
  • 【专业英语 复习】第10章 Information System
  • 【vuex入门系列02】mutation接收单个参数和多个参数
  • Flex布局到底解决了什么问题
  • Intervention/image 图片处理扩展包的安装和使用
  • java 多线程基础, 我觉得还是有必要看看的
  • js对象的深浅拷贝
  • mockjs让前端开发独立于后端
  • PHP面试之三:MySQL数据库
  • python docx文档转html页面
  • Python进阶细节
  • Spring-boot 启动时碰到的错误
  • supervisor 永不挂掉的进程 安装以及使用
  • VirtualBox 安装过程中出现 Running VMs found 错误的解决过程
  • 阿里云爬虫风险管理产品商业化,为云端流量保驾护航
  • 不发不行!Netty集成文字图片聊天室外加TCP/IP软硬件通信
  • 机器学习中为什么要做归一化normalization
  • 排序(1):冒泡排序
  • 如何实现 font-size 的响应式
  • 如何优雅的使用vue+Dcloud(Hbuild)开发混合app
  • 网页视频流m3u8/ts视频下载
  • 微信开放平台全网发布【失败】的几点排查方法
  • 温故知新之javascript面向对象
  • 系统认识JavaScript正则表达式
  • 最近的计划
  • C# - 为值类型重定义相等性
  • 基于django的视频点播网站开发-step3-注册登录功能 ...
  • 如何在 Intellij IDEA 更高效地将应用部署到容器服务 Kubernetes ...
  • 数据可视化之下发图实践
  • ​埃文科技受邀出席2024 “数据要素×”生态大会​
  • ​一、什么是射频识别?二、射频识别系统组成及工作原理三、射频识别系统分类四、RFID与物联网​
  • ​云纳万物 · 数皆有言|2021 七牛云战略发布会启幕,邀您赴约
  • # MySQL server 层和存储引擎层是怎么交互数据的?
  • #14vue3生成表单并跳转到外部地址的方式
  • #Datawhale AI夏令营第4期#多模态大模型复盘
  • (AngularJS)Angular 控制器之间通信初探
  • (pycharm)安装python库函数Matplotlib步骤
  • (多级缓存)多级缓存
  • (附源码)springboot“微印象”在线打印预约系统 毕业设计 061642
  • (回溯) LeetCode 131. 分割回文串
  • (一) springboot详细介绍
  • (转) SpringBoot:使用spring-boot-devtools进行热部署以及不生效的问题解决