
目的与范围
本文档解释了VTJ.PRO如何使单一DSL定义能够部署到三个不同的平台:Web、H5(移动端网页)和UniApp(跨平台原生应用)。多平台运行时系统基于@vtj/renderer包构建,该包提供了核心的Provider抽象和渲染引擎。每个平台都有自己的初始化逻辑和入口点,但共享相同的DSL源和渲染核心。
有关整体架构以及此系统如何融入更广泛系统的信息,请参阅架构文档。有关应用程序和模板管理的详细信息,请参阅低代码应用系统。

多平台运行时系统由三层组成:
frontend/src/platform/{web,h5,uniapp}/main.ts的平台特定初始化@vtj/renderer包,包含createProvider和Provider类templates/{web,h5,uniapp}/的独立模板项目运行时架构图

运行时系统在两种不同的上下文模式和两种Node环境中运行,这决定了应用程序如何初始化和执行。
模式 | 值 | 目的 | 服务类型 | 使用场景 |
|---|---|---|---|---|
ContextMode.Runtime | 'runtime' | 生产/预览部署 | createService() | Web/H5/UniApp运行时平台 |
ContextMode.Raw | 'raw' | 本地开发模式 | LocalService | 模板项目、离线开发 |
环境 | 值 | 预览标志 | 行为 |
|---|---|---|---|
NodeEnv.Development | 'development' | preview === true | 启用热重载、调试模式 |
NodeEnv.Production | 'production' | preview === false | 优化打包、启用自动更新 |
上下文模式选择逻辑

Web平台是主要的部署目标,使用标准的Vue Router和HTML5历史模式。
Web平台入口点图

Web平台使用以下关键参数配置createProvider:
ts 体验AI代码助手 代码解读复制代码createProvider({
nodeEnv: preview ? NodeEnv.Development : NodeEnv.Production,
mode: ContextMode.Runtime,
service: createService(template, preview, version),
project: {
id: code,
platform: AppPlatform.Web
},
materialPath: MATERIAL_PATH,
dependencies: {
Vue: () => import('vue'),
VueRouter: () => import('vue-router'),
Pinia: () => import('pinia')
},
router,
enableStaticRoute: true,
routeAppendTo: ROUTER_APPEND_TO,
adapter: {
notify,
loading,
alert,
useTitle
}
});关键配置选项:
service: 通过createService(template, preview, version)创建,从后端获取DSLproject.id: 来自URL的应用程序代码(例如/web/myapp)project.platform: 设置为AppPlatform.Web以进行平台特定渲染dependencies: Vue生态系统的动态导入(Vue、VueRouter、Pinia)router: 现有的Vue Router实例,动态路由将附加到其中enableStaticRoute: 允许混合静态和动态路由routeAppendTo: 指定DSL生成路由的父路由adapter: 平台特定的notify、loading、alert、useTitle实现H5平台针对移动端网页进行了优化,具有触摸设备的视口配置和移动端特定的样式。
方面 | Web | H5 |
|---|---|---|
视口 | 标准桌面 | 移动端,使用maximum-scale=1.0, minimum-scale=1.0, viewport-fit=cover |
平台ID | AppPlatform.Web | AppPlatform.H5 |
样式 | 桌面优化 | 触摸优化,更大的点击目标 |
入口HTML | frontend/web/index.html | frontend/h5/index.html |
主脚本 | frontend/src/platform/web/main.ts | frontend/src/platform/h5/main.ts |
H5初始化与Web几乎相同,仅平台标识符不同:
ts 体验AI代码助手 代码解读复制代码project: {
id: code,
platform: AppPlatform.H5 // 与Web的唯一区别
}Web和H5使用相同的:
createProvider配置结构Access认证系统createService用于获取DSLenableStaticRoute的Vue RouterUniApp与Web/H5有显著不同,因为它使用UniApp的组件系统和基于页面的路由生成原生类应用。
UniApp初始化流程

UniApp不向createProvider传递router参数:
ts 体验AI代码助手 代码解读复制代码createProvider({
nodeEnv: preview ? NodeEnv.Development : NodeEnv.Production,
mode: ContextMode.Runtime,
service,
project: {
id: code,
platform: AppPlatform.UniApp
}
// 无router参数
// 无enableStaticRoute
// 无routeAppendTo
});UniApp需要使用createUniAppComponent创建自定义App组件:
ts 体验AI代码助手 代码解读复制代码const App = createUniAppComponent(project.uniConfig || {}, (script) =>
parseFunction(script, window, false, true)
);此函数:
uniConfig(包含pagesJson、manifestJson、css)parseFunction在沙盒化的window上下文中执行自定义脚本UniApp使用基于页面的路由,而不是Vue Router,通过createUniRoutes:
ts 体验AI代码助手 代码解读复制代码const routes = await createUniRoutes(provider, true, basePath);这基于以下内容生成路由:
pagesJson配置/pages)UniApp需要setupUniApp来初始化UniApp生态系统:
ts 体验AI代码助手 代码解读复制代码const app = setupUniApp({
Vue,
App,
UniH5,
routes,
css,
pagesJson,
manifestJson
});此函数:
UniH5)集成uniConfig.css的全局CSSpagesJson配置页面manifestJson设置应用清单UniApp直接挂载到document.body而不是#app:
ini 体验AI代码助手 代码解读复制代码app.mount(document.body);当未提供应用程序代码或路由为空时,UniApp显示NotFound.vue组件:
ts 体验AI代码助手 代码解读复制代码if (routes.length === 0) {
routes.push({
id: 'NotFound',
path: '/',
component: NotFound as any
});
}模板项目是独立的启动项目,开发人员可以用于本地开发,无需完整的VTJ.PRO平台后端。
方面 | 运行时平台 | 模板项目 |
|---|---|---|
位置 | frontend/src/platform/{web,h5,uniapp}/ | templates/{web,h5,uniapp}/ |
上下文模式 | ContextMode.Runtime | ContextMode.Raw |
服务 | createService(template, preview, version) | LocalService(createServiceRequest(notify)) |
DSL源 | 从后端API获取 | 嵌入在项目中或本地文件 |
项目ID | 来自URL的应用代码 | 来自package.json(vtj.id或name) |
依赖项 | 运行时导入 | 在项目中打包 |
自动更新 | 在生产环境中启用 | 在生产环境中启用 |
Web模板配置
ts 体验AI代码助手 代码解读复制代码const service = new LocalService(createServiceRequest(notify));
const { provider, onReady } = createProvider({
nodeEnv: process.env.NODE_ENV as NodeEnv,
mode: ContextMode.Raw,
modules: createModules(),
adapter: createAdapter({ loading, notify, Startup, useTitle }),
service,
router,
dependencies: {
Vue: () => import('vue'),
VueRouter: () => import('vue-router'),
Pinia: () => import('pinia'),
VueI18n: () => import('vue-i18n')
},
project: {
id: vtj?.id || name // 来自package.json
},
enableStaticRoute: true
});关键模板特性:
modules: createModules()为本地开发提供预配置的模块LocalService: 从本地源而不是API加载DSLcreateServiceRequest: 创建带有notify回调的请求处理器VueI18n: 包含国际化支持IconsPlugin: UI组件的图标系统插件autoUpdate(): 仅在生产环境中检查更新的功能H5模板遵循与Web模板相同的模式,但有以下调整:
@vtj/h5包而不是@vtj/webIconsPlugin(移动端不需要)useTitle(移动端不更改文档标题)UniApp模板由于SSR(服务器端渲染)架构而使用完全不同的结构:
ts 体验AI代码助手 代码解读复制代码export function createApp() {
const app = createSSRApp(App);
onReady(() => {
app.use(provider);
});
return {
app
};
}UniApp模板差异:
createSSRApp而不是createApp以支持SSRcreateApp函数而不是立即挂载VueI18n依赖项(无router或Pinia)服务层负责获取DSL定义并将其提供给渲染器。
服务层对比

createService函数在运行时平台中用于从后端获取DSL:
ts 体验AI代码助手 代码解读复制代码const service = createService(template, preview, version);参数:
template: 布尔值,指示是否加载模板(true)或应用(false)preview: 布尔值,指示预览模式与生产模式version: 可选版本标识符,用于特定版本加载行为:
/api/apps/{code}或/api/templates/{id}LocalService类在模板项目中用于本地开发:
ts 体验AI代码助手 代码解读复制代码const service = new LocalService(createServiceRequest(notify));特性:
createServiceRequest创建带有notify回调的请求处理器Access类为运行时平台提供认证和授权。
Access配置模式
ts 体验AI代码助手 代码解读复制代码const access = new Access({
alert,
storageKey: STORAGE_KEY,
privateKey: ACCESS_PRIVATE_KEY
});
access.connect({ request, mode: ContextMode.Runtime });参数 | 类型 | 目的 |
|---|---|---|
alert | 函数 | 向用户显示错误消息 |
storageKey | 字符串 | 用于存储加密登录数据的localStorage键 |
privateKey | 字符串 | 用于保护存储凭据的加密密钥 |
whiteList | 函数 | 可选函数,用于确定路由是否需要认证 |
unauthorized | 字符串 | 未授权访问的可选重定向路径 |
connect方法初始化访问控制:
request: 由setPlatformRequest创建的请求处理器mode: ContextMode(Runtime或Raw)router: 可选的Vue Router用于导航守卫
运行时平台(Web、H5、UniApp):
ts 体验AI代码助手 代码解读复制代码app.use(access); // 作为Vue插件安装开发平台:
ts 体验AI代码助手 代码解读复制代码access.connect({ request, router }); // 不需要mode参数模板项目:
适配器提供常见UI操作的平台特定实现。
ts 体验AI代码助手 代码解读复制代码adapter: {
notify: Function,
loading: Function,
alert: Function,
useTitle?: Function // 仅Web/H5平台
}函数 | 目的 | 参数 | 返回 |
|---|---|---|---|
notify | 显示Toast通知 | (message, type, duration) | void |
loading | 显示/隐藏加载旋转器 | (show: boolean) | void |
alert | 显示警告对话框 | (message, type) | Promise<void> |
useTitle | 更新文档标题 | (title: string) | void |
Web平台:
@vueuse/core的useTitleH5平台:
UniApp平台:
notify映射到uni.showToastloading映射到uni.showLoading/uni.hideLoadingalert映射到uni.showModaluseTitle(在原生上下文中不适用)运行时平台:
ts 体验AI代码助手 代码解读复制代码import { notify, loading, alert, useTitle } from './adapter';
adapter: {
(notify, loading, alert, useTitle);
}模板项目:
ts 体验AI代码助手 代码解读复制代码import { createAdapter } from '@vtj/web';
const adapter = createAdapter({ loading, notify, Startup, useTitle });渲染器动态导入Vue生态系统依赖项,以避免初始包过大。
运行时平台(Web、H5):
ts 体验AI代码助手 代码解读复制代码dependencies: {
Vue: () => import('vue'),
VueRouter: () => import('vue-router'),
Pinia: () => import('pinia')
}模板项目(Web、H5):
ts 体验AI代码助手 代码解读复制代码dependencies: {
Vue: () => import('vue'),
VueRouter: () => import('vue-router'),
Pinia: () => import('pinia'),
VueI18n: () => import('vue-i18n')
}UniApp运行时:
无依赖配置(UniApp提供自己的生态系统)
UniApp模板:
ts 体验AI代码助手 代码解读复制代码dependencies: {
VueI18n: async () => VueI18n; // 直接导入
}渲染器在需要时异步加载依赖项:
此策略减少了初始包大小并提高了加载性能。
运行时系统支持三种部署上下文,由URL结构决定。
上下文 | Web URL 模式 | H5 URL 模式 | UniApp URL 模式 |
|---|---|---|---|
生产环境 | /web/{code} | /h5/{code} | /uniapp/{code} |
预览环境 | /web/{code}/preview | /h5/{code}/preview | /uniapp/{code}/preview |
版本环境 | /web/{code}/version/{v} | /h5/{code}/version/{v} | /uniapp/{code}/version/{v} |
getAppInfo() 函数从URL中提取上下文:
ts 体验AI代码助手 代码解读复制代码const { code, preview, version, template } = getAppInfo();返回值:
code: 应用程序代码标识符(来自URL段)preview: 布尔值,指示预览模式(如果URL中包含/preview则为true)version: 版本标识符(来自URL中的/version/{v})template: 布尔值,指示模板模式(如果URL中包含/template/则为true)生产环境上下文:
NodeEnv.Production预览环境上下文:
NodeEnv.Development版本环境上下文:
NodeEnv.ProductionWeb和H5平台与Vue Router集成,以支持静态和动态路由。
ts 体验AI代码助手 代码解读复制代码{
router,
enableStaticRoute: true,
routeAppendTo: ROUTER_APPEND_TO
}参数:
router: 现有的Vue Router实例enableStaticRoute: 允许混合平台路由和DSL路由routeAppendTo: DSL路由附加到的父路由名称静态路由(平台定义):
ts 体验AI代码助手 代码解读复制代码/web/{code}. → 应用容器
/web/{code}/preview. → 预览模式
/h5/{code} → 应用容器
/h5/{code}/preview → 预览模式动态路由(DSL定义):
ts 体验AI代码助手 代码解读复制代码/web/{code}/#/page/{id} → DSL页面路由
/web/{code}/#/... → 自定义DSL路由渲染器将DSL生成的路由作为平台路由的子路由附加:

哈希模式 vs 历史模式:
/#/page/1)每个平台都有一个HTML入口点,用于加载适当的主脚本。
平台 | HTML 路径 | 脚本路径 | 视口 |
|---|---|---|---|
主应用 | frontend/index.html | /src/main.ts | 桌面 |
Web运行时 | frontend/web/index.html | /src/platform/web/main.ts | 桌面 |
H5运行时 | frontend/h5/index.html | /src/platform/h5/main.ts | 移动端 |
开发环境 | frontend/dev/index.html | /src/platform/dev/main.ts | 响应式 |
Web模板 | templates/web/index.html | /src/main.ts | 桌面 |
H5模板 | templates/h5/index.html | /src/main.ts | 移动端 |
UniApp模板 | templates/uniapp/index.html | /src/main.ts | 动态 |
H5平台使用以下视口配置以获得最佳移动端体验:
ts 体验AI代码助手 代码解读复制代码<meta
name="viewport"
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, viewport-fit=cover" />关键设置:
width=device-width: 匹配设备屏幕宽度initial-scale=1.0: 无初始缩放maximum-scale=1.0: 防止缩放(以获得类似应用的体验)minimum-scale=1.0: 防止缩小viewport-fit=cover: 处理刘海屏和安全区域UniApp使用JavaScript检测安全区域支持:
ts 体验AI代码助手 代码解读复制代码var coverSupport =
'CSS' in window &&
typeof CSS.supports === 'function' &&
(CSS.supports('top: env(a)') || CSS.supports('top: constant(a)'));
document.write(
'<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0' +
(coverSupport ? ', viewport-fit=cover' : '') +
'" />'
);这确保仅在设备支持时才添加viewport-fit=cover。
多平台运行时系统使VTJ.PRO能够通过以下方式从单一DSL源部署应用到Web、H5和UniApp:
@vtj/renderer包,包含createProvider和Provider类低代码引擎开源仓库:https://gitee.com/newgateway/vtj
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。