MoonBit UI for mobile: Android, iOS and Web from one rabbita (TEA) app, rendered by React Native
用 MoonBit 写一次 UI,跑在 Android / iOS / Web。
| MoonBit 包 | moon add XiLaiTL/moobile@0.2.2 |
| 宿主(JS) | npm install moobile-host(React Native / Expo) |
| 已实测 | Web ✅ | Android 真机 ✅(Android 14 / x86_64) |
| 未实测 | iOS(宿主工程可生成,本机无法构建验证)| 桌面(未提供宿主) |
| 目标平台 | 只支持 js 目标(库本身编到 JS,再交给 React) |
| 许可 | Apache-2.0(内含 rabbita fork,见 THIRD-PARTY-NOTICE.md) |
# app/moon.mod
name = "you/myapp"
version = "0.1.0"
preferred_target = "js"
import {
"XiLaiTL/moobile@0.2.2",
}// app/moon.pkg
import {
"XiLaiTL/moobile" @moobile, // 库本体
"XiLaiTL/moobile/style", // 类型化样式(写视图的入口)
"XiLaiTL/moobile/html", // @html DSL
"XiLaiTL/moobile/cmd", // Cmd / Emit
"XiLaiTL/moobile/sub", // Sub(订阅:定时器、传感器…)
}
options(
link: {
"js": { "format": "esm", "exports": [ "app" ] },
},
)要用到的包就这五条。fork 住在 vendor/rabbita/,但使用者看不到那一层: 根上的 html/ cmd/ sub/ http/ 是转发包(由 tools/gen_forwarders.py 生成), 所以 XiLaiTL/moobile/html 这种短路径照旧可用。
fn view(m : Model, emit : @cmd.Emit[Msg]) -> @html.Html {
@html.div(
attrs=@html.Attrs::build().styles(
@style.Style::new().font_size(16.0).padding_horizontal(@style.px(12.0)),
),
[ @html.button(on_click=emit(Bump), "+1") ],
)
}
/// 应用入口:交出句柄表(start / snapshot / subscribe / element)。
pub fn app() -> @moobile.JsValue {
@moobile.handlers(model=initial(), update~, view~)
}// App.js —— 全部手写代码就这几行
import { mountApp } from 'moobile-host';
import { app } from './myapp.js'; // MoonBit 编译产物
import { registry } from './registry.generated.js'; // npx moobile-host regen 生成
export default mountApp(app, { registry });moon build --target js
cp _build/js/debug/build/<你的模块>/<你的模块>.js <Expo 工程>/myapp.js
cd <Expo 工程> && npx expo start --port 8081 # 浏览器打开 http://localhost:8081
# 真机/模拟器:adb reverse tcp:8081 tcp:8081 之后扫码或用 expo run:android| 项 | 现状 |
|---|---|
| 支持的标签 | 42 条 HTML 标签有映射(div→View、span→Text…);img video audio canvas svg table iframe select details summary dialog marquee 明确不支持 |
| 未收录标签 | 兜底渲染成 View(不崩),但会被计数,便于你发现迁移漏项 |
| 样式 | 类型化:Attrs::styles(Style::new().font_size(16.0))。class= 与 style="…" 在 RN 上不生效(不报错,只是没效果) |
| 事件 | click→onPress、input→onChangeText 这类映射可用;但 Mouse / Keyboard / Scroll 的载荷是零值 —— 能写、不崩、拿不到坐标,真实手势要接 RN 手势通道 |
| 副作用 / 订阅 | Cmd(@cmd.perform 等)与 subscriptions? 都可用;持续型原生流建议走 @sub.custom_sub |
| 原生能力 | 生态里有现成 RN / Expo 包的(数据库、剪贴板、文件、相机…)→ 在 MoonBit 里写绑定即可,不需要写 Kotlin/Swift;需要自研原生模块时才要 |
| 平台 | 库与 RN 版本无关;换平台通常等于换一个宿主,而不是改库 |
| 能力 | 提供者 | MoonBit 入口 | 说明 |
|---|---|---|---|
| db 本地数据库 | expo-sqlite | XiLaiTL/moobile/sqlite | Android 走预编译 AAR;Web 官方标 alpha,本库只用异步 API |
你的 MoonBit 应用 Model / Msg / update / view ← 与 rabbita 同款 TEA 写法
│
│ 视图用 @html DSL;样式用 @style(类型化,不是 CSS 字符串)
▼
rabbita 运行时(vendor fork) VNode 树 + Cmd / Sub + 事件派发 + 异步 effect
│
│ moobile 的翻译层:① 标签表 ② 事件映射 ③ 样式 map → RN style
▼
React 元素(React.createElement 的产物,不是 DOM 节点、也不是 HTML 字符串)
│
├──► React Native ──────► Android / iOS 原生视图
└──► react-native-web ──► 浏览器 DOM| 想做什么 | 去哪 |
|---|---|
| 看一个真应用怎么写 | examples/apps/todo-app/(含 MoonBit 后端 examples/services/todo-server/) |
| 把库接进自己的项目(宿主包细节、契约、兼容表) | npm/moobile-host/README.md |
| 跑起来 / 排错 / 环境 | DEV.md |
| 改这个库(构建、验证、发版、文档规矩) | CONTRIBUTING.md |
| 架构与契约(分层、宿主契约、发布形态) | docs/ARCHITECTURE.md |
| 为什么这么设计 / 实测过什么 | docs/FINDINGS.md、docs/design/ |
| 我们对 rabbita 改了什么(14 个 patch) | FORK.md |
| 接下来打算做什么 | PLAN.md |
| 全部文档的三条路线 | docs/README.md |
pub fn app() -> @moobile.JsValue { @moobile.handlers(model=initial(), update~, view~) }let host_contract_version : IntInstall
Download zipMoonBit UI for mobile: Android, iOS and Web from one rabbita (TEA) app, rendered by React Native