它是什么
一句话:cj-tauri 是仓颉语言版的 Tauri------后端用仓颉静态编译,界面仍然用 HTML/CSS/JS 写,渲染交给系统自带的 WebView,两者之间由一套 IPC 双向桥和权限白名单连起来。
这么做的理由很直接。桌面应用的界面层历来只有两条路:
- 把浏览器打包进去(Electron 系):开发体验最好,代价是每个应用背一份 Chromium,安装包上百兆起步,内存也实打实吃在用户机器上;
- 复用系统自带的 WebView(Tauri 系):界面照样用 Web 写,但渲染交给操作系统的 WebView,后端只管业务逻辑并直接调系统能力。产物小、启动快,也不用跟着浏览器内核升级。
cj-tauri 走的是第二条,只是把后端从 Rust 换成仓颉。换成仓颉带来两件事:一是静态编译的单文件产物(仓库里十个工具实测 4.7 MB--32.8 MB,双击即用,不装任何运行时);二是同一份业务代码能同时出 Windows、Linux 和鸿蒙 PC(2in1) 的产物------鸿蒙侧的宿主写在仓颉里,直接接 ArkWeb。
| 维度 | Tauri 2 | cj-tauri |
|---|---|---|
| 后端语言 | Rust | 仓颉(cjc 静态编译) |
| 前端 | 任意 Web 技术 | 任意 Web 技术(含 Vue 3 / React 18 模板) |
| 前端桥 API | @tauri-apps/api 的 invoke/emit/listen |
window.__CJ_TAURI__ 的 invoke/emit/listen |
| 命令定义 | #[tauri::command] + Builder 注册 |
CommandHandler 子类 + TauriApp().register() |
| 权限模型 | capabilities/*.json |
capabilities/*.json(同构:命令/事件白名单,默认最小权限) |
| 目标平台 | Windows / Linux / macOS / 移动端 | Windows(WebView2) / Linux(WebKitGTK) / 鸿蒙 PC(ArkWeb) |
调用形态刻意贴着 Tauri 走,会写 Tauri 的人基本不用重新学。
现在能做什么
当前版本 2.1.3,已经跑通的东西(每一项都有实机取证,不是路线图):
- 三平台宿主:Windows 走 WebView2、Linux 走 WebKitGTK 4.1、鸿蒙 PC 走 ArkWeb(宿主写在仓颉侧,多窗口 label 路由已在真机取证);
- 14 个官方插件 :
fs/dialog/clipboard/menu/notification/http/ws/shell/sqlite/store/log/redis/mqtt/serial; - 四道白名单 :命令、事件、路径 scope、出站 host(
http/ws)------默认全拒,声明才放行; - 33 个示例工程 :从最小
hello-world到音乐播放器、Markdown 笔记、串口示波器、判题器、AI 聊天客户端,每个都带无人值守自检; - 350 个单元测试 + C 桥桩自检 259 条断言,
scripts/test.sh一条命令跑完; - 单文件分发 :
scripts/pack-win-single.sh/winbuild.sh打静态链接的 GUI 子系统 exe,页面与能力清单编进二进制。

架构:一次 invoke 走完的链路
javascript
前端页面 (HTML/CSS/JS)
│ window.__CJ_TAURI__.invoke('greet', { name: '仓颉' })
▼
注入的桥 JS(document-start 注入,早于页面脚本)
│ JSON over postMessage
▼
WebView 宿主 host.cj
├─ Windows: host_webview2.cj + native/platforms/windows/bridge_win.c
└─ Linux/鸿蒙: host_webkit.cj / ArkWeb 宿主 + 对应平台桥
▼
IPC 消息桥 ipc_hub.cj ← 反序列化 / 校验 / 分发(命令在 worker 线程执行)
▼
能力模型 capability.cj ← 命令与事件白名单,未声明直接拒
▼
你注册的 CommandHandler
几条设计上的硬约束,值得在动手之前知道:
- 平台差异只允许出现在两处 ------仓颉侧的
@When条件编译,和 C 桥的native/platforms/*/bridge_*.c。IPC、能力校验、命令分发跨平台共用,不存在「Linux 一套逻辑、Windows 另一套」。 - 命令是异步分发的 :
handleInvoke只做校验,通过后spawn到 worker 线程执行,结果经jsSink回投。所以慢命令(shell:exec、原生对话框)不会钉住宿主 UI 线程;反过来,worker 里也只能经jsSink回投,不能直接调 GTK/WebKit。 - 预置类能力必须在
start()之前定稿 (窗口菜单、拖放策略这类),运行期改动走setMenuItemState之类的窄接口。
三步跑起来
准备环境
只差三样:仓颉 SDK(1.0.5+)、stdx、平台依赖(Windows 要 mingw gcc + WebView2 Runtime,Linux 要 libwebkit2gtk-4.1-dev + libgtk-3-dev)。
先跑一条自检命令,它会把缺什么、怎么补直接说出来:
bash
npx cj-tauri doctor
生成工程
bash
npx cj-tauri create myapp # 零安装;默认零 Node 的内联 HTML 模板
cd myapp
npx cj-tauri dev # 构建 C 桥 + cjpm build + 开窗口
create 还带两个前端模板,Vite 工程在 assets/ui/,dev 会接管它们的 dev server,改页面在窗口里热更新:
bash
npx cj-tauri create myapp --template vue # Vue 3
npx cj-tauri create myapp --template react # React 18
加一条命令(三处联动,漏一处就用不了)
① 实现 handler:
cangjie
public class ShoutCommand <: CommandHandler {
public init() {}
public func handle(cmd: String, args: JsonObject, ipc: IpcContext): JsonValue {
var text = "hello"
if (let Some(v) <- args.get("text")) {
match (v.kind()) {
case JsonKind.JsString => text = v.asString().getValue()
case _ => ()
}
}
return JsonString("收到:${text}")
}
}
② 注册进应用:
cangjie
main(): Int64 {
let app = TauriApp()
.window(WindowConfig("我的应用", 1000, 700))
.register("shout", ShoutCommand())
app.run(pageHtml)
return 0
}
③ 写进能力清单 capabilities/default.json:
json
{
"identifier": "default",
"windows": ["main"],
"commands": ["shout", "system:version"],
"events": ["tick"]
}
前端调用:
js
window.__CJ_TAURI__.invoke('shout', { text: 'hi' }).then(r => console.log(r));
只做 ①③ 会得到 command not registered,只做 ①② 会得到 command not allowed------这两条报错本身就是三处联动的验收标准。
权限不是开关,是四层
cj-tauri 的安全模型和 Tauri 2 是同一套思路:默认全拒,声明才放行。
- 命令 / 事件白名单 :
capabilities/*.json里没写的命令,前端调了直接回command not allowed,不会进业务代码。 - 命名权限集 :插件可以声明
permissions(),把一组命令收成fs:readonly这样的短集名,清单里写一行就展开成成员。 - 路径 scope :命令放行 ≠ 参数放行 。清单可以给插件写
scopes:
json
{
"scopes": {
"fs": { "allow": ["$HOME/notes/**"], "deny": ["**/id_rsa"] }
}
}
语义是先 deny 后 allow ,allow 必须显式命中,模式匹配前先规范化路径(存在则解析软链接)。越界被拒会打一行 scope denied,回给前端的是业务错误,而不是悄悄放行。
- 出站 host 白名单 :
http/ws插件是页面可控的网络出口,host 白名单写在scopes.http;什么都不声明等于全拒 。examples/ai-chat就是靠这层把「页面能连哪个模型服务商」这件事钉死的。
值得强调的是:所有校验都在共享层(src/scope.cj、src/capability.cj),平台实现里不另判一份------这也是「换平台不改安全语义」的前提。
一份代码,三份产物
这是 cj-tauri 相对其他方案最实用的一点。新形态工程只有一个真相------cj-tauri.toml 清单:
toml
[app]
name = "music-next"
version = "0.1.0"
id = "com.example.music_next" # 桌面是安装 ID,鸿蒙直接是 bundleName
package = "music_next"
[[windows]]
label = "main"
title = "仓颉爱音乐 · cj-tauri"
width = 1280
height = 820
[assets]
page = "ui/index.html"
[capabilities]
dir = "assets/capabilities"
骨架由 cj-tauri sync 从清单里派生出来,不手写、也不手改:
bash
npx cj-tauri sync desktop # 生成 targets/desktop
npx cj-tauri sync ohos # 生成 targets/ohos(DevEco 工程骨架)
npx cj-tauri check # 校验清单并检测骨架漂移
bash
music-next/
├── cj-tauri.toml # 单一真相
├── src/ # 业务 static 包(三端共用)
├── assets/ui/index.html # 页面
├── assets/capabilities/ # 能力清单
└── targets/ # 生成物:desktop/ 与 ohos/,别手改
targets/ 是派生物,改它等于改缓存:下一次 sync 就覆盖回去了。业务代码、页面、能力清单三处才是你应该动的地方。

同一份 music-next 业务代码,在鸿蒙 PC 上出的产物:

打包分发
单文件包是这套框架的交付形态之一:静态链接仓颉运行时与 stdx,页面与能力清单编进二进制,WebView2Loader.dll 内嵌并在首次运行时释放,PE 子系统设成 GUI------双击就是窗口,不弹黑框,不要求机器上有 SDK。
bash
bash scripts/winbuild.sh examples/typing-english # 在 Linux 上交叉打出 Windows exe
实测产物:typing-english.exe 4.95 MB、导入表只剩 Windows 系统 DLL、无第三方运行库。仓库里已经随包带了十个这样的工具(AI 聊天、串口调试、Modbus、Markdown 笔记、判题器、音乐播放器......),可以直接下载来点。


几个真实踩过的坑
写这篇不是为了说「没有坑」。挑三条有代表性的:
① 三引号字符串会处理反斜杠转义。 把 HTML+JS 内联在 """ 里时,JS 的 \n 会先被仓颉吃掉变成真换行,把 JS 字符串拆断------整段 <script> 语法错误,页面里一行都不执行,而 stderr 毫无提示。内联 JS 里不要出现反斜杠。
② 子包调不了框架的 foreign func。 foreign func 没有函数体、也就没有跨包符号,单测里直接调 C 桥导出会在链接期炸(undefined symbol),编译期不报。办法是在框架包内包一层。
③ GUI 子系统 + 控制台子进程 = 桌面上多一个黑窗口。 应用产物是 GUI 子系统、没有控制台,用普通 launch 起一个控制台程序(CLI 工具),Windows 会给子进程单独配一个控制台窗口,而 stderr 一行错误都没有。解法是走框架的 launchHidden():CreateProcessW + DETACHED_PROCESS,并把三个标准句柄显式指到 NUL。
仓库里的 AGENTS.md 第 4 节维护着上百条这样的实测坑------每条都写着现象、判据和落法。这类内容比 API 文档更能省时间:它记录的是「为什么是这样」,而不只是「接口长什么样」。
从哪开始
| 想做的事 | 去哪 |
|---|---|
| 先看效果 | 仓库 examples/*/dist-single/*.exe,双击即用,不装 SDK |
| 写自己的应用 | npx cj-tauri doctor → create → dev |
| 查命令与平台陷阱 | docs/使用文档.md |
| 看架构与决策理由 | docs/设计决策记录.md、docs/安全模型.md |
| 鸿蒙 PC 端 | npx cj-tauri create myohosapp --template ohos,再 sync ohos |
- 仓库:atomgit.com/qq8864/cj-t...(AtomGit,国内直连) / github.com/yangyongzhe...(GitHub 镜像)
- 官网(含 24 张实机截图):yangyongzhen.github.io/cj-tauri/
- npm:www.npmjs.com/package/cj-...
- 许可证:MIT
仓库同时托管在两个远端,一次 git push 双推。有问题直接提 issue,或者照着 AGENTS.md 的开发契约提 PR------那份契约本身就是这个项目「怎么长成这样」的记录。