Skip to content

OpenTray v0.23.0:扩展打包入口,改进图标体验

jixoai v0.23.0 opentray

OpenTray 0.22.0 与 0.23.0:给一个网址就能打包出带自己身份的桌面应用,不跑进程、不依赖 node-pty;窗口标题默认跟随页面;省略 appIcon 时自动合成首字符图标,图标内核统一收进 @opentray/icon 的 WASM 栈,无 sharp 依赖。

$ npm view create-opentray version
0.23.0

OpenTray 0.22.0 到 0.23.0 有两个主题:create-opentray 的打包入口从命令扩展到网址;省略 appIcon 的应用现在自动生成默认图标。其余是一次 macOS CPU 修复与两处图标容错。上一篇讲到 0.21.1(2026-09-06 发文),本篇按主题归组覆盖区间全部变更,链接栏列出两个版本的 release 永链。

网址也能打包成应用

从 0.23.0 起,给 create-opentray 一个网址就够了:别人部署好的实例、线上服务与纯静态页面都可以。npx create-opentray create --url <地址> 直接注册一个指向该地址的桌面应用,身份从地址推导,--app-id 与 --app-name 可覆盖。

npx create-opentray create --url https://example.com
# https://example.com/app → app id "app.com.example",名字 "App"

产物比命令模式更轻:不跑任何进程,没有 PTY 与 shell 资源,也不依赖 node-pty。URL 应用与命令应用走同一条 v1 注册管线,app list、app edit、app copy、app export、app uninstall 同样适用,app edit 还能在命令与 URL 两种来源之间互切。

窗口、托盘与身份推导用的都是现成机制,这一版补的是入口的覆盖面,定位对齐 PWA 的那类用途:装一个图标,点开就是应用,常驻托盘。区别在产物形态:PWA 走浏览器的安装模型,能力受浏览器沙箱约束;这里的产物是一个有自己身份的桌面应用,托盘菜单、Dock 图钉与窗口生命周期都由它自己掌管。

对照的原有路径是命令模式:spawn 一条本地起 HTTP 服务的命令,等它监听到端口,用一个固定的应用身份开原生窗口指向那个端口,托盘里留一个负责显示、唤起与退出的入口。ComfyUI、gradio 与 llama.cpp 的 server,或者你自己项目的 npm run dev,都是「界面是 webui、入口只有一条命令」的典型。

--arg 可重复,每个值就是一个 argv 元素,不做 shell 拆分,&& 会被当成字面参数。端口不写死在配置里:生成的应用持续嗅探命令进程树监听的端口并逐个做 HTTP 验证,动态端口与固定端口同等对待。想看命令输出,在向导里打开「显示启动终端」,旁边会多一个终端窗口。

示例:命令模式的完整调用
npx create-opentray create \
  --app-id app.local.mytool --app-name "My Tool" \
  --exec npm --arg run --arg dev --cwd /path/to/project

地址在动手之前就已知,所以创建时会抓一次页面,把 <title> 和最佳 favicon 作为默认值。显式传入的 flag 永远优先,抓取失败就静默回落到地址推导的名字和字形图标,--no-scrape 关掉这次抓取。

加 --toolbar 会套一层地址栏外壳:后退、前进与重载按钮,加上焦点在壳内时可用的 ⌘/Ctrl+←→、⌘/Ctrl+[ 、⌘/Ctrl+R、F5、⌘/Ctrl+L 快捷键。禁止被嵌入的页面套不了这层壳,对方拒绝就回退成直连窗口并打印提示。不管用不用 toolbar,URL 应用的托盘菜单里都有 Reload。

窗口标题默认跟随页面文档,--no-title-follow 关闭;图标默认不跟随 favicon 的运行时变化,--icon-follow 开启。两项都持久化在 v1 配置里,经 app edit 与 app export 往返,命令模式的服务窗口同样适用。

--exec(命令模式)--url(URL 模式)
入口一条本地命令一个 http(s) 地址
产物做什么起进程 / 等端口 / 开窗口只开窗口
运行时依赖可选 PTY 与 shell 资源无 PTY、无 shell
应用身份runner 与包名从地址推导
标题与图标默认值你自己给抓一次页面
机制:嵌入探测怎么读

探测发生在默认抓取阶段:以 GET 请求读响应头里的 X-Frame-Options 与 CSP frame-ancestors,HEAD 会漏掉只在 GET 响应里出现的策略头,所以探测方法与浏览器一致。--no-scrape 跳过抓取时没有探测数据,--toolbar 请求照常成立。

App 图标自动生成

省略 appIcon 时,运行时会用应用名的首字符合成一个蓝底白字的 squircle 字形图标,写进 macOS 应用包并缓存在运行时目录下,Dock 上不再出现通用的可执行占位图;应用名是中文时渲染中文首字。appBundle.defaultAppIcon: false 退回旧行为,显式传入的 appIcon 始终优先。

await createTray({ /* 托盘图标与菜单 */ }, {
  appId: "com.example.build",
  appName: "Build",
  appBundle: { defaultAppIcon: false },   // 不合成默认图标
});

生成内核在 0.22.0 收进一个新包 @opentray/icon:字形默认值 / 合成 / squircle 切图 / ICNS、ICO 与 Linux PNG 编码都在其中,底层是 WebAssembly 图像栈(jsquash 负责解码与缩放,resvg 负责渲染 SVG),全程没有 sharp,也没有 libvips。

create-opentray、@opentray/vite-plugin 与裸 SDK 三方消费者从此共用同一套图标标准;Vite 插件调的是同一个内核,公开 API 不变,并去掉了 sharp 依赖。

向导的 URL 模式把 macOS 需要的两个图标分开处理:App 图标候选是原图加上 AI 抠出的主体,托盘图标用实心剪影,后者才是 macOS 托盘模板要的东西。抠图在浏览器里跑,模型由向导服务端代理,后面垫着一层持久的磁盘缓存:模型从 CDN 下载一次,之后每次会话(端口随机)都从本机回环地址取。

高级设置(模型精度、alpha 阈值与边缘收缩)改动即生效,替换掉上一个主体和由它派生的剪影;换网址或重新提取后,缩略图与已选中的候选自动重新融合。

机制:抠图模型与缓存口径

模型是 @imgly/background-removal(ONNX Runtime Web),默认档位本版从 isnet_quint8 换成 isnet_fp16:量化版会在白底 favicon 上留下更多白边残留(实测残留覆盖 0.175,fp16 为 0.081)。向导里可选高精(isnet)/ 标准(fp16)/ 轻量(quint8)三档。磁盘缓存位于 ~/.opentray/cache/imgly-data/<version>/,单文件上限 16 MB;模型不随发布包分发,webui 产物保持在 4 MB。

下一步:页面直接调用本地能力

URL 模式的边界很清楚:窗口里的页面仍然是个普通网页,能做的事和在浏览器里一样多。

下一步打算补一组标准接口,让纯前端页面也能调到 fs 与 child_process 这类基础能力。补齐之后,用 OpenTray 打包的网页理论上就能读写本地文件,起子进程,管自己的数据,而不必先写一个 Node 后端。这是计划中的方向,尚未发布。

其余变更

  • macOS 上的 @opentray/ext-webview 不再在每个事件排空 tick 重复断言激活策略。原来每秒约 60 次都调 set_activation_policy,重设应用图标并重绘 Dock 图块,一个 broker 稳定吃掉单核 50-80%,实测修复后从 77.3% 降到 4.4%(2026-09-09);现在先跟 AppKit 的当前值比对再决定(029d286)。

  • @opentray/icon 的自动背景会匹配实色边缘环,白底方角 favicon 能保住自己的底色。

  • 系统缺中日韩字体时,图标生成退到中性终端标记,不再直接失败。

0.21.1 之后 52 个提交,按 conventional 前缀计:15 feat / 10 fix / 15 docs / 6 chore / 2 test / 2 perf,另有 1 style 与 1 refactor。区间无破坏性变更,升级不需要迁移。

升级

npm i create-opentray@0.23.0
npm i opentray@0.23.0          # SDK

用官方扩展时锁同一条协议线:pnpm add opentray@stable-A-B @opentray/ext-webview@stable-A-B(协议线 tag 由 @opentray/spec 发布,不要和 latest 混用)。

链接