# SpokeML **Repository Path**: nottyjay/spoke-ml ## Basic Information - **Project Name**: SpokeML - **Description**: An experimental XML-to-Flutter compiler with data binding, state observation, third-party widgets, and XML source-mapped diagnostics. - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: main - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-07-27 - **Last Updated**: 2026-07-31 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # SpokeML [English](README.md) SpokeML 是一个实验性的 XML-to-Flutter 编译器。它允许 Flutter 应用使用 XML 描述 Widget 树,同时继续使用 Dart 管理状态、业务逻辑和资源生命周期。 ```text layouts/account/login.xml │ build_runner ▼ lib/generated/account/login.g.dart ``` 生成结果是普通、可读且可进行类型检查的 Flutter 代码。发布后的应用不需要在运行时 解析 XML,也不依赖动态 Widget 解释器。 常见 Flutter 布局 Widget 以原生 XML 标签提供,包括 `Row`、`Column`、 `Stack`、`Positioned`、`Align`、`Container`、`Padding`、`Center` 和 `SizedBox`。 > **项目状态:** SpokeML 目前处于方案验证阶段,还不是生产就绪的软件包。代码生成、 > Source Map、布局分组、局部状态边界、生命周期和第三方 Widget 接入均已有可执行测试; > example 也已在 Android 与 macOS 上完成编译和启动验证。 ## 为什么使用 SpokeML? SpokeML 尝试为 Flutter 提供类似 Android Data Binding 的开发流程: - 声明式 Widget 结构只需编辑 `layouts/` 下的 XML; - 通过 `build_runner watch` 自动生成 `.g.dart`; - XML 输入目录和生成的 Dart 目录保持一一对应; - 使用有类型的 Dart 表达式,不引入运行时字符串表达式引擎; - 只重建动态区域,不要求整个页面都成为 StatefulWidget; - 通过 XML namespace 使用 Flutter 或第三方 Widget; - 将生成阶段和后续 Dart 分析错误定位回原始 XML。 SpokeML 不会替代 Flutter 的状态管理生态。Controller、网络请求、分页、重试、 表单验证和领域逻辑仍然使用普通 Dart 代码实现。 ## 环境要求 - Dart `^3.10.4` - Flutter `>=3.38.0` - `build_runner` 当前建议通过本地路径引用该项目: ```yaml # pubspec.yaml dependencies: flutter: sdk: flutter spoke_ml: path: ../SpokeML dev_dependencies: build_runner: ">=2.14.1 <2.15.0" ``` 在构建系统中包含顶层 `layouts/` 目录: ```yaml # build.yaml targets: $default: sources: - "$package$" - "lib/**" - "layouts/**" ``` ## 快速开始 创建 `layouts/profile.xml`: ```xml
``` 单次生成 Dart 代码: ```sh dart run build_runner build --delete-conflicting-outputs ``` 开发期间持续监听 XML 变化: ```sh dart run build_runner watch --delete-conflicting-outputs ``` SpokeML 会生成: ```text layouts/profile.xml lib/generated/profile.g.dart lib/generated/profile.g.dart.spoke.json ``` 像普通 Flutter Widget 一样使用生成的 View: ```dart import 'generated/profile.g.dart'; ProfileView(controller: profileController) ``` `.spoke.json` 是将生成 Dart 代码映射回 XML 的构建元数据,SpokeML 的错误重映射 工具会使用它。 ## 布局目录和分组 输入与输出路径始终一一对应: ```text layouts/profile.xml layouts/account/login.xml layouts/admin/login.xml lib/generated/profile.g.dart lib/generated/account/login.g.dart lib/generated/admin/login.g.dart ``` 因此,不同功能目录可以使用相同的 XML 文件名,而不需要把全部布局放进同一个生成 目录。每个布局仍然应该声明合适且不冲突的 Dart 类名。 移动、重命名或删除 XML 后,Dart 构建系统会同步更新其声明的生成输出。 ## 数据与表达式 声明生成 View 接收的每个数据: ```xml ``` 这会生成有类型的字段和构造参数: ```dart final CalendarController calendar; ``` 使用 `@{...}` 编写 Dart 表达式: ```xml ``` 早期原型可以省略 `type`,此时会生成 `dynamic`。但有类型的变量能提供更好的生成代码 分析能力,正式开发时应优先使用。 字符串、布尔值、数字、颜色、Insets、常用枚举和主题文字样式等简单 XML 值会自动 转换。复杂对象和回调应使用绑定表达式。 ## 静态页面与动态区域 默认生成的布局 View 是 `StatelessWidget`。页面整体可以保持静态,同时让一个或多个 小区域分别监听自身状态: ```xml ``` Controller 发出通知时,只有 `Observe` 内部的子树会重建。同一页面可以设置多个互不 影响的 `Observe` 节点。 ### Listenable ```xml ``` ### ValueListenable ```xml ``` ### Stream ```xml ``` 它们会分别生成局部的 `ListenableBuilder`、`ValueListenableBuilder` 和 `StreamBuilder` 边界。 使用 `visible` 隐藏但保留子树;使用 `if` 按条件创建和销毁子树: ```xml ``` ## 动态列表 SpokeML 提供构建 Widget 的通用机制,不实现具体分页方案。请求、游标、重试、去重和 滚动触发策略应由应用 Controller 或第三方组件负责。 命名 Builder slot 可以对接 Flutter 的懒加载 Widget: ```xml ``` Controller 追加商品并调用 `notifyListeners()` 后,只有列表区域重建, `ListView.builder` 会继续按需创建新的列表项。稳定的 Key 用于保持列表项身份。 对于直接生成 `children` 的集合,可以使用 `ForEach`: ```xml ``` ## 布局组合 使用 `Include` 组合其他生成布局。相对路径以当前 XML 所在目录为基准,数据作为构造 参数向下传递: ```xml ``` 被 Include 的布局会作为构建依赖进行跟踪,因此它发生变化时,依赖它的布局也会重新 生成。缺失或未知参数会在生成阶段被检查。 ## 资源生命周期 变量默认由外部持有,由 Dart 父级负责创建和释放: ```dart CalendarDemoView(calendar: calendarController) ``` 当布局明确声明自己持有资源时,SpokeML 会生成一个轻量的 `StatefulWidget` Host, 同时让真正负责渲染的 View 继续保持无状态: ```xml ``` 对于名为 `LoginView` 的布局,使用生成的 `LoginViewHost`。Host 创建 Controller, 将其传给 `LoginView`,并在销毁时调用 Controller 的 `dispose()`。如果对象没有 `dispose()` 方法,可以添加 `dispose="none"`。 这种职责分离让 View 生成保持可预测: ```text LoginViewHost(StatefulWidget:持有和释放资源) └── LoginView(StatelessWidget:负责 XML 渲染) ``` ## 第三方 Widget 将 XML namespace 前缀映射到 Dart library: ```xml ``` 应用的 `pubspec.yaml` 中也必须声明对应依赖。SpokeML 会导入 namespace 映射的 Dart library,并通过 analyzer 检查生成的构造调用。该能力已使用 [`table_calendar`](https://pub.dev/packages/table_calendar) 完成验证。 命名构造器或第三方组件需要位置参数时,使用连续的 `arg0`、`arg1` 属性: ```xml ``` SpokeML 会按编号在命名参数之前生成位置参数,并检查参数数量与编号连续性。 使用 `Slot` 传递 `title`、`body`、`leading` 等命名 Widget 参数: ```xml ``` ## 错误定位 当 Source Location 可用时,SpokeML 会将 XML 解析、Schema、构造函数、命名参数和 绑定错误定位到 XML 文件、行和列。 通过 SpokeML 执行 Dart 分析,可以把生成代码中的后续错误重映射回 XML: ```sh dart run spoke_ml analyze ``` 也可以只分析指定目录: ```sh dart run spoke_ml analyze lib/generated ``` 例如,生成 Dart 中的无效绑定可以被报告到其原始表达式: ```text ERROR|COMPILE_TIME_ERROR|ARGUMENT_TYPE_NOT_ASSIGNABLE| layouts/catalog/product_list.xml|10|36|10|... ``` Analyzer 的退出码保持不变。不存在 SpokeML Source Map 的诊断会原样输出。 Source Map 基于 Dart 构建工具实现,不依赖 Android 或 Apple 平台的运行时代码。因此 同一机制适用于 Android、iOS、macOS、Windows、Linux 和 Web 构建。 ## 单文件编译 不使用 `build_runner` 时,也可以单独编译一个布局: ```sh dart run spoke_ml layouts/profile.xml lib/generated/profile.g.dart ``` 省略输出路径时,生成的 Dart 代码会输出到 stdout。 ## 运行 Example 仓库中的可执行 Flutter example 展示了: - 第三方 `TableCalendar` Widget; - 有类型的 `ChangeNotifier` Controller; - 局部监听的动态商品列表; - 稳定列表项 Key 和懒加载 Builder; - 自持有 Controller 的生命周期 Host; - 一一对应的分组布局目录。 ```sh cd example dart run build_runner build --delete-conflicting-outputs flutter run ``` 运行测试: ```sh cd example flutter test ``` 构建平台应用: ```sh cd example flutter build apk --debug flutter build macos --debug ``` ## 当前能力范围 已实现: - 常用 Flutter 布局、文本、按钮和进度 Widget; - 字符串、数字、布尔值、枚举、颜色、Insets 和主题文字样式; - 有类型的数据变量和 Dart 绑定表达式; - 结构化 `if` 和不销毁状态的 `visible`; - 局部 `Listenable`、`ValueListenable` 和 Stream 监听; - 通用 `ForEach`、稳定 Key 和命名 Builder 回调; - 通过 `Include` 使用相对路径组合布局; - 外部持有和布局自持有两种资源生命周期; - 第三方 Dart package namespace; - 与 XML 一一对应的功能分组目录; - XML 文件、行、列和表达式范围诊断; - 基于 Analyzer 的构造函数与命名参数检查。 计划中或尚未完成: - 对所有回调和表达式进行完整的静态类型检查; - 覆盖所有 Flutter child 与 Builder slot 约定的丰富 Schema; - XML 编辑器/LSP 中的实时诊断和代码补全; - 更全面的内置 Widget 与值转换目录; - 覆盖更多 Flutter 版本的兼容性和 Golden Test。 ## 参与开发 提交修改前请运行: ```sh flutter test dart analyze cd example && flutter test ``` 生成的 `.g.dart` 被有意设计为可读代码。修改应保持正常 Flutter 语义,并避免把状态 管理策略放入编译器。 ## 许可证 参见 [LICENSE](LICENSE)。