一起来搞低代码--华为TinyEngine搭建&源码开发

本文适合对低代码开发有一定兴趣,并具备 Vue.js、Node.js 及基本命令行操作经验的前端开发者。

一起来搞低代码--华为TinyEngine初步上手指南

在深度开发、使用一个月之后,我想来跟你分享一下这款华为开源的低代码引擎--TinyEngine。它基于 Vue3,利用 DSL 实现从 schema 生成源码,支持多框架集成,且拥有强大的插件系统,能实现各种定制化开发的需求。如果你正在寻找一款趁手的低代码引擎或者你对低代码开发感兴趣,那你不妨来试试 TinyEngine。我会用这篇文章手把手带你完成项目搭建、组件库部署与注册并开发一款简单的插件。

前端部署

tiny-engine 拉取 develop 分支并安装依赖后就可以直接开启本项目:

bash 复制代码
git clone [email protected]:opentiny/tiny-engine.git -b develop
pnpm i
pnpm dev

是的,就跟其他项目一样,现在我们可以访问 localhost:8090 来初步体验了。

在初步体验之后,发现出码后的项目无法成功安装依赖,这是因为 dev 其实启动了一个 mock server 做为服务端,但是这个 mock server 已经落后于版本需求了(截止到 v2.3)。

所以我们直接进入下一个环节。

后端部署

同样从 tiny-engine-backend-java 部署拉取 develop 分支并安装依赖后就可以直接开启本项目,具体步骤请参考官方说明

可以根据实际情况调整配置信息:app/src/main/resources/application-dev.yml

yml 复制代码
server:
	address: 0.0.0.0
	port: 7090
	
spring:
    # 数据库信息
	datasource: 
		username: xxx
		password: xxx
		url: xxx

启动后端服务后,我们需要同步调整 tiny-engine 项目中的以下配置:

  1. designer-demo/engine.config.js 中的material属性增加域名信息: http://localhost:8090/mock/bundle.json (这里的域名应该是 tiny-engine 的地址,不是后端服务地址)
  2. tiny-engine/designer-demo/env/.env.developmentVITE_ORIGIN 设置为刚刚部署的后端服务地址:VITE_ORIGIN=http://127.0.0.1:7090

经过以上配置的调整,我们现在就可以通过 pnpm serve:frontend 启动项目了。

经过测试,出码后的项目可以正常安装依赖&运行。

组件库搭建与发布

组件库

如果你没有现成的组件库,你可以考虑用我发布到这个组件库项目模板 component-library-thin

bash 复制代码
git clone [email protected]:Ljhhhhhh/component-library-thin.git

verdaccio

组件库可以发布到公网的npm,不过我需要用私有仓库,所以就选择 verdaccio。按照官方说明进行安装并开启服务:

bash 复制代码
npm install -g verdaccio
verdaccio

开启服务后注意一下给出的配置地址(/path/to/config.yaml),增加这条配置:listen: 0.0.0.0:4873,使他可以通过多种地址进行访问。

修改完成后,重启服务。

发布组件库

通过 verdaccio 搭建私有仓库后,我们回到组件库项目并按实际情况来配置组件库中的 publishConfig 信息:

json 复制代码
"publishConfig": {
    "registry": "http://localhost:4873"
}

随后就是打包、添加账号并发布:

bash 复制代码
npm build
npm adduser --registry http://localhost:4873/
npm publish

发布完成后我们访问 http://localhost:4873/ 可以看到已经有了刚刚发布的组件信息。

组件资源服务

如果你是把组件库部署到了公共的npm上,可以跳过这部分内容,组件资源可以直接通过 unpkg获取

在 tinyEngine 中,想要注册第三方组件库做为物料需要通过资源服务的方式,那么我们可以用自己部署unpkg的方式来提供资源服务。

这里推荐 lzwme的 unpkg,因为官方的 unpkg 需要搭配CLOUDFLARE,此项目已经做了一些适配国内网络的工作,可以减少一些心智负担。

在 clone 此项目后,在根目录下新建一个.env文件,并根据实际情况写入配置:

bash 复制代码
# verdaccio 服务地址
NPM_REGISTRY_URL=http://127.0.0.1:4873

# unpkg 部署信息
ORIGIN=http://127.0.0.1
PORT=4874

现在我们可以安装依赖并通过pnpm serve 来启动unpkg服务了。 启动完成后,可以通过访问http://127.0.0.1:4874/:package获取到组件的静态资源。

静态资源的地址可以保存一下,下一步就会用到

如何注册组件到 TinyEngine

发布了组件库,也提供了静态资源服务,现在就可以在 TinyEngine 中消费我们的组件了。 不过别急,还需要你跟着我一步一步操作:

第一步: 基于 mock 下的 bundle.json 拆分组件:pnpm splitMaterials,执行此指令会在根目录下生成两部分物料配置文件:

  1. materials/packages.json 这个是依赖包的信息
  2. materials/components/**.json 这里存放着所有组件的 schema

第二步: 追加需要注册的组件库信息到 materials/packages.json的 packages 数组,如:

json 复制代码
{
  "name": "我的组件库",
  "package": "@dcp/component-library", // 组件库包名
  "version": "0.0.1",
  "script": "http://127.0.0.1:4874/@dcp/[email protected]/js/component-library.mjs", // 上一步得到的组件静态资源地址
  "destructuring": true, // 引入方式,true=变量引入,false=default 引入
  "npmrc": "@dcp:registry=http://127.0.0.1:4873" // 需要追加到 npmrc 的镜像信息
}

第三步: 根据物料协议添加组件 schema(component.json),这里列举出比较重要的一些配置:

json 复制代码
{
  "component": "MainMenuButton",
  "icon": "button",
  "npm": {
    "package": "@dcp/component-library",
    "exportName": "MainMenuButton",
    "version": "0.0.1",
    "script": "http://127.0.0.1:4874/@dcp/[email protected]/js/component-library.mjs",
    "destructuring": true,
    "npmrc": "@dcp:registry=http://127.0.0.1:4873"
  },
  "group": "customer",
  "category": "button",
  "schema": {
	  properties: {}, // 组件的 props 信息
	  events: {}, // 组件提供的事件
	  slots: {} // 组件的插槽信息
  },
  "snippets": {} // 组件面板配置
}

组件配置信息还是挺多的,推荐参考已有的组件 schema 进行学习验证。 当然,我们也可以应用 AI 帮我们生成组件 schema,我在附录中提供了一条 prompt,经过我的多次优化,使用效果还不错。

第四步: 按照数据库配置更新根目录下.env.local配置:

ini 复制代码
SQL_HOST=127.0.0.1
SQL_PORT=3306
SQL_USER=root
SQL_PASSWORD=12345678
SQL_DATABASE=tiny_engine_data_java

第五步: 执行指令:pnpm buildMaterials

如果一切正常的话,designer-demo/public/mock/bundle.json 下的配置和数据库的 t_components 表中会插入新增的组件信息。 现在到验收成果的时候了。

执行指令:pnpm run serve:frontend 并访问:http://127.0.0.1:8090/?type=app&id=1&tenant=1&pageid=1

可以看到我们的组件已经可用:

但是我们出码后会发现无法安装依赖,并提示找不到组件库对应的包地址。 这是因为我们私有化部署的组件库还需要提供对应的私库地址,那我们动手实现一个 npmrc 文件生成的插件吧。

源码开发-出码生成 npmrc 文件

建议先从官网教程了解插件开发:opentiny.design/tiny-engine...

debug 思路

通过 Vue DevTools 可以定位到DesignToolbars 组件 再根据 css 类定位到 toolbar-right-content 对应的 div,可以看到左侧的按钮是通过遍历state.rightBar得到的。 对 DesignToolbars进行 debug,可以看到 state.rightBar 数据如下:

json 复制代码
[
    [
        "engine.toolbars.themeSwitch",
        "engine.toolbars.redoundo",
        "engine.toolbars.clean"
    ],
    [
        "engine.toolbars.preview"
    ],
    [
        "engine.toolbars.generate-code",
        "engine.toolbars.save"
    ]
]

合理猜测,出码对应的是engine.toolbars.generate-code,通过全局查询,可以在packages/toolbars下找到generate-code 这个插件

gererate 函数找到生成文件函数:getPreGenerateInfo,继续追查找到 generateAppCode,发现此函数从engine.service.generateCode 得到

继续追查,发现 generateAppCode 核心为 @opentiny/tiny-engine-dsl-vue 导入的generateApp。 那就直接找到 name@opentiny/tiny-engine-dsl-vue 的包。

通过@opentiny/tiny-engine-dsl-vue下的generateApp方法,可以看到它主要是调用各种内置的插件进行文件的生成,比如genDependenciesPlugin

具体实现

那么我们可以模仿genDependenciesPlugin来写一个genNpmrcPlugin

js 复制代码
import { mergeOptions } from '../utils/mergeOptions'
import { parseImport } from '@/generator/vue/sfc/parseImport'

const defaultOption = {
  fileName: '.npmrc',
  path: '.'
}

const getComponentsSet = (schema) => {
  const { pageSchema = [], blockSchema = [] } = schema
  let allComponents = []

  pageSchema.forEach((pageItem) => {
    allComponents = allComponents.concat(parseImport(pageItem.children || [])?.components || [])
  })

  blockSchema.forEach((blockItem) => {
    allComponents = allComponents.concat(parseImport(blockItem.children || [])?.components || [])
  })

  return new Set(allComponents)
}

const parseSchema = (schema) => {
  const { componentsMap = [] } = schema
  const resDeps = []
  const componentsSet = getComponentsSet(schema)

  for (const { package: packageName, npmrc, componentName } of componentsMap) {
    if (
      npmrc &&
      packageName &&
      componentsSet.has(componentName) &&
      resDeps.findIndex((item) => item === npmrc) === -1
    ) {
      resDeps.push(npmrc)
    }
  }

  return resDeps
}

function genNpmrcPlugin(options = {}) {
  const realOptions = mergeOptions(defaultOption, options)

  const { path, fileName } = realOptions

  return {
    name: 'tinyEngine-generateCode-plugin-npmrc',
    description: 'add package dependencies to .npmrc',
    /**
     * 分析依赖,写入 package.json
     * @param {import('@opentiny/tiny-engine-dsl-vue').IAppSchema} schema
     * @returns
     */
    run(schema) {
      const npmrcInfo = parseSchema(schema)
      const originNpmrcItem = this.getFile(path, fileName)

      // 如果没有 .npmrc 文件,直接写入
      if (!originNpmrcItem) {
        this.addFile({ fileType: 'npmrc', fileName, path, fileContent: npmrcInfo.join('\n') }, true)
        return
      }

      // 如果有 .npmrc 文件,需要先读取文件内容,进行合并去重
      const originNpmrcContent = originNpmrcItem.fileContent
      const newNpmrcContent = [...new Set([...npmrcInfo, ...originNpmrcContent])].join('\n')

      this.addFile({ fileType: 'npmrc', fileName, path, fileContent: newNpmrcContent }, true)
    }
  }
}

export default genNpmrcPlugin

剩下的就是注册此插件,然后 build 一下当前包之后,就可以去验收成果了:

成功生成.npmrc,且内容正确,可以成功安装依赖。

结语

通过这一趟实践旅程,我们不仅让 TinyEngine 在本地顺利运行起来,更重要的是,我们亲手打通了从搭建私有组件库、配置资源服务,到将其无缝融入 TinyEngine 物料体系,乃至最终通过编写插件解决实际工程问题的完整闭环。这不仅仅是一次技术的演练,更是对 TinyEngine 作为一款开源低代码引擎其强大扩展性和定制能力的深度体验。

希望这篇指南能为你揭开 TinyEngine 的面纱,让你感受到它在提升开发效率、应对复杂场景方面的潜力。掌握这些基础后,你将能更自信地去探索 TinyEngine 提供的丰富功能,构建更复杂的低代码应用,甚至为这个充满活力的开源社区贡献自己的一份力量。低代码的世界广阔,而 TinyEngine 无疑是其中值得你深入探索的利器。

附录

组件 schema 生成 prompt

markdown 复制代码
你是一位心思缜密且喜欢反复验证的数据处理大师
我现在需要你来帮我生成组件的 schema 数据,在这之前你需要先充分学习此说明文档:@web:https://opentiny.design/tiny-engine#/help-center/course/dev/15,在此文档的基础上,你需要遵守以下规则:

- version 为当前@package.json 的 version
- group 请你给我设置为 xxx(按需)
- doc_url、screenshot、tags、keywords 默认为空
- dev_mode 为 proCode
- id 为 1
- npm 配置除 exportName 外为默认值
- configure 配置为相同的默认值
- 仔细思考并验证后再输出 schema
- 仔细思考并验证后再输出 snippets

### widget.component

对于 schema.properties.content 下的 widget.component(配置属性的渲染组件)说明如下:
一般可以通过属性的类型判断选用哪个组件,string 类型一般选择 MetaInput 或 MetaBindI18n、enum 类型一般选择 MetaSelect、object 类型一般选择 MetaCodeEditor,具体可用的有如下组件:

- MetaInput
- MetaBindI18n
- MetaBindVariable
- MetaCodeEditor
- MetaNumber
- MetaRadio
- MetaSelect
- MetaSlider
- MetaSwitch
- MetaColor
- MetaDatePicker
- MetaJsSlot
- MetaSlot

请你根据组件属性来合理选择 component

## 预习验证

请你参考以下组件和对应的 schema 文件做一次预习验证

1. @index.vue 以及他对应的 schema 文件 @SixAxisRobot.json
2. @Index.vue 以及他对应的 schema 文件 @DcpStatus.json
3. @index.vue 以及他对应的 schema 文件 @MainMenuButton.json

## 生成

请你帮我生成 @index.vue 组件的 schema,不要急于给出最终的 schema,而是充分验证后给出准确的 schema
相关推荐
inxunoffice16 分钟前
批量在多个 PDF 的指定位置插入页,如插入封面、插入尾页
前端·pdf
木木黄木木21 分钟前
HTML5 Canvas绘画板项目实战:打造一个功能丰富的在线画板
前端·html·html5
豆芽81923 分钟前
基于Web的交互式智能成绩管理系统设计
前端·python·信息可视化·数据分析·交互·web·数据可视化
不是鱼23 分钟前
XSS 和 CSRF 为什么值得你的关注?
前端·javascript
顺遂时光26 分钟前
微信小程序——解构赋值与普通赋值
前端·javascript·vue.js
anyeyongzhou28 分钟前
img标签请求浏览器资源携带请求头
前端·vue.js
Captaincc37 分钟前
腾讯云 EdgeOne Pages「MCP Server」正式发布
前端·腾讯·mcp
最新资讯动态1 小时前
想让鸿蒙应用快的“飞起”,来HarmonyOS开发者官网“最佳实践-性能专区”
前端
雾岛LYC听风1 小时前
3. 轴指令(omron 机器自动化控制器)——>MC_GearInPos
前端·数据库·自动化
weixin_443566981 小时前
39-Ajax工作原理
前端·ajax