返回博客

Tauri 2 event name 禁用点号——Mac 跑得好好的,Windows 一启动就崩

桌面 App 发布新版,macOS 内测毫无问题,Windows 首装机用户一启动就弹错。同一份 binary、同一份 Tauri 2 runtime,表现截然不同。第一反应是平台差异,结果排查半天发现根因极其简单:event name 里有一个 .

现象

Windows 首次启动,进入某个需要下载依赖的初始化页面,前端注册事件监听后立即弹出运行时错误:

invalid args 'event' for command 'listen':
Event name must include only alphanumeric characters, '-', '/', ':' and '_'.

界面显示「初始化失败」,整个流程卡死。macOS 的同型号版本从不复现。

根因

Tauri 2 在 plugin:event|listen 这个 IPC invoke 入口做了参数校验,event name 只允许字符集:

[a-zA-Z0-9]   以及   -  /  :  _

点号(.)不在白名单内,一旦出现直接拒绝并抛错。

这是 Tauri 1 → 2 的 silent breaking change:Tauri 1 几乎不限制 event name 格式,JS 世界里用 namespace.event 做命名是很自然的习惯(app.readyrpa.progresssidecar.notify……),移植代码时不会有任何编译或类型报错,跑到运行时才炸。

三重陷阱叠加,导致这个 bug 极难被提前发现:

坑:emit 静默失败 · listen 才报错 · 两端不对称

Rust 端调 app_handle.emit("namespace.event", payload) 不会报错,payload 被静默丢弃,日志里没有任何 emit 失败信号。

只有前端调 listen("namespace.event", cb) 才会触发 IPC 校验层报错。错误 message 是 invalid args 'event',第一眼以为是参数名拼错,其实是 event name 字符串的非法。

诊断陷阱:不是平台 bug,是触发路径不同

「Mac 好 Windows 坏」和「Tauri 版本一样」两个事实叠在一起,极容易误判为跨平台 bug。

实际原因是:出错的 listen() 调用藏在一个条件分支里——仅在「首次安装且运行时依赖不存在」时才会执行。macOS 开发机早已下载过依赖,这条路径从来没走到,所以看起来「没问题」。Windows 新机子首次安装,条件满足,路径触发,即崩。

诊断原则:看到「同 binary 一边好一边坏」,先查有没有平台独占路径 / 首次运行 only 路径,再往平台差异方向想。

正解

将所有 event name 里的 . 改为 :。Tauri 2 官方推荐用 : 做命名空间分隔(与内部 plugin:event|listen 协议风格一致)。

前端改 listen 调用

- listen('app.progress', handler)
+ listen('app:progress', handler)

Rust 端改 emit

- let _ = app_handle.emit("app.progress", payload);
+ let _ = app_handle.emit("app:progress", payload);

sidecar 模式的最小改动:若 sidecar 进程以 JSON-RPC 风格发 notification({ method: "app.progress", params: {...} }),Rust 侧把 method 当 event name 透传时只需在边界做一次转换,sidecar 内部 method 注册表和前端的 callSidecar 调用全部不动:

// 仅在 Rust 处理 sidecar stdout 时做字符替换
let event_name = method.replace('.', ":");
let _ = app_handle.emit(&event_name, payload);
正解:`.` 换 `:`,一行不留

一次性扫全项目所有受影响位置:

# 前端 listen 调用(会爆的地方)
grep -rn "listen(" src --include="*.ts" --include="*.tsx" | grep "'.*\..*'"
 
# Rust emit 静默失败的地方
grep -rn 'emit("[^"]*\.' src-tauri/src

修完后重启 App,走到原先崩的代码路径验证事件能正常被 listen 收到即可。

主动防御:在 CI 加 lint rule,禁掉新增的带 . 的 event name:

# pre-commit / CI gate
grep -rn "listenEvent\|listen(" src --include="*.ts" --include="*.tsx" | grep "'.*\\..*'" && echo "FAIL: event name contains dot" && exit 1 || true

一句话外卖

Tauri 2 的 event name 字符集硬校验、emit 静默失败、listen 才报错——三者叠加,加上条件分支导致的跨平台「表现不一致」,是迁移自 Tauri 1 时必踩的隐形坑,根因只是一个点号。