0x00 前言

书接上文,本文将从源码功能方面讲解下 vue-code-view 组件核心逻辑,您可以了解以下内容:

  • 动态组件的使用。
  • codeMirror插件的使用。
  • 单文件组件(SFC,single-file component) Parser。

0x01 CodeEditor组件

项目使用功能丰富的codeMirror实现在线代码展示编辑功能。

npm 包安装:

  1. npm install codemirror --save

子组件 src\src\code-editor.vue 完整源码:

  1. <template>
  2. <div class="code-editor">
  3. <textarea ref="codeContainer" />
  4. </div>
  5. </template>
  6. <script>
  7. // 引入核心
  8. import CodeMirror from "codemirror";
  9. import "codemirror/lib/codemirror.css";
  10. // 主题 theme style
  11. import "codemirror/theme/base16-light.css";
  12. import "codemirror/theme/base16-dark.css";
  13. // 语言 mode
  14. import "codemirror/mode/vue/vue";
  15. // 括号/标签 匹配
  16. import "codemirror/addon/edit/matchbrackets";
  17. import "codemirror/addon/edit/matchtags";
  18. // 括号/标签 自动关闭
  19. import "codemirror/addon/edit/closebrackets";
  20. import "codemirror/addon/edit/closetag";
  21. // 代码折叠
  22. import "codemirror/addon/fold/foldgutter.css";
  23. import "codemirror/addon/fold/brace-fold";
  24. import "codemirror/addon/fold/foldcode";
  25. import "codemirror/addon/fold/foldgutter";
  26. import "codemirror/addon/fold/comment-fold";
  27. // 缩进文件
  28. import "codemirror/addon/fold/indent-fold";
  29. // 光标行背景高亮
  30. import "codemirror/addon/selection/active-line";
  31. export default {
  32. name: "CodeEditor",
  33. props: {
  34. value: { type: String },
  35. readOnly: { type: Boolean },
  36. theme: { type: String },
  37. matchBrackets: { type: Boolean },
  38. lineNumbers: { type: Boolean },
  39. lineWrapping: { type: Boolean },
  40. tabSize: { type: Number },
  41. codeHandler: { type: Function },
  42. },
  43. data() {
  44. return {
  45. // 编辑器实例
  46. codeEditor: null,
  47. // 默认配置
  48. defaultOptions: {
  49. mode: "text/x-vue", //语法高亮 MIME-TYPE
  50. gutters: [
  51. "CodeMirror-linenumbers",
  52. "CodeMirror-foldgutter",
  53. ],
  54. lineNumbers: this.lineNumbers, //显示行号
  55. lineWrapping: this.lineWrapping || "wrap", // 长行时文字是换行 换行(wrap)/滚动(scroll)
  56. styleActiveLine: true, // 高亮选中行
  57. tabSize: this.tabSize || 2, // tab 字符的宽度
  58. theme: this.theme || "base16-dark", //设置主题
  59. autoCloseBrackets: true, // 括号自动关闭
  60. autoCloseTags: true, // 标签自动关闭
  61. matchTags: true, // 标签匹配
  62. matchBrackets: this.matchBrackets || true, // 括号匹配
  63. foldGutter: true, // 代码折叠
  64. readOnly: this.readOnly ? "nocursor" : false, // boolean|string “nocursor” 设置只读外,编辑区域还不能获得焦点。
  65. },
  66. };
  67. },
  68. watch: {
  69. value(value) {
  70. const editorValue = this.codeEditor.getValue();
  71. if (value !== editorValue) {
  72. this.codeEditor.setValue(this.value);
  73. }
  74. },
  75. immediate: true,
  76. deep: true,
  77. },
  78. mounted() {
  79. // 初始化
  80. this._initialize();
  81. },
  82. methods: {
  83. // 初始化
  84. _initialize() {
  85. // 初始化编辑器实例,传入需要被实例化的文本域对象和默认配置
  86. this.codeEditor = CodeMirror.fromTextArea(
  87. this.$refs.codeContainer,
  88. this.defaultOptions
  89. );
  90. this.codeEditor.setValue(this.value);
  91. // 使用 prop function 替换 onChange 事件
  92. this.codeEditor.on("change", (item) => {
  93. this.codeHandler(item.getValue());
  94. });
  95. },
  96. },
  97. };
  98. </script>

插件启用功能的配置选项,同时需要引入相关的js,css 文件。

参数 说明 类型
mode 支持语言语法高亮 MIME-TYPE string
lineNumbers 是否在编辑器左侧显示行号。 boolean
lineWrapping 在长行时文字是换行(wrap)还是滚动(scroll),默认为滚动(scroll)。 boolean
styleActiveLine 高亮选中行 boolean
tabSize tab 字符的宽度 number
theme 设置主题 tring
autoCloseBrackets 括号自动关闭 boolean
autoCloseTags 标签自动关闭 boolean
matchTags 标签匹配 boolean
matchBrackets 括号匹配 boolean
foldGutter 代码折叠 boolean
readOnly 是否只读。 “nocursor” 设置只读外,编辑区域还不能获得焦点。 boolean|string

组件初始化时,会自动初始化编辑器示例,同时将源码赋值给编辑器,并注册监听change事件。当编辑器的值发生改变时,会触发 onchange 事件,调用组件prop 属性 codeHandler将最新值传给父组件。

  1. // 初始化编辑器实例,传入需要被实例化的文本域对象和默认配置
  2. this.codeEditor = CodeMirror.fromTextArea( this.$refs.codeContainer, this.defaultOptions );
  3. this.codeEditor.setValue(this.value);
  4. // 注册监听`change`事件
  5. this.codeEditor.on("change", (item) => { this.codeHandler(item.getValue()); });

0x02 SFC Parser

组件的功能场景是用于简单示例代码运行展示,将源码视为 单文件组件(SFC,single-file component)的简单实例。

文件src\utils\sfcParser\parser.js 移植 vue 源码 sfc/parser.jsparseComponent 方法,用于实现源码解析生成组件 SFCDescriptor

暂不支持组件和样式的动态引入,此处功能代码已经移除。

  1. // SFCDescriptor 接口声明
  2. export interface SFCDescriptor {
  3. template: SFCBlock | undefined; //
  4. script: SFCBlock | undefined;
  5. styles: SFCBlock[];
  6. customBlocks: SFCBlock[];
  7. }
  8. export interface SFCBlock {
  9. type: string;
  10. content: string;
  11. attrs: Record<string, string>;
  12. start?: number;
  13. end?: number;
  14. lang?: string;
  15. src?: string;
  16. scoped?: boolean;
  17. module?: string | boolean;
  18. }

SFCDescriptor 包含 templatescriptstylescustomBlocks 四个部分,将用于示例组件的动态构建。 其中 styles是数组,可以包含多个代码块并解析; templatescript 若存在多个代码块只能解析最后一个。

customBlocks是没在template的HTML代码,处理逻辑暂未包含此内容。

0x03 组件动态样式

文件src\utils\style-loader\addStylesClient.js 移植 vue-style-loader 源码 addStylesClient 方法,用于在页面DOM中动态创建组件样式。

根据 SFCDescriptor 中的 styles和组件编号,在DOM中添加对应样式内容,若新增删除 <style>,页面DOM中对应创建或移除该样式内容。若更新 <style>内容,DOM节点只更新对应块的内容,优化页面性能。

0x04 CodeViewer 组件

使用 JSX 语法实现组件核心代码。

  1. <script>
  2. export default {
  3. name: "CodeViewer",
  4. props: {
  5. theme: { type: String, default: "dark" }, //light
  6. source: { type: String },
  7. },
  8. data() {
  9. return {
  10. code: ``,
  11. dynamicComponent: {
  12. component: {
  13. template: "<div>Hello Vue.js!</div>",
  14. },
  15. },
  16. };
  17. },
  18. created() {
  19. this.viewId = `vcv-${generateId()}`;
  20. // 组件样式动态更新
  21. this.stylesUpdateHandler = addStylesClient(this.viewId, {});
  22. },
  23. mounted() {
  24. this._initialize();
  25. },
  26. methods: {
  27. // 初始化
  28. _initialize() {
  29. ...
  30. },
  31. // 生成组件
  32. genComponent() {
  33. ...
  34. },
  35. // 更新 code 内容
  36. handleCodeChange(val) {
  37. ...
  38. },
  39. // 动态组件render
  40. renderPreview() {
  41. ...
  42. },
  43. },
  44. computed: {
  45. // 源码解析为sfcDescriptor
  46. sfcDescriptor: function () {
  47. return parseComponent(this.code);
  48. },
  49. },
  50. watch: {
  51. // 监听源码内容
  52. code(newSource, oldSource) {
  53. this.genComponent();
  54. },
  55. },
  56. // JSX 渲染函数
  57. render() {
  58. ...
  59. },
  60. };
  61. </script>

组件初始化生成组件编号,注册方法 stylesUpdateHandler 用于样式的动态添加。

组件初始化调用 handleCodeChange 方法将传入prop source值赋值给code

  1. methods: {
  2. _initialize() {
  3. this.handleCodeChange(this.source);
  4. },
  5. handleCodeChange(val) {
  6. this.code = val;
  7. },
  8. }

计算属性sfcDescriptor 调用parseComponent方法解析code内容生成组件的 sfcDescriptor

  1. computed: {
  2. // 源码解析为sfcDescriptor
  3. sfcDescriptor: function () {
  4. return parseComponent(this.code);
  5. },
  6. },

组件监听code值是否发生变化,调用genComponent方法更新组件。

  1. methods: {
  2. // 生成组件
  3. genComponent() {
  4. ...
  5. },
  6. },
  7. watch: {
  8. // 监听源码内容
  9. code(newSource, oldSource) {
  10. this.genComponent();
  11. },
  12. },

方法 genComponent将代码的sfcDescriptor 动态生成组件,更新至 dynamicComponent 用于示例呈现。同时调用 stylesUpdateHandler方法使用addStylesClient在DOM中添加实例中样式,用于示例样式渲染。

  1. genComponent() {
  2. const { template, script, styles, customBlocks, errors } = this.sfcDescriptor;
  3. const templateCode = template ? template.content.trim() : ``;
  4. let scriptCode = script ? script.content.trim() : ``;
  5. const styleCodes = genStyleInjectionCode(styles, this.viewId);
  6. // 构建组件
  7. const demoComponent = {};
  8. // 组件 script
  9. if (!isEmpty(scriptCode)) {
  10. const componentScript = {};
  11. scriptCode = scriptCode.replace(
  12. /export\s+default/,
  13. "componentScript ="
  14. );
  15. eval(scriptCode);
  16. extend(demoComponent, componentScript);
  17. }
  18. // 组件 template
  19. demoComponent.template = `<section id="${this.viewId}" class="result-box" >
  20. ${templateCode}
  21. </section>`;
  22. // 组件 style
  23. this.stylesUpdateHandler(styleCodes);
  24. // 组件内容更新
  25. extend(this.dynamicComponent, {
  26. name: this.viewId,
  27. component: demoComponent,
  28. });
  29. },

JSX 渲染函数展示基于code内容动态生成的组件内容。调用 CodeEditor 组件传入源码value和主题theme,提供了 codeHandler 处理方法handleCodeChange用于获取编辑器内最新的代码。

  1. methods: {
  2. renderPreview() {
  3. const renderComponent = this.dynamicComponent.component;
  4. return (
  5. <div class="code-view zoom-1">
  6. <renderComponent></renderComponent>
  7. </div>
  8. );
  9. },
  10. },
  11. // JSX 渲染函数
  12. render() {
  13. return (
  14. <div ref="codeViewer">
  15. <div class="code-view-wrapper">
  16. {this.renderPreview()}
  17. ...
  18. <CodeEditor
  19. codeHandler={this.handleCodeChange}
  20. theme={`base16-${this.theme}`}
  21. value={this.code}
  22. />
  23. </div>
  24. </div>
  25. );
  26. },

handleCodeChange 被调用后,触发 watch =>genComponent=>render ,页面内容刷新,从而达到代码在线编辑,实时预览效果的功能。


完结

此组件编写是个人对于 Element 2 源码学习系列 学习实践的总结,希望会对您有所帮助!

从0到1搭建自己的组件(vue-code-view)库(下)的更多相关文章

  1. 从0到1搭建自己的组件(vue-code-view)库(上)

    0x00 前言 本文将从结构.功能等方面讲解下项目 vue-code-view 的搭建过程,您可以了解以下内容: 使用 vue cli 4从0搭建一个组件库及细致配置信息. 项目的多环境构建配置. 项 ...

  2. 从0到1搭建一款Vue可配置视频播放器组件(Npm已发布)

    前言 话不多说,这篇文章主要讲述如何从0到1搭建一款适用于Vue.js的自定义配置视频播放器.我们平时在PC端网站上观看视频时,会看到有很多丰富样式的视频播放器,而我们自己写的video标签样式却是那 ...

  3. django从0到1搭建网站

    曾经有人说我前端很水,那么在这一系列文章中我打算把前后端融合在一起来做一次网站的全面重构,希望可以把刚刚入行的同学带上正途   请尊重原创,转载请注明来源网站www.shareditor.com以及原 ...

  4. 使用vuejs2.0和element-ui 搭建的一个后台管理界面

    说明: 这是一个用vuejs2.0和element-ui搭建的后台管理界面. 相关技术: vuejs2.0:一套构建用户界面的渐进式JavaScript框架,易用.灵活.高效. element-ui: ...

  5. selenium win7+selenium2.0+python环境搭建

    win7+selenium2.0+python环境搭建 by:授客 QQ:1033553122 步骤1:下载python 担心最新版的支持不太好,这里我下载的是python 2.7(selenium之 ...

  6. iOS 从0到1搭建高可用App框架

    iOS 从0到1搭建高可用App框架 最近在搭建新项目的iOS框架,一直在思考如何才能搭建出高可用App框架,能否避免后期因为代码质量问题的重构.以前接手过许多“烂代码”,架构松散,底层混乱,缺少规范 ...

  7. 基于Vue搭建自己的组件库(1)

    本项目演示地址:https://husilang.github.io/zm-ui 项目参考文章:从零开始搭建Vue组件库 VV-UI 项目的初衷是学习怎么封装一个基于Vue的UI组件库,顺便记录每个步 ...

  8. 十七、.net core(.NET 6)搭建基于Quartz组件的定时调度任务

     搭建基于Quartz组件的定时调度任务 先在package包项目下,添加Quartz定时器组件: 新建类库项目Wsk.Core.QuartzNet,并且引用包类库项目.然后新建一个中间调度类,叫Qu ...

  9. 从零搭建react+ts组件库(封装antd)

    为什么会有这样一篇文章?因为网上的教程/示例只说了怎么做,没有系统详细的介绍引入这些依赖.为什么要这样配置,甚至有些文章还是错的!迫于技术洁癖,我希望更多的开发小伙伴能够真正的理解一个项目搭建各个方面 ...

随机推荐

  1. 免费iApp后台-云接口

    免费稳定,UI易懂简洁,功能强大 应用名称:云接口 应用版本:1.5.9 应用大小:3.55 MB 适用平台:Android(安卓) 应用用处:详情请下载软件 软件安全无毒 更新内容: 1.支付宝当面 ...

  2. PHP出现iconv(): Detected an illegal character in input string

    PHP传给JS字符串用ecsape转换加到url里,又用PHP接收,再用网上找的unscape函数转换一下,这样得到的字符串是UTF-8的,但我需要的是GB2312,于是用iconv转换 开始是这样用 ...

  3. PHPCMS V9轻松完成WAP手机网站搭建全教程

    ---恢复内容开始--- 应用PHPCMS V9轻松完成WAP手机网站搭建全教程 用PHPCMS最新发布的V9搭建了PHPCMS研究中心网站(http://www.17huiyi.net)完成后,有用 ...

  4. Elasticsearch6.8.6版本 在head插件中 对数据的增删改操作

    一.访问ES方法:http://IP:PORT/ 一.创建索引:head插件创建索引的实例:在"索引"-"新建索引"中创建索引名称,默认了分片与副本情况: 直接 ...

  5. bzoj#4423-[AMPPZ2013]Bytehattan【并查集】

    正题 题目链接:https://darkbzoj.tk/problem/4423 题目大意 给出一个\(n*n\)的网格图,然后四联通的点之间连接.每次删掉一条边求这条边的两个点是否连通.强制在线. ...

  6. P5325-[模板]Min_25筛

    正题 题目链接:https://www.luogu.com.cn/problem/P5325 题目大意 定义一个积性函数满足\(f(p^k)=p^k(p^k-1)\) 求\(\sum_{i=1}^nf ...

  7. 智汀家庭云-开发指南Golang:设备插件开发

    设备插件模块 开发前先阅读插件设计概要:智汀家庭云-开发指南Golang: 插件模块 使用 plugin-sdk 可以忽略不重要的逻辑,快速实现插件 插件实现 获取sdk go get github. ...

  8. Pandas高级教程之:时间处理

    目录 简介 时间分类 Timestamp DatetimeIndex date_range 和 bdate_range origin 格式化 Period DateOffset 作为index 切片和 ...

  9. 倒计时 | 7.24 阿里云 Serverless Developer Meetup 杭州站报名火热进行中!

    本周六阿里云 Serverless Developer Meetup 即将亮相杭州 ​ 时间:7.24 本周六 13:30 - 17:30 地点:杭州市良睦路 999 号乐佳国际 1-3-7 特洛伊星 ...

  10. 「JOISC 2020 Day2」变态龙之色 题解

    题目传送门 注意 同性必定不同色 必有一个同色异性,且不相互不喜欢 Solution 我们发现,我们问题比较大的就是如何确定性别问题.我们可以一个一个加进去,在原来已经确定了的二分图上增加新的性别关系 ...