@nifrajs/web-react 4.0.2 → 5.1.0
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/auth.d.ts.map +1 -1
- package/dist/auth.js +39 -18
- package/dist/auth.js.map +1 -1
- package/dist/island.d.ts.map +1 -1
- package/dist/island.js +4 -1
- package/dist/island.js.map +1 -1
- package/dist/react-dom-server.js +1 -1
- package/dist/route-props.d.ts.map +1 -1
- package/dist/route-props.js +1 -0
- package/dist/route-props.js.map +1 -1
- package/dist/server-components/client.d.ts +3 -0
- package/dist/server-components/client.d.ts.map +1 -0
- package/dist/server-components/client.js +167 -0
- package/dist/server-components/client.js.map +1 -0
- package/dist/server-components/flight.d.ts +4 -0
- package/dist/server-components/flight.d.ts.map +1 -0
- package/dist/server-components/flight.js +87 -0
- package/dist/server-components/flight.js.map +1 -0
- package/dist/server-components/guard.d.ts +8 -0
- package/dist/server-components/guard.d.ts.map +1 -0
- package/dist/server-components/guard.js +139 -0
- package/dist/server-components/guard.js.map +1 -0
- package/dist/server-components/jsx-dev-runtime.d.ts +8 -0
- package/dist/server-components/jsx-dev-runtime.d.ts.map +1 -0
- package/dist/server-components/jsx-dev-runtime.js +12 -0
- package/dist/server-components/jsx-dev-runtime.js.map +1 -0
- package/dist/server-components/jsx-runtime.d.ts +5 -0
- package/dist/server-components/jsx-runtime.d.ts.map +1 -0
- package/dist/server-components/jsx-runtime.js +14 -0
- package/dist/server-components/jsx-runtime.js.map +1 -0
- package/dist/server-components/nested.d.ts +11 -0
- package/dist/server-components/nested.d.ts.map +1 -0
- package/dist/server-components/nested.js +12 -0
- package/dist/server-components/nested.js.map +1 -0
- package/dist/server-components/nested.props.d.ts +5 -0
- package/dist/server-components/nested.props.d.ts.map +1 -0
- package/dist/server-components/nested.props.js +3 -0
- package/dist/server-components/nested.props.js.map +1 -0
- package/dist/server-components/outlet.d.ts +19 -0
- package/dist/server-components/outlet.d.ts.map +1 -0
- package/dist/server-components/outlet.js +76 -0
- package/dist/server-components/outlet.js.map +1 -0
- package/dist/server-components/rsc.d.ts +4 -0
- package/dist/server-components/rsc.d.ts.map +1 -0
- package/dist/server-components/rsc.js +33 -0
- package/dist/server-components/rsc.js.map +1 -0
- package/dist/server-components/server.d.ts +4 -0
- package/dist/server-components/server.d.ts.map +1 -0
- package/dist/server-components/server.js +34 -0
- package/dist/server-components/server.js.map +1 -0
- package/package.json +63 -9
- package/src/auth.ts +34 -18
- package/src/island.ts +4 -1
- package/src/react-dom-server.ts +1 -1
- package/src/route-props.ts +1 -0
- package/src/server-components/client.ts +176 -0
- package/src/server-components/flight.ts +87 -0
- package/src/server-components/guard.ts +199 -0
- package/src/server-components/jsx-dev-runtime.ts +27 -0
- package/src/server-components/jsx-runtime.ts +19 -0
- package/src/server-components/nested.props.ts +2 -0
- package/src/server-components/nested.ts +18 -0
- package/src/server-components/outlet.ts +109 -0
- package/src/server-components/rsc.ts +39 -0
- package/src/server-components/server.ts +52 -0
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type ComponentClientAdapter,
|
|
3
|
+
ComponentController,
|
|
4
|
+
type ServerComponent as ComponentHandle,
|
|
5
|
+
type ComponentKey,
|
|
6
|
+
type ComponentState,
|
|
7
|
+
resolveComponentRecord,
|
|
8
|
+
} from "@nifrajs/web/server-components/client"
|
|
9
|
+
import { createElement, useLayoutEffect, useRef } from "react"
|
|
10
|
+
|
|
11
|
+
declare const __NIFRA_COMPONENT_BROWSER__: boolean
|
|
12
|
+
function lazyClient(): ComponentClientAdapter {
|
|
13
|
+
let pending: Promise<ComponentClientAdapter> | undefined
|
|
14
|
+
const client = () => {
|
|
15
|
+
if (typeof __NIFRA_COMPONENT_BROWSER__ === "boolean" && !__NIFRA_COMPONENT_BROWSER__)
|
|
16
|
+
throw new Error("component_contract")
|
|
17
|
+
pending ??= import("./client.ts").then((module) => module.reactComponentClient())
|
|
18
|
+
return pending
|
|
19
|
+
}
|
|
20
|
+
return {
|
|
21
|
+
name: "react",
|
|
22
|
+
prepare: async (...args) => (await client()).prepare(...args),
|
|
23
|
+
hydrate: async (...args) => (await client()).hydrate(...args),
|
|
24
|
+
create: async (...args) => {
|
|
25
|
+
const create = (await client()).create
|
|
26
|
+
if (create === undefined) throw new Error("component_contract")
|
|
27
|
+
return create(...args)
|
|
28
|
+
},
|
|
29
|
+
update: async (...args) => (await client()).update(...args),
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
export interface ServerComponentProps<Input, Props, View> {
|
|
33
|
+
readonly state: ComponentState
|
|
34
|
+
readonly component: ComponentHandle<Input, Props, View>
|
|
35
|
+
readonly instanceKey: ComponentKey
|
|
36
|
+
readonly input: NoInfer<Input>
|
|
37
|
+
readonly parent?: string
|
|
38
|
+
readonly controller?: ComponentController | undefined
|
|
39
|
+
readonly clientAdapter?: ComponentClientAdapter
|
|
40
|
+
readonly onError?: (code: string) => void
|
|
41
|
+
}
|
|
42
|
+
export function ServerComponent<Input, Props, View>(
|
|
43
|
+
props: ServerComponentProps<Input, Props, View>,
|
|
44
|
+
) {
|
|
45
|
+
const record = resolveComponentRecord(
|
|
46
|
+
props.state,
|
|
47
|
+
props.component,
|
|
48
|
+
props.instanceKey,
|
|
49
|
+
props.input,
|
|
50
|
+
props.parent ?? null,
|
|
51
|
+
)
|
|
52
|
+
return createElement(ComponentRange, {
|
|
53
|
+
key: record.instance,
|
|
54
|
+
state: props.state,
|
|
55
|
+
record,
|
|
56
|
+
controller: props.controller,
|
|
57
|
+
clientAdapter: props.clientAdapter,
|
|
58
|
+
onError: props.onError,
|
|
59
|
+
})
|
|
60
|
+
}
|
|
61
|
+
function ComponentRange(props: {
|
|
62
|
+
readonly state: ComponentState
|
|
63
|
+
readonly record: import("@nifrajs/web/server-components/client").ComponentRecord
|
|
64
|
+
readonly controller?: ComponentController | undefined
|
|
65
|
+
readonly clientAdapter?: ComponentClientAdapter | undefined
|
|
66
|
+
readonly onError?: ((code: string) => void) | undefined
|
|
67
|
+
}) {
|
|
68
|
+
const record = props.record
|
|
69
|
+
const host = useRef<HTMLElement>(null)
|
|
70
|
+
// React owns the shell; the independently hydrated native root is an unassociated child.
|
|
71
|
+
const initial = useRef({
|
|
72
|
+
__html: `<nifra-component data-nifra-component="${record.instance}">${record.html}</nifra-component>`,
|
|
73
|
+
})
|
|
74
|
+
const active = useRef<ComponentController | undefined>(undefined)
|
|
75
|
+
const lifecycle = useRef(props)
|
|
76
|
+
lifecycle.current = props
|
|
77
|
+
useLayoutEffect(() => {
|
|
78
|
+
const options = lifecycle.current
|
|
79
|
+
const element = host.current?.firstElementChild
|
|
80
|
+
if (!(element instanceof HTMLElement) || element.localName !== "nifra-component") return
|
|
81
|
+
const controller =
|
|
82
|
+
options.controller ??
|
|
83
|
+
new ComponentController({
|
|
84
|
+
state: options.state,
|
|
85
|
+
adapter: options.clientAdapter ?? lazyClient(),
|
|
86
|
+
onError: (code) => lifecycle.current.onError?.(code),
|
|
87
|
+
})
|
|
88
|
+
active.current = controller
|
|
89
|
+
void controller
|
|
90
|
+
.mount(element, record.instance)
|
|
91
|
+
.catch(() => lifecycle.current.onError?.("component_failed"))
|
|
92
|
+
return () => {
|
|
93
|
+
void (
|
|
94
|
+
options.controller === undefined
|
|
95
|
+
? controller.dispose()
|
|
96
|
+
: controller.unmount(record.instance)
|
|
97
|
+
).catch(() => lifecycle.current.onError?.("component_failed"))
|
|
98
|
+
}
|
|
99
|
+
}, [record.instance])
|
|
100
|
+
useLayoutEffect(() => {
|
|
101
|
+
if (active.current !== undefined)
|
|
102
|
+
void active.current.adopt(props.state).catch(() => props.onError?.("component_failed"))
|
|
103
|
+
}, [props.state, props.onError])
|
|
104
|
+
return createElement("nifra-component-outlet", {
|
|
105
|
+
ref: host,
|
|
106
|
+
// biome-ignore lint/security/noDangerouslySetInnerHtml: Record HTML passed the shared component policy before an outlet can resolve it.
|
|
107
|
+
dangerouslySetInnerHTML: initial.current,
|
|
108
|
+
})
|
|
109
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { ComponentRenderContext, ServerComponentHandle } from "@nifrajs/web/server-components"
|
|
2
|
+
import { guardComponentProps } from "@nifrajs/web/server-components/props"
|
|
3
|
+
import { renderToReadableStream } from "@vitejs/plugin-rsc/rsc/server"
|
|
4
|
+
import { type ComponentType, createElement } from "react"
|
|
5
|
+
import { publicFlightStream } from "./flight.ts"
|
|
6
|
+
|
|
7
|
+
/** Export this renderer from the compiler's RSC entry, which owns React's server condition. */
|
|
8
|
+
export function createReactComponentRenderer(components: readonly ServerComponentHandle[]) {
|
|
9
|
+
const references = new Map(components.map((component) => [component.reference, component]))
|
|
10
|
+
if (references.size !== components.length || references.has(undefined))
|
|
11
|
+
throw new Error("component_contract")
|
|
12
|
+
return async (
|
|
13
|
+
reference: string,
|
|
14
|
+
props: unknown,
|
|
15
|
+
context: ComponentRenderContext,
|
|
16
|
+
): Promise<ReadableStream<Uint8Array>> => {
|
|
17
|
+
const component = references.get(reference)
|
|
18
|
+
if (component === undefined) throw new Error("component_unavailable")
|
|
19
|
+
const projected = await guardComponentProps(component.props, props)
|
|
20
|
+
const stream = renderToReadableStream(
|
|
21
|
+
// biome-ignore lint/plugin/requireSafetyCommentForTypeAssertion: The renderer resolves an allowlisted definition and guards its public props before invoking that definition.
|
|
22
|
+
createElement(component.view as ComponentType<{ props: unknown; children: unknown }>, {
|
|
23
|
+
props: projected,
|
|
24
|
+
// biome-ignore lint/correctness/noChildrenProp: Server views receive verified record metadata through their declared children prop.
|
|
25
|
+
children: context.children ?? [],
|
|
26
|
+
}),
|
|
27
|
+
{
|
|
28
|
+
signal: context.signal,
|
|
29
|
+
// React development debug metadata can contain server-only props; it has no public transport.
|
|
30
|
+
debugChannel: { writable: new WritableStream<Uint8Array>({ write() {} }) },
|
|
31
|
+
filterStackFrame: () => false,
|
|
32
|
+
onError() {
|
|
33
|
+
return "component_failed"
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
)
|
|
37
|
+
return publicFlightStream(stream, context.signal)
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
ComponentRenderContext,
|
|
3
|
+
ServerComponentRenderAdapter,
|
|
4
|
+
} from "@nifrajs/web/server-components"
|
|
5
|
+
import {
|
|
6
|
+
bytesStream,
|
|
7
|
+
encodeComponentBytes,
|
|
8
|
+
readComponentBytes,
|
|
9
|
+
} from "@nifrajs/web/server-components/stream"
|
|
10
|
+
import { createFromReadableStream } from "@vitejs/plugin-rsc/ssr"
|
|
11
|
+
import type { ReactNode } from "react"
|
|
12
|
+
import { renderToReadableStream } from "react-dom/server.edge"
|
|
13
|
+
|
|
14
|
+
export type ReactFlightRenderer = (
|
|
15
|
+
reference: string,
|
|
16
|
+
props: unknown,
|
|
17
|
+
context: ComponentRenderContext,
|
|
18
|
+
) => Promise<ReadableStream<Uint8Array>>
|
|
19
|
+
|
|
20
|
+
export function reactServerComponents(
|
|
21
|
+
renderFlight: ReactFlightRenderer,
|
|
22
|
+
): ServerComponentRenderAdapter {
|
|
23
|
+
return {
|
|
24
|
+
name: "react",
|
|
25
|
+
version: 1,
|
|
26
|
+
async render(_view, props, context) {
|
|
27
|
+
const flight = await readComponentBytes(
|
|
28
|
+
await renderFlight(context.reference, props, context),
|
|
29
|
+
context.signal,
|
|
30
|
+
)
|
|
31
|
+
const tree = await createFromReadableStream<ReactNode>(bytesStream(flight))
|
|
32
|
+
let failed = false
|
|
33
|
+
const options: NonNullable<Parameters<typeof renderToReadableStream>[1]> = {
|
|
34
|
+
identifierPrefix: context.instance + "-",
|
|
35
|
+
signal: context.signal,
|
|
36
|
+
onError() {
|
|
37
|
+
failed = true
|
|
38
|
+
return "component_failed"
|
|
39
|
+
},
|
|
40
|
+
}
|
|
41
|
+
if (context.nonce !== undefined) options.nonce = context.nonce
|
|
42
|
+
const html = await renderToReadableStream(tree, options)
|
|
43
|
+
await html.allReady
|
|
44
|
+
const bytes = await readComponentBytes(html, context.signal)
|
|
45
|
+
if (failed) throw new Error("component_failed")
|
|
46
|
+
return {
|
|
47
|
+
html: new TextDecoder().decode(bytes),
|
|
48
|
+
payload: { flight: encodeComponentBytes(flight) },
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
}
|
|
52
|
+
}
|