PocketJS:没有 DOM 的组件运行时
把 Solid、Vue Vapor 与 Octane 组件编译成同一棵原生树,在没有 DOM、CSS 引擎和 WebView 的设备上直接绘制像素。
本文目录
PocketJS 是我最近看到的最“反常规”的前端项目之一:它让现代组件代码运行在没有 DOM、没有 CSS 引擎、没有 WebView 的环境里。
不是把浏览器塞进设备,而是把组件编译成一棵原生树,由 Rust 核心做 flexbox 布局并绘制像素。
三种框架,一个运行时
PocketJS 支持 Solid、Vue Vapor 和 Octane。三者最终编译到同一棵原生树,运行在同一个 QuickJS guest 上。
| 框架 | 状态与生命周期 | 源码形式 |
|---|---|---|
| Solid | solid-js | JSX |
| Vue Vapor | vue | JSX 与 <script setup> SFC |
| Octane | octane | 编译期 hooks 与 JSX |
框架只影响应用代码,不影响下面的运行时、输入、动画和原生边界。
从组件到像素
一个 Solid 计数器在 PocketJS 里大概是这样:
import { createSignal, Show } from "solid-js";
import { mount } from "@pocketjs/framework/solid";
import { Text, View } from "@pocketjs/framework/solid/components";
function Counter() {
const [count, setCount] = createSignal(0);
return (
<View class="w-full h-full flex-col items-center gap-4 p-4 bg-slate-50">
<Text class="text-xl text-slate-950 font-bold">Count: {count()}</Text>
<View
class="px-4 py-2 rounded-xl bg-blue-600"
focusable
onPress={() => setCount(count() + 1)}
>
<Text class="text-base text-white font-bold">Press Circle</Text>
</View>
<Show when={count() > 3}>
<Text class="text-sm text-emerald-600">Reactive on real hardware.</Text>
</Show>
</View>
);
}
mount(() => <Counter />);
这段代码没有 DOM 节点,也没有 CSS 解析。class 字面量在构建期被编译成样式表,运行时只做查表。
样式与动画
PocketJS 接受一个固定的 Tailwind 子集。构建时把 class 编译成 baked style table,运行时没有 CSS parser、cascade、specificity 或 reflow。
动画也是同一套思路:关键帧时间线和弹簧曲线被烘焙进样式表,由 Rust 核心按自己的时钟推进。也就是说,一屏动画可以在没有逐帧 JavaScript 的情况下运行。
目标准入
一个 bundle 可以服务不同密度的设备。应用在 pocket.json 里声明视口和所需 API,目标 profile 必须满足声明才能编译和打包。
pocket check --target psp
pocket check --target vita
同一个 bundle,不需要为不同设备改组件。
为什么值得关注
PocketJS 把前端开发者熟悉的组件模型,带到了资源受限、没有浏览器的手持设备上。它没有试图模拟 Web,而是重新定义了“组件到像素”的路径。
这条路当然有边界:固定样式子集、目标设备能力、QuickJS/Rust 的调试成本。但它至少证明了一件事——组件化不一定等于 DOM。
讨论
评论由 GitHub Discussions 提供,点击后加载。未加载前不会产生第三方请求。