mirror of
https://github.com/Darkatse/TauriTavern.git
synced 2026-10-19 15:45:12 +00:00
10 KiB
10 KiB
开屏启动优化(Stage-based Boot)现状
本文档描述 当前已经落地 的开屏启动优化实现:它解决什么问题、端到端链路如何工作、阶段语义与契约是什么、以及后续开发最容易误改的边界。
规划与设计背景见:
docs/StartupOptimizationPlan.md(本文只写“现在怎么跑”)。
1. 范围与结论
当前开屏优化的核心落点是把启动从“一个巨大的串行 init”拆成 可见可点(Shell)→ 核心可用(Core)→ 主应用可交互(Full / APP_READY),并同时降低首屏 JS/库解析负担。
已落地的关键点:
- Shell 先到:
#preloader在firstLoadInit()的 Shell 阶段就移除,主页可以尽早可见可点。 - Host Ready 显式等待:在首次
/api/*访问前等待__TAURITAVERN_MAIN_READY__,避免拦截器/路由未安装导致的“偶发首包失败”。 - bootstrap 快照:用一次
/api/bootstrap拉齐启动关键数据,并在前端以“prime snapshot”方式避免重复请求(settings/characters/groups/avatars/secret_state)。 - 扩展启动分层:扩展发现可提前后台启动;系统扩展仍在 Full 阶段完成,local/global third-party 扩展延后到
APP_READY后串行激活,从启动关键路径移出。 - lib.bundle 拆分为 core/optional:重库(如
highlight.js/ Readability)迁到可选 bundle,通过lib.js的 async helper 按需加载,减少首屏解析/编译压力。 - 重任务后移:
initTokenizers()、initScrapers()在APP_READY后、两次 paint 之后后台启动。
2. 端到端链路(从 index.html 到 APP_READY)
2.1 HTML 入口与预加载遮罩
src/index.html- 首屏遮罩:
<div id="preloader"></div> - 模块入口:
<script type="module" src="init.js"></script> - 约束:不使用 modulepreload(移动端 WebView 上曾有缓存/预加载失败问题)。
- 首屏遮罩:
2.2 Bootloader:分层 import + Android import 重试
src/init.js- 设置:
window.__TAURI_RUNNING__ = true,并计算globalThis.__TAURITAVERN_PERF_ENABLED__ - 通过
importWithRetry()依次加载:./lib.js(静态依赖src/dist/lib.core.bundle.js)./tauri-main.js(安装 Host Kernel:路由/拦截器/ABI)./script.js(SillyTavern 主前端)
- 目的:在 Android WebView 首启 I/O 抖动场景下提高模块加载确定性。
- 设置:
2.3 Host Kernel:把同源 /api 变成可路由端点
src/tauri/main/bootstrap.js- 安装
window.__TAURITAVERN__(稳定 Host ABI):invoke broker / 资源路径 helpers 等 - Patch:
fetch拦截(同源、命中路由表则走router.handle(...))jQuery.ajax拦截(同源、命中路由表则转发)- same-origin iframe/window 的补丁(保证扩展/脚本在 iframe 内也能命中路由/下载桥)
- 设置 readiness:
window.__TAURITAVERN_MAIN_READY__ = readyPromise- 前端用
waitForTauriMainReady()等它,确保首次/api/*调用前 Host 已就绪。
- 安装
2.4 前端启动编排:Shell → Core → Full(保持 APP_READY 语义)
src/script.js:firstLoadInit()- Shell 阶段
removePreloader()(src/scripts/loader.js):移除#preloader- 初始化纯前端 UI/DOM handler、基础 patch(不会做
/api/*)
- Core 阶段
await waitForTauriMainReady():保证 Host 拦截器就绪/csrf-token+ 并发启动fetch('/api/bootstrap')initSecrets()+primeSecretStateSnapshot(...)+readSecretState()initLocales()、默认 slash commands、模型/设置等核心模块初始化
- Full 阶段
- 应用 settings/角色/群组/头像快照:
applySettingsSnapshot(bootstrap.settings)applyCharactersSnapshot(bootstrap.characters)applyGroupsSnapshot(bootstrap.groups)primeUserAvatarsSnapshot(bootstrap.avatars)+getUserAvatars(...)
- 扩展(如果启用):
- 后台:
startOfflineExtensionsDiscovery()(可在 Core 阶段提前启动) - Full:
activateStartupSystemExtensions({ parallelism })只激活系统扩展,并在需要时提前完成 Extras auto-connect
- 后台:
- 对外事件保持原语义:
event_types.APP_INITIALIZED→event_types.APP_READY
- 应用 settings/角色/群组/头像快照:
- Post-ready(后台)
initTokenizers()、initScrapers():在APP_READY后异步执行(避免阻塞首屏)。- 若存在启用中的 third-party 扩展:
activateDeferredThirdPartyExtensions({ parallelism: 1 })在首屏完成后后台激活,并在完成后 emitEXTENSION_SETTINGS_LOADED。
- Shell 阶段
3. 阶段语义与全局信号(契约)
3.1 内部阶段(UI 体验)
Shell:允许 UI 可见/可点;不允许依赖扩展已激活。Core:允许进行首次/api/*与核心数据加载;扩展仍可能未激活。Full:完成主应用可交互所需初始化与系统扩展激活;third-party 扩展可能仍在后台补全。
当前暴露的信号:
globalThis.__TAURITAVERN_STARTUP_STAGE__:由firstLoadInit()写入(shell/core/full)event_types.APP_READY:作为“主应用可交互”的稳定语义;晚加载的扩展依赖其 auto-fire 行为完成 ready 钩子
3.2 Host 就绪(拦截器与路由)
window.__TAURITAVERN_MAIN_READY__:在src/tauri/main/bootstrap.js设置waitForTauriMainReady():前端轮询等待该 promise 注册并 resolve(src/scripts/extensions/runtime/tauri-ready.js)
4. bootstrap 快照(/api/bootstrap)现状
4.1 前端消费
src/script.js:fetch('/api/bootstrap', { method: 'POST' })- 负载(当前使用字段):
settings:直接喂给applySettingsSnapshot(...)characters:直接喂给applyCharactersSnapshot(...)groups:直接喂给applyGroupsSnapshot(...)avatars:喂给primeUserAvatarsSnapshot(...)secret_state:喂给primeSecretStateSnapshot(...)
4.2 Tauri 主进程路由
src/tauri/main/routes/bootstrap-routes.jsrouter.post('/api/bootstrap', ...)context.safeInvoke('get_bootstrap_snapshot')- 角色做一次
context.normalizeCharacter(...)后返回 JSON
4.3 Rust 命令(并发采样)
src-tauri/src/presentation/commands/bootstrap_commands.rs:get_bootstrap_snapshot- 使用
tokio::try_join!并发获取:settings / characters / groups / avatars / secret_state - 目的:减少启动关键路径的串行 I/O 等待。
- 使用
5. 扩展加载(离线发现 + 激活批处理)现状
详细扩展兼容链路见:
docs/CurrentState/ThirdPartyExtensions.md
当前策略要点:
- 发现:
startOfflineExtensionsDiscovery()等待 Host Ready 后调用/api/extensions/discover,并加载各扩展manifest.json。 - 系统扩展激活:
activateStartupSystemExtensions({ parallelism })- 同
loading_order的扩展分组;组内按parallelism分块Promise.all并在 chunk 间delay(0)主动让出事件循环。 - Android 设备默认
parallelism = 1;其他默认parallelism = 2(见src/script.js)。
- 同
- third-party 激活:
activateDeferredThirdPartyExtensions({ parallelism: 1 })- 仅处理
local/global扩展,默认在APP_READY后执行,避免把大体量 third-party 模块求值放进启动关键路径。
- 仅处理
- third-party 兼容(hljs):
- third-party 扩展激活前会
await getHljs(),确保window.hljs存在(src/scripts/extensions.js)。
- third-party 扩展激活前会
6. lib.core / lib.optional(按需加载)现状
6.1 产物与入口
- webpack 入口:
webpack.config.jslib.core→src/lib-bundle-core.js→src/dist/lib.core.bundle.jslib.optional→src/lib-bundle-optional.js→src/dist/lib.optional.bundle.js
6.2 前端门面(lib.js)
src/lib.js- 静态 import core bundle,并 re-export 上游常用库
- 可选库按需加载:
getHljs():动态 import optional bundle;并注册stscript语言(src/scripts/slash-commands/stscript-hljs-language.js)getReadability():动态 import optional bundle
initLibraryShims():将少量库挂到window(用于第三方扩展兼容)
6.3 代码高亮(延迟执行)
src/scripts/tauri/perf/code-highlight-coordinator.js- 通过
IntersectionObserver+requestIdleCallback做“近视区”高亮 - 内部通过
getHljs()拉起可选 bundle(不进入 Shell/Core 静态依赖图)
- 通过
7. Panel Runtime 的扩展 DOM “停车”与兼容白名单
Panel Runtime 会在 APP_READY 后安装,用于在抽屉关闭时把部分面板 DOM 子树 park 到 DocumentFragment,减少低端设备的 DOM/observer 压力。
已知兼容点:
src/tauri/main/adapters/panel-runtime/extensions-subtree-gates.js- 当前对扩展设置容器启用“子树 gate + park/hydrate”
- 白名单永远保持连接:
regex_container、qr_container- 目的:避免 SPresets 等脚本在抽屉关闭时找不到
#saved_regex_scripts触发MutationObserver.observe(target not Node)。
- 目的:避免 SPresets 等脚本在抽屉关闭时找不到
8. 可观测性(perf + 状态)
- Perf 开关:
localStorage tt:perf = '1'或 URL?ttPerf=1 - 标记(部分):
src/init.js:tt:init:*src/tauri/main/bootstrap.js:tt:tauri:*src/script.js:tt:startup:shell/core/full+tt:startup:ready
- 运行时提示:
src/scripts/tauri/startup/startup-status-overlay.js:右下角非阻塞启动状态 overlay(APP_READY后移除)
9. 明确支持 / 不支持边界
支持:
- 主页尽早可见可点(Shell 先到)。
APP_READY表示主应用已可交互;late-loaded third-party 扩展依赖其 auto-fire 语义继续完成初始化。- third-party 扩展资源同源加载与
window.hljs兼容(见上)。
不保证(需要按需扩展白名单/契约):
- 任何第三方脚本在 扩展抽屉关闭 时都能访问到其期望的“扩展设置 DOM”(Panel Runtime 可能 park 子树;目前只对白名单容器强保证)。