From f0b9f3f5c23f1c93e2245359879fe0b474388516 Mon Sep 17 00:00:00 2001 From: kdletters Date: Mon, 22 Jun 2026 11:35:29 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E6=A1=8C=E9=9D=A2=E5=A3=B3?= =?UTF-8?q?=E5=BC=80=E5=8F=91=E5=85=A5=E5=8F=A3=E5=9C=B0=E5=9D=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 桌面壳 dev 模式手动创建主窗口时改用 Tauri devUrl 桌面导航策略允许本机 Vite devUrl 留在 WebView 内 补充桌面壳 devUrl 回归测试和文档记忆 --- apps/desktop-shell/scripts/check-config.mjs | 7 +++ apps/desktop-shell/src-tauri/src/app.rs | 49 ++++++++++++++++++- .../src-tauri/src/shell/navigation.rs | 16 ++++++ .../shared-memory/decision-log.md | 4 +- docs/project-memory/shared-memory/pitfalls.md | 8 +++ ...€‘ExpoReactNative与Tauri宿主壳方案-2026-06-17.md | 2 +- 6 files changed, 82 insertions(+), 4 deletions(-) diff --git a/apps/desktop-shell/scripts/check-config.mjs b/apps/desktop-shell/scripts/check-config.mjs index b2b76828a..328cce0c3 100644 --- a/apps/desktop-shell/scripts/check-config.mjs +++ b/apps/desktop-shell/scripts/check-config.mjs @@ -2772,12 +2772,16 @@ const requiredRustHostSnippets = [ 'desktop_h5_url_with_host_context(target_url)', 'desktop_h5_url_with_host_context(normalized_url)', 'desktop_window_config_with_runtime_platform', + 'apply_desktop_dev_url_to_main_window_config', 'DESKTOP_MAIN_WINDOW_LABEL: &str = "main"', 'desktop_main_window_config_from_windows', '.find(|window| window.label == DESKTOP_MAIN_WINDOW_LABEL)', 'tauri::Error::WindowNotFound', 'desktop_main_window_config(app)?', 'desktop_main_window_config_uses_labeled_main_window', + 'desktop_main_window_config_uses_dev_url_in_dev_builds', + 'config.url = tauri::WebviewUrl::External(dev_url.clone())', + 'assert_eq!(url.origin().ascii_serialization(), "http://127.0.0.1:3000")', 'path.starts_with("index.html?")', 'path.contains("clientRuntime=native_app")', 'path.contains("clientType=native_app")', @@ -2787,6 +2791,9 @@ const requiredRustHostSnippets = [ 'path.contains("bridgeVersion=")', 'path.contains("hostCapabilities=")', 'should_allow_desktop_webview_navigation', + 'url.host_str() == Some("127.0.0.1")', + 'url.port_or_known_default() == Some(3000)', + 'Url::parse("http://127.0.0.1:3000/works/detail?work=PZ-1")', 'desktop_external_navigation_url', 'open_normalized_desktop_external_url', 'open_desktop_external_navigation', diff --git a/apps/desktop-shell/src-tauri/src/app.rs b/apps/desktop-shell/src-tauri/src/app.rs index 126c99ed8..5ebd709cc 100644 --- a/apps/desktop-shell/src-tauri/src/app.rs +++ b/apps/desktop-shell/src-tauri/src/app.rs @@ -32,10 +32,24 @@ fn desktop_main_window_config_from_windows( .ok_or(tauri::Error::WindowNotFound) } +#[cfg(dev)] +fn apply_desktop_dev_url_to_main_window_config( + mut config: tauri::utils::config::WindowConfig, + dev_url: &tauri::Url, +) -> tauri::utils::config::WindowConfig { + config.url = tauri::WebviewUrl::External(dev_url.clone()); + desktop_window_config_with_runtime_platform(config) +} + fn desktop_main_window_config( app: &tauri::App, ) -> tauri::Result { - desktop_main_window_config_from_windows(&app.config().app.windows) + let config = desktop_main_window_config_from_windows(&app.config().app.windows)?; + #[cfg(dev)] + if let Some(dev_url) = app.config().build.dev_url.as_ref() { + return Ok(apply_desktop_dev_url_to_main_window_config(config, dev_url)); + } + Ok(config) } fn desktop_new_window_external_url(url: &tauri::Url) -> Option { @@ -157,6 +171,39 @@ mod tests { } } + #[cfg(dev)] + #[test] + fn desktop_main_window_config_uses_dev_url_in_dev_builds() { + let mut main_window = tauri::utils::config::WindowConfig::default(); + main_window.label = "main".to_string(); + main_window.url = tauri::WebviewUrl::App(PathBuf::from("index.html")); + + let mut config = tauri::utils::config::Config::default(); + config.app.windows = vec![main_window]; + config.build.dev_url = + Some(tauri::Url::parse("http://127.0.0.1:3000/").expect("desktop dev url")); + + let mut resolved = desktop_main_window_config_from_windows(&config.app.windows) + .expect("main window config"); + if let Some(dev_url) = config.build.dev_url.as_ref() { + resolved = apply_desktop_dev_url_to_main_window_config(resolved, dev_url); + } + + match resolved.url { + tauri::WebviewUrl::External(url) => { + assert_eq!(url.origin().ascii_serialization(), "http://127.0.0.1:3000"); + assert_eq!(url.path(), "/"); + assert_eq!( + url.query_pairs() + .find(|(key, _)| key == "hostShell") + .map(|(_, value)| value.into_owned()), + Some("tauri_desktop".to_string()) + ); + } + other => panic!("unexpected dev main window url {other:?}"), + } + } + #[test] fn desktop_main_window_config_rejects_missing_main_window() { let mut secondary_window = tauri::utils::config::WindowConfig::default(); diff --git a/apps/desktop-shell/src-tauri/src/shell/navigation.rs b/apps/desktop-shell/src-tauri/src/shell/navigation.rs index 8ff40270e..facb4f58c 100644 --- a/apps/desktop-shell/src-tauri/src/shell/navigation.rs +++ b/apps/desktop-shell/src-tauri/src/shell/navigation.rs @@ -134,6 +134,14 @@ pub(crate) fn should_allow_desktop_webview_navigation(url: &Url) -> bool { return true; } + #[cfg(dev)] + if url.scheme() == "http" + && url.host_str() == Some("127.0.0.1") + && url.port_or_known_default() == Some(3000) + { + return true; + } + if url.scheme() == "https" { let base_url = Url::parse(WEB_APP_ORIGIN).ok(); return base_url @@ -249,6 +257,14 @@ mod tests { .expect("same-origin url"); assert!(should_allow_desktop_webview_navigation(&same_origin_url)); assert_eq!(desktop_external_navigation_url(&same_origin_url), None); + + #[cfg(dev)] + { + let dev_url = + Url::parse("http://127.0.0.1:3000/works/detail?work=PZ-1").expect("dev url"); + assert!(should_allow_desktop_webview_navigation(&dev_url)); + assert_eq!(desktop_external_navigation_url(&dev_url), None); + } } #[test] diff --git a/docs/project-memory/shared-memory/decision-log.md b/docs/project-memory/shared-memory/decision-log.md index 8b8b8d39a..fe49ce155 100644 --- a/docs/project-memory/shared-memory/decision-log.md +++ b/docs/project-memory/shared-memory/decision-log.md @@ -3268,6 +3268,6 @@ ## 2026-06-21 Tauri devUrl 与 Vite 端口必须显式对齐 - 背景:`npm run desktop-shell:dev` 通过 Tauri `devUrl` 固定加载 `http://127.0.0.1:3000/`,但 Linux dev 端口段逻辑会把未显式指定的 `dev:web` 主站端口映射到用户端口段,例如 `10000+`。只在桌面壳 package script 里设置 `WEB_PORT=3000` 不会让 `scripts/dev.mjs` 把 Web 端口视为显式 CLI 参数,结果 Tauri 仍打开 3000,而 Vite 实际监听其它端口。 -- 决策:桌面壳 `beforeDevCommand` 必须执行 `npm --prefix ../.. run dev:web -- --web-port 3000 --strict-web-port`,用 CLI 参数锁定主站 Vite 端口并禁止静默漂移;`devUrl` 继续固定 `http://127.0.0.1:3000/`。如果 3000 被占用,应该释放端口后再启动桌面壳,而不是让 Vite 漂移后继续由 Tauri 加载旧端口。 +- 决策:桌面壳 `beforeDevCommand` 必须执行 `npm --prefix ../.. run dev:web -- --web-port 3000 --strict-web-port`,用 CLI 参数锁定主站 Vite 端口并禁止静默漂移;`devUrl` 继续固定 `http://127.0.0.1:3000/`。如果 3000 被占用,应该释放端口后再启动桌面壳,而不是让 Vite 漂移后继续由 Tauri 加载旧端口。由于主窗口设置了 `create=false` 并由 Rust 手动创建,`app.rs` 在 dev build 下必须把主窗口 URL 替换为 `build.devUrl` 后再补写 HostBridge query;release 仍从 `index.html` 打包资源进入。 - 影响范围:`apps/desktop-shell/src-tauri/tauri.conf.json`、`apps/desktop-shell/scripts/check-config.mjs`、`scripts/dev.test.ts`、Expo / Tauri HostBridge 方案文档。 -- 验证方式:`npm run test -- scripts/dev.test.ts -t "Linux 桌面壳显式指定 web-port"`、`npm run desktop-shell:typecheck`、`npm run check:native-shells`、`npm run check:encoding`、`git diff --check`。 +- 验证方式:`npm run test -- scripts/dev.test.ts -t "Linux 桌面壳显式指定 web-port"`、`cargo test --manifest-path apps/desktop-shell/src-tauri/Cargo.toml desktop_main_window_config_uses_dev_url_in_dev_builds`、`npm run desktop-shell:typecheck`、`npm run check:native-shells`、`npm run check:encoding`、`git diff --check`。 diff --git a/docs/project-memory/shared-memory/pitfalls.md b/docs/project-memory/shared-memory/pitfalls.md index cec5b6a7b..d3f1c493d 100644 --- a/docs/project-memory/shared-memory/pitfalls.md +++ b/docs/project-memory/shared-memory/pitfalls.md @@ -2206,6 +2206,14 @@ - 验证:`npm run test -- scripts/dev.test.ts -t "Linux 桌面壳显式指定 web-port"`、`npm run desktop-shell:typecheck`、实际启动时终端应显示 `[dev] web: http://127.0.0.1:3000`。 - 关联:`apps/desktop-shell/src-tauri/tauri.conf.json`、`apps/desktop-shell/scripts/check-config.mjs`、`scripts/dev.mjs`。 +## Tauri 手动创建主窗口时 devUrl 不会自动套到 index.html + +- 现象:`npm run desktop-shell:dev` 启动后窗口地址显示 `http://tauri.localhost/index.html` 或 `tauri://localhost/index.html`,即使 Vite 已经在 `http://127.0.0.1:3000/` 正常监听。 +- 原因:桌面壳为了注册导航、下载、生命周期和托盘行为,把 Tauri 配置里的主窗口设为 `create=false`,再在 Rust `app.rs` 中用 `WebviewWindowBuilder::from_config(...)` 手动创建窗口。此时如果只读取 `app.windows[].url = index.html` 并补 HostBridge query,手动窗口会沿 release 入口走打包资源协议;Tauri CLI 的 `build.devUrl` 不会自动替换这份手动克隆后的窗口 URL。 +- 处理:`app.rs` 在 dev build 下必须先把主窗口 URL 替换为 `config.build.dev_url`,再调用 `desktop_window_config_with_runtime_platform(...)` 补写宿主上下文;`shell/navigation.rs` 也必须允许 dev build 下的 `http://127.0.0.1:3000` 留在 WebView 内,不要把自己的 Vite 首页当外链交给系统浏览器。release build 保持 `index.html` 打包入口。 +- 验证:`cargo test --manifest-path apps/desktop-shell/src-tauri/Cargo.toml desktop_main_window_config_uses_dev_url_in_dev_builds desktop_webview_navigation_stays_on_packaged_or_same_origin_pages`,实际启动时窗口应加载 `http://127.0.0.1:3000/...` 而不是 `tauri.localhost/index.html`。 +- 关联:`apps/desktop-shell/src-tauri/src/app.rs`、`apps/desktop-shell/src-tauri/src/shell/url.rs`、`apps/desktop-shell/src-tauri/src/shell/navigation.rs`、`apps/desktop-shell/src-tauri/tauri.conf.json`。 + ## 自动试玩退出不要回到生成页 - 现象:拼图草稿生成完成后自动进入试玩,用户从试玩退出或使用系统返回时落回生成进度页,页面还暴露“重新生成”按钮。 diff --git a/docs/【前端架构】ExpoReactNative与Tauri宿主壳方案-2026-06-17.md b/docs/【前端架构】ExpoReactNative与Tauri宿主壳方案-2026-06-17.md index 04ade60cd..3bbb52897 100644 --- a/docs/【前端架构】ExpoReactNative与Tauri宿主壳方案-2026-06-17.md +++ b/docs/【前端架构】ExpoReactNative与Tauri宿主壳方案-2026-06-17.md @@ -560,7 +560,7 @@ GameBridge 禁止: 2026-06-18 追加:桌面壳 bundle 图标集从现有真实品牌 PNG `apps/desktop-shell/src-tauri/icons/icon.png` 派生,补齐 `32x32.png`、`128x128.png`、`128x128@2x.png`、`icon.ico` 和 `icon.icns`,Tauri `bundle.icon` 同时声明这些平台图标。没有引入外部素材或占位图;`apps/desktop-shell/scripts/check-config.mjs` 会校验 PNG 尺寸、ICO 多尺寸头部、ICNS 容器长度和 bundle 图标列表,避免后续退回单图标或替换为非品牌素材。 -2026-06-21 追加:桌面壳本地开发入口必须让 Tauri `devUrl` 和主站 Vite 实际监听端口保持一致。`apps/desktop-shell/src-tauri/tauri.conf.json` 的 `devUrl` 固定为 `http://127.0.0.1:3000/`,`beforeDevCommand` 必须执行 `npm --prefix ../.. run dev:web -- --web-port 3000 --strict-web-port`,不能只依赖 `WEB_PORT=3000` 环境变量;Linux 用户端口段分配会改写未显式指定的 Web 端口,裸 `dev:web` 可能漂移到 `10000+`,导致 Tauri 窗口加载 3000 时白屏或连接失败。3000 被占用时必须直接失败并提示释放端口,不能静默漂移。`apps/desktop-shell/scripts/check-config.mjs` 和 `scripts/dev.test.ts` 会反查该口径。 +2026-06-21 追加:桌面壳本地开发入口必须让 Tauri `devUrl` 和主站 Vite 实际监听端口保持一致。`apps/desktop-shell/src-tauri/tauri.conf.json` 的 `devUrl` 固定为 `http://127.0.0.1:3000/`,`beforeDevCommand` 必须执行 `npm --prefix ../.. run dev:web -- --web-port 3000 --strict-web-port`,不能只依赖 `WEB_PORT=3000` 环境变量;Linux 用户端口段分配会改写未显式指定的 Web 端口,裸 `dev:web` 可能漂移到 `10000+`,导致 Tauri 窗口加载 3000 时白屏或连接失败。3000 被占用时必须直接失败并提示释放端口,不能静默漂移。由于桌面壳主窗口设置了 `create=false` 并由 Rust 手动创建,`apps/desktop-shell/src-tauri/src/app.rs` 在 dev build 下必须把主窗口 URL 替换为 `build.devUrl` 后再补写 HostBridge query,不能继续用 release 的 `index.html`,否则 WebView 会落到 `tauri.localhost/index.html`。`apps/desktop-shell/scripts/check-config.mjs`、`scripts/dev.test.ts` 和桌面壳 Rust 单测会反查该口径。 2026-06-18 追加:移动壳生产包网络安全元数据显式收紧。Android `usesCleartextTraffic=false`,iOS `NSAppTransportSecurity.NSAllowsArbitraryLoads=false`,并设置 `ITSAppUsesNonExemptEncryption=false` 作为当前未接入自定义加密能力的出口合规声明;开发联调本机 Vite 仍通过显式 `EXPO_PUBLIC_GENARRATIVE_WEB_URL=http://127.0.0.1:3000/` 进入 development build,不把任意明文流量开关带进默认包配置。`apps/mobile-shell/scripts/check-config.mjs` 会校验这些网络安全字段。