@tanstack/redact 0.0.21 → 0.1.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/attributes.js +2 -1
- package/dist/core/attributes.js.map +2 -2
- package/dist/core/browser.d.ts +9 -0
- package/dist/core/browser.js +35 -0
- package/dist/core/browser.js.map +7 -0
- package/dist/core/index.d.ts +2 -2
- package/dist/core/index.js +3 -3
- package/dist/core/internal.d.ts +31 -8
- package/dist/core/internal.js +3 -1
- package/dist/core/internal.js.map +2 -2
- package/dist/core/resource-hints.d.ts +7 -0
- package/dist/core/resource-hints.js +19 -0
- package/dist/core/resource-hints.js.map +7 -0
- package/dist/core/types.js +1 -1
- package/dist/dom/_all.d.ts +11 -16
- package/dist/dom/_all.js +9 -20
- package/dist/dom/_all.js.map +2 -2
- package/dist/dom/client.d.ts +4 -4
- package/dist/dom/client.js +4 -4
- package/dist/dom/commit.d.ts +23 -0
- package/dist/dom/commit.js +191 -0
- package/dist/dom/commit.js.map +7 -0
- package/dist/dom/dispatcher.d.ts +4 -22
- package/dist/dom/dispatcher.js +184 -126
- package/dist/dom/dispatcher.js.map +3 -3
- package/dist/dom/dom.d.ts +2 -0
- package/dist/dom/dom.js +72 -52
- package/dist/dom/dom.js.map +2 -2
- package/dist/dom/error-info.d.ts +2 -0
- package/dist/dom/error-info.js +16 -0
- package/dist/dom/error-info.js.map +7 -0
- package/dist/dom/event-replay.js +1 -1
- package/dist/dom/features/activity/full.d.ts +1 -0
- package/dist/dom/features/activity/full.js +162 -0
- package/dist/dom/features/activity/full.js.map +7 -0
- package/dist/dom/features/activity/index.d.ts +1 -0
- package/dist/dom/features/activity/index.js +3 -0
- package/dist/dom/features/activity/index.js.map +7 -0
- package/dist/dom/features/activity/stub.d.ts +1 -0
- package/dist/dom/features/activity/stub.js +12 -0
- package/dist/dom/features/activity/stub.js.map +7 -0
- package/dist/dom/features/class/full.js +87 -27
- package/dist/dom/features/class/full.js.map +2 -2
- package/dist/dom/features/class/index.d.ts +1 -1
- package/dist/dom/features/class/index.js +2 -2
- package/dist/dom/features/class/stub.js +3 -3
- package/dist/dom/features/context/full.d.ts +4 -1
- package/dist/dom/features/context/full.js +72 -15
- package/dist/dom/features/context/full.js.map +2 -2
- package/dist/dom/features/context/index.d.ts +1 -1
- package/dist/dom/features/context/index.js +2 -2
- package/dist/dom/features/context/stub.d.ts +4 -1
- package/dist/dom/features/context/stub.js +17 -8
- package/dist/dom/features/context/stub.js.map +2 -2
- package/dist/dom/features/forward-ref/full.js +12 -21
- package/dist/dom/features/forward-ref/full.js.map +2 -2
- package/dist/dom/features/forward-ref/index.d.ts +1 -1
- package/dist/dom/features/forward-ref/index.js +2 -2
- package/dist/dom/features/forward-ref/stub.js +4 -4
- package/dist/dom/features/fragment-refs/index.d.ts +1 -0
- package/dist/dom/features/fragment-refs/index.js +8 -0
- package/dist/dom/features/fragment-refs/index.js.map +7 -0
- package/dist/dom/features/fragment-refs/stub.d.ts +3 -0
- package/dist/dom/features/fragment-refs/stub.js +14 -0
- package/dist/dom/features/fragment-refs/stub.js.map +7 -0
- package/dist/dom/features/hydration/full.d.ts +11 -6
- package/dist/dom/features/hydration/full.js +76 -77
- package/dist/dom/features/hydration/full.js.map +2 -2
- package/dist/dom/features/hydration/index.d.ts +1 -1
- package/dist/dom/features/hydration/index.js +2 -2
- package/dist/dom/features/hydration/stub.d.ts +7 -2
- package/dist/dom/features/hydration/stub.js +1 -1
- package/dist/dom/features/hydration/stub.js.map +1 -1
- package/dist/dom/features/index.d.ts +8 -7
- package/dist/dom/features/index.js +9 -8
- package/dist/dom/features/index.js.map +2 -2
- package/dist/dom/features/lazy/full.js +4 -4
- package/dist/dom/features/lazy/index.d.ts +1 -1
- package/dist/dom/features/lazy/index.js +2 -2
- package/dist/dom/features/lazy/stub.js +4 -4
- package/dist/dom/features/memo/full.js +8 -6
- package/dist/dom/features/memo/full.js.map +2 -2
- package/dist/dom/features/memo/index.d.ts +1 -1
- package/dist/dom/features/memo/index.js +2 -2
- package/dist/dom/features/memo/stub.js +4 -4
- package/dist/dom/features/portal/full.js +4 -4
- package/dist/dom/features/portal/index.d.ts +1 -1
- package/dist/dom/features/portal/index.js +2 -2
- package/dist/dom/features/portal/stub.js +4 -4
- package/dist/dom/features/suspense/full.js +197 -52
- package/dist/dom/features/suspense/full.js.map +2 -2
- package/dist/dom/features/suspense/index.d.ts +1 -1
- package/dist/dom/features/suspense/index.js +2 -2
- package/dist/dom/features/suspense/stub.js +4 -4
- package/dist/dom/features/view-transition/full.d.ts +6 -0
- package/dist/dom/features/view-transition/full.js +280 -0
- package/dist/dom/features/view-transition/full.js.map +7 -0
- package/dist/dom/features/view-transition/index.d.ts +1 -0
- package/dist/dom/features/view-transition/index.js +3 -0
- package/dist/dom/features/view-transition/index.js.map +7 -0
- package/dist/dom/features/view-transition/resources.d.ts +11 -0
- package/dist/dom/features/view-transition/resources.js +108 -0
- package/dist/dom/features/view-transition/resources.js.map +7 -0
- package/dist/dom/features/view-transition/stub.d.ts +6 -0
- package/dist/dom/features/view-transition/stub.js +22 -0
- package/dist/dom/features/view-transition/stub.js.map +7 -0
- package/dist/dom/features/view-transition/visual.d.ts +14 -0
- package/dist/dom/features/view-transition/visual.js +356 -0
- package/dist/dom/features/view-transition/visual.js.map +7 -0
- package/dist/dom/fragment-instance.d.ts +40 -0
- package/dist/dom/fragment-instance.js +324 -0
- package/dist/dom/fragment-instance.js.map +7 -0
- package/dist/dom/index.d.ts +11 -11
- package/dist/dom/index.js +11 -18
- package/dist/dom/index.js.map +3 -3
- package/dist/dom/portal.d.ts +2 -2
- package/dist/dom/portal.js +2 -2
- package/dist/dom/portal.js.map +1 -1
- package/dist/dom/reconcile.d.ts +32 -3
- package/dist/dom/reconcile.js +689 -221
- package/dist/dom/reconcile.js.map +2 -2
- package/dist/dom/resource-hints.d.ts +6 -0
- package/dist/dom/resource-hints.js +115 -0
- package/dist/dom/resource-hints.js.map +7 -0
- package/dist/dom/resource-store.d.ts +13 -0
- package/dist/dom/resource-store.js +43 -0
- package/dist/dom/resource-store.js.map +7 -0
- package/dist/dom/resources.d.ts +1 -0
- package/dist/dom/resources.js +44 -0
- package/dist/dom/resources.js.map +7 -0
- package/dist/dom/retained-effects.d.ts +6 -0
- package/dist/dom/retained-effects.js +49 -0
- package/dist/dom/retained-effects.js.map +7 -0
- package/dist/dom/root-internal.d.ts +4 -4
- package/dist/dom/root-internal.js +2 -2
- package/dist/dom/root-internal.js.map +2 -2
- package/dist/dom/root.d.ts +5 -5
- package/dist/dom/root.js +11 -11
- package/dist/dom/root.js.map +2 -2
- package/dist/dom/test-utils.js +1 -1
- package/dist/react/activity.d.ts +7 -0
- package/dist/react/activity.js +8 -0
- package/dist/react/activity.js.map +7 -0
- package/dist/react/children.d.ts +1 -1
- package/dist/react/children.js +3 -3
- package/dist/react/children.js.map +1 -1
- package/dist/react/class.d.ts +1 -1
- package/dist/react/class.js +3 -3
- package/dist/react/class.js.map +1 -1
- package/dist/react/compiler-runtime.js +2 -2
- package/dist/react/context.d.ts +7 -2
- package/dist/react/context.js +3 -11
- package/dist/react/context.js.map +3 -3
- package/dist/react/element.d.ts +6 -3
- package/dist/react/element.js +7 -11
- package/dist/react/element.js.map +2 -2
- package/dist/react/hooks.d.ts +26 -7
- package/dist/react/hooks.js +13 -3
- package/dist/react/hooks.js.map +2 -2
- package/dist/react/index.d.ts +39 -24
- package/dist/react/index.js +39 -19
- package/dist/react/index.js.map +3 -3
- package/dist/react/jsx-runtime.d.ts +2 -2
- package/dist/react/jsx-runtime.js +3 -3
- package/dist/react/memo.d.ts +1 -1
- package/dist/react/memo.js +1 -1
- package/dist/react/portal.js +1 -1
- package/dist/react/shared-internals.d.ts +15 -5
- package/dist/react/shared-internals.js +2 -3
- package/dist/react/shared-internals.js.map +2 -2
- package/dist/react/suspense.js +1 -1
- package/dist/react/view-transition.d.ts +32 -0
- package/dist/react/view-transition.js +13 -0
- package/dist/react/view-transition.js.map +7 -0
- package/dist/scheduler/index.js +1 -1
- package/dist/server/bootstrap-script.d.ts +1 -23
- package/dist/server/bootstrap-script.js +7 -6
- package/dist/server/bootstrap-script.js.map +2 -2
- package/dist/server/dispatcher.d.ts +28 -6
- package/dist/server/dispatcher.js +33 -15
- package/dist/server/dispatcher.js.map +2 -2
- package/dist/server/escape.js +16 -15
- package/dist/server/escape.js.map +2 -2
- package/dist/server/index.d.ts +6 -6
- package/dist/server/index.js +7 -7
- package/dist/server/renderToString.d.ts +1 -1
- package/dist/server/renderToString.js +12 -8
- package/dist/server/renderToString.js.map +2 -2
- package/dist/server/resource-hints.d.ts +22 -0
- package/dist/server/resource-hints.js +148 -0
- package/dist/server/resource-hints.js.map +7 -0
- package/dist/server/stream.d.ts +15 -2
- package/dist/server/stream.js +155 -63
- package/dist/server/stream.js.map +2 -2
- package/dist/server/walk.d.ts +15 -2
- package/dist/server/walk.js +150 -110
- package/dist/server/walk.js.map +2 -2
- package/dist/vite/index.d.ts +6 -0
- package/dist/vite/index.js +22 -6
- package/dist/vite/index.js.map +2 -2
- package/package.json +1 -1
- package/src/core/attributes.ts +1 -0
- package/src/core/browser.ts +33 -0
- package/src/core/internal.ts +24 -6
- package/src/core/resource-hints.ts +36 -0
- package/src/dom/_all.ts +1 -7
- package/src/dom/commit.ts +186 -0
- package/src/dom/dispatcher.ts +190 -134
- package/src/dom/dom.ts +70 -54
- package/src/dom/error-info.ts +15 -0
- package/src/dom/features/activity/full.ts +166 -0
- package/src/dom/features/activity/index.ts +1 -0
- package/src/dom/features/activity/stub.ts +14 -0
- package/src/dom/features/class/full.ts +72 -28
- package/src/dom/features/context/full.ts +70 -13
- package/src/dom/features/context/stub.ts +10 -5
- package/src/dom/features/forward-ref/full.ts +9 -19
- package/src/dom/features/fragment-refs/index.ts +1 -0
- package/src/dom/features/fragment-refs/stub.ts +3 -0
- package/src/dom/features/hydration/full.ts +84 -85
- package/src/dom/features/hydration/stub.ts +1 -1
- package/src/dom/features/index.ts +1 -0
- package/src/dom/features/memo/full.ts +4 -3
- package/src/dom/features/suspense/full.ts +192 -55
- package/src/dom/features/view-transition/full.ts +288 -0
- package/src/dom/features/view-transition/index.ts +1 -0
- package/src/dom/features/view-transition/resources.ts +107 -0
- package/src/dom/features/view-transition/stub.ts +7 -0
- package/src/dom/features/view-transition/visual.ts +406 -0
- package/src/dom/fragment-instance.ts +337 -0
- package/src/dom/index.ts +6 -7
- package/src/dom/portal.ts +1 -1
- package/src/dom/reconcile.ts +695 -242
- package/src/dom/resource-hints.ts +115 -0
- package/src/dom/resource-store.ts +52 -0
- package/src/dom/resources.ts +39 -0
- package/src/dom/retained-effects.ts +38 -0
- package/src/dom/root-internal.ts +4 -4
- package/src/dom/root.ts +12 -14
- package/src/react/activity.ts +10 -0
- package/src/react/context.ts +4 -11
- package/src/react/element.ts +11 -12
- package/src/react/hooks.ts +12 -3
- package/src/react/index.ts +18 -0
- package/src/react/shared-internals.ts +3 -3
- package/src/react/view-transition.ts +41 -0
- package/src/server/bootstrap-script.ts +36 -5
- package/src/server/dispatcher.ts +35 -13
- package/src/server/escape.ts +15 -12
- package/src/server/renderToString.ts +7 -5
- package/src/server/resource-hints.ts +165 -0
- package/src/server/stream.ts +154 -62
- package/src/server/walk.ts +79 -26
- package/src/vite/index.ts +30 -13
package/src/server/stream.ts
CHANGED
|
@@ -2,21 +2,25 @@ import type { ReactNode } from '../core'
|
|
|
2
2
|
import {
|
|
3
3
|
beginSSR,
|
|
4
4
|
endSSR,
|
|
5
|
-
installSSRDispatcher,
|
|
6
|
-
uninstallSSRDispatcher,
|
|
7
5
|
applyContextSnapshot,
|
|
6
|
+
currentSSRFrame,
|
|
7
|
+
type SSRFrame,
|
|
8
8
|
} from './dispatcher'
|
|
9
|
-
import { walk, type SuspendedBoundary } from './walk'
|
|
9
|
+
import { walk, type SuspendedBoundary, type BrowserBailoutCallback } from './walk'
|
|
10
|
+
import { browserError, isBrowserToken, isBrowserError } from '../core/browser'
|
|
10
11
|
import { BOUNDARY_REVEAL_RUNTIME, revealScript } from './bootstrap-script'
|
|
11
|
-
import { escapeScript } from './escape'
|
|
12
|
+
import { escapeAttr, escapeScript } from './escape'
|
|
13
|
+
import { resourceURL } from '../core/resource-hints'
|
|
14
|
+
import type { ResourceScope } from './resource-hints'
|
|
12
15
|
|
|
13
16
|
export interface StreamOptions {
|
|
14
17
|
identifierPrefix?: string
|
|
15
|
-
nonce?: string
|
|
18
|
+
nonce?: string | { script?: string; style?: string }
|
|
16
19
|
bootstrapScriptContent?: string | ReadonlyArray<string>
|
|
17
20
|
bootstrapScripts?: ReadonlyArray<string | { src: string; async?: boolean; nonce?: string }>
|
|
18
21
|
bootstrapModules?: ReadonlyArray<string | { src: string; nonce?: string }>
|
|
19
22
|
onError?: (error: unknown) => string | void
|
|
23
|
+
onBrowserBailout?: BrowserBailoutCallback
|
|
20
24
|
signal?: AbortSignal
|
|
21
25
|
progressiveChunkSize?: number
|
|
22
26
|
}
|
|
@@ -28,6 +32,7 @@ export interface ReadableStreamResult extends ReadableStream<Uint8Array> {
|
|
|
28
32
|
export interface PipeableWritable {
|
|
29
33
|
write(chunk: string): unknown
|
|
30
34
|
end(): unknown
|
|
35
|
+
destroy?(error?: unknown): unknown
|
|
31
36
|
}
|
|
32
37
|
|
|
33
38
|
export interface OrchestratorState {
|
|
@@ -35,6 +40,28 @@ export interface OrchestratorState {
|
|
|
35
40
|
pending: Set<Promise<void>>
|
|
36
41
|
closed: boolean
|
|
37
42
|
errored: unknown | null
|
|
43
|
+
aborted: boolean
|
|
44
|
+
reason: unknown
|
|
45
|
+
abort: (reason?: unknown) => void
|
|
46
|
+
cancelled: Promise<void>
|
|
47
|
+
onShellReady?: () => void
|
|
48
|
+
frame?: SSRFrame
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function createState(): OrchestratorState {
|
|
52
|
+
let wake!: () => void
|
|
53
|
+
const state: OrchestratorState = {
|
|
54
|
+
nextId: 0, pending: new Set(), closed: false, errored: null,
|
|
55
|
+
aborted: false, reason: undefined,
|
|
56
|
+
cancelled: new Promise<void>((resolve) => { wake = resolve }),
|
|
57
|
+
abort(reason) {
|
|
58
|
+
if (state.closed || state.aborted) return
|
|
59
|
+
state.aborted = true
|
|
60
|
+
state.reason = isBrowserToken(reason) ? browserError(reason) : reason ?? new Error('The render was aborted by the server without a reason.')
|
|
61
|
+
wake()
|
|
62
|
+
},
|
|
63
|
+
}
|
|
64
|
+
return state
|
|
38
65
|
}
|
|
39
66
|
|
|
40
67
|
type Emit = (chunk: string) => void
|
|
@@ -45,9 +72,7 @@ export async function streamHtml(
|
|
|
45
72
|
options: StreamOptions,
|
|
46
73
|
state: OrchestratorState,
|
|
47
74
|
): Promise<void> {
|
|
48
|
-
|
|
49
|
-
beginSSR(options.identifierPrefix)
|
|
50
|
-
const nonce = options.nonce
|
|
75
|
+
const nonce = scriptNonce(options)
|
|
51
76
|
|
|
52
77
|
try {
|
|
53
78
|
const boundaries: SuspendedBoundary[] = []
|
|
@@ -60,6 +85,7 @@ export async function streamHtml(
|
|
|
60
85
|
// one encode and one enqueue per shell — measured ~2-4% of total SSR
|
|
61
86
|
// time on CPU profiles.
|
|
62
87
|
const shellChunks: string[] = []
|
|
88
|
+
let head = 0
|
|
63
89
|
const bufferedEmit: Emit = (chunk) => {
|
|
64
90
|
shellChunks.push(chunk)
|
|
65
91
|
}
|
|
@@ -70,17 +96,27 @@ export async function streamHtml(
|
|
|
70
96
|
// partial shell chunks or boundary ids from the aborted attempt.
|
|
71
97
|
let shellRendered = false
|
|
72
98
|
let rootRetries = 0
|
|
99
|
+
let resources: SSRFrame['resources']
|
|
73
100
|
while (!shellRendered) {
|
|
74
101
|
const chunkCount = shellChunks.length
|
|
75
102
|
const boundaryCount = boundaries.length
|
|
76
103
|
const nextId = state.nextId
|
|
104
|
+
head = 0
|
|
105
|
+
const previous = beginSSR(options.identifierPrefix)
|
|
106
|
+
currentSSRFrame().styleNonce = typeof options.nonce === 'object' ? options.nonce?.style : undefined
|
|
107
|
+
if (resources) currentSSRFrame().resources = resources
|
|
108
|
+
let suspended: Promise<unknown> | undefined
|
|
77
109
|
try {
|
|
110
|
+
if (state.aborted) throw state.reason
|
|
78
111
|
walk(children, {
|
|
79
112
|
emit: bufferedEmit,
|
|
80
113
|
onSuspend: (b) => boundaries.push(b),
|
|
81
114
|
nextBoundaryId: () => state.nextId++,
|
|
115
|
+
onBrowserBailout: options.onBrowserBailout,
|
|
116
|
+
onHead: () => { head = shellChunks.length },
|
|
82
117
|
})
|
|
83
118
|
shellRendered = true
|
|
119
|
+
state.frame = currentSSRFrame()
|
|
84
120
|
} catch (err) {
|
|
85
121
|
shellChunks.length = chunkCount
|
|
86
122
|
boundaries.length = boundaryCount
|
|
@@ -89,10 +125,18 @@ export async function streamHtml(
|
|
|
89
125
|
if (++rootRetries > 50) {
|
|
90
126
|
throw new Error('renderToReadableStream exceeded 50 root suspension retries.')
|
|
91
127
|
}
|
|
92
|
-
|
|
128
|
+
suspended = err
|
|
129
|
+
} finally {
|
|
130
|
+
resources = currentSSRFrame().resources
|
|
131
|
+
endSSR(previous)
|
|
93
132
|
}
|
|
133
|
+
if (suspended) await Promise.race([suspended, state.cancelled])
|
|
94
134
|
}
|
|
95
135
|
|
|
136
|
+
const hints = state.frame?.resources?.drain()
|
|
137
|
+
if (hints) shellChunks.splice(head, 0, hints)
|
|
138
|
+
if (state.frame) state.frame.shellFlushed = true
|
|
139
|
+
|
|
96
140
|
// 2. Inject runtime + bootstrap scripts (once, after shell). Skip the
|
|
97
141
|
// reveal/event-replay runtime when nothing needs it — no suspensions to
|
|
98
142
|
// reveal and no bootstrap scripts to guard against early user input. That
|
|
@@ -104,7 +148,7 @@ export async function streamHtml(
|
|
|
104
148
|
(options.bootstrapScripts?.length ?? 0) > 0 ||
|
|
105
149
|
(options.bootstrapModules?.length ?? 0) > 0
|
|
106
150
|
if (boundaries.length > 0 || hasBootstrap) {
|
|
107
|
-
shellChunks.push(`<script${nonce ? ` nonce="${nonce}"` : ''}>${BOUNDARY_REVEAL_RUNTIME}</script>`)
|
|
151
|
+
shellChunks.push(`<script${nonce ? ` nonce="${escapeAttr(nonce)}"` : ''}>${BOUNDARY_REVEAL_RUNTIME}</script>`)
|
|
108
152
|
for (const content of bootstrapScriptContentToArray(options.bootstrapScriptContent)) {
|
|
109
153
|
shellChunks.push(inlineBootstrapTag(content, nonce))
|
|
110
154
|
}
|
|
@@ -117,17 +161,20 @@ export async function streamHtml(
|
|
|
117
161
|
}
|
|
118
162
|
|
|
119
163
|
if (shellChunks.length) emit(normalizeDocumentShell(shellChunks.join('')))
|
|
164
|
+
state.onShellReady?.()
|
|
120
165
|
|
|
121
166
|
// 3. Stream suspended boundaries as they resolve
|
|
122
167
|
for (const b of boundaries) streamBoundary(b, emit, options, state)
|
|
123
168
|
await drain(state)
|
|
124
169
|
} catch (err) {
|
|
170
|
+
if (isBrowserError(err)) {
|
|
171
|
+
err = new Error('The server render could not complete because client rendering was requested outside a Suspense boundary.',
|
|
172
|
+
Object.hasOwn(err, 'cause') ? { cause: err.cause } : undefined)
|
|
173
|
+
}
|
|
125
174
|
state.errored = err
|
|
126
175
|
if (options.onError) options.onError(err)
|
|
127
176
|
throw err
|
|
128
177
|
} finally {
|
|
129
|
-
endSSR()
|
|
130
|
-
uninstallSSRDispatcher()
|
|
131
178
|
state.closed = true
|
|
132
179
|
}
|
|
133
180
|
}
|
|
@@ -138,39 +185,59 @@ function streamBoundary(
|
|
|
138
185
|
options: StreamOptions,
|
|
139
186
|
state: OrchestratorState,
|
|
140
187
|
): void {
|
|
188
|
+
const nonce = scriptNonce(options)
|
|
141
189
|
const task = (async () => {
|
|
142
190
|
try {
|
|
143
|
-
await b.thenable
|
|
144
|
-
} catch
|
|
145
|
-
if (options.onError) options.onError(err)
|
|
146
|
-
}
|
|
191
|
+
await Promise.race([b.thenable, state.cancelled])
|
|
192
|
+
} catch {}
|
|
147
193
|
if (state.closed) return
|
|
148
194
|
|
|
195
|
+
const defer = (error: unknown) => {
|
|
196
|
+
if (isBrowserError(error)) options.onBrowserBailout?.(error, { componentStack: (error as any).componentStack || b.componentStack })
|
|
197
|
+
else options.onError?.(error)
|
|
198
|
+
emit(`<script${nonce ? ` nonce="${escapeAttr(nonce)}"` : ''}>$RB(${b.id}${isBrowserError(error) ? '' : ',1'})</script>`)
|
|
199
|
+
}
|
|
200
|
+
if (state.aborted) { defer(state.reason); return }
|
|
201
|
+
|
|
149
202
|
// Re-render the boundary's children into a string, restoring the
|
|
150
203
|
// provider stack from when the boundary first suspended.
|
|
151
204
|
const parts: string[] = []
|
|
152
205
|
const sub: SuspendedBoundary[] = []
|
|
206
|
+
const resourceScope: ResourceScope = {}
|
|
207
|
+
let styles: string[][] | undefined
|
|
208
|
+
const previous = beginSSR(options.identifierPrefix, state.frame)
|
|
209
|
+
state.frame?.resources?.takeStylesheets()
|
|
153
210
|
const restore = applyContextSnapshot(b.contextSnapshot)
|
|
154
211
|
try {
|
|
155
212
|
walk(b.children, {
|
|
156
213
|
emit: (s) => parts.push(s),
|
|
157
214
|
onSuspend: (n) => sub.push(n),
|
|
158
215
|
nextBoundaryId: () => state.nextId++,
|
|
216
|
+
onBrowserBailout: options.onBrowserBailout,
|
|
217
|
+
componentStack: b.componentStack,
|
|
218
|
+
resourceScope,
|
|
159
219
|
})
|
|
220
|
+
state.frame?.resources?.mergeScope(resourceScope)
|
|
160
221
|
} catch (err) {
|
|
161
|
-
if (
|
|
162
|
-
|
|
222
|
+
if (isThenable(err)) {
|
|
223
|
+
streamBoundary({ ...b, thenable: err }, emit, options, state)
|
|
224
|
+
} else defer(err)
|
|
163
225
|
return
|
|
226
|
+
} finally {
|
|
227
|
+
restore()
|
|
228
|
+
endSSR(previous)
|
|
229
|
+
const hints = state.frame?.resources?.drain()
|
|
230
|
+
styles = state.frame?.resources?.takeStylesheets()
|
|
231
|
+
if (hints) emit(hints)
|
|
164
232
|
}
|
|
165
|
-
restore()
|
|
166
233
|
|
|
167
|
-
emit(`<div hidden id="S:${b.id}">${parts.join('')}</div>${revealScript(b.id,
|
|
234
|
+
emit(`<div hidden id="S:${b.id}">${parts.join('')}</div>${revealScript(b.id, nonce, styles)}`)
|
|
168
235
|
|
|
169
236
|
// Recurse: any nested suspensions inside the now-revealed content
|
|
170
237
|
for (const s of sub) streamBoundary(s, emit, options, state)
|
|
171
238
|
})()
|
|
172
239
|
state.pending.add(task)
|
|
173
|
-
task.
|
|
240
|
+
task.then(() => state.pending.delete(task), () => state.pending.delete(task))
|
|
174
241
|
}
|
|
175
242
|
|
|
176
243
|
async function drain(state: OrchestratorState): Promise<void> {
|
|
@@ -183,6 +250,10 @@ function isThenable(value: unknown): value is Promise<unknown> {
|
|
|
183
250
|
return !!value && typeof (value as { then?: unknown }).then === 'function'
|
|
184
251
|
}
|
|
185
252
|
|
|
253
|
+
function scriptNonce(options: StreamOptions): string | undefined {
|
|
254
|
+
return typeof options.nonce === 'string' ? options.nonce : options.nonce?.script
|
|
255
|
+
}
|
|
256
|
+
|
|
186
257
|
function bootstrapScriptContentToArray(
|
|
187
258
|
content: StreamOptions['bootstrapScriptContent'],
|
|
188
259
|
): string[] {
|
|
@@ -191,7 +262,7 @@ function bootstrapScriptContentToArray(
|
|
|
191
262
|
}
|
|
192
263
|
|
|
193
264
|
function inlineBootstrapTag(content: string, defaultNonce: string | undefined): string {
|
|
194
|
-
const nAttr = defaultNonce ? ` nonce="${defaultNonce}"` : ''
|
|
265
|
+
const nAttr = defaultNonce ? ` nonce="${escapeAttr(defaultNonce)}"` : ''
|
|
195
266
|
return `<script${nAttr}>${escapeScript(content)}</script>`
|
|
196
267
|
}
|
|
197
268
|
|
|
@@ -237,9 +308,9 @@ function bootstrapTag(
|
|
|
237
308
|
kind: 'script' | 'module',
|
|
238
309
|
defaultNonce: string | undefined,
|
|
239
310
|
): string {
|
|
240
|
-
const src = typeof entry === 'string' ? entry : entry.src
|
|
311
|
+
const src = escapeAttr(resourceURL(typeof entry === 'string' ? entry : entry.src))
|
|
241
312
|
const nonce = typeof entry === 'string' ? defaultNonce : entry.nonce ?? defaultNonce
|
|
242
|
-
const nAttr = nonce ? ` nonce="${nonce}"` : ''
|
|
313
|
+
const nAttr = nonce ? ` nonce="${escapeAttr(nonce)}"` : ''
|
|
243
314
|
if (kind === 'module') return `<script type="module"${nAttr} src="${src}"></script>`
|
|
244
315
|
return `<script async${nAttr} src="${src}"></script>`
|
|
245
316
|
}
|
|
@@ -252,13 +323,15 @@ export function renderToReadableStream(
|
|
|
252
323
|
children: ReactNode,
|
|
253
324
|
options: StreamOptions = {},
|
|
254
325
|
): Promise<ReadableStreamResult> {
|
|
255
|
-
const state
|
|
256
|
-
nextId: 0,
|
|
257
|
-
pending: new Set(),
|
|
258
|
-
closed: false,
|
|
259
|
-
errored: null,
|
|
260
|
-
}
|
|
326
|
+
const state = createState()
|
|
261
327
|
const encoder = new TextEncoder()
|
|
328
|
+
let abortListener: (() => void) | undefined
|
|
329
|
+
const detachAbortListener = () => {
|
|
330
|
+
if (abortListener) {
|
|
331
|
+
options.signal?.removeEventListener('abort', abortListener)
|
|
332
|
+
abortListener = undefined
|
|
333
|
+
}
|
|
334
|
+
}
|
|
262
335
|
|
|
263
336
|
let allReadyResolve!: () => void
|
|
264
337
|
let allReadyReject!: (e: unknown) => void
|
|
@@ -266,6 +339,15 @@ export function renderToReadableStream(
|
|
|
266
339
|
allReadyResolve = r
|
|
267
340
|
allReadyReject = rej
|
|
268
341
|
})
|
|
342
|
+
// Shell failures reject the outer promise before callers receive allReady.
|
|
343
|
+
allReady.catch(() => {})
|
|
344
|
+
let shellResolve!: (stream: ReadableStreamResult) => void
|
|
345
|
+
let shellReject!: (error: unknown) => void
|
|
346
|
+
const shell = new Promise<ReadableStreamResult>((resolve, reject) => {
|
|
347
|
+
shellResolve = resolve
|
|
348
|
+
shellReject = reject
|
|
349
|
+
})
|
|
350
|
+
state.onShellReady = () => queueMicrotask(() => shellResolve(Object.assign(stream, { allReady })))
|
|
269
351
|
|
|
270
352
|
const stream = new ReadableStream<Uint8Array>({
|
|
271
353
|
start(controller) {
|
|
@@ -275,31 +357,41 @@ export function renderToReadableStream(
|
|
|
275
357
|
} catch {}
|
|
276
358
|
}
|
|
277
359
|
|
|
360
|
+
if (options.signal) {
|
|
361
|
+
abortListener = () => {
|
|
362
|
+
detachAbortListener()
|
|
363
|
+
state.abort(options.signal!.reason)
|
|
364
|
+
}
|
|
365
|
+
options.signal.addEventListener('abort', abortListener)
|
|
366
|
+
if (options.signal.aborted) abortListener()
|
|
367
|
+
}
|
|
368
|
+
|
|
278
369
|
streamHtml(children, emit, options, state).then(
|
|
279
370
|
() => {
|
|
371
|
+
detachAbortListener()
|
|
280
372
|
try {
|
|
281
373
|
controller.close()
|
|
282
374
|
} catch {}
|
|
283
375
|
allReadyResolve()
|
|
284
376
|
},
|
|
285
377
|
(err) => {
|
|
378
|
+
detachAbortListener()
|
|
286
379
|
try {
|
|
287
380
|
controller.error(err)
|
|
288
381
|
} catch {}
|
|
289
382
|
allReadyReject(err)
|
|
383
|
+
shellReject(err)
|
|
290
384
|
},
|
|
291
385
|
)
|
|
292
386
|
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
} catch {}
|
|
298
|
-
})
|
|
387
|
+
},
|
|
388
|
+
cancel(reason) {
|
|
389
|
+
state.abort(reason)
|
|
390
|
+
detachAbortListener()
|
|
299
391
|
},
|
|
300
392
|
})
|
|
301
393
|
|
|
302
|
-
return
|
|
394
|
+
return shell
|
|
303
395
|
}
|
|
304
396
|
|
|
305
397
|
// ---------------------------------------------------------------------------
|
|
@@ -321,18 +413,20 @@ export function renderToPipeableStream(
|
|
|
321
413
|
children: ReactNode,
|
|
322
414
|
options: PipeableOptions = {},
|
|
323
415
|
): PipeableHandle {
|
|
324
|
-
const state
|
|
325
|
-
nextId: 0,
|
|
326
|
-
pending: new Set(),
|
|
327
|
-
closed: false,
|
|
328
|
-
errored: null,
|
|
329
|
-
}
|
|
416
|
+
const state = createState()
|
|
330
417
|
|
|
331
418
|
const buffers: string[] = []
|
|
332
419
|
let dest: PipeableWritable | null = null
|
|
333
420
|
let shellReady = false
|
|
421
|
+
let shellFailed = false
|
|
334
422
|
let finished = false
|
|
335
|
-
let
|
|
423
|
+
let failed = false
|
|
424
|
+
let failure: unknown
|
|
425
|
+
|
|
426
|
+
const finishDestination = (target: PipeableWritable) => {
|
|
427
|
+
if (failed && target.destroy) target.destroy(failure)
|
|
428
|
+
else target.end()
|
|
429
|
+
}
|
|
336
430
|
|
|
337
431
|
const flushTo = (w: PipeableWritable) => {
|
|
338
432
|
if (!buffers.length) return
|
|
@@ -341,11 +435,17 @@ export function renderToPipeableStream(
|
|
|
341
435
|
}
|
|
342
436
|
|
|
343
437
|
const emit: Emit = (chunk) => {
|
|
344
|
-
if (
|
|
438
|
+
if (finished) return
|
|
345
439
|
if (dest) dest.write(chunk)
|
|
346
440
|
else buffers.push(chunk)
|
|
347
441
|
}
|
|
348
442
|
|
|
443
|
+
state.onShellReady = () => queueMicrotask(() => {
|
|
444
|
+
if (shellFailed) return
|
|
445
|
+
shellReady = true
|
|
446
|
+
options.onShellReady?.()
|
|
447
|
+
})
|
|
448
|
+
|
|
349
449
|
// Kick off rendering
|
|
350
450
|
streamHtml(children, emit, options, state).then(
|
|
351
451
|
() => {
|
|
@@ -354,35 +454,27 @@ export function renderToPipeableStream(
|
|
|
354
454
|
options.onAllReady?.()
|
|
355
455
|
},
|
|
356
456
|
(err) => {
|
|
457
|
+
finished = true
|
|
458
|
+
failed = true
|
|
459
|
+
failure = err
|
|
357
460
|
if (!shellReady) {
|
|
461
|
+
shellFailed = true
|
|
358
462
|
options.onShellError?.(err)
|
|
359
463
|
} else {
|
|
360
|
-
|
|
361
|
-
if (dest) dest.end()
|
|
464
|
+
if (dest) finishDestination(dest)
|
|
362
465
|
}
|
|
363
466
|
},
|
|
364
467
|
)
|
|
365
468
|
|
|
366
|
-
// We call onShellReady once the first synchronous emit has landed.
|
|
367
|
-
// streamHtml above runs the shell synchronously before the first await in drain(),
|
|
368
|
-
// so we can schedule onShellReady right after the first microtask.
|
|
369
|
-
queueMicrotask(() => {
|
|
370
|
-
if (aborted || finished) return
|
|
371
|
-
shellReady = true
|
|
372
|
-
options.onShellReady?.()
|
|
373
|
-
})
|
|
374
|
-
|
|
375
469
|
return {
|
|
376
470
|
pipe<T extends PipeableWritable>(target: T): T {
|
|
377
471
|
dest = target
|
|
378
|
-
flushTo(target)
|
|
379
|
-
if (finished) target
|
|
472
|
+
if (!failed) flushTo(target)
|
|
473
|
+
if (finished) finishDestination(target)
|
|
380
474
|
return target
|
|
381
475
|
},
|
|
382
|
-
abort(
|
|
383
|
-
|
|
384
|
-
state.closed = true
|
|
385
|
-
if (dest) dest.end()
|
|
476
|
+
abort(reason?: unknown) {
|
|
477
|
+
state.abort(reason)
|
|
386
478
|
},
|
|
387
479
|
}
|
|
388
480
|
}
|