用仓颉语言写桌面应用:cj-tauri 框架与 AI 笔记软件 cjnote 实战
如果你手上有一个桌面应用要写,并且符合下面任何一条,我建议你花半小时试试 cj-tauri。不装 Node、不捆 Chromium,后端静态编译;九份单文件产物 4.69–14.1MB,双击即用。
📖 官网:https://yangyongzhen.github.io/cj-tauri/
📦 仓库:https://atomgit.com/qq8864/cj-tauri
🧩 样板应用:https://atomgit.com/qq8864/cj-tauri/examples/cjnote/
cjnote是一个使用cj-tauri框架开发的,满足日常办公用的 Markdown 笔记软件AI应用,它的存在本身就是这套框架的能力清单——原生菜单栏、系统副标题、AI 流式对话、笔记增删改查、单文件交付,一样不缺。关键是体积才33M大小左右(因为内嵌了cjh的AI智能体,所以体积才大了点儿)。
还在用Typora?可以玩下这个。体积小但功能一点儿也不弱,因为我将cjh这个仓颉语言写的智能体也嵌入进去啦,打造了一款小巧的AI笔记软件。

如果你手上有个桌面工具要写,符合下面任何一条,就值得花半小时试试 cj-tauri:
- 不想为了一个工具装 Node / 捆 Chromium,也不想让用户下 100MB 运行时;
- 后端逻辑希望是编译成机器码、可审计的,而不是一棵 JS 依赖树;
- 本来就在仓颉这条技术栈上,希望桌面端和鸿蒙端共用一套后端代码;
- 想要的安全模型是「没在清单里写的东西,前端就是调不通」;
- 在用 AI 写代码,希望错误尽早被编译器拦住、改完能自己跑出证据。
- 不想用Rust,因编译速度慢且还总是占用好几个G的磁盘空间。
- 不单方便AI,也方便看懂语言语法,仓颉的语法比Rust简单太多,方便审计。
一、cj-tauri 是什么
一句话:用华为仓颉语言实现的 Tauri。前端仍是 HTML/CSS/JS,跑在系统 WebView 里;后端是仓颉静态编译的原生二进制。产物不带 Chromium、不带 Node。

| Tauri 组件 | cj-tauri 对应 | 说明 |
|---|---|---|
| WRY + TAO | src/host.cj + 各平台实现 | Linux = WebKitGTK,Windows = WebView2,鸿蒙 = ArkWeb |
| IPC(invoke / event) | src/ipc_hub.cj | JSON over postMessage,window.__CJ_TAURI__.invoke/listen/emit |
| capability 模型 | src/capability.cj + capabilities/*.json | 命令 / 事件白名单,默认最小权限 |
@tauri-apps/api | 注入的桥接 JS | 宿主在页面脚本之前注入 |
| tauri-cli | cli/cj-tauri | create / dev / build / run / info / doctor |
写一个应用就三步(对应 Tauri 的 command / Builder / capabilities):
public class GreetCommand <: CommandHandler {
public func handle(cmd: String, args: JsonObject, ipc: IpcContext): JsonValue {
return JsonString("Hello, ${argString(args, "name", "world")}!")
}
}
main(): Int64 {
let app = TauriApp()
.window(WindowConfig("hello", 1000, 700))
.register("greet", GreetCommand())
app.run(html)
return 0
}
window.__CJ_TAURI__.invoke('greet', { name: '仓颉' }).then(d => console.log(d));
二、为什么另起一套
不是「再写一个 Tauri」,是三件很具体的事:
- 仓颉需要一个真实的桌面落地场景。 语言能力(编译、FFI、条件编译)都在,但「能不能用它做完一个能交付的 GUI 应用」只有真做出来才算数。
- 混合开发的体积账值得重算。 桌面端最贵的从来不是业务代码,是「为了跑业务代码而捆进来的运行时」——系统 WebView 就在那儿。
- 鸿蒙这条线,后端同源最省。 ArkWeb 与桌面 WebView 的差别主要在宿主层,IPC、命令分发、能力校验本该共用。
这三件事决定了框架的形状:平台差异只允许出现在两处——仓颉侧 @When 条件编译、C 桥 native/platforms/*/,其余一切跨平台共用。这条规矩到今天没被破过。
三、五个值得单独说的设计
1. 小,而且小得可核对
单个示例产物 4~10MB;仓库随版本分发九份静态链接的单文件 Windows 工具(4.69–9.79MB,另加内嵌 AI 后端的 cjnote 34.1MB),免安装、双击即用,PE 导入表里只有系统 DLL,双击不弹控制台。
这不是宣传口径,是一条命令能复核的口径:
bash scripts/dist-report.sh # 体积、依赖、子系统逐项复算
2. 默认拒绝,而且分两层
第一层是命令级白名单:capabilities/*.json 里没声明的命令,前端调了就是 command not allowed,stderr 留一行凭证。
第二层更值钱——命令放行 ≠ 参数放行:
- 路径参数走
scopes({"fs": {"allow": ["$HOME/notes/**"], "deny": ["**/id_rsa"]}}),先 deny 后 allow、allow 必须显式命中、匹配前先规范化(顺带解析软链接); - 出网口(
http/ws插件)走主机白名单,没写 = 全拒,且禁自动重定向; - 判定逻辑只有共享层一份,插件要用就调
IpcContext.resolveScopedPath(...),不允许各平台实现里再判一遍。
后果是明确的:页面是可能的攻击面,而页面能碰到的每个字符串,都在进宿主之前被一层统一的判断挡过;想绕过就得改共享层,而共享层是有单测的。
3. 插件:一行接入,不用不链接
14 个官方插件(fs / dialog / shell / serial / store / clipboard / notification / menu / mqtt / redis / log / http / ws / sqlite),接入就一行:
let app = TauriApp().plugin(FsPlugin()).plugin(StorePlugin())
值得说的是**「一个插件一个子包」**这个决定。最早所有插件挤在一个包里,结果只用 fs 的应用会被 sqlite 的 FFI 连坐,Windows 上还要跟着链 -lsqlite3。拆成一个插件一个静态库之后,没引用的插件根本不进链接。
权限也不想当然:插件只声明「我提供什么」,不自动放行——放行永远是应用清单说了算。声明与放行分开,是这套体系里最容易抄错、也最不该抄错的一条。
4. 可验证性是被设计进去的
框架里写着一句硬规矩:行为变更必须给出可观测证据。所以日志不是「调试用的」,是交付凭证:
- 桥的 stderr 每行即时落盘,端到端验证以它为准(仓颉
println有缓冲,被强杀会丢日志,诊断一律走 stderr); - 应用可以开页面自检:断言经
report命令回投 stderr,于是「无人值守跑一轮」可行; - 命令失败必留
command rejected (...)/command failed (...),前缀不同方便分流——「日志里没有错误行」从来不等于「命令成功了」,这是被实际坑过才写进规范的。
配套的测试也是真在跑:
bash scripts/test.sh # 302 个单测
bash scripts/test-bridge-core.sh # C 桥公共核心的桩自检,不依赖 SDK 与图形栈
bash scripts/check-static.sh # 免 SDK 的静态门禁,CI 跑的就是它(27 项)
test-bridge-core.sh 用桩平台编 C 桥核心,任何 Linux 机器上都能跑——把可验证的部分从平台里剥出来,是我在这个项目里最喜欢的工程习惯。
5. 双平台同名导出,上层不长平台分支
IPC、能力校验、命令分发跨平台共用;平台实现只在 C 桥各写一份 cj_bridge_*,且导出名必须逐字一致。应用代码里不会出现「Linux 这么调、Windows 那么调」。
能力位如实上报:没实现的能力 system:host 就报 0,应用据此优雅降级——cjnote 的副标题就是这么写的(有原生副标题就用,没有就退回页面自己画一条)。平台没这能力就如实说没有,比「假装成功」省掉后面所有排查。
四、AI 时代,选语言本身就是优势
这两年写代码的方式变了:很大一部分是「我描述、AI 落笔、编译器裁决」。这个流程里决定效率的不是 AI 多聪明,而是错误暴露得多早、上下文能不能一次喂清、改完能不能自己证明。系统级语言被重新追捧,根子就在这里——Rust 是最有名的答案,仓颉也在这条线上,而且有几点别处不容易凑齐。
① 静态类型是给 AI 幻觉设的落点。 动态语言里 AI 编错的 API 名字要跑到那一行才炸,仓颉编译期就过不去。框架一侧就有 15 838 行仓颉代码(50 个源文件)、302 个单测、两平台同名的 C 桥导出——编译器的报错比 AI 的自述可信。
② 后端只有一门语言,上下文小。 没有「Node 那一半」要同时记住:命令就是类、能力就是一份 JSON、前端只有 invoke / listen / emit、平台差异被压到两处。AI 说「加个命令」,要改的就是注册处 + 清单各一行。
③ 仓库本身就是按 AI 协作的要求长出来的。 根目录 AGENTS.md 是开发契约,§4 是一份几十条的**「已踩坑清单」**——「编译器报错指向的不是真原因」那种(少一行 import std.convert.* 报 undeclared identifier 'parse';TlsClientConfig 不在 net.http 里,漏 import 报「这个类型不存在」)。配合同样沉淀出的 5 个 AtomCode skills:环境检测 → 应用开发 → 实机取证 → 踩坑速查 → 回贡献。
④ 「改完能自己证明」是收口。 页面自检回投 stderr、桥日志即时落盘、单测 + 桩自检 + 免 SDK 静态门禁三道闸门——一轮 AI 改动是有机器判据的:编得过、跑得出、证据躺在 stderr 里。没有证据的「已完成」不算完成。
⑤ 语法简单、代码短,编出来的东西也小。 类型推断、模式匹配、lambda、字符串插值都在,一个命令处理器六行、一个窗口应用十来行;FFI 一行 foreign func,本仓 SQLite 插件就是直连系统 libsqlite3,中间没有 C 桥包装。产物是静态编译的原生二进制,没有 VM、不要求用户先装运行时。
Rust 的代价也很具体,而这恰好是仓颉能补的地方:编译慢、构建目录吐垃圾——依赖一多、泛型单态化与过程宏一起来,target/ 动辄几百 MB 到几 GB。本机没装 cargo,不做同机对照,只给仓颉侧实测,你自己按比例感受:
| 项目 | 实测 |
|---|---|
examples/hello 全量冷编译(静态链入框架) | 50 秒 |
| 同一示例改一行再编 | 25 秒 |
构建目录 target/(该示例 / 框架本身) | 8.3M / 36M |
产物 main(静态链接) | 4.5M |
| 独立小程序(leetcode-gui 的判题器,静态链 + strip) | 冷编译 0.4–1 秒,产物 2.7M |
中间产物是几十兆级不是几个 G,全量编译是几十秒级不是几分钟。不过因果要说清:仓颉快,一部分是因为项目本身瘦(依赖少、没有一排第三方 crate 要先编)和工具链轻,不是编译器比 rustc 更聪明。
代价也照实说:库生态远小于 crates.io,很多轮子得自己造(本仓的 MQTT、Redis RESP2 都是纯仓颉手写——好处是零第三方依赖,代价是自己写);工具链还在快速演进,版本配对的坑真实存在(cjc 与 opt 差一代会崩、cjpm 增量缓存会跳过链接),所以本仓的判定统一成「看产物」而不是「看 build success」。
一句话:要「AI 帮你写 + 编译器把关 + 自检收口」这套工作流,仓颉很合适;要现成的海量第三方库,先去 Rust。
五、示例不是演示壳
examples/ 下有 27 个可直接运行的桌面示例(外加 1 个鸿蒙探针工程 ohos-probe),另有三个脚手架模板(零 Node 的纯内联版 / Vue 3 / React 18)。挑几个最能说明问题的:
| 示例 | 一句话 |
|---|---|
| modbus-poll | Modbus TCP/RTU 调试工具:多面板并行监控、FC 01-04/05/06/15/16、报文监视、CSV 记录——对标 Modbus Poll 并补齐它让人难受的地方 |
| mqtt-client | MQTT 调试台:协议由 mqtt 插件纯仓颉实现(没用现成库),自带 mock broker,实机 E2E 27 条断言两侧对账 |
| redis-client | Redis 调试台:SCAN 游标分页 + MATCH + TYPE 过滤、类型化取值写值、发布订阅,RESP2 双 socket 同样自研,实机 E2E 47 条断言 |
| sqlite-browser | SQLite 浏览器:仓颉 FFI 直连系统 libsqlite3(不经 C 桥),只读走内核级连接、ATTACH 由 authorizer 按语句拒 |
| http-client | HTTP API 客户端:页面零 fetch,请求全由仓颉侧直发(TLS 走系统证书库、不关校验),E2E 55 条断言含截断三侧对账 |
| cj-link | 串口/网络二合一调试助手:串口 + TCP 客户端/服务器 + UDP 共用收发日志,HEX/ASCII、CRC、定时发送 |
| leetcode-gui | 桌面版算法练习:点「运行」把代码交给 cjc 编译再逐用例判定——判题器就是编译器,全程离线 |
| clipboard-history | 剪贴板历史管理器:托盘 + 全局快捷键 + 最小化到托盘的首个消费者,Windows 与 Linux 双实机 |
再往下还有 music / movie / typing-poem / markdown-notes / serial-waveform / multi-window / ipc-bench……每个示例带自己的 README 与自检方式。
免安装试玩:九份单文件产物
下面每份都是静态链接的单文件 Windows 可执行程序(GUI 子系统、双击不弹控制台、PE 导入表里只有系统 DLL),点文件名进仓库文件页下载即可:
| 产物 | 体积 | 说明 |
|---|---|---|
| cj-link.exe | 5.48MB | 串口/网络二合一调试助手(串口 + TCP/UDP,HEX/ASCII、CRC、定时发送) |
| modbus-poll.exe | 5.59MB | Modbus TCP/RTU 调试工具(多面板并行监控、报文监视、CSV 记录) |
| serial-waveform.exe | 4.69MB | 串口实时波形示波器(400 Hz 样本流下页面自算速率/上穿/丢帧) |
| markdown-notes.exe | 4.78MB | Markdown 笔记(文件树 / 编辑 / 实时预览三态,marked + Highlight.js 内联) |
| leetcode-gui.exe | 9.79MB | 仓颉力扣(题库 + 编辑器 + 判题终端,判题器就是 cjc) |
| music.exe | 9.50MB | 音乐播放器(榜单/歌单/收藏 + 同步歌词 + 4 套主题色) |
| movie.exe | 9.42MB | 观影应用(数据取自第三方公开接口,仅供学习研究) |
| typing-poem.exe | 5.50MB | 古诗词打字练习(three.js 3D 卡面) |
| cjnote.exe | 34.1MB | AI 原生 Markdown 笔记,cjh 与 4 个运行时 DLL 一并嵌进 exe(下一节详说) |
最大的那份是 cjnote——因为它把一整个 AI 后端装进了同一个文件;其余八份 4.69–9.79MB,也就是一个中等网页的体积。体积、依赖与子系统都能一条命令复算。
六、cjnote:把这套能力一次用足
examples/cjnote/ —— 左笔记库、中分屏(代码高亮 + 行号)、右 AI 助手。顶栏只剩「文件 · 状态 · 字数」,因为标题与保存状态是通过 setTitlebarSubtitle 推到了系统菜单行右侧。
它不是为了演示写的壳:笔记是真 .md、笔记库是真目录、AI 是真接后端,日常可以拿来用。推荐它当样板有三个理由:
① 原生能力用得克制且诚实。 文件 / 视图 / 帮助三个顶层菜单、Ctrl+O/P/N/S/Q 全套快捷键、副标题、行号开关,全走宿主能力位;副标题在能力位缺失的平台(如 WebView2)上自动退回页面顶栏,不假装成功也不白屏。菜单勾选与页面状态双向同步。
② AI 只有一条路,密钥不留在应用里。 页面零 fetch,聊天走 ws 插件连本机 cjh 的 ws://127.0.0.1:8765/ws 收流式帧;配置读写走宿主命令,页面没有 ~/.cjh 的写权限。cjh 随包分发、自动拉起、退出收尾(端口上已有活着的就复用、不动别人的进程)。结果是应用自己的 store.json 里一个 API Key 都没有,设置页的密钥框只脱敏回显、留空即不修改。
两个我挺得意的小设计:保存是两条腿——写 cjh 的配置文件(管下次拉起与协议字段)+ 把地址 / 模型 / 密钥即时推给正在运行的那只 cjh,所以哪怕那只 cjh 是别的程序拉起来的,改完也立刻生效;「测试连通」用帧级覆盖——把表单里的值随一条最小对话发过去,单次生效、不落盘、另开会话,配置一个字都没动。
③ AI 上下文只给指针,不给正文。 每一问默认只带当前笔记的绝对路径(外加笔记库根),正文由 AI 自己按需 read_file——省 token,也保证 AI 读到的永远是盘上最新内容(页面持有的文本可能已经落后于磁盘)。勾「问笔记库」才要求这一轮做全库检索。(绝对路径是硬前提:cjh 的 read_file 对相对路径按它当时的工作区解析,给相对路径会静默读错目录。)
实测数字(都能自己复现):
| 项目 | 值 |
|---|---|
| 应用命令 | 18 条(note:* / cjh:* / theme:* / report 等),能力清单放行 32 条 |
| 页面自检 | 沙盒实测 68 PASS / 0 FAIL / 1 SKIP(含真实 cjh 的聊天与连通探针,连不上记 SKIP 不算失败) |
| 开发态产物 | target/release/bin/main 7.6M |
| 单文件产物 | Linux dist-single/cjnote 11.2M(页面 / 前端库 / 能力清单全内嵌);Windows cjnote.exe 34.1MB(另把 cjh 与 4 个运行时 DLL 嵌进 PE 资源,首次运行释放到 %LOCALAPPDATA%\cjnote\runtime\) |
| 装配插件 | 6 个(store / http / log / clipboard / dialog / ws) |
Windows 侧两个形态都实测过:cjnote.exe 在干净目录双击等价启动,cjh 自行释放并拉起、8765 LISTENING、页面 ws 连上,cjh 窗口句柄为 0、可见控制台窗口 0 个。
七、三条上手路径
① 只想先看一眼效果(什么都不装):从上一节下 cjnote.exe 或任意一份单文件产物,双击就用。
② 想写自己的应用:
npx cj-tauri create myapp # 前置:仓颉 SDK 1.0.5+ 与 stdx
cd myapp
npx cj-tauri doctor # 缺什么、怎么补,逐行告诉你
npx cj-tauri dev # 开发模式;Vue/React 模板会接管 Vite dev server,HMR 在窗口内生效
npx cj-tauri build # 出产物
跨平台链接、GUI 子系统(Windows 下别弹 DOS 黑窗)这些坑,脚手架生成时就替你填好了。
③ 想看完整应用长什么样,把 cjnote 跑起来:
cd examples/cjnote
bash ../../native/build_linux.sh # 首次编 C 桥(产物落在框架根 native/)
cjpm build
export LD_LIBRARY_PATH=$CANGJIE_HOME/runtime/lib/linux_x86_64_cjnative:$CANGJIE_STDX:../../native:$LD_LIBRARY_PATH
./target/release/bin/main # 笔记库是当前目录的 notes/,首启种一篇「欢迎.md」
沙盒自检:CJNOTE_SELFCHECK=1 ./target/release/bin/main 2>&1 | grep SELFCHECK;交付成单文件:bash ../../scripts/pack-linux-single.sh。
八、不足,也照实说
推荐一个东西时把短板藏起来没意义。这些希望你用之前就知道:
- 鸿蒙还在半路:ArkWeb 宿主已在真机闭环(页面往返、多窗口 label 路由都取证过),但 C 桥尚未落地,宿主能力位仍全 0——菜单 / 托盘 / 快捷键 / 拖放 / 对话框在鸿蒙上一律如实拒绝,不是「悄悄不生效」。
- macOS 没有做。 只有 Linux / Windows 两个宿主实机跑通,鸿蒙是第三个。我不写我没验证过的东西。
- Linux 拿不到「全静态」:系统 WebKitGTK / GTK 没有静态库,单文件产物仍动态依赖系统图形栈;能静态下来的只是本框架带来的那部分(桥、仓颉运行时、stdx、各插件包)。
- 插件只有 14 个,生态还在早期,想要的能力可能得自己写一个。好消息是成本很低(一个子包 +
name()+commands()),而且不用不链接。 - 构建有门槛:仓颉 SDK 1.0.5+ 与 stdx 得装好;好在有
doctor和 5 个 skills 兜着。
九、想一起用起来
- 官网(先看这个):https://yangyongzhen.github.io/cj-tauri/ —— 中英双语、19 屏实机截图、特性与文档入口
- 仓库:https://atomgit.com/qq8864/cj-tauri(GitHub 有镜像,国内直连建议 AtomGit)
- 单文件产物下载:见上文「免安装试玩」一节
- 上手文档:使用文档 → 前端入门教程 → 插件体系教程(均在本仓
docs/) - 想改点什么:
CONTRIBUTING、docs/设计决策记录.md(每条决策都写了理由与代价)、docs/需求追踪.md(每个能力做到哪、证据在哪) - AI 助手辅助开发:装 5 个 AtomCode skills
atomcode plugin marketplace add https://atomgit.com/qq8864/cj-tauri.git
atomcode plugin install cj-tauri-skills@cj-tauri-skills
全部代码 MIT。你要做的第一件事,我建议不是「从零写」,而是把 examples/cjnote 复制一份、改掉业务逻辑——原生菜单、AI 通路、能力清单、单文件打包,该配的都配好了。
最后
桌面开发这些年一直在两个极端间来回:要么轻(但什么都要自己写),要么方便(但捆进来一整个浏览器)。系统 WebView 这条路证明过能走通,只是它需要一门能静态编译、能直接调 C、还能编译到鸿蒙的语言在后面撑着——仓颉恰好是这门语言,cj-tauri 就是把它用在桌面上的一次认真尝试。
它还不完美(见上一节),但它是能用、能核对、能改的:每个数字都有脚本可复算,每条设计决策都写了理由和代价,每个示例都能自己跑出自检结论。
如果你在找一个「小、快、安全模型清楚、后端可审计」的桌面方案,至少花半小时试一下——从官网进去,或直接从 examples/cjnote 开始。
更多推荐



所有评论(0)