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 style
import "codemirror/theme/base16-light.css";
import "codemirror/theme/base16-dark.css";
// 语言 mode
import "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
蕴含 template
、script
、styles
、customBlocks
四个局部,将用于示例组件的动静构建。其中 styles
是数组,能够蕴含多个代码块并解析;template
和 script
若存在多个代码块只能解析最初一个。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 源码学习系列 学习实际的总结, 心愿会对您有所帮忙!