近日,Flutter 又提出了一個新的提案:為 Flutter 增加一個標準原語:ComponentLibrary。
ComponentLibrary 的主要想法是:為 Flutter 提供一個統一的「元件註冊表/元件目錄資料協議」,讓 GenUI、Stac、Widgetbook、OpenUI 這類工具可以用同一套方式發現、描述、呼叫元件。
前面我們應該聊過 GenUI,作為一個在執行階段由 Agent 動態生成 UI 的 lib,它支援的 A2UI 協議所生成的是一套與框架無關的 UI 中繼資料,而 Flutter 需要針對這份協議資料做 GenUI 實作。本來其實也沒什麼問題,但架不住現在場景不只 GenUI:
場景它需要什麼
GenUI
AI 需要知道有哪些元件、元件 ID 是什麼、怎麼傳參數
Server-driven UI,例如 Stac
伺服器端 payload 需要對應到 Flutter widget
Widgetbook / showcase 工具
需要枚舉元件,生成元件目錄和 playground
自動化測試 / storyboarding 工具
需要知道元件參數、範例、可變狀態
但目前這些工具都需要在專案裡做自己的自訂支援:
json_schema 解析執行json_schema 處理參數所以如果同一個企業設計系統想同時接 GenUI、Stac、Widgetbook、自動化測試,就得寫多套重複的 wrapper / registry / adapter,這就造成生態割裂:
同一批元件,需要不同工具用不同結構描述,但對 AI 時代來說,這種結構化能力本身就是必須的。
不得不說,這段時間在做 AI e2e 專案測試時確實很煩,Compose、iOS 和鴻蒙都沒有標準的 MCP 注入支援,像鴻蒙和 Android 都是 adb 和 hdc 注入加截圖辨識來取得點位,效率和效能都太難受了。Flutter 能走 MCP 執行,相對效率還好一點點,但是對 AI 仍然不夠友善。
而且隨著 Material / Cupertino 逐步解耦落地,Flutter 的整體設計風格會越來越中立,所以 Flutter core 未來應該會更傾向設計系統無關的方向,不把某個具體設計系統深度綁在核心層裡,而是:
「這個 App / 公司 / package 有一組元件,它們叫什麼、怎麼建構、怎麼被工具發現。」
這就是 ComponentLibrary 提議想要支援的場景,特別是 GenUI / SDUI 也在推動這個需求,因為傳統 Flutter 是開發者手寫 widget tree,像這樣:
scss 体验AI代码助手 代码解读复制代码Column(
children: [
Text(...),
ProfileCard(...),
],
)
但是在 GenUI / SDUI 裡,widget tree 可能來自:
json 体验AI代码助手 代码解读复制代码{
"type": "cards.profile",
"args": {
"name": "Asher",
"avatarUrl": "..."
}
}
也可能來自其他 LLM tool calling 或工具渲染,這時候系統就必須知道:
cards.profile對應哪個 Flutter widget,參數怎麼傳,建構失敗怎麼報錯。
所以 ComponentLibrary 最重要的就是提供一個統一的元件庫契約,讓這些工具可以共用。
對此,Flutter 官方這次主要設計了兩個類:Component 和 ComponentLibrary,其中 Component 主要用於描述單個元件,一個 Component 大概包含:
dart 体验AI代码助手 代码解读复制代码@immutable
class Component {
final String id;
final String description;
final Widget Function(
BuildContext context,
Map<String, dynamic> arguments,
) builder;
}
也就是:
| 字段 | 作用 |
|---|---|
id |
元件唯一識別,例如 text.primary、cards.profile |
description |
給 AI / 工具 / 文件看的語意描述 |
builder |
真正把參數轉換成 Flutter Widget 的函式 |
例如下面程式碼,Component 就是 widget 的「註冊描述和建構入口」,這一套感覺就是從 GenUI 拆出來的既視感:
php 体验AI代码助手 代码解读复制代码Component(
id: 'cards.profile',
description: 'Displays user details alongside avatar layouts within standard dashboard feeds.',
builder: (context, args) {
return ProfileCard(
displayName: args['name'] as String,
avatarUrl: args['avatarUrl'] as String?,
);
},
)
而 ComponentLibrary 主要是管理一套元件的概念,大概如下:
dart 体验AI代码助手 代码解读复制代码@immutable
class ComponentLibrary {
final String name;
final Map<String, Component> components;
const ComponentLibrary({
required this.name,
required this.components,
});
factory ComponentLibrary.compose(
String name,
List<ComponentLibrary> libraries,
) {
final aggregated = <String, Component>{};
for (final lib in libraries) {
aggregated.addAll(lib.components);
}
return ComponentLibrary(name: name, components: aggregated);
}
Widget buildComponent(
BuildContext context,
String componentId,
Map<String, dynamic> arguments,
) {
final component = components[componentId];
if (component == null) {
throw FlutterError.fromParts(<DiagnosticsNode>[
ErrorSummary('Component resolution failed within library: $name'),
ErrorDescription('The componentId "$componentId" was not registered.'),
]);
}
return component.builder(context, arguments);
}
}
ComponentLibrary 主要做三件事:
ComponentLibrary.compose(...),例如可以把 coreDesignSystem、adminDashboardComponents、marketingComponents 合成一個總庫componentId 建構 widget,如果找不到元件,就拋出 Flutter 風格的 FlutterErrordart 体验AI代码助手 代码解读复制代码library.buildComponent(
context,
'cards.profile',
{
'name': 'Asher',
'avatarUrl': '...',
},
)
例如對應 App 企業的設計系統可以統一註冊,例如:
php 体验AI代码助手 代码解读复制代码final acmeDesignSystem = ComponentLibrary(
name: 'AcmeCorporate',
components: [
Component(
id: 'text.primary',
description: 'Primary body text used when text is information or descriptive...',
builder: (context, args) {
return AcmeText(
content: args['content'] as String,
);
},
),
Component(
id: 'cards.profile',
description: 'Displays user details alongside avatar layouts within standard dashboard feeds.',
builder: (context, args) {
return ProfileCard(
displayName: args['name'] as String,
avatarUrl: args['avatarUrl'] as String?,
);
},
),
],
);
這個方向就是,設計系統的維護者把元件集中宣告出來,形成一個標準 library。
另一個方向就是給 GenUI / Server-driven UI 用,例如這種場景通常需要:
javascript 体验AI代码助手 代码解读复制代码Widget buildDynamicNode(
BuildContext context,
Map<String, dynamic> remotePayload,
) {
return Renderer(
context,
library: acmeDesignSystem,
payload: remotePayload,
);
}
也就是伺服器或 AI 回傳 payload,Renderer 根據 payload 裡的 component id 去 ComponentLibrary 裡找對應元件,不過 Flutter 不負責做這個 Renderer,也不負責定義 JSON 協議,Flutter 只提供元件庫結構。
另一個場景就是給 Widgetbook 之類的專案使用,例如:
csharp 体验AI代码助手 代码解读复制代码Widget buildDevelopmentPlayground() {
return Widgetbook.fromLibrary(
library: acmeDesignSystem,
);
}
也就是說,Widgetbook 這類工具可以直接讀取 ComponentLibrary,自動生成側邊欄、元件目錄、參數面板、預覽頁面。以前每個工具都要自己定義,以後理論上可以統一消費 ComponentLibrary。
當然,ComponentLibrary 的目標是抽象出一套 Flutter 統一的控制項描述協議,但它不做完整的鏈路,因為這個設計本身不是 Remote UI Parsing Engine:
Component 不改變 rendering tree、layout behavior 和 semantics nodesComponent.description 和 ComponentLibrary.name 主要是開發期、除錯、工具索引用的,不要求 runtime localization,真正使用者可見的多語文案,還是要在 builder 裡實作當然,目前這個設計還處於草稿階段,一些問題還需要後續解決,例如:
InheritedComponentLibrary,提供類似 Theme.of(context) 的能力還沒定所以 ComponentLibrary 的核心是暴露一個乾淨、獨立的註冊協議,第三方庫可以漸進式採用,不會破壞既有設計,同時又把元件設計支援提升成 Flutter framework 的統一實作,這在 AI 時代來說還蠻有用的。
就是不知道下一個版本能不能落地。
flutter.dev/go/introduce-component-library