xote 6.1.2 → 6.2.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/README.md +90 -10
- package/dist/xote.cjs +10 -10
- package/dist/xote.mjs +1491 -1330
- package/dist/xote.umd.js +8 -8
- package/package.json +16 -1
- package/rescript.json +2 -0
- package/src/Html.res +13 -13
- package/src/Html.res.mjs +13 -13
- package/src/Hydration.res +134 -79
- package/src/Hydration.res.mjs +255 -186
- package/src/Node.res +2 -594
- package/src/Node.res.mjs +31 -535
- package/src/Prop.res +16 -0
- package/src/Prop.res.mjs +35 -0
- package/src/ReactiveProp.res +2 -14
- package/src/ReactiveProp.res.mjs +7 -20
- package/src/Route.res +4 -0
- package/src/Route.res.mjs +9 -0
- package/src/Router.res +25 -49
- package/src/Router.res.mjs +22 -34
- package/src/RuntimeAttr.res +21 -0
- package/src/RuntimeAttr.res.mjs +42 -0
- package/src/RuntimeDom.res +95 -0
- package/src/RuntimeDom.res.mjs +101 -0
- package/src/RuntimeHtml.res +27 -0
- package/src/RuntimeHtml.res.mjs +34 -0
- package/src/RuntimeHydrationMarkers.res +24 -0
- package/src/RuntimeHydrationMarkers.res.mjs +68 -0
- package/src/RuntimeJsxProp.res +46 -0
- package/src/RuntimeJsxProp.res.mjs +52 -0
- package/src/RuntimeOwner.res +43 -0
- package/src/RuntimeOwner.res.mjs +51 -0
- package/src/SSR.res +25 -93
- package/src/SSR.res.mjs +59 -126
- package/src/SSRState.res +3 -0
- package/src/SSRState.res.mjs +8 -2
- package/src/View.res +599 -0
- package/src/View.res.mjs +614 -0
- package/src/XoteJSX.res +64 -118
- package/src/XoteJSX.res.mjs +79 -118
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
let signalTextStart = "<!--$-->"
|
|
2
|
+
let signalTextEnd = "<!--/$-->"
|
|
3
|
+
let signalTextStartContent = "$"
|
|
4
|
+
let signalTextEndContent = "/$"
|
|
5
|
+
|
|
6
|
+
let signalFragmentStart = "<!--#-->"
|
|
7
|
+
let signalFragmentEnd = "<!--/#-->"
|
|
8
|
+
let signalFragmentStartContent = "#"
|
|
9
|
+
let signalFragmentEndContent = "/#"
|
|
10
|
+
|
|
11
|
+
let keyedListStart = "<!--kl-->"
|
|
12
|
+
let keyedListEnd = "<!--/kl-->"
|
|
13
|
+
let keyedListStartContent = "kl"
|
|
14
|
+
let keyedListEndContent = "/kl"
|
|
15
|
+
|
|
16
|
+
let keyedItemPrefixContent = "k:"
|
|
17
|
+
let keyedItemStart = (key: string): string => `<!--${keyedItemPrefixContent}${key}-->`
|
|
18
|
+
let keyedItemEnd = "<!--/k-->"
|
|
19
|
+
let keyedItemEndContent = "/k"
|
|
20
|
+
|
|
21
|
+
let lazyComponentStart = "<!--lc-->"
|
|
22
|
+
let lazyComponentEnd = "<!--/lc-->"
|
|
23
|
+
let lazyComponentStartContent = "lc"
|
|
24
|
+
let lazyComponentEndContent = "/lc"
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
let keyedItemPrefixContent = "k:";
|
|
5
|
+
|
|
6
|
+
function keyedItemStart(key) {
|
|
7
|
+
return `<!--` + keyedItemPrefixContent + key + `-->`;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
let signalTextStart = "<!--$-->";
|
|
11
|
+
|
|
12
|
+
let signalTextEnd = "<!--/$-->";
|
|
13
|
+
|
|
14
|
+
let signalTextStartContent = "$";
|
|
15
|
+
|
|
16
|
+
let signalTextEndContent = "/$";
|
|
17
|
+
|
|
18
|
+
let signalFragmentStart = "<!--#-->";
|
|
19
|
+
|
|
20
|
+
let signalFragmentEnd = "<!--/#-->";
|
|
21
|
+
|
|
22
|
+
let signalFragmentStartContent = "#";
|
|
23
|
+
|
|
24
|
+
let signalFragmentEndContent = "/#";
|
|
25
|
+
|
|
26
|
+
let keyedListStart = "<!--kl-->";
|
|
27
|
+
|
|
28
|
+
let keyedListEnd = "<!--/kl-->";
|
|
29
|
+
|
|
30
|
+
let keyedListStartContent = "kl";
|
|
31
|
+
|
|
32
|
+
let keyedListEndContent = "/kl";
|
|
33
|
+
|
|
34
|
+
let keyedItemEnd = "<!--/k-->";
|
|
35
|
+
|
|
36
|
+
let keyedItemEndContent = "/k";
|
|
37
|
+
|
|
38
|
+
let lazyComponentStart = "<!--lc-->";
|
|
39
|
+
|
|
40
|
+
let lazyComponentEnd = "<!--/lc-->";
|
|
41
|
+
|
|
42
|
+
let lazyComponentStartContent = "lc";
|
|
43
|
+
|
|
44
|
+
let lazyComponentEndContent = "/lc";
|
|
45
|
+
|
|
46
|
+
export {
|
|
47
|
+
signalTextStart,
|
|
48
|
+
signalTextEnd,
|
|
49
|
+
signalTextStartContent,
|
|
50
|
+
signalTextEndContent,
|
|
51
|
+
signalFragmentStart,
|
|
52
|
+
signalFragmentEnd,
|
|
53
|
+
signalFragmentStartContent,
|
|
54
|
+
signalFragmentEndContent,
|
|
55
|
+
keyedListStart,
|
|
56
|
+
keyedListEnd,
|
|
57
|
+
keyedListStartContent,
|
|
58
|
+
keyedListEndContent,
|
|
59
|
+
keyedItemPrefixContent,
|
|
60
|
+
keyedItemStart,
|
|
61
|
+
keyedItemEnd,
|
|
62
|
+
keyedItemEndContent,
|
|
63
|
+
lazyComponentStart,
|
|
64
|
+
lazyComponentEnd,
|
|
65
|
+
lazyComponentStartContent,
|
|
66
|
+
lazyComponentEndContent,
|
|
67
|
+
}
|
|
68
|
+
/* No side effect */
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
let isReactiveProp = (value: 'a): bool => {
|
|
2
|
+
ignore(value)
|
|
3
|
+
%raw(`value && typeof value === 'object' && ('TAG' in value) && (value.TAG === 'Static' || value.TAG === 'Reactive')`)
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
let toStringAttr = (key: string, value: 'a): (string, View.attrValue) => {
|
|
7
|
+
if isReactiveProp(value) {
|
|
8
|
+
let prop: Prop.t<string> = Obj.magic(value)
|
|
9
|
+
switch prop {
|
|
10
|
+
| Static(value) => View.attr(key, value)
|
|
11
|
+
| Reactive(signal) => View.signalAttr(key, signal)
|
|
12
|
+
}
|
|
13
|
+
} else if typeof(value) == #function {
|
|
14
|
+
let compute: unit => string = Obj.magic(value)
|
|
15
|
+
View.computedAttr(key, compute)
|
|
16
|
+
} else if typeof(value) == #object {
|
|
17
|
+
let signal: Signal.t<string> = Obj.magic(value)
|
|
18
|
+
View.signalAttr(key, signal)
|
|
19
|
+
} else {
|
|
20
|
+
let value: string = Obj.magic(value)
|
|
21
|
+
View.attr(key, value)
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
let toBoolAttr = (key: string, value: 'a): (string, View.attrValue) => {
|
|
26
|
+
if isReactiveProp(value) {
|
|
27
|
+
let prop: Prop.t<bool> = Obj.magic(value)
|
|
28
|
+
switch prop {
|
|
29
|
+
| Static(value) => View.attr(key, RuntimeAttr.boolToString(value))
|
|
30
|
+
| Reactive(signal) => {
|
|
31
|
+
let stringSignal = Computed.make(() => RuntimeAttr.boolToString(Signal.get(signal)))
|
|
32
|
+
View.signalAttr(key, stringSignal)
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
} else if typeof(value) == #function {
|
|
36
|
+
let compute: unit => bool = Obj.magic(value)
|
|
37
|
+
View.computedAttr(key, () => RuntimeAttr.boolToString(compute()))
|
|
38
|
+
} else if typeof(value) == #object {
|
|
39
|
+
let signal: Signal.t<bool> = Obj.magic(value)
|
|
40
|
+
let stringSignal = Computed.make(() => RuntimeAttr.boolToString(Signal.get(signal)))
|
|
41
|
+
View.signalAttr(key, stringSignal)
|
|
42
|
+
} else {
|
|
43
|
+
let value: bool = Obj.magic(value)
|
|
44
|
+
View.attr(key, RuntimeAttr.boolToString(value))
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
|
+
|
|
3
|
+
import * as View$Xote from "./View.res.mjs";
|
|
4
|
+
import * as Signal$Xote from "./Signal.res.mjs";
|
|
5
|
+
import * as Computed$Xote from "./Computed.res.mjs";
|
|
6
|
+
import * as RuntimeAttr$Xote from "./RuntimeAttr.res.mjs";
|
|
7
|
+
|
|
8
|
+
function isReactiveProp(value) {
|
|
9
|
+
return (value && typeof value === 'object' && ('TAG' in value) && (value.TAG === 'Static' || value.TAG === 'Reactive'));
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function toStringAttr(key, value) {
|
|
13
|
+
if (isReactiveProp(value)) {
|
|
14
|
+
if (value.TAG === "Reactive") {
|
|
15
|
+
return View$Xote.signalAttr(key, value._0);
|
|
16
|
+
} else {
|
|
17
|
+
return View$Xote.attr(key, value._0);
|
|
18
|
+
}
|
|
19
|
+
} else if (typeof value === "function") {
|
|
20
|
+
return View$Xote.computedAttr(key, value);
|
|
21
|
+
} else if (typeof value === "object") {
|
|
22
|
+
return View$Xote.signalAttr(key, value);
|
|
23
|
+
} else {
|
|
24
|
+
return View$Xote.attr(key, value);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function toBoolAttr(key, value) {
|
|
29
|
+
if (isReactiveProp(value)) {
|
|
30
|
+
if (value.TAG !== "Reactive") {
|
|
31
|
+
return View$Xote.attr(key, RuntimeAttr$Xote.boolToString(value._0));
|
|
32
|
+
}
|
|
33
|
+
let signal = value._0;
|
|
34
|
+
let stringSignal = Computed$Xote.make(() => RuntimeAttr$Xote.boolToString(Signal$Xote.get(signal)), undefined, undefined);
|
|
35
|
+
return View$Xote.signalAttr(key, stringSignal);
|
|
36
|
+
}
|
|
37
|
+
if (typeof value === "function") {
|
|
38
|
+
return View$Xote.computedAttr(key, () => RuntimeAttr$Xote.boolToString(value()));
|
|
39
|
+
}
|
|
40
|
+
if (typeof value !== "object") {
|
|
41
|
+
return View$Xote.attr(key, RuntimeAttr$Xote.boolToString(value));
|
|
42
|
+
}
|
|
43
|
+
let stringSignal$1 = Computed$Xote.make(() => RuntimeAttr$Xote.boolToString(Signal$Xote.get(value)), undefined, undefined);
|
|
44
|
+
return View$Xote.signalAttr(key, stringSignal$1);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export {
|
|
48
|
+
isReactiveProp,
|
|
49
|
+
toStringAttr,
|
|
50
|
+
toBoolAttr,
|
|
51
|
+
}
|
|
52
|
+
/* View-Xote Not a pure module */
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
type owner = {
|
|
2
|
+
disposers: array<Effect.disposer>,
|
|
3
|
+
mutable computeds: array<Obj.t>,
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
let currentOwner: ref<option<owner>> = ref(None)
|
|
7
|
+
|
|
8
|
+
let createOwner = (): owner => {
|
|
9
|
+
disposers: [],
|
|
10
|
+
computeds: [],
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
let runWithOwner = (owner: owner, fn: unit => 'a): 'a => {
|
|
14
|
+
let previousOwner = currentOwner.contents
|
|
15
|
+
currentOwner := Some(owner)
|
|
16
|
+
let result = fn()
|
|
17
|
+
currentOwner := previousOwner
|
|
18
|
+
result
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
let addDisposer = (owner: owner, disposer: Effect.disposer): unit => {
|
|
22
|
+
owner.disposers->Array.push(disposer)->ignore
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
let disposeOwner = (owner: owner): unit => {
|
|
26
|
+
owner.disposers->Array.forEach(disposer => disposer.dispose())
|
|
27
|
+
|
|
28
|
+
owner.computeds->Array.forEach(computed => {
|
|
29
|
+
let c: Signal.t<Obj.t> = Obj.magic(computed)
|
|
30
|
+
Computed.dispose(c)
|
|
31
|
+
})
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
@warning("-27")
|
|
35
|
+
let setOwner = (element: Dom.element, owner: owner): unit => {
|
|
36
|
+
%raw(`element["__xote_owner__"] = owner`)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
@warning("-27")
|
|
40
|
+
let getOwner = (element: Dom.element): option<owner> => {
|
|
41
|
+
let owner: Nullable.t<owner> = %raw(`element["__xote_owner__"]`)
|
|
42
|
+
owner->Nullable.toOption
|
|
43
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
|
+
|
|
3
|
+
import * as Computed$Xote from "./Computed.res.mjs";
|
|
4
|
+
import * as Primitive_option from "@rescript/runtime/lib/es6/Primitive_option.js";
|
|
5
|
+
|
|
6
|
+
let currentOwner = {
|
|
7
|
+
contents: undefined
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
function createOwner() {
|
|
11
|
+
return {
|
|
12
|
+
disposers: [],
|
|
13
|
+
computeds: []
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function runWithOwner(owner, fn) {
|
|
18
|
+
let previousOwner = currentOwner.contents;
|
|
19
|
+
currentOwner.contents = owner;
|
|
20
|
+
let result = fn();
|
|
21
|
+
currentOwner.contents = previousOwner;
|
|
22
|
+
return result;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function addDisposer(owner, disposer) {
|
|
26
|
+
owner.disposers.push(disposer);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function disposeOwner(owner) {
|
|
30
|
+
owner.disposers.forEach(disposer => disposer.dispose());
|
|
31
|
+
owner.computeds.forEach(Computed$Xote.dispose);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function setOwner(element, owner) {
|
|
35
|
+
((element["__xote_owner__"] = owner));
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function getOwner(element) {
|
|
39
|
+
return Primitive_option.fromNullable((element["__xote_owner__"]));
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export {
|
|
43
|
+
currentOwner,
|
|
44
|
+
createOwner,
|
|
45
|
+
runWithOwner,
|
|
46
|
+
addDisposer,
|
|
47
|
+
disposeOwner,
|
|
48
|
+
setOwner,
|
|
49
|
+
getOwner,
|
|
50
|
+
}
|
|
51
|
+
/* No side effect */
|
package/src/SSR.res
CHANGED
|
@@ -1,62 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
* ============================================================================ */
|
|
4
|
-
|
|
5
|
-
module Html = {
|
|
6
|
-
/* Escape HTML special characters to prevent XSS */
|
|
7
|
-
let escape = (str: string): string => {
|
|
8
|
-
str
|
|
9
|
-
->String.replaceAll("&", "&")
|
|
10
|
-
->String.replaceAll("<", "<")
|
|
11
|
-
->String.replaceAll(">", ">")
|
|
12
|
-
->String.replaceAll("\"", """)
|
|
13
|
-
->String.replaceAll("'", "'")
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
/* Void elements that don't have closing tags */
|
|
17
|
-
let voidElements = [
|
|
18
|
-
"area",
|
|
19
|
-
"base",
|
|
20
|
-
"br",
|
|
21
|
-
"col",
|
|
22
|
-
"embed",
|
|
23
|
-
"hr",
|
|
24
|
-
"img",
|
|
25
|
-
"input",
|
|
26
|
-
"link",
|
|
27
|
-
"meta",
|
|
28
|
-
"param",
|
|
29
|
-
"source",
|
|
30
|
-
"track",
|
|
31
|
-
"wbr",
|
|
32
|
-
]
|
|
33
|
-
|
|
34
|
-
let isVoidElement = (tag: string): bool => {
|
|
35
|
-
voidElements->Array.includes(tag)
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/* ============================================================================
|
|
40
|
-
* Hydration Markers
|
|
41
|
-
* ============================================================================ */
|
|
42
|
-
|
|
43
|
-
module Markers = {
|
|
44
|
-
/* Markers for different reactive node types */
|
|
45
|
-
let signalTextStart = "<!--$-->"
|
|
46
|
-
let signalTextEnd = "<!--/$-->"
|
|
47
|
-
|
|
48
|
-
let signalFragmentStart = "<!--#-->"
|
|
49
|
-
let signalFragmentEnd = "<!--/#-->"
|
|
50
|
-
|
|
51
|
-
let keyedListStart = "<!--kl-->"
|
|
52
|
-
let keyedListEnd = "<!--/kl-->"
|
|
53
|
-
|
|
54
|
-
let keyedItemStart = (key: string): string => `<!--k:${key}-->`
|
|
55
|
-
let keyedItemEnd = "<!--/k-->"
|
|
56
|
-
|
|
57
|
-
let lazyComponentStart = "<!--lc-->"
|
|
58
|
-
let lazyComponentEnd = "<!--/lc-->"
|
|
59
|
-
}
|
|
1
|
+
module Html = RuntimeHtml
|
|
2
|
+
module Markers = RuntimeHydrationMarkers
|
|
60
3
|
|
|
61
4
|
/* ============================================================================
|
|
62
5
|
* Render Options
|
|
@@ -73,39 +16,26 @@ type renderOptions = {
|
|
|
73
16
|
|
|
74
17
|
module Attributes = {
|
|
75
18
|
/* Render a single attribute to string */
|
|
76
|
-
let renderAttr = ((key, value): (string,
|
|
19
|
+
let renderAttr = ((key, value): (string, View.attrValue)): string => {
|
|
77
20
|
let attrValue = switch value {
|
|
78
|
-
|
|
|
79
|
-
|
|
|
80
|
-
|
|
|
21
|
+
| View.Static(v) => v
|
|
22
|
+
| View.SignalValue(signal) => Signal.peek(signal)
|
|
23
|
+
| View.Compute(fn) => fn()
|
|
81
24
|
}
|
|
82
25
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
| "checked"
|
|
86
|
-
| "disabled"
|
|
87
|
-
| "required"
|
|
88
|
-
| "readonly"
|
|
89
|
-
| "multiple"
|
|
90
|
-
| "aria-hidden"
|
|
91
|
-
| "aria-expanded"
|
|
92
|
-
| "aria-selected"
|
|
93
|
-
| "draggable"
|
|
94
|
-
| "hidden"
|
|
95
|
-
| "contenteditable"
|
|
96
|
-
| "spellcheck"
|
|
97
|
-
| "autofocus" =>
|
|
98
|
-
if attrValue == "true" {
|
|
26
|
+
if RuntimeAttr.isBoolean(key) {
|
|
27
|
+
if RuntimeAttr.shouldRenderBoolean(attrValue) {
|
|
99
28
|
key
|
|
100
29
|
} else {
|
|
101
30
|
""
|
|
102
31
|
}
|
|
103
|
-
|
|
32
|
+
} else {
|
|
33
|
+
`${key}="${Html.escape(attrValue)}"`
|
|
104
34
|
}
|
|
105
35
|
}
|
|
106
36
|
|
|
107
37
|
/* Render all attributes to string */
|
|
108
|
-
let renderAttrs = (attrs: array<(string,
|
|
38
|
+
let renderAttrs = (attrs: array<(string, View.attrValue)>): string => {
|
|
109
39
|
let rendered =
|
|
110
40
|
attrs
|
|
111
41
|
->Array.map(renderAttr)
|
|
@@ -120,30 +50,32 @@ module Attributes = {
|
|
|
120
50
|
}
|
|
121
51
|
|
|
122
52
|
/* ============================================================================
|
|
123
|
-
*
|
|
53
|
+
* View Rendering
|
|
124
54
|
* ============================================================================ */
|
|
125
55
|
|
|
126
56
|
/* Render a virtual node to an HTML string */
|
|
127
|
-
let rec renderNodeToString = (node:
|
|
57
|
+
let rec renderNodeToString = (node: View.node): string => {
|
|
128
58
|
switch node {
|
|
129
|
-
|
|
|
59
|
+
| View.Text(content) => Html.escape(content)
|
|
130
60
|
|
|
131
|
-
|
|
|
61
|
+
| View.SignalText(signal) => {
|
|
132
62
|
/* Read current signal value and wrap with hydration markers */
|
|
133
63
|
let value = Signal.peek(signal)
|
|
134
64
|
Markers.signalTextStart ++ Html.escape(value) ++ Markers.signalTextEnd
|
|
135
65
|
}
|
|
136
66
|
|
|
137
|
-
|
|
|
67
|
+
| View.Fragment(children) => children->Array.map(renderNodeToString)->Array.join("")
|
|
138
68
|
|
|
139
|
-
|
|
|
69
|
+
| View.SignalFragment(signal) => {
|
|
140
70
|
/* Read current signal value and wrap with hydration markers */
|
|
141
71
|
let children = Signal.peek(signal)
|
|
142
72
|
let content = children->Array.map(renderNodeToString)->Array.join("")
|
|
143
73
|
Markers.signalFragmentStart ++ content ++ Markers.signalFragmentEnd
|
|
144
74
|
}
|
|
145
75
|
|
|
146
|
-
|
|
|
76
|
+
| View.Keyed({child, key: _, identity: _}) => renderNodeToString(child)
|
|
77
|
+
|
|
78
|
+
| View.Element({tag, attrs, children, events: _}) => {
|
|
147
79
|
let attrsStr = Attributes.renderAttrs(attrs)
|
|
148
80
|
|
|
149
81
|
if Html.isVoidElement(tag) {
|
|
@@ -154,13 +86,13 @@ let rec renderNodeToString = (node: Node.node): string => {
|
|
|
154
86
|
}
|
|
155
87
|
}
|
|
156
88
|
|
|
157
|
-
|
|
|
89
|
+
| View.LazyComponent(fn) => {
|
|
158
90
|
/* Execute the lazy component and render its result */
|
|
159
91
|
let childNode = fn()
|
|
160
92
|
Markers.lazyComponentStart ++ renderNodeToString(childNode) ++ Markers.lazyComponentEnd
|
|
161
93
|
}
|
|
162
94
|
|
|
163
|
-
|
|
|
95
|
+
| View.KeyedList({signal, keyFn, renderItem}) => {
|
|
164
96
|
let items = Signal.peek(signal)
|
|
165
97
|
let content =
|
|
166
98
|
items
|
|
@@ -181,7 +113,7 @@ let rec renderNodeToString = (node: Node.node): string => {
|
|
|
181
113
|
* ============================================================================ */
|
|
182
114
|
|
|
183
115
|
/* Render a component to an HTML string synchronously */
|
|
184
|
-
let renderToString = (component: unit =>
|
|
116
|
+
let renderToString = (component: unit => View.node, ~options: renderOptions={}): string => {
|
|
185
117
|
let _ = options /* Will be used for nonce/renderId in future phases */
|
|
186
118
|
let node = component()
|
|
187
119
|
renderNodeToString(node)
|
|
@@ -189,7 +121,7 @@ let renderToString = (component: unit => Node.node, ~options: renderOptions={}):
|
|
|
189
121
|
|
|
190
122
|
/* Render a component and wrap with a hydration root marker */
|
|
191
123
|
let renderToStringWithRoot = (
|
|
192
|
-
component: unit =>
|
|
124
|
+
component: unit => View.node,
|
|
193
125
|
~rootId: string="root",
|
|
194
126
|
~options: renderOptions={},
|
|
195
127
|
): string => {
|
|
@@ -219,7 +151,7 @@ let renderDocument = (
|
|
|
219
151
|
~styles: array<string>=[],
|
|
220
152
|
~stateScript: string="",
|
|
221
153
|
~nonce: option<string>=?,
|
|
222
|
-
component: unit =>
|
|
154
|
+
component: unit => View.node,
|
|
223
155
|
): string => {
|
|
224
156
|
let content = renderToString(component)
|
|
225
157
|
let hydrationScript = generateHydrationScript(~nonce?)
|