@tanstack/react-start-rsc 0.1.15 → 0.1.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -11,7 +11,8 @@ function* findPendingLazyPayloads(obj, seen = /* @__PURE__ */ new Set(), cssColl
11
11
  const el = obj;
12
12
  if (el.$$typeof === ReactElement && el.type === ReactSuspense) return;
13
13
  if (el.$$typeof === ReactElement && el.type === "link" && el.props?.rel === "stylesheet") {
14
- const cssHref = el.props["data-rsc-css-href"];
14
+ let cssHref;
15
+ if ("data-rsc-css-href" in el.props) cssHref = el.props.href;
15
16
  if (cssHref && cssCollector) cssCollector(cssHref);
16
17
  }
17
18
  if (el.$$typeof === ReactLazy) {
@@ -1 +1 @@
1
- {"version":3,"file":"awaitLazyElements.js","names":[],"sources":["../../src/awaitLazyElements.ts"],"sourcesContent":["import { ReactElement, ReactLazy, ReactSuspense } from './reactSymbols'\n\n/**\n * Optional callback for collecting CSS hrefs during tree traversal.\n * Only called server-side when processing <link rel=\"stylesheet\" data-rsc-css-href>\n */\nexport type CssHrefCollector = (href: string) => void\n\n/**\n * Yields pending lazy element payloads from a tree, stopping at Suspense boundaries.\n * Also collects CSS hrefs from <link rel=\"stylesheet\" data-rsc-css-href> elements.\n */\nfunction* findPendingLazyPayloads(\n obj: unknown,\n seen = new Set(),\n cssCollector?: CssHrefCollector,\n): Generator<PromiseLike<unknown>> {\n if (!obj || typeof obj !== 'object') return\n if (seen.has(obj)) return\n seen.add(obj)\n\n const el = obj as any\n\n // Stop at Suspense boundaries - lazy elements inside are intentionally deferred\n if (el.$$typeof === ReactElement && el.type === ReactSuspense) {\n return\n }\n\n // Collect CSS hrefs from <link rel=\"stylesheet\" data-rsc-css-href>\n // The active RSC bundler adapter injects these for CSS module imports\n if (\n el.$$typeof === ReactElement &&\n el.type === 'link' &&\n el.props?.rel === 'stylesheet'\n ) {\n const cssHref = el.props['data-rsc-css-href'] as string | undefined\n if (cssHref && cssCollector) {\n cssCollector(cssHref)\n }\n }\n\n // Yield pending lazy element payload\n if (el.$$typeof === ReactLazy) {\n const payload = el._payload\n if (\n payload &&\n typeof payload === 'object' &&\n (payload.status === 'pending' || payload.status === 'blocked') &&\n typeof payload.then === 'function'\n ) {\n yield payload\n }\n }\n\n // Recurse into children\n if (Array.isArray(obj)) {\n for (const item of obj) {\n yield* findPendingLazyPayloads(item, seen, cssCollector)\n }\n } else {\n for (const key of Object.keys(obj)) {\n if (key !== '_owner' && key !== '_store') {\n yield* findPendingLazyPayloads(el[key], seen, cssCollector)\n }\n }\n }\n}\n\n/**\n * Wait for all lazy elements in a tree to be resolved.\n * This ensures client component chunks are fully loaded before rendering,\n * preventing Suspense boundaries from flashing during SWR navigation.\n *\n * Also collects CSS hrefs from <link rel=\"stylesheet\" data-rsc-css-href>\n * elements for preloading in <head>.\n *\n * @param tree - The tree to process\n * @param cssCollector - Optional callback to collect CSS hrefs (server-only)\n */\nexport async function awaitLazyElements(\n tree: unknown,\n cssCollector?: CssHrefCollector,\n): Promise<void> {\n for (const payload of findPendingLazyPayloads(\n tree,\n new Set(),\n cssCollector,\n )) {\n await Promise.resolve(payload).catch(() => {})\n }\n}\n"],"mappings":";;;;;;AAYA,UAAU,wBACR,KACA,uBAAO,IAAI,IAAI,GACf,cACiC;CACjC,IAAI,CAAC,OAAO,OAAO,QAAQ,UAAU;CACrC,IAAI,KAAK,IAAI,GAAG,GAAG;CACnB,KAAK,IAAI,GAAG;CAEZ,MAAM,KAAK;CAGX,IAAI,GAAG,aAAa,gBAAgB,GAAG,SAAS,eAC9C;CAKF,IACE,GAAG,aAAa,gBAChB,GAAG,SAAS,UACZ,GAAG,OAAO,QAAQ,cAClB;EACA,MAAM,UAAU,GAAG,MAAM;EACzB,IAAI,WAAW,cACb,aAAa,OAAO;CAExB;CAGA,IAAI,GAAG,aAAa,WAAW;EAC7B,MAAM,UAAU,GAAG;EACnB,IACE,WACA,OAAO,YAAY,aAClB,QAAQ,WAAW,aAAa,QAAQ,WAAW,cACpD,OAAO,QAAQ,SAAS,YAExB,MAAM;CAEV;CAGA,IAAI,MAAM,QAAQ,GAAG,GACnB,KAAK,MAAM,QAAQ,KACjB,OAAO,wBAAwB,MAAM,MAAM,YAAY;MAGzD,KAAK,MAAM,OAAO,OAAO,KAAK,GAAG,GAC/B,IAAI,QAAQ,YAAY,QAAQ,UAC9B,OAAO,wBAAwB,GAAG,MAAM,MAAM,YAAY;AAIlE;;;;;;;;;;;;AAaA,eAAsB,kBACpB,MACA,cACe;CACf,KAAK,MAAM,WAAW,wBACpB,sBACA,IAAI,IAAI,GACR,YACF,GACE,MAAM,QAAQ,QAAQ,OAAO,EAAE,YAAY,CAAC,CAAC;AAEjD"}
1
+ {"version":3,"file":"awaitLazyElements.js","names":[],"sources":["../../src/awaitLazyElements.ts"],"sourcesContent":["import { ReactElement, ReactLazy, ReactSuspense } from './reactSymbols'\n\n/**\n * Optional callback for collecting CSS hrefs during tree traversal.\n * Only called server-side when processing <link rel=\"stylesheet\" data-rsc-css-href>\n */\nexport type CssHrefCollector = (href: string) => void\n\n/**\n * Yields pending lazy element payloads from a tree, stopping at Suspense boundaries.\n * Also collects CSS hrefs from <link rel=\"stylesheet\" data-rsc-css-href> elements.\n */\nfunction* findPendingLazyPayloads(\n obj: unknown,\n seen = new Set(),\n cssCollector?: CssHrefCollector,\n): Generator<PromiseLike<unknown>> {\n if (!obj || typeof obj !== 'object') return\n if (seen.has(obj)) return\n seen.add(obj)\n\n const el = obj as any\n\n // Stop at Suspense boundaries - lazy elements inside are intentionally deferred\n if (el.$$typeof === ReactElement && el.type === ReactSuspense) {\n return\n }\n\n // Collect CSS hrefs from <link rel=\"stylesheet\" data-rsc-css-href>\n // The active RSC bundler adapter injects these for CSS module imports\n if (\n el.$$typeof === ReactElement &&\n el.type === 'link' &&\n el.props?.rel === 'stylesheet'\n ) {\n let cssHref: string | undefined\n if ('data-rsc-css-href' in el.props) {\n cssHref = el.props.href\n }\n if (cssHref && cssCollector) {\n cssCollector(cssHref)\n }\n }\n\n // Yield pending lazy element payload\n if (el.$$typeof === ReactLazy) {\n const payload = el._payload\n if (\n payload &&\n typeof payload === 'object' &&\n (payload.status === 'pending' || payload.status === 'blocked') &&\n typeof payload.then === 'function'\n ) {\n yield payload\n }\n }\n\n // Recurse into children\n if (Array.isArray(obj)) {\n for (const item of obj) {\n yield* findPendingLazyPayloads(item, seen, cssCollector)\n }\n } else {\n for (const key of Object.keys(obj)) {\n if (key !== '_owner' && key !== '_store') {\n yield* findPendingLazyPayloads(el[key], seen, cssCollector)\n }\n }\n }\n}\n\n/**\n * Wait for all lazy elements in a tree to be resolved.\n * This ensures client component chunks are fully loaded before rendering,\n * preventing Suspense boundaries from flashing during SWR navigation.\n *\n * Also collects CSS hrefs from <link rel=\"stylesheet\" data-rsc-css-href>\n * elements for preloading in <head>.\n *\n * @param tree - The tree to process\n * @param cssCollector - Optional callback to collect CSS hrefs (server-only)\n */\nexport async function awaitLazyElements(\n tree: unknown,\n cssCollector?: CssHrefCollector,\n): Promise<void> {\n for (const payload of findPendingLazyPayloads(\n tree,\n new Set(),\n cssCollector,\n )) {\n await Promise.resolve(payload).catch(() => {})\n }\n}\n"],"mappings":";;;;;;AAYA,UAAU,wBACR,KACA,uBAAO,IAAI,IAAI,GACf,cACiC;CACjC,IAAI,CAAC,OAAO,OAAO,QAAQ,UAAU;CACrC,IAAI,KAAK,IAAI,GAAG,GAAG;CACnB,KAAK,IAAI,GAAG;CAEZ,MAAM,KAAK;CAGX,IAAI,GAAG,aAAa,gBAAgB,GAAG,SAAS,eAC9C;CAKF,IACE,GAAG,aAAa,gBAChB,GAAG,SAAS,UACZ,GAAG,OAAO,QAAQ,cAClB;EACA,IAAI;EACJ,IAAI,uBAAuB,GAAG,OAC5B,UAAU,GAAG,MAAM;EAErB,IAAI,WAAW,cACb,aAAa,OAAO;CAExB;CAGA,IAAI,GAAG,aAAa,WAAW;EAC7B,MAAM,UAAU,GAAG;EACnB,IACE,WACA,OAAO,YAAY,aAClB,QAAQ,WAAW,aAAa,QAAQ,WAAW,cACpD,OAAO,QAAQ,SAAS,YAExB,MAAM;CAEV;CAGA,IAAI,MAAM,QAAQ,GAAG,GACnB,KAAK,MAAM,QAAQ,KACjB,OAAO,wBAAwB,MAAM,MAAM,YAAY;MAGzD,KAAK,MAAM,OAAO,OAAO,KAAK,GAAG,GAC/B,IAAI,QAAQ,YAAY,QAAQ,UAC9B,OAAO,wBAAwB,GAAG,MAAM,MAAM,YAAY;AAIlE;;;;;;;;;;;;AAaA,eAAsB,kBACpB,MACA,cACe;CACf,KAAK,MAAM,WAAW,wBACpB,sBACA,IAAI,IAAI,GACR,YACF,GACE,MAAM,QAAQ,QAAQ,OAAO,EAAE,YAAY,CAAC,CAAC;AAEjD"}
@@ -15,11 +15,12 @@ import { createFromReadableStream } from "virtual:tanstack-rsc-browser-decode";
15
15
  * - Supports nested access: `{data.foo.bar}`
16
16
  * - Masquerades as a React element
17
17
  */
18
- function createRenderableFromStream(stream) {
19
- const { getTree, streamWrapper, cssHrefs } = setupStreamDecode(stream);
18
+ function createRenderableFromStream(stream, options) {
19
+ const { getTree, streamWrapper, cssHrefs, jsPreloads } = setupStreamDecode(stream, options);
20
20
  return createRscProxy(getTree, {
21
21
  stream: streamWrapper,
22
22
  cssHrefs,
23
+ jsPreloads,
23
24
  renderable: true
24
25
  });
25
26
  }
@@ -33,10 +34,11 @@ function createRenderableFromStream(stream) {
33
34
  * - Must be rendered via `<CompositeComponent src={...} />`
34
35
  */
35
36
  function createCompositeFromStream(stream, options) {
36
- const { getTree, streamWrapper, cssHrefs } = setupStreamDecode(stream);
37
+ const { getTree, streamWrapper, cssHrefs, jsPreloads } = setupStreamDecode(stream, options);
37
38
  return createRscProxy(getTree, {
38
39
  stream: streamWrapper,
39
40
  cssHrefs,
41
+ jsPreloads,
40
42
  renderable: false,
41
43
  slotUsagesStream: options?.slotUsagesStream
42
44
  });
@@ -44,29 +46,38 @@ function createCompositeFromStream(stream, options) {
44
46
  /**
45
47
  * Shared stream decode setup for both renderable and composite.
46
48
  */
47
- function setupStreamDecode(stream) {
48
- const decodeThenable = createFromReadableStream(stream);
49
- const cssHrefs = /* @__PURE__ */ new Set();
49
+ function setupStreamDecode(stream, options) {
50
+ const cssHrefs = new Set(options?.cssHrefs ?? []);
51
+ const jsPreloads = options?.jsPreloads ? new Set(options.jsPreloads) : void 0;
52
+ const shouldDeferDecode = cssHrefs.size > 0 || !!jsPreloads?.size;
50
53
  let cachedTree = void 0;
51
54
  let cacheReady = false;
52
- const transformedTreePromise = Promise.resolve(decodeThenable).then(async (result) => {
53
- await awaitLazyElements(result, (href) => {
54
- cssHrefs.add(href);
55
- });
56
- cachedTree = unwrapRscCssEnvelope(result);
57
- cacheReady = true;
58
- return cachedTree;
59
- });
60
- trackPostProcessPromise(transformedTreePromise);
55
+ let transformedTreePromise;
56
+ const startDecode = () => {
57
+ if (!transformedTreePromise) {
58
+ transformedTreePromise = Promise.resolve(createFromReadableStream(stream)).then(async (result) => {
59
+ await awaitLazyElements(result, (href) => {
60
+ cssHrefs.add(href);
61
+ });
62
+ cachedTree = unwrapRscCssEnvelope(result);
63
+ cacheReady = true;
64
+ return cachedTree;
65
+ });
66
+ trackPostProcessPromise(transformedTreePromise);
67
+ }
68
+ return transformedTreePromise;
69
+ };
70
+ if (!shouldDeferDecode) startDecode();
61
71
  const streamWrapper = { createReplayStream: () => stream };
62
72
  const getTree = () => {
63
73
  if (cacheReady) return cachedTree;
64
- return use(transformedTreePromise);
74
+ return use(startDecode());
65
75
  };
66
76
  return {
67
77
  getTree,
68
78
  streamWrapper,
69
- cssHrefs
79
+ cssHrefs,
80
+ jsPreloads
70
81
  };
71
82
  }
72
83
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"createServerComponentFromStream.js","names":[],"sources":["../../src/createServerComponentFromStream.ts"],"sourcesContent":["'use client'\n\nimport { use } from 'react'\nimport { trackPostProcessPromise } from '@tanstack/start-client-core'\nimport { createFromReadableStream as browserDecode } from 'virtual:tanstack-rsc-browser-decode'\n\nimport { awaitLazyElements } from './awaitLazyElements'\nimport { createRscProxy } from './createRscProxy'\nimport { unwrapRscCssEnvelope } from './rscCssEnvelope'\nimport type {\n AnyCompositeComponent,\n RscSlotUsageEvent,\n ServerComponentStream,\n} from './ServerComponentTypes'\n\n/**\n * Creates a renderable RSC proxy from a raw Flight stream.\n * Client-side only - used by the client serialization adapter for `renderServerComponent`.\n *\n * Returns a Proxy that:\n * - Can be rendered directly as `{data}` in JSX\n * - Supports nested access: `{data.foo.bar}`\n * - Masquerades as a React element\n */\nexport function createRenderableFromStream(\n stream: ReadableStream<Uint8Array>,\n): any {\n const { getTree, streamWrapper, cssHrefs } = setupStreamDecode(stream)\n\n return createRscProxy(getTree, {\n stream: streamWrapper,\n cssHrefs,\n renderable: true,\n })\n}\n\n/**\n * Creates a composite RSC proxy from a raw Flight stream.\n * Client-side only - used by the client serialization adapter for `createCompositeComponent`.\n *\n * Returns a Proxy that:\n * - NOT directly renderable\n * - Supports nested access: `src.foo.bar`\n * - Must be rendered via `<CompositeComponent src={...} />`\n */\nexport function createCompositeFromStream(\n stream: ReadableStream<Uint8Array>,\n options?: {\n slotUsagesStream?: ReadableStream<RscSlotUsageEvent>\n },\n): AnyCompositeComponent {\n const { getTree, streamWrapper, cssHrefs } = setupStreamDecode(stream)\n\n return createRscProxy(getTree, {\n stream: streamWrapper,\n cssHrefs,\n renderable: false,\n slotUsagesStream: options?.slotUsagesStream,\n })\n}\n\n/**\n * Shared stream decode setup for both renderable and composite.\n */\nfunction setupStreamDecode(stream: ReadableStream<Uint8Array>): {\n getTree: () => unknown\n streamWrapper: ServerComponentStream\n cssHrefs: Set<string> | undefined\n} {\n // Start decoding eagerly during deserialization\n const decodeThenable = browserDecode(stream)\n const cssHrefs = new Set<string>()\n\n // Synchronous cache for the decoded tree.\n let cachedTree: unknown = undefined\n let cacheReady = false\n\n // Promise for the tree with lazy elements awaited.\n const transformedTreePromise = Promise.resolve(decodeThenable).then(\n async (result) => {\n await awaitLazyElements(result, (href) => {\n cssHrefs.add(href)\n })\n cachedTree = unwrapRscCssEnvelope(result)\n cacheReady = true\n return cachedTree\n },\n )\n\n // Track the lazy element loading - prevents flash\n trackPostProcessPromise(transformedTreePromise)\n\n const streamWrapper: ServerComponentStream = {\n createReplayStream: () => stream,\n }\n\n const getTree = () => {\n if (cacheReady) return cachedTree\n // eslint-disable-next-line react-hooks/rules-of-hooks\n return use(transformedTreePromise)\n }\n\n return { getTree, streamWrapper, cssHrefs }\n}\n// Legacy export for backwards compatibility during migration\nexport const createServerComponentFromStream = createCompositeFromStream\n"],"mappings":";;;;;;;;;;;;;;;;;AAwBA,SAAgB,2BACd,QACK;CACL,MAAM,EAAE,SAAS,eAAe,aAAa,kBAAkB,MAAM;CAErE,OAAO,eAAe,SAAS;EAC7B,QAAQ;EACR;EACA,YAAY;CACd,CAAC;AACH;;;;;;;;;;AAWA,SAAgB,0BACd,QACA,SAGuB;CACvB,MAAM,EAAE,SAAS,eAAe,aAAa,kBAAkB,MAAM;CAErE,OAAO,eAAe,SAAS;EAC7B,QAAQ;EACR;EACA,YAAY;EACZ,kBAAkB,SAAS;CAC7B,CAAC;AACH;;;;AAKA,SAAS,kBAAkB,QAIzB;CAEA,MAAM,iBAAiB,yBAAc,MAAM;CAC3C,MAAM,2BAAW,IAAI,IAAY;CAGjC,IAAI,aAAsB,KAAA;CAC1B,IAAI,aAAa;CAGjB,MAAM,yBAAyB,QAAQ,QAAQ,cAAc,EAAE,KAC7D,OAAO,WAAW;EAChB,MAAM,kBAAkB,SAAS,SAAS;GACxC,SAAS,IAAI,IAAI;EACnB,CAAC;EACD,aAAa,qBAAqB,MAAM;EACxC,aAAa;EACb,OAAO;CACT,CACF;CAGA,wBAAwB,sBAAsB;CAE9C,MAAM,gBAAuC,EAC3C,0BAA0B,OAC5B;CAEA,MAAM,gBAAgB;EACpB,IAAI,YAAY,OAAO;EAEvB,OAAO,IAAI,sBAAsB;CACnC;CAEA,OAAO;EAAE;EAAS;EAAe;CAAS;AAC5C"}
1
+ {"version":3,"file":"createServerComponentFromStream.js","names":[],"sources":["../../src/createServerComponentFromStream.ts"],"sourcesContent":["'use client'\n\nimport { use } from 'react'\nimport { trackPostProcessPromise } from '@tanstack/start-client-core'\nimport { createFromReadableStream as browserDecode } from 'virtual:tanstack-rsc-browser-decode'\n\nimport { awaitLazyElements } from './awaitLazyElements'\nimport { createRscProxy } from './createRscProxy'\nimport { unwrapRscCssEnvelope } from './rscCssEnvelope'\nimport type {\n AnyCompositeComponent,\n RscSlotUsageEvent,\n ServerComponentStream,\n} from './ServerComponentTypes'\n\ntype StreamDecodeOptions = {\n cssHrefs?: Iterable<string>\n jsPreloads?: Iterable<string>\n}\n\n/**\n * Creates a renderable RSC proxy from a raw Flight stream.\n * Client-side only - used by the client serialization adapter for `renderServerComponent`.\n *\n * Returns a Proxy that:\n * - Can be rendered directly as `{data}` in JSX\n * - Supports nested access: `{data.foo.bar}`\n * - Masquerades as a React element\n */\nexport function createRenderableFromStream(\n stream: ReadableStream<Uint8Array>,\n options?: StreamDecodeOptions,\n): any {\n const { getTree, streamWrapper, cssHrefs, jsPreloads } = setupStreamDecode(\n stream,\n options,\n )\n\n return createRscProxy(getTree, {\n stream: streamWrapper,\n cssHrefs,\n jsPreloads,\n renderable: true,\n })\n}\n\n/**\n * Creates a composite RSC proxy from a raw Flight stream.\n * Client-side only - used by the client serialization adapter for `createCompositeComponent`.\n *\n * Returns a Proxy that:\n * - NOT directly renderable\n * - Supports nested access: `src.foo.bar`\n * - Must be rendered via `<CompositeComponent src={...} />`\n */\nexport function createCompositeFromStream(\n stream: ReadableStream<Uint8Array>,\n options?: {\n slotUsagesStream?: ReadableStream<RscSlotUsageEvent>\n } & StreamDecodeOptions,\n): AnyCompositeComponent {\n const { getTree, streamWrapper, cssHrefs, jsPreloads } = setupStreamDecode(\n stream,\n options,\n )\n\n return createRscProxy(getTree, {\n stream: streamWrapper,\n cssHrefs,\n jsPreloads,\n renderable: false,\n slotUsagesStream: options?.slotUsagesStream,\n })\n}\n\n/**\n * Shared stream decode setup for both renderable and composite.\n */\nfunction setupStreamDecode(\n stream: ReadableStream<Uint8Array>,\n options?: StreamDecodeOptions,\n): {\n getTree: () => unknown\n streamWrapper: ServerComponentStream\n cssHrefs: Set<string>\n jsPreloads: Set<string> | undefined\n} {\n const cssHrefs = new Set(options?.cssHrefs ?? [])\n const jsPreloads = options?.jsPreloads\n ? new Set(options.jsPreloads)\n : undefined\n const shouldDeferDecode = cssHrefs.size > 0 || !!jsPreloads?.size\n\n // Synchronous cache for the decoded tree.\n let cachedTree: unknown = undefined\n let cacheReady = false\n let transformedTreePromise: Promise<unknown> | undefined\n\n const startDecode = () => {\n if (!transformedTreePromise) {\n // Promise for the tree with lazy elements awaited.\n transformedTreePromise = Promise.resolve(browserDecode(stream)).then(\n async (result) => {\n await awaitLazyElements(result, (href) => {\n cssHrefs.add(href)\n })\n cachedTree = unwrapRscCssEnvelope(result)\n cacheReady = true\n return cachedTree\n },\n )\n\n // Track the lazy element loading - prevents flash for RPC/RSC fetches.\n trackPostProcessPromise(transformedTreePromise)\n }\n\n return transformedTreePromise\n }\n\n if (!shouldDeferDecode) {\n // Start decoding eagerly during deserialization for non-SSR streams.\n startDecode()\n }\n\n const streamWrapper: ServerComponentStream = {\n createReplayStream: () => stream,\n }\n\n const getTree = () => {\n if (cacheReady) return cachedTree\n // eslint-disable-next-line react-hooks/rules-of-hooks\n return use(startDecode())\n }\n\n return { getTree, streamWrapper, cssHrefs, jsPreloads }\n}\n// Legacy export for backwards compatibility during migration\nexport const createServerComponentFromStream = createCompositeFromStream\n"],"mappings":";;;;;;;;;;;;;;;;;AA6BA,SAAgB,2BACd,QACA,SACK;CACL,MAAM,EAAE,SAAS,eAAe,UAAU,eAAe,kBACvD,QACA,OACF;CAEA,OAAO,eAAe,SAAS;EAC7B,QAAQ;EACR;EACA;EACA,YAAY;CACd,CAAC;AACH;;;;;;;;;;AAWA,SAAgB,0BACd,QACA,SAGuB;CACvB,MAAM,EAAE,SAAS,eAAe,UAAU,eAAe,kBACvD,QACA,OACF;CAEA,OAAO,eAAe,SAAS;EAC7B,QAAQ;EACR;EACA;EACA,YAAY;EACZ,kBAAkB,SAAS;CAC7B,CAAC;AACH;;;;AAKA,SAAS,kBACP,QACA,SAMA;CACA,MAAM,WAAW,IAAI,IAAI,SAAS,YAAY,CAAC,CAAC;CAChD,MAAM,aAAa,SAAS,aACxB,IAAI,IAAI,QAAQ,UAAU,IAC1B,KAAA;CACJ,MAAM,oBAAoB,SAAS,OAAO,KAAK,CAAC,CAAC,YAAY;CAG7D,IAAI,aAAsB,KAAA;CAC1B,IAAI,aAAa;CACjB,IAAI;CAEJ,MAAM,oBAAoB;EACxB,IAAI,CAAC,wBAAwB;GAE3B,yBAAyB,QAAQ,QAAQ,yBAAc,MAAM,CAAC,EAAE,KAC9D,OAAO,WAAW;IAChB,MAAM,kBAAkB,SAAS,SAAS;KACxC,SAAS,IAAI,IAAI;IACnB,CAAC;IACD,aAAa,qBAAqB,MAAM;IACxC,aAAa;IACb,OAAO;GACT,CACF;GAGA,wBAAwB,sBAAsB;EAChD;EAEA,OAAO;CACT;CAEA,IAAI,CAAC,mBAEH,YAAY;CAGd,MAAM,gBAAuC,EAC3C,0BAA0B,OAC5B;CAEA,MAAM,gBAAgB;EACpB,IAAI,YAAY,OAAO;EAEvB,OAAO,IAAI,YAAY,CAAC;CAC1B;CAEA,OAAO;EAAE;EAAS;EAAe;EAAU;CAAW;AACxD"}
@@ -1,4 +1,3 @@
1
- import { setOnClientReference } from "@rspack/core/rsc/ssr";
2
1
  import { createFromReadableStream } from "react-server-dom-rspack/client.node";
3
2
  //#region src/rsbuild/ssr-decode.ts
4
3
  /**
@@ -7,7 +6,50 @@ import { createFromReadableStream } from "react-server-dom-rspack/client.node";
7
6
  * Bundler-owned rsbuild virtual modules re-export this module for SSR-side
8
7
  * Flight decode.
9
8
  */
9
+ var CHUNK_FILENAME_INDEX = 1;
10
+ var CHUNK_PAIR_SIZE = 2;
11
+ var onClientReference;
12
+ var clientReferenceDepsByModuleId;
13
+ function getClientReferenceDepsByModuleId() {
14
+ if (clientReferenceDepsByModuleId) return clientReferenceDepsByModuleId;
15
+ clientReferenceDepsByModuleId = /* @__PURE__ */ new Map();
16
+ const prefix = __rspack_rsc_manifest__.moduleLoading?.prefix ?? "";
17
+ for (const entry of Object.values(__rspack_rsc_manifest__.clientManifest)) {
18
+ let deps = clientReferenceDepsByModuleId.get(entry.id);
19
+ if (!deps) {
20
+ deps = {
21
+ js: [],
22
+ css: []
23
+ };
24
+ clientReferenceDepsByModuleId.set(entry.id, deps);
25
+ }
26
+ for (let i = CHUNK_FILENAME_INDEX; i < entry.chunks.length; i += CHUNK_PAIR_SIZE) deps.js.push(prefix + entry.chunks[i]);
27
+ if (entry.cssFiles) deps.css.push(...entry.cssFiles);
28
+ }
29
+ return clientReferenceDepsByModuleId;
30
+ }
31
+ function emitClientReferencePreloadsForModule(moduleId) {
32
+ const deps = getClientReferenceDepsByModuleId().get(moduleId);
33
+ if (!onClientReference || !deps) return;
34
+ if (deps.js.length === 0 && deps.css.length === 0) return;
35
+ onClientReference({
36
+ id: moduleId,
37
+ deps,
38
+ runtime: "rsbuild"
39
+ });
40
+ }
41
+ if (__rspack_rsc_manifest__.serverConsumerModuleMap) __rspack_rsc_manifest__.serverConsumerModuleMap = new Proxy(__rspack_rsc_manifest__.serverConsumerModuleMap, { get(target, property, receiver) {
42
+ const moduleExports = Reflect.get(target, property, receiver);
43
+ if (typeof property === "string" && moduleExports !== void 0) emitClientReferencePreloadsForModule(property);
44
+ return moduleExports;
45
+ } });
46
+ function setOnClientReference(callback) {
47
+ onClientReference = callback;
48
+ }
49
+ async function createFromReadableStreamWithClientPreloads(stream, options) {
50
+ return createFromReadableStream(stream, options);
51
+ }
10
52
  //#endregion
11
- export { createFromReadableStream, setOnClientReference };
53
+ export { createFromReadableStreamWithClientPreloads as createFromReadableStream, setOnClientReference };
12
54
 
13
55
  //# sourceMappingURL=ssr-decode.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"ssr-decode.js","names":[],"sources":["../../../src/rsbuild/ssr-decode.ts"],"sourcesContent":["/**\n * Rsbuild SSR decode implementation.\n *\n * Bundler-owned rsbuild virtual modules re-export this module for SSR-side\n * Flight decode.\n */\n\nimport { setOnClientReference } from '@rspack/core/rsc/ssr'\nimport { createFromReadableStream } from 'react-server-dom-rspack/client.node'\n\nexport { createFromReadableStream, setOnClientReference }\n"],"mappings":""}
1
+ {"version":3,"file":"ssr-decode.js","names":[],"sources":["../../../src/rsbuild/ssr-decode.ts"],"sourcesContent":["/**\n * Rsbuild SSR decode implementation.\n *\n * Bundler-owned rsbuild virtual modules re-export this module for SSR-side\n * Flight decode.\n */\n\nimport { createFromReadableStream as decodeFlightStream } from 'react-server-dom-rspack/client.node'\n\ntype ClientReferenceDeps = {\n js: Array<string>\n css: Array<string>\n}\n\ntype OnClientReference = (reference: {\n id: string\n deps: ClientReferenceDeps\n runtime: 'rsbuild'\n}) => void\n\ntype ImportManifestEntry = {\n id: string\n chunks: Array<string>\n cssFiles?: Array<string>\n name: string\n async?: boolean\n}\n\ntype ClientManifest = Record<string, ImportManifestEntry>\ntype ServerConsumerModuleMap = null | Record<\n string,\n Record<string, ImportManifestEntry>\n>\ntype ModuleLoading = null | {\n prefix: string\n crossOrigin?: 'use-credentials' | ''\n}\n\ndeclare const __rspack_rsc_manifest__: {\n moduleLoading: ModuleLoading\n serverConsumerModuleMap: ServerConsumerModuleMap\n clientManifest: ClientManifest\n}\n\nconst CHUNK_FILENAME_INDEX = 1\nconst CHUNK_PAIR_SIZE = 2\n\nlet onClientReference: OnClientReference | undefined\nlet clientReferenceDepsByModuleId: Map<string, ClientReferenceDeps> | undefined\n\nfunction getClientReferenceDepsByModuleId() {\n if (clientReferenceDepsByModuleId) return clientReferenceDepsByModuleId\n\n clientReferenceDepsByModuleId = new Map()\n const prefix = __rspack_rsc_manifest__.moduleLoading?.prefix ?? ''\n\n for (const entry of Object.values(__rspack_rsc_manifest__.clientManifest)) {\n let deps = clientReferenceDepsByModuleId.get(entry.id)\n if (!deps) {\n deps = { js: [], css: [] }\n clientReferenceDepsByModuleId.set(entry.id, deps)\n }\n\n // React/Rspack stores JS chunks as chunk id/filename pairs. CSS files are\n // separate manifest entries and are already emitted as public hrefs.\n for (\n let i = CHUNK_FILENAME_INDEX;\n i < entry.chunks.length;\n i += CHUNK_PAIR_SIZE\n ) {\n deps.js.push(prefix + entry.chunks[i])\n }\n\n if (entry.cssFiles) {\n deps.css.push(...entry.cssFiles)\n }\n }\n\n return clientReferenceDepsByModuleId\n}\n\nfunction emitClientReferencePreloadsForModule(moduleId: string) {\n const deps = getClientReferenceDepsByModuleId().get(moduleId)\n if (!onClientReference || !deps) return\n\n if (deps.js.length === 0 && deps.css.length === 0) {\n return\n }\n\n onClientReference({\n id: moduleId,\n deps,\n runtime: 'rsbuild',\n })\n}\n\nif (__rspack_rsc_manifest__.serverConsumerModuleMap) {\n __rspack_rsc_manifest__.serverConsumerModuleMap = new Proxy(\n __rspack_rsc_manifest__.serverConsumerModuleMap,\n {\n get(target, property, receiver) {\n const moduleExports = Reflect.get(target, property, receiver)\n\n if (typeof property === 'string' && moduleExports !== undefined) {\n emitClientReferencePreloadsForModule(property)\n }\n\n return moduleExports\n },\n },\n )\n}\n\nfunction setOnClientReference(callback: OnClientReference | undefined) {\n onClientReference = callback\n}\n\nasync function createFromReadableStreamWithClientPreloads<T = unknown>(\n stream: ReadableStream<Uint8Array>,\n options?: object,\n): Promise<T> {\n return decodeFlightStream<T>(stream, options)\n}\n\nexport {\n setOnClientReference,\n createFromReadableStreamWithClientPreloads as createFromReadableStream,\n}\n"],"mappings":";;;;;;;;AA4CA,IAAM,uBAAuB;AAC7B,IAAM,kBAAkB;AAExB,IAAI;AACJ,IAAI;AAEJ,SAAS,mCAAmC;CAC1C,IAAI,+BAA+B,OAAO;CAE1C,gDAAgC,IAAI,IAAI;CACxC,MAAM,SAAS,wBAAwB,eAAe,UAAU;CAEhE,KAAK,MAAM,SAAS,OAAO,OAAO,wBAAwB,cAAc,GAAG;EACzE,IAAI,OAAO,8BAA8B,IAAI,MAAM,EAAE;EACrD,IAAI,CAAC,MAAM;GACT,OAAO;IAAE,IAAI,CAAC;IAAG,KAAK,CAAC;GAAE;GACzB,8BAA8B,IAAI,MAAM,IAAI,IAAI;EAClD;EAIA,KACE,IAAI,IAAI,sBACR,IAAI,MAAM,OAAO,QACjB,KAAK,iBAEL,KAAK,GAAG,KAAK,SAAS,MAAM,OAAO,EAAE;EAGvC,IAAI,MAAM,UACR,KAAK,IAAI,KAAK,GAAG,MAAM,QAAQ;CAEnC;CAEA,OAAO;AACT;AAEA,SAAS,qCAAqC,UAAkB;CAC9D,MAAM,OAAO,iCAAiC,EAAE,IAAI,QAAQ;CAC5D,IAAI,CAAC,qBAAqB,CAAC,MAAM;CAEjC,IAAI,KAAK,GAAG,WAAW,KAAK,KAAK,IAAI,WAAW,GAC9C;CAGF,kBAAkB;EAChB,IAAI;EACJ;EACA,SAAS;CACX,CAAC;AACH;AAEA,IAAI,wBAAwB,yBAC1B,wBAAwB,0BAA0B,IAAI,MACpD,wBAAwB,yBACxB,EACE,IAAI,QAAQ,UAAU,UAAU;CAC9B,MAAM,gBAAgB,QAAQ,IAAI,QAAQ,UAAU,QAAQ;CAE5D,IAAI,OAAO,aAAa,YAAY,kBAAkB,KAAA,GACpD,qCAAqC,QAAQ;CAG/C,OAAO;AACT,EACF,CACF;AAGF,SAAS,qBAAqB,UAAyC;CACrE,oBAAoB;AACtB;AAEA,eAAe,2CACb,QACA,SACY;CACZ,OAAO,yBAAsB,QAAQ,OAAO;AAC9C"}
@@ -10,8 +10,15 @@ var adapter = createSerializationAdapter({
10
10
  throw new Error("RSC cannot be serialized on client");
11
11
  },
12
12
  fromSerializable: (value) => {
13
- if (value.kind === "renderable") return createRenderableFromStream(value.stream);
14
- return createCompositeFromStream(value.stream, { slotUsagesStream: value.slotUsagesStream });
13
+ const options = {
14
+ cssHrefs: value.cssHrefs,
15
+ jsPreloads: value.jsPreloads
16
+ };
17
+ if (value.kind === "renderable") return createRenderableFromStream(value.stream, options);
18
+ return createCompositeFromStream(value.stream, {
19
+ slotUsagesStream: value.slotUsagesStream,
20
+ ...options
21
+ });
15
22
  }
16
23
  });
17
24
  var rscSerializationAdapter = () => [adapter];
@@ -1 +1 @@
1
- {"version":3,"file":"serialization.client.js","names":[],"sources":["../../src/serialization.client.ts"],"sourcesContent":["import { createSerializationAdapter } from '@tanstack/react-router'\n// RSC HMR setup (dev-only, provided by the active Start bundler adapter).\nimport { setupRscHmr } from 'virtual:tanstack-rsc-hmr'\nimport {\n createCompositeFromStream,\n createRenderableFromStream,\n} from './createServerComponentFromStream'\nimport type {\n AnyCompositeComponent,\n RscSlotUsageEvent,\n} from './ServerComponentTypes'\n\nif (process.env.NODE_ENV === 'development') {\n setupRscHmr()\n}\n\n/**\n * Client-side serialization adapter for RSC (renderable + composite).\n */\ntype SerializedRsc = {\n kind: 'renderable' | 'composite'\n stream: ReadableStream<Uint8Array>\n slotUsagesStream?: ReadableStream<RscSlotUsageEvent>\n}\n\nconst adapter = createSerializationAdapter({\n key: '$RSC',\n test: (_value: unknown): _value is never => false,\n toSerializable: (): never => {\n throw new Error('RSC cannot be serialized on client')\n },\n fromSerializable: (value: SerializedRsc): AnyCompositeComponent => {\n if (value.kind === 'renderable') {\n return createRenderableFromStream(value.stream)\n }\n\n return createCompositeFromStream(value.stream, {\n slotUsagesStream: value.slotUsagesStream,\n })\n },\n})\n\nexport const rscSerializationAdapter = () => [adapter]\n"],"mappings":";;;;AAYA,IAAA,QAAA,IAAA,aAA6B,eAC3B,YAAY;AAYd,IAAM,UAAU,2BAA2B;CACzC,KAAK;CACL,OAAO,WAAqC;CAC5C,sBAA6B;EAC3B,MAAM,IAAI,MAAM,oCAAoC;CACtD;CACA,mBAAmB,UAAgD;EACjE,IAAI,MAAM,SAAS,cACjB,OAAO,2BAA2B,MAAM,MAAM;EAGhD,OAAO,0BAA0B,MAAM,QAAQ,EAC7C,kBAAkB,MAAM,iBAC1B,CAAC;CACH;AACF,CAAC;AAED,IAAa,gCAAgC,CAAC,OAAO"}
1
+ {"version":3,"file":"serialization.client.js","names":[],"sources":["../../src/serialization.client.ts"],"sourcesContent":["import { createSerializationAdapter } from '@tanstack/react-router'\n// RSC HMR setup (dev-only, provided by the active Start bundler adapter).\nimport { setupRscHmr } from 'virtual:tanstack-rsc-hmr'\nimport {\n createCompositeFromStream,\n createRenderableFromStream,\n} from './createServerComponentFromStream'\nimport type {\n AnyCompositeComponent,\n RscSlotUsageEvent,\n} from './ServerComponentTypes'\n\nif (process.env.NODE_ENV === 'development') {\n setupRscHmr()\n}\n\n/**\n * Client-side serialization adapter for RSC (renderable + composite).\n */\ntype SerializedRsc = {\n kind: 'renderable' | 'composite'\n stream: ReadableStream<Uint8Array>\n slotUsagesStream?: ReadableStream<RscSlotUsageEvent>\n cssHrefs?: Array<string>\n jsPreloads?: Array<string>\n}\n\nconst adapter = createSerializationAdapter({\n key: '$RSC',\n test: (_value: unknown): _value is never => false,\n toSerializable: (): never => {\n throw new Error('RSC cannot be serialized on client')\n },\n fromSerializable: (value: SerializedRsc): AnyCompositeComponent => {\n // Rsbuild SSR sends asset deps with each serialized RSC so the client can\n // preload only if that specific stream is rendered. When absent, decode\n // starts eagerly and discovers assets from the Flight payload as before.\n const options = {\n cssHrefs: value.cssHrefs,\n jsPreloads: value.jsPreloads,\n }\n\n if (value.kind === 'renderable') {\n return createRenderableFromStream(value.stream, options)\n }\n\n return createCompositeFromStream(value.stream, {\n slotUsagesStream: value.slotUsagesStream,\n ...options,\n })\n },\n})\n\nexport const rscSerializationAdapter = () => [adapter]\n"],"mappings":";;;;AAYA,IAAA,QAAA,IAAA,aAA6B,eAC3B,YAAY;AAcd,IAAM,UAAU,2BAA2B;CACzC,KAAK;CACL,OAAO,WAAqC;CAC5C,sBAA6B;EAC3B,MAAM,IAAI,MAAM,oCAAoC;CACtD;CACA,mBAAmB,UAAgD;EAIjE,MAAM,UAAU;GACd,UAAU,MAAM;GAChB,YAAY,MAAM;EACpB;EAEA,IAAI,MAAM,SAAS,cACjB,OAAO,2BAA2B,MAAM,QAAQ,OAAO;EAGzD,OAAO,0BAA0B,MAAM,QAAQ;GAC7C,kBAAkB,MAAM;GACxB,GAAG;EACL,CAAC;CACH;AACF,CAAC;AAED,IAAa,gCAAgC,CAAC,OAAO"}
@@ -1,4 +1,4 @@
1
- import { RENDERABLE_RSC, RSC_SLOT_USAGES_STREAM, SERVER_COMPONENT_STREAM, isServerComponent } from "./ServerComponentTypes.js";
1
+ import { RENDERABLE_RSC, RSC_SLOT_USAGES_STREAM, SERVER_COMPONENT_CSS_HREFS, SERVER_COMPONENT_JS_PRELOADS, SERVER_COMPONENT_STREAM, isServerComponent } from "./ServerComponentTypes.js";
2
2
  import { unwrapRscCssEnvelope } from "./rscCssEnvelope.js";
3
3
  import { awaitLazyElements } from "./awaitLazyElements.js";
4
4
  import { createRscProxy } from "./createRscProxy.js";
@@ -8,15 +8,16 @@ import { AsyncLocalStorage } from "node:async_hooks";
8
8
  import { RawStream } from "@tanstack/router-core";
9
9
  import { createFromReadableStream, setOnClientReference } from "virtual:tanstack-rsc-ssr-decode";
10
10
  //#region src/serialization.server.ts
11
- var decodeCollectorStorage = new AsyncLocalStorage();
12
- var jsCollectorStorage = new AsyncLocalStorage();
11
+ var proxyCssCollectorStorage = new AsyncLocalStorage();
12
+ var proxyJsCollectorStorage = new AsyncLocalStorage();
13
13
  setOnClientReference(({ deps, runtime }) => {
14
- const ctx = getStartContext({ throwIfNotFound: false });
15
- const cssCollector = decodeCollectorStorage.getStore();
14
+ const cssCollector = proxyCssCollectorStorage.getStore();
16
15
  if (cssCollector) for (const href of deps.css) cssCollector.add(href);
17
- const jsCollector = jsCollectorStorage.getStore();
16
+ const jsCollector = proxyJsCollectorStorage.getStore();
18
17
  if (jsCollector) for (const href of deps.js) jsCollector.add(href);
19
- if (!ctx || runtime === "rsbuild" || !deps.js.length && !deps.css.length) return;
18
+ if (runtime === "rsbuild" || cssCollector || jsCollector) return;
19
+ const ctx = getStartContext({ throwIfNotFound: false });
20
+ if (!ctx || !deps.js.length && !deps.css.length) return;
20
21
  if (!ctx.requestAssets) ctx.requestAssets = {};
21
22
  const seenHrefs = /* @__PURE__ */ new Set();
22
23
  for (const preload of ctx.requestAssets.preloads ?? []) seenHrefs.add(typeof preload === "string" ? preload : preload.href);
@@ -41,18 +42,18 @@ setOnClientReference(({ deps, runtime }) => {
41
42
  globalThis.__RSC_SSR__ = {
42
43
  async decode(stream) {
43
44
  const readableStream = stream.createReplayStream();
44
- const cssCollector = /* @__PURE__ */ new Set();
45
- const jsCollector = /* @__PURE__ */ new Set();
46
- return decodeCollectorStorage.run(cssCollector, async () => {
47
- return jsCollectorStorage.run(jsCollector, async () => {
45
+ const proxyCssCollector = /* @__PURE__ */ new Set();
46
+ const proxyJsCollector = /* @__PURE__ */ new Set();
47
+ return proxyCssCollectorStorage.run(proxyCssCollector, async () => {
48
+ return proxyJsCollectorStorage.run(proxyJsCollector, async () => {
48
49
  const decodedTree = await createFromReadableStream(readableStream);
49
50
  await awaitLazyElements(decodedTree, (href) => {
50
- cssCollector.add(href);
51
+ proxyCssCollector.add(href);
51
52
  });
52
53
  return {
53
54
  tree: unwrapRscCssEnvelope(decodedTree),
54
- cssHrefs: cssCollector.size > 0 ? cssCollector : void 0,
55
- jsPreloads: jsCollector.size > 0 ? jsCollector : void 0
55
+ cssHrefs: proxyCssCollector.size > 0 ? proxyCssCollector : void 0,
56
+ jsPreloads: proxyJsCollector.size > 0 ? proxyJsCollector : void 0
56
57
  };
57
58
  });
58
59
  });
@@ -95,11 +96,18 @@ var adapter = createSerializationAdapter({
95
96
  toSerializable: (component) => {
96
97
  const stream = component[SERVER_COMPONENT_STREAM].createReplayStream();
97
98
  const kind = isRenderableRsc(component) ? "renderable" : "composite";
99
+ const jsPreloads = component[SERVER_COMPONENT_JS_PRELOADS];
100
+ const cssHrefs = component[SERVER_COMPONENT_CSS_HREFS];
101
+ const serializedAssetDeps = jsPreloads?.size ? {
102
+ ...cssHrefs?.size ? { cssHrefs: Array.from(cssHrefs) } : {},
103
+ jsPreloads: Array.from(jsPreloads)
104
+ } : {};
98
105
  const slotUsagesStream = kind === "composite" && process.env.NODE_ENV === "development" && RSC_SLOT_USAGES_STREAM in component ? component[RSC_SLOT_USAGES_STREAM] : void 0;
99
106
  return {
100
107
  kind,
101
108
  stream: new RawStream(stream, { hint: "text" }),
102
- slotUsagesStream
109
+ slotUsagesStream,
110
+ ...serializedAssetDeps
103
111
  };
104
112
  },
105
113
  fromSerializable: () => {
@@ -1 +1 @@
1
- {"version":3,"file":"serialization.server.js","names":[],"sources":["../../src/serialization.server.ts"],"sourcesContent":["import { AsyncLocalStorage } from 'node:async_hooks'\nimport { createSerializationAdapter } from '@tanstack/react-router'\nimport { RawStream } from '@tanstack/router-core'\nimport { getStartContext } from '@tanstack/start-storage-context'\nimport {\n setOnClientReference,\n createFromReadableStream as ssrDecode,\n} from 'virtual:tanstack-rsc-ssr-decode'\nimport {\n RENDERABLE_RSC,\n RSC_SLOT_USAGES_STREAM,\n SERVER_COMPONENT_STREAM,\n isServerComponent,\n} from './ServerComponentTypes'\nimport { createRscProxy } from './createRscProxy'\nimport { awaitLazyElements } from './awaitLazyElements'\nimport { unwrapRscCssEnvelope } from './rscCssEnvelope'\nimport type {\n AnyCompositeComponent,\n ServerComponentStream,\n} from './ServerComponentTypes'\nimport type { RscDecodeResult, RscSsrHandler } from './rscSsrHandler'\n\n// ===== SSR Handler Registration =====\n// This handler is registered on globalThis for the RSC environment to access.\n// The RSC env calls these functions during loader execution to pre-decode streams\n// and create renderable proxies.\n//\n// This MUST happen in a module without 'use client' directive.\n// Modules with 'use client' may be transformed to client references in the\n// SSR environment when RSC is enabled, preventing the side effect from running.\n\n// AsyncLocalStorage for decode-scoped CSS collector.\n// Each decode() runs in its own async context with its own collector.\n// The onClientReference callback reads from this to write CSS hrefs.\nconst decodeCollectorStorage = new AsyncLocalStorage<Set<string>>()\nconst jsCollectorStorage = new AsyncLocalStorage<Set<string>>()\n\nsetOnClientReference(\n ({\n deps,\n runtime,\n }: {\n deps: { js: Array<string>; css: Array<string> }\n runtime?: 'rsbuild'\n }) => {\n const ctx = getStartContext({ throwIfNotFound: false })\n\n const cssCollector = decodeCollectorStorage.getStore()\n if (cssCollector) {\n for (const href of deps.css) {\n cssCollector.add(href)\n }\n }\n\n const jsCollector = jsCollectorStorage.getStore()\n if (jsCollector) {\n for (const href of deps.js) {\n jsCollector.add(href)\n }\n }\n\n if (\n !ctx ||\n runtime === 'rsbuild' ||\n (!deps.js.length && !deps.css.length)\n ) {\n return\n }\n\n if (!ctx.requestAssets) ctx.requestAssets = {}\n const seenHrefs = new Set<string>()\n for (const preload of ctx.requestAssets.preloads ?? []) {\n seenHrefs.add(typeof preload === 'string' ? preload : preload.href)\n }\n for (const stylesheet of ctx.requestAssets.css ?? []) {\n seenHrefs.add(\n typeof stylesheet === 'string' ? stylesheet : stylesheet.href,\n )\n }\n\n let nextPreloads: typeof ctx.requestAssets.preloads | undefined\n for (const href of deps.js) {\n if (seenHrefs.has(href)) continue\n seenHrefs.add(href)\n if (!nextPreloads) {\n nextPreloads = ctx.requestAssets.preloads\n ? ctx.requestAssets.preloads.slice()\n : []\n }\n nextPreloads.push(href)\n }\n if (nextPreloads) {\n ctx.requestAssets.preloads = nextPreloads\n }\n\n let nextCss: typeof ctx.requestAssets.css | undefined\n for (const href of deps.css) {\n if (seenHrefs.has(href)) continue\n seenHrefs.add(href)\n if (!nextCss) {\n nextCss = ctx.requestAssets.css ? ctx.requestAssets.css.slice() : []\n }\n nextCss.push(href)\n }\n if (nextCss) {\n ctx.requestAssets.css = nextCss\n }\n },\n)\n\nconst ssrHandler: RscSsrHandler = {\n async decode(stream: ServerComponentStream): Promise<RscDecodeResult> {\n const readableStream = stream.createReplayStream()\n\n // Create a collector for this decode operation.\n // Run the decode in an AsyncLocalStorage context so the onClientReference\n // callback can write to this specific collector even with parallel decodes.\n const cssCollector = new Set<string>()\n const jsCollector = new Set<string>()\n\n return decodeCollectorStorage.run(cssCollector, async () => {\n return jsCollectorStorage.run(jsCollector, async () => {\n const decodedTree = await ssrDecode(readableStream)\n await awaitLazyElements(decodedTree, (href) => {\n cssCollector.add(href)\n })\n\n return {\n tree: unwrapRscCssEnvelope(decodedTree),\n cssHrefs: cssCollector.size > 0 ? cssCollector : undefined,\n jsPreloads: jsCollector.size > 0 ? jsCollector : undefined,\n }\n })\n })\n },\n\n createRenderableProxy(stream, decoded): any {\n return createRscProxy(() => decoded.tree, {\n stream,\n cssHrefs: decoded.cssHrefs,\n jsPreloads: decoded.jsPreloads,\n renderable: true,\n })\n },\n\n createCompositeProxy(\n stream,\n decoded,\n slotUsagesStream,\n ): AnyCompositeComponent {\n const proxy = createRscProxy(() => decoded.tree, {\n stream,\n cssHrefs: decoded.cssHrefs,\n jsPreloads: decoded.jsPreloads,\n renderable: false,\n slotUsagesStream,\n })\n return proxy\n },\n}\n\n// Register SSR handler on globalThis for RSC environment to access.\nglobalThis.__RSC_SSR__ = ssrHandler\n\n// ===== End SSR Handler Registration =====\n\n/**\n * Helper to check if a value is a renderable RSC (from renderServerComponent).\n * The value can be either an object (proxy target) or a function (stub for server functions).\n */\nfunction isRenderableRsc(value: unknown): boolean {\n if (value === null || value === undefined) return false\n if (typeof value !== 'object' && typeof value !== 'function') return false\n return RENDERABLE_RSC in value && (value as any)[RENDERABLE_RSC] === true\n}\n\n/**\n * Server-side serialization adapter for RSC (renderable + composite).\n */\nconst adapter = createSerializationAdapter({\n key: '$RSC',\n test: (value: unknown): value is AnyCompositeComponent => {\n return isServerComponent(value)\n },\n toSerializable: (component: AnyCompositeComponent) => {\n const stream = component[SERVER_COMPONENT_STREAM]!.createReplayStream()\n\n const kind = isRenderableRsc(component) ? 'renderable' : 'composite'\n\n const slotUsagesStream =\n kind === 'composite' &&\n process.env.NODE_ENV === 'development' &&\n RSC_SLOT_USAGES_STREAM in component\n ? ((component as any)[RSC_SLOT_USAGES_STREAM] as unknown as\n | ReadableStream<any>\n | undefined)\n : undefined\n\n return {\n kind,\n stream: new RawStream(stream, { hint: 'text' }),\n slotUsagesStream,\n }\n },\n fromSerializable: (): never => {\n throw new Error('Server should never deserialize RSC data')\n },\n})\n\n/**\n * Factory function for server-side RSC serialization adapter.\n */\nexport const rscSerializationAdapter = () => [adapter]\n"],"mappings":"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"}
1
+ {"version":3,"file":"serialization.server.js","names":[],"sources":["../../src/serialization.server.ts"],"sourcesContent":["import { AsyncLocalStorage } from 'node:async_hooks'\nimport { createSerializationAdapter } from '@tanstack/react-router'\nimport { RawStream } from '@tanstack/router-core'\nimport { getStartContext } from '@tanstack/start-storage-context'\nimport {\n setOnClientReference,\n createFromReadableStream as ssrDecode,\n} from 'virtual:tanstack-rsc-ssr-decode'\nimport {\n RENDERABLE_RSC,\n RSC_SLOT_USAGES_STREAM,\n SERVER_COMPONENT_CSS_HREFS,\n SERVER_COMPONENT_JS_PRELOADS,\n SERVER_COMPONENT_STREAM,\n isServerComponent,\n} from './ServerComponentTypes'\nimport { createRscProxy } from './createRscProxy'\nimport { awaitLazyElements } from './awaitLazyElements'\nimport { unwrapRscCssEnvelope } from './rscCssEnvelope'\nimport type {\n AnyCompositeComponent,\n ServerComponentStream,\n} from './ServerComponentTypes'\nimport type { RscDecodeResult, RscSsrHandler } from './rscSsrHandler'\n\n// ===== SSR Handler Registration =====\n// This handler is registered on globalThis for the RSC environment to access.\n// The RSC env calls these functions during loader execution to pre-decode streams\n// and create renderable proxies.\n//\n// This MUST happen in a module without 'use client' directive.\n// Modules with 'use client' may be transformed to client references in the\n// SSR environment when RSC is enabled, preventing the side effect from running.\n\n// AsyncLocalStorage for decode-scoped proxy asset collectors.\n// Client-reference deps stay on the decoded RSC proxy so unused streams don't\n// emit global request assets. CSS marker links are collected for all adapters\n// because the decoded envelope is unwrapped before SSR render.\nconst proxyCssCollectorStorage = new AsyncLocalStorage<Set<string>>()\nconst proxyJsCollectorStorage = new AsyncLocalStorage<Set<string>>()\n\nsetOnClientReference(\n ({\n deps,\n runtime,\n }: {\n deps: { js: Array<string>; css: Array<string> }\n runtime?: 'rsbuild'\n }) => {\n const cssCollector = proxyCssCollectorStorage.getStore()\n if (cssCollector) {\n for (const href of deps.css) {\n cssCollector.add(href)\n }\n }\n\n const jsCollector = proxyJsCollectorStorage.getStore()\n if (jsCollector) {\n for (const href of deps.js) {\n jsCollector.add(href)\n }\n }\n\n if (runtime === 'rsbuild' || cssCollector || jsCollector) {\n return\n }\n\n const ctx = getStartContext({ throwIfNotFound: false })\n\n // Non-Rsbuild adapters emit decoded client-reference assets through the\n // request manifest.\n if (!ctx || (!deps.js.length && !deps.css.length)) {\n return\n }\n\n if (!ctx.requestAssets) ctx.requestAssets = {}\n const seenHrefs = new Set<string>()\n for (const preload of ctx.requestAssets.preloads ?? []) {\n seenHrefs.add(typeof preload === 'string' ? preload : preload.href)\n }\n for (const stylesheet of ctx.requestAssets.css ?? []) {\n seenHrefs.add(\n typeof stylesheet === 'string' ? stylesheet : stylesheet.href,\n )\n }\n\n let nextPreloads: typeof ctx.requestAssets.preloads | undefined\n for (const href of deps.js) {\n if (seenHrefs.has(href)) continue\n seenHrefs.add(href)\n if (!nextPreloads) {\n nextPreloads = ctx.requestAssets.preloads\n ? ctx.requestAssets.preloads.slice()\n : []\n }\n nextPreloads.push(href)\n }\n if (nextPreloads) {\n ctx.requestAssets.preloads = nextPreloads\n }\n\n let nextCss: typeof ctx.requestAssets.css | undefined\n for (const href of deps.css) {\n if (seenHrefs.has(href)) continue\n seenHrefs.add(href)\n if (!nextCss) {\n nextCss = ctx.requestAssets.css ? ctx.requestAssets.css.slice() : []\n }\n nextCss.push(href)\n }\n if (nextCss) {\n ctx.requestAssets.css = nextCss\n }\n },\n)\n\nconst ssrHandler: RscSsrHandler = {\n async decode(stream: ServerComponentStream): Promise<RscDecodeResult> {\n const readableStream = stream.createReplayStream()\n\n // Create a collector for this decode operation.\n // Run the decode in an AsyncLocalStorage context so the onClientReference\n // callback can write to this specific collector even with parallel decodes.\n const proxyCssCollector = new Set<string>()\n const proxyJsCollector = new Set<string>()\n\n return proxyCssCollectorStorage.run(proxyCssCollector, async () => {\n return proxyJsCollectorStorage.run(proxyJsCollector, async () => {\n const decodedTree = await ssrDecode(readableStream)\n await awaitLazyElements(decodedTree, (href) => {\n proxyCssCollector.add(href)\n })\n\n return {\n tree: unwrapRscCssEnvelope(decodedTree),\n cssHrefs: proxyCssCollector.size > 0 ? proxyCssCollector : undefined,\n jsPreloads: proxyJsCollector.size > 0 ? proxyJsCollector : undefined,\n }\n })\n })\n },\n\n createRenderableProxy(stream, decoded): any {\n return createRscProxy(() => decoded.tree, {\n stream,\n cssHrefs: decoded.cssHrefs,\n jsPreloads: decoded.jsPreloads,\n renderable: true,\n })\n },\n\n createCompositeProxy(\n stream,\n decoded,\n slotUsagesStream,\n ): AnyCompositeComponent {\n const proxy = createRscProxy(() => decoded.tree, {\n stream,\n cssHrefs: decoded.cssHrefs,\n jsPreloads: decoded.jsPreloads,\n renderable: false,\n slotUsagesStream,\n })\n return proxy\n },\n}\n\n// Register SSR handler on globalThis for RSC environment to access.\nglobalThis.__RSC_SSR__ = ssrHandler\n\n// ===== End SSR Handler Registration =====\n\n/**\n * Helper to check if a value is a renderable RSC (from renderServerComponent).\n * The value can be either an object (proxy target) or a function (stub for server functions).\n */\nfunction isRenderableRsc(value: unknown): boolean {\n if (value === null || value === undefined) return false\n if (typeof value !== 'object' && typeof value !== 'function') return false\n return RENDERABLE_RSC in value && (value as any)[RENDERABLE_RSC] === true\n}\n\n/**\n * Server-side serialization adapter for RSC (renderable + composite).\n */\nconst adapter = createSerializationAdapter({\n key: '$RSC',\n test: (value: unknown): value is AnyCompositeComponent => {\n return isServerComponent(value)\n },\n toSerializable: (component: AnyCompositeComponent) => {\n const stream = component[SERVER_COMPONENT_STREAM]!.createReplayStream()\n\n const kind = isRenderableRsc(component) ? 'renderable' : 'composite'\n const jsPreloads = component[SERVER_COMPONENT_JS_PRELOADS]\n const cssHrefs = component[SERVER_COMPONENT_CSS_HREFS]\n // `cssHrefs` can also come from loadCss() marker links. Client-reference\n // collection fills `jsPreloads`, so that gates per-stream serialization.\n const serializedAssetDeps = jsPreloads?.size\n ? {\n ...(cssHrefs?.size ? { cssHrefs: Array.from(cssHrefs) } : {}),\n jsPreloads: Array.from(jsPreloads),\n }\n : {}\n\n const slotUsagesStream =\n kind === 'composite' &&\n process.env.NODE_ENV === 'development' &&\n RSC_SLOT_USAGES_STREAM in component\n ? ((component as any)[RSC_SLOT_USAGES_STREAM] as unknown as\n | ReadableStream<any>\n | undefined)\n : undefined\n\n return {\n kind,\n stream: new RawStream(stream, { hint: 'text' }),\n slotUsagesStream,\n ...serializedAssetDeps,\n }\n },\n fromSerializable: (): never => {\n throw new Error('Server should never deserialize RSC data')\n },\n})\n\n/**\n * Factory function for server-side RSC serialization adapter.\n */\nexport const rscSerializationAdapter = () => [adapter]\n"],"mappings":";;;;;;;;;;AAsCA,IAAM,2BAA2B,IAAI,kBAA+B;AACpE,IAAM,0BAA0B,IAAI,kBAA+B;AAEnE,sBACG,EACC,MACA,cAII;CACJ,MAAM,eAAe,yBAAyB,SAAS;CACvD,IAAI,cACF,KAAK,MAAM,QAAQ,KAAK,KACtB,aAAa,IAAI,IAAI;CAIzB,MAAM,cAAc,wBAAwB,SAAS;CACrD,IAAI,aACF,KAAK,MAAM,QAAQ,KAAK,IACtB,YAAY,IAAI,IAAI;CAIxB,IAAI,YAAY,aAAa,gBAAgB,aAC3C;CAGF,MAAM,MAAM,gBAAgB,EAAE,iBAAiB,MAAM,CAAC;CAItD,IAAI,CAAC,OAAQ,CAAC,KAAK,GAAG,UAAU,CAAC,KAAK,IAAI,QACxC;CAGF,IAAI,CAAC,IAAI,eAAe,IAAI,gBAAgB,CAAC;CAC7C,MAAM,4BAAY,IAAI,IAAY;CAClC,KAAK,MAAM,WAAW,IAAI,cAAc,YAAY,CAAC,GACnD,UAAU,IAAI,OAAO,YAAY,WAAW,UAAU,QAAQ,IAAI;CAEpE,KAAK,MAAM,cAAc,IAAI,cAAc,OAAO,CAAC,GACjD,UAAU,IACR,OAAO,eAAe,WAAW,aAAa,WAAW,IAC3D;CAGF,IAAI;CACJ,KAAK,MAAM,QAAQ,KAAK,IAAI;EAC1B,IAAI,UAAU,IAAI,IAAI,GAAG;EACzB,UAAU,IAAI,IAAI;EAClB,IAAI,CAAC,cACH,eAAe,IAAI,cAAc,WAC7B,IAAI,cAAc,SAAS,MAAM,IACjC,CAAC;EAEP,aAAa,KAAK,IAAI;CACxB;CACA,IAAI,cACF,IAAI,cAAc,WAAW;CAG/B,IAAI;CACJ,KAAK,MAAM,QAAQ,KAAK,KAAK;EAC3B,IAAI,UAAU,IAAI,IAAI,GAAG;EACzB,UAAU,IAAI,IAAI;EAClB,IAAI,CAAC,SACH,UAAU,IAAI,cAAc,MAAM,IAAI,cAAc,IAAI,MAAM,IAAI,CAAC;EAErE,QAAQ,KAAK,IAAI;CACnB;CACA,IAAI,SACF,IAAI,cAAc,MAAM;AAE5B,CACF;AAsDA,WAAW,cAAc;CAnDvB,MAAM,OAAO,QAAyD;EACpE,MAAM,iBAAiB,OAAO,mBAAmB;EAKjD,MAAM,oCAAoB,IAAI,IAAY;EAC1C,MAAM,mCAAmB,IAAI,IAAY;EAEzC,OAAO,yBAAyB,IAAI,mBAAmB,YAAY;GACjE,OAAO,wBAAwB,IAAI,kBAAkB,YAAY;IAC/D,MAAM,cAAc,MAAM,yBAAU,cAAc;IAClD,MAAM,kBAAkB,cAAc,SAAS;KAC7C,kBAAkB,IAAI,IAAI;IAC5B,CAAC;IAED,OAAO;KACL,MAAM,qBAAqB,WAAW;KACtC,UAAU,kBAAkB,OAAO,IAAI,oBAAoB,KAAA;KAC3D,YAAY,iBAAiB,OAAO,IAAI,mBAAmB,KAAA;IAC7D;GACF,CAAC;EACH,CAAC;CACH;CAEA,sBAAsB,QAAQ,SAAc;EAC1C,OAAO,qBAAqB,QAAQ,MAAM;GACxC;GACA,UAAU,QAAQ;GAClB,YAAY,QAAQ;GACpB,YAAY;EACd,CAAC;CACH;CAEA,qBACE,QACA,SACA,kBACuB;EAQvB,OAPc,qBAAqB,QAAQ,MAAM;GAC/C;GACA,UAAU,QAAQ;GAClB,YAAY,QAAQ;GACpB,YAAY;GACZ;EACF,CACO;CACT;AAIuB;;;;;AAQzB,SAAS,gBAAgB,OAAyB;CAChD,IAAI,UAAU,QAAQ,UAAU,KAAA,GAAW,OAAO;CAClD,IAAI,OAAO,UAAU,YAAY,OAAO,UAAU,YAAY,OAAO;CACrE,OAAO,kBAAkB,SAAU,MAAc,oBAAoB;AACvE;;;;AAKA,IAAM,UAAU,2BAA2B;CACzC,KAAK;CACL,OAAO,UAAmD;EACxD,OAAO,kBAAkB,KAAK;CAChC;CACA,iBAAiB,cAAqC;EACpD,MAAM,SAAS,UAAU,yBAA0B,mBAAmB;EAEtE,MAAM,OAAO,gBAAgB,SAAS,IAAI,eAAe;EACzD,MAAM,aAAa,UAAU;EAC7B,MAAM,WAAW,UAAU;EAG3B,MAAM,sBAAsB,YAAY,OACpC;GACE,GAAI,UAAU,OAAO,EAAE,UAAU,MAAM,KAAK,QAAQ,EAAE,IAAI,CAAC;GAC3D,YAAY,MAAM,KAAK,UAAU;EACnC,IACA,CAAC;EAEL,MAAM,mBACJ,SAAS,eAAA,QAAA,IAAA,aACgB,iBACzB,0BAA0B,YACpB,UAAkB,0BAGpB,KAAA;EAEN,OAAO;GACL;GACA,QAAQ,IAAI,UAAU,QAAQ,EAAE,MAAM,OAAO,CAAC;GAC9C;GACA,GAAG;EACL;CACF;CACA,wBAA+B;EAC7B,MAAM,IAAI,MAAM,0CAA0C;CAC5D;AACF,CAAC;;;;AAKD,IAAa,gCAAgC,CAAC,OAAO"}
@@ -1,4 +1,8 @@
1
1
  import { AnyCompositeComponent, RscSlotUsageEvent } from './ServerComponentTypes.js';
2
+ type StreamDecodeOptions = {
3
+ cssHrefs?: Iterable<string>;
4
+ jsPreloads?: Iterable<string>;
5
+ };
2
6
  /**
3
7
  * Creates a renderable RSC proxy from a raw Flight stream.
4
8
  * Client-side only - used by the client serialization adapter for `renderServerComponent`.
@@ -8,7 +12,7 @@ import { AnyCompositeComponent, RscSlotUsageEvent } from './ServerComponentTypes
8
12
  * - Supports nested access: `{data.foo.bar}`
9
13
  * - Masquerades as a React element
10
14
  */
11
- export declare function createRenderableFromStream(stream: ReadableStream<Uint8Array>): any;
15
+ export declare function createRenderableFromStream(stream: ReadableStream<Uint8Array>, options?: StreamDecodeOptions): any;
12
16
  /**
13
17
  * Creates a composite RSC proxy from a raw Flight stream.
14
18
  * Client-side only - used by the client serialization adapter for `createCompositeComponent`.
@@ -20,5 +24,6 @@ export declare function createRenderableFromStream(stream: ReadableStream<Uint8A
20
24
  */
21
25
  export declare function createCompositeFromStream(stream: ReadableStream<Uint8Array>, options?: {
22
26
  slotUsagesStream?: ReadableStream<RscSlotUsageEvent>;
23
- }): AnyCompositeComponent;
27
+ } & StreamDecodeOptions): AnyCompositeComponent;
24
28
  export declare const createServerComponentFromStream: typeof createCompositeFromStream;
29
+ export {};
@@ -1,3 +1,18 @@
1
- import { setOnClientReference } from '@rspack/core/rsc/ssr';
2
- import { createFromReadableStream } from 'react-server-dom-rspack/client.node';
3
- export { createFromReadableStream, setOnClientReference };
1
+ /**
2
+ * Rsbuild SSR decode implementation.
3
+ *
4
+ * Bundler-owned rsbuild virtual modules re-export this module for SSR-side
5
+ * Flight decode.
6
+ */
7
+ type ClientReferenceDeps = {
8
+ js: Array<string>;
9
+ css: Array<string>;
10
+ };
11
+ type OnClientReference = (reference: {
12
+ id: string;
13
+ deps: ClientReferenceDeps;
14
+ runtime: 'rsbuild';
15
+ }) => void;
16
+ declare function setOnClientReference(callback: OnClientReference | undefined): void;
17
+ declare function createFromReadableStreamWithClientPreloads<T = unknown>(stream: ReadableStream<Uint8Array>, options?: object): Promise<T>;
18
+ export { setOnClientReference, createFromReadableStreamWithClientPreloads as createFromReadableStream, };
@@ -6,6 +6,8 @@ type SerializedRsc = {
6
6
  kind: 'renderable' | 'composite';
7
7
  stream: ReadableStream<Uint8Array>;
8
8
  slotUsagesStream?: ReadableStream<RscSlotUsageEvent>;
9
+ cssHrefs?: Array<string>;
10
+ jsPreloads?: Array<string>;
9
11
  };
10
12
  export declare const rscSerializationAdapter: () => import('@tanstack/router-core').SerializationAdapter<AnyCompositeComponent, SerializedRsc, never>[];
11
13
  export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tanstack/react-start-rsc",
3
- "version": "0.1.15",
3
+ "version": "0.1.17",
4
4
  "description": "React Server Components support for TanStack Start",
5
5
  "author": "Tanner Linsley",
6
6
  "license": "MIT",
@@ -82,15 +82,15 @@
82
82
  },
83
83
  "dependencies": {
84
84
  "pathe": "^2.0.3",
85
- "@tanstack/react-router": "1.170.9",
86
- "@tanstack/react-start-server": "1.167.11",
87
- "@tanstack/router-core": "1.171.7",
85
+ "@tanstack/react-router": "1.170.10",
86
+ "@tanstack/react-start-server": "1.167.13",
87
+ "@tanstack/router-core": "1.171.8",
88
88
  "@tanstack/router-utils": "1.162.1",
89
- "@tanstack/start-client-core": "1.170.5",
89
+ "@tanstack/start-client-core": "1.170.6",
90
90
  "@tanstack/start-fn-stubs": "1.162.0",
91
- "@tanstack/start-plugin-core": "1.171.8",
92
- "@tanstack/start-server-core": "1.169.6",
93
- "@tanstack/start-storage-context": "1.167.9"
91
+ "@tanstack/start-plugin-core": "1.171.10",
92
+ "@tanstack/start-server-core": "1.169.8",
93
+ "@tanstack/start-storage-context": "1.167.10"
94
94
  },
95
95
  "devDependencies": {
96
96
  "@rspack/core": "2.0.0",
@@ -33,7 +33,10 @@ function* findPendingLazyPayloads(
33
33
  el.type === 'link' &&
34
34
  el.props?.rel === 'stylesheet'
35
35
  ) {
36
- const cssHref = el.props['data-rsc-css-href'] as string | undefined
36
+ let cssHref: string | undefined
37
+ if ('data-rsc-css-href' in el.props) {
38
+ cssHref = el.props.href
39
+ }
37
40
  if (cssHref && cssCollector) {
38
41
  cssCollector(cssHref)
39
42
  }
@@ -13,6 +13,11 @@ import type {
13
13
  ServerComponentStream,
14
14
  } from './ServerComponentTypes'
15
15
 
16
+ type StreamDecodeOptions = {
17
+ cssHrefs?: Iterable<string>
18
+ jsPreloads?: Iterable<string>
19
+ }
20
+
16
21
  /**
17
22
  * Creates a renderable RSC proxy from a raw Flight stream.
18
23
  * Client-side only - used by the client serialization adapter for `renderServerComponent`.
@@ -24,12 +29,17 @@ import type {
24
29
  */
25
30
  export function createRenderableFromStream(
26
31
  stream: ReadableStream<Uint8Array>,
32
+ options?: StreamDecodeOptions,
27
33
  ): any {
28
- const { getTree, streamWrapper, cssHrefs } = setupStreamDecode(stream)
34
+ const { getTree, streamWrapper, cssHrefs, jsPreloads } = setupStreamDecode(
35
+ stream,
36
+ options,
37
+ )
29
38
 
30
39
  return createRscProxy(getTree, {
31
40
  stream: streamWrapper,
32
41
  cssHrefs,
42
+ jsPreloads,
33
43
  renderable: true,
34
44
  })
35
45
  }
@@ -47,13 +57,17 @@ export function createCompositeFromStream(
47
57
  stream: ReadableStream<Uint8Array>,
48
58
  options?: {
49
59
  slotUsagesStream?: ReadableStream<RscSlotUsageEvent>
50
- },
60
+ } & StreamDecodeOptions,
51
61
  ): AnyCompositeComponent {
52
- const { getTree, streamWrapper, cssHrefs } = setupStreamDecode(stream)
62
+ const { getTree, streamWrapper, cssHrefs, jsPreloads } = setupStreamDecode(
63
+ stream,
64
+ options,
65
+ )
53
66
 
54
67
  return createRscProxy(getTree, {
55
68
  stream: streamWrapper,
56
69
  cssHrefs,
70
+ jsPreloads,
57
71
  renderable: false,
58
72
  slotUsagesStream: options?.slotUsagesStream,
59
73
  })
@@ -62,33 +76,51 @@ export function createCompositeFromStream(
62
76
  /**
63
77
  * Shared stream decode setup for both renderable and composite.
64
78
  */
65
- function setupStreamDecode(stream: ReadableStream<Uint8Array>): {
79
+ function setupStreamDecode(
80
+ stream: ReadableStream<Uint8Array>,
81
+ options?: StreamDecodeOptions,
82
+ ): {
66
83
  getTree: () => unknown
67
84
  streamWrapper: ServerComponentStream
68
- cssHrefs: Set<string> | undefined
85
+ cssHrefs: Set<string>
86
+ jsPreloads: Set<string> | undefined
69
87
  } {
70
- // Start decoding eagerly during deserialization
71
- const decodeThenable = browserDecode(stream)
72
- const cssHrefs = new Set<string>()
88
+ const cssHrefs = new Set(options?.cssHrefs ?? [])
89
+ const jsPreloads = options?.jsPreloads
90
+ ? new Set(options.jsPreloads)
91
+ : undefined
92
+ const shouldDeferDecode = cssHrefs.size > 0 || !!jsPreloads?.size
73
93
 
74
94
  // Synchronous cache for the decoded tree.
75
95
  let cachedTree: unknown = undefined
76
96
  let cacheReady = false
97
+ let transformedTreePromise: Promise<unknown> | undefined
77
98
 
78
- // Promise for the tree with lazy elements awaited.
79
- const transformedTreePromise = Promise.resolve(decodeThenable).then(
80
- async (result) => {
81
- await awaitLazyElements(result, (href) => {
82
- cssHrefs.add(href)
83
- })
84
- cachedTree = unwrapRscCssEnvelope(result)
85
- cacheReady = true
86
- return cachedTree
87
- },
88
- )
99
+ const startDecode = () => {
100
+ if (!transformedTreePromise) {
101
+ // Promise for the tree with lazy elements awaited.
102
+ transformedTreePromise = Promise.resolve(browserDecode(stream)).then(
103
+ async (result) => {
104
+ await awaitLazyElements(result, (href) => {
105
+ cssHrefs.add(href)
106
+ })
107
+ cachedTree = unwrapRscCssEnvelope(result)
108
+ cacheReady = true
109
+ return cachedTree
110
+ },
111
+ )
112
+
113
+ // Track the lazy element loading - prevents flash for RPC/RSC fetches.
114
+ trackPostProcessPromise(transformedTreePromise)
115
+ }
89
116
 
90
- // Track the lazy element loading - prevents flash
91
- trackPostProcessPromise(transformedTreePromise)
117
+ return transformedTreePromise
118
+ }
119
+
120
+ if (!shouldDeferDecode) {
121
+ // Start decoding eagerly during deserialization for non-SSR streams.
122
+ startDecode()
123
+ }
92
124
 
93
125
  const streamWrapper: ServerComponentStream = {
94
126
  createReplayStream: () => stream,
@@ -97,10 +129,10 @@ function setupStreamDecode(stream: ReadableStream<Uint8Array>): {
97
129
  const getTree = () => {
98
130
  if (cacheReady) return cachedTree
99
131
  // eslint-disable-next-line react-hooks/rules-of-hooks
100
- return use(transformedTreePromise)
132
+ return use(startDecode())
101
133
  }
102
134
 
103
- return { getTree, streamWrapper, cssHrefs }
135
+ return { getTree, streamWrapper, cssHrefs, jsPreloads }
104
136
  }
105
137
  // Legacy export for backwards compatibility during migration
106
138
  export const createServerComponentFromStream = createCompositeFromStream
@@ -5,7 +5,124 @@
5
5
  * Flight decode.
6
6
  */
7
7
 
8
- import { setOnClientReference } from '@rspack/core/rsc/ssr'
9
- import { createFromReadableStream } from 'react-server-dom-rspack/client.node'
8
+ import { createFromReadableStream as decodeFlightStream } from 'react-server-dom-rspack/client.node'
10
9
 
11
- export { createFromReadableStream, setOnClientReference }
10
+ type ClientReferenceDeps = {
11
+ js: Array<string>
12
+ css: Array<string>
13
+ }
14
+
15
+ type OnClientReference = (reference: {
16
+ id: string
17
+ deps: ClientReferenceDeps
18
+ runtime: 'rsbuild'
19
+ }) => void
20
+
21
+ type ImportManifestEntry = {
22
+ id: string
23
+ chunks: Array<string>
24
+ cssFiles?: Array<string>
25
+ name: string
26
+ async?: boolean
27
+ }
28
+
29
+ type ClientManifest = Record<string, ImportManifestEntry>
30
+ type ServerConsumerModuleMap = null | Record<
31
+ string,
32
+ Record<string, ImportManifestEntry>
33
+ >
34
+ type ModuleLoading = null | {
35
+ prefix: string
36
+ crossOrigin?: 'use-credentials' | ''
37
+ }
38
+
39
+ declare const __rspack_rsc_manifest__: {
40
+ moduleLoading: ModuleLoading
41
+ serverConsumerModuleMap: ServerConsumerModuleMap
42
+ clientManifest: ClientManifest
43
+ }
44
+
45
+ const CHUNK_FILENAME_INDEX = 1
46
+ const CHUNK_PAIR_SIZE = 2
47
+
48
+ let onClientReference: OnClientReference | undefined
49
+ let clientReferenceDepsByModuleId: Map<string, ClientReferenceDeps> | undefined
50
+
51
+ function getClientReferenceDepsByModuleId() {
52
+ if (clientReferenceDepsByModuleId) return clientReferenceDepsByModuleId
53
+
54
+ clientReferenceDepsByModuleId = new Map()
55
+ const prefix = __rspack_rsc_manifest__.moduleLoading?.prefix ?? ''
56
+
57
+ for (const entry of Object.values(__rspack_rsc_manifest__.clientManifest)) {
58
+ let deps = clientReferenceDepsByModuleId.get(entry.id)
59
+ if (!deps) {
60
+ deps = { js: [], css: [] }
61
+ clientReferenceDepsByModuleId.set(entry.id, deps)
62
+ }
63
+
64
+ // React/Rspack stores JS chunks as chunk id/filename pairs. CSS files are
65
+ // separate manifest entries and are already emitted as public hrefs.
66
+ for (
67
+ let i = CHUNK_FILENAME_INDEX;
68
+ i < entry.chunks.length;
69
+ i += CHUNK_PAIR_SIZE
70
+ ) {
71
+ deps.js.push(prefix + entry.chunks[i])
72
+ }
73
+
74
+ if (entry.cssFiles) {
75
+ deps.css.push(...entry.cssFiles)
76
+ }
77
+ }
78
+
79
+ return clientReferenceDepsByModuleId
80
+ }
81
+
82
+ function emitClientReferencePreloadsForModule(moduleId: string) {
83
+ const deps = getClientReferenceDepsByModuleId().get(moduleId)
84
+ if (!onClientReference || !deps) return
85
+
86
+ if (deps.js.length === 0 && deps.css.length === 0) {
87
+ return
88
+ }
89
+
90
+ onClientReference({
91
+ id: moduleId,
92
+ deps,
93
+ runtime: 'rsbuild',
94
+ })
95
+ }
96
+
97
+ if (__rspack_rsc_manifest__.serverConsumerModuleMap) {
98
+ __rspack_rsc_manifest__.serverConsumerModuleMap = new Proxy(
99
+ __rspack_rsc_manifest__.serverConsumerModuleMap,
100
+ {
101
+ get(target, property, receiver) {
102
+ const moduleExports = Reflect.get(target, property, receiver)
103
+
104
+ if (typeof property === 'string' && moduleExports !== undefined) {
105
+ emitClientReferencePreloadsForModule(property)
106
+ }
107
+
108
+ return moduleExports
109
+ },
110
+ },
111
+ )
112
+ }
113
+
114
+ function setOnClientReference(callback: OnClientReference | undefined) {
115
+ onClientReference = callback
116
+ }
117
+
118
+ async function createFromReadableStreamWithClientPreloads<T = unknown>(
119
+ stream: ReadableStream<Uint8Array>,
120
+ options?: object,
121
+ ): Promise<T> {
122
+ return decodeFlightStream<T>(stream, options)
123
+ }
124
+
125
+ export {
126
+ setOnClientReference,
127
+ createFromReadableStreamWithClientPreloads as createFromReadableStream,
128
+ }
@@ -21,6 +21,8 @@ type SerializedRsc = {
21
21
  kind: 'renderable' | 'composite'
22
22
  stream: ReadableStream<Uint8Array>
23
23
  slotUsagesStream?: ReadableStream<RscSlotUsageEvent>
24
+ cssHrefs?: Array<string>
25
+ jsPreloads?: Array<string>
24
26
  }
25
27
 
26
28
  const adapter = createSerializationAdapter({
@@ -30,12 +32,21 @@ const adapter = createSerializationAdapter({
30
32
  throw new Error('RSC cannot be serialized on client')
31
33
  },
32
34
  fromSerializable: (value: SerializedRsc): AnyCompositeComponent => {
35
+ // Rsbuild SSR sends asset deps with each serialized RSC so the client can
36
+ // preload only if that specific stream is rendered. When absent, decode
37
+ // starts eagerly and discovers assets from the Flight payload as before.
38
+ const options = {
39
+ cssHrefs: value.cssHrefs,
40
+ jsPreloads: value.jsPreloads,
41
+ }
42
+
33
43
  if (value.kind === 'renderable') {
34
- return createRenderableFromStream(value.stream)
44
+ return createRenderableFromStream(value.stream, options)
35
45
  }
36
46
 
37
47
  return createCompositeFromStream(value.stream, {
38
48
  slotUsagesStream: value.slotUsagesStream,
49
+ ...options,
39
50
  })
40
51
  },
41
52
  })
@@ -9,6 +9,8 @@ import {
9
9
  import {
10
10
  RENDERABLE_RSC,
11
11
  RSC_SLOT_USAGES_STREAM,
12
+ SERVER_COMPONENT_CSS_HREFS,
13
+ SERVER_COMPONENT_JS_PRELOADS,
12
14
  SERVER_COMPONENT_STREAM,
13
15
  isServerComponent,
14
16
  } from './ServerComponentTypes'
@@ -30,11 +32,12 @@ import type { RscDecodeResult, RscSsrHandler } from './rscSsrHandler'
30
32
  // Modules with 'use client' may be transformed to client references in the
31
33
  // SSR environment when RSC is enabled, preventing the side effect from running.
32
34
 
33
- // AsyncLocalStorage for decode-scoped CSS collector.
34
- // Each decode() runs in its own async context with its own collector.
35
- // The onClientReference callback reads from this to write CSS hrefs.
36
- const decodeCollectorStorage = new AsyncLocalStorage<Set<string>>()
37
- const jsCollectorStorage = new AsyncLocalStorage<Set<string>>()
35
+ // AsyncLocalStorage for decode-scoped proxy asset collectors.
36
+ // Client-reference deps stay on the decoded RSC proxy so unused streams don't
37
+ // emit global request assets. CSS marker links are collected for all adapters
38
+ // because the decoded envelope is unwrapped before SSR render.
39
+ const proxyCssCollectorStorage = new AsyncLocalStorage<Set<string>>()
40
+ const proxyJsCollectorStorage = new AsyncLocalStorage<Set<string>>()
38
41
 
39
42
  setOnClientReference(
40
43
  ({
@@ -44,27 +47,29 @@ setOnClientReference(
44
47
  deps: { js: Array<string>; css: Array<string> }
45
48
  runtime?: 'rsbuild'
46
49
  }) => {
47
- const ctx = getStartContext({ throwIfNotFound: false })
48
-
49
- const cssCollector = decodeCollectorStorage.getStore()
50
+ const cssCollector = proxyCssCollectorStorage.getStore()
50
51
  if (cssCollector) {
51
52
  for (const href of deps.css) {
52
53
  cssCollector.add(href)
53
54
  }
54
55
  }
55
56
 
56
- const jsCollector = jsCollectorStorage.getStore()
57
+ const jsCollector = proxyJsCollectorStorage.getStore()
57
58
  if (jsCollector) {
58
59
  for (const href of deps.js) {
59
60
  jsCollector.add(href)
60
61
  }
61
62
  }
62
63
 
63
- if (
64
- !ctx ||
65
- runtime === 'rsbuild' ||
66
- (!deps.js.length && !deps.css.length)
67
- ) {
64
+ if (runtime === 'rsbuild' || cssCollector || jsCollector) {
65
+ return
66
+ }
67
+
68
+ const ctx = getStartContext({ throwIfNotFound: false })
69
+
70
+ // Non-Rsbuild adapters emit decoded client-reference assets through the
71
+ // request manifest.
72
+ if (!ctx || (!deps.js.length && !deps.css.length)) {
68
73
  return
69
74
  }
70
75
 
@@ -116,20 +121,20 @@ const ssrHandler: RscSsrHandler = {
116
121
  // Create a collector for this decode operation.
117
122
  // Run the decode in an AsyncLocalStorage context so the onClientReference
118
123
  // callback can write to this specific collector even with parallel decodes.
119
- const cssCollector = new Set<string>()
120
- const jsCollector = new Set<string>()
124
+ const proxyCssCollector = new Set<string>()
125
+ const proxyJsCollector = new Set<string>()
121
126
 
122
- return decodeCollectorStorage.run(cssCollector, async () => {
123
- return jsCollectorStorage.run(jsCollector, async () => {
127
+ return proxyCssCollectorStorage.run(proxyCssCollector, async () => {
128
+ return proxyJsCollectorStorage.run(proxyJsCollector, async () => {
124
129
  const decodedTree = await ssrDecode(readableStream)
125
130
  await awaitLazyElements(decodedTree, (href) => {
126
- cssCollector.add(href)
131
+ proxyCssCollector.add(href)
127
132
  })
128
133
 
129
134
  return {
130
135
  tree: unwrapRscCssEnvelope(decodedTree),
131
- cssHrefs: cssCollector.size > 0 ? cssCollector : undefined,
132
- jsPreloads: jsCollector.size > 0 ? jsCollector : undefined,
136
+ cssHrefs: proxyCssCollector.size > 0 ? proxyCssCollector : undefined,
137
+ jsPreloads: proxyJsCollector.size > 0 ? proxyJsCollector : undefined,
133
138
  }
134
139
  })
135
140
  })
@@ -187,6 +192,16 @@ const adapter = createSerializationAdapter({
187
192
  const stream = component[SERVER_COMPONENT_STREAM]!.createReplayStream()
188
193
 
189
194
  const kind = isRenderableRsc(component) ? 'renderable' : 'composite'
195
+ const jsPreloads = component[SERVER_COMPONENT_JS_PRELOADS]
196
+ const cssHrefs = component[SERVER_COMPONENT_CSS_HREFS]
197
+ // `cssHrefs` can also come from loadCss() marker links. Client-reference
198
+ // collection fills `jsPreloads`, so that gates per-stream serialization.
199
+ const serializedAssetDeps = jsPreloads?.size
200
+ ? {
201
+ ...(cssHrefs?.size ? { cssHrefs: Array.from(cssHrefs) } : {}),
202
+ jsPreloads: Array.from(jsPreloads),
203
+ }
204
+ : {}
190
205
 
191
206
  const slotUsagesStream =
192
207
  kind === 'composite' &&
@@ -201,6 +216,7 @@ const adapter = createSerializationAdapter({
201
216
  kind,
202
217
  stream: new RawStream(stream, { hint: 'text' }),
203
218
  slotUsagesStream,
219
+ ...serializedAssetDeps,
204
220
  }
205
221
  },
206
222
  fromSerializable: (): never => {