@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.
Files changed (65) hide show
  1. package/dist/auth.d.ts.map +1 -1
  2. package/dist/auth.js +39 -18
  3. package/dist/auth.js.map +1 -1
  4. package/dist/island.d.ts.map +1 -1
  5. package/dist/island.js +4 -1
  6. package/dist/island.js.map +1 -1
  7. package/dist/react-dom-server.js +1 -1
  8. package/dist/route-props.d.ts.map +1 -1
  9. package/dist/route-props.js +1 -0
  10. package/dist/route-props.js.map +1 -1
  11. package/dist/server-components/client.d.ts +3 -0
  12. package/dist/server-components/client.d.ts.map +1 -0
  13. package/dist/server-components/client.js +167 -0
  14. package/dist/server-components/client.js.map +1 -0
  15. package/dist/server-components/flight.d.ts +4 -0
  16. package/dist/server-components/flight.d.ts.map +1 -0
  17. package/dist/server-components/flight.js +87 -0
  18. package/dist/server-components/flight.js.map +1 -0
  19. package/dist/server-components/guard.d.ts +8 -0
  20. package/dist/server-components/guard.d.ts.map +1 -0
  21. package/dist/server-components/guard.js +139 -0
  22. package/dist/server-components/guard.js.map +1 -0
  23. package/dist/server-components/jsx-dev-runtime.d.ts +8 -0
  24. package/dist/server-components/jsx-dev-runtime.d.ts.map +1 -0
  25. package/dist/server-components/jsx-dev-runtime.js +12 -0
  26. package/dist/server-components/jsx-dev-runtime.js.map +1 -0
  27. package/dist/server-components/jsx-runtime.d.ts +5 -0
  28. package/dist/server-components/jsx-runtime.d.ts.map +1 -0
  29. package/dist/server-components/jsx-runtime.js +14 -0
  30. package/dist/server-components/jsx-runtime.js.map +1 -0
  31. package/dist/server-components/nested.d.ts +11 -0
  32. package/dist/server-components/nested.d.ts.map +1 -0
  33. package/dist/server-components/nested.js +12 -0
  34. package/dist/server-components/nested.js.map +1 -0
  35. package/dist/server-components/nested.props.d.ts +5 -0
  36. package/dist/server-components/nested.props.d.ts.map +1 -0
  37. package/dist/server-components/nested.props.js +3 -0
  38. package/dist/server-components/nested.props.js.map +1 -0
  39. package/dist/server-components/outlet.d.ts +19 -0
  40. package/dist/server-components/outlet.d.ts.map +1 -0
  41. package/dist/server-components/outlet.js +76 -0
  42. package/dist/server-components/outlet.js.map +1 -0
  43. package/dist/server-components/rsc.d.ts +4 -0
  44. package/dist/server-components/rsc.d.ts.map +1 -0
  45. package/dist/server-components/rsc.js +33 -0
  46. package/dist/server-components/rsc.js.map +1 -0
  47. package/dist/server-components/server.d.ts +4 -0
  48. package/dist/server-components/server.d.ts.map +1 -0
  49. package/dist/server-components/server.js +34 -0
  50. package/dist/server-components/server.js.map +1 -0
  51. package/package.json +63 -9
  52. package/src/auth.ts +34 -18
  53. package/src/island.ts +4 -1
  54. package/src/react-dom-server.ts +1 -1
  55. package/src/route-props.ts +1 -0
  56. package/src/server-components/client.ts +176 -0
  57. package/src/server-components/flight.ts +87 -0
  58. package/src/server-components/guard.ts +199 -0
  59. package/src/server-components/jsx-dev-runtime.ts +27 -0
  60. package/src/server-components/jsx-runtime.ts +19 -0
  61. package/src/server-components/nested.props.ts +2 -0
  62. package/src/server-components/nested.ts +18 -0
  63. package/src/server-components/outlet.ts +109 -0
  64. package/src/server-components/rsc.ts +39 -0
  65. 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
+ }