Budibase 客户端组件清单(manifest.json)深度解析:builder 与 client 之间的组件契约
【免费下载链接】budibaseAI agents, automations and apps that run your operations. Model agnostic.项目地址: https://gitcode.com/GitHub_Trending/bu/budibase
导读:本文以 Budibase 开源仓库 packages/client/README.md 为骨架,系统讲解客户端库中
manifest.json的作用与结构——它是 builder(构建器)与 client(客户端运行时)之间定义组件元数据的核心契约。读完本文,你将掌握组件定义(Component Definitions)与设置定义(Settings Definitions)的每一个字段语义、类型系统的合法取值与典型用法,并理解 manifest 如何在构建器面板、组件树、运行时渲染与插件机制中被消费,为二次开发自定义组件提供可直接落地的参照。
一、manifest 是什么:连接 builder 与 client 的组件契约
Budibase 是一个低代码平台,应用由“屏幕”(Screen)与“组件树”(Component Tree)构成。在设计期,构建器(builder)负责展示组件、配置属性、搭建页面;在运行期,客户端库(client)负责按组件树实际渲染页面。两者之间共享的、关于“有哪些组件、每个组件长什么样、可以配置什么”的知识,就统一存放在packages/client/manifest.json中。
按照 packages/client/README.md 的官方定义:
manifest.json导出当前版本客户端库中所有可用组件的定义。manifest 被 builder 用来正确展示组件及其设置,并知道如何与它们正确交互。
换句话说,manifest 是一份契约:组件作者在此声明元数据,builder 据此生成属性面板、组件列表与拖拽行为,client 据此查找组件定义并实例化渲染。该文件在仓库中实际存在且体量庞大(约一万行),除了组件与设置定义外,还额外包含features(能力开关)、typeSupportPresets(类型支持预设)等区块,是理解 Budibase 前端架构的枢纽文件。
组件定义在运行时如何被读取?见 packages/client/src/stores/components.js 中的getComponentDefinition:内置组件统一以@budibase/standard-components/为前缀(见同文件BudibasePrefix常量),去掉前缀后直接在Manifest中按组件名取值:
// packages/client/src/stores/components.js export const BudibasePrefix = "@budibase/standard-components/" const getComponentDefinition = type => { // ... if (type.startsWith(BudibasePrefix)) { type = type.replace(BudibasePrefix, "") return type ? Manifest[type] : null } // 自定义组件走 customComponentManifest const { customComponentManifest } = get(store) return customComponentManifest?.[type]?.schema?.schema }可以看到,manifest 以“组件名”为键的顶层对象结构,与 README 中“对象键是组件名”的说明完全对应;同时客户端区分了内置组件(查 manifest)与插件自定义组件(查运行期注册的customComponentManifest)两条查找路径,这一点在理解 manifest 的边界时非常关键。
二、组件定义(Component Definitions):决定组件“长什么样、能装什么”
2.1 结构总览
manifest 的顶层是组件定义对象,对象键即组件名(与 index.ts 导出的组件同名),每个组件定义的字段如下:
| 字段 | 含义 | 说明 |
|---|---|---|
name | builder 中显示的组件名称 | 展示用名称 |
description | 组件描述 | README 注明“当前未使用” |
icon | builder 中显示的图标 | 在组件列表、拖拽控件中展示 |
hasChildren | 是否接受子组件 | 决定组件能否作为容器嵌套其他组件 |
styleable | 是否接受设计属性(设计样式 props) | 决定是否暴露样式配置 |
dataProvider | 是否提供数据上下文 | 决定该组件是否为子组件提供数据 |
bindable | 是否提供可绑定值 | 决定组件是否可作为绑定来源 |
settings | builder 中显示的设置项数组 | 每个元素是一条设置定义 |
2.2 从真实 manifest 看字段形态
以 packages/client/manifest.json 中的layout组件为例,可以看到一个典型组件定义的完整写法:
{ "name": "Layout", "description": "This component is specific only to layouts", "icon": "columns", "hasChildren": true, "styles": ["padding", "background"], "settings": [ { "type": "text", "label": "Logo URL", "key": "logoUrl" }, { "type": "text", "label": "Title", "key": "title" }, { "type": "select", "label": "Navigation", "key": "navigation", "options": ["Top", "Left", "None"], "defaultValue": "Top" }, { "type": "select", "label": "Width", "key": "width", "options": ["Small", "Medium", "Large", "Max"], "defaultValue": "Large" }, { "type": "navigation", "label": "Links", "key": "links" }, { "type": "boolean", "label": "Hide title", "key": "hideTitle", "defaultValue": false }, { "type": "boolean", "label": "Hide logo", "key": "hideLogo", "defaultValue": false }, { "type": "boolean", "label": "Sticky header", "key": "sticky", "defaultValue": false } ] }注意layout使用了styles数组(["padding", "background"]),而 README 中对应概念叫styleable——从源码结构看,这两者是一对关联表达:styleable决定组件“是否”接受设计属性,styles具体列出可配置的样式能力(如 padding、size、background、border、shadow 等)。例如同文件中的container组件就声明了"styles": ["padding", "size", "background", "border", "shadow"],而hasChildren: true表明它是一个可容纳子组件的容器。
container组件的设置项还展示了 README 之外、manifest 实际支持的高级字段(packages/client/manifest.json):
{ "type": "select", "label": "Layout", "key": "layout", "showInBar": true, "placeholder": false, "options": [ { "label": "Flex", "value": "flex" }, { "label": "Grid", "value": "grid" } ], "defaultValue": "grid" }, { "type": "select", "label": "Direction", "key": "direction", "showInBar": true, "barStyle": "buttons", "options": [ { "label": "Column", "value": "column", "barIcon": "rows-plus-bottom", "barTitle": "Column layout" }, { "label": "Row", "value": "row", "barIcon": "columns-plus-right", "barTitle": "Row layout" } ], "defaultValue": "column", "dependsOn": { "setting": "layout", "value": "grid", "invert": true } }这里出现的showInBar(是否在顶部工具条中直接显示)、barStyle(工具条按钮样式)、barIcon/barTitle(工具条图标与提示)、dependsOn(条件依赖:仅当layout的值满足条件时才显示)等字段,都是对 README 基础字段的重要补充,是属性面板实现“联动显隐”“快捷操作”能力的来源。用户在 builder 中切换 Container 的 Layout 时,Direction、对齐方式等设置项随之出现/隐藏,正是dependsOn在起作用。
2.3 组件分类与合法性约束
builder 侧的组件面板(Component Panel)通过 componentStructure.ts 定义分组(Blocks、Layout、Data、Basic、Form、Chart 等),再结合 manifest 中的定义做“合法子组件”过滤。在 NewComponentPanel.svelte 中可以看到,builder 依据组件定义中的legalDirectChildren与illegalChildren字段计算当前选中组件允许添加哪些子组件:
const definition = componentStore.getDefinition(component?._component) if (definition?.legalDirectChildren?.length) { allowedComponents = definition.legalDirectChildren.map(x => `@budibase/standard-components/${x}` ) } else { allowedComponents = Object.keys(allComponents) }getDefinition的底层实现正是上文提到的 manifest 查找(Manifest[type]),因此legalDirectChildren、illegalChildren也属于 manifest 组件定义中实际生效的字段。此外该面板还会处理sidepanel、modal等“任意位置可嵌套但实际渲染在顶层”的特殊组件(NewComponentPanel.svelte#L76-L86),这解释了为何 manifest 中这些组件往往带有hasChildren却又在树结构中处于特殊位置。
三、设置定义(Settings Definitions):属性面板的渲染指令
3.1type字段:决定 builder 用哪个控件
每个设置定义中的type字段是最关键的字段——builder 据此决定使用哪个 UI 控件来渲染该设置,因此必须正确填写。README 列出的合法取值如下:
| type 取值 | 渲染控件 | 补充说明 |
|---|---|---|
text | 文本输入框 | 最通用的字符串输入 |
select | 下拉选择框 | 需配合options字段提供选项 |
datasource | 数据源选择器 | 选择表(table)或视图(view)等数据源 |
boolean | 布尔开关 | 渲染为开关控件 |
number | 数字输入框 | 仅接受数值 |
detailURL | 行详情页 URL | 专用于“跳转到某行详情页”的网格类组件 |
README 特别强调detailURL的用途:它指向一个展示某行详细信息的页面 URL,只用于需要链接到行详情的网格(grid)组件。这是该类型的专属约束,其它类型的组件不应使用。
3.2 设置定义的全部字段
除type外,每条设置定义还可包含以下字段:
| 字段 | 含义 |
|---|---|
type | 字段类型,决定 builder 使用的渲染控件(见 3.1) |
key | 该设置在组件中的键名(即组件 props 上的属性名) |
label | builder 中显示的标签文本 |
defaultValue | 设置的默认值 |
placeholder | 输入控件的占位提示 |
以 manifest 中的真实设置为例(packages/client/manifest.json#L113-L160):
{ "type": "text", "label": "Logo URL", "key": "logoUrl" }, { "type": "select", "label": "Navigation", "key": "navigation", "options": ["Top", "Left", "None"], "defaultValue": "Top" }, { "type": "boolean", "label": "Sticky header", "key": "sticky", "defaultValue": false }可以看到:key与组件运行时读取的属性一一对应(logoUrl、navigation、sticky);select类型通过options数组给出候选值;defaultValue提供默认值;placeholder用于在输入控件为空时给出提示。前文 2.2 中提到的showInBar、barStyle、dependsOn等字段则是在这五个基础字段之上增强属性面板交互的扩展能力。
3.3 各类型在 manifest 中的实际分布
从 packages/client/manifest.json 全文来看,number类型被大量用于图表(Chart)与数值类组件的配置(如坐标轴、数据点、边界值等,可参见第 3009、3872、5157、5506 行附近的数字设置项),select类型常配合options提供枚举选择,datasource类型则出现在需要绑定表/视图的数据类组件中(例如附件上传组件通过datasourceId绑定 S3 数据源,见 manifest 第 6025-6026 行"label": "S3 datasource", "key": "datasourceId")。这些真实分布可以帮助组件开发者判断自己的场景应该选用哪种类型。
四、manifest 的发布与消费链路:从仓库文件到 builder 面板
4.1 打包与导出
manifest.json随客户端包一起发布:packages/client/package.json 在exports中显式导出了./manifest.json,并声明主入口为dist/budibase-client.js(module与main字段),Svelte 组件源码入口为src/index.ts。构建时 packages/client/vite.config.mjs 通过别名将manifest.json解析到实际文件位置:
find: "manifest.json", replacement: path.resolve("./manifest.json"),4.2 builder 侧的消费入口
builder 面板展示的组件结构来自getComponentStructure()(componentStructure.ts),随后在NewComponentPanel.svelte中经enrichStructure将分类结构里的组件名与componentStore.components(即 manifest 定义)合并,得到带名称、图标、合法子组件等完整信息的最终列表(NewComponentPanel.svelte#L115-L153):
const enrichStructure = (structure, definitions, customComponents) => { // 自定义组件单独放入 "Plugins" 分类 if (customComponents?.length) { /* ... */ } structure.forEach(item => { if (typeof item === "string") { const def = definitions[`@budibase/standard-components/${item}`] if (def) { enrichedStructure.push({ ...def, isCategory: false }) } } else { // 递归处理子分类 enrichedStructure.push({ ...item, isCategory: true, children: enrichStructure(...) }) } }) return enrichedStructure }同时面板还支持按名称与别名搜索(如text的别名是 headline、heading、title、paragraph、markdown,见 NewComponentPanel.svelte#L26-L28),并依据legalDirectChildren/illegalChildren过滤出当前上下文允许的组件。整个流程清晰印证了 README 的定位:manifest 定义“有什么组件、叫什么、能不能嵌套”,builder 负责“怎么展示、怎么过滤、怎么配置”。
4.3 client 侧的运行时查找
运行期,客户端通过getComponentDefinition(查定义)与getComponentConstructor(查构造函数,见 packages/client/src/stores/components.js#L135-L184)按名实例化组件。内置组件通过@budibase/standard-components/前缀从 manifest 定位,组件实现则从../components/app/index.ts动态加载并按需缓存;插件自定义组件通过registerCustomComponent在运行时注册到customComponentManifest,键名为plugin/${schema.schema.name},并支持对已挂载实例的即时重载(components.js#L190-L214)。这套机制表明:manifest 只描述内置组件,插件组件拥有自己独立的 schema 运行时清单,两者在 builder 面板中并列展示(插件归入 "Plugins" 分类)。
五、manifest 的扩展区块:features 与 typeSupportPresets
除了组件与设置定义,当前仓库的 manifest 还包含两个 README 未展开但值得开发者了解的区块(packages/client/manifest.json#L2-L106):
features:声明当前客户端版本启用的能力开关,例如spectrumThemes(Spectrum 主题)、unifiedTheme(统一主题)、intelligentLoading(智能加载)、deviceAwareness(设备感知)、state(状态)、customThemes(自定义主题)、devicePreview(设备预览)、messagePassing(消息传递)、rowSelection(行选择)、modal/sidePanel(弹窗与侧栏)、skeletonLoader(骨架屏)等。这些开关用于让 builder 判断当前 client 版本支持哪些交互与渲染能力。typeSupportPresets:定义“类型支持预设”,描述不同数据类型的支持程度。例如numberLike(数值型)完全支持number、boolean,对字符串、bigint、选项、公式等属于“部分支持”(会提示stringAsNumber),对 JSON 类型不支持(提示jsonPrimitivesOnly);datetimeLike(日期型)完全支持datetime,对字符串、选项、公式等部分支持(提示stringAsDate)。这一机制用于在 builder 中向开发者提示“字段类型与组件数据要求是否匹配”,并给出可理解的转译提示。
这两个区块的存在说明manifest.json已从“纯组件定义”演进为客户端能力与数据契约的综合声明文件。
六、面向组件开发者的实践要点
结合 README 与仓库源码,为需要在 Budibase 中新增或修改组件的开发者总结以下要点:
type必须准确:它是 builder 渲染属性控件的唯一依据(README 明确强调)。选错类型会导致属性面板无法正确编辑该属性。常用选择:普通文本用text,枚举用select(配options),开关用boolean,数值用number,绑数据源用datasource,行详情跳转仅网格组件用detailURL。key要与组件 props 对齐:设置面板写入的值最终会落到组件 props 上(如logoUrl、navigation、sticky),因此key必须与组件实现中的属性名严格一致。- 善用
options与defaultValue:select必须提供options;给所有设置提供合理的defaultValue,避免属性面板出现空值。 - 容器与数据提供者要声明清楚:
hasChildren、legalDirectChildren、illegalChildren决定组件能否嵌套以及可嵌套的子集;dataProvider、bindable、styles/styleable决定数据上下文、绑定能力与样式能力,直接影响 builder 的拖拽校验与设计器行为。 - 利用高级字段优化体验:
showInBar/barStyle/barIcon可把常用设置提到工具条上,dependsOn可实现设置项的联动显隐(如 Container 的布局切换),placeholder可改善空值提示。 - 区分内置与插件组件:内置组件定义写入
manifest.json并通过@budibase/standard-components/前缀访问;插件组件则通过运行时registerCustomComponent注册独立 schema,二者查找路径不同(见 components.js)。 - 验证 manifest 完整性:manifest 会被 builder 与 client 两端同时消费(builder 侧入口见 NewComponentPanel.svelte,client 侧见 components.js),任何组件新增都应确保
packages/client/manifest.json中定义与packages/client/src/components/app/index.ts中的实现一一对应,否则会出现“面板可见但运行时报加载器缺失”的问题(对应Component loader missing for ${name}告警)。
七、总结
packages/client/manifest.json是 Budibase 前端体系中连接设计期与运行期的关键契约:组件定义(Component Definitions)声明组件的展示名、图标、嵌套能力、数据能力与设置项;设置定义(Settings Definitions)通过type精确驱动 builder 属性面板的控件渲染;features与typeSupportPresets进一步声明客户端能力边界与数据类型支持关系。理解这份契约,既是深入 Budibase 低代码组件机制的门径,也是编写高质量自定义组件、保证组件在 builder 与 client 两端行为一致的基础。
参考文件索引
- 本文主体文档:packages/client/README.md
- 组件清单实体:packages/client/manifest.json
- 客户端运行时组件查找:packages/client/src/stores/components.js
- 客户端入口与 SDK 导出:packages/client/src/index.ts
- 客户端包导出配置:packages/client/package.json
- builder 组件面板:NewComponentPanel.svelte
- builder 组件分组结构:componentStructure.ts
【免费下载链接】budibaseAI agents, automations and apps that run your operations. Model agnostic.项目地址: https://gitcode.com/GitHub_Trending/bu/budibase
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考