渲染器系统与运行时
渲染系统与运行时构成了执行引擎,能够在运行时将 DSL(领域特定语言)规范转换为功能完整的 Vue 组件。该系统使 VTJ 能够无需编译即可动态渲染可视化界面,并支持多种执行上下文,包括设计模式、运行时预览和源代码渲染。
架构概览
渲染器架构基于三层基础运行:运行时层(Provider 和 Context)、渲染层(createRenderer 和 nodeRender)以及集成层(服务和插件)。核心渲染流水线通过感知上下文的转换处理 BlockSchema,将声明式节点规范转换为响应式 Vue VNode 树。
flowchart TB
subgraph 输入层
DSL[BlockSchema<br>DSL 规范]
Components[组件库]
APIs[API 服务]
end
subgraph 运行时层
Provider[Provider<br>服务管理]
Context[Context<br>执行环境]
end
subgraph 渲染层
createRenderer[createRenderer<br>组件工厂]
nodeRender[nodeRender<br>节点处理]
DirectiveHandler[指令处理器<br>v-if/v-for/v-model]
end
subgraph 输出层
VNode[VNode 树]
Component[Vue 组件<br>运行时实例]
end
DSL --> Provider
Components --> Context
APIs --> Context
Provider --> Context
Context --> createRenderer
createRenderer --> nodeRender
nodeRender --> DirectiveHandler
DirectiveHandler --> VNode
VNode --> Component
渲染过程始于 Provider 加载项目架构,通过 Context 类建立执行上下文,该类维护状态、引用和组件引用。随后,createRenderer 函数根据 DSL 规范生成响应式 Vue 组件,而 nodeRender 处理单个节点的转换,包括指令处理、事件绑定和子插槽管理。
核心渲染流水线
createRenderer 函数充当将 BlockSchema 定义转换为可执行 Vue 组件的主要工厂。它编排完整的组件生命周期,包括 props 定义、状态管理、计算属性、方法、数据源、监视器和生命周期钩子。渲染器使用 Vue.reactive 创建响应式状态对象,通过表达式解析处理计算属性和方法,并为响应式依赖建立监视器。
渲染流水线支持 ContextMode 中定义的四种不同执行模式:Runtime(生产环境执行)、Design(编辑器环境)、Raw(源代码模式)和 VNode(虚拟节点模式)。每种模式影响组件解析、事件处理和调试能力等行为。渲染器通过 adoptedStyleSheets 应用作用域 CSS,确保通过 data-v- 属性实现样式隔离。
| 渲染阶段 | 函数 | 主要职责 |
|---|---|---|
| 组件定义 | createRenderer | Props, emits, expose, 生命周期钩子 |
| 状态初始化 | createState | 响应式状态创建, JSExpression 解析 |
| 计算属性处理 | createComputed | Vue.computed 包装, 函数解析 |
| 方法绑定 | createMethods | 函数上下文绑定, 执行设置 |
| 数据源设置 | createDataSources | Mock/API 集成, 转换函数 |
| 监视器注册 | setWatches | Vue.watch 配置, deep/immediate 选项 |
| 生命周期执行 | createLifeCycles | 钩子注册, 异步处理 |
上下文和执行环境
Context 类为渲染的组件提供执行环境,维护关键的运行时状态,包括响应式数据、组件引用 (refs)、元素访问 (el) 和 Vue 实例属性。它将 Vue 实例属性(如 emit, nextTick, parent, slots)代理到上下文,使 DSL 代码能够在无需显式导入的情况下访问标准 Vue API。上下文还管理 components(注册的组件库)、libs(第三方库)和 $apis(服务函数)。
上下文设置发生在组件初始化期间,此时它代理 Vue 实例属性,建立全局属性引用,并在卸载时管理清理。上下文支持表达式解析 (__parseExpression) 和函数解析 (__parseFunction),将 DSL 中的 JSExpression 和 JSFunction 类型转换为正确执行上下文中的可执行 JavaScript。这实现了具有适当作用域和 this 绑定的动态代码求值。
上下文转换功能 (__transform) 允许在渲染期间进行属性名映射,支持不同命名约定之间的兼容性。上下文克隆 (__clone) 能够创建嵌套组件的隔离执行上下文,同时保留父级引用。
sequenceDiagram
participant Client
participant createRenderer
participant Context
participant Vue
participant nodeRender
Client->>createRenderer: BlockSchema + options
createRenderer->>Context: new Context(mode, dsl, attrs)
createRenderer->>Context: setup(attrs, Vue)
Context->>Vue: getCurrentInstance()
Context-->>Context: __proxy() instance properties
createRenderer-->>createRenderer: createState(dsl.state)
createRenderer-->>createRenderer: createComputed(dsl.computed)
createRenderer-->>createRenderer: createMethods(dsl.methods)
createRenderer-->>createRenderer: createDataSources(dsl.dataSources)
createRenderer-->>createRenderer: setWatches(dsl.watch)
createRenderer->>Context: setup(attrs with state/methods)
Vue->>Context: render() lifecycle
Context->>nodeRender: nodeRender(schema, context)
nodeRender-->>Context: VNode tree
Context-->>Client: Rendered component
节点级渲染
nodeRender 函数处理将单个 NodeSchema 元素转换为 VNode 实例,递归处理指令、props、事件和子元素。它支持条件渲染 (v-if, v-else-if, v-else)、列表渲染 (v-for)、双向绑定 (v-model) 和其他标准 Vue 指令。该函数通过 BlockLoader 解析组件名称,处理内置标签(component, slot)、原生 HTML 元素以及组件库中的注册组件。
指令处理通过专用的渲染函数进行:vIfRender 评估条件表达式,vForRender 管理带有索引跟踪的列表迭代,vModelRender 处理表单输入绑定,vShowRender 切换 CSS display 属性。事件解析通过 Vue.withModifiers 将 NodeEvents 转换为带有适当修饰符处理(capture, once, passive)的 Vue 事件处理程序。
Props 解析对 JSExpression 和 JSFunction 类型执行深度递归解析,确保所有动态表达式在正确的上下文中求值。子元素转换将 NodeChildren 数组转换为插槽配置,通过 childrenToSlots 函数支持命名插槽、作用域插槽和默认插槽,该函数将子节点处理为适当的插槽结构。
💡 渲染器采用复杂的组件解析策略:内置组件返回直接引用,原生标签保持不变,而自定义组件通过 BlockLoader 解析,它支持通过缓存和异步组件定义从 Schema、UrlSchema 或 Plugin 来源动态加载。
组件加载和解析
BlockLoader 接口提供灵活的组件解析,支持多种组件来源:注册组件的字符串名称、来自 DSL ID 的基于 Schema 的加载、来自远程 URL 的 UrlSchema 加载,以及来自第三方库的 Plugin 加载。createLoader 函数使用 __loaders 映射实现缓存策略以防止冗余加载,并使用队列系统 (__queue) 管理并发异步请求。
Schema 加载从项目服务检索 BlockSchema,创建具有隔离上下文的子渲染器,并通过 Vue.defineAsyncComponent 返回异步组件。Plugin 加载执行 getPlugin 以获取第三方素材,处理 window 对象上的库注册,并缓存已加载的插件以供重用。加载器维护插件名称注册表 (__plugins),以便在环境更改期间进行清理和重置操作。
远程组件加载支持 Schema ID 和基于 URL 的获取,实现从分布式源动态组合页面。加载器与 Provider 的素材加载系统集成,支持自定义素材路径和生产部署场景的库选项。
| 加载器类型 | 来自配置 | 加载机制 | 使用案例 |
|---|---|---|---|
| 默认 | undefined | 直接字符串返回 | 注册的组件, 原生标签 |
| Schema | {type: 'Schema', id: string} | getDsl(id) → createRenderer | 块级组件, 嵌套块 |
| UrlSchema | {type: 'UrlSchema', url: string} | getDslByUrl(url) → createRenderer | 远程组件, 跨域资源 |
| Plugin | {type: 'Plugin', library: string} | getPlugin() → defineAsyncComponent | 第三方库, 素材包 |
运行时 Provider 系统
Provider 类管理整个运行时环境,包括服务集成、项目配置、模块加载、依赖管理和适配器配置。它充当应用程序与 VTJ 渲染系统之间的中央协调器,提供组件检索、DSL 解析和动态组件创建的 API。Provider 支持多种 ContextMode 配置,使行为适应设计时编辑与运行时执行。
Provider 初始化加载项目架构,建立服务连接(BaseService 实现),通过 Access 插件配置访问控制,并初始化路由器集成以进行页面导航。它通过异步加载函数管理素材集合,处理第三方依赖的库注册,并建立所有渲染组件可访问的全局变量。
Provider 公开关键的运行时方法:getFile/getPage 检索组件定义,getDsl/getDslByUrl 获取用于渲染的 BlockSchema,createDslRenderer 从 DSL 生成组件渲染器,getRenderComponent 创建已解析所有依赖的完整 Vue 组件。错误处理集成通过 setErrorHandler 进行,为运行时异常建立全局错误边界。
💡 Provider 支持静态和动态路由配置。启用 enableStaticRoute 时,它从页面定义预生成路由配置,通过避免运行时路由计算来提高大型应用程序的性能。
表达式和函数解析
渲染器工具为 DSL 规范内的动态代码执行提供强大的解析功能。parseExpression 函数评估 JSExpression 类型,支持字符串表达式和带有可选 value 属性的较新结构化格式。它使用 with 语句在指定上下文中执行代码以方便属性访问,并根据 throwError 参数优雅地处理错误。
parseFunction 函数处理 JSFunction 类型,将函数体字符串转换为具有适当 this 绑定的可执行函数。它支持箭头函数和标准函数语法,返回绑定到提供的上下文的函数。解析器工具包括用于安全类型检查的类型守卫 (isJSExpression, isJSFunction) 和用于序列化目的的 JSCodeToString。
表达式解析通过 deepParseNodeProps 中的递归评估支持嵌套对象、数组和复杂数据结构,实现整个组件树中的动态配置。函数解析保留上下文引用,允许 DSL 代码无缝访问组件状态、方法和其他运行时功能。
样式和 CSS 处理
渲染器提供高级 CSS 处理功能,包括通过 adoptedStyleSheets 工具进行的作用域样式采用,该工具使用 Constructable StyleSheet API 将 CSS 注入文档,并具有适当的作用域属性。对于不支持 Constructable StyleSheets 的环境,它会回退到带有作用域属性 data-v-{id} 的 style 元素注入。
convertCssRpx 工具通过基于屏幕宽度和设计基准宽度(默认 750px)将 rpx 单位(移动开发中常用的相对像素单位)转换为 px 单位,从而实现响应式设计。这种转换确保在不同设备尺寸上的一致渲染,特别是对于使用 VTJ 的 uni-app 集成的移动优先应用程序。
样式作用域通过向渲染元素添加 data-v- 属性并在 CSS 中匹配作用域选择器来工作,确保组件之间的样式隔离。当 BlockSchema.id 存在时,渲染器会自动应用作用域,防止复杂组件层次结构中的样式泄漏。
数据源和服务集成
createDataSources 函数为渲染组件建立数据访问模式,支持两种主要数据源类型:用于开发的 Mock 源和用于生产的 API 源。Mock 源使用 createMock 工具根据架构定义生成合成数据,从而在没有后端依赖的情况下进行前端开发。
基于 API 的数据源与通过 Context 提供的 $apis 集成,支持异步数据获取,并可选择用于响应处理的转换函数。数据源定义为接受参数并返回处理数据的异步函数,从而实现组件模板中的声明式数据绑定。
服务层 (BaseService) 提供后端集成功能,包括项目持久化(saveProject, saveFile)、文件管理(getFile, removeFile)、历史记录跟踪(saveHistory, getHistory)、静态资源处理(uploadStaticFile, getStaticFiles)和代码生成(genVueContent, parseVue)。服务使用请求适配器进行 HTTP 通信,支持针对不同部署环境的自定义配置。
| 服务方法 | 目的 | 参数 | 返回类型 |
|---|---|---|---|
| getExtension | 获取 VTJ 配置 | - | Promise<VTJConfig|undefined> |
| getFile | 检索组件 DSL | id: string | Promise<BlockSchema> |
| saveFile | 持久化组件 DSL | file: BlockSchema | Promise<boolean> |
| genVueContent | 从 DSL 生成 Vue 代码 | project, dsl | Promise<string> |
| parseVue | 将 Vue 源码解析为 DSL | project, options | Promise<BlockSchema> |
| publishFile | 发布组件 | project, file | Promise<boolean> |
访问控制和身份验证
Access 提供全面的身份验证和授权功能,支持基于令牌的身份验证、权限验证和路由守卫。它通过具有可配置会话模式、令牌密钥和存储前缀的存储(localStorage/cookie)管理身份验证状态。Access 类与 Vue Router 集成,使用白名单配置进行自动路由保护。
身份验证方法包括 login(存储令牌和权限)、logout(清除会话)、isLogined(会话验证)和 can/some(权限检查)。路由守卫拦截导航,在允许访问受保护路由之前检查身份验证状态和权限。它支持基于字符串和基于函数的白名单配置,以实现灵活的授权规则。
访问控制通过适配器配置与 Provider 集成,与请求拦截器连接以自动将令牌注入 API 调用。系统支持用于用户通知的自定义警报实现、未经授权的重定向配置以及用于企业部署增强安全性的 RSA 加密。
运行时执行模式
渲染器支持四种不同的执行模式,每种模式都针对特定用例进行了优化。Runtime 模式 (ContextMode.Runtime) 启用具有完整功能支持、优化性能和最少调试开销的生产环境执行。Design 模式 (ContextMode.Design) 为可视化设计器提供增强的内省功能,暴露内部状态并启用交互式编辑功能。
Raw 模式 (ContextMode.Raw) 从源代码而不是 DSL 渲染组件,支持 VTJ 项目中的传统 Vue 开发工作流。此模式实现 DSL 生成组件和手写组件之间的无缝集成。VNode 模式 (ContextMode.VNode) 在虚拟节点级别运行,用于高级组件组合和转换场景的内部使用。
模式选择发生在 Provider 初始化期间,并通过 Context 对象传播到渲染流水线。每种模式影响组件解析行为、事件处理、错误报告和调试输出。整个渲染器中应用特定模式的优化,以平衡性能与功能需求。
开源项目仓库:gitee.com/newgateway/…