Hacktoberfest 2026:维护者为十月标记出来的 issue,仍然开放、适合新手。 浏览 Hacktoberfest issue

[2.0.0-rc.18] renderToStream: one value seroval cannot serialize leaves every value in its hydration batch unresolved

已关闭
#4,242 1 条评论 0 个 reaction 已指派 0 人 在 GitHub 查看

维护者通常 1 天内回复

还没有人认领这个 Issue。

评估

难度
4/5
预计耗时
3-5 天
新手友好度
48/100
Issue 类型
缺陷
描述清晰度
描述清楚
活跃度
活跃
领域
backend

调研方向

Start by running the supplied repro.mjs with solid-js and @solidjs/web at rc.17 and rc.18, then trace renderToStream and getHydrationWriter in the @solidjs/web source to find where writes are batched and serialized. Check how a serialization failure affects neighboring keys and the nested boundary IDs. Done when an unsupported value leaves unrelated keys and boundary completion flags resolvable, with the repro confirming the behavior.

由索引模型根据 Issue 内容生成。

描述

Summary

On the server, a Loading boundary whose async memo resolves to another Loading boundary (an Element) streams a document in which none of the hydration batch's boundaries ever resolves on rc.18. On rc.17 only the outer memo's own value was left unresolved, and the boundaries themselves completed.

The outer memo's value is the inner boundary's function ($lhSkip), which seroval cannot serialize, and renderToStream logs SerovalUnsupportedTypeError on both versions. On rc.17 that error drops just that key. On rc.18 the same error drops every value written in the same $B batch, including both boundaries' completion flags (_fr) and the inner memo's value, which serialized fine. The client then waits forever on those boundaries.

seroval 1.7.1 forced through overrides behaves like rc.18's pinned 1.7.0, so the change looks like how rc.18's writer batches values, with seroval 1.7 ending a write at its first failing value.

Reproduction

In a fresh directory, install one version pair:

npm install --save-exact [email protected] @solidjs/[email protected]
# Then repeat in a separate directory with both packages at 2.0.0-rc.18.
node --conditions=node repro.mjs

repro.mjs:

import { createComponent, createMemo, Loading } from "solid-js";
import { renderToStream } from "@solidjs/web";

// A Loading boundary whose async memo resolves to a value.
function slot(read, fallback) {
  return createComponent(Loading, {
    fallback,
    get children() {
      const value = createMemo(async () => read());
      return [() => value()];
    }
  });
}

const flat = () => slot(async () => "flat-41", "outer");
// The outer memo resolves to an inner Loading boundary (an Element).
const nested = () =>
  slot(() => Promise.resolve(1).then(v => slot(async () => `nested-${v * 41}`, "inner")), "outer");

function render(app) {
  return new Promise(resolve => {
    let text = "";
    renderToStream(app).pipe({ write: c => void (text += c), end: () => resolve(text) });
  });
}

for (const [name, app] of [["flat", flat], ["nested", nested]]) {
  const text = await render(app);
  const keys = [...text.matchAll(/"?([\w]+)"?:\$R\[(\d+)\]=\(\$R\[(\d+)\]=/g)].map(m => [m[1], m[3]]);
  const settled = new Set([...text.matchAll(/\(\$R\[(\d+)\],/g)].map(m => m[1]));
  console.log(`${name}: body ${JSON.stringify(text.slice(0, text.indexOf("<script>")))}`);
  for (const [key, ref] of keys) console.log(`  ${key}: ${settled.has(ref) ? "resolved" : "NEVER RESOLVED"}`);
}

Output

rc.17:

flat: body "flat-41"
  0_fr: resolved
  0000: resolved
SerovalUnsupportedTypeError: Seroval Error (specific: 1)
  value: [Function (anonymous)] { '$lhSkip': true }
nested: body "nested-41"
  0_fr: resolved
  0000: NEVER RESOLVED
  undefined_fr: resolved
  undefined000: resolved

rc.18 (and rc.18 with seroval 1.7.1):

flat: body "flat-41"
  0_fr: resolved
  0000: resolved
SerovalUnsupportedTypeError: Seroval Error (specific: 1)
  value: [Function (anonymous)] { '$lhSkip': true }
nested: body "nested-41"
  0_fr: NEVER RESOLVED
  0000: NEVER RESOLVED
  undefined_fr: NEVER RESOLVED
  undefined000: NEVER RESOLVED

The raw rc.18 tail is ..._$HY.r["$B"]=($R[0]={"0_fr":...,"0000":...,undefined_fr:...,undefined000:...});(b=>{...})(_$HY.r["$B"]);</script> with no resolver calls after it, where rc.17 ends with ($R[9],"nested-41");$R[11]($R[2],!0);$R[11]($R[7],!0);.

The same rule reaches values an app writes itself. Two writes in one tick, one resolving to a function, drop the other on rc.18 and stream it on rc.17:

import { renderToStream, getHydrationWriter } from "@solidjs/web";

const app = () => {
  const writer = getHydrationWriter();
  writer.write("bad", Promise.resolve(() => 10));
  writer.write("fine", Promise.resolve("still here"));
  return "odd";
};
const text = await new Promise(resolve => {
  let t = "";
  renderToStream(app).pipe({ write: c => void (t += c), end: () => resolve(t) });
});
console.log(`"still here" streamed: ${text.includes("still here")}`);

This prints true on rc.17 and false on rc.18, after the same SerovalUnsupportedTypeError on both.

Expected

A value that cannot be serialized fails only its own key, as on rc.17, and the boundaries in the same batch still complete. Separately, the inner boundary's keys read undefined_fr and undefined000 on both versions, which looks like a missing id for a boundary created inside a resolved memo.

Environment

Node 26.10.0, solid-js and @solidjs/web 2.0.0-rc.17 and 2.0.0-rc.18 (seroval 1.6.7 and 1.7.0 as each pins), Linux.

主要语言
TypeScript
星标
36.2k
派生
1.1k
平均合并
11 小时 5 分钟
30 天内合并 PR
353

环境准备

从这里开始

  1. 先读完整个 Issue,再读项目的贡献指南。
  2. 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
  3. Fork 仓库,在一个分支上完成修改。
  4. 提交 Pull Request,并在描述里引用这个 Issue 编号。

solidjs/solid 的其他 Issue

查看 solidjs/solid 的全部 Issue

相似的 Issue

更多 TypeScript Issue

把新 issue 发到你的邮箱

精选适合新手参与的 GitHub issue 摘要。