背景
目前低代码引擎在设计上是需要在单独的页面上使用的,他的扩展功能也都是适用于页面设计的。
但是大多数的场景中,我们都不是只设计一个页面,我们还有导航配置、依赖配置、低代码组件和逻辑编排等等应用级别需要的功能。
甚至我们希望在低代码搭建的过程中能有 VsCode 般丝滑的体验。快速切换页面,快速配置应用能力等等。而不是现在开发一个页面和一个低代码组件就需要打开至少三个新的标签页。如果开发的页面和组件更多,需要打开的标签页也就越多。
因此,在使用低代码平台时,我的浏览器常常就成为了这样的。
而当我们需要在同一个设计器中,使用两种不同类型的设计方式时,只能通过 弹窗 + iframe 的方式,这样的方式体验上也很不友好。
因此我们需要提供应用级别的低代码引擎,用于低代码平台者开发出应用级别的低代码设计器。
应用级能力介绍
我们这里提供的应用级能力是指,在一个设计器中即可完成低代码页面、低代码组件、应用级逻辑、应用级国际化配置等的编辑。甚至对于应用相关的配置也可以通过 Webview 的方式进行编辑,而不需要来回切换浏览器页面才能达到这样的效果。
为了实现这样的效果,我们在低代码引擎原有的基础上新增了以下能力:
- 新增 workspace 模式,新增资源、视图的概念
- 新增应用级扩展区域
- 多层作用域开发模式
新增 workspace 模式
由于应用级别的设计器和页面级别的设计器在开发时略有不同,所以需要在设计器初始化时进行配置,是否开启应用级别设计器的模式。
开启这个模式之后,就需要注册“资源”和“资源视图”。
资源
其中,资源是指在应用级别的设计器中,需要一个独立的设计器环境来进行编辑、修改、查看等操作的一份文件,这份文件可能是某一个页面的 Schema、可能是某一个低代码组件的 Schema,也可能是一段 JS 逻辑的代码。
如图,我们可以在应用级别的设计器中编辑所有注册的资源,而不用新开多个浏览器窗口。
资源类型
其中每个资源所需的设计器插件、物料、扩展的面板可能都是不一样的,也就是设计区域是不一样的。我们根据资源所需的设计器资源将资源分为几种类型,这里我们称为资源类型。
如图,在设计页面的时候,使用的资源都是一样的,这里所有页面的编辑都会使用页面的资源类型,而逻辑设计区域,可以看到左侧和右边是没有注册扩展区域的,这里就需要一个和页面不一样的资源类型,也就是逻辑资源类型。
这样,根据资源和资源类型,我们就可以在一个设计器中编辑和设计不同类型的低代码相关的资源。
资源视图
在同一个资源中,可以根据要编辑方式的不同,可能需要使用不同的设计视图,因此我们还定义了一个资源视图的概念,可以给同一个资源注册不同的视图,通过切换视图编辑同一个资源的不同的模块,来提升整体的编辑体验。
新增应用级扩展区域
为了提供应用级别的设计器,我们新增了一些扩展区域来帮助大家在合适的未来提供应用级别的能力。
如图,在应用级别模式下,我们分为应用扩展、资源扩展和视图扩展,其中每种级别扩展又提供了不同的扩展区域:
- 应用扩展:应用级别的扩展只会在最开始的时候初始化一次,不会因为资源/视图的切换而重新初始化。
- leftArea:这里用于展示应用级别能力的面板,例如:资源管理器,应用配置,分支管理等等。
- topArea:这里主要是用于展示应用级别的信息,例如:应用的名字和图标、应用的保存和预览、应用的国际化配置等等。
- subTopArea:这是和 topArea 类似的区域,这里主要是用于展示应用编辑时的辅助信息,也就是我们的资源切换的标签栏等等。
- 资源扩展:资源级别的扩展,会在新开资源的时候随着资源的初始化一起初始化,相当于每一个资源都会对应一个资源级别的扩展区域,但是在资源视图切换的时候不会产生变化。
- topArea:这里是资源级别的顶部扩展区域,可以展示一些资源相关的信息。
- 视图扩展:视图扩展在每个视图初始化的时候,都会初始化自己的扩展区域。
- leftArea:视图的左侧扩展区域,一般会展示大纲树、组件列表等等视图所需的面板,默认会注册大纲树的面板。
- topArea:视图的顶部扩展区域,可以用户展示对应视图特有的能力。
- mainArea:视图的编辑区域
- rightArea:视图的右侧扩展区域,主要是提供设计过程中的组件配置能力。
大家可能会担心了,有那么多扩展区域,设计器的画布岂不会很小。为了解决这个问题,其中大部分扩展区域在没有注册的情况下,是不会展示的,也就是低代码平台的开发者可以自行选择适合自己平台的扩展区域,而不是使用所有的扩展区域。
多层作用域开发模式
上面提到的应用级别的扩展区域,分为了应用层、资源层、视图层。我们在 API 的设计和使用上不能再延续之前的开发模式了。
在之前的开发模式下,我们常常会使用如下的代码来注册面板,但是这样的代码没有办法区分我们的面板是注册在哪一层的。因此我们需要在原来插件的开发模式下,根据插件注册的位置不同,将作用域分为几层。
import { skeleton } from "@alilc/lowcode-engine"; // 注册 logo 面板 skeleton.add({ area: "topArea", type: "Widget", name: "logo", content: Logo, // Widget 组件实例 contentProps: { // Widget 插件 props logo: "https://img.alicdn.com/tfs/TB1_SocGkT2gK0jSZFkXXcIQFXa-66-66.png", href: "/", }, props: { align: "left", width: 100, }, });
因此我们需要在插件的作用域中获取 skeleton 来进行面板的注册,而不是从全局,也就是从 @alilc/lowcode-engine 中获取 skeleton API。
import { IPublicModelPluginContext } from '@alilc/lowcode-types'; function pluginDemo(ctx: IPublicModelPluginContext) { const { skeleton, } = ctx; return { init() { skeleton.add({ area: 'topArea', type: 'Widget', name: 'pluginDemo', props: { align: 'left', width: 800, }, index: -1, content: Content, contentProps: { ctx, }, }); } } } pluginDemo.pluginName = 'pluginDemo';
这样,我们在不同的层级进行插件注册,就可以使用不同层级的 skeleton 来注册对应的扩展区域了。
应用层
import { init, workspace } from '@alilc/lowcode-engine'; (async function main() { workspace.registerResourceType(pageResourceType); await workspace.plugins.register(pluginDemo) init(undefined, { enableWorkspaceMode: true, }) })()
如图,在 workspace.plugins 下注册的插件,插件下获取的 skeleton 就是应用级别的 skeleton。
资源层
import { IPublicModelPluginContext } from '@alilc/lowcode-types'; function pageResourceType(ctx: IPublicModelPluginContext) { return { category: '页面', defaultViewType: 'page', defaultTitle: window.pageConfig.title, editorViews: [pageView], icon: PageIcon, async init() { await ctx.plugins.register(pluginDemo) }, } } pageResourceType.resourceName = 'page'; pageResourceType.resourceType = 'editor'; export default pageResourceType;
这样我们 plugin 里面的 ctx.skeleton 获取到的就是资源层的 skeleton,可以向资源层注册扩展了。
视图层
import { IPublicModelPluginContext } from '@alilc/lowcode-types'; export const pageView = (ctx: IPublicModelPluginContext, options: any) => { return { async init() { // 注册插件 await ctx.plugins.register(pluginDemo) }, }; }; pageView.viewName = 'page';
这样我们 plugin 里面的 ctx.skeleton 获取到的就是视图层的 skeleton,可以向视图层注册扩展了。
此外,视图层内的 ctx 还可以获取到其他的 API,来进行视图层设计器区域的扩展,包括:material、project、simulatorHost、hotkey、setters、canvas 等等。
如何升级应用级设计器
开启低代码引擎的应用模式
import { init, workspace } from '@alilc/lowcode-engine'; (async function main() { // ... init(undefined, { enableWorkspaceMode: true, }) })()
注册应用的资源类型和视图
开发页面视图
import { IPublicModelPluginContext } from '@alilc/lowcode-types'; export const pageView = (ctx: IPublicModelPluginContext, options: any) => { return { async init() { // 注册插件 }, }; }; pageView.viewName = 'page';
开发页面资源类型
import { IPublicModelPluginContext } from '@alilc/lowcode-types'; function pageResourceType(ctx: IPublicModelPluginContext) { return { category: '页面', defaultViewType: 'page', defaultTitle: window.pageConfig.title, editorViews: [pageView], icon: PageIcon, async init() { await ctx.plugins.register(pluginDemo) }, } } pageResourceType.resourceName = 'page'; pageResourceType.resourceType = 'editor'; export default pageResourceType;
注册页面资源类型
import { init, workspace } from '@alilc/lowcode-engine'; (async function main() { workspace.registerResourceType(pageResourceType); init(undefined, { enableWorkspaceMode: true, }) })()
注意事项
正如上面介绍的,我们需要通过插件的 ctx 作用域来获取 API,而不是通过全局作用域来获取 API,否则在多视图/多资源的情况下,作用域可能会混乱,导致 API 在非预期的视图下使用,出现问题。
示例:在下面的 Plugin 中,使用的是全局的 skeleton API,假设 pluginDemo 在 A 视图中注入的,这也有可能会调用到 B 视图的 skeleton API,导致我们的面板注册到非预期的视图中。
import { skeleton } from '@alilc/lowcode-engine'; import { IPublicModelPluginContext } from '@alilc/lowcode-types'; function pluginDemo(ctx: IPublicModelPluginContext) { return { init() { setTimeout(() => { skeleton.add({ area: 'topArea', type: 'Widget', name: 'pluginDemo', props: { align: 'left', width: 800, }, index: -1, content: Content, contentProps: { ctx, }, }); }, 2000) } } } pluginDemo.pluginName = 'pluginDemo';
未来规划
应用级别的低代码平台落地
接下来我们会在集团内使用应用级别的能力,升级已有的低代码平台,将其升级成应用级别的设计器,不断打磨我们的低代码平台和低代码引擎的应用级别的能力,当然落地之后也会对外分享我们的经验和教训。
提供应用级的精品插件
我们也会开发更多的应用级别的精品插件,帮助大家使用到应用级别的设计器能力。
应用级别的设计器性能优化
应用级别的设计器会拥有更多的资源类型,更多的插件,也可以同时编辑多个设计区域,后续对于性能的损耗肯定不低,我们在实践的同时也需要不断打磨设计器的性能。