window.TAURI is undefined
主线位置:启动主线上
prepare_pending_webview拼初始化脚本那一步。window.__TAURI__是一段可选的初始化脚本(bundle.global.js)挂上去的,window.__TAURI_INTERNALS__才是每个页面都有的那一个。两者各由哪段脚本建立、按什么顺序注入,见初始化脚本。
flowchart TB
cfg{"app.withGlobalTauri"} -->|false 默认| none["不读 bundle.global.js"]
none --> undef["window.__TAURI__ undefined"]
cfg -->|true| ctx["Context 带上脚本"]
ctx --> inject["初始化脚本 只进主 frame"]
inject --> main["主 frame 有 __TAURI__"]
inject --> ifr["iframe 里没有"]
实验 crate exp-tauri/pit3-global:从模板 base 复制,页面里嵌一个同源 iframe(frame.html)。页面按教程的写法取 window.__TAURI__.core,记录结果和 iframe 里的两个全局对象,再用一定存在的 __TAURI_INTERNALS__ 把结果报回来:
window.addEventListener('load', async () => {
const out = { typeof___TAURI__: typeof window.__TAURI__ };
try {
// what tutorials and copied snippets do
const { invoke } = window.__TAURI__.core;
out.call = 'invoke is a ' + typeof invoke;
} catch (e) {
out.call = String(e);
}
const fw = document.getElementById('f').contentWindow;
out.iframe = {
typeof___TAURI__: typeof fw.__TAURI__,
typeof___TAURI_INTERNALS__: typeof fw.__TAURI_INTERNALS__,
};
// __TAURI_INTERNALS__ is always injected, so it can carry the report
await window.__TAURI_INTERNALS__.invoke('report', { msg: JSON.stringify(out, null, 1) });
});
tauri =2.12.5,--features tauri/custom-protocol,隐藏窗口,debug 构建,平台 M2 Max、macOS 27、rustc 1.99.0(WKWebView)。
现象
一:默认配置。 tauri.conf.json 里没有 withGlobalTauri:
REPORT {
"typeof___TAURI__": "undefined",
"call": "TypeError: undefined is not an object (evaluating 'window.__TAURI__.core')",
"iframe": {
"typeof___TAURI__": "undefined",
"typeof___TAURI_INTERNALS__": "undefined"
}
}
TypeError: undefined is not an object (evaluating 'window.__TAURI__.core') 是 WebKit 的措辞,WebView2、WebKitGTK 上措辞不同(没有实测),意思一样。
二:开关写在 1.x 的位置。 从 1.x 的配置或旧教程抄来的写法放在 build 下:
"build": { "frontendDist": "./dist", "withGlobalTauri": true },
这次不是运行时 undefined,而是构建失败,tauri-build 的输出(节选):
unknown field `withGlobalTauri`, expected one of `runner`, `dev-url`, `devUrl`, `frontend-dist`, `frontendDist`, `before-dev-command`, `beforeDevCommand`, `before-build-command`, `beforeBuildCommand`, `before-bundle-command`, `beforeBundleCommand`, `features`, `remove-unused-commands`, `removeUnusedCommands`, `additional-watch-directories`, `additional-watch-folders`, `additionalWatchFolders`, `windows`
found an unknown configuration field. This usually means that you are using a CLI version that is newer than `tauri-build` and is incompatible. Please try updating the Rust crates by running `cargo update` in the Tauri app folder.
第二行的建议(「CLI 比 tauri-build 新,去 cargo update」)在这里是误导:版本没问题,是字段放错了地方。BuildConfig 带 deny_unknown_fields,所以至少不会被静默忽略。
三:开关打开了,代码在 iframe 里。 见下面修法的输出:主 frame 有了 __TAURI__,iframe 里两个全局对象都没有。
出处
开关是 AppConfig 的 with_global_tauri,#[serde(default)],默认 false。generate_context! 在编译期按它决定要不要把全局 API 脚本读进 Context:
let plugin_global_api_scripts = if config.app.with_global_tauri {
if let Some(scripts) = tauri_utils::plugin::read_global_api_scripts(&out_dir) {
let scripts = scripts.into_iter().map(|s| quote!(#s));
quote!(::std::option::Option::Some(&[#(#scripts),*]))
} else {
quote!(::std::option::Option::None)
}
} else {
quote!(::std::option::Option::None)
};
为 false 时是 None,二进制里根本没有 bundle.global.js 这 41 KB,运行时无从注入。为 true 时,prepare_pending_webview 把每段脚本逐个包成 main_frame_script 追加到初始化脚本里,只进主 frame。__TAURI_INTERNALS__ 所在的那几段脚本同样只进主 frame,所以 iframe 里两个都没有。
__TAURI__ 是 @tauri-apps/api 打包成的 IIFE,__TAURI__.core.invoke 就是 core.ts 的 invoke,函数体只有一句 return window.__TAURI_INTERNALS__.invoke(cmd, args, options)(见初始化脚本的「withGlobalTauri」)。
1.x 的开关是 build.withGlobalTauri。#8723(「feat(core): implement new config structure」,首次发布于 tauri 2.0.0-beta.0)按 RFC 5 重排了整份配置,CHANGELOG 的 Breaking Changes 里有一行:「Moved build.withGlobalTauri to app.withGlobalTauri.」同一批改动还把 tauri 对象改名为 app、build.distDir 改名为 frontendDist。这就是上面第二种情形的来历:照 1.x 写法放在 build 下的开关,在 2.x 的 BuildConfig 里是未知字段。
修法
一:打开开关,放在 app 下。
"app": {
"withGlobalTauri": true, "windows": [{ "title": "global", "width": 400, "height": 300, "visible": false }]
}
REPORT {
"typeof___TAURI__": "object",
"call": "invoke is a function",
"iframe": {
"typeof___TAURI__": "undefined",
"typeof___TAURI_INTERNALS__": "undefined"
}
}
主 frame 拿到了 __TAURI__;iframe 仍然没有,这是设计如此。iframe 里的代码要调命令,就让它 postMessage 给父页面,由父页面代调,并在父页面里检查消息的来源。
二:用打包器的项目不需要这个开关。 import { invoke } from '@tauri-apps/api/core' 把同一份 API 打进你自己的包,直接使用 __TAURI_INTERNALS__,不依赖 window.__TAURI__。开关是给不用打包器的纯 HTML 页面准备的;开着它,页面上任何脚本都能从全局拿到整套 API。
怎么确认没中招
- 页面里
typeof window.__TAURI__和typeof window.__TAURI_INTERNALS__各打一次。前者undefined、后者object:开关没开;两个都是undefined:代码不在 tauri 的主 frame 里,可能是 iframe,也可能是在普通浏览器里打开了开发服务器的地址。 - 搜
tauri.conf.json(以及tauri.*.conf.json、TAURI_CONFIG)里的withGlobalTauri,确认它在app下。 - 用打包器的项目搜
window.__TAURI__,换成@tauri-apps/api的导入,就不再依赖这个开关。
小结
window.__TAURI__只在app.withGlobalTauri为true时注入,默认关闭;关闭时连脚本都不进二进制。- 1.x 的
build.withGlobalTauri在 2.x 是未知字段,tauri-build直接报错,提示里的cargo update是误导。 - 打开后也只进主 frame,iframe 里
__TAURI__和__TAURI_INTERNALS__都没有。 - 用打包器时改用
@tauri-apps/api的导入,不需要全局对象。