@nifrajs/web-preact 4.0.2 → 5.0.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/server-components/client.d.ts +3 -0
- package/dist/server-components/client.d.ts.map +1 -0
- package/dist/server-components/client.js +55 -0
- package/dist/server-components/client.js.map +1 -0
- package/dist/server-components/outlet.d.ts +20 -0
- package/dist/server-components/outlet.d.ts.map +1 -0
- package/dist/server-components/outlet.js +61 -0
- package/dist/server-components/outlet.js.map +1 -0
- package/dist/server-components/server.d.ts +3 -0
- package/dist/server-components/server.d.ts.map +1 -0
- package/dist/server-components/server.js +9 -0
- package/dist/server-components/server.js.map +1 -0
- package/package.json +20 -5
- package/src/server-components/client.ts +57 -0
- package/src/server-components/outlet.ts +97 -0
- package/src/server-components/server.ts +11 -0
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { type NativeRangeOptions } from "@nifrajs/web/server-components/range";
|
|
2
|
+
export declare function preactComponentClient(options: Omit<NativeRangeOptions, "adapter" | "runtime">): import("@nifrajs/web/server-components/client").ComponentClientAdapter;
|
|
3
|
+
//# sourceMappingURL=client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/server-components/client.ts"],"names":[],"mappings":"AACA,OAAO,EACL,KAAK,kBAAkB,EAExB,MAAM,sCAAsC,CAAA;AAG7C,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,IAAI,CAAC,kBAAkB,EAAE,SAAS,GAAG,SAAS,CAAC,0EAiD7F"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { disposeComponentPortals } from "@nifrajs/web/server-components/portal";
|
|
2
|
+
import { nativeComponentClient, } from "@nifrajs/web/server-components/range";
|
|
3
|
+
import { h, hydrate, render } from "preact";
|
|
4
|
+
export function preactComponentClient(options) {
|
|
5
|
+
return nativeComponentClient({
|
|
6
|
+
...options,
|
|
7
|
+
adapter: "preact",
|
|
8
|
+
runtime: {
|
|
9
|
+
hydrate(host, contract, record, current) {
|
|
10
|
+
// biome-ignore lint/plugin/requireSafetyCommentForTypeAssertion: The shared range validates the registered island and projects public props before native hydration.
|
|
11
|
+
const component = contract.component;
|
|
12
|
+
// biome-ignore lint/plugin/requireSafetyCommentForTypeAssertion: The shared range validates the registered island and projects public props before native hydration.
|
|
13
|
+
let props = record.props;
|
|
14
|
+
const slots = Object.fromEntries(contract.slots.map((name) => [
|
|
15
|
+
name,
|
|
16
|
+
'<nifra-slot data-nifra-slot="' +
|
|
17
|
+
name +
|
|
18
|
+
'">' +
|
|
19
|
+
(record.slots?.[name] ?? "") +
|
|
20
|
+
"</nifra-slot>",
|
|
21
|
+
]));
|
|
22
|
+
let isCurrent = current;
|
|
23
|
+
let previous = h(component, { ...props, slots });
|
|
24
|
+
const Island = () => {
|
|
25
|
+
if (isCurrent())
|
|
26
|
+
previous = h(component, { ...props, slots });
|
|
27
|
+
return previous;
|
|
28
|
+
};
|
|
29
|
+
hydrate(h(Island, null), host);
|
|
30
|
+
let disposed = false;
|
|
31
|
+
return {
|
|
32
|
+
update(next, valid) {
|
|
33
|
+
if (disposed || !valid())
|
|
34
|
+
return;
|
|
35
|
+
isCurrent = valid;
|
|
36
|
+
props = next;
|
|
37
|
+
render(h(Island, null), host);
|
|
38
|
+
},
|
|
39
|
+
async dispose() {
|
|
40
|
+
if (!disposed) {
|
|
41
|
+
disposed = true;
|
|
42
|
+
try {
|
|
43
|
+
await disposeComponentPortals(host);
|
|
44
|
+
}
|
|
45
|
+
finally {
|
|
46
|
+
render(null, host);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/server-components/client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,uBAAuB,EAAE,MAAM,uCAAuC,CAAA;AAC/E,OAAO,EAEL,qBAAqB,GACtB,MAAM,sCAAsC,CAAA;AAC7C,OAAO,EAAsB,CAAC,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAA;AAE/D,MAAM,UAAU,qBAAqB,CAAC,OAAwD;IAC5F,OAAO,qBAAqB,CAAC;QAC3B,GAAG,OAAO;QACV,OAAO,EAAE,QAAQ;QACjB,OAAO,EAAE;YACP,OAAO,CAAC,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO;gBACrC,qKAAqK;gBACrK,MAAM,SAAS,GAAG,QAAQ,CAAC,SAAmD,CAAA;gBAC9E,qKAAqK;gBACrK,IAAI,KAAK,GAAG,MAAM,CAAC,KAAgC,CAAA;gBACnD,MAAM,KAAK,GAAG,MAAM,CAAC,WAAW,CAC9B,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC;oBAC3B,IAAI;oBACJ,+BAA+B;wBAC7B,IAAI;wBACJ,IAAI;wBACJ,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;wBAC5B,eAAe;iBAClB,CAAC,CACH,CAAA;gBACD,IAAI,SAAS,GAAG,OAAO,CAAA;gBACvB,IAAI,QAAQ,GAAG,CAAC,CAAC,SAAS,EAAE,EAAE,GAAG,KAAK,EAAE,KAAK,EAAE,CAAC,CAAA;gBAChD,MAAM,MAAM,GAAG,GAAG,EAAE;oBAClB,IAAI,SAAS,EAAE;wBAAE,QAAQ,GAAG,CAAC,CAAC,SAAS,EAAE,EAAE,GAAG,KAAK,EAAE,KAAK,EAAE,CAAC,CAAA;oBAC7D,OAAO,QAAQ,CAAA;gBACjB,CAAC,CAAA;gBACD,OAAO,CAAC,CAAC,CAAC,MAAM,EAAE,IAAI,CAAC,EAAE,IAAI,CAAC,CAAA;gBAC9B,IAAI,QAAQ,GAAG,KAAK,CAAA;gBACpB,OAAO;oBACL,MAAM,CAAC,IAAI,EAAE,KAAK;wBAChB,IAAI,QAAQ,IAAI,CAAC,KAAK,EAAE;4BAAE,OAAM;wBAChC,SAAS,GAAG,KAAK,CAAA;wBACjB,KAAK,GAAG,IAAI,CAAA;wBACZ,MAAM,CAAC,CAAC,CAAC,MAAM,EAAE,IAAI,CAAC,EAAE,IAAI,CAAC,CAAA;oBAC/B,CAAC;oBACD,KAAK,CAAC,OAAO;wBACX,IAAI,CAAC,QAAQ,EAAE,CAAC;4BACd,QAAQ,GAAG,IAAI,CAAA;4BACf,IAAI,CAAC;gCACH,MAAM,uBAAuB,CAAC,IAAI,CAAC,CAAA;4BACrC,CAAC;oCAAS,CAAC;gCACT,MAAM,CAAC,IAAI,EAAE,IAAI,CAAC,CAAA;4BACpB,CAAC;wBACH,CAAC;oBACH,CAAC;iBACF,CAAA;YACH,CAAC;SACF;KACF,CAAC,CAAA;AACJ,CAAC"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { type ComponentClientAdapter, ComponentController, type ServerComponent as ComponentHandle, type ComponentKey, type ComponentState } from "@nifrajs/web/server-components/client";
|
|
2
|
+
export interface ServerComponentProps<Input, Props, View> {
|
|
3
|
+
readonly state: ComponentState;
|
|
4
|
+
readonly component: ComponentHandle<Input, Props, View>;
|
|
5
|
+
readonly instanceKey: ComponentKey;
|
|
6
|
+
readonly input: NoInfer<Input>;
|
|
7
|
+
readonly parent?: string;
|
|
8
|
+
readonly controller?: ComponentController | undefined;
|
|
9
|
+
readonly clientAdapter?: ComponentClientAdapter | undefined;
|
|
10
|
+
readonly onError?: ((code: string) => void) | undefined;
|
|
11
|
+
}
|
|
12
|
+
export declare function ServerComponent<Input, Props, View>(props: ServerComponentProps<Input, Props, View>): import("preact").VNode<import("preact").Attributes & {
|
|
13
|
+
key: string;
|
|
14
|
+
state: ComponentState;
|
|
15
|
+
record: import("@nifrajs/web/server-components").ComponentRecord;
|
|
16
|
+
controller: ComponentController | undefined;
|
|
17
|
+
clientAdapter: ComponentClientAdapter | undefined;
|
|
18
|
+
onError: ((code: string) => void) | undefined;
|
|
19
|
+
}>;
|
|
20
|
+
//# sourceMappingURL=outlet.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"outlet.d.ts","sourceRoot":"","sources":["../../src/server-components/outlet.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,sBAAsB,EAC3B,mBAAmB,EACnB,KAAK,eAAe,IAAI,eAAe,EACvC,KAAK,YAAY,EACjB,KAAK,cAAc,EAEpB,MAAM,uCAAuC,CAAA;AAI9C,MAAM,WAAW,oBAAoB,CAAC,KAAK,EAAE,KAAK,EAAE,IAAI;IACtD,QAAQ,CAAC,KAAK,EAAE,cAAc,CAAA;IAC9B,QAAQ,CAAC,SAAS,EAAE,eAAe,CAAC,KAAK,EAAE,KAAK,EAAE,IAAI,CAAC,CAAA;IACvD,QAAQ,CAAC,WAAW,EAAE,YAAY,CAAA;IAClC,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC,KAAK,CAAC,CAAA;IAC9B,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,UAAU,CAAC,EAAE,mBAAmB,GAAG,SAAS,CAAA;IACrD,QAAQ,CAAC,aAAa,CAAC,EAAE,sBAAsB,GAAG,SAAS,CAAA;IAC3D,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC,GAAG,SAAS,CAAA;CACxD;AAED,wBAAgB,eAAe,CAAC,KAAK,EAAE,KAAK,EAAE,IAAI,EAChD,KAAK,EAAE,oBAAoB,CAAC,KAAK,EAAE,KAAK,EAAE,IAAI,CAAC;;;;;;qBAJpB,MAAM,KAAK,IAAI;GAqB3C"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { ComponentController, resolveComponentRecord, } from "@nifrajs/web/server-components/client";
|
|
2
|
+
import { h } from "preact";
|
|
3
|
+
import { useLayoutEffect, useRef } from "preact/hooks";
|
|
4
|
+
export function ServerComponent(props) {
|
|
5
|
+
const record = resolveComponentRecord(props.state, props.component, props.instanceKey, props.input, props.parent ?? null);
|
|
6
|
+
return h(ComponentRange, {
|
|
7
|
+
key: record.instance,
|
|
8
|
+
state: props.state,
|
|
9
|
+
record,
|
|
10
|
+
controller: props.controller,
|
|
11
|
+
clientAdapter: props.clientAdapter,
|
|
12
|
+
onError: props.onError,
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
function ComponentRange(props) {
|
|
16
|
+
const record = props.record;
|
|
17
|
+
const host = useRef(null);
|
|
18
|
+
// Preact owns the shell while the controller owns its independently hydrated native children.
|
|
19
|
+
const initial = useRef({
|
|
20
|
+
__html: `<nifra-component data-nifra-component="${record.instance}">${record.html}</nifra-component>`,
|
|
21
|
+
});
|
|
22
|
+
const active = useRef(undefined);
|
|
23
|
+
const lifecycle = useRef(props);
|
|
24
|
+
lifecycle.current = props;
|
|
25
|
+
useLayoutEffect(() => {
|
|
26
|
+
const options = lifecycle.current;
|
|
27
|
+
if (options.controller === undefined && options.clientAdapter === undefined)
|
|
28
|
+
throw new Error("component_contract");
|
|
29
|
+
const element = host.current?.firstElementChild;
|
|
30
|
+
if (!(element instanceof HTMLElement) || element.localName !== "nifra-component")
|
|
31
|
+
throw new Error("component_contract");
|
|
32
|
+
const controller = options.controller ??
|
|
33
|
+
new ComponentController({
|
|
34
|
+
state: options.state,
|
|
35
|
+
// biome-ignore lint/plugin/requireSafetyCommentForTypeAssertion: The effect rejects missing adapters before creating a controller; callers with a controller bypass this expression.
|
|
36
|
+
adapter: options.clientAdapter,
|
|
37
|
+
onError: (code) => lifecycle.current.onError?.(code),
|
|
38
|
+
});
|
|
39
|
+
active.current = controller;
|
|
40
|
+
void controller
|
|
41
|
+
.mount(element, record.instance)
|
|
42
|
+
.catch(() => controller.reportError("component_failed"));
|
|
43
|
+
return () => {
|
|
44
|
+
void (options.controller === undefined
|
|
45
|
+
? controller.dispose()
|
|
46
|
+
: controller.unmount(record.instance)).catch(() => controller.reportError("component_failed"));
|
|
47
|
+
};
|
|
48
|
+
}, [record.instance]);
|
|
49
|
+
useLayoutEffect(() => {
|
|
50
|
+
if (active.current !== undefined)
|
|
51
|
+
void active.current
|
|
52
|
+
.adopt(props.state)
|
|
53
|
+
.catch(() => active.current?.reportError("component_failed"));
|
|
54
|
+
}, [props.state]);
|
|
55
|
+
return h("nifra-component-outlet", {
|
|
56
|
+
ref: host,
|
|
57
|
+
// Record HTML passed the shared component policy before an outlet can resolve it.
|
|
58
|
+
dangerouslySetInnerHTML: initial.current,
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
//# sourceMappingURL=outlet.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"outlet.js","sourceRoot":"","sources":["../../src/server-components/outlet.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/server-components/server.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,4BAA4B,EAAE,MAAM,gCAAgC,CAAA;AAKlF,eAAO,MAAM,sBAAsB,QAAO,4BAKtC,CAAA"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { nativeServerComponents } from "@nifrajs/web/server-components/native";
|
|
2
|
+
import { h } from "preact";
|
|
3
|
+
import { preactRenderToString } from "../preact-render.js";
|
|
4
|
+
export const preactServerComponents = () => nativeServerComponents("preact", async (component, props) => {
|
|
5
|
+
const renderer = await preactRenderToString();
|
|
6
|
+
// biome-ignore lint/plugin/requireSafetyCommentForTypeAssertion: The native renderer receives only its registered component and schema-projected props from the shared executor.
|
|
7
|
+
return renderer.renderToString(h(component, props));
|
|
8
|
+
});
|
|
9
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../../src/server-components/server.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,sBAAsB,EAAE,MAAM,uCAAuC,CAAA;AAC9E,OAAO,EAAsB,CAAC,EAAE,MAAM,QAAQ,CAAA;AAC9C,OAAO,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAA;AAE1D,MAAM,CAAC,MAAM,sBAAsB,GAAG,GAAiC,EAAE,CACvE,sBAAsB,CAAC,QAAQ,EAAE,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE,EAAE;IAC1D,MAAM,QAAQ,GAAG,MAAM,oBAAoB,EAAE,CAAA;IAC7C,iLAAiL;IACjL,OAAO,QAAQ,CAAC,cAAc,CAAC,CAAC,CAAC,SAAmD,EAAE,KAAK,CAAC,CAAC,CAAA;AAC/F,CAAC,CAAC,CAAA"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nifrajs/web-preact",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "5.0.0",
|
|
4
4
|
"description": "Preact render adapter for @nifrajs/web - streaming SSR + hydration (no build plugin).",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"bun",
|
|
@@ -70,6 +70,21 @@
|
|
|
70
70
|
"bun": "./src/fn.ts",
|
|
71
71
|
"types": "./dist/fn.d.ts",
|
|
72
72
|
"default": "./dist/fn.js"
|
|
73
|
+
},
|
|
74
|
+
"./server-components": {
|
|
75
|
+
"bun": "./src/server-components/server.ts",
|
|
76
|
+
"types": "./dist/server-components/server.d.ts",
|
|
77
|
+
"default": "./dist/server-components/server.js"
|
|
78
|
+
},
|
|
79
|
+
"./server-components/client": {
|
|
80
|
+
"bun": "./src/server-components/client.ts",
|
|
81
|
+
"types": "./dist/server-components/client.d.ts",
|
|
82
|
+
"default": "./dist/server-components/client.js"
|
|
83
|
+
},
|
|
84
|
+
"./server-components/outlet": {
|
|
85
|
+
"bun": "./src/server-components/outlet.ts",
|
|
86
|
+
"types": "./dist/server-components/outlet.d.ts",
|
|
87
|
+
"default": "./dist/server-components/outlet.js"
|
|
73
88
|
}
|
|
74
89
|
},
|
|
75
90
|
"files": [
|
|
@@ -85,10 +100,10 @@
|
|
|
85
100
|
"access": "public"
|
|
86
101
|
},
|
|
87
102
|
"dependencies": {
|
|
88
|
-
"@nifrajs/core": "
|
|
89
|
-
"@nifrajs/i18n": "
|
|
90
|
-
"@nifrajs/image": "
|
|
91
|
-
"@nifrajs/web": "
|
|
103
|
+
"@nifrajs/core": "5.0.0",
|
|
104
|
+
"@nifrajs/i18n": "5.0.0",
|
|
105
|
+
"@nifrajs/image": "5.0.0",
|
|
106
|
+
"@nifrajs/web": "5.0.0",
|
|
92
107
|
"preact-render-to-string": "^6.5.0"
|
|
93
108
|
},
|
|
94
109
|
"peerDependencies": {
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { disposeComponentPortals } from "@nifrajs/web/server-components/portal"
|
|
2
|
+
import {
|
|
3
|
+
type NativeRangeOptions,
|
|
4
|
+
nativeComponentClient,
|
|
5
|
+
} from "@nifrajs/web/server-components/range"
|
|
6
|
+
import { type ComponentType, h, hydrate, render } from "preact"
|
|
7
|
+
|
|
8
|
+
export function preactComponentClient(options: Omit<NativeRangeOptions, "adapter" | "runtime">) {
|
|
9
|
+
return nativeComponentClient({
|
|
10
|
+
...options,
|
|
11
|
+
adapter: "preact",
|
|
12
|
+
runtime: {
|
|
13
|
+
hydrate(host, contract, record, current) {
|
|
14
|
+
// biome-ignore lint/plugin/requireSafetyCommentForTypeAssertion: The shared range validates the registered island and projects public props before native hydration.
|
|
15
|
+
const component = contract.component as ComponentType<Record<string, unknown>>
|
|
16
|
+
// biome-ignore lint/plugin/requireSafetyCommentForTypeAssertion: The shared range validates the registered island and projects public props before native hydration.
|
|
17
|
+
let props = record.props as Record<string, unknown>
|
|
18
|
+
const slots = Object.fromEntries(
|
|
19
|
+
contract.slots.map((name) => [
|
|
20
|
+
name,
|
|
21
|
+
'<nifra-slot data-nifra-slot="' +
|
|
22
|
+
name +
|
|
23
|
+
'">' +
|
|
24
|
+
(record.slots?.[name] ?? "") +
|
|
25
|
+
"</nifra-slot>",
|
|
26
|
+
]),
|
|
27
|
+
)
|
|
28
|
+
let isCurrent = current
|
|
29
|
+
let previous = h(component, { ...props, slots })
|
|
30
|
+
const Island = () => {
|
|
31
|
+
if (isCurrent()) previous = h(component, { ...props, slots })
|
|
32
|
+
return previous
|
|
33
|
+
}
|
|
34
|
+
hydrate(h(Island, null), host)
|
|
35
|
+
let disposed = false
|
|
36
|
+
return {
|
|
37
|
+
update(next, valid) {
|
|
38
|
+
if (disposed || !valid()) return
|
|
39
|
+
isCurrent = valid
|
|
40
|
+
props = next
|
|
41
|
+
render(h(Island, null), host)
|
|
42
|
+
},
|
|
43
|
+
async dispose() {
|
|
44
|
+
if (!disposed) {
|
|
45
|
+
disposed = true
|
|
46
|
+
try {
|
|
47
|
+
await disposeComponentPortals(host)
|
|
48
|
+
} finally {
|
|
49
|
+
render(null, host)
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
})
|
|
57
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
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 { h } from "preact"
|
|
10
|
+
import { useLayoutEffect, useRef } from "preact/hooks"
|
|
11
|
+
|
|
12
|
+
export interface ServerComponentProps<Input, Props, View> {
|
|
13
|
+
readonly state: ComponentState
|
|
14
|
+
readonly component: ComponentHandle<Input, Props, View>
|
|
15
|
+
readonly instanceKey: ComponentKey
|
|
16
|
+
readonly input: NoInfer<Input>
|
|
17
|
+
readonly parent?: string
|
|
18
|
+
readonly controller?: ComponentController | undefined
|
|
19
|
+
readonly clientAdapter?: ComponentClientAdapter | undefined
|
|
20
|
+
readonly onError?: ((code: string) => void) | undefined
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function ServerComponent<Input, Props, View>(
|
|
24
|
+
props: ServerComponentProps<Input, Props, View>,
|
|
25
|
+
) {
|
|
26
|
+
const record = resolveComponentRecord(
|
|
27
|
+
props.state,
|
|
28
|
+
props.component,
|
|
29
|
+
props.instanceKey,
|
|
30
|
+
props.input,
|
|
31
|
+
props.parent ?? null,
|
|
32
|
+
)
|
|
33
|
+
return h(ComponentRange, {
|
|
34
|
+
key: record.instance,
|
|
35
|
+
state: props.state,
|
|
36
|
+
record,
|
|
37
|
+
controller: props.controller,
|
|
38
|
+
clientAdapter: props.clientAdapter,
|
|
39
|
+
onError: props.onError,
|
|
40
|
+
})
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function ComponentRange(props: {
|
|
44
|
+
readonly state: ComponentState
|
|
45
|
+
readonly record: import("@nifrajs/web/server-components/client").ComponentRecord
|
|
46
|
+
readonly controller?: ComponentController | undefined
|
|
47
|
+
readonly clientAdapter?: ComponentClientAdapter | undefined
|
|
48
|
+
readonly onError?: ((code: string) => void) | undefined
|
|
49
|
+
}) {
|
|
50
|
+
const record = props.record
|
|
51
|
+
const host = useRef<HTMLElement>(null)
|
|
52
|
+
// Preact owns the shell while the controller owns its independently hydrated native children.
|
|
53
|
+
const initial = useRef({
|
|
54
|
+
__html: `<nifra-component data-nifra-component="${record.instance}">${record.html}</nifra-component>`,
|
|
55
|
+
})
|
|
56
|
+
const active = useRef<ComponentController | undefined>(undefined)
|
|
57
|
+
const lifecycle = useRef(props)
|
|
58
|
+
lifecycle.current = props
|
|
59
|
+
useLayoutEffect(() => {
|
|
60
|
+
const options = lifecycle.current
|
|
61
|
+
if (options.controller === undefined && options.clientAdapter === undefined)
|
|
62
|
+
throw new Error("component_contract")
|
|
63
|
+
const element = host.current?.firstElementChild
|
|
64
|
+
if (!(element instanceof HTMLElement) || element.localName !== "nifra-component")
|
|
65
|
+
throw new Error("component_contract")
|
|
66
|
+
const controller =
|
|
67
|
+
options.controller ??
|
|
68
|
+
new ComponentController({
|
|
69
|
+
state: options.state,
|
|
70
|
+
// biome-ignore lint/plugin/requireSafetyCommentForTypeAssertion: The effect rejects missing adapters before creating a controller; callers with a controller bypass this expression.
|
|
71
|
+
adapter: options.clientAdapter as ComponentClientAdapter,
|
|
72
|
+
onError: (code) => lifecycle.current.onError?.(code),
|
|
73
|
+
})
|
|
74
|
+
active.current = controller
|
|
75
|
+
void controller
|
|
76
|
+
.mount(element, record.instance)
|
|
77
|
+
.catch(() => controller.reportError("component_failed"))
|
|
78
|
+
return () => {
|
|
79
|
+
void (
|
|
80
|
+
options.controller === undefined
|
|
81
|
+
? controller.dispose()
|
|
82
|
+
: controller.unmount(record.instance)
|
|
83
|
+
).catch(() => controller.reportError("component_failed"))
|
|
84
|
+
}
|
|
85
|
+
}, [record.instance])
|
|
86
|
+
useLayoutEffect(() => {
|
|
87
|
+
if (active.current !== undefined)
|
|
88
|
+
void active.current
|
|
89
|
+
.adopt(props.state)
|
|
90
|
+
.catch(() => active.current?.reportError("component_failed"))
|
|
91
|
+
}, [props.state])
|
|
92
|
+
return h("nifra-component-outlet", {
|
|
93
|
+
ref: host,
|
|
94
|
+
// Record HTML passed the shared component policy before an outlet can resolve it.
|
|
95
|
+
dangerouslySetInnerHTML: initial.current,
|
|
96
|
+
})
|
|
97
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { ServerComponentRenderAdapter } from "@nifrajs/web/server-components"
|
|
2
|
+
import { nativeServerComponents } from "@nifrajs/web/server-components/native"
|
|
3
|
+
import { type ComponentType, h } from "preact"
|
|
4
|
+
import { preactRenderToString } from "../preact-render.ts"
|
|
5
|
+
|
|
6
|
+
export const preactServerComponents = (): ServerComponentRenderAdapter =>
|
|
7
|
+
nativeServerComponents("preact", async (component, props) => {
|
|
8
|
+
const renderer = await preactRenderToString()
|
|
9
|
+
// biome-ignore lint/plugin/requireSafetyCommentForTypeAssertion: The native renderer receives only its registered component and schema-projected props from the shared executor.
|
|
10
|
+
return renderer.renderToString(h(component as ComponentType<Record<string, unknown>>, props))
|
|
11
|
+
})
|