社区版 Dart macros?一个可以解决 JSON 序列化的Fluter “宏编程”第三方包

有点意思,社区最近发布了一个叫 dmx 的第三方包,主要是做 Dart 源码内联的 Code Generator,而且和 Freezed / json_serializable / build_runner 走的不同路线,简单来说就是:保存 Dart 文件时直接生成代码,同时把生成结果写回当前 .dart 文件的 class 里面。

它定位自己叫做 "Dart macros",但这里的 macro 不是 Dart 编译器提供的语言级 Macro ,项目提供的 @dmx('model') 本质也只是一个普通 annotation,主要靠的还是外部的 dmx generator。

比如比如平时 Flutter 写 mode 大概类似:

dart 复制代码
class User {
  const User({
    required this.id,
    required this.name,
    this.email,
  });
​
​
  final String id;
  final String name;
  final String? email;
}

而常见的 Freezed 做法是定义注解,然后 dart run build_runner build

scala 复制代码
@freezed
class User with _$User {
  ...
}

然后 dmx 的想法就简单粗暴很多,比如下面的注解,你只需要 Ctrl+S,然后它自己就直接生成或者修改 user.dart

kotlin 复制代码
import 'package:dmx/dmx.dart';
​
​
@dmx('model')
class User {
  const User({
    required this.id,
    required this.name,
    this.email,
  });
​
​
  final String id;
  final String name;
  final String? email;
}

最有意思的是什么?生成的代码就在你的 class 里面 ,没有比如 part 'user.g.dart'; 之类的东西,也不用每次 build_runner watch,直接就是 "Generated on save " :

所以它最有意思的就是「内联 Codegen」,dmx 做的事情大概类似:

所以 dmx 主生成器是 Rust 写的 ,VS Code 插件里面直接带着对应平台的 dmx binary,你安装插件之后,打开 Dart workspace,它自动启动 watcher,watcher 看到 profile.dart changed 就只重新处理对应文件。

然后 dmx 内部会用 tree-sitter 解析 Dart ,它没有拿 regex 搜类似 class xxx { ,dmx 用的是 tree-sitter-dart,构造一个 lossless Concrete Syntax Tree,也就是可以知道:

class body 在哪,是什么 field,是什么 annotation,已经 //#region 到底属于哪个 class。

而且 dmx 的 spec 专门要求 comment token 和 class-body span 必须可靠。

然后 Rust 会把 Dart class 转成生成器能理解的数据,比如:

arduino 复制代码
class User {
  final String id;
  final String? email;
}

dmx 会解析成类似:

ini 复制代码
className = User
​
​
fields:
  id:
    type = String
    nullable = false
​
​
  email:
    type = String?
    nullable = true

随后 Rust 会进一步算好:

复制代码
decodeExpr
encodeExpr
equalsExpr
hashExpr
copyParam
copyArg

然后复杂的 Dart 类型判断放 Rust 里完成,Mustache template 只负责"长什么样",所以模板可能只是:

dart 复制代码
@override
bool operator ==(Object other) =>
    identical(this, other) ||
    (other is {{className}}
{{#fields}}
      && {{equalsExpr}}
{{/fields}}
    );

equalsExpr 是通过 Rust 算好的,然后用 Mustache 生成 Dart 。

目前 dmx 自带 11 个 generator:

modelunionenumdifflerpvalidatetablerouteclifakerestClient

例如:

  • @dmx('model') 就走 model template
  • @dmx('table') 可以生成 SQL schema、binding、row conversion
  • @dmx('restClient') 可以生成 REST client implementation

所以它的目标是做一个通用 Dart codegen framework。**

而且你自己也可以写「Macro」,这也是最有意思的地方,比如你可以自己写:

scala 复制代码
final class AuditMacro extends DmxMacro {
​
​
  @override
  String get name => 'audit';
​
​
  @override
  DmxOutput expand(DmxInvocation invocation) {
    final name = invocation.declaration.name;
​
​
    return DmxFragment(
      "String get auditLabel => '$name';",
    );
  }
}

然后在项目里写:

kotlin 复制代码
@dmx('audit')
class Order {
  final int id;
}

保存以后就可以得到:

dart 复制代码
@dmx('audit')
class Order {
  final int id;
​
​
  //#region
​
​
  String get auditLabel => 'Order';
​
​
  //#endregion
}

所以这里所谓「Dart Custom Macro」其实就是用用 Dart 编写的外部 source generator plugin

而这样的话,其实就能干很多很邪门的东西了,比如 SQLite,Macro 可以直接读 SQLite database schema,然后我们:

kotlin 复制代码
@dmx('sqliteSchema')
class ProductRow {}

这样就可以根据真实数据库里的:

sql 复制代码
products
 ├ id INTEGER
 ├ name TEXT
 └ price REAL

直接生成:

arduino 复制代码
class ProductRow {
  final int id;
  final String name;
  final double price;
​
​
  ...
}

或者读取项目里的 openapi.json,然后分析一些列参数:

php 复制代码
paths
schemas
$ref
nullable
array
response

然后直接生成:

  • API Client
  • Model
  • JSON codec

甚至可以处理 OpenAPI 里没名字的结构。同时给它取 Dart class name,也就是这时候 dmx 已经有点接近:

diff 复制代码
source_gen
+
build_runner
+
模板引擎
+
IDE watcher
+
source rewriter

整体来看,我感觉这个比 Flutter 搞 runner 舒服好多,角度也很不错,最近社区的想法确实比官方有意思多了,不过问题来了,现在貌似大家都不打开 IDE 了,貌似对 JSON 这种输出场景的需求也不多了,感觉这个项目还是来的有点晚了。

不过就算不开 VSCode 实际项目也可以用, dmx 本身有独立 CLI ,比如 :

css 复制代码
dmx build [PATHS...] [--insert-regions] [--check]
dmx watch [PATHS...]

所以 AI 场景也能用,不过是不是需要就看你个人的想法了。

链接

github.com/Nimblesite/...

相关推荐
tsumikistep42 分钟前
【前端】Vue + Axios 跨域问题实战:7070 代理转发 8080 + 401 报错分析(黑马外卖)
前端·javascript·vue.js
码农阿豪1 小时前
我把 OpenClaw 装进旧安卓手机后,又折腾了 3 天:飞书、ADB、SSH 全部打通
android·智能手机·飞书
lichenyang4531 小时前
鸿蒙首页从等高商品 Grid 到双列瀑布流:同一张图也能做出小红书式浏览节奏
前端
里欧跑得慢1 小时前
Flutter主题与样式详解
前端·css·flutter·web
计算机魔术师1 小时前
TPU推理性价比翻50%,NVIDIA的护城河要见底了?
前端
IT_陈寒1 小时前
Python的多线程就是个假把式,我算是体验到了
前端·人工智能·后端
有什么事2 小时前
把安卓搬上云端:云手机系统改造与内核优化拆解
android·智能手机
OpenTiny社区2 小时前
【直播分享】GenUI SDK 技术公开课第二讲 | GenuiChat 核心配置深度解析
前端·github
计算机魔术师2 小时前
kernel.org 每天 600 万次请求,98% 不是人点的
前端
今日无bug2 小时前
从0到1手撕流式输出:Vue3 + Vite 实现 LLM 流式响应全解析
前端·vue.js·vite