拓冰建站拓冰建站
首页 / 资讯中心 / 正文

Svelte `unresolved_hydratable` 服务器警告解析:`hydratable` 在 SSR 中悬而未决的原因、机制与修复方案

Svelteunresolved_hydratable服务器警告解析hydratable在 SSR 中悬而未决的原因、机制与修复方案【免费下载链接】svelteweb development for the rest of us项目地址: https://gitcode.com/GitHub_Trending/sv/svelte本文围绕 Svelte 仓库中由消息处理脚本自动生成的服务器警告参考文档 server-warnings.md 展开。该文档目前收录了唯一一个服务器端警告unresolved_hydratable它对应 Svelte 异步 SSR 中hydratableAPI 的一种典型误用场景。读完本文你将理解这条警告的触发条件与判定时机、其背后服务器序列化 — 客户端水合恢复的完整数据流以及如何按照官方建议正确改写组件以消除警告。警告总览unresolved_hydratable是什么unresolved_hydratable是 Svelte 在服务器端渲染SSR阶段发出的开发期警告。当某个hydratable值被创建、但在整个渲染过程中至少有它的一部分promise未被消费时就会触发该警告。警告的完整模板见 server-warnings.md为A hydratable value with key %key% was created, but at least part of it was not used during the render. The hydratable was initialized in: %stack%其中%key%是你传给hydratable(key, fn)的字符串键%stack%是该hydratable初始化位置的用户代码调用栈。这条警告最终输出的格式由消息处理管线生成源文件 warnings.md 经 scripts/process-messages/index.js 处理同时产出运行时代码 internal/server/warnings.js 与文档站参考页 98-reference/.generated/server-warnings.md。运行时代码中可以看到行为差异// 摘自 packages/svelte/src/internal/server/warnings.js export function unresolved_hydratable(key, stack) { if (DEV) { console.warn( %c[svelte] unresolved_hydratable\n%cA \hydratable\ value with key \${key}\ was created, but at least part of it was not used during the render. ... ); } else { // 生产环境下仅输出一条指向官方错误码参考页的简短链接 console.warn(https://svelte.dev/e/unresolved_hydratable); } }也就是说详细信息key 初始化调用栈只在开发模式DEV下完整打印生产模式会退化为一条简短的错误码链接便于线上排障时跳转到官方文档定位问题。官方诊断最典型的触发场景参考文档给出的最可能原因是在组件的script块中创建hydratable却在带pending片段的svelte:boundary内部await它的结果。文档给出的反模式示例如下script import { hydratable } from svelte; import { getUser } from $lib/get-user.js; const user hydratable(user, getUser); /script svelte:boundary h1{(await user).name}/h1 {#snippet pending()} divLoading.../div {/snippet} /svelte:boundary问题在于script块中的hydratable(user, getUser)在组件实例化时无条件执行此时它注册了一个 promise 并期望被序列化进 HTML但如果服务器端渲染时svelte:boundary走的是pending分支例如该边界因上游错误提前降级或渲染在 promise 解决前就已结算这个 promise 就永远不会被 await 消费。渲染结束时它仍悬挂在未解决 promise 集合里Svelte 便会警告你正在为一个永远不会出现在响应里的内容阻塞响应。文档同时给出了官方修复建议把hydratable调用内联到 boundary 内部这样它只在实际需要该异步内容时才被调用服务器端根本不会创建这个悬空条目。此外文档还提示了一种更隐蔽的情形当同一个hydratable的值中包含多个 promise且只有其中一部分被使用时同样会触发此警告——因为判定粒度是逐个 promise而非整个hydratable值。底层机制警告在渲染管线的何处被判定要理解这条警告为何可信需要看它发出的位置。在 internal/server/renderer.js 中异步渲染流程在完成内容收集后会执行#collect_hydratables()// 摘自 packages/svelte/src/internal/server/renderer.js async #collect_hydratables() { const ctx get_render_context().hydratable; for (const [_, key] of ctx.unresolved_promises) { // 问题所在渲染已经结束但仍有一个 promise 未解决 // 导致我们在为无用内容阻塞响应 w.unresolved_hydratable(key, ctx.lookup.get(key)?.stack ?? missing stack trace); } for (const comparison of ctx.comparisons) { await comparison; } return await this.#hydratable_block(ctx); }调用链是Renderer.#render_async()先await renderer.#collect_content_async()收集完所有 HTML 内容再调用#collect_hydratables()见 renderer.js 中 L741-L745。此时任何仍留在unresolved_promises集合里的 promise都意味着它们没有被任何await消费——这正是警告的判定依据。这也解释了文档中多个 promise 部分使用的场景只要有一个 promise 未解决对应 key 就会被报告。hydratable在服务器端的注册与序列化服务器端实现位于 internal/server/hydratable.js。核心逻辑键去重以 key 为索引维护hydratable.lookup。同一 key 第二次调用时直接返回已缓存的值DEV下还会对两次序列化结果做异步比对若不一致则抛出hydratable_clobbering错误见 compare 函数首次调用时序列化encode(key, value, hydratable.unresolved_promises)用devalue.uneval把值序列化为可嵌入script的 JS 字面量promise 占位符机制序列化遇到 promise 时先放入形如1的占位符源码注释解释了为何选这种格式——带引号的序号字符串在自然数据中不可能出现随后注册unresolved映射并await该 promise解析成功后把占位符替换为r(序列化结果)见 encode 函数// 摘自 packages/svelte/src/internal/server/hydratable.jsencode 内部 unresolved?.set(p, key); // 防止未处理的 rejection 搞垮服务器 // 并在渲染结束时追踪哪些 promise 仍未解决 p.catch(() {}).finally(() unresolved?.delete(p));注意finally(() unresolved?.delete(p))promise 一解决就会从未解决集合中移除。所以#collect_hydratables()里还剩的条目是渲染结束时仍在等待中的 promise——它们既无法被写进 HTML又白白拖住了响应。序列化结果如何到达浏览器#collect_hydratables()末尾会调用#hydratable_block(ctx)renderer.js把所有已解决条目的key: serialized对注入head的一个script中let prelude const h (window.__svelte ?? {}).h ?? new Map();; // 若含 promise 结果还会附加 r (v) Promise.resolve(v) // 最终生成形如 // const h (window.__svelte ?? {}).h ?? new Map(); // for (const [k, v] of [ [user, r({...})] ]) { h.set(k, v); }客户端水合阶段internal/client/hydratable.js 中的同名hydratable(key, fn)会优先从window.__svelte?.h中按键取值命中则直接复用服务器结果跳过fn未命中且在DEV下则报hydratable_missing_but_required。这就是完整的闭环——服务器把数据提前放进 HTML客户端跳过重复请求。单元测试 internal/server/hydratable.test.ts 验证了这个 head 注入格式还专门覆盖了替换占位符时的安全性细节promise 解析值里若含$这类替换 tokenString.replace必须使用函数形式防止被当作替换模式解释源码中的注释直接引用了 MDN 的相关说明。用仓库测试用例验证警告行为仓库内置了一个端到端测试样本 tests/runtime-runes/samples/hydratable-unused-keys它精确复刻了文档描述的误用场景!-- packages/svelte/tests/runtime-runes/samples/hydratable-unused-keys/main.svelte -- script langts import { hydratable } from svelte; const { environment } $props(); const unresolved_hydratable hydratable( unused_key, () new Promise( (res, rej) environment server ? setTimeout(() res(did you ever hear the tragedy of darth plagueis the wise?), 0) : rej(should not run) ) ); /script svelte:boundary div{await unresolved_hydratable}/div {#snippet pending()} divLoading.../div {/snippet} /svelte:boundary配套的 _config.js 断言了三层行为SSR 阶段恰好产生 1 条警告且内容包含A hydratable value with key unused_key——证明警告在服务器端、渲染完成时点发出SSR 输出的 HTML 是divLoading.../div走pending分支promise 结果没进 HTML水合后客户端仍渲染出 promise 的最终值且客户端的fn从未被调用测试中environment ! server分支若执行会 reject测试借此反证服务器数据被正确复用。注意该测试要求skip_no_async: true并且运行模式为async-serverhydrate。这也提示了一个适用前提hydratable与本文讨论的异步边界能力依赖 Svelte 的实验性异步模式——服务器与客户端实现都在入口处检查async_mode_flag未开启时直接抛出experimental_async_required见 server/hydratable.js 与 client/hydratable.js。修复实践如何消除unresolved_hydratable结合文档建议与上述机制修复思路可以归纳为两条1. 把hydratable调用内联到真正消费它的位置。不要在script块顶层无条件创建而是放到svelte:boundary或其他确定会 await 它的异步上下文内部调用让创建与消费在服务器端同步发生。这样当渲染走pending或错误降级分支时根本不会产生悬空条目。2. 对包含多个 promise 的复合值确保每个 promise 都会被 await。判定粒度是 promise 级别的unresolved_promises中任何一项未解决都会按 key 报告。如果你构造的是hydratable(data, () [fetchA(), fetchB()])这类复合值模板中只消费其一就会触发警告——请补齐消费或拆分为独立 key 并保证各自都被使用。排查时的定位线索直接来自警告本身%stack%给出初始化位置key帮你在全局代码中grep出所有创建点再对照#collect_hydratables的判定逻辑渲染结束 集合非空即告警基本可以快速锁定是哪条分支跳过了 await。小结unresolved_hydratable是 Svelte 服务器端唯一的运行时警告当前版本参考文档仅收录此一条完整文本由 98-reference/.generated/server-warnings.md 定义运行时代码在 internal/server/warnings.js它的语义是你创建了hydratable但渲染结束时仍有 promise 未被消费由 renderer.js 在异步渲染收尾阶段基于unresolved_promises集合判定典型成因是script块中创建、boundary 中未走成功分支消费修复方式是内联调用或补齐所有 promise 的消费该机制依赖实验性异步模式完整闭环测试见 hydratable-unused-keys 测试样本可用其作为回归验证的参考实现。【免费下载链接】svelteweb development for the rest of us项目地址: https://gitcode.com/GitHub_Trending/sv/svelte创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门