TinyEngine 实战指南

从零开始搭建到二开 TinyEngine 低代码平台

华为开源低代码引擎的完整实践指南,涵盖环境部署、组件开发、插件扩展全流程

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

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

在深度开发、使用一个月之后,我想来跟你分享一下这款华为开源的低代码引擎——TinyEngine。它基于 Vue3,利用 DSL 实现从 schema 生成源码,支持多框架集成,且拥有强大的插件系统,能实现各种定制化开发的需求。

如果你正在寻找一款趁手的低代码引擎或者你对低代码开发感兴趣,那你不妨来试试 TinyEngine。我会用这篇文章手把手带你完成项目搭建、组件库部署与注册并开发一款简单的插件。

graph LR A[🚀 **起点: 探索TinyEngine**
华为开源低代码引擎] --> B subgraph B [🛠️ 环境准备] direction TB B1[快速启动前端
遇Mock Server小坑] B2[⚙️ 部署后端服务
打通数据流!] B3[✅ 前后端联调成功] B1 --> B2 --> B3 end B --> C subgraph C [🧩 组件开发] direction TB C1[创建组件库] C2[☁️ 私有仓库发布
Verdaccio搞定] C3[🌐 资源服务部署
unpkg提供静态资源] C1 --> C2 --> C3 end C --> D subgraph D [✨ 引擎集成] direction TB D1[拆分物料配置] D2[配置Package & Schema
含AI提效技巧🤫] D3[构建物料
注入引擎!] D_Check[👀 设计器中
看到自定义组件!] D1 --> D2 --> D3 --> D_Check end D --> E_Prob(❗**挑战:**
出码后依赖安装失败?) E_Prob --> F subgraph F [🔧 插件开发] direction TB F1[定位出码逻辑] F2[👨‍💻 开发genNpmrcPlugin插件
自动生成.npmrc] F3[注册&验证插件] F1 --> F2 --> F3 end F --> G[🏆 **终点: 完全掌控!**
从部署到扩展的全流程实践] %% 样式优化 style A fill:#f9f,stroke:#333,stroke-width:2px,color:#000 style G fill:#ccf,stroke:#333,stroke-width:2px,color:#000 style E_Prob fill:#f8d7da,stroke:#721c24,stroke-width:1px,color:#721c24 style D_Check fill:#d4edda,stroke:#155724,stroke-width:1px,color:#155724 %% 阶段分组样式(半透明处理避免喧宾夺主) style B fill:#e6f3ff80,stroke:#0066cc,stroke-width:1px style C fill:#e6ffe680,stroke:#009900,stroke-width:1px style D fill:#fff2e680,stroke:#ff6600,stroke-width:1px style F fill:#f0e6ff80,stroke:#6600cc,stroke-width:1px %% 连接线优化 linkStyle 0,3,6,9,12 stroke:#666,stroke-width:1.5px
缩放: 100%

环境准备与部署

1

前端部署

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

git clone git@github.com:opentiny/tiny-engine.git -b develop
pnpm i
pnpm dev

访问 localhost:8090 来初步体验。

注意:dev 启动了一个 mock server 做为服务端,但是这个 mock server 已经落后于版本需求了(截止到 v2.3)

2

后端部署

从 tiny-engine-backend-java 部署拉取 develop 分支并安装依赖后就可以直接开启本项目。

根据实际情况调整配置信息:

server:
  address: 0.0.0.0
  port: 7090
  
spring:
  # 数据库信息
  datasource: 
    username: xxx
    password: xxx
    url: xxx

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

  • 调整 designer-demo/engine.config.js 中的 material 属性
  • 设置 tiny-engine/designer-demo/env/.env.development 中的 VITE_ORIGIN
3

联调验证

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

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

TinyEngine演示
TinyEngine低代码平台演示
TinyEngine界面概览
TinyEngine出码后预览

组件库搭建与发布

1. 组件库项目

如果没有现成的组件库,可以使用提供的组件库项目模板:

git clone git@github.com:Ljhhhhhh/component-library-thin.git

2. Verdaccio私有仓库

使用 Verdaccio 做为私有仓库:

npm install -g verdaccio
verdaccio

修改配置地址(/path/to/config.yaml),增加:listen: 0.0.0.0:4873,使他可以通过多种地址进行访问。

3. 发布组件库

配置组件库中的 publishConfig 信息:

"publishConfig": {
  "registry": "http://localhost:4873"
}

打包、添加账号并发布:

npm build
npm adduser --registry http://localhost:4873/
npm publish

发布完成后访问 http://localhost:4873/ 可以看到刚刚发布的组件信息。

组件库发布成功
组件库发布到私有仓库成功

组件资源服务

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

推荐使用 git@github.com:lzwme/unpkg.git,此项目已经做了一些适配国内网络的工作。

在 clone 此项目后,在根目录下新建一个 .env 文件:

# 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

第一步:拆分组件配置

基于 mock 下的 bundle.json 拆分组件:

pnpm splitMaterials

执行此指令会在根目录下生成两部分物料配置文件:

  • materials/packages.json - 依赖包的信息
  • materials/components/**.json - 所有组件的 schema

第二步:追加组件库信息

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

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

第三步:添加组件 schema

根据物料协议添加组件 schema(component.json):

{
  "component": "MainMenuButton",
  "icon": "button",
  "npm": {
    "package": "@dcp/component-library",
    "exportName": "MainMenuButton",
    "version": "0.0.1",
    "script": "http://127.0.0.1:4874/@dcp/component-library@0.0.1/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 辅助提示:可以使用 AI 帮助生成组件 schema,文章附录中提供了一条优化过的 prompt。

第四步:数据库配置

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

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

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

TinyEngine组件注册演示
成功注册并使用组件

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

问题发现:出码后无法安装依赖,提示找不到组件库对应的包地址。这是因为私有化部署的组件库还需要提供对应的私库地址。

1. 定位出码逻辑

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

DesignToolbars 进行 debug,可以看到 state.rightBar 数据如下:

[
    [
        "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 这个插件。

2. 开发 genNpmrcPlugin 插件

模仿 genDependenciesPlugin 来写一个 genNpmrcPlugin

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

3. 注册并验证插件

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

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

结语

通过这一趟实践旅程,我们不仅让 TinyEngine 在本地顺利运行起来,更重要的是,我们亲手打通了从搭建私有组件库、配置资源服务,到将其无缝融入 TinyEngine 物料体系,乃至最终通过编写插件解决实际工程问题的完整闭环。

这不仅仅是一次技术的演练,更是对 TinyEngine 作为一款开源低代码引擎其强大扩展性和定制能力的深度体验。

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

附录

组件 schema 生成 prompt

你是一位心思缜密且喜欢反复验证的数据处理大师
我现在需要你来帮我生成组件的 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