@vidact/runtime 0.2.0-beta.0 → 0.2.0-beta.5
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/CHANGELOG.md +52 -0
- package/README.md +1 -1
- package/dist/actions.d.ts +32 -30
- package/dist/actions.d.ts.map +1 -1
- package/dist/actions.js +251 -266
- package/dist/actions.js.map +1 -1
- package/dist/async.d.ts +20 -17
- package/dist/async.d.ts.map +1 -1
- package/dist/async.js +18 -17
- package/dist/async.js.map +1 -1
- package/dist/compiled/async-resource.d.ts +9 -6
- package/dist/compiled/async-resource.d.ts.map +1 -1
- package/dist/compiled/async-resource.js +69 -77
- package/dist/compiled/async-resource.js.map +1 -1
- package/dist/compiled/core.d.ts +136 -147
- package/dist/compiled/core.d.ts.map +1 -1
- package/dist/compiled/core.js +2672 -2985
- package/dist/compiled/core.js.map +1 -1
- package/dist/compiled/types.d.ts +44 -56
- package/dist/compiled/types.d.ts.map +1 -1
- package/dist/compiled/types.js +10 -6
- package/dist/compiled/types.js.map +1 -1
- package/dist/compiled/validation.d.ts +4 -1
- package/dist/compiled/validation.d.ts.map +1 -1
- package/dist/compiled/validation.js +17 -20
- package/dist/compiled/validation.js.map +1 -1
- package/dist/component-spread.d.ts +5 -3
- package/dist/component-spread.d.ts.map +1 -1
- package/dist/component-spread.js +49 -67
- package/dist/component-spread.js.map +1 -1
- package/dist/concurrent.d.ts +20 -18
- package/dist/concurrent.d.ts.map +1 -1
- package/dist/concurrent.js +191 -210
- package/dist/concurrent.js.map +1 -1
- package/dist/direct-dom.d.ts +17 -12
- package/dist/direct-dom.d.ts.map +1 -1
- package/dist/direct-dom.js +187 -221
- package/dist/direct-dom.js.map +1 -1
- package/dist/dom/attributes.d.ts +9 -6
- package/dist/dom/attributes.d.ts.map +1 -1
- package/dist/dom/attributes.js +65 -47
- package/dist/dom/attributes.js.map +1 -1
- package/dist/dom/events.d.ts +5 -2
- package/dist/dom/events.d.ts.map +1 -1
- package/dist/dom/events.js +53 -65
- package/dist/dom/events.js.map +1 -1
- package/dist/dom/forms.d.ts +9 -6
- package/dist/dom/forms.d.ts.map +1 -1
- package/dist/dom/forms.js +152 -198
- package/dist/dom/forms.js.map +1 -1
- package/dist/dom/intrinsic.d.ts +17 -15
- package/dist/dom/intrinsic.d.ts.map +1 -1
- package/dist/dom/intrinsic.js +48 -50
- package/dist/dom/intrinsic.js.map +1 -1
- package/dist/dom/namespace.d.ts +4 -1
- package/dist/dom/namespace.d.ts.map +1 -1
- package/dist/dom/namespace.js +25 -28
- package/dist/dom/namespace.js.map +1 -1
- package/dist/dom/namespaces.d.ts +11 -8
- package/dist/dom/namespaces.d.ts.map +1 -1
- package/dist/dom/namespaces.js +14 -12
- package/dist/dom/namespaces.js.map +1 -1
- package/dist/dom/properties.d.ts +18 -16
- package/dist/dom/properties.d.ts.map +1 -1
- package/dist/dom/properties.js +273 -296
- package/dist/dom/properties.js.map +1 -1
- package/dist/dom/style-properties.d.ts +4 -1
- package/dist/dom/style-properties.d.ts.map +1 -1
- package/dist/dom/style-properties.js +49 -45
- package/dist/dom/style-properties.js.map +1 -1
- package/dist/dom/styles.d.ts +5 -2
- package/dist/dom/styles.d.ts.map +1 -1
- package/dist/dom/styles.js +47 -67
- package/dist/dom/styles.js.map +1 -1
- package/dist/dom-forms.d.ts +4 -1
- package/dist/dom-forms.d.ts.map +1 -1
- package/dist/dom-forms.js +16 -13
- package/dist/dom-forms.js.map +1 -1
- package/dist/dom-styles.d.ts +4 -1
- package/dist/dom-styles.d.ts.map +1 -1
- package/dist/dom-styles.js +10 -7
- package/dist/dom-styles.js.map +1 -1
- package/dist/framework-hydrate.d.ts +20 -17
- package/dist/framework-hydrate.d.ts.map +1 -1
- package/dist/framework-hydrate.js +128 -146
- package/dist/framework-hydrate.js.map +1 -1
- package/dist/framework-protocol.d.ts +27 -25
- package/dist/framework-protocol.d.ts.map +1 -1
- package/dist/framework-protocol.js +173 -211
- package/dist/framework-protocol.js.map +1 -1
- package/dist/framework.d.ts +34 -32
- package/dist/framework.d.ts.map +1 -1
- package/dist/framework.js +74 -93
- package/dist/framework.js.map +1 -1
- package/dist/hydrate.d.ts +11 -8
- package/dist/hydrate.d.ts.map +1 -1
- package/dist/hydrate.js +13 -9
- package/dist/hydrate.js.map +1 -1
- package/dist/hydration-bridge.d.ts +70 -58
- package/dist/hydration-bridge.d.ts.map +1 -1
- package/dist/hydration-bridge.js +77 -66
- package/dist/hydration-bridge.js.map +1 -1
- package/dist/hydration.d.ts +54 -28
- package/dist/hydration.d.ts.map +1 -1
- package/dist/hydration.js +450 -463
- package/dist/hydration.js.map +1 -1
- package/dist/index.d.ts +13 -13
- package/dist/index.js +11 -13
- package/dist/indexed-list.d.ts +8 -5
- package/dist/indexed-list.d.ts.map +1 -1
- package/dist/indexed-list.js +14 -7
- package/dist/indexed-list.js.map +1 -1
- package/dist/jsx-runtime.d.ts +10 -8
- package/dist/jsx-runtime.d.ts.map +1 -1
- package/dist/jsx-runtime.js +28 -23
- package/dist/jsx-runtime.js.map +1 -1
- package/dist/keyed-list.d.ts +13 -14
- package/dist/keyed-list.d.ts.map +1 -1
- package/dist/keyed-list.js +132 -160
- package/dist/keyed-list.js.map +1 -1
- package/dist/metadata.d.ts +6 -3
- package/dist/metadata.d.ts.map +1 -1
- package/dist/metadata.js +131 -164
- package/dist/metadata.js.map +1 -1
- package/dist/nested-prop.d.ts +4 -2
- package/dist/nested-prop.d.ts.map +1 -1
- package/dist/nested-prop.js +17 -16
- package/dist/nested-prop.js.map +1 -1
- package/dist/profiling.d.ts +11 -8
- package/dist/profiling.d.ts.map +1 -1
- package/dist/profiling.js +17 -17
- package/dist/profiling.js.map +1 -1
- package/dist/protocol.d.ts +5 -2
- package/dist/protocol.d.ts.map +1 -1
- package/dist/protocol.js +8 -5
- package/dist/protocol.js.map +1 -1
- package/dist/raw-html.d.ts +5 -2
- package/dist/raw-html.d.ts.map +1 -1
- package/dist/raw-html.js +86 -146
- package/dist/raw-html.js.map +1 -1
- package/dist/ref.d.ts +8 -3
- package/dist/ref.d.ts.map +1 -1
- package/dist/ref.js +10 -2
- package/dist/ref.js.map +1 -1
- package/dist/renderable-protocol.d.ts +26 -8
- package/dist/renderable-protocol.d.ts.map +1 -1
- package/dist/renderable-protocol.js +30 -9
- package/dist/renderable-protocol.js.map +1 -1
- package/dist/renderable.d.ts +27 -22
- package/dist/renderable.d.ts.map +1 -1
- package/dist/renderable.js +141 -133
- package/dist/renderable.js.map +1 -1
- package/dist/retained-ui.d.ts +9 -6
- package/dist/retained-ui.d.ts.map +1 -1
- package/dist/retained-ui.js +12 -10
- package/dist/retained-ui.js.map +1 -1
- package/dist/root.d.ts +18 -15
- package/dist/root.d.ts.map +1 -1
- package/dist/root.js +73 -85
- package/dist/root.js.map +1 -1
- package/dist/scheduler.d.ts +10 -8
- package/dist/scheduler.d.ts.map +1 -1
- package/dist/scheduler.js +97 -105
- package/dist/scheduler.js.map +1 -1
- package/dist/server-framework.d.ts +45 -42
- package/dist/server-framework.d.ts.map +1 -1
- package/dist/server-framework.js +209 -217
- package/dist/server-framework.js.map +1 -1
- package/dist/server.d.ts +130 -128
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +603 -710
- package/dist/server.js.map +1 -1
- package/dist/shared/promise.d.ts +6 -3
- package/dist/shared/promise.d.ts.map +1 -1
- package/dist/shared/promise.js +21 -19
- package/dist/shared/promise.js.map +1 -1
- package/dist/source-mask.d.ts +9 -6
- package/dist/source-mask.d.ts.map +1 -1
- package/dist/source-mask.js +45 -63
- package/dist/source-mask.js.map +1 -1
- package/dist/spread.d.ts +5 -2
- package/dist/spread.d.ts.map +1 -1
- package/dist/spread.js +79 -99
- package/dist/spread.js.map +1 -1
- package/dist/state-slot.d.ts +18 -15
- package/dist/state-slot.d.ts.map +1 -1
- package/dist/state-slot.js +43 -42
- package/dist/state-slot.js.map +1 -1
- package/dist/testing.d.ts +9 -6
- package/dist/testing.d.ts.map +1 -1
- package/dist/testing.js +16 -12
- package/dist/testing.js.map +1 -1
- package/package.json +8 -7
- package/src/actions.ts +415 -0
- package/src/async.ts +78 -0
- package/src/compiled/async-resource.ts +113 -0
- package/src/compiled/core.ts +4041 -0
- package/src/compiled/types.ts +96 -0
- package/src/compiled/validation.ts +23 -0
- package/src/component-spread.ts +86 -0
- package/src/concurrent.ts +310 -0
- package/src/direct-dom.ts +365 -0
- package/src/dom/attributes.ts +55 -0
- package/src/dom/events.ts +93 -0
- package/src/dom/forms.ts +220 -0
- package/src/dom/intrinsic.ts +94 -0
- package/src/dom/namespace.ts +30 -0
- package/src/dom/namespaces.ts +14 -0
- package/src/dom/properties.ts +355 -0
- package/src/dom/style-properties.ts +46 -0
- package/src/dom/styles.ts +86 -0
- package/src/dom-forms.ts +23 -0
- package/src/dom-styles.ts +11 -0
- package/src/env.d.ts +6 -0
- package/src/framework-hydrate.ts +224 -0
- package/src/framework-protocol.ts +335 -0
- package/src/framework.ts +161 -0
- package/src/hydrate.ts +38 -0
- package/src/hydration-bridge.ts +196 -0
- package/src/hydration.ts +642 -0
- package/src/index.ts +92 -0
- package/src/indexed-list.ts +20 -0
- package/src/jsx-runtime.ts +55 -0
- package/src/keyed-list.ts +233 -0
- package/src/metadata.ts +212 -0
- package/src/nested-prop.ts +27 -0
- package/src/profiling.ts +43 -0
- package/src/protocol.ts +8 -0
- package/src/raw-html.ts +206 -0
- package/src/ref.ts +13 -0
- package/src/renderable-protocol.ts +63 -0
- package/src/renderable.ts +283 -0
- package/src/retained-ui.ts +19 -0
- package/src/root.ts +122 -0
- package/src/scheduler.ts +136 -0
- package/src/server-framework.ts +397 -0
- package/src/server.ts +1160 -0
- package/src/shared/promise.ts +28 -0
- package/src/source-mask.ts +83 -0
- package/src/spread.ts +131 -0
- package/src/state-slot.ts +76 -0
- package/src/testing.ts +27 -0
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
package/src/server.ts
ADDED
|
@@ -0,0 +1,1160 @@
|
|
|
1
|
+
// oxlint-disable-next-line typescript/triple-slash-reference -- Include compiler feature defines.
|
|
2
|
+
/// <reference path="./env.d.ts" />
|
|
3
|
+
|
|
4
|
+
import type { JSX as ReactJSX } from 'react'
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
LINK_IDENTITY_PROPS,
|
|
8
|
+
META_IDENTITY_PROPS,
|
|
9
|
+
SERVER_BOOLEAN_ATTRIBUTES,
|
|
10
|
+
VALID_ATTRIBUTE_NAME,
|
|
11
|
+
serverHtmlAttributeName,
|
|
12
|
+
} from './dom/attributes.ts'
|
|
13
|
+
import { UNITLESS_STYLE_PROPERTIES } from './dom/style-properties.ts'
|
|
14
|
+
import type { PreinitOptions, PreloadOptions, ResourceHintOptions } from './framework.ts'
|
|
15
|
+
import { isPromiseLike } from './shared/promise.ts'
|
|
16
|
+
|
|
17
|
+
const SERVER_NODE = Symbol.for('vidact.v1.ServerNode')
|
|
18
|
+
const SERVER_NODE_KIND = Symbol.for('vidact.v1.ServerNodeKind')
|
|
19
|
+
const SERVER_CONTEXT = Symbol('Vidact.ServerContext')
|
|
20
|
+
const SERVER_ASYNC_RESOURCE = Symbol('Vidact.ServerAsyncResource')
|
|
21
|
+
const SERVER_SUSPENSION = Symbol('Vidact.ServerSuspension')
|
|
22
|
+
const SERVER_RENDERABLE = Symbol.for('vidact.v1.ServerRenderable')
|
|
23
|
+
|
|
24
|
+
export interface ServerNode {
|
|
25
|
+
readonly [SERVER_NODE]: (context: RenderContext) => string
|
|
26
|
+
readonly [SERVER_NODE_KIND]: 'component' | 'intrinsic' | 'transparent'
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export type ServerChild =
|
|
30
|
+
| ServerNode
|
|
31
|
+
| ServerRenderable
|
|
32
|
+
| string
|
|
33
|
+
| number
|
|
34
|
+
| bigint
|
|
35
|
+
| boolean
|
|
36
|
+
| null
|
|
37
|
+
| undefined
|
|
38
|
+
| readonly ServerChild[]
|
|
39
|
+
|
|
40
|
+
export type ServerComponent = (props: Record<string, unknown>) => ServerChild
|
|
41
|
+
export type ServerElementType = string | typeof Fragment | ServerComponent | ServerRenderable
|
|
42
|
+
export type ServerProps = Record<string, unknown> | null
|
|
43
|
+
|
|
44
|
+
type ServerRenderable = {
|
|
45
|
+
readonly props: Record<string, unknown>
|
|
46
|
+
readonly [SERVER_RENDERABLE]: {
|
|
47
|
+
readonly input: Record<string, unknown>
|
|
48
|
+
readonly construct: (input: Record<string, unknown>) => ServerChild
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
type ServerIntrinsicElements = {
|
|
53
|
+
[Name in keyof ReactJSX.IntrinsicElements]: Omit<ReactJSX.IntrinsicElements[Name], 'children'> & {
|
|
54
|
+
readonly children?: ServerChild
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export namespace JSX {
|
|
59
|
+
export type Element = ServerNode
|
|
60
|
+
export type ElementType = keyof IntrinsicElements | ServerComponent | ServerRenderable
|
|
61
|
+
|
|
62
|
+
export interface ElementChildrenAttribute extends ReactJSX.ElementChildrenAttribute {}
|
|
63
|
+
|
|
64
|
+
export interface IntrinsicAttributes extends ReactJSX.IntrinsicAttributes {}
|
|
65
|
+
|
|
66
|
+
export type IntrinsicElements = ServerIntrinsicElements
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function createRenderable(
|
|
70
|
+
input: Record<string, unknown>,
|
|
71
|
+
construct: (input: Record<string, unknown>) => ServerChild,
|
|
72
|
+
): ServerRenderable {
|
|
73
|
+
return {
|
|
74
|
+
props: input,
|
|
75
|
+
[SERVER_RENDERABLE]: { input, construct },
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function isRenderable(value: unknown): value is ServerRenderable {
|
|
80
|
+
return (
|
|
81
|
+
typeof value === 'object' &&
|
|
82
|
+
value !== null &&
|
|
83
|
+
Object.hasOwn(value, SERVER_RENDERABLE) &&
|
|
84
|
+
typeof (value as ServerRenderable)[SERVER_RENDERABLE]?.construct === 'function'
|
|
85
|
+
)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export function renderableToArray(value: unknown): [ServerRenderable] {
|
|
89
|
+
if (!isRenderable(value)) throw new TypeError('expected a compiled server renderable')
|
|
90
|
+
return [value]
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export function renderableMarker(value: unknown): undefined {
|
|
94
|
+
if (!isRenderable(value)) throw new TypeError('expected a compiled server renderable')
|
|
95
|
+
return undefined
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export function cloneRenderable(
|
|
99
|
+
value: unknown,
|
|
100
|
+
overrides: Record<string, unknown> | null = {},
|
|
101
|
+
childrenOverride?: ServerChild,
|
|
102
|
+
): ServerChild {
|
|
103
|
+
if (!isRenderable(value)) throw new TypeError('expected a compiled server renderable')
|
|
104
|
+
const renderable = value[SERVER_RENDERABLE]
|
|
105
|
+
const input = { ...renderable.input, ...overrides }
|
|
106
|
+
if (arguments.length >= 3) input.children = childrenOverride
|
|
107
|
+
return renderable.construct(input)
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export function cloneRenderableComponent(props: Record<string, unknown>): ServerChild {
|
|
111
|
+
if (Object.hasOwn(props, 'childrenOverride')) {
|
|
112
|
+
return cloneRenderable(
|
|
113
|
+
props.value,
|
|
114
|
+
props.overrides as Record<string, unknown> | null | undefined,
|
|
115
|
+
props.childrenOverride as ServerChild,
|
|
116
|
+
)
|
|
117
|
+
}
|
|
118
|
+
return cloneRenderable(props.value, props.overrides as Record<string, unknown> | null | undefined)
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export function keyedFragmentComponent(props: Record<string, unknown>): ServerChild {
|
|
122
|
+
return props.children as ServerChild
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export function renderableProps(input: Record<string, unknown>): Record<string, unknown> {
|
|
126
|
+
return Object.fromEntries(
|
|
127
|
+
Object.entries(input).filter(([name]) => !['children', 'key', 'ref'].includes(name)),
|
|
128
|
+
)
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export function renderableChildren(input: Record<string, unknown>): ServerChild {
|
|
132
|
+
return input.children as ServerChild
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export function renderableRef(_input: Record<string, unknown>): undefined {
|
|
136
|
+
return undefined
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export function forwardedRef(_props: Record<string, unknown>): undefined {
|
|
140
|
+
return undefined
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export function dynamicIntrinsicComponent(props: Record<string, unknown>): ServerNode {
|
|
144
|
+
if (typeof props.tag !== 'string') {
|
|
145
|
+
throw new TypeError('dynamic intrinsic construction requires a string tag')
|
|
146
|
+
}
|
|
147
|
+
const input = { ...(props.props as Record<string, unknown>) }
|
|
148
|
+
if (Object.hasOwn(props, 'childrenOverride')) input.children = props.childrenOverride
|
|
149
|
+
return jsx(props.tag, {
|
|
150
|
+
...renderableProps(input),
|
|
151
|
+
children: renderableChildren(input),
|
|
152
|
+
})
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export interface ServerRenderOptions {
|
|
156
|
+
readonly identifierPrefix?: string
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
type FrameworkCacheEntry = {
|
|
160
|
+
readonly args: readonly unknown[]
|
|
161
|
+
readonly status: 'fulfilled' | 'rejected'
|
|
162
|
+
readonly value: unknown
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/** @internal */
|
|
166
|
+
export interface ServerFrameworkRenderContext {
|
|
167
|
+
readonly signal: AbortSignal
|
|
168
|
+
readonly pending: Set<PromiseLike<unknown>>
|
|
169
|
+
readonly cache: Map<Function, FrameworkCacheEntry[]>
|
|
170
|
+
readonly head: Map<string, { readonly html: string; readonly precedence: string }>
|
|
171
|
+
readonly stylePrecedence: string[]
|
|
172
|
+
sawHead: boolean
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
interface RenderContext {
|
|
176
|
+
hydrationMarkers: boolean
|
|
177
|
+
identifierPrefix: string
|
|
178
|
+
nextBoundary: number
|
|
179
|
+
nextId: number
|
|
180
|
+
activityHidden: boolean
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
interface ContextState<Value> {
|
|
184
|
+
readonly defaultValue: Value
|
|
185
|
+
readonly stack: Value[]
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
export interface ServerContext<Value> {
|
|
189
|
+
readonly Provider: ServerComponent
|
|
190
|
+
readonly [SERVER_CONTEXT]: ContextState<Value>
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
type ServerAsyncState<Value> =
|
|
194
|
+
| { readonly status: 'pending' }
|
|
195
|
+
| { readonly status: 'fulfilled'; readonly value: Value }
|
|
196
|
+
| { readonly status: 'rejected'; readonly reason: unknown }
|
|
197
|
+
|
|
198
|
+
export interface ServerAsyncResource<Value> {
|
|
199
|
+
readonly [SERVER_ASYNC_RESOURCE]: ServerAsyncState<Value>
|
|
200
|
+
readonly promise: PromiseLike<Value>
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
type ServerSuspension = {
|
|
204
|
+
readonly [SERVER_SUSPENSION]: true
|
|
205
|
+
readonly resource: ServerAsyncResource<unknown>
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
export type ServerLazyModule<Props extends Record<string, unknown>> = {
|
|
209
|
+
readonly default: (props: Props) => ServerChild
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const VOID_ELEMENTS = new Set([
|
|
213
|
+
'area',
|
|
214
|
+
'base',
|
|
215
|
+
'br',
|
|
216
|
+
'col',
|
|
217
|
+
'embed',
|
|
218
|
+
'hr',
|
|
219
|
+
'img',
|
|
220
|
+
'input',
|
|
221
|
+
'keygen',
|
|
222
|
+
'link',
|
|
223
|
+
'menuitem',
|
|
224
|
+
'meta',
|
|
225
|
+
'param',
|
|
226
|
+
'source',
|
|
227
|
+
'track',
|
|
228
|
+
'wbr',
|
|
229
|
+
])
|
|
230
|
+
|
|
231
|
+
// Mirrors HYDRATION_PREFIX in hydration-bridge.ts; the server entry stays free of client modules.
|
|
232
|
+
const HYDRATION_PREFIX = 'v2'
|
|
233
|
+
const UNSAFE_HTML = typeof __VIDACT_UNSAFE_HTML__ !== 'undefined' && __VIDACT_UNSAFE_HTML__
|
|
234
|
+
|
|
235
|
+
let activeRender: RenderContext | undefined
|
|
236
|
+
let activeFrameworkRender: ServerFrameworkRenderContext | undefined
|
|
237
|
+
const serverBuiltins = new WeakSet<ServerComponent>()
|
|
238
|
+
serverBuiltins.add(dynamicIntrinsicComponent)
|
|
239
|
+
serverBuiltins.add(cloneRenderableComponent)
|
|
240
|
+
serverBuiltins.add(keyedFragmentComponent)
|
|
241
|
+
const serverPromiseResources = new WeakMap<object, ServerAsyncResource<unknown>>()
|
|
242
|
+
|
|
243
|
+
const FRAMEWORK_HEAD_PLACEHOLDER = '<!--vidact-framework:v1:head-->'
|
|
244
|
+
|
|
245
|
+
export const Fragment = Symbol('Vidact.ServerFragment')
|
|
246
|
+
|
|
247
|
+
/** @internal */
|
|
248
|
+
export function createServerFrameworkRenderContext(
|
|
249
|
+
signal: AbortSignal,
|
|
250
|
+
): ServerFrameworkRenderContext {
|
|
251
|
+
return {
|
|
252
|
+
signal,
|
|
253
|
+
pending: new Set(),
|
|
254
|
+
cache: new Map(),
|
|
255
|
+
head: new Map(),
|
|
256
|
+
stylePrecedence: [],
|
|
257
|
+
sawHead: false,
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/** @internal */
|
|
262
|
+
export function withServerFrameworkRenderContext<Result>(
|
|
263
|
+
context: ServerFrameworkRenderContext,
|
|
264
|
+
operation: () => Result,
|
|
265
|
+
): Result {
|
|
266
|
+
const previous = activeFrameworkRender
|
|
267
|
+
activeFrameworkRender = context
|
|
268
|
+
try {
|
|
269
|
+
return operation()
|
|
270
|
+
} finally {
|
|
271
|
+
activeFrameworkRender = previous
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
export function cache<Arguments extends readonly unknown[], Result>(
|
|
276
|
+
operation: (...arguments_: Arguments) => Result,
|
|
277
|
+
): (...arguments_: Arguments) => Result {
|
|
278
|
+
return (...arguments_) => {
|
|
279
|
+
const context = activeFrameworkRender
|
|
280
|
+
if (context === undefined) return operation(...arguments_)
|
|
281
|
+
let entries = context.cache.get(operation)
|
|
282
|
+
if (entries === undefined) {
|
|
283
|
+
entries = []
|
|
284
|
+
context.cache.set(operation, entries)
|
|
285
|
+
}
|
|
286
|
+
const existing = entries.find(
|
|
287
|
+
(entry) =>
|
|
288
|
+
entry.args.length === arguments_.length &&
|
|
289
|
+
entry.args.every((argument, index) => Object.is(argument, arguments_[index])),
|
|
290
|
+
)
|
|
291
|
+
if (existing !== undefined) {
|
|
292
|
+
if (existing.status === 'rejected') throw existing.value
|
|
293
|
+
return existing.value as Result
|
|
294
|
+
}
|
|
295
|
+
try {
|
|
296
|
+
const value = operation(...arguments_)
|
|
297
|
+
entries.push({ args: [...arguments_], status: 'fulfilled', value })
|
|
298
|
+
return value
|
|
299
|
+
} catch (error) {
|
|
300
|
+
entries.push({ args: [...arguments_], status: 'rejected', value: error })
|
|
301
|
+
throw error
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
export function cacheSignal(): AbortSignal | null {
|
|
307
|
+
return activeFrameworkRender?.signal ?? null
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
export function preconnect(href: string, options: ResourceHintOptions = {}): void {
|
|
311
|
+
registerResourceHint('preconnect', href, options)
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
export function prefetchDNS(href: string): void {
|
|
315
|
+
registerResourceHint('dns-prefetch', href, {})
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
export function preload(href: string, options: PreloadOptions): void {
|
|
319
|
+
registerResourceHint('preload', href, options)
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
export function preloadModule(href: string, options: ResourceHintOptions = {}): void {
|
|
323
|
+
registerResourceHint('modulepreload', href, options)
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
export function preinit(href: string, options: PreinitOptions): void {
|
|
327
|
+
if (options.as === 'style') {
|
|
328
|
+
registerHeadEntry(
|
|
329
|
+
`style:${href}`,
|
|
330
|
+
`<link rel="stylesheet" href="${escapeAttribute(href)}"${serializeHintOptions(options)}>`,
|
|
331
|
+
`2:${options.precedence ?? 'default'}`,
|
|
332
|
+
)
|
|
333
|
+
return
|
|
334
|
+
}
|
|
335
|
+
registerHeadEntry(
|
|
336
|
+
`script:${href}`,
|
|
337
|
+
`<script src="${escapeAttribute(href)}"${serializeHintOptions(options)}></script>`,
|
|
338
|
+
'3:script',
|
|
339
|
+
)
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
export function preinitModule(href: string, options: ResourceHintOptions = {}): void {
|
|
343
|
+
registerHeadEntry(
|
|
344
|
+
`module:${href}`,
|
|
345
|
+
`<script type="module" src="${escapeAttribute(href)}"${serializeHintOptions(options)}></script>`,
|
|
346
|
+
'3:script',
|
|
347
|
+
)
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
export function jsx(type: ServerElementType, props: ServerProps, _key?: unknown): ServerNode {
|
|
351
|
+
return createServerElement(type, props, false)
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
export function jsxs(type: ServerElementType, props: ServerProps, _key?: unknown): ServerNode {
|
|
355
|
+
return createServerElement(type, props, true)
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
export function jsxDEV(
|
|
359
|
+
type: ServerElementType,
|
|
360
|
+
props: ServerProps,
|
|
361
|
+
_key?: unknown,
|
|
362
|
+
isStaticChildren = false,
|
|
363
|
+
): ServerNode {
|
|
364
|
+
return createServerElement(type, props, isStaticChildren)
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
export function createElement(
|
|
368
|
+
type: ServerElementType,
|
|
369
|
+
props: ServerProps,
|
|
370
|
+
...children: ServerChild[]
|
|
371
|
+
): ServerNode {
|
|
372
|
+
const nextProps: Record<string, unknown> = { ...props }
|
|
373
|
+
if (children.length === 1) nextProps.children = children[0]
|
|
374
|
+
else if (children.length > 1) nextProps.children = children
|
|
375
|
+
return createServerElement(type, nextProps, children.length > 1)
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
/** @internal */
|
|
379
|
+
export function createServerHydrationBoundary(
|
|
380
|
+
value: ServerChild,
|
|
381
|
+
hostProps: Readonly<Record<string, unknown>> = {},
|
|
382
|
+
): ServerNode {
|
|
383
|
+
return serverNode((context) => {
|
|
384
|
+
const boundaryIndex = context.nextBoundary
|
|
385
|
+
context.nextBoundary += 1
|
|
386
|
+
const identifierPrefix = `${context.identifierPrefix}b${boundaryIndex}-`
|
|
387
|
+
const boundaryContext: RenderContext = {
|
|
388
|
+
hydrationMarkers: true,
|
|
389
|
+
identifierPrefix,
|
|
390
|
+
nextBoundary: 0,
|
|
391
|
+
nextId: 0,
|
|
392
|
+
activityHidden: false,
|
|
393
|
+
}
|
|
394
|
+
const attributes = serializeAttributes(
|
|
395
|
+
{
|
|
396
|
+
...hostProps,
|
|
397
|
+
'data-vidact-identifier-prefix': identifierPrefix,
|
|
398
|
+
},
|
|
399
|
+
context.activityHidden,
|
|
400
|
+
)
|
|
401
|
+
return `<div${attributes}>${hydrationRange('r', serializeSlot(value, boundaryContext))}</div>`
|
|
402
|
+
}, 'transparent')
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
function createServerElement(
|
|
406
|
+
type: ServerElementType,
|
|
407
|
+
props: ServerProps,
|
|
408
|
+
multipleChildren: boolean,
|
|
409
|
+
): ServerNode {
|
|
410
|
+
if (type === Fragment) {
|
|
411
|
+
return serverNode(
|
|
412
|
+
(context) =>
|
|
413
|
+
Object.hasOwn(props ?? {}, 'children')
|
|
414
|
+
? multipleChildren
|
|
415
|
+
? serializeChildren(props?.children as ServerChild, context)
|
|
416
|
+
: serializeSlot(props?.children as ServerChild, context)
|
|
417
|
+
: '',
|
|
418
|
+
'transparent',
|
|
419
|
+
)
|
|
420
|
+
}
|
|
421
|
+
if (isRenderable(type)) {
|
|
422
|
+
return serverNode(
|
|
423
|
+
(context) => serializeSlot(cloneRenderable(type, props ?? {}), context),
|
|
424
|
+
'component',
|
|
425
|
+
)
|
|
426
|
+
}
|
|
427
|
+
if (typeof type === 'function') {
|
|
428
|
+
const builtin = serverBuiltins.has(type)
|
|
429
|
+
return serverNode(
|
|
430
|
+
(context) => {
|
|
431
|
+
const content = serializeSlot(type(props ?? {}) as ServerChild, context)
|
|
432
|
+
return context.hydrationMarkers && !builtin ? hydrationRange('c', content) : content
|
|
433
|
+
},
|
|
434
|
+
builtin ? 'transparent' : 'component',
|
|
435
|
+
)
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
if (!VALID_ATTRIBUTE_NAME.test(type)) throw new TypeError(`invalid server element name ${type}`)
|
|
439
|
+
return serverNode(
|
|
440
|
+
(context) => serializeIntrinsicElement(type, props, multipleChildren, context),
|
|
441
|
+
'intrinsic',
|
|
442
|
+
)
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
function serializeIntrinsicElement(
|
|
446
|
+
type: string,
|
|
447
|
+
props: ServerProps,
|
|
448
|
+
multipleChildren: boolean,
|
|
449
|
+
context: RenderContext,
|
|
450
|
+
): string {
|
|
451
|
+
if (activeFrameworkRender !== undefined && isHoistableHeadElement(type, props)) {
|
|
452
|
+
const hydrationMarkers = context.hydrationMarkers
|
|
453
|
+
context.hydrationMarkers = false
|
|
454
|
+
try {
|
|
455
|
+
const html = serializeIntrinsicContents(type, props, multipleChildren, context)
|
|
456
|
+
registerHeadEntry(metadataKey(type, props), html, metadataPrecedence(type, props))
|
|
457
|
+
return ''
|
|
458
|
+
} finally {
|
|
459
|
+
context.hydrationMarkers = hydrationMarkers
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
return serializeIntrinsicContents(type, props, multipleChildren, context)
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
function serializeIntrinsicContents(
|
|
466
|
+
type: string,
|
|
467
|
+
props: ServerProps,
|
|
468
|
+
multipleChildren: boolean,
|
|
469
|
+
context: RenderContext,
|
|
470
|
+
): string {
|
|
471
|
+
const hiddenByActivity = context.activityHidden
|
|
472
|
+
const previousActivityHidden = context.activityHidden
|
|
473
|
+
context.activityHidden = false
|
|
474
|
+
try {
|
|
475
|
+
const attributes = serializeAttributes(props, hiddenByActivity)
|
|
476
|
+
if (VOID_ELEMENTS.has(type)) {
|
|
477
|
+
if (hasRenderableChild(props?.children as ServerChild) || readRawHtml(props) !== undefined) {
|
|
478
|
+
throw new TypeError(`${type} is a void element and cannot have children or raw HTML`)
|
|
479
|
+
}
|
|
480
|
+
return `<${type}${attributes}>`
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
const rawHtml = readRawHtml(props)
|
|
484
|
+
if (rawHtml !== undefined && hasRenderableChild(props?.children as ServerChild)) {
|
|
485
|
+
throw new TypeError('cannot set both children and dangerouslySetInnerHTML')
|
|
486
|
+
}
|
|
487
|
+
const hasChildren = Object.hasOwn(props ?? {}, 'children')
|
|
488
|
+
const children =
|
|
489
|
+
rawHtml === undefined
|
|
490
|
+
? hasChildren
|
|
491
|
+
? serializeElementChildren(
|
|
492
|
+
type,
|
|
493
|
+
props?.children as ServerChild,
|
|
494
|
+
multipleChildren,
|
|
495
|
+
context,
|
|
496
|
+
)
|
|
497
|
+
: ''
|
|
498
|
+
: context.hydrationMarkers
|
|
499
|
+
? hydrationRange('h', assertSafeHydrationRawHtml(rawHtml))
|
|
500
|
+
: rawHtml
|
|
501
|
+
if (type === 'head' && activeFrameworkRender !== undefined) {
|
|
502
|
+
activeFrameworkRender.sawHead = true
|
|
503
|
+
return `<head${attributes}>${children}${FRAMEWORK_HEAD_PLACEHOLDER}</head>`
|
|
504
|
+
}
|
|
505
|
+
return `<${type}${attributes}>${children}</${type}>`
|
|
506
|
+
} finally {
|
|
507
|
+
context.activityHidden = previousActivityHidden
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
export function renderToString(
|
|
512
|
+
value: ServerChild | (() => ServerChild),
|
|
513
|
+
options: ServerRenderOptions = {},
|
|
514
|
+
): string {
|
|
515
|
+
const previous = activeRender
|
|
516
|
+
beginFrameworkRenderPass()
|
|
517
|
+
const context = {
|
|
518
|
+
hydrationMarkers: true,
|
|
519
|
+
identifierPrefix: options.identifierPrefix ?? '',
|
|
520
|
+
nextBoundary: 0,
|
|
521
|
+
nextId: 0,
|
|
522
|
+
activityHidden: false,
|
|
523
|
+
}
|
|
524
|
+
activeRender = context
|
|
525
|
+
try {
|
|
526
|
+
const html = hydrationRange(
|
|
527
|
+
'r',
|
|
528
|
+
serializeSlot(typeof value === 'function' ? value() : value, context),
|
|
529
|
+
)
|
|
530
|
+
return finalizeFrameworkHtml(html)
|
|
531
|
+
} finally {
|
|
532
|
+
activeRender = previous
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
export function renderToStaticMarkup(
|
|
537
|
+
value: ServerChild | (() => ServerChild),
|
|
538
|
+
options: ServerRenderOptions = {},
|
|
539
|
+
): string {
|
|
540
|
+
const previous = activeRender
|
|
541
|
+
beginFrameworkRenderPass()
|
|
542
|
+
const context = {
|
|
543
|
+
hydrationMarkers: false,
|
|
544
|
+
identifierPrefix: options.identifierPrefix ?? '',
|
|
545
|
+
nextBoundary: 0,
|
|
546
|
+
nextId: 0,
|
|
547
|
+
activityHidden: false,
|
|
548
|
+
}
|
|
549
|
+
activeRender = context
|
|
550
|
+
try {
|
|
551
|
+
return finalizeFrameworkHtml(
|
|
552
|
+
serializeChild(typeof value === 'function' ? value() : value, context),
|
|
553
|
+
)
|
|
554
|
+
} finally {
|
|
555
|
+
activeRender = previous
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
export type StateUpdate<Value> = Value | ((previous: Value) => Value)
|
|
560
|
+
|
|
561
|
+
export function useState<Value>(
|
|
562
|
+
initial: Value | (() => Value),
|
|
563
|
+
): [Value, (value: StateUpdate<Value>) => void] {
|
|
564
|
+
const value = typeof initial === 'function' ? (initial as () => Value)() : initial
|
|
565
|
+
return [value, serverDispatch]
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
export function useReducer<State, Action>(
|
|
569
|
+
_reducer: (state: State, action: Action) => State,
|
|
570
|
+
initial: State,
|
|
571
|
+
initialize?: (value: State) => State,
|
|
572
|
+
): [State, (action: Action) => void] {
|
|
573
|
+
return [initialize === undefined ? initial : initialize(initial), serverDispatch]
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
export function startTransition(action: () => void | PromiseLike<void>): void {
|
|
577
|
+
void action()
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
export function useTransition(): [boolean, typeof startTransition] {
|
|
581
|
+
return [false, startTransition]
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
export function useDeferredValue<Value>(value: Value, _initialValue?: Value): Value {
|
|
585
|
+
return value
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
export function flushSync<Result>(operation?: () => Result): Result | undefined {
|
|
589
|
+
return operation?.()
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
export function useActionState<State, Payload>(
|
|
593
|
+
_action: (previousState: State, payload: Payload) => State | PromiseLike<State>,
|
|
594
|
+
initialState: State,
|
|
595
|
+
permalink?: string,
|
|
596
|
+
): [State, (payload: Payload) => void, boolean] {
|
|
597
|
+
const dispatch = (_payload: Payload): void => serverDispatch()
|
|
598
|
+
if (permalink !== undefined) Object.defineProperty(dispatch, 'permalink', { value: permalink })
|
|
599
|
+
return [initialState, dispatch, false]
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
export function useOptimistic<Value, Update = Value>(
|
|
603
|
+
passthrough: Value,
|
|
604
|
+
_reducer?: (current: Value, update: Update) => Value,
|
|
605
|
+
): [Value, (update: Update) => void] {
|
|
606
|
+
return [passthrough, serverDispatch]
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
export function useFormStatus(): {
|
|
610
|
+
readonly pending: false
|
|
611
|
+
readonly data: null
|
|
612
|
+
readonly method: 'get'
|
|
613
|
+
readonly action: null
|
|
614
|
+
} {
|
|
615
|
+
return { pending: false, data: null, method: 'get', action: null }
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
export function useMemo<Value>(factory: () => Value, _dependencies: readonly unknown[]): Value {
|
|
619
|
+
return factory()
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
export function useCallback<Value extends (...arguments_: never[]) => unknown>(
|
|
623
|
+
callback: Value,
|
|
624
|
+
_dependencies: readonly unknown[],
|
|
625
|
+
): Value {
|
|
626
|
+
return callback
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
export function useRef<Value>(initial: Value): { current: Value } {
|
|
630
|
+
return { current: initial }
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
export function createRef<Value>(): { current: Value | null } {
|
|
634
|
+
return { current: null }
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
export function useEffect(
|
|
638
|
+
_create: () => void | (() => void),
|
|
639
|
+
_dependencies?: readonly unknown[],
|
|
640
|
+
): void {}
|
|
641
|
+
|
|
642
|
+
export const useLayoutEffect = useEffect
|
|
643
|
+
export const useInsertionEffect = useEffect
|
|
644
|
+
|
|
645
|
+
export function useImperativeHandle(
|
|
646
|
+
_ref: unknown,
|
|
647
|
+
_create: () => unknown,
|
|
648
|
+
_dependencies?: readonly unknown[],
|
|
649
|
+
): void {}
|
|
650
|
+
|
|
651
|
+
export function useEffectEvent<Arguments extends unknown[], Result>(
|
|
652
|
+
callback: (...arguments_: Arguments) => Result,
|
|
653
|
+
): (...arguments_: Arguments) => Result {
|
|
654
|
+
return callback
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
export function useSyncExternalStore<Value>(
|
|
658
|
+
_subscribe: (notify: () => void) => () => void,
|
|
659
|
+
getSnapshot: () => Value,
|
|
660
|
+
getServerSnapshot?: () => Value,
|
|
661
|
+
): Value {
|
|
662
|
+
return (getServerSnapshot ?? getSnapshot)()
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
export function useId(): string {
|
|
666
|
+
const context = (activeRender ??= {
|
|
667
|
+
hydrationMarkers: true,
|
|
668
|
+
identifierPrefix: '',
|
|
669
|
+
nextBoundary: 0,
|
|
670
|
+
nextId: 0,
|
|
671
|
+
activityHidden: false,
|
|
672
|
+
})
|
|
673
|
+
const id = `:${context.identifierPrefix}r${context.nextId}:`
|
|
674
|
+
context.nextId += 1
|
|
675
|
+
return id
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
export function createContext<Value>(defaultValue: Value): ServerContext<Value> {
|
|
679
|
+
let context: ServerContext<Value>
|
|
680
|
+
const Provider: ServerComponent = (props) =>
|
|
681
|
+
serverNode((renderContext) => {
|
|
682
|
+
context[SERVER_CONTEXT].stack.push(props.value as Value)
|
|
683
|
+
try {
|
|
684
|
+
return serializeChild(props.children as ServerChild, renderContext)
|
|
685
|
+
} finally {
|
|
686
|
+
context[SERVER_CONTEXT].stack.pop()
|
|
687
|
+
}
|
|
688
|
+
}, 'transparent')
|
|
689
|
+
context = { [SERVER_CONTEXT]: { defaultValue, stack: [] }, Provider }
|
|
690
|
+
serverBuiltins.add(Provider)
|
|
691
|
+
return context
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
export function useContext<Value>(context: ServerContext<Value>): Value {
|
|
695
|
+
const state = context[SERVER_CONTEXT]
|
|
696
|
+
return state.stack.length === 0 ? state.defaultValue : (state.stack.at(-1) as Value)
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
export function createResource<Value>(input: PromiseLike<Value>): ServerAsyncResource<Value> {
|
|
700
|
+
if (!isPromiseLike(input)) throw new TypeError('createResource requires a promise-like value')
|
|
701
|
+
const existing = serverPromiseResources.get(input as object) as
|
|
702
|
+
| ServerAsyncResource<Value>
|
|
703
|
+
| undefined
|
|
704
|
+
if (existing !== undefined) return existing
|
|
705
|
+
let state: ServerAsyncState<Value> = { status: 'pending' }
|
|
706
|
+
const resource = { promise: input } as ServerAsyncResource<Value>
|
|
707
|
+
Object.defineProperty(resource, SERVER_ASYNC_RESOURCE, { get: () => state })
|
|
708
|
+
serverPromiseResources.set(input as object, resource as ServerAsyncResource<unknown>)
|
|
709
|
+
void Promise.resolve(input).then(
|
|
710
|
+
(value) => {
|
|
711
|
+
if (state.status === 'pending') state = { status: 'fulfilled', value }
|
|
712
|
+
},
|
|
713
|
+
(reason: unknown) => {
|
|
714
|
+
if (state.status === 'pending') state = { status: 'rejected', reason }
|
|
715
|
+
},
|
|
716
|
+
)
|
|
717
|
+
return resource
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
export function use<Value>(
|
|
721
|
+
input: ServerContext<Value> | ServerAsyncResource<Value> | PromiseLike<Value>,
|
|
722
|
+
): Value {
|
|
723
|
+
if (isServerContext<Value>(input)) return useContext(input)
|
|
724
|
+
const resource = isServerAsyncResource<Value>(input) ? input : createResource(input)
|
|
725
|
+
const state = resource[SERVER_ASYNC_RESOURCE]
|
|
726
|
+
if (state.status === 'fulfilled') return state.value
|
|
727
|
+
if (state.status === 'rejected') throw state.reason
|
|
728
|
+
throw { [SERVER_SUSPENSION]: true, resource } satisfies ServerSuspension
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
export function lazy<Props extends Record<string, unknown>>(
|
|
732
|
+
load: () => PromiseLike<ServerLazyModule<Props>>,
|
|
733
|
+
): ServerComponent {
|
|
734
|
+
let resource: ServerAsyncResource<ServerLazyModule<Props>> | undefined
|
|
735
|
+
return (props) => {
|
|
736
|
+
resource ??= createResource(load())
|
|
737
|
+
const module = use(resource)
|
|
738
|
+
if (typeof module?.default !== 'function') {
|
|
739
|
+
throw new TypeError('lazy loader must resolve to a default component export')
|
|
740
|
+
}
|
|
741
|
+
return module.default(props as Props)
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
export function Suspense(props: Record<string, unknown>): ServerNode {
|
|
746
|
+
const render = props.children
|
|
747
|
+
const fallback = props.fallback
|
|
748
|
+
if (typeof render !== 'function' || typeof fallback !== 'function') {
|
|
749
|
+
throw new TypeError(
|
|
750
|
+
'Suspense children and fallback must be compiler-generated render functions',
|
|
751
|
+
)
|
|
752
|
+
}
|
|
753
|
+
return serverNode((context) => {
|
|
754
|
+
try {
|
|
755
|
+
return serializeChild((render as () => ServerChild)(), context)
|
|
756
|
+
} catch (error) {
|
|
757
|
+
if (!isServerSuspension(error)) throw error
|
|
758
|
+
activeFrameworkRender?.pending.add(error.resource.promise)
|
|
759
|
+
const pendingMarker = context.hydrationMarkers ? `<!--${HYDRATION_PREFIX}:p-->` : ''
|
|
760
|
+
return pendingMarker + serializeChild((fallback as () => ServerChild)(), context)
|
|
761
|
+
}
|
|
762
|
+
}, 'transparent')
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
serverBuiltins.add(Suspense)
|
|
766
|
+
|
|
767
|
+
export function Activity(props: Record<string, unknown>): ServerNode {
|
|
768
|
+
const render = props.children
|
|
769
|
+
if (typeof render !== 'function') {
|
|
770
|
+
throw new TypeError('Activity children must be a compiler-generated render function')
|
|
771
|
+
}
|
|
772
|
+
if (props.mode !== 'visible' && props.mode !== 'hidden') {
|
|
773
|
+
throw new TypeError('Activity mode must be "visible" or "hidden"')
|
|
774
|
+
}
|
|
775
|
+
return serverNode((context) => {
|
|
776
|
+
const previousActivityHidden = context.activityHidden
|
|
777
|
+
context.activityHidden = props.mode === 'hidden' || previousActivityHidden
|
|
778
|
+
try {
|
|
779
|
+
return serializeChild((render as () => ServerChild)(), context)
|
|
780
|
+
} finally {
|
|
781
|
+
context.activityHidden = previousActivityHidden
|
|
782
|
+
}
|
|
783
|
+
}, 'transparent')
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
serverBuiltins.add(Activity)
|
|
787
|
+
|
|
788
|
+
export function Profiler(props: Record<string, unknown>): ServerChild {
|
|
789
|
+
const render = props.children
|
|
790
|
+
if (typeof render !== 'function') {
|
|
791
|
+
throw new TypeError('Profiler children must be a compiler-generated render function')
|
|
792
|
+
}
|
|
793
|
+
if (typeof props.id !== 'string') throw new TypeError('Profiler id must be a string')
|
|
794
|
+
if (typeof props.onRender !== 'function') {
|
|
795
|
+
throw new TypeError('Profiler onRender must be a function')
|
|
796
|
+
}
|
|
797
|
+
return (render as () => ServerChild)()
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
serverBuiltins.add(Profiler)
|
|
801
|
+
|
|
802
|
+
export function useDebugValue<Value>(_value: Value, _format?: (value: Value) => unknown): void {}
|
|
803
|
+
|
|
804
|
+
export function captureOwnerStack(): null {
|
|
805
|
+
return null
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
export function createPortal(_children: ServerChild, _container: unknown): never {
|
|
809
|
+
throw new Error(
|
|
810
|
+
'portals cannot be emitted by the server target; render portal content in the client root',
|
|
811
|
+
)
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
function serverNode(
|
|
815
|
+
render: (context: RenderContext) => string,
|
|
816
|
+
kind: ServerNode[typeof SERVER_NODE_KIND],
|
|
817
|
+
): ServerNode {
|
|
818
|
+
return { [SERVER_NODE]: render, [SERVER_NODE_KIND]: kind }
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
function isServerNode(value: unknown): value is ServerNode {
|
|
822
|
+
return typeof value === 'object' && value !== null && SERVER_NODE in value
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
function isServerContext<Value>(value: unknown): value is ServerContext<Value> {
|
|
826
|
+
return typeof value === 'object' && value !== null && SERVER_CONTEXT in value
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
function isServerAsyncResource<Value>(value: unknown): value is ServerAsyncResource<Value> {
|
|
830
|
+
return typeof value === 'object' && value !== null && SERVER_ASYNC_RESOURCE in value
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
function isServerSuspension(value: unknown): value is ServerSuspension {
|
|
834
|
+
return typeof value === 'object' && value !== null && SERVER_SUSPENSION in value
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
function serializeChild(value: ServerChild, context: RenderContext): string {
|
|
838
|
+
// Empty scalars render nothing and intrinsic elements and text render bare: the
|
|
839
|
+
// hydrator infers both from the DOM inside the enclosing child slot.
|
|
840
|
+
if (value === null || value === undefined || typeof value === 'boolean') return ''
|
|
841
|
+
if (isServerNode(value)) return value[SERVER_NODE](context)
|
|
842
|
+
if (isRenderable(value)) return serializeChild(cloneRenderable(value), context)
|
|
843
|
+
if (Array.isArray(value)) {
|
|
844
|
+
const content = value.map((child) => serializeChild(child, context)).join('')
|
|
845
|
+
return context.hydrationMarkers ? hydrationRange('a', content) : content
|
|
846
|
+
}
|
|
847
|
+
if (typeof value === 'object' || typeof value === 'function' || typeof value === 'symbol') {
|
|
848
|
+
throw new TypeError('unsupported server child value')
|
|
849
|
+
}
|
|
850
|
+
const content = escapeText(String(value))
|
|
851
|
+
if (context.activityHidden && content !== '') {
|
|
852
|
+
throw new TypeError('initially hidden Activity server children require a host element root')
|
|
853
|
+
}
|
|
854
|
+
return content
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
function hasRenderableChild(value: ServerChild): boolean {
|
|
858
|
+
if (value === null || value === undefined || typeof value === 'boolean') return false
|
|
859
|
+
return !Array.isArray(value) || value.some(hasRenderableChild)
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
function serializeAttributes(props: ServerProps, hiddenByActivity = false): string {
|
|
863
|
+
if (props === null) return ' style="display:none!important"'
|
|
864
|
+
const attributes = Object.keys(props)
|
|
865
|
+
.toSorted()
|
|
866
|
+
.map((name) =>
|
|
867
|
+
name === 'style'
|
|
868
|
+
? serializeStyleAttribute(props[name], hiddenByActivity)
|
|
869
|
+
: serializeAttribute(name, props[name]),
|
|
870
|
+
)
|
|
871
|
+
.join('')
|
|
872
|
+
return hiddenByActivity && !Object.hasOwn(props, 'style')
|
|
873
|
+
? `${attributes} style="display:none!important"`
|
|
874
|
+
: attributes
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
function serializeAttribute(name: string, value: unknown): string {
|
|
878
|
+
if (
|
|
879
|
+
(name === 'action' || name === 'formAction') &&
|
|
880
|
+
typeof value === 'function' &&
|
|
881
|
+
typeof Reflect.get(value, 'permalink') === 'string'
|
|
882
|
+
) {
|
|
883
|
+
return ` ${serverHtmlAttributeName(name)}="${escapeAttribute(Reflect.get(value, 'permalink') as string)}"`
|
|
884
|
+
}
|
|
885
|
+
if (
|
|
886
|
+
name === 'children' ||
|
|
887
|
+
name === 'dangerouslySetInnerHTML' ||
|
|
888
|
+
name === 'key' ||
|
|
889
|
+
name === 'precedence' ||
|
|
890
|
+
name === 'ref' ||
|
|
891
|
+
/^on/i.test(name) ||
|
|
892
|
+
value === null ||
|
|
893
|
+
value === undefined ||
|
|
894
|
+
typeof value === 'function' ||
|
|
895
|
+
typeof value === 'symbol'
|
|
896
|
+
) {
|
|
897
|
+
return ''
|
|
898
|
+
}
|
|
899
|
+
if (name === 'style') return serializeStyleAttribute(value)
|
|
900
|
+
const normalizedName = serverHtmlAttributeName(name)
|
|
901
|
+
if (name.startsWith('data-') || name.startsWith('aria-')) {
|
|
902
|
+
return ` ${normalizedName}="${escapeAttribute(String(value))}"`
|
|
903
|
+
}
|
|
904
|
+
if (SERVER_BOOLEAN_ATTRIBUTES.has(name)) return value ? ` ${normalizedName}=""` : ''
|
|
905
|
+
if (typeof value === 'boolean') return ''
|
|
906
|
+
if (!VALID_ATTRIBUTE_NAME.test(normalizedName)) return ''
|
|
907
|
+
return ` ${normalizedName}="${escapeAttribute(String(value))}"`
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
function beginFrameworkRenderPass(): void {
|
|
911
|
+
const framework = activeFrameworkRender
|
|
912
|
+
if (framework === undefined) return
|
|
913
|
+
if (framework.signal.aborted) throw frameworkAbortReason(framework.signal)
|
|
914
|
+
framework.pending.clear()
|
|
915
|
+
framework.head.clear()
|
|
916
|
+
framework.stylePrecedence.length = 0
|
|
917
|
+
framework.sawHead = false
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
function finalizeFrameworkHtml(html: string): string {
|
|
921
|
+
const framework = activeFrameworkRender
|
|
922
|
+
if (framework === undefined) return html
|
|
923
|
+
const head = [...framework.head.entries()]
|
|
924
|
+
.toSorted(([, left], [, right]) => compareHeadPrecedence(framework, left, right))
|
|
925
|
+
.map(([, entry]) => entry.html)
|
|
926
|
+
.join('')
|
|
927
|
+
if (framework.sawHead) return html.replace(FRAMEWORK_HEAD_PLACEHOLDER, head)
|
|
928
|
+
if (head === '') return html
|
|
929
|
+
const htmlStart = html.indexOf('<html')
|
|
930
|
+
if (htmlStart !== -1) {
|
|
931
|
+
const htmlOpenEnd = html.indexOf('>', htmlStart)
|
|
932
|
+
if (htmlOpenEnd !== -1) {
|
|
933
|
+
return `${html.slice(0, htmlOpenEnd + 1)}<head>${head}</head>${html.slice(htmlOpenEnd + 1)}`
|
|
934
|
+
}
|
|
935
|
+
}
|
|
936
|
+
return `<head>${head}</head>${html}`
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
function isHoistableHeadElement(type: string, props: ServerProps): boolean {
|
|
940
|
+
if (type === 'title' || type === 'meta') return !hasOwnProp(props, 'itemProp')
|
|
941
|
+
if (type === 'link') {
|
|
942
|
+
if (['itemProp', 'onLoad', 'onError', 'disabled'].some((name) => hasOwnProp(props, name))) {
|
|
943
|
+
return false
|
|
944
|
+
}
|
|
945
|
+
return props?.rel !== 'stylesheet' || typeof props.precedence === 'string'
|
|
946
|
+
}
|
|
947
|
+
if (type === 'style') {
|
|
948
|
+
return typeof props?.href === 'string' && typeof props.precedence === 'string'
|
|
949
|
+
}
|
|
950
|
+
return type === 'script' && props?.async === true && typeof props.src === 'string'
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
function hasOwnProp(props: ServerProps, name: string): boolean {
|
|
954
|
+
return props !== null && Object.hasOwn(props, name)
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
function metadataKey(type: string, props: ServerProps): string {
|
|
958
|
+
if (type === 'title') return 'title'
|
|
959
|
+
if (type === 'meta') {
|
|
960
|
+
for (const name of META_IDENTITY_PROPS) {
|
|
961
|
+
const value = props?.[name]
|
|
962
|
+
if (value !== undefined) return `meta:${name}:${String(value)}`
|
|
963
|
+
}
|
|
964
|
+
}
|
|
965
|
+
if (type === 'link')
|
|
966
|
+
return `link:${LINK_IDENTITY_PROPS.map((name) => String(props?.[name] ?? '')).join(':')}`
|
|
967
|
+
if (type === 'style') return `style:${String(props?.href ?? '')}`
|
|
968
|
+
if (type === 'script') return `script:${String(props?.src ?? '')}`
|
|
969
|
+
return `${type}:${JSON.stringify(props)}`
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
function metadataPrecedence(type: string, props: ServerProps): string {
|
|
973
|
+
if (type === 'style' || (type === 'link' && props?.rel === 'stylesheet')) {
|
|
974
|
+
return `2:${String(props?.precedence ?? 'default')}`
|
|
975
|
+
}
|
|
976
|
+
if (type === 'script') return '3:script'
|
|
977
|
+
return '1:metadata'
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
function registerResourceHint(
|
|
981
|
+
relation: string,
|
|
982
|
+
href: string,
|
|
983
|
+
options: ResourceHintOptions | PreloadOptions,
|
|
984
|
+
): void {
|
|
985
|
+
if (typeof href !== 'string' || href.length === 0)
|
|
986
|
+
throw new TypeError('resource href must be non-empty')
|
|
987
|
+
const as =
|
|
988
|
+
'as' in options && typeof options.as === 'string' ? ` as="${escapeAttribute(options.as)}"` : ''
|
|
989
|
+
registerHeadEntry(
|
|
990
|
+
`hint:${relation}:${href}:${as}`,
|
|
991
|
+
`<link rel="${relation}" href="${escapeAttribute(href)}"${as}${serializeHintOptions(options)}>`,
|
|
992
|
+
'0:hint',
|
|
993
|
+
)
|
|
994
|
+
}
|
|
995
|
+
|
|
996
|
+
function registerHeadEntry(key: string, html: string, precedence: string): void {
|
|
997
|
+
const framework = activeFrameworkRender
|
|
998
|
+
if (framework === undefined) return
|
|
999
|
+
if (precedence.startsWith('2:')) {
|
|
1000
|
+
const stylePrecedence = precedence.slice(2)
|
|
1001
|
+
if (!framework.stylePrecedence.includes(stylePrecedence)) {
|
|
1002
|
+
framework.stylePrecedence.push(stylePrecedence)
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
framework.head.set(key, { html, precedence })
|
|
1006
|
+
}
|
|
1007
|
+
|
|
1008
|
+
function compareHeadPrecedence(
|
|
1009
|
+
framework: ServerFrameworkRenderContext,
|
|
1010
|
+
left: { readonly precedence: string },
|
|
1011
|
+
right: { readonly precedence: string },
|
|
1012
|
+
): number {
|
|
1013
|
+
const leftRank = Number(left.precedence[0])
|
|
1014
|
+
const rightRank = Number(right.precedence[0])
|
|
1015
|
+
if (leftRank !== rightRank) return leftRank - rightRank
|
|
1016
|
+
if (leftRank !== 2) return 0
|
|
1017
|
+
return (
|
|
1018
|
+
framework.stylePrecedence.indexOf(left.precedence.slice(2)) -
|
|
1019
|
+
framework.stylePrecedence.indexOf(right.precedence.slice(2))
|
|
1020
|
+
)
|
|
1021
|
+
}
|
|
1022
|
+
|
|
1023
|
+
function serializeHintOptions(
|
|
1024
|
+
options: ResourceHintOptions | PreloadOptions | PreinitOptions,
|
|
1025
|
+
): string {
|
|
1026
|
+
const attributes: string[] = []
|
|
1027
|
+
if (options.crossOrigin !== undefined) {
|
|
1028
|
+
attributes.push(` crossorigin="${escapeAttribute(options.crossOrigin)}"`)
|
|
1029
|
+
}
|
|
1030
|
+
if ('fetchPriority' in options && options.fetchPriority !== undefined) {
|
|
1031
|
+
attributes.push(` fetchpriority="${options.fetchPriority}"`)
|
|
1032
|
+
}
|
|
1033
|
+
if ('imageSizes' in options && options.imageSizes !== undefined) {
|
|
1034
|
+
attributes.push(` imagesizes="${escapeAttribute(options.imageSizes)}"`)
|
|
1035
|
+
}
|
|
1036
|
+
if ('imageSrcSet' in options && options.imageSrcSet !== undefined) {
|
|
1037
|
+
attributes.push(` imagesrcset="${escapeAttribute(options.imageSrcSet)}"`)
|
|
1038
|
+
}
|
|
1039
|
+
if ('integrity' in options && options.integrity !== undefined) {
|
|
1040
|
+
attributes.push(` integrity="${escapeAttribute(options.integrity)}"`)
|
|
1041
|
+
}
|
|
1042
|
+
if ('nonce' in options && options.nonce !== undefined) {
|
|
1043
|
+
attributes.push(` nonce="${escapeAttribute(options.nonce)}"`)
|
|
1044
|
+
}
|
|
1045
|
+
if ('referrerPolicy' in options && options.referrerPolicy !== undefined) {
|
|
1046
|
+
attributes.push(` referrerpolicy="${escapeAttribute(options.referrerPolicy)}"`)
|
|
1047
|
+
}
|
|
1048
|
+
if ('type' in options && options.type !== undefined) {
|
|
1049
|
+
attributes.push(` type="${escapeAttribute(options.type)}"`)
|
|
1050
|
+
}
|
|
1051
|
+
return attributes.join('')
|
|
1052
|
+
}
|
|
1053
|
+
|
|
1054
|
+
function frameworkAbortReason(signal: AbortSignal): unknown {
|
|
1055
|
+
return signal.reason ?? new DOMException('The operation was aborted', 'AbortError')
|
|
1056
|
+
}
|
|
1057
|
+
|
|
1058
|
+
function serializeStyleAttribute(value: unknown, hiddenByActivity = false): string {
|
|
1059
|
+
if (typeof value === 'string') {
|
|
1060
|
+
const separator = value === '' || value.endsWith(';') ? '' : ';'
|
|
1061
|
+
const style = hiddenByActivity ? `${value}${separator}display:none!important` : value
|
|
1062
|
+
return style === '' ? '' : ` style="${escapeAttribute(style)}"`
|
|
1063
|
+
}
|
|
1064
|
+
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
|
|
1065
|
+
return hiddenByActivity ? ' style="display:none!important"' : ''
|
|
1066
|
+
}
|
|
1067
|
+
|
|
1068
|
+
const declarations = Object.entries(value)
|
|
1069
|
+
.toSorted(([left], [right]) => left.localeCompare(right))
|
|
1070
|
+
.flatMap(([name, item]) => {
|
|
1071
|
+
if (item === null || item === undefined || typeof item === 'boolean' || item === '') return []
|
|
1072
|
+
const cssName = name.startsWith('--') ? name : camelToKebab(name)
|
|
1073
|
+
const cssValue =
|
|
1074
|
+
typeof item === 'number' && item !== 0 && !UNITLESS_STYLE_PROPERTIES.has(name)
|
|
1075
|
+
? `${item}px`
|
|
1076
|
+
: item
|
|
1077
|
+
return `${cssName}:${String(cssValue)}`
|
|
1078
|
+
})
|
|
1079
|
+
if (hiddenByActivity) declarations.push('display:none!important')
|
|
1080
|
+
return declarations.length === 0 ? '' : ` style="${escapeAttribute(declarations.join(';'))}"`
|
|
1081
|
+
}
|
|
1082
|
+
|
|
1083
|
+
function camelToKebab(name: string): string {
|
|
1084
|
+
return name
|
|
1085
|
+
.replace(/[A-Z]/g, (character) => `-${character.toLowerCase()}`)
|
|
1086
|
+
.replace(/^ms-/, '-ms-')
|
|
1087
|
+
}
|
|
1088
|
+
|
|
1089
|
+
function readRawHtml(props: ServerProps): string | undefined {
|
|
1090
|
+
const value = props?.dangerouslySetInnerHTML
|
|
1091
|
+
if (value === null || value === undefined) return undefined
|
|
1092
|
+
if (!UNSAFE_HTML) {
|
|
1093
|
+
throw new Error('dangerouslySetInnerHTML requires the unsafe-html compiler feature')
|
|
1094
|
+
}
|
|
1095
|
+
if (typeof value !== 'object' || !('__html' in value)) {
|
|
1096
|
+
throw new TypeError('dangerouslySetInnerHTML must be an object with an __html property')
|
|
1097
|
+
}
|
|
1098
|
+
const html = Reflect.get(value, '__html') as unknown
|
|
1099
|
+
return html === null || html === undefined ? '' : String(html)
|
|
1100
|
+
}
|
|
1101
|
+
|
|
1102
|
+
function escapeText(value: string): string {
|
|
1103
|
+
return value.replaceAll('&', '&').replaceAll('<', '<').replaceAll('>', '>')
|
|
1104
|
+
}
|
|
1105
|
+
|
|
1106
|
+
function escapeAttribute(value: string): string {
|
|
1107
|
+
return escapeText(value).replaceAll('"', '"').replaceAll("'", ''')
|
|
1108
|
+
}
|
|
1109
|
+
|
|
1110
|
+
function serializeElementChildren(
|
|
1111
|
+
type: string,
|
|
1112
|
+
value: ServerChild,
|
|
1113
|
+
multipleChildren: boolean,
|
|
1114
|
+
context: RenderContext,
|
|
1115
|
+
): string {
|
|
1116
|
+
if (!isRawTextElement(type)) {
|
|
1117
|
+
return multipleChildren ? serializeChildren(value, context) : serializeSlot(value, context)
|
|
1118
|
+
}
|
|
1119
|
+
// The HTML parser treats the contents of <script>, <style>, <textarea> and <title> as
|
|
1120
|
+
// text, so a hydration marker here would surface as literal content — a visible
|
|
1121
|
+
// "<!--v2:b-->" inside the textarea until hydration replaced it. The client claims the
|
|
1122
|
+
// parsed text node directly instead.
|
|
1123
|
+
const hydrationMarkers = context.hydrationMarkers
|
|
1124
|
+
context.hydrationMarkers = false
|
|
1125
|
+
try {
|
|
1126
|
+
return multipleChildren ? serializeChildren(value, context) : serializeSlot(value, context)
|
|
1127
|
+
} finally {
|
|
1128
|
+
context.hydrationMarkers = hydrationMarkers
|
|
1129
|
+
}
|
|
1130
|
+
}
|
|
1131
|
+
|
|
1132
|
+
function serializeChildren(value: ServerChild, context: RenderContext): string {
|
|
1133
|
+
return Array.isArray(value)
|
|
1134
|
+
? value.map((child) => serializeSlot(child, context)).join('')
|
|
1135
|
+
: serializeSlot(value, context)
|
|
1136
|
+
}
|
|
1137
|
+
|
|
1138
|
+
function serializeSlot(value: ServerChild, context: RenderContext): string {
|
|
1139
|
+
const content = serializeChild(value, context)
|
|
1140
|
+
return context.hydrationMarkers ? hydrationRange('b', content) : content
|
|
1141
|
+
}
|
|
1142
|
+
|
|
1143
|
+
function hydrationRange(kind: 'a' | 'b' | 'c' | 'h' | 'r', content: string): string {
|
|
1144
|
+
return `<!--${HYDRATION_PREFIX}:${kind}-->${content}<!--/${HYDRATION_PREFIX}:${kind}-->`
|
|
1145
|
+
}
|
|
1146
|
+
|
|
1147
|
+
function assertSafeHydrationRawHtml(value: string): string {
|
|
1148
|
+
if (/<!--\/?(?:vidact:)?v\d+:[a-z]-->/i.test(value)) {
|
|
1149
|
+
throw new Error('raw HTML cannot contain Vidact hydration marker syntax')
|
|
1150
|
+
}
|
|
1151
|
+
return value
|
|
1152
|
+
}
|
|
1153
|
+
|
|
1154
|
+
function isRawTextElement(type: string): boolean {
|
|
1155
|
+
return type === 'script' || type === 'style' || type === 'textarea' || type === 'title'
|
|
1156
|
+
}
|
|
1157
|
+
|
|
1158
|
+
function serverDispatch(): never {
|
|
1159
|
+
throw new Error('state dispatch is unavailable during server rendering')
|
|
1160
|
+
}
|