0x00 前言
书接上文,本文将从源码功能方面讲解下 vue-code-view
组件核心逻辑,您可以了解以下内容:
- 动态组件的使用。
codeMirror
插件的使用。- 单文件组件(SFC,single-file component) Parser。
0x01 CodeEditor组件
项目使用功能丰富的codeMirror
实现在线代码展示编辑功能。
npm 包安装:
npm install codemirror --save
子组件 src\src\code-editor.vue
完整源码:
插件启用功能的配置选项,同时需要引入相关的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;
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中添加对应样式内容,若新增删除 ,页面DOM中对应创建或移除该样式内容。若更新
内容,DOM节点只更新对应块的内容,优化页面性能。
0x04 CodeViewer 组件
使用 JSX
语法实现组件核心代码。
组件初始化生成组件编号,注册方法 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 = `
${templateCode}
`;
// 组件 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 (
);
},
},
// JSX 渲染函数
render() {
return (
{this.renderPreview()}
...
);
},
handleCodeChange
被调用后,触发 watch =>genComponent=>render ,页面内容刷新,从而达到代码在线编辑,实时预览效果的功能。
完结
此组件编写是个人对于 Element 2 源码学习系列 学习实践的总结,希望会对您有所帮助!