Worker 扩展
宿主渲染的 Raycast 风格 React 扩展,在 Web Worker 或 Node.js 进程中运行。
Worker 扩展在沙箱(Web Worker 或 Node.js/Deno 进程)中运行 React 组件。你使用 @kunkunsh/sdk/ui 中的原语编写 React;宿主将其渲染为原生组件——一种具有一致样式的 Raycast 式体验。运行机制参见扩展如何运行。
模式
| 模式 | 运行时 | UI | Node.js |
|---|---|---|---|
worker-view | Web Worker | 宿主渲染 | 否 |
node-view | Node.js / Deno 进程 | 宿主渲染 | 是 |
worker-headless | Web Worker | 无 | 否 |
node-headless | Node.js / Deno 进程 | 无 | 是 |
项目结构
设置
mkdir my-worker-ext && cd my-worker-ext
pnpm init && pnpm add @kunkunsh/sdk react valibot
pnpm add -D @types/react @types/bun构建脚本
// build.ts
import { dedupeReact, kunkunCommandPlugin } from "@kunkunsh/sdk/build";
import type { BunPlugin } from "bun";
// 浏览器目标 → worker-view
await Bun.build({
entrypoints: ["./src/App.tsx"],
outdir: "./dist",
target: "browser", format: "esm", minify: true,
plugins: [kunkunCommandPlugin({ mode: "view" }) as BunPlugin, dedupeReact(import.meta.dir)],
});
// Node 目标 → node-view
await Bun.build({
entrypoints: ["./src/App.tsx"],
outdir: "./dist/node",
target: "node", format: "esm", minify: true,
plugins: [kunkunCommandPlugin({ mode: "view" }) as BunPlugin, dedupeReact(import.meta.dir)],
});kunkunCommandPlugin 注入引导代码和 RPC 连接。dedupeReact 必须使用——两套 React 会破坏协调器。
dist 必须自包含:Bun 默认会打包 JS 依赖,因此除非宿主提供该依赖,否则避免使用 external 条目。原生 .node 插件不能内联——请打包其加载程序 + 平台文件,或惰性加载。
组件
import { useState } from "react";
import { Button, Div, H2, H3, P, Hr } from "@kunkunsh/sdk/ui";
import { popToRoot, showToast, Toast, Clipboard, LocalStorage } from "@kunkunsh/sdk";
export default function App() {
const [text, setText] = useState("");
return (
<Div className="p-6" style={{ display: "flex", flexDirection: "column", gap: 16 }}>
<H2>My Worker Extension</H2>
<Hr />
<Button title="读取剪贴板" variant="outline" onClick={async () => setText(await Clipboard.readText())} />
<Button title="提示" variant="primary" onClick={() => showToast({ title: "Hi", style: Toast.Style.Success })} />
<Button title="返回" variant="outline" onClick={() => popToRoot()} />
{text && <P>{text}</P>}
</Div>
);
}从 @kunkunsh/sdk/ui 导入 UI 原语:
import { Button, Div, H2, H3, P, Code, Pre, Hr, Image, List, Grid, Form, TextField, TextArea, Select, Checkbox } from "@kunkunsh/sdk/ui";<List>
<List.Item title="项目" subtitle="副标题" icon="mdi:file" onClick={() => {}} />
</List>
<Form>
<TextField title="名称" value={name} onChange={setName} />
<Select title="选择" value={choice} onChange={setChoice}
options={[{ label: "A", value: "a" }, { label: "B", value: "b" }]} />
</Form>清单
{
"kunkun": {
"identifier": "com.you.my-worker-ext",
"name": "My Worker Extension",
"permissions": ["clipboard-read", "clipboard-write", "storage"],
"commands": [
{ "name": "main", "title": "My Command", "mode": "worker-view", "main": "dist/App.js" },
{ "name": "with-fs", "title": "Node 版本", "mode": "node-view", "main": "dist/node/App.js" }
]
}
}可用 API
Worker-view 和 node-view 都能通过 RPC 获得完整的宿主 API——Clipboard、LocalStorage、db、showToast、showHUD、popToRoot、getEnvironment、network.fetch、path,以及 fs、shell、dialog、permissions 和 spawnBackend。每个调用都受权限门控:在 manifest 中声明你要用的能力(例如 scoped shell 权限列出要运行的程序)。
Node-view 额外提供的是运行时本身:你的 bundle 跑在 Node.js/Deno 进程里,可以直接使用 Node 内置模块(child_process、fs、原生 .node 插件),而不必经过宿主 API。除非需要这些,优先用 worker-view。
完整接口请参阅 SDK 参考。
实时更新的列表
Worker 和 node 视图可以直接绑定记录存储并使用 useQuery,这样列表在数据变化时会自动重新渲染——无需手动重新获取:
import { useQuery } from "@kunkunsh/sdk/utils";
import { List } from "@kunkunsh/sdk/ui";
function Bookmarks() {
const { data } = useQuery<{ title: string; url: string }>("bookmarks", { orderBy: [["createdAt", "desc"]] });
return <List>{data?.map((r) => <List.Item key={r.id} title={r.data.title} subtitle={r.data.url} />)}</List>;
}实际示例
youtube-tools(service + worker)和youtube-podcast-generator(worker-view 消费者)port-manager——一个扩展中包含 worker-view、无视图命令和菜单栏命令git-repos——扫描本地仓库的 React 视图