恒美微站 Logo 恒美微站
  • 首页
  • 关于我们
  • 建站服务
  • 主题模板
  • 案例展示
  • 资讯中心
  • 联系我们

gpui-shell 架构与实践:用 JavaScript 扩展 Rust GPUI 应用的脚本运行时

  • 首页
  • 资讯中心
  • /
  • gpui-shell 架构与实践:用 JavaScript 扩展 Rust GPUI 应用的脚本运行时

相关资讯

杰理之长文件名录音支持【篇】 2026/9/15 19:21:23
基于 OpenFGA 的两层 Tenant 树权限模型:BISHENG v2.5.1 F013-tenant-fga-tree 设计与实现全解析 2026/9/15 19:21:23
SkyPilot Job Groups:并行异构任务编排与 RL 后训练实战指南 2026/9/15 19:21:23

最新资讯

Flame 游戏引擎布局组件深入解析:PaddingComponent 内边距组件的原理与实战
Mastra 工作区接入 Archil:使用 @mastra/archil 构建弹性无服务器文件系统
Frappe UI Island 的挂载契约:为什么 `mountVueIsland` 归属于 frappe-ui 侧(而不是 desk)
水体渲染:从光学建模到跨平台落地的完整方案
C++实现小猫钓鱼纸牌游戏:队列与STL容器实战解析
FastapiAdmin定时任务实战:从cron配置到APScheduler调度原理

今日推荐

GDPR下大数据架构重构与隐私保护实践
多组学数据平台架构设计与优化实践
企业主数据管理系统架构设计与实施全解析

本周热门

AI SDK Harness 依赖更新指南:掌握 harness 包 SDK 依赖的升级、桥接同步与一致性校验
Refine v5 Ant Design NumberField 组件实战:基于 Intl 的本地化数字格式化
Flutter应用改名全指南:从Android到iOS的配置与工具实践

本月精选

自研推理加速器Redwood:两周内实现PyTorch模型高效部署的实战教程
V4L2摄像头采集实战:从camera_client.rar到出图全流程解析
从“谁发明了钢琴键”到知识问答智能体:RAG与记忆工程实践

gpui-shell 架构与实践:用 JavaScript 扩展 Rust GPUI 应用的脚本运行时

发布时间:2026/9/15 19:21:23
gpui-shell 架构与实践:用 JavaScript 扩展 Rust GPUI 应用的脚本运行时 gpui-shell 架构与实践用 JavaScript 扩展 Rust GPUI 应用的脚本运行时【免费下载链接】gpui-kitRust GUI components for building fantastic cross-platform desktop application by using GPUI.项目地址: https://gitcode.com/GitHub_Trending/gp/gpui-kitgpui-shell是 gpui-kit 仓库中一个直接构建在gpui-base之上的脚本化应用运行时它让一个用 Rust 编写的 GPUI 应用能够被JavaScript 扩展同时界面仍然完全由 GPUI 自己渲染——没有 WebView、没有 DOM、没有浏览器引擎。本文围绕它的设计定位、插件优先的决策逻辑、脚本描述/Host 渲染的架构、性能与体积实测、默认拒绝的安全模型展开并辅以crates/shell源码与配套文档佐证。读完你可以理解为什么脚本不在每一帧里、13.5 MiB的脚本运行时成本从何而来以及如何把脚本应用接进自己的 Rust 应用。定位它不是 Electron也不是 Taurigpui-shell存在的原因只有一句话让一个用 Rust 写的 GPUI 应用能被 JavaScript 扩展。它有两个目标一主一次首要目标是插件扩展。Host 应用编译一次、发布一次此后新增一块面板、一个侧边工具或一段业务逻辑都以脚本的形式加载进同一个进程——不必重新编译不必重新分发二进制想加一块面板的人也不必 fork 整个 Host。次要目标是用 JavaScript 写完整的应用。CLI 可以直接跑起一个应用目录。这本身就是一条能用的路径同时也是插件的开发方式先把脚本单独跑通再挂进 Host。它明确不是 Electron也不是 Tauri没有 WebView没有 DOM没有 HTML 与 CSS没有浏览器引擎也没有 Node.js。脚本从不负责渲染它只把界面描述一次此后每一帧都由 Rust 把这份描述重放成真正的 GPUI 元素——和一个基于gpui-base的 Rust 应用所构建的是同一套元素模型、同一个 GPU 渲染器。在这里 JavaScript 是应用层不是渲染层所以一次重绘完全不执行 JavaScript而带上整个运行时也只多 13.5 MiB 二进制。这两个目标建立在同一条分工上。gpui-shell直接构建在gpui-base之上QuickJS 跑在 Host 的线程上具体来说是与 GPUI 的App共享主线程见下文线程与内存。由 Host 构建运行时、决定脚本能碰到什么而脚本在同一个进程里画出真正的界面。Rust 负责渲染、布局、文本编辑、虚拟化、焦点、浮层以及全部系统能力脚本负责界面组合、视觉呈现与业务逻辑。一个最小的脚本 View 长这样摘自 GPUI Shell 概览文档import { View } from gpui-kit; import { v_flex, Button } from gpui-base; export default class Counter extends View { init() { this.count 0; } render(cx) { return v_flex() .size_full() .items_center() .justify_center() .gap(20) .bg(cx.theme().colors.background) .child( div() .text_3xl() .text_color(cx.theme().colors.foreground) .child(${this.count}), ) .child( Button.new(increment) .h(32) .px(14) .items_center() .justify_center() .bg(cx.theme().colors.primary) .text_color(cx.theme().colors.primary_foreground) .rounded(6) .on_click((_event, cx) { this.count 1; cx.notify(); }) .child(Increment), ); } }注意其中的分工痕迹v_flex、Button来自gpui-base无样式的行为与布局层View、div来自gpui-kitGPUI 自身的元素与运行时补全样式方法是 snake_case由 Rust 反射表生成业务代码是 camelCase改完状态要自己调cx.notify()没有任何东西会自动重绘。为什么插件优先crates/base/src/dock已经具备了插件系统所需的一半布局是纯数据PanelRegistry能按持久化文件里的名字重建面板每块面板还带着一份属于自己的serde_json::Value。缺的另一半是——面板的实现必须编进 Host 的二进制因此没有人能在不 fork 的前提下贡献一块面板。gpui-shell补的正是这一半。「插件优先」不是一句定位口号。下面这些设计决策如果只面向独立脚本每一条都可以是另一种选择放在插件的语境下才成为必然设计决策为什么它由插件推导而来Capabilities::default()是空集由 Host 授予插件是别人写的代码授权必须来自 Host而不能由插件在自己的 manifest 里声明即得每个插件一份独立Policy卸载即取消它名下的全部任务多个插件共用同一个运行时授权之间不能相互渗透脚本出错是可恢复的异常Host 进程存活一个插件写崩了不该把整个应用一起带走重绘只重放快照从不进入 VM帧预算由 Host 负责插件的 JavaScript 不能压在上面HostModule把 Host 自己的 Rust 借给脚本只有脚本跑在 Host 内部时才有意义——独立应用没有 Host 可借Dock 面板在应用被卸载后仍保留位置与状态插件会被装了又卸重新装回来时面板还在原来的位置状态也还在基座不提供任何视觉呈现权整个交给脚本插件要长得像 Host 的一部分就必须能掌控每一个像素独立脚本应用用得上其中的很少几条。它真正获得的是迭代速度——hot-reload、check以及自动生成的gpui-kit.d.ts。这也是它排在第二位的原因它是插件被开发和验证的地方而不是这套运行时的目的本身。文本编辑、语法高亮、LSP、虚拟化与动画采样都留在 Rust。这条线是职责划分而不是对脚本的限制所有必须贴着 GPU 与系统运行的部分都归 Host插件因此不会成为应用性能与稳定性上的变量。警告插件是目标但接口还没有全部开放插件之下的机制已经建成并有测试覆盖——manifest 解析与发现、加载与卸载、每个插件独立的 policy 与数据目录。脚本现在已经可以贡献面板并绘制 dock 的 chromeDockArea、dock_area(...)与DockArea.register_panel都已公开带着脚本面板的布局也能熬过一次重启。还缺的是贡献注册表的其余部分gpui.command、gpui.keymap、授权 UI以及一个用上PluginManager的 CLI。今天能完整跑通的是独立脚本这条路径dock 也在其中。见 Dock 与面板。核心特点之一架构——脚本负责描述Host 负责渲染脚本从不持有 GPUI 元素它记录的是元素的描述——builder 链上的每一次调用都会往一块 arena 里写入一条操作等某一帧需要时Rust 再把这些操作重放成真实元素。布局、绘制、命中测试、滚动、IME 与文本编辑全部留在 Rust不会回调进脚本。引擎是这套设计的一个参数而不是其中一部分。今天只有 QuickJS 一种但这条分界线之上的全部模块——arena、把描述变成真实元素的materialize、CallScope、样式表、主题、能力模型、浮层 Host、hot-reload——源码里都没有出现任何 VM 的名字。这一点可以在crates/shell/src/lib.rs的模块声明里直接看到engine被声明为pub(crate) mod engine;而对外公开的是host_modules、policy、root、view、snapshot、metrics、dock等模块——公开面很小本身就是接口承诺的一部分。引擎分界线的完整讨论见 The Engine Seam。核心特点之二能力——一整层应用层而不是一套控件脚本拿到的正是一个基于gpui-base的 Rust 应用能拿到的东西元素与布局、链接与控件、建立在语义主题 token 之上的流式样式接口、通过init/render/cx.notify()管理的 View 状态、由 Host 留存的状态例如文本输入的 rope 与选区、dialog / sheet / toast、异步任务、原生 transition 与 spring以及需要授权才能用的文件、存储、剪贴板、进程、HTTP、TCP 与 WebSocket 接口。围绕它的还有一套开发工具链--watch保存文件即 hot-reloadgpui-shell.json在代码运行前声明身份与最小权限自动生成的gpui-kit.d.ts把整套 API 描述给编辑器或模型check在应用跑起来之前就报出问题。提示gpui-kit.d.ts可以加进.gitignore它是自动生成的。从源码看这套应用层的 Rust 侧入口集中在 crates/shell/src/lib.rs 的根导出中ShellRuntime运行时本体、Capabilities/ExecuteGrant/HttpRequestGrant能力模型、ShellRootdialog / sheet / toast 宿主、ScriptView脚本 View、RenderSnapshot渲染快照、RuntimeMetrics指标、HostModule把 Host 的 Rust 借给脚本。lib.rs顶部的模块级注释明确写道Everythingpubhere is a promise——公开即承诺。核心特点之三性能——脚本不在每一帧里render不是每帧跑一次。它把界面描述一次、存进一份 Snapshot在下一次cx.notify()之前每一次重绘都由 Rust 重放这份 Snapshot。指针划过按钮、光标闪烁、列表滚动、原生 transition 或 spring 推进这些重绘都不执行 JavaScript。运行时把两件事分开计数gallery 的 Shell storycargo run -- shell把这两个数摆在界面上对应仓库中的shell-render-frequency图深色版见 dark 变体界面在做什么每秒画的帧每秒跑的 JavaScript只是重绘JavaScript 的数据没有变化600价格每 50 ms 变动一次6019帧数取决于屏幕JavaScript 的次数取决于数据。第二行里另外 41 帧重放的是已有的描述。成本因此按用户操作计而不是按帧计。443 节点的面板跑一遍render、把整个界面记进 Snapshot 要 1.1 ms只在状态变化时付之后每一帧 1.3 ms那是渲染本身——把 Snapshot 变成元素、布局、绘制其中没有 JavaScript。每帧成本没有 Snapshot1.1 ms (JS render) 1.3 ms (Rust render) 2.4 ms/frame render有 Snapshot1.3 ms面板变大也不改变这条性质基准测试覆盖到 8,403 个节点各档的每一帧都不执行 JavaScript最小一档由每次 CI 运行的断言保证。从 engine.md 的实测看描述成本随节点数近乎线性增长443 → 2,103 → 4,203 → 8,403 节点描述耗时 1.1 → 5.1 → 10.3 → 20.5 ms整帧 1.3 → 5.9 → 12.0 → 27.0 ms但任何一档的每一帧都不执行 JavaScript——4,203 节点以上账单的主体已经不是脚本而是 materialize、布局与绘制。以上数字都取自一台 MacBook ProM38 核24 GB帧数与次数来自 Shell story毫秒数来自 release 构建的基准测试。核心特点之四体积——一个脚本运行时只要 13.5 MiB跑一个真实脚本应用的 Host二进制26.1 MiB、常驻内存81 MiB——QuickJS 和整个标准运行时都在里面。相比同一个应用不带它的版本取这个依赖的代价是二进制 13.5 MiB、内存 14 MiB对比基准是 examples/hello_world 与gpui-shellCLI 的 release 构建。这个数是个常数不是比例组件 gallery——体量是它的五倍——增加的同样是 13.5 MiBstrip 后是 19.8% 而非 107%。链接它要付多少给出了测量所用的那一对程序以及这些兆字节都去了哪里。根据 engine.md 的拆解其中大头并不是 QuickJS 解释器只有 12 MiB而是随标准运行时进来的网络与密码学栈fetch、websocket与crypto带来了hyper、rustls、ring、h2、webpki根证书存储与压缩 crate——hello_world本身不链接任何 HTTP、TLS 或tokio整个栈都从这一个 crate 进来。核心特点之五安全——默认什么都没有语言本身也一并收紧Capabilities::default()是空集——没有文件访问、没有存储、没有剪贴板、不能执行进程、没有网络。Host 在加载 View 之前决定授权View 随后在自己的整个生命周期里保持这份授权fs接口上的每一条路径都走同一个解析器任何落在授权根之外的结果都会被拒绝。这条默认拒绝在源码里有直接体现。crates/shell/src/capability.rs 中Capabilities的每个字段都是私有的且#[derive(Default)]就是空集lib.rs 的set_capabilities注释写道Nothing is permitted until this is called。能力授予包含读根、写根、进程执行授权ExecuteGrant、网络主机列表、HTTP 请求授权HttpRequestGrant按 scheme/host/port/method/path 精细限定、存储、剪贴板读写、退出请求。从命令行运行一个目录gpui-shell directory时授予的是一组刻意很窄的默认值读应用目录以及它自己的存储目录写它自己的存储目录存储授予剪贴板不授予进程执行不授予退出请求授予网络不授予在授权之下沙箱还收紧了语言本身——因为一个 VM 早晚要同时承载多个插件eval与四个函数编译器全部移除内置原型被冻结避免一个插件改动Object.prototype波及另一个模块解析被限制在应用目录内堆256 MiB、解释器栈1 MiB与单次调用耗时render为 50 ms都有上限。其中的耗时上限是一个catch无法吞掉的中断这一点由测试保证。完整的资源上限清单见 Capabilities上限值堆256 MiB解释器栈1 MiB已加载的 JavaScript module每个源码文件 8 MiB尚未完成的 host task每个 runtime 1,024 个单次调用耗时render 与 layout50 ms单次调用耗时event 与 task500 ms单次调用耗时不在任何调用中例如模块求值5 秒--dev开发模式会恢复动态代码构造器并让内建原型保持可写但从不放宽能力约束——因为一项作者从没写下来的授权就是一项在生产环境里会缺失的授权。一次渲染是怎么走完的这张图画的是一帧的过程而这张图的形状基本解释了本节文档的其余部分深色版见 dark 变体。GPUI 的元素是被消费的值RenderOnce::render按值取走self.child()按值取走子元素View 每次重绘都从零重建整棵元素树。因此一个 JavaScript 对象永远不可能就是一个 GPUI 元素——它没有东西可以长期持有。所以脚本不构建元素而是描述元素。builder 链上的每一次调用都会把一条操作记录进一块元素描述 arena脚本手里的对象只带一个指向 arena 的整数下标。当 GPUI 要求 View 渲染时Rust 把这些记录下来的操作重放成真实元素、交给 GPUI然后整块清空 arena。布局、绘制、命中测试、滚动与 IME 全程不再回到脚本。由此直接推出三条结论每条对应下面一个页面元素是一次性的。描述在本次渲染结束时就消失了所以被保存下来的元素在下次使用时抛出异常而不是画出一个意料之外的东西。见 Elements。cx只属于产生它的那次调用。它带着一个 generation 编号每次使用都与实时的调用栈比对一个跨过await仍在使用的cx会给出明确错误而不是去访问一个早已失效的栈帧。见 State and Views。回调属于注册它的那次渲染。下一次渲染会整体替换它们这正是脚本闭包不会在 Host 里堆积的原因。见 Elements。这三条都是把脚本绑到一个会消费其值的元素模型上必然的结果。呈现权在脚本一侧大多数脚本层的做法是把一批做好的控件交给脚本去摆放。这里没有这样的控件可交因为它下面那一层同样没有。gpui-base的控件完全不带视觉样式。Rust 里的Button::new(save)没有内边距、没有背景、没有圆角、没有尺寸这就是接口约定。JavaScript 绑定原样保留了这一点Button.new(save)不写样式时除了它的子元素之外什么都不画。结论才是重点因为基础层不提供任何呈现呈现权就完整地落在脚本一侧——颜色、间距、hover 状态、圆角全部由脚本决定。这与 Rust 应用选择基于gpui-base而不是gpui-component时做的取舍完全一样区别在于这里的取舍写在一个存盘就能立刻看到结果的文件里中间不需要cargo build。多打的字换来的是整个应用层。改一个按钮的圆角不必再回到 Rust。适用场景为已有的 GPUI 应用增加插件能力——首要场景。插件跑在 Host 进程内能力由 Host 一项一项授予起点是什么都没有。扩展产品不再意味着 fork 或者发一个新版本界面与业务逻辑以脚本形式交付改动不需要重新编译、也不需要重新分发二进制插件出错会呈现为一个可恢复的错误而不是把 Host 一起带走。基于gpui-shell编写纯 JavaScript 的应用——次要场景。整个应用层——元素、样式、View 状态、浮层与系统接口——都在 JavaScript 一侧而渲染、文本编辑、虚拟化与每一个动画帧仍留在 Rust。这里也是一个插件在挂进 Host 之前被写出来、被验证的地方。它在架构中的位置JavaScript 应用 main.js · views · 样式 · 业务逻辑 │ import { … } from gpui-kit ▼ gpui-shell 引擎分界线 · 元素描述 · CallScope 样式表 · 主题 token · 能力模型 ShellRootdialog / sheet / toast· 调度器 │ ▼ gpui-base 行为 · 状态 · 基础设施无样式 │ ▼ gpui 元素 · 样式 · 渲染 · GPU · 平台gpui-shell与gpui-component是并列关系而不是在它下游两者都是gpui-base的使用者都补上了 Base 不提供的那一层呈现。gpui-component用 Rust 提供了一套成品且统一的呈现gpui-shell提供的是让脚本自己去提供呈现的那套机制。从源码看实现细节crates/shell/src/lib.rs是这个 crate 面向 Host 的全部表面几个关键入口值得逐一对应init(cx)/init_with_components(cx, components)lib.rs初始化gpui-base与样式反射表必须在任何脚本运行前调用一次带组件目录的 Host 用后者以便安装目录自己注册的 initializer。仓库里还有对应的#[gpui::test]用例验证无组件目录时也安装好 base 全局量。set_capabilities(Capabilities)lib.rs授权之前什么都不允许授予位于引擎分界线之上因此不存在某个引擎悄悄无视授权的路径。set_bundle_id(id)/bundle_id_for_path(root)lib.rs以 bundle id 命名应用、决定存储位置与 dock 面板的持久化命名空间shell:id/panel。id 只允许a-z、0-9、.、-、_不允许..——因为它会被拼进用户数据目录。export_module(HostModule)/clear_exported_modules()lib.rsHost 的整个扩展面——脚本不能dlopen原生扩展只能 import 这里注册的模块。HostModule::function是同步的慢任务要用async_function注册必须在load之前完成。类型声明生成write_type_declarations_with_components与write_dependency_links分别是gpui-kit.d.ts与 manifest Git 依赖链接的显式工具入口gpui-shell types命令用的就是它们普通应用加载会自动更新。能力模型在 crates/shell/src/capability.rs 中是纯数据read_roots、write_roots、execute、network_hosts、http_requests、storage、clipboard_read/write、exit。HttpRequestGrantcapability.rs默认httpsscheme、默认标准端口且与network_hosts刻意分离——允许读某个 REST 资源不自动等于获得对同一 host 的 TCP/WebSocket 访问。快速上手CLI 命令一览gpui-shell没有发布到 crates.io所以先克隆仓库再在仓库根目录运行。完整的入门流程Rust Host 嵌入、最小main.js、check、types、hot-reload见 Getting Started这里先给出命令一览gpui-shell directory [--watch] [--dev] gpui-shell check directory [--print-spec] gpui-shell types directory gpui-shell --help | --version参数含义directory应用根目录或其中的main.jscheck不开窗口地加载并渲染一次退出码0或1types写出gpui-kit.d.ts、链接 manifest 依赖、生成配置--watch源码变化时重载--dev开发模式隐含--watch--print-spec配合check额外打印构建出的元素描述自带的示例应用 examples/js_todolist519 行 JavaScript、四个模块会把整个运行时跑一遍带留存状态的输入框、受控 checkbox、确认 dialog、toast、从应用自身目录加载的图标以及在未获授权时退化为内存存储的持久化。运行方式cargo run -p gpui-shell -- examples/js_todolist接着读页面内容Getting Started运行示例、最小应用、check与typesExamples仓库里的独立应用、Host 状态与原生动画示例Elements构造器、child/children/when以及元素为什么是一次性的Styling流式样式接口、长度与颜色、语义 token、状态样式State and Viewsinit/render、cx.notify()、留存状态、异步Overlaysdialog、sheet、toast以及 phase 规则Capabilitiesgpui-shell.json、默认拒绝、文件、存储、进程与网络 API依赖shell package什么样的仓库算一个manifest 如何命名与钉住它以及编辑器拿到的类型HostingRust 这一侧的全貌挂载、刷新、指标、退出、hot-reloadHostModule把 Host 自己的 Rust 借给脚本以及那条纯数据边界Dock 与面板把脚本 View 变成可停靠面板、为它绘制 chrome以及重启后什么会留下Performance脚本的成本失效频率乘以描述规模、View 这条边界以及那几个计数器The Engine SeamQuickJS、这条分界线存在的理由以及把脚本成本与帧成本分开的三项实测当前状态该 crate 处于M0里程碑一条可行性基线而不是稳定接口。它没有发布到 crates.io脚本 API 预计还会变化。以上写到的东西都是已经实现并可用的部分缺失的部分贡献注册表的其余部分、授权 UI、用上PluginManager的 CLI 等会写在你最可能去找它的那一页上。完整设计见仓库中的 docs/gpui-shell.md代码位于 crates/shell。【免费下载链接】gpui-kitRust GUI components for building fantastic cross-platform desktop application by using GPUI.项目地址: https://gitcode.com/GitHub_Trending/gp/gpui-kit创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

关于恒美微站

恒美微站专注于为个体商户、工作室提供极简自助建站服务,让每个人都能轻松拥有专业网站。

快速链接

  • 关于我们
  • 建站服务
  • 主题模板
  • 案例展示
  • 资讯中心

服务项目

  • 可视化建站
  • 拖拽编辑
  • 主题定制
  • SEO 优化
  • 网站托管

联系方式

  • 📍 地址:北京市朝阳区建国路 88 号
  • 📞 电话:400-888-8888
  • ✉️ 邮箱:info@hmyw.cn
  • 🕐 时间:周一至周日 9:00-18:00

© 2024 恒美微站 hmyw.cn 版权所有 | 京 ICP 备 12345678 号