@tschk/moonshine-react 0.3.7 → 0.4.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/package.json +1 -1
- package/src/client.tsx +2 -2
- package/src/islands.tsx +15 -8
- package/src/serialize.ts +4 -29
package/package.json
CHANGED
package/src/client.tsx
CHANGED
package/src/islands.tsx
CHANGED
|
@@ -62,7 +62,10 @@ export function island<P>(
|
|
|
62
62
|
const mod = use(loader());
|
|
63
63
|
const Component = ((mod as { default?: ComponentType<P> }).default ??
|
|
64
64
|
mod) as FunctionComponent<P>;
|
|
65
|
-
const children = createElement(
|
|
65
|
+
const children = createElement(
|
|
66
|
+
Component as FunctionComponent<unknown>,
|
|
67
|
+
props as unknown as Record<string, unknown>,
|
|
68
|
+
);
|
|
66
69
|
if (registry) {
|
|
67
70
|
registry.entries.set(id, {
|
|
68
71
|
id,
|
|
@@ -74,13 +77,14 @@ export function island<P>(
|
|
|
74
77
|
Fragment,
|
|
75
78
|
null,
|
|
76
79
|
createElement("div", { "data-moonshine-island": id }, children),
|
|
77
|
-
createElement(
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
80
|
+
createElement(
|
|
81
|
+
"script",
|
|
82
|
+
{
|
|
83
|
+
type: "application/json",
|
|
84
|
+
"data-moonshine-island-props": id,
|
|
82
85
|
},
|
|
83
|
-
|
|
86
|
+
serializeIslandProps(props),
|
|
87
|
+
),
|
|
84
88
|
);
|
|
85
89
|
};
|
|
86
90
|
return IslandComponent;
|
|
@@ -107,7 +111,10 @@ export function hydrateIslands(): void {
|
|
|
107
111
|
const { createElement } = await import("react");
|
|
108
112
|
const Component = ((mod as { default?: ComponentType<unknown> })
|
|
109
113
|
.default ?? mod) as FunctionComponent<unknown>;
|
|
110
|
-
hydrateRoot(
|
|
114
|
+
hydrateRoot(
|
|
115
|
+
marker,
|
|
116
|
+
createElement(Component, props as Record<string, unknown>),
|
|
117
|
+
);
|
|
111
118
|
});
|
|
112
119
|
}
|
|
113
120
|
}
|
package/src/serialize.ts
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
}
|
|
1
|
+
// Island props go into a `<script>` tag, the same hazard the server's data
|
|
2
|
+
// payload has, so both use the framework's one HTML-safe JSON serializer.
|
|
3
|
+
// A second copy here is a second place to forget an escape vector.
|
|
4
|
+
export { serializeData as serializeIslandProps } from "@tschk/moonshine-framework";
|
|
6
5
|
|
|
7
6
|
function escapeHtmlValue(value: string): string {
|
|
8
7
|
return value
|
|
@@ -13,30 +12,6 @@ function escapeHtmlValue(value: string): string {
|
|
|
13
12
|
.replace(/'/g, "'");
|
|
14
13
|
}
|
|
15
14
|
|
|
16
|
-
export function serializeIslandProps(value: unknown): string {
|
|
17
|
-
// Ancestor chain, not a visited set: a value repeated in sibling positions
|
|
18
|
-
// is shared, not cyclic, and must still serialize.
|
|
19
|
-
const ancestors: unknown[] = [];
|
|
20
|
-
function replacer(this: object, _key: string, v: unknown): unknown {
|
|
21
|
-
if (v === undefined) return undefined;
|
|
22
|
-
const type = typeof v;
|
|
23
|
-
if (type === "function" || type === "symbol" || type === "bigint") {
|
|
24
|
-
throw new TypeError(`cannot serialize ${type}`);
|
|
25
|
-
}
|
|
26
|
-
if (v !== null && typeof v === "object") {
|
|
27
|
-
while (ancestors.length > 0 && ancestors[ancestors.length - 1] !== this) {
|
|
28
|
-
ancestors.pop();
|
|
29
|
-
}
|
|
30
|
-
if (ancestors.includes(v)) throw new TypeError("cyclic reference");
|
|
31
|
-
ancestors.push(v);
|
|
32
|
-
}
|
|
33
|
-
return v;
|
|
34
|
-
}
|
|
35
|
-
const raw = JSON.stringify(value, replacer);
|
|
36
|
-
if (raw === undefined) return "null";
|
|
37
|
-
return raw.replace(HTML_ESCAPE, escapeHtmlChar);
|
|
38
|
-
}
|
|
39
|
-
|
|
40
15
|
export function escapeHtml(value: string): string {
|
|
41
16
|
return escapeHtmlValue(value);
|
|
42
17
|
}
|