Expo 团队发布 Exact 跨平台 UI 框架
Expo 团队推出 Exact,一套跨平台 UI 框架,同一份代码可原生运行于 Web、macOS、iOS 与 Linux,并专为 AI 编码智能体设计,使其能自动构建、运行、观察与测试。官方演示中,Claude Code 在约 57 分钟内完成一个待办应用并在三端测试通过,代码体积 26 KB,远小于 React 的 81 KB,热更新延迟约 99 毫秒。
报道正文
本文目录3 个章节
Exact
应用只写一次。它就能以原生方式运行在 Web、macOS、iOS 和 Linux 上,而 AI agent 可以在每一个平台上构建它、运行它、查看它并测试它。
<table> <tr> <th>Web</th> <th>iOS</th> <th>macOS</th> </tr> <tr valign="top"> <td><img src="docs/screenshots/softwear-web.webp" width="360" alt="Softwear 的连帽衫页面在 Chrome 中的显示"></td> <td><img src="docs/screenshots/softwear-ios.webp" width="160" alt="Softwear 的连帽衫页面作为原生 iOS 应用的显示"></td> <td><img src="docs/screenshots/softwear-macos.webp" width="290" alt="Softwear 的连帽衫页面作为原生 macOS 应用的显示"></td> </tr> <tr valign="top"> <td>浏览器自身的 DOM 和 CSS</td> <td>UIKit 视图、原生控件和系统标签栏</td> <td>AppKit 视图和原生控件</td> </tr> </table>[!TIP] 用编码 agent 试一试。 在一台已安装 Xcode、Rust、Bun 和 Chrome 的 Mac 上,把下面这段提示词交给 Claude Code:
TEXT Clone https://github.com/expo/exact and follow its README to make a new Exact app with `exact new`. Run `bun scripts/exact.mjs setup` once first. Make a todo list where I can add items, check them off, delete them, and see how many are left. Put the view in Contract and keep the list in `app.ts`. Write an `app.test.contract`, pass it on web, macOS, and the iOS Simulator with `scripts/agent.mjs`, then open the app for me on all three.在一个干净的克隆上,一个全新的 agent 拿到这段提示词的早期版本(Hermes 已在机器缓存中,Cargo 缓存已预热),大约 57 分钟就完成了,而且它的测试在三个平台上全部通过。其中约 25 分钟花在首次 macOS 和 iOS 构建上,各约 13 分钟;之后的构建只需一两分钟。在缺少 Hermes 的机器上,会先进行源码构建,从而增加耗时。当前的安装流程改为一次性下载并校验固定版本的 host/iOS bundle;日常构建则保持离线。
<sub>一个 <code>app.contract</code>,同一个界面在三个宿主上的呈现,由 agent 驱动捕获。Softwear 是一个演示商店,你可以从 exact-templates 生成它:它展示 Alternative Apparel 的商品目录,与该品牌并无关联,也不接受真实订单或付款。</sub>
Exact 是一个跨平台应用运行时。你用 Contract——一种小型声明式语言——来描述界面,用 TypeScript 或 Rust 编写数据逻辑。Contract 编译器会把界面转换成一个紧凑的计划。在 Web 上,这个计划会变成一个 ES 模块,运行在约 20 KB 的运行时之上,驱动真实的 DOM。在 macOS、iOS 和 Linux 上,一个 Rust runner 执行同一个计划,一个 Rust 布局内核用 CSS 规则计算每一个盒子,各平台再用自己的工具绘制:AppKit、UIKit 或 GPU 绘制器。UI 中没有 webview,也没有 JavaScript 桥接,并且在第一个像素出现之前不会运行任何应用 JavaScript。
[!NOTE] Exact 尚未发布 1.0,每天都在变化。按照设计,它没有 API 稳定性保证,不提供向后兼容,也没有迁移指南。
升级说明:Snapback 变更回复现在会以 ok: false 抛出 refused(why) 并暴露 failure(slot);snapback4 包的 README 中有前后对比和继承应用的命令。
三条原则
1. Agent 原生
Exact 假定大部分代码将由 AI agent 编写。agent 可以创建应用、运行它、查看它、操作它,并在无需人工介入的情况下证明它能正常工作。
- 十个操作,在每个宿主上完全一致。
tree · screenshot · tap · type · state · layout · logs · clock · prefer · perf通过同一个脚本scripts/agent.mjs驱动 Web、macOS、iOS(模拟器或真机 iPhone)和 Linux。之所以刻意定为十个:agent API 会一次一个“合理”地增加,直到没人能把它记在脑子里。 - 时钟归 agent 所有。 在两个操作之间,一切静止不动。agent 不必等到动画结束,而是说
clock +60000或clock settle,然后读取结果。没有时序抖动,没有等待。 - 测试就是这些操作组成的脚本。 一个
app.test.contract文件在任何宿主上都能原样运行,不需要第二个求值器。 - 每个工具都能与机器对话。
contract build --json给出带有稳定 id 和源码范围的诊断信息。contract symbols提供导航。开发用 source map 可以从屏幕上的任意节点追溯到声明它的那一行。驱动会拒绝驱动比其源码更旧的构建,并指出需要重新构建。 - Contract 刻意保持小巧。 整个应用都能放进一个上下文窗口。每次写入都是声明式的,每个列表都有 key,每个值都有类型。错误是说明该做什么的拒绝,而不是悄无声息的错误行为。
bun scripts/agent.mjs web tree "tap change-station" "type station-search Palo" \
"clock +60000" state "screenshot out.png"2. 速度至上
应用的速度和循环的速度。rules/RULES.md 中的每一项预算都会在每次提交时被跟踪,一旦出现回退就是 P0 级问题,并且会明确责任人。
| 预算 | 目标 |
|---|---|
| 冷启动到可交互首帧 | 100 ms p50 |
| 开发重启,从编辑到呈现 | 100 ms p50 |
| 首像素前执行的应用 JavaScript | 无 |
| 改动一行,重新构建该 crate | 30 s |
| 整个阻塞式检查套件 | 65 s |
设计如何保持这些数字:
- 启动路径不执行也不编译任何东西。 TypeScript 在构建时被编译为 Hermes 字节码,并在首像素之后加载。首帧来自预先烘焙进计划中的数据。
- 只为用到的部分付费。 GPU 渲染、Markdown 编辑器、文本流和浏览器 SQLite 都是独立的产物,按需加载。没有任何一项是核心 crate 上的功能开关。
- Web 拥有自己的小型运行时。 在 RealWorld(“Conduit”)基准测试中,Exact 的运行时在 React SSR 构建完成 hydration 之前约 300 ms 就已启动。它使用了 26 KB 的代码,而 React 为 81 KB(
apps/realworld中的 RealWorld 基准测试)。 - 编辑约在十分之一秒内生效。 在由
exact new创建的新应用中,保存的 Contract 或 TypeScript 编辑在 99 ms 内完成重建并进入页面。
(注:更多技术实现细节与完整 API 文档请查阅原项目 README)