@tanstack/redact 0.0.1
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/core/index.d.ts +2 -0
- package/dist/core/index.js +4 -0
- package/dist/core/index.js.map +7 -0
- package/dist/core/internal.d.ts +76 -0
- package/dist/core/internal.js +59 -0
- package/dist/core/internal.js.map +7 -0
- package/dist/core/types.d.ts +21 -0
- package/dist/core/types.js +10 -0
- package/dist/core/types.js.map +7 -0
- package/dist/dom/_all.d.ts +17 -0
- package/dist/dom/_all.js +51 -0
- package/dist/dom/_all.js.map +7 -0
- package/dist/dom/client.d.ts +3 -0
- package/dist/dom/client.js +8 -0
- package/dist/dom/client.js.map +7 -0
- package/dist/dom/dispatcher.d.ts +20 -0
- package/dist/dom/dispatcher.js +267 -0
- package/dist/dom/dispatcher.js.map +7 -0
- package/dist/dom/dom.d.ts +10 -0
- package/dist/dom/dom.js +262 -0
- package/dist/dom/dom.js.map +7 -0
- package/dist/dom/event-replay.d.ts +13 -0
- package/dist/dom/event-replay.js +46 -0
- package/dist/dom/event-replay.js.map +7 -0
- package/dist/dom/features/class/full.d.ts +1 -0
- package/dist/dom/features/class/full.js +91 -0
- package/dist/dom/features/class/full.js.map +7 -0
- package/dist/dom/features/class/index.d.ts +1 -0
- package/dist/dom/features/class/index.js +3 -0
- package/dist/dom/features/class/index.js.map +7 -0
- package/dist/dom/features/class/stub.d.ts +1 -0
- package/dist/dom/features/class/stub.js +50 -0
- package/dist/dom/features/class/stub.js.map +7 -0
- package/dist/dom/features/context/full.d.ts +1 -0
- package/dist/dom/features/context/full.js +49 -0
- package/dist/dom/features/context/full.js.map +7 -0
- package/dist/dom/features/context/index.d.ts +1 -0
- package/dist/dom/features/context/index.js +3 -0
- package/dist/dom/features/context/index.js.map +7 -0
- package/dist/dom/features/context/stub.d.ts +1 -0
- package/dist/dom/features/context/stub.js +22 -0
- package/dist/dom/features/context/stub.js.map +7 -0
- package/dist/dom/features/forward-ref/full.d.ts +1 -0
- package/dist/dom/features/forward-ref/full.js +51 -0
- package/dist/dom/features/forward-ref/full.js.map +7 -0
- package/dist/dom/features/forward-ref/index.d.ts +1 -0
- package/dist/dom/features/forward-ref/index.js +3 -0
- package/dist/dom/features/forward-ref/index.js.map +7 -0
- package/dist/dom/features/forward-ref/stub.d.ts +1 -0
- package/dist/dom/features/forward-ref/stub.js +22 -0
- package/dist/dom/features/forward-ref/stub.js.map +7 -0
- package/dist/dom/features/hydration/full.d.ts +61 -0
- package/dist/dom/features/hydration/full.js +264 -0
- package/dist/dom/features/hydration/full.js.map +7 -0
- package/dist/dom/features/hydration/index.d.ts +1 -0
- package/dist/dom/features/hydration/index.js +3 -0
- package/dist/dom/features/hydration/index.js.map +7 -0
- package/dist/dom/features/hydration/stub.d.ts +28 -0
- package/dist/dom/features/hydration/stub.js +66 -0
- package/dist/dom/features/hydration/stub.js.map +7 -0
- package/dist/dom/features/index.d.ts +7 -0
- package/dist/dom/features/index.js +9 -0
- package/dist/dom/features/index.js.map +7 -0
- package/dist/dom/features/lazy/full.d.ts +1 -0
- package/dist/dom/features/lazy/full.js +78 -0
- package/dist/dom/features/lazy/full.js.map +7 -0
- package/dist/dom/features/lazy/index.d.ts +1 -0
- package/dist/dom/features/lazy/index.js +3 -0
- package/dist/dom/features/lazy/index.js.map +7 -0
- package/dist/dom/features/lazy/stub.d.ts +1 -0
- package/dist/dom/features/lazy/stub.js +36 -0
- package/dist/dom/features/lazy/stub.js.map +7 -0
- package/dist/dom/features/memo/full.d.ts +1 -0
- package/dist/dom/features/memo/full.js +59 -0
- package/dist/dom/features/memo/full.js.map +7 -0
- package/dist/dom/features/memo/index.d.ts +1 -0
- package/dist/dom/features/memo/index.js +3 -0
- package/dist/dom/features/memo/index.js.map +7 -0
- package/dist/dom/features/memo/stub.d.ts +1 -0
- package/dist/dom/features/memo/stub.js +36 -0
- package/dist/dom/features/memo/stub.js.map +7 -0
- package/dist/dom/features/portal/full.d.ts +1 -0
- package/dist/dom/features/portal/full.js +19 -0
- package/dist/dom/features/portal/full.js.map +7 -0
- package/dist/dom/features/portal/index.d.ts +1 -0
- package/dist/dom/features/portal/index.js +3 -0
- package/dist/dom/features/portal/index.js.map +7 -0
- package/dist/dom/features/portal/stub.d.ts +1 -0
- package/dist/dom/features/portal/stub.js +7 -0
- package/dist/dom/features/portal/stub.js.map +7 -0
- package/dist/dom/features/suspense/full.d.ts +1 -0
- package/dist/dom/features/suspense/full.js +146 -0
- package/dist/dom/features/suspense/full.js.map +7 -0
- package/dist/dom/features/suspense/index.d.ts +1 -0
- package/dist/dom/features/suspense/index.js +3 -0
- package/dist/dom/features/suspense/index.js.map +7 -0
- package/dist/dom/features/suspense/stub.d.ts +1 -0
- package/dist/dom/features/suspense/stub.js +6 -0
- package/dist/dom/features/suspense/stub.js.map +7 -0
- package/dist/dom/index.d.ts +25 -0
- package/dist/dom/index.js +45 -0
- package/dist/dom/index.js.map +7 -0
- package/dist/dom/portal.d.ts +2 -0
- package/dist/dom/portal.js +15 -0
- package/dist/dom/portal.js.map +7 -0
- package/dist/dom/reconcile.d.ts +40 -0
- package/dist/dom/reconcile.js +795 -0
- package/dist/dom/reconcile.js.map +7 -0
- package/dist/dom/root.d.ts +15 -0
- package/dist/dom/root.js +85 -0
- package/dist/dom/root.js.map +7 -0
- package/dist/dom/test-utils.d.ts +1 -0
- package/dist/dom/test-utils.js +10 -0
- package/dist/dom/test-utils.js.map +7 -0
- package/dist/react/children.d.ts +8 -0
- package/dist/react/children.js +75 -0
- package/dist/react/children.js.map +7 -0
- package/dist/react/class.d.ts +31 -0
- package/dist/react/class.js +40 -0
- package/dist/react/class.js.map +7 -0
- package/dist/react/context.d.ts +27 -0
- package/dist/react/context.js +31 -0
- package/dist/react/context.js.map +7 -0
- package/dist/react/element.d.ts +10 -0
- package/dist/react/element.js +64 -0
- package/dist/react/element.js.map +7 -0
- package/dist/react/hooks.d.ts +37 -0
- package/dist/react/hooks.js +146 -0
- package/dist/react/hooks.js.map +7 -0
- package/dist/react/index.d.ts +76 -0
- package/dist/react/index.js +187 -0
- package/dist/react/index.js.map +7 -0
- package/dist/react/jsx-runtime.d.ts +5 -0
- package/dist/react/jsx-runtime.js +21 -0
- package/dist/react/jsx-runtime.js.map +7 -0
- package/dist/react/memo.d.ts +29 -0
- package/dist/react/memo.js +47 -0
- package/dist/react/memo.js.map +7 -0
- package/dist/react/portal.d.ts +1 -0
- package/dist/react/portal.js +6 -0
- package/dist/react/portal.js.map +7 -0
- package/dist/react/shared-internals.d.ts +33 -0
- package/dist/react/shared-internals.js +29 -0
- package/dist/react/shared-internals.js.map +7 -0
- package/dist/react/suspense.d.ts +15 -0
- package/dist/react/suspense.js +16 -0
- package/dist/react/suspense.js.map +7 -0
- package/dist/scheduler/index.d.ts +44 -0
- package/dist/scheduler/index.js +109 -0
- package/dist/scheduler/index.js.map +7 -0
- package/dist/server/bootstrap-script.d.ts +22 -0
- package/dist/server/bootstrap-script.js +16 -0
- package/dist/server/bootstrap-script.js.map +7 -0
- package/dist/server/dispatcher.d.ts +37 -0
- package/dist/server/dispatcher.js +140 -0
- package/dist/server/dispatcher.js.map +7 -0
- package/dist/server/escape.d.ts +7 -0
- package/dist/server/escape.js +159 -0
- package/dist/server/escape.js.map +7 -0
- package/dist/server/index.d.ts +16 -0
- package/dist/server/index.js +26 -0
- package/dist/server/index.js.map +7 -0
- package/dist/server/renderToString.d.ts +7 -0
- package/dist/server/renderToString.js +29 -0
- package/dist/server/renderToString.js.map +7 -0
- package/dist/server/stream.d.ts +40 -0
- package/dist/server/stream.js +200 -0
- package/dist/server/stream.js.map +7 -0
- package/dist/server/walk.d.ts +33 -0
- package/dist/server/walk.js +298 -0
- package/dist/server/walk.js.map +7 -0
- package/dist/vite/index.d.ts +96 -0
- package/dist/vite/index.js +217 -0
- package/dist/vite/index.js.map +7 -0
- package/package.json +83 -0
- package/src/core/index.ts +2 -0
- package/src/core/internal.ts +107 -0
- package/src/core/types.ts +36 -0
- package/src/dom/_all.ts +54 -0
- package/src/dom/client.ts +4 -0
- package/src/dom/dispatcher.ts +318 -0
- package/src/dom/dom.ts +316 -0
- package/src/dom/event-replay.ts +55 -0
- package/src/dom/features/class/full.ts +104 -0
- package/src/dom/features/class/index.ts +1 -0
- package/src/dom/features/class/stub.ts +65 -0
- package/src/dom/features/context/full.ts +57 -0
- package/src/dom/features/context/index.ts +1 -0
- package/src/dom/features/context/stub.ts +32 -0
- package/src/dom/features/forward-ref/full.ts +54 -0
- package/src/dom/features/forward-ref/index.ts +1 -0
- package/src/dom/features/forward-ref/stub.ts +28 -0
- package/src/dom/features/hydration/full.ts +345 -0
- package/src/dom/features/hydration/index.ts +1 -0
- package/src/dom/features/hydration/stub.ts +72 -0
- package/src/dom/features/index.ts +11 -0
- package/src/dom/features/lazy/full.ts +98 -0
- package/src/dom/features/lazy/index.ts +1 -0
- package/src/dom/features/lazy/stub.ts +45 -0
- package/src/dom/features/memo/full.ts +74 -0
- package/src/dom/features/memo/index.ts +1 -0
- package/src/dom/features/memo/stub.ts +42 -0
- package/src/dom/features/portal/full.ts +22 -0
- package/src/dom/features/portal/index.ts +1 -0
- package/src/dom/features/portal/stub.ts +11 -0
- package/src/dom/features/suspense/full.ts +191 -0
- package/src/dom/features/suspense/index.ts +1 -0
- package/src/dom/features/suspense/stub.ts +12 -0
- package/src/dom/index.ts +33 -0
- package/src/dom/portal.ts +16 -0
- package/src/dom/reconcile.ts +1169 -0
- package/src/dom/root.ts +107 -0
- package/src/dom/test-utils.ts +6 -0
- package/src/react/children.ts +85 -0
- package/src/react/class.ts +56 -0
- package/src/react/context.ts +50 -0
- package/src/react/element.ts +80 -0
- package/src/react/hooks.ts +190 -0
- package/src/react/index.ts +132 -0
- package/src/react/jsx-runtime.ts +16 -0
- package/src/react/memo.ts +45 -0
- package/src/react/portal.ts +1 -0
- package/src/react/shared-internals.ts +79 -0
- package/src/react/suspense.ts +18 -0
- package/src/scheduler/index.ts +124 -0
- package/src/server/bootstrap-script.ts +52 -0
- package/src/server/dispatcher.ts +158 -0
- package/src/server/escape.ts +187 -0
- package/src/server/index.ts +26 -0
- package/src/server/renderToString.ts +33 -0
- package/src/server/stream.ts +302 -0
- package/src/server/walk.ts +448 -0
- package/src/vite/index.ts +401 -0
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export { renderToString, renderToStaticMarkup } from './renderToString'
|
|
2
|
+
export { renderToReadableStream, renderToPipeableStream } from './stream'
|
|
3
|
+
export type {
|
|
4
|
+
StreamOptions,
|
|
5
|
+
PipeableOptions,
|
|
6
|
+
PipeableHandle,
|
|
7
|
+
ReadableStreamResult,
|
|
8
|
+
} from './stream'
|
|
9
|
+
export { BOUNDARY_REVEAL_RUNTIME } from './bootstrap-script'
|
|
10
|
+
|
|
11
|
+
export const version = '19.2.3'
|
|
12
|
+
|
|
13
|
+
// Default export — React's real `react-dom/server` ships one, and TanStack
|
|
14
|
+
// Router's SSR code does `import ReactDOMServer from 'react-dom/server'` and
|
|
15
|
+
// reads `.renderToReadableStream` off the default.
|
|
16
|
+
import { renderToString, renderToStaticMarkup } from './renderToString'
|
|
17
|
+
import { renderToReadableStream, renderToPipeableStream } from './stream'
|
|
18
|
+
import { BOUNDARY_REVEAL_RUNTIME } from './bootstrap-script'
|
|
19
|
+
export default {
|
|
20
|
+
renderToString,
|
|
21
|
+
renderToStaticMarkup,
|
|
22
|
+
renderToReadableStream,
|
|
23
|
+
renderToPipeableStream,
|
|
24
|
+
BOUNDARY_REVEAL_RUNTIME,
|
|
25
|
+
version: '19.2.3',
|
|
26
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { ReactNode } from '../core'
|
|
2
|
+
import { beginSSR, endSSR, installSSRDispatcher, uninstallSSRDispatcher } from './dispatcher'
|
|
3
|
+
import { walk } from './walk'
|
|
4
|
+
|
|
5
|
+
export function renderToString(
|
|
6
|
+
children: ReactNode,
|
|
7
|
+
options: { identifierPrefix?: string } = {},
|
|
8
|
+
): string {
|
|
9
|
+
installSSRDispatcher()
|
|
10
|
+
beginSSR(options.identifierPrefix)
|
|
11
|
+
let output = ''
|
|
12
|
+
let id = 0
|
|
13
|
+
try {
|
|
14
|
+
walk(children, {
|
|
15
|
+
emit: (s) => {
|
|
16
|
+
output += s
|
|
17
|
+
},
|
|
18
|
+
nextBoundaryId: () => id++,
|
|
19
|
+
})
|
|
20
|
+
} finally {
|
|
21
|
+
endSSR()
|
|
22
|
+
uninstallSSRDispatcher()
|
|
23
|
+
}
|
|
24
|
+
return output
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function renderToStaticMarkup(
|
|
28
|
+
children: ReactNode,
|
|
29
|
+
options: { identifierPrefix?: string } = {},
|
|
30
|
+
): string {
|
|
31
|
+
// For our purposes identical to renderToString (no hydration markers in static output)
|
|
32
|
+
return renderToString(children, options).replace(/<!--[^>]*-->/g, '')
|
|
33
|
+
}
|
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
import type { ReactNode } from '../core'
|
|
2
|
+
import {
|
|
3
|
+
beginSSR,
|
|
4
|
+
endSSR,
|
|
5
|
+
installSSRDispatcher,
|
|
6
|
+
uninstallSSRDispatcher,
|
|
7
|
+
applyContextSnapshot,
|
|
8
|
+
} from './dispatcher'
|
|
9
|
+
import { walk, type SuspendedBoundary } from './walk'
|
|
10
|
+
import { BOUNDARY_REVEAL_RUNTIME, revealScript } from './bootstrap-script'
|
|
11
|
+
|
|
12
|
+
export interface StreamOptions {
|
|
13
|
+
identifierPrefix?: string
|
|
14
|
+
nonce?: string
|
|
15
|
+
bootstrapScripts?: ReadonlyArray<string | { src: string; async?: boolean; nonce?: string }>
|
|
16
|
+
bootstrapModules?: ReadonlyArray<string | { src: string; nonce?: string }>
|
|
17
|
+
onError?: (error: unknown) => string | void
|
|
18
|
+
signal?: AbortSignal
|
|
19
|
+
progressiveChunkSize?: number
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface ReadableStreamResult extends ReadableStream<Uint8Array> {
|
|
23
|
+
allReady: Promise<void>
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface OrchestratorState {
|
|
27
|
+
nextId: number
|
|
28
|
+
pending: Set<Promise<void>>
|
|
29
|
+
closed: boolean
|
|
30
|
+
errored: unknown | null
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
type Emit = (chunk: string) => void
|
|
34
|
+
|
|
35
|
+
export async function streamHtml(
|
|
36
|
+
children: ReactNode,
|
|
37
|
+
emit: Emit,
|
|
38
|
+
options: StreamOptions,
|
|
39
|
+
state: OrchestratorState,
|
|
40
|
+
): Promise<void> {
|
|
41
|
+
installSSRDispatcher()
|
|
42
|
+
beginSSR(options.identifierPrefix)
|
|
43
|
+
const nonce = options.nonce
|
|
44
|
+
|
|
45
|
+
try {
|
|
46
|
+
const boundaries: SuspendedBoundary[] = []
|
|
47
|
+
|
|
48
|
+
// Buffer shell + bootstrap into a string[] and flush as a single emit.
|
|
49
|
+
// Per-emit overhead in renderToReadableStream is TextEncoder.encode +
|
|
50
|
+
// controller.enqueue — each walkHost normally fires 3+ emits (opening
|
|
51
|
+
// tag, per-attribute, closing bracket), which for a ~30-component tree
|
|
52
|
+
// is ~100 stream-controller round-trips. Batching collapses those into
|
|
53
|
+
// one encode and one enqueue per shell — measured ~2-4% of total SSR
|
|
54
|
+
// time on CPU profiles.
|
|
55
|
+
const shellChunks: string[] = []
|
|
56
|
+
const bufferedEmit: Emit = (chunk) => {
|
|
57
|
+
shellChunks.push(chunk)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// 1. Render the shell
|
|
61
|
+
walk(children, {
|
|
62
|
+
emit: bufferedEmit,
|
|
63
|
+
onSuspend: (b) => boundaries.push(b),
|
|
64
|
+
nextBoundaryId: () => state.nextId++,
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
// 2. Inject runtime + bootstrap scripts (once, after shell). Skip the
|
|
68
|
+
// reveal/event-replay runtime when nothing needs it — no suspensions to
|
|
69
|
+
// reveal and no bootstrap scripts to guard against early user input. That
|
|
70
|
+
// keeps fully-static SSR responses byte-equivalent to a plain walk and
|
|
71
|
+
// matches React's behavior where `renderToReadableStream` of a static
|
|
72
|
+
// tree emits only the markup.
|
|
73
|
+
const hasBootstrap =
|
|
74
|
+
(options.bootstrapScripts?.length ?? 0) > 0 ||
|
|
75
|
+
(options.bootstrapModules?.length ?? 0) > 0
|
|
76
|
+
if (boundaries.length > 0 || hasBootstrap) {
|
|
77
|
+
shellChunks.push(`<script${nonce ? ` nonce="${nonce}"` : ''}>${BOUNDARY_REVEAL_RUNTIME}</script>`)
|
|
78
|
+
for (const s of options.bootstrapScripts ?? []) {
|
|
79
|
+
shellChunks.push(bootstrapTag(s, 'script', nonce))
|
|
80
|
+
}
|
|
81
|
+
for (const m of options.bootstrapModules ?? []) {
|
|
82
|
+
shellChunks.push(bootstrapTag(m, 'module', nonce))
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (shellChunks.length) emit(shellChunks.join(''))
|
|
87
|
+
|
|
88
|
+
// 3. Stream suspended boundaries as they resolve
|
|
89
|
+
for (const b of boundaries) streamBoundary(b, emit, options, state)
|
|
90
|
+
await drain(state)
|
|
91
|
+
} catch (err) {
|
|
92
|
+
state.errored = err
|
|
93
|
+
if (options.onError) options.onError(err)
|
|
94
|
+
throw err
|
|
95
|
+
} finally {
|
|
96
|
+
endSSR()
|
|
97
|
+
uninstallSSRDispatcher()
|
|
98
|
+
state.closed = true
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function streamBoundary(
|
|
103
|
+
b: SuspendedBoundary,
|
|
104
|
+
emit: Emit,
|
|
105
|
+
options: StreamOptions,
|
|
106
|
+
state: OrchestratorState,
|
|
107
|
+
): void {
|
|
108
|
+
const task = (async () => {
|
|
109
|
+
try {
|
|
110
|
+
await b.thenable
|
|
111
|
+
} catch (err) {
|
|
112
|
+
if (options.onError) options.onError(err)
|
|
113
|
+
}
|
|
114
|
+
if (state.closed) return
|
|
115
|
+
|
|
116
|
+
// Re-render the boundary's children into a string, restoring the
|
|
117
|
+
// provider stack from when the boundary first suspended.
|
|
118
|
+
const parts: string[] = []
|
|
119
|
+
const sub: SuspendedBoundary[] = []
|
|
120
|
+
const restore = applyContextSnapshot(b.contextSnapshot)
|
|
121
|
+
try {
|
|
122
|
+
walk(b.children, {
|
|
123
|
+
emit: (s) => parts.push(s),
|
|
124
|
+
onSuspend: (n) => sub.push(n),
|
|
125
|
+
nextBoundaryId: () => state.nextId++,
|
|
126
|
+
})
|
|
127
|
+
} catch (err) {
|
|
128
|
+
if (options.onError) options.onError(err)
|
|
129
|
+
restore()
|
|
130
|
+
return
|
|
131
|
+
}
|
|
132
|
+
restore()
|
|
133
|
+
|
|
134
|
+
emit(`<div hidden id="S:${b.id}">${parts.join('')}</div>${revealScript(b.id, options.nonce)}`)
|
|
135
|
+
|
|
136
|
+
// Recurse: any nested suspensions inside the now-revealed content
|
|
137
|
+
for (const s of sub) streamBoundary(s, emit, options, state)
|
|
138
|
+
})()
|
|
139
|
+
state.pending.add(task)
|
|
140
|
+
task.finally(() => state.pending.delete(task))
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
async function drain(state: OrchestratorState): Promise<void> {
|
|
144
|
+
while (state.pending.size > 0) {
|
|
145
|
+
await Promise.race(state.pending)
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function bootstrapTag(
|
|
150
|
+
entry: string | { src: string; async?: boolean; nonce?: string },
|
|
151
|
+
kind: 'script' | 'module',
|
|
152
|
+
defaultNonce: string | undefined,
|
|
153
|
+
): string {
|
|
154
|
+
const src = typeof entry === 'string' ? entry : entry.src
|
|
155
|
+
const nonce = typeof entry === 'string' ? defaultNonce : entry.nonce ?? defaultNonce
|
|
156
|
+
const nAttr = nonce ? ` nonce="${nonce}"` : ''
|
|
157
|
+
if (kind === 'module') return `<script type="module"${nAttr} src="${src}"></script>`
|
|
158
|
+
return `<script async${nAttr} src="${src}"></script>`
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// ---------------------------------------------------------------------------
|
|
162
|
+
// Web Streams: renderToReadableStream
|
|
163
|
+
// ---------------------------------------------------------------------------
|
|
164
|
+
|
|
165
|
+
export function renderToReadableStream(
|
|
166
|
+
children: ReactNode,
|
|
167
|
+
options: StreamOptions = {},
|
|
168
|
+
): Promise<ReadableStreamResult> {
|
|
169
|
+
const state: OrchestratorState = {
|
|
170
|
+
nextId: 0,
|
|
171
|
+
pending: new Set(),
|
|
172
|
+
closed: false,
|
|
173
|
+
errored: null,
|
|
174
|
+
}
|
|
175
|
+
const encoder = new TextEncoder()
|
|
176
|
+
|
|
177
|
+
let allReadyResolve!: () => void
|
|
178
|
+
let allReadyReject!: (e: unknown) => void
|
|
179
|
+
const allReady = new Promise<void>((r, rej) => {
|
|
180
|
+
allReadyResolve = r
|
|
181
|
+
allReadyReject = rej
|
|
182
|
+
})
|
|
183
|
+
|
|
184
|
+
const stream = new ReadableStream<Uint8Array>({
|
|
185
|
+
start(controller) {
|
|
186
|
+
const emit = (chunk: string) => {
|
|
187
|
+
try {
|
|
188
|
+
controller.enqueue(encoder.encode(chunk))
|
|
189
|
+
} catch {}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
streamHtml(children, emit, options, state).then(
|
|
193
|
+
() => {
|
|
194
|
+
try {
|
|
195
|
+
controller.close()
|
|
196
|
+
} catch {}
|
|
197
|
+
allReadyResolve()
|
|
198
|
+
},
|
|
199
|
+
(err) => {
|
|
200
|
+
try {
|
|
201
|
+
controller.error(err)
|
|
202
|
+
} catch {}
|
|
203
|
+
allReadyReject(err)
|
|
204
|
+
},
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
options.signal?.addEventListener('abort', () => {
|
|
208
|
+
state.closed = true
|
|
209
|
+
try {
|
|
210
|
+
controller.close()
|
|
211
|
+
} catch {}
|
|
212
|
+
})
|
|
213
|
+
},
|
|
214
|
+
})
|
|
215
|
+
|
|
216
|
+
return Promise.resolve(Object.assign(stream, { allReady }))
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// ---------------------------------------------------------------------------
|
|
220
|
+
// Node Streams: renderToPipeableStream
|
|
221
|
+
// ---------------------------------------------------------------------------
|
|
222
|
+
|
|
223
|
+
export interface PipeableHandle {
|
|
224
|
+
pipe<T extends NodeJS.WritableStream>(dest: T): T
|
|
225
|
+
abort(reason?: unknown): void
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
export interface PipeableOptions extends StreamOptions {
|
|
229
|
+
onShellReady?: () => void
|
|
230
|
+
onShellError?: (err: unknown) => void
|
|
231
|
+
onAllReady?: () => void
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
export function renderToPipeableStream(
|
|
235
|
+
children: ReactNode,
|
|
236
|
+
options: PipeableOptions = {},
|
|
237
|
+
): PipeableHandle {
|
|
238
|
+
const state: OrchestratorState = {
|
|
239
|
+
nextId: 0,
|
|
240
|
+
pending: new Set(),
|
|
241
|
+
closed: false,
|
|
242
|
+
errored: null,
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
const buffers: string[] = []
|
|
246
|
+
let dest: NodeJS.WritableStream | null = null
|
|
247
|
+
let shellReady = false
|
|
248
|
+
let finished = false
|
|
249
|
+
let aborted = false
|
|
250
|
+
|
|
251
|
+
const flushTo = (w: NodeJS.WritableStream) => {
|
|
252
|
+
if (!buffers.length) return
|
|
253
|
+
for (const b of buffers) w.write(b)
|
|
254
|
+
buffers.length = 0
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
const emit: Emit = (chunk) => {
|
|
258
|
+
if (aborted || finished) return
|
|
259
|
+
if (dest) dest.write(chunk)
|
|
260
|
+
else buffers.push(chunk)
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// Kick off rendering
|
|
264
|
+
streamHtml(children, emit, options, state).then(
|
|
265
|
+
() => {
|
|
266
|
+
finished = true
|
|
267
|
+
if (dest) dest.end()
|
|
268
|
+
options.onAllReady?.()
|
|
269
|
+
},
|
|
270
|
+
(err) => {
|
|
271
|
+
if (!shellReady) {
|
|
272
|
+
options.onShellError?.(err)
|
|
273
|
+
} else {
|
|
274
|
+
options.onError?.(err)
|
|
275
|
+
if (dest) dest.end()
|
|
276
|
+
}
|
|
277
|
+
},
|
|
278
|
+
)
|
|
279
|
+
|
|
280
|
+
// We call onShellReady once the first synchronous emit has landed.
|
|
281
|
+
// streamHtml above runs the shell synchronously before the first await in drain(),
|
|
282
|
+
// so we can schedule onShellReady right after the first microtask.
|
|
283
|
+
queueMicrotask(() => {
|
|
284
|
+
if (aborted || finished) return
|
|
285
|
+
shellReady = true
|
|
286
|
+
options.onShellReady?.()
|
|
287
|
+
})
|
|
288
|
+
|
|
289
|
+
return {
|
|
290
|
+
pipe<T extends NodeJS.WritableStream>(target: T): T {
|
|
291
|
+
dest = target
|
|
292
|
+
flushTo(target)
|
|
293
|
+
if (finished) target.end()
|
|
294
|
+
return target
|
|
295
|
+
},
|
|
296
|
+
abort(_reason?: unknown) {
|
|
297
|
+
aborted = true
|
|
298
|
+
state.closed = true
|
|
299
|
+
if (dest) dest.end()
|
|
300
|
+
},
|
|
301
|
+
}
|
|
302
|
+
}
|