最近开始折腾低代码平台,用了阿里的低代码框架。整体体验还行,但是坑也不少,特别是物料描述这一块,官方文档虽然不少内容,但是一些小东西还是缺了不少。所以想把整个过程记录下来分享给大家。
我打算是利用这个引擎做一个比较全面的定制化改造的,目前进度推到了出码功能这一块,今天先讲搭建平台(编辑器)和对接本地的物料仓库进行调试,后续内容会慢慢更新,估计会和我的vscode插件开发一样是个长系列
贴一下官方文档

https://lowcode-engine.cn/site/docs/guide/create/useEditor

创建新的编辑器

先安装官方的命令行工具

npm install -g @alilc/create-element@latest

通过命令行工具创建

npm init @alilc/element editor-project-name

image.png
选择编辑器后根据提示往下进行,接下来就是常规启动脚手架的流程,这部分官方文档有详细说明,就不再赘述

目录结构简单介绍

image.png
主要看 src 下面的文件,可以看到 plugins 下面有很多插件,这些都是可以改的。官方比较贴心的把各种场景下的开发方法尽量考虑了,这些看一下对应的插件代码都能知道,这里只做简单介绍

plugin-component-panel

image.png
这边的代码很简单,就是在左侧加了一个弹出面板,对应页面上的位置
image.png
点开后可以看到里面的组件
我这边已经做了不少改造,所以和各位的不一样,因此就不展示了。
这里的代码只是加了一个面板,里面的组件并不是在这里添加的
关于如何拓展定制化面板可以看这里

https://lowcode-engine.cn/site/docs/guide/expand/editor/pluginWidget

plugin-editor-init

image.png
这里就是官方例子中添加组件的地方,官方将其称作“物料”,后面我们也会用这个称呼。这部分代码设置了物料并进行了初始化,引入的一个文件很重要

import assets from '../../services/assets.json';

这个文件就是设置面板中物料的地方,也就是资产配置文件
image.png
packages 我这边暂时理解为依赖的包体,而 components 是设置显示在页面上物料的地方,sort 和 groupList 都是分类和排序相关的

plugin-logo-sample

这个文件如其名,是设置 logo 相关的内容

plugin-lowcode-component

这个插件也是用来配置物料的,不过是用代码来进行配置。官方举例子嘛,考虑各种情况。
我们打开组件面板,里面的低代码组件就是这个文件配置的
image.png
在同级目录下还有一个文件是 lowcode-schema.json ,这个文件和上面说的资产文件不一样,是一个组件树的描述文件,我们在页面上,将这个组件拖拽到中间区域中
image.png
我们看到会有很多内容被添加进来,这个文件就是这些内容的描述文件。因为我移除了官方的物料引用,所以这里部分组件没有加载出来,大家看到的内容和我是不一样的

剩下的插件就不介绍了,大家可自行查看,上面所有这些插件都是在 src/index.ts 下引用的

创建物料仓库

其余步骤和创建编辑器的时候一样,只是在选择类型时我们选择 组件/物料
image.png
物料仓库有两种启动方式

npm start
npm run lowcode:dev

上面是组件开发模式,运行这个命令主要专注于开发组件,关注组件具有的功能
下面的是物料接入编辑器的本地调试开发,这个模式关注我们开发好的组件在编辑器中的实际表现
组件开发不是这篇文章关注的内容,所以接下来涉及到的内容默认在 npm run lowcode:dev 情况下进行

接入本地物料仓库

物料仓库默认跑在3333端口,打开页面会发现也是一个编辑器页面。这个编辑器具备所有基本的功能,所以直接在这个编辑器上调试基本是没有问题的,但是如果我们自己创建的编辑器做了很多定制化开发、很多改造,希望能够在另外一个编辑器中看效果的话要怎么做呢?
官方考虑到了这种场景,所以物料仓库启动的服务能够直接访问到物料描述文件,我们在浏览器打开下方的链接

http://localhost:3333/build/lowcode/meta.js

image.png
meta 就是物料描述文件,物料仓库启动后是可以直接通过本地链接访问这个文件的。而 build 下的文件是启动服务时自动生成的
image.png
我们回到我们自己创建的编辑器中,找到之前提到的 assets.json 文件
image.png
我们需要在 packages 和 components 中添加我们的物料仓库地址

{
  "packages": [
    {
      "package": "editor-components",
      "version": "0.1.0",
      "library": "BizComps",
      "urls": [
        "http://localhost:3333/build/lowcode/render/default/view.js",
        "http://localhost:3333/build/lowcode/render/default/view.css"
      ],
      "editUrls": [
        "http://localhost:3333/build/lowcode/view.js",
        "http://localhost:3333/build/lowcode/view.css"
      ],
      "advancedUrls": {
        "default": [
          "http://localhost:3333/build/lowcode/render/default/view.js",
          "http://localhost:3333/build/lowcode/render/default/view.css"
        ]
      },
      "advancedEditUrls": {}
    }
  ],
  "components": [
    {
      "exportName": "EditorComponentsMeta",
      "npm": {
        "package": "editor-components",
        "version": "0.1.0"
      },
      "url": "http://localhost:3333/build/lowcode/meta.js",
      "urls": {
        "default": "http://localhost:3333/build/lowcode/meta.js"
      },
      "advancedUrls": {
        "default": [
          "http://localhost:3333/build/lowcode/meta.js"
        ]
      }
    }
  ],
  "sort": {
    "groupList": [
      "精选组件",
      "原子组件",
      "低代码组件"
    ],
    "categoryList": [
      "基础元素",
      "布局容器类",
      "表格类",
      "表单详情类",
      "帮助类",
      "对话框类",
      "业务类",
      "通用",
      "引导",
      "信息输入",
      "信息展示",
      "信息反馈"
    ]
  },
  "groupList": [
    "精选组件",
    "原子组件",
    "低代码组件"
  ],
  "ignoreComponents": {}
}

为了避免出现问题,建议先不要删除原本的配置,我们追加配置就行了,因为不知道哪些地方用到了原来配置的那些组件,也就是最好只是增加下面两段内容

{
  "package": "editor-components",
  "version": "0.1.0",
  "library": "BizComps",
  "urls": [
    "http://localhost:3333/build/lowcode/render/default/view.js",
    "http://localhost:3333/build/lowcode/render/default/view.css"
  ],
  "editUrls": [
    "http://localhost:3333/build/lowcode/view.js",
    "http://localhost:3333/build/lowcode/view.css"
  ],
  "advancedUrls": {
    "default": [
      "http://localhost:3333/build/lowcode/render/default/view.js",
      "http://localhost:3333/build/lowcode/render/default/view.css"
    ]
  },
  "advancedEditUrls": {}
}
{
      "exportName": "EditorComponentsMeta",
      "npm": {
        "package": "editor-components",
        "version": "0.1.0"
      },
      "url": "http://localhost:3333/build/lowcode/meta.js",
      "urls": {
        "default": "http://localhost:3333/build/lowcode/meta.js"
      },
      "advancedUrls": {
        "default": [
          "http://localhost:3333/build/lowcode/meta.js"
        ]
      }
    }

这里要注意的一些地方,是配置中 package、library,exportName、npm 这些字段,有时候本地改的时候改错了一些内容都是会导致编辑器没办法顺利加载物料,甚至会导致组件面板无法打开的问题,需要仔细核对
大家可以参考物料仓库自动生成的文件,我们回到物料仓库找到 build/lowcode 下面的 assets-dev.json 文件
image.png
这是物料仓库本地调试的时候引入的文件,我们可以把里面的 packages 和 components 拷过去,改一下路径就行了,需要注意物料仓库的服务必须是启动的状态
这时我们刷新编辑器页面(一定要刷新)

自定义物料描述生成

当我们本地启动物料仓库的服务时,引擎会自动生成一个 lowcode 文件夹,我们可以看到下面对应了我们使用到的所有组件,打开其中一个 meta.ts


import { IPublicTypeComponentMetadata, IPublicTypeSnippet } from '@alilc/lowcode-types';

const CustomerDivMeta: IPublicTypeComponentMetadata = {
  "componentName": "CustomerDiv",
  "title": "基础容器(自定义表单)",
  "docUrl": "",
  "screenshot": "",
  "devMode": "proCode",
  "npm": {
      "package": "editor-components",
      "version": "0.1.0",
      "exportName": "CustomerDiv",
      "main": "src\\index.tsx",
      "destructuring": true,
      "subName": ""
  },
  "configure": {
      "supports": {
          "style": true
      },
      "component": {
          "isContainer": true
      }
  },
};
  
const snippets: IPublicTypeSnippet[] = [
  {
    "title": "基础容器",
    "schema": {
        "componentName": "CustomerDiv",
        "props": {}
    }
}
];


export default {
  ...CustomerDivMeta,
  snippets
};

这个文件就是每个组件对应的描述文件,将会直接影响到该组件在编辑器中的表现。
比如我们需要控制组件所在的 tab 页和 分组,就需要添加 group 和 category,这里要添加在上方
image.png
image.png
又或者,我们想要给这个组件添加一个用于展示的图片,需要添加 screenshot 这个字段。这个字段要添加在下方
image.png
image.png
其他内容就不在赘述,大家可以参考官方文档

https://lowcode-engine.cn/site/docs/specs/material-spec

存在的问题

通过官方文档和介绍视频我们可以知道,这个文档是支持手动修改的。这里可以理解,很多配置是没办法直接通过我们的组件代码获取的,或者暂时没有获取的手段。手动配置也行,但是会导致一个问题,自动生成的 meta 会和手动配置的产生冲突。所以官方只会在组件第一次创建时生成这个文件,后续更新改代码似乎都不会去动这个文件,导致 props 无法正确更新,这里我和官方演示视频中还不太一样,但是反复试了很多次确信了确实不会更新这个文件。
这个文件无法自动更新的话会导致组件在编辑器中无法更新,如果一个一个手动配置的话工作量又会很大。问题非常多,就不展开了

自定义 meta 修改插件

就我个人来说,依旧不希望直接去修改这个文件。我希望这个文件始终是自动生成的,而缺少的一些配置,或者需要修改的一些配置,我们可以增加一些配置文件插入进去,而不是直接去修改。
好消息是可以通过自定义的插件做到这一点,不过我目前只研究了生产打包时的插入,直接本地就能更新配置的方法等我后续更新吧
下面分享一下我的解决办法,其实非常的简单
我们打开
build.lowcode.js

const { library } = require('./build.json');

module.exports = {
  alias: {
    '@': './src',
  },
  plugins: [
    [
      '@alifd/build-plugin-lowcode',
      {
        library,
        engineScope: "@alilc"
      },
    ]
  ],
};

直接说结论,meta 文件就是这里配置的插件 @alifd/build-plugin-lowcode 生成的,好消息是它生成的只是 ts 文件,也就是说是中间产物,后续还会有其他程序负责处理 ts 并输出最终结果。也就是说我们可以在这里配置一个插件,让其处理 这个插件生成的 meta 文件,插入我们额外的配置,这些插入的内容会生效并且会直接插入到最终的产物当中

坏消息是这个插件只会在 meta 初次生成或者 build 的时候执行,因此我们需要不断执行 build 命令来更新 meta
引入自定义插件的方式如下

const { library } = require('./build.json');

module.exports = {
  alias: {
    '@': './src',
  },
  plugins: [
    [
      '@alifd/build-plugin-lowcode',
      {
        library,
        engineScope: "@alilc"
      },
    ],
    [
      './plugins/did-meta.js',
    ],
  ],
};

did-meta.js 就是我们自定义的插件,负责处理已经生成的 meta 文件,插入我们配置的其他内容
既然已经打算完全自动生成 meta 了,我就打算贯彻到底。我们可以在 @alifd/build-plugin-lowcode 前再配置一个插件,用于删除所有的 meta 文件,这样就意味这个 meta 是完全由程序控制生成的了,无法再手动变更,引入方式是一样的,大家自行考虑是否添加

编写 did-meta

这个代码其实很简单,我的想法就是我们在主目录下增加一个配置文件,由代码将其写入 meta 当中,这里将这个文件名定为 inject.config.json

{
  "customer-div": {
    "group": "测试组件组别",
    "category": "布局",
    "title": "基础容器",
    "snippets": {
      "title": "基础容器",
      "screenshot": "https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fb-ssl.duitang.com%2Fuploads%2Fitem%2F201506%2F11%2F20150611221542_L8kzP.jpeg&refer=http%3A%2F%2Fb-ssl.duitang.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1696428206&t=29789e9b5b914cb8b617e4389ca6aa65"
    },
    "configure": {
      "component": {
        "isContainer": true
      }
    }
  }
}

最外层就是组件名,里面就是具体的配置参数
现在我们来写具体的代码

const fs = require('fs')
const metaConfig = require('../inject.config.json');

module.exports = async () => {
  const files = await fs.readdirSync('lowcode/')
  for (const file of files) {
    const contextBuffer = await fs.readFileSync(`lowcode/${file}/meta.ts`);
    const context = contextBuffer.toString();
    const after = context.split('IPublicTypeComponentMetadata = ')[1];
    let jsonTarget;
    let metaJson;
    try {
      jsonTarget = after.split(';\n')[0];
      metaJson = JSON.parse(jsonTarget);
    } catch {
      jsonTarget = /(.*?);[\n|\r| ]*const/.exec(after)[1];
      metaJson = JSON.parse(jsonTarget);
    }
    const config = metaConfig[file];

    if (config) {
      // 插入主配置
      for (const key of Object.keys(config)) {
        if (key === 'configure') {
          // 主配置不允许写入 props
          Reflect.deleteProperty(metaJson[key], 'props');
          metaJson[key] = Object.assign(metaJson[key], config[key]);
        } else if (key !== 'snippets') {
          metaJson[key] = config[key];
        }
      }

      // 插入 snippets 配置
      const snippetsContext = context.split('IPublicTypeSnippet[] = [')[1];
      const snippetsJson = JSON.parse(snippetsContext.split('];')[0]);
      for (const key of Object.keys(config.snippets || [])) {
        snippetsJson[key] = config.snippets[key];
      }
  
      const targetContext = `${context.split('IPublicTypeComponentMetadata = ')[0]}IPublicTypeComponentMetadata = ${JSON.stringify(metaJson)};
  
const snippets: IPublicTypeSnippet[] = [
  ${JSON.stringify(snippetsJson)}
];
${snippetsContext.split('];')[1]}`;
      await fs.writeFileSync(`lowcode/${file}/meta.ts`, targetContext);
    }
  }
}

简单讲一下,前面部分就是遍历文件,获取具体的配置,并 parse 成实际的对象以便我们进行修改

try {
  jsonTarget = after.split(';\n')[0];
  metaJson = JSON.parse(jsonTarget);
} catch {
  jsonTarget = /(.*?);[\n|\r| ]*const/.exec(after)[1];
  metaJson = JSON.parse(jsonTarget);
}

这部分有两个获取方式的原因是我们修改后的 meta 和官方插件直接生成的 meta 格式有些微差别,其实也就多了一些换行和空格,我正则弱,实在折腾不出能够兼容两种情况的正则,大家可以自行修改

// 插入主配置
  for (const key of Object.keys(config)) {
    if (key === 'configure') {
      // 主配置不允许写入 props
      Reflect.deleteProperty(config[key], 'props');
      metaJson[key] = Object.assign(metaJson[key], config[key]);
    } else if (key !== 'snippets') {
      metaJson[key] = config[key];
    }
  }

我们自己写的配置当中是不允许有 props 的,因为我们费这么大劲就是想用官方插件自动生成的 props 配置,为了避免无心之举,因此这里删除了 props 配置。当然,还有一些问题,我们后面再解决

// 插入 snippets 配置
  const snippetsContext = context.split('IPublicTypeSnippet[] = [')[1];
  const snippetsJson = JSON.parse(snippetsContext.split('];')[0]);
  for (const key of Object.keys(config.snippets || [])) {
    snippetsJson[key] = config.snippets[key];
  }

我们看 meta 都清楚,配置分两部分,其中 snippets 是写在下面的,因此需要插入的步骤就有了一点区别
最后就是写入文件了
写完保存后,我们执行 npm run lowcode:build
我们配置文件中的内容就会插入到 meta 中,并且会被写进最终的物料描述当中
image.png
这也是刚刚为什么写两个获取JSON对象的原因,我们插入的内容都被折叠到一行里面了,当然,这并不影响最终产物

设置器传参

我们参考官方原本的两个默认的组件,colorful-button、colorful-input,组件代码结构是下面这样的
image.png
在物料组件中,我们基本都需要为每个组件声明一个 props 的 interface,这个 interface 的作用会体现在设置器中
什么是设置器?
当我们在编辑器中点击组件时会弹出物料的属性面板
image.png
而我们在每个属性上方写的注释,将会被低代码引擎解析为属性的中文名,因此这个注释是必要的
而负责解析这些属性的,就是设置器
官方为我们内置了很多设置器,比如在我图例中有很多属性是 string 类型,他们就会调用官方的 string 设置器,我们查看官方文档关于这个 string 设置器
image.png
我们可以发现它居然还能传参,那要怎么传呢?
我没找到别的方法,因此判定是直接写在 meta 里的
image.png
这样写了之后我们我们回到页面上看
image.png
如果你是按照我上面的流程来的,写在就会产生新的问题,我们已经不手动修改 meta 了,这个参数要怎么写进去

拓展 did-meta

其实还是一样的,理清 json 结构后插入进去就行了,为此我重新写了一个配置文件 props-params.json

{
  "customer-input": {
    "otherClass": {
      "placeholder": "请输入"
    }
  }
}

修改 did-meta

const propsParams = require('../props-params.json');
··· // 其他代码
if (config) {
      // 插入主配置
      ··· // 省略部分代码

      // 给设置器传参
      for (let i = 0; i < (metaJson.configure.props || []).length; i++) {
        const prop = metaJson.configure.props[i];
        const paramsConfig = propsParams[file];
        if (paramsConfig) {
          const params = paramsConfig[prop.name];
          if (metaJson.configure.props[i].setter) {
            metaJson.configure.props[i].setter.props = {
              ...(metaJson.configure.props[i].setter.props || {}),
              ...params
            }
          }
        }
      }

这样就能为 porps 的 设置器传参了。
后面我打算直接读取对应的组件文件,对注释内容进行解析,不过目前而言这样就够了

Logo

低代码爱好者的网上家园

更多推荐