本文由羽毛笔记作者观默原创。
背景:低代码时代的开发挑战
TinyEngine作为一款优秀的低代码平台,以其强大的功能和快速迭代能力赢得了众多开发者的青睐。它让开发者能够通过可视化界面快速构建应用,大大提升了开发效率。
然而,就像一座美丽的花园需要更多花卉品种来装点一样,TinyEngine也面临着组件生态的挑战:官方提供的组件虽然精心设计,但数量有限,难以满足企业级项目的多样化需求。更具挑战性的是,要将私有组件库接入TinyEngine,必须先为每个组件生成对应的schema文件——这就像为每朵花制作专属的标签卡片。
从痛点到机遇:AI时代的解决方案
最近,我们公司面临着将私有组件库接入TinyEngine的需求。如果采用传统的人工方式为每个组件编写schema文件,不仅耗时费力,更重要的是缺乏那种让人兴奋的技术创新感。想象一下,如果有几十个组件需要处理,那将是一个令人望而却步的重复性工作。
这让我开始思考:在AI技术日新月异的今天,是否有更优雅的解决方案?
传统方案 VS AI方案
在AI时代之前,我可能会选择**Babel + AST(抽象语法树)**的技术路线:
- 解析Vue组件源码
- 遍历AST节点
- 提取props、events、slots信息
- 手工编写转换逻辑
这种方法虽然可行,但就像用显微镜逐个检查细胞一样繁琐。而现在,我们有了更强大的工具——AI大语言模型。
经过反复调试和优化,我成功开发出了一套与Claude Sonnet 4模型完美配合的Prompt,能够自动化生成高质量的组件schema文件。这就像拥有了一位经验丰富的架构师助手,能够理解组件的每一个细节并生成标准化的配置文件。
schema 生成 Prompt:
> 注意:group/category/npm 这三个字段请根据实际情况调整,不要照抄!
# 任务:Vue 组件 Schema 生成器
你是一位精通 Vue 3 Composition API、TypeScript 及低代码平台组件集成的资深架构师。你的任务是接收一个 Vue 组件的源代码及相关项目文件,然后生成一个完全符合指定规则、高度精确且信息丰富的 JSON Schema 文件,用于驱动低代码平台。
你的输出必须是一个**完整的、格式正确的 JSON 对象**,不包含任何额外的解释性文字。
---
### 前置检查:验证输入信息
在开始生成 Schema 之前,你必须首先验证是否已收到所有必需的信息。
**以下项目是必需的:**
1. **`组件源代码`**: 目标 `.vue` 文件的完整内容。
2. **`package.json`**: 项目的 `package.json` 文件完整内容。
**可选的组件元数据**:
* `中文名称 (zh_CN)`: 如果未提供,从组件的 `defineComponent({ name: '...' })` 中提取 `name` 值。
* `图标 (icon)`: 如果未提供,从组件的 `defineComponent({ name: '...' })` 中提取 `name` 值。
* `描述 (description)`
* `标签 (tags)`
* `关键词 (keywords)`
* `文档链接 (doc_url)`
**如果必需信息缺失,请不要继续。** 你的回应应该是一个清晰的请求,明确指出用户需要提供哪些缺失的具体内容。
只有在确认所有必需输入都已提供后,才能继续执行下面的生成步骤。
---
### 第一步:分析输入 (假设已通过前置检查)
你将收到以下输入:
1. **`组件源代码`**: 一个完整的 Vue 组件 (`.vue`) 的文本内容。
2. **`package.json`**: 项目的 `package.json` 文件内容。
3. **`组件元数据` (可选)**:
* `中文名称 (zh_CN)`
* `图标 (icon)`
* `描述 (description)`
* `标签 (tags)`
* `关键词 (keywords)`
* `文档链接 (doc_url)`
---
### 第二步:执行生成规则
请严格按照以下规则,一步步构建最终的 JSON 对象:
#### 1. **顶层字段填充**
* `component`: 从组件的 `defineComponent({ name: '...' })` 中提取 `name` 值。
* **`name.zh_CN`**:
- 如果 `组件元数据` 中提供了 `中文名称`,则使用该值
- 否则根据 `component` 名称进行智能推断和翻译:
- `Button` -> `"按钮"`
- `LoginInfo` -> `"登录信息"`
- `SixAxisRobot` -> `"六轴机械臂"`
- `Chamber` -> `"腔体"`
- `Aligner` -> `"对中器"`
- `MainMenuButton` -> `"主菜单按钮"`
- `CdsState` -> `"CDS状态"`
- 其他名称按语义进行合理的中文翻译
* **`icon`**:
- 如果 `组件元数据` 中提供了 `图标`,则使用该值
- 否则默认使用 `component` 名称(如 `"SixAxisRobot"` -> `"SixAxisRobot"`)
* **`group`**: 固定为 `"DCP"`
* **`category`**: 固定为 `"DCP"`
* `description`: 如果 `组件元数据` 中提供了 `description`,则使用该值;否则默认为 `""`。
* `tags`: 如果 `组件元数据` 中提供了 `tags`,则使用该值;否则默认为 `""`。
* `keywords`: 如果 `组件元数据` 中提供了 `keywords`,则使用该值;否则默认为 `""`。
* `doc_url`: 如果 `组件元数据` 中提供了 `doc_url`,则使用该值;否则默认为 `""`。
* `devMode`: 固定为 `"proCode"`。
#### 2. **`npm` 对象构建**
根据 `package.json` 的内容,动态构建 `npm` 对象:
* `package`: 从 `package.json` 中读取 `name` 字段。
* `exportName`: **必须**与顶层 `component` 字段的值保持一致。
* `version`: 从 `package.json` 中读取 `version` 字段。
* `script`: 基于 `package.json` 的信息,拼接成固定格式:`"http://192.168.0.212:4874/{package}@{version}/js/web-component.mjs"`。
* `destructuring`: 固定为 `true`。
* `npmrc`:
1. 从 `package.json` 的 `name` 字段提取 scope (例如 `@dcp/component-library` -> `@dcp`)。
2. 从 `package.json` 的 `publishConfig.registry` 字段提取 registry URL (并移除末尾的 `/`)。
3. 拼接成 `"{scope}:registry={registry_url}"` 的格式。
#### 3. **`configure` 对象构建**
生成完整的 `configure` 对象,包含以下所有字段:
**基础行为控制**:
* `loop`: 固定为 `true`(支持循环渲染)
* `condition`: 固定为 `true`(支持条件渲染)
* `styles`: 固定为 `true`(支持样式配置)
**组件类型标识**:
* `isContainer`: 根据组件分析决定:
- 如果组件模板中包含 `<slot>` 标签,设置为 `true`
- 如果组件名称暗示容器用途(如 Layout、Container、Wrapper),设置为 `true`
- 否则设置为 `false`
* `isModal`: 固定为 `false`(除非组件明确是模态框)
* `isPopper`: 固定为 `false`(除非组件明确是弹出框)
* `isNullNode`: 固定为 `false`
* `isLayout`: 根据组件用途判断,Layout 类组件设置为 `true`,否则为 `false`
**嵌套规则**:
* `nestingRule`: 对象包含以下字段,通常设置为默认值:
- `childWhitelist`: `""`(允许的子组件白名单,通常为空)
- `parentWhitelist`: `""`(允许的父组件白名单,通常为空)
- `descendantBlacklist`: `""`(禁止的后代组件黑名单,通常为空)
- `ancestorWhitelist`: `""`(允许的祖先组件白名单,通常为空)
**编辑器配置**:
* `rootSelector`: 固定为 `""`
* `shortcuts.properties`: 识别出组件最核心、最常用的 1-3 个 props,填入此数组
* `contextMenu`: 对象包含:
- `actions`: 默认为 `["copy", "remove", "insert", "updateAttr", "bindEevent"]`
- `disable`: 默认为 `[]`
**交互行为** (可选字段,根据组件类型添加):
* `clickCapture`: 对于按钮类、交互类组件设置为 `true`,其他组件可省略或设置为 `false`
* `framework`: 如果是第三方组件库保持原值,自定义组件设置为 `"Vue"`
#### 4. **`schema.properties` (Props 分组映射)**
将 Vue 组件的所有 props 按逻辑功能分组,生成一个**分组数组**:
**分组策略**:
* **基础属性**: 核心功能相关的 props(如 name、size、type 等)
* **样式属性**: 外观、颜色、尺寸相关的 props(如 width、height、backgroundColor、color 等)
* **行为属性**: 交互、事件、状态相关的 props(如 disabled、loading、onClick 等)
* **高级属性**: 可选的、专业配置项(如复杂对象配置、高级选项等)
**每个分组对象必须包含**:
* `name`: 分组标识符,使用数字字符串(如 `"0"`, `"1"`, `"2"`)
* `label.zh_CN`: 分组的中文显示名称(如 `"基础属性"`, `"样式属性"`)
* `description.zh_CN`: 分组的中文描述
* `content`: 该分组下的具体属性配置数组
**`content` 数组中的每个属性对象必须包含以下固定字段**:
* `property`: Prop 的名称
* `label.text.zh_CN`: 中文标签
* `description`: 中文描述
* `required`: 根据 Vue Prop 中的 `required` 字段决定,默认为 `false`
* `readOnly`: 固定为 `false`
* `disabled`: **固定为 `false`**
* `cols`: **固定为 `12`**
* `labelPosition`: 固定为 `"left"`
* `type`: Vue 类型转换为小写字符串
* `defaultValue`: Vue Prop 的默认值
* `widget`: 根据以下规则推断
**Widget 推断规则 (按优先级顺序)**:
1. **validator 函数解析 (最高优先级)**:
- 如果 Prop 定义中存在 `validator` 函数,解析函数体中的选项数组
- 设置 `widget.component` 为 `"SelectConfigurator"`
- 设置 `widget.props.options` 为解析出的选项数组
2. **属性名称模式匹配**:
- 名称包含 `color` 或默认值以 `#` 开头 -> `"ColorConfigurator"`,props: `{}`
- 名称包含 `icon` -> `"InputConfigurator"`,props: `{ "placeholder": "请输入图标名称" }`
3. **Vue 类型 + 语义推断**:
- `Boolean` 类型:
- 开关语义 (show*, enable*, is*) -> `"SwitchConfigurator"`,props: `{}`
- 选项语义 (disabled, loading, plain, round, circle) -> `"CheckBoxConfigurator"`,props: `{}`
- `Number` 类型 -> `"NumberConfigurator"`,根据属性名称设置 props:
- 尺寸类 (width, height, size): `{ "min": 50, "max": 2000, "step": 10 }`
- 角度类 (rotate, angle): `{ "min": 0, "max": 360, "step": 1 }`
- 比例类 (scale): `{ "min": 0.1, "max": 5, "step": 0.1 }`
- 时间类 (duration, delay): `{ "min": 0, "max": 50, "step": 0.1 }`
- 默认: `{ "step": 1 }`
- `String` 类型 -> `"InputConfigurator"`,props: `{ "placeholder": "请输入..." }`
- `Object`/`Array` 类型 -> `"CodeConfigurator"`,props: `{ "language": "json", "height": 150 }`
4. **智能类型分析**:
- 如果 Prop 类型为 `Array as PropType<someinterface[]>`,在 `description` 中补充接口结构信息
#### 5. **`schema.events` (事件映射)**
* 在组件 `<script>` 中搜索所有 `emit('event-name', ...)` 的调用。
* 每一个 `event-name` (kebab-case) 都对应 `events` 对象中的一个键。
* 该键的命名规则为 **`'on' +` 将 `event-name` 转换为首字母大写的驼峰式 (CamelCase)**。例如,`emit('menu-item-click')` 映射为 `onMenuItemClick`。
* 分析 `emit` 的参数,为该事件生成 `functionInfo.params` 数组。
#### 6. **`schema.slots` (插槽分析)**
* 扫描组件的 `<template>` 部分,寻找所有 `<slot>` 标签。
* 对于每一个**具名插槽** (例如 `<slot name="menu-items">`),在 `schema.slots` 对象中为其添加一个条目。
* 该条目的键为插槽名 (`menu-items`),值为一个包含 `label.zh_CN` 和 `description.zh_CN` 的对象,用于描述该插槽的用途。
#### 7. **`snippets` (智能代码片段生成)**
* 生成一个只包含**单个默认 Snippet** 的数组 `[]`。
* `name.zh_CN`, `icon`, `snippetName` 与顶层字段保持一致。
* `schema.props`:
* **优先策略**: 在工作区中查找与组件同名的 `.stories.ts` 文件。如果找到,请使用其 `args` 对象作为 `props` 的数据来源。
* **备用策略**: 如果找不到 Storybook 文件,请不要简单地使用 `defaultValue`。应根据每个 Prop 的语境,创建一组有意义、更具代表性的示例值(例如,`username` 使用 `'Admin'` 而不是 `'User'`)。
---
### 第三步:输出最终 JSON
请整合以上所有分析结果,生成最终的 JSON 文件。
实战指南:从理论到实践
第一步:智能生成Schema
运用AI生成schema的过程就像与一位专业顾问的对话。让我们以Cursor编辑器为例,一步步地体验这个过程:
操作步骤:
- 打开组件项目:启动Cursor,进入你的Vue组件库项目
- 准备上下文材料:在聊天界面中添加以下关键文件:
- 项目根目录的
package.json
- 目标组件的
.vue
源码文件
- 项目根目录的
- 粘贴Prompt:将上文提供的完整Prompt复制到输入框
- 发起对话:按下回车键,等待AI的魔法发生
为什么选择Claude Sonnet 4?
经过测试对比,我发现Claude Sonnet 4在理解Vue组件结构和生成精准schema方面表现出色,像deepseek、qwen这些国内模型暂时无法:
- 精准的类型推断:能够正确识别Props的TypeScript类型并转换为对应的widget配置
- 智能的语义理解:根据属性名称自动推断合适的编辑器组件(颜色选择器、数字输入框等)
- 结构化输出:生成的JSON格式规范、完整,直接可用
实际效果展示
设想我们有一个名为CustomButton
的Vue组件:
<template>
<button
:class="buttonClass"
:disabled="disabled"
@click="handleClick"
>
<slot>{{ label }}</slot>
</button>
</template>
<script setup lang="ts">
import { computed } from 'vue'
interface Props {
label?: string
type?: 'primary' | 'secondary' | 'danger'
size?: 'small' | 'medium' | 'large'
disabled?: boolean
loading?: boolean
}
const props = withDefaults(defineProps<Props>(), {
label: '按钮',
type: 'primary',
size: 'medium',
disabled: false,
loading: false
})
const emit = defineEmits<{
click: [event: MouseEvent]
}>()
const handleClick = (event: MouseEvent) => {
if (!props.disabled && !props.loading) {
emit('click', event)
}
}
</script>
通过AI处理后,会生成一个包含以下关键信息的schema:
- Props分组:label、type、size被分类为“基础属性”,disabled、loading分类为“行为属性”
- 组件类型:type属性自动配置为下拉选择器,并包含正确的选项
- 事件映射:click事件被正确转换为onClick处理函数
第二步:添加物料
把上一步得到的json文件保存到TinyEngine项目根目录下的materials/components
文件夹下。
第三步:添加仓库
如果你没有添加过仓库配置,那么你需要在 项目根目录下的materials/packages.json
文件中添加你的仓库信息:
{
"name": "DCP组件库",
"package": "@dcp/component-library",
"version": "0.0.60",
"script": "http://192.168.0.212:4874/@dcp/component-library@0.0.60/js/web-component.mjs",
"destructuring": true,
"npmrc": "@dcp:registry=http://192.168.0.212:4873"
}
第四步:构建物料
现在你可以直接在终端执行pnpm buildMaterials
,等终端不再有新的输出时,可以ctrl + c
退出脚本。
一切就绪,启动项目验收
至此,你可以pnpm serve:frontend
后访问项目来使用新增的组件了。
问题排查指南
在实际开发过程中,即使按照上述步骤操作,也可能遇到组件未正常显示的情况,这里为您提供了系统化的排查方法。
关键检查点一:组件获取机制
首先检查TinyEngine的组件获取机制是否正常工作。在以下位置添加调试信息:
文件位置: packages/canvas/render/src/material-function/material-getter.ts#L109
export const getComponent = (name) => {
// 添加调试信息
console.log(`正在获取组件: ${name}:${getNative(name)}`)
const result = Mapper[name] || getNative(name) || getBlock(name) || (isHTMLTag(name) ? name : getBlockComponent(name))
console.log(`获取结果:`, result)
return result
}
ℹ️ 排查要点:对于引入的组件,getNative(name)
应该返回非空值。如果返回undefined
,说明组件没有被正确注册。
关键检查点二:动态导入机制
如果上一步检查发现问题,接下来排查动态导入机制。在以下位置添加调试代码:
文件位置: packages/canvas/common/src/utils.ts#L100
const dynamicImportComponentLib = async ({ pkg, script }: DynamicImportParams): Promise<any> => {
console.log(`开始导入组件库: ${pkg}`)
if (window.TinyComponentLibs[pkg]) {
console.log(`组件库已存在缓存中: ${pkg}`)
return window.TinyComponentLibs[pkg]
}
if (!script) {
console.warn(`组件库 ${pkg} 缺少 script 配置`)
return {}
}
const href = window.parent.location.href || location.href
const scriptUrl = script.startsWith('.') ? new URL(script, href).href : script
console.log(`动态导入组件库脚本: ${scriptUrl}`)
if (!window.TinyComponentLibs[pkg]) {
try {
const modules = await import(/* @vite-ignore */ scriptUrl)
console.log(`组件库导入成功:`, modules)
window.TinyComponentLibs[pkg] = modules
} catch (error) {
console.error(`组件库导入失败: ${pkg}`, error)
return {}
}
}
return window.TinyComponentLibs[pkg]
}
常见问题及解决方案
📌 最常见问题:组件未正确导出
在我的实际经验中,这个问题出现频率最高。很多时候我们在组件库中开发了新组件,但忘记在入口文件中导出它。
✅ 解决方案:
- 检查组件库的
index.ts
或index.js
文件 - 确认目标组件已经被正确导出:
export { default as YourComponent } from './YourComponent.vue'
📌 脚本路径错误
检查packages.json
中的script
字段是否正确。常见错误包括:
- 版本号不匹配
- 域名或端口错误
- 文件路径错误
通过以上系统化的排查步骤,绝大多数问题都能够得到快速解决。
如果您对AI开发感兴趣,欢迎关注我的公众号:观默视界
在这里,我会分享更多关于AI技术在实际开发中的实战经验和最新趋势。
关于OpenTiny
欢迎加入 OpenTiny 开源社区。添加微信小助手:opentiny-official 一起参与交流前端技术~
OpenTiny 官网:
如果你也想要共建,可以进入代码仓库,找到 good first issue标签,一起参与开源贡献~
</div>
维权提醒:如果你或身边的朋友近五年内因投顾公司虚假宣传、诱导交费导致亏损,别放弃!立即联系小羊维权(158 2783 9931,微信同号),专业团队帮你讨回公道! 📞立即免费咨询退费