@tanstack/react-start-rsc 0.1.16 → 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.
- package/dist/esm/awaitLazyElements.js +2 -1
- package/dist/esm/awaitLazyElements.js.map +1 -1
- package/dist/esm/createServerComponentFromStream.js +28 -17
- package/dist/esm/createServerComponentFromStream.js.map +1 -1
- package/dist/esm/rsbuild/ssr-decode.js +44 -2
- package/dist/esm/rsbuild/ssr-decode.js.map +1 -1
- package/dist/esm/serialization.client.js +9 -2
- package/dist/esm/serialization.client.js.map +1 -1
- package/dist/esm/serialization.server.js +23 -15
- package/dist/esm/serialization.server.js.map +1 -1
- package/dist/esm/src/createServerComponentFromStream.d.ts +7 -2
- package/dist/esm/src/rsbuild/ssr-decode.d.ts +18 -3
- package/dist/esm/src/serialization.client.d.ts +2 -0
- package/package.json +4 -4
- package/src/awaitLazyElements.ts +4 -1
- package/src/createServerComponentFromStream.ts +55 -23
- package/src/rsbuild/ssr-decode.ts +120 -3
- package/src/serialization.client.ts +12 -1
- package/src/serialization.server.ts +37 -21
|
@@ -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
|
-
|
|
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
|
|
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
|
|
49
|
-
const
|
|
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
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
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(
|
|
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
|
|
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 {
|
|
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
|
-
|
|
14
|
-
|
|
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;
|
|
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
|
|
12
|
-
var
|
|
11
|
+
var proxyCssCollectorStorage = new AsyncLocalStorage();
|
|
12
|
+
var proxyJsCollectorStorage = new AsyncLocalStorage();
|
|
13
13
|
setOnClientReference(({ deps, runtime }) => {
|
|
14
|
-
const
|
|
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 =
|
|
16
|
+
const jsCollector = proxyJsCollectorStorage.getStore();
|
|
18
17
|
if (jsCollector) for (const href of deps.js) jsCollector.add(href);
|
|
19
|
-
if (
|
|
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
|
|
45
|
-
const
|
|
46
|
-
return
|
|
47
|
-
return
|
|
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
|
-
|
|
51
|
+
proxyCssCollector.add(href);
|
|
51
52
|
});
|
|
52
53
|
return {
|
|
53
54
|
tree: unwrapRscCssEnvelope(decodedTree),
|
|
54
|
-
cssHrefs:
|
|
55
|
-
jsPreloads:
|
|
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":"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"}
|
|
@@ -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
|
|
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
|
-
|
|
2
|
-
|
|
3
|
-
|
|
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.
|
|
3
|
+
"version": "0.1.17",
|
|
4
4
|
"description": "React Server Components support for TanStack Start",
|
|
5
5
|
"author": "Tanner Linsley",
|
|
6
6
|
"license": "MIT",
|
|
@@ -83,13 +83,13 @@
|
|
|
83
83
|
"dependencies": {
|
|
84
84
|
"pathe": "^2.0.3",
|
|
85
85
|
"@tanstack/react-router": "1.170.10",
|
|
86
|
-
"@tanstack/react-start-server": "1.167.
|
|
86
|
+
"@tanstack/react-start-server": "1.167.13",
|
|
87
87
|
"@tanstack/router-core": "1.171.8",
|
|
88
88
|
"@tanstack/router-utils": "1.162.1",
|
|
89
89
|
"@tanstack/start-client-core": "1.170.6",
|
|
90
90
|
"@tanstack/start-fn-stubs": "1.162.0",
|
|
91
|
-
"@tanstack/start-plugin-core": "1.171.
|
|
92
|
-
"@tanstack/start-server-core": "1.169.
|
|
91
|
+
"@tanstack/start-plugin-core": "1.171.10",
|
|
92
|
+
"@tanstack/start-server-core": "1.169.8",
|
|
93
93
|
"@tanstack/start-storage-context": "1.167.10"
|
|
94
94
|
},
|
|
95
95
|
"devDependencies": {
|
package/src/awaitLazyElements.ts
CHANGED
|
@@ -33,7 +33,10 @@ function* findPendingLazyPayloads(
|
|
|
33
33
|
el.type === 'link' &&
|
|
34
34
|
el.props?.rel === 'stylesheet'
|
|
35
35
|
) {
|
|
36
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
79
|
+
function setupStreamDecode(
|
|
80
|
+
stream: ReadableStream<Uint8Array>,
|
|
81
|
+
options?: StreamDecodeOptions,
|
|
82
|
+
): {
|
|
66
83
|
getTree: () => unknown
|
|
67
84
|
streamWrapper: ServerComponentStream
|
|
68
|
-
cssHrefs: Set<string>
|
|
85
|
+
cssHrefs: Set<string>
|
|
86
|
+
jsPreloads: Set<string> | undefined
|
|
69
87
|
} {
|
|
70
|
-
|
|
71
|
-
const
|
|
72
|
-
|
|
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
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
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
|
-
|
|
91
|
-
|
|
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(
|
|
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 {
|
|
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
|
-
|
|
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
|
|
34
|
-
//
|
|
35
|
-
//
|
|
36
|
-
|
|
37
|
-
const
|
|
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
|
|
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 =
|
|
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
|
-
|
|
65
|
-
|
|
66
|
-
|
|
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
|
|
120
|
-
const
|
|
124
|
+
const proxyCssCollector = new Set<string>()
|
|
125
|
+
const proxyJsCollector = new Set<string>()
|
|
121
126
|
|
|
122
|
-
return
|
|
123
|
-
return
|
|
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
|
-
|
|
131
|
+
proxyCssCollector.add(href)
|
|
127
132
|
})
|
|
128
133
|
|
|
129
134
|
return {
|
|
130
135
|
tree: unwrapRscCssEnvelope(decodedTree),
|
|
131
|
-
cssHrefs:
|
|
132
|
-
jsPreloads:
|
|
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 => {
|