0x00 前言

书接上文,本文将从源码性能方面解说下 vue-code-view 组件外围逻辑,您能够理解以下内容:

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

0x01 CodeEditor组件

我的项目应用功能丰富的codeMirror实现在线代码展现编辑性能。

npm 包装置:

npm install codemirror --save 

子组件 src\src\code-editor.vue 残缺源码:

<template>  <div class="code-editor">    <textarea ref="codeContainer" />  </div></template><script>// 引入外围import CodeMirror from "codemirror";import "codemirror/lib/codemirror.css"; // 主题 theme styleimport "codemirror/theme/base16-light.css";import "codemirror/theme/base16-dark.css"; // 语言 modeimport "codemirror/mode/vue/vue";  // 括号/标签 匹配import "codemirror/addon/edit/matchbrackets";import "codemirror/addon/edit/matchtags";// 括号/标签 主动敞开import "codemirror/addon/edit/closebrackets";import "codemirror/addon/edit/closetag"; // 代码折叠import "codemirror/addon/fold/foldgutter.css";import "codemirror/addon/fold/brace-fold";import "codemirror/addon/fold/foldcode";import "codemirror/addon/fold/foldgutter";import "codemirror/addon/fold/comment-fold";// 缩进文件import "codemirror/addon/fold/indent-fold";// 光标行背景高亮import "codemirror/addon/selection/active-line"; export default {  name: "CodeEditor",  props: {    value: { type: String },    readOnly: { type: Boolean },    theme: { type: String },    matchBrackets: { type: Boolean },    lineNumbers: { type: Boolean },    lineWrapping: { type: Boolean },    tabSize: { type: Number },    codeHandler: { type: Function },  },  data() {    return {      // 编辑器实例      codeEditor: null,      // 默认配置      defaultOptions: {        mode: "text/x-vue", //语法高亮   MIME-TYPE            gutters: [          "CodeMirror-linenumbers",          "CodeMirror-foldgutter",         ],         lineNumbers: this.lineNumbers, //显示行号        lineWrapping: this.lineWrapping || "wrap", // 长行时文字是换行  换行(wrap)/滚动(scroll)        styleActiveLine: true, // 高亮选中行        tabSize: this.tabSize || 2, // tab 字符的宽度        theme: this.theme || "base16-dark", //设置主题         autoCloseBrackets: true, // 括号主动敞开        autoCloseTags: true, // 标签主动敞开        matchTags: true, // 标签匹配        matchBrackets: this.matchBrackets || true, // 括号匹配        foldGutter: true, // 代码折叠        readOnly: this.readOnly ? "nocursor" : false, //  boolean|string  “nocursor” 设置只读外,编辑区域还不能取得焦点。      },    };  },  watch: {    value(value) {      const editorValue = this.codeEditor.getValue();      if (value !== editorValue) {        this.codeEditor.setValue(this.value);      }    },    immediate: true,    deep: true,  },  mounted() {    // 初始化    this._initialize();  },  methods: {    // 初始化    _initialize() {      // 初始化编辑器实例,传入须要被实例化的文本域对象和默认配置      this.codeEditor = CodeMirror.fromTextArea(        this.$refs.codeContainer,        this.defaultOptions      );       this.codeEditor.setValue(this.value);       // 应用 prop function 替换 onChange 事件      this.codeEditor.on("change", (item) => {        this.codeHandler(item.getValue());      });    },  },};</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将最新值传给父组件。

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

0x02 SFC Parser

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

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

暂不反对组件和款式的动静引入,此处性能代码曾经移除。
// SFCDescriptor 接口申明export interface SFCDescriptor {  template: SFCBlock | undefined; //  script: SFCBlock | undefined;  styles: SFCBlock[];  customBlocks: SFCBlock[];}export interface SFCBlock {  type: string;  content: string;  attrs: Record<string, string>;  start?: number;  end?: number;  lang?: string;  src?: string;  scoped?: boolean;  module?: string | boolean;}

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 语法实现组件外围代码。

<script> export default {  name: "CodeViewer",   props: {    theme: { type: String, default: "dark" }, //light     source: { type: String },   },  data() {    return {      code: ``,       dynamicComponent: {        component: {          template: "<div>Hello Vue.js!</div>",        },      },     };  },  created() {    this.viewId = `vcv-${generateId()}`;     // 组件款式动静更新    this.stylesUpdateHandler = addStylesClient(this.viewId, {});  },  mounted() {    this._initialize();  },  methods: {    // 初始化    _initialize() {      ...    },    // 生成组件    genComponent() {      ...    },    // 更新 code 内容    handleCodeChange(val) {      ...    },    // 动静组件render    renderPreview() {       ...    },   },  computed: {    // 源码解析为sfcDescriptor    sfcDescriptor: function () {      return parseComponent(this.code);    },   },  watch: {     // 监听源码内容    code(newSource, oldSource) {       this.genComponent();    },  },  // JSX 渲染函数  render() {     ...  },};</script> 

组件初始化生成组件编号,注册办法 stylesUpdateHandler 用于款式的动静增加。

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

methods: {    _initialize() {       this.handleCodeChange(this.source);    },    handleCodeChange(val) {      this.code = val;    },}

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

computed: {    // 源码解析为sfcDescriptor    sfcDescriptor: function () {      return parseComponent(this.code);    },   },

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

 methods: {     // 生成组件    genComponent() {      ...    },   },   watch: {     // 监听源码内容    code(newSource, oldSource) {       this.genComponent();    },  },

办法 genComponent将代码的sfcDescriptor 动静生成组件,更新至 dynamicComponent 用于示例出现。同时调用 stylesUpdateHandler办法应用addStylesClient在DOM中增加实例中款式,用于示例款式渲染。

  genComponent() {      const { template, script, styles, customBlocks, errors } = this.sfcDescriptor;             const templateCode = template ? template.content.trim() : ``;      let scriptCode = script ? script.content.trim() : ``;      const styleCodes = genStyleInjectionCode(styles, this.viewId);      // 构建组件      const demoComponent = {};      // 组件 script      if (!isEmpty(scriptCode)) {        const componentScript = {};        scriptCode = scriptCode.replace(          /export\s+default/,          "componentScript ="        );        eval(scriptCode);        extend(demoComponent, componentScript);      }      // 组件 template       demoComponent.template = `<section id="${this.viewId}" class="result-box" >        ${templateCode}      </section>`;      // 组件 style       this.stylesUpdateHandler(styleCodes);      // 组件内容更新      extend(this.dynamicComponent, {        name: this.viewId,        component: demoComponent,      });    },

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

  methods: {     renderPreview() {       const renderComponent = this.dynamicComponent.component;      return (        <div class="code-view zoom-1">          <renderComponent></renderComponent>        </div>      );    },  },  // JSX 渲染函数  render() {     return (      <div ref="codeViewer">        <div class="code-view-wrapper">           {this.renderPreview()}            ...          <CodeEditor               codeHandler={this.handleCodeChange}              theme={`base16-${this.theme}`}              value={this.code}            />        </div>      </div>    );  },

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


完结

此组件编写是集体对于 Element 2 源码学习系列 学习实际的总结,心愿会对您有所帮忙!