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
蕴含 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 源码学习系列 学习实际的总结,心愿会对您有所帮忙!