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.ready、rpa.progress、sidecar.notify……),移植代码时不会有任何编译或类型报错,跑到运行时才炸。
三重陷阱叠加,导致这个 bug 极难被提前发现:
Rust 端调 app_handle.emit("namespace.event", payload) 不会报错,payload 被静默丢弃,日志里没有任何 emit 失败信号。
只有前端调 listen("namespace.event", cb) 才会触发 IPC 校验层报错。错误 message 是 invalid args 'event',第一眼以为是参数名拼错,其实是 event name 字符串的值非法。
「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 时必踩的隐形坑,根因只是一个点号。