本文适合对低代码开发有一定兴趣,并具备 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 项目中的以下配置:
designer-demo/engine.config.js
中的material
属性增加域名信息: http://localhost:8090/mock/bundle.json (这里的域名应该是 tiny-engine 的地址,不是后端服务地址)tiny-engine/designer-demo/env/.env.development
中VITE_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
,执行此指令会在根目录下生成两部分物料配置文件:
- materials/packages.json 这个是依赖包的信息
- 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