@typed/template 0.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/LICENSE +21 -0
- package/README.md +5 -0
- package/dist/cjs/Directive.js +76 -0
- package/dist/cjs/Directive.js.map +1 -0
- package/dist/cjs/ElementRef.js +83 -0
- package/dist/cjs/ElementRef.js.map +1 -0
- package/dist/cjs/ElementSource.js +244 -0
- package/dist/cjs/ElementSource.js.map +1 -0
- package/dist/cjs/Entry.js +6 -0
- package/dist/cjs/Entry.js.map +1 -0
- package/dist/cjs/EventHandler.js +65 -0
- package/dist/cjs/EventHandler.js.map +1 -0
- package/dist/cjs/Html.js +169 -0
- package/dist/cjs/Html.js.map +1 -0
- package/dist/cjs/HtmlChunk.js +257 -0
- package/dist/cjs/HtmlChunk.js.map +1 -0
- package/dist/cjs/Hydrate.js +49 -0
- package/dist/cjs/Hydrate.js.map +1 -0
- package/dist/cjs/Many.js +45 -0
- package/dist/cjs/Many.js.map +1 -0
- package/dist/cjs/Meta.js +37 -0
- package/dist/cjs/Meta.js.map +1 -0
- package/dist/cjs/Parser.js +331 -0
- package/dist/cjs/Parser.js.map +1 -0
- package/dist/cjs/Part.js +6 -0
- package/dist/cjs/Part.js.map +1 -0
- package/dist/cjs/Placeholder.js +38 -0
- package/dist/cjs/Placeholder.js.map +1 -0
- package/dist/cjs/Platform.js +64 -0
- package/dist/cjs/Platform.js.map +1 -0
- package/dist/cjs/Render.js +39 -0
- package/dist/cjs/Render.js.map +1 -0
- package/dist/cjs/RenderContext.js +130 -0
- package/dist/cjs/RenderContext.js.map +1 -0
- package/dist/cjs/RenderEvent.js +44 -0
- package/dist/cjs/RenderEvent.js.map +1 -0
- package/dist/cjs/RenderTemplate.js +41 -0
- package/dist/cjs/RenderTemplate.js.map +1 -0
- package/dist/cjs/Renderable.js +6 -0
- package/dist/cjs/Renderable.js.map +1 -0
- package/dist/cjs/Template.js +266 -0
- package/dist/cjs/Template.js.map +1 -0
- package/dist/cjs/TemplateInstance.js +51 -0
- package/dist/cjs/TemplateInstance.js.map +1 -0
- package/dist/cjs/Test.js +90 -0
- package/dist/cjs/Test.js.map +1 -0
- package/dist/cjs/Token.js +270 -0
- package/dist/cjs/Token.js.map +1 -0
- package/dist/cjs/Tokenizer.js +18 -0
- package/dist/cjs/Tokenizer.js.map +1 -0
- package/dist/cjs/Vitest.js +44 -0
- package/dist/cjs/Vitest.js.map +1 -0
- package/dist/cjs/index.js +147 -0
- package/dist/cjs/index.js.map +1 -0
- package/dist/cjs/internal/HydrateContext.js +13 -0
- package/dist/cjs/internal/HydrateContext.js.map +1 -0
- package/dist/cjs/internal/browser.js +109 -0
- package/dist/cjs/internal/browser.js.map +1 -0
- package/dist/cjs/internal/chunks.js +54 -0
- package/dist/cjs/internal/chunks.js.map +1 -0
- package/dist/cjs/internal/errors.js +23 -0
- package/dist/cjs/internal/errors.js.map +1 -0
- package/dist/cjs/internal/hydrate.js +197 -0
- package/dist/cjs/internal/hydrate.js.map +1 -0
- package/dist/cjs/internal/indexRefCounter.js +32 -0
- package/dist/cjs/internal/indexRefCounter.js.map +1 -0
- package/dist/cjs/internal/module-augmentation.js +6 -0
- package/dist/cjs/internal/module-augmentation.js.map +1 -0
- package/dist/cjs/internal/parser.js +492 -0
- package/dist/cjs/internal/parser.js.map +1 -0
- package/dist/cjs/internal/parts.js +350 -0
- package/dist/cjs/internal/parts.js.map +1 -0
- package/dist/cjs/internal/readAttribute.js +34 -0
- package/dist/cjs/internal/readAttribute.js.map +1 -0
- package/dist/cjs/internal/render.js +332 -0
- package/dist/cjs/internal/render.js.map +1 -0
- package/dist/cjs/internal/server.js +219 -0
- package/dist/cjs/internal/server.js.map +1 -0
- package/dist/cjs/internal/tokenizer.js +264 -0
- package/dist/cjs/internal/tokenizer.js.map +1 -0
- package/dist/cjs/internal/utils.js +68 -0
- package/dist/cjs/internal/utils.js.map +1 -0
- package/dist/dts/Directive.d.ts +70 -0
- package/dist/dts/Directive.d.ts.map +1 -0
- package/dist/dts/ElementRef.d.ts +40 -0
- package/dist/dts/ElementRef.d.ts.map +1 -0
- package/dist/dts/ElementSource.d.ts +72 -0
- package/dist/dts/ElementSource.d.ts.map +1 -0
- package/dist/dts/Entry.d.ts +26 -0
- package/dist/dts/Entry.d.ts.map +1 -0
- package/dist/dts/EventHandler.d.ts +61 -0
- package/dist/dts/EventHandler.d.ts.map +1 -0
- package/dist/dts/Html.d.ts +17 -0
- package/dist/dts/Html.d.ts.map +1 -0
- package/dist/dts/HtmlChunk.d.ts +56 -0
- package/dist/dts/HtmlChunk.d.ts.map +1 -0
- package/dist/dts/Hydrate.d.ts +20 -0
- package/dist/dts/Hydrate.d.ts.map +1 -0
- package/dist/dts/Many.d.ts +32 -0
- package/dist/dts/Many.d.ts.map +1 -0
- package/dist/dts/Meta.d.ts +24 -0
- package/dist/dts/Meta.d.ts.map +1 -0
- package/dist/dts/Parser.d.ts +16 -0
- package/dist/dts/Parser.d.ts.map +1 -0
- package/dist/dts/Part.d.ts +147 -0
- package/dist/dts/Part.d.ts.map +1 -0
- package/dist/dts/Placeholder.d.ts +51 -0
- package/dist/dts/Placeholder.d.ts.map +1 -0
- package/dist/dts/Platform.d.ts +23 -0
- package/dist/dts/Platform.d.ts.map +1 -0
- package/dist/dts/Render.d.ts +23 -0
- package/dist/dts/Render.d.ts.map +1 -0
- package/dist/dts/RenderContext.d.ts +88 -0
- package/dist/dts/RenderContext.d.ts.map +1 -0
- package/dist/dts/RenderEvent.d.ts +37 -0
- package/dist/dts/RenderEvent.d.ts.map +1 -0
- package/dist/dts/RenderTemplate.d.ts +38 -0
- package/dist/dts/RenderTemplate.d.ts.map +1 -0
- package/dist/dts/Renderable.d.ts +28 -0
- package/dist/dts/Renderable.d.ts.map +1 -0
- package/dist/dts/Template.d.ts +218 -0
- package/dist/dts/Template.d.ts.map +1 -0
- package/dist/dts/TemplateInstance.d.ts +32 -0
- package/dist/dts/TemplateInstance.d.ts.map +1 -0
- package/dist/dts/Test.d.ts +58 -0
- package/dist/dts/Test.d.ts.map +1 -0
- package/dist/dts/Token.d.ts +202 -0
- package/dist/dts/Token.d.ts.map +1 -0
- package/dist/dts/Tokenizer.d.ts +6 -0
- package/dist/dts/Tokenizer.d.ts.map +1 -0
- package/dist/dts/Vitest.d.ts +28 -0
- package/dist/dts/Vitest.d.ts.map +1 -0
- package/dist/dts/index.d.ts +65 -0
- package/dist/dts/index.d.ts.map +1 -0
- package/dist/dts/internal/HydrateContext.d.ts +2 -0
- package/dist/dts/internal/HydrateContext.d.ts.map +1 -0
- package/dist/dts/internal/browser.d.ts +8 -0
- package/dist/dts/internal/browser.d.ts.map +1 -0
- package/dist/dts/internal/chunks.d.ts +22 -0
- package/dist/dts/internal/chunks.d.ts.map +1 -0
- package/dist/dts/internal/errors.d.ts +9 -0
- package/dist/dts/internal/errors.d.ts.map +1 -0
- package/dist/dts/internal/hydrate.d.ts +37 -0
- package/dist/dts/internal/hydrate.d.ts.map +1 -0
- package/dist/dts/internal/indexRefCounter.d.ts +6 -0
- package/dist/dts/internal/indexRefCounter.d.ts.map +1 -0
- package/dist/dts/internal/module-augmentation.d.ts +36 -0
- package/dist/dts/internal/module-augmentation.d.ts.map +1 -0
- package/dist/dts/internal/parser.d.ts +12 -0
- package/dist/dts/internal/parser.d.ts.map +1 -0
- package/dist/dts/internal/parts.d.ts +304 -0
- package/dist/dts/internal/parts.d.ts.map +1 -0
- package/dist/dts/internal/readAttribute.d.ts +9 -0
- package/dist/dts/internal/readAttribute.d.ts.map +1 -0
- package/dist/dts/internal/render.d.ts +30 -0
- package/dist/dts/internal/render.d.ts.map +1 -0
- package/dist/dts/internal/server.d.ts +31 -0
- package/dist/dts/internal/server.d.ts.map +1 -0
- package/dist/dts/internal/tokenizer.d.ts +3 -0
- package/dist/dts/internal/tokenizer.d.ts.map +1 -0
- package/dist/dts/internal/utils.d.ts +15 -0
- package/dist/dts/internal/utils.d.ts.map +1 -0
- package/dist/esm/Directive.js +64 -0
- package/dist/esm/Directive.js.map +1 -0
- package/dist/esm/ElementRef.js +72 -0
- package/dist/esm/ElementRef.js.map +1 -0
- package/dist/esm/ElementSource.js +237 -0
- package/dist/esm/ElementSource.js.map +1 -0
- package/dist/esm/Entry.js +2 -0
- package/dist/esm/Entry.js.map +1 -0
- package/dist/esm/EventHandler.js +52 -0
- package/dist/esm/EventHandler.js.map +1 -0
- package/dist/esm/Html.js +167 -0
- package/dist/esm/Html.js.map +1 -0
- package/dist/esm/HtmlChunk.js +274 -0
- package/dist/esm/HtmlChunk.js.map +1 -0
- package/dist/esm/Hydrate.js +37 -0
- package/dist/esm/Hydrate.js.map +1 -0
- package/dist/esm/Many.js +33 -0
- package/dist/esm/Many.js.map +1 -0
- package/dist/esm/Meta.js +29 -0
- package/dist/esm/Meta.js.map +1 -0
- package/dist/esm/Parser.js +342 -0
- package/dist/esm/Parser.js.map +1 -0
- package/dist/esm/Part.js +5 -0
- package/dist/esm/Part.js.map +1 -0
- package/dist/esm/Placeholder.js +30 -0
- package/dist/esm/Placeholder.js.map +1 -0
- package/dist/esm/Platform.js +41 -0
- package/dist/esm/Platform.js.map +1 -0
- package/dist/esm/Render.js +27 -0
- package/dist/esm/Render.js.map +1 -0
- package/dist/esm/RenderContext.js +113 -0
- package/dist/esm/RenderContext.js.map +1 -0
- package/dist/esm/RenderEvent.js +36 -0
- package/dist/esm/RenderEvent.js.map +1 -0
- package/dist/esm/RenderTemplate.js +26 -0
- package/dist/esm/RenderTemplate.js.map +1 -0
- package/dist/esm/Renderable.js +2 -0
- package/dist/esm/Renderable.js.map +1 -0
- package/dist/esm/Template.js +239 -0
- package/dist/esm/Template.js.map +1 -0
- package/dist/esm/TemplateInstance.js +43 -0
- package/dist/esm/TemplateInstance.js.map +1 -0
- package/dist/esm/Test.js +68 -0
- package/dist/esm/Test.js.map +1 -0
- package/dist/esm/Token.js +264 -0
- package/dist/esm/Token.js.map +1 -0
- package/dist/esm/Tokenizer.js +9 -0
- package/dist/esm/Tokenizer.js.map +1 -0
- package/dist/esm/Vitest.js +29 -0
- package/dist/esm/Vitest.js.map +1 -0
- package/dist/esm/index.js +65 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/internal/HydrateContext.js +7 -0
- package/dist/esm/internal/HydrateContext.js.map +1 -0
- package/dist/esm/internal/browser.js +102 -0
- package/dist/esm/internal/browser.js.map +1 -0
- package/dist/esm/internal/chunks.js +47 -0
- package/dist/esm/internal/chunks.js.map +1 -0
- package/dist/esm/internal/errors.js +15 -0
- package/dist/esm/internal/errors.js.map +1 -0
- package/dist/esm/internal/hydrate.js +165 -0
- package/dist/esm/internal/hydrate.js.map +1 -0
- package/dist/esm/internal/indexRefCounter.js +24 -0
- package/dist/esm/internal/indexRefCounter.js.map +1 -0
- package/dist/esm/internal/module-augmentation.js +2 -0
- package/dist/esm/internal/module-augmentation.js.map +1 -0
- package/dist/esm/internal/parser.js +493 -0
- package/dist/esm/internal/parser.js.map +1 -0
- package/dist/esm/internal/parts.js +291 -0
- package/dist/esm/internal/parts.js.map +1 -0
- package/dist/esm/internal/readAttribute.js +24 -0
- package/dist/esm/internal/readAttribute.js.map +1 -0
- package/dist/esm/internal/render.js +329 -0
- package/dist/esm/internal/render.js.map +1 -0
- package/dist/esm/internal/server.js +174 -0
- package/dist/esm/internal/server.js.map +1 -0
- package/dist/esm/internal/tokenizer.js +296 -0
- package/dist/esm/internal/tokenizer.js.map +1 -0
- package/dist/esm/internal/utils.js +52 -0
- package/dist/esm/internal/utils.js.map +1 -0
- package/dist/esm/package.json +4 -0
- package/package.json +242 -0
- package/src/Directive.ts +114 -0
- package/src/ElementRef.ts +123 -0
- package/src/ElementSource.ts +417 -0
- package/src/Entry.ts +28 -0
- package/src/EventHandler.ts +104 -0
- package/src/Html.ts +258 -0
- package/src/HtmlChunk.ts +346 -0
- package/src/Hydrate.ts +53 -0
- package/src/Many.ts +128 -0
- package/src/Meta.ts +32 -0
- package/src/Parser.ts +457 -0
- package/src/Part.ts +186 -0
- package/src/Placeholder.ts +70 -0
- package/src/Platform.ts +71 -0
- package/src/Render.ts +45 -0
- package/src/RenderContext.ts +221 -0
- package/src/RenderEvent.ts +67 -0
- package/src/RenderTemplate.ts +88 -0
- package/src/Renderable.ts +34 -0
- package/src/Template.ts +284 -0
- package/src/TemplateInstance.ts +83 -0
- package/src/Test.ts +151 -0
- package/src/Token.ts +269 -0
- package/src/Tokenizer.ts +10 -0
- package/src/Vitest.ts +61 -0
- package/src/index.ts +66 -0
- package/src/internal/HydrateContext.ts +23 -0
- package/src/internal/browser.ts +132 -0
- package/src/internal/chunks.ts +73 -0
- package/src/internal/errors.ts +11 -0
- package/src/internal/external.d.ts +11 -0
- package/src/internal/hydrate.ts +262 -0
- package/src/internal/indexRefCounter.ts +33 -0
- package/src/internal/module-augmentation.ts +48 -0
- package/src/internal/parser.ts +637 -0
- package/src/internal/parts.ts +527 -0
- package/src/internal/readAttribute.ts +28 -0
- package/src/internal/render.ts +529 -0
- package/src/internal/server.ts +293 -0
- package/src/internal/tokenizer.ts +338 -0
- package/src/internal/utils.ts +73 -0
package/src/Html.ts
ADDED
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @since 1.0.0
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import * as Fx from "@typed/fx/Fx"
|
|
6
|
+
import { Sink } from "@typed/fx/Sink"
|
|
7
|
+
import { TypeId } from "@typed/fx/TypeId"
|
|
8
|
+
import type { Rendered } from "@typed/wire"
|
|
9
|
+
import { Effect, Option } from "effect"
|
|
10
|
+
import { join } from "effect/ReadonlyArray"
|
|
11
|
+
import type * as Scope from "effect/Scope"
|
|
12
|
+
import { isDirective } from "./Directive"
|
|
13
|
+
import * as ElementRef from "./ElementRef"
|
|
14
|
+
import type { ServerEntry } from "./Entry"
|
|
15
|
+
import type { HtmlChunk, PartChunk, SparsePartChunk, TextChunk } from "./HtmlChunk"
|
|
16
|
+
import { templateToHtmlChunks } from "./HtmlChunk"
|
|
17
|
+
import { parse } from "./internal/parser"
|
|
18
|
+
import { partNodeToPart } from "./internal/server"
|
|
19
|
+
import { TEXT_START, TYPED_END, TYPED_HOLE, TYPED_START } from "./Meta"
|
|
20
|
+
import type { Placeholder } from "./Placeholder"
|
|
21
|
+
import type { Renderable } from "./Renderable"
|
|
22
|
+
import { RenderContext } from "./RenderContext"
|
|
23
|
+
import { HtmlRenderEvent, isRenderEvent } from "./RenderEvent"
|
|
24
|
+
import type { RenderEvent } from "./RenderEvent"
|
|
25
|
+
import { RenderTemplate } from "./RenderTemplate"
|
|
26
|
+
import { TemplateInstance } from "./TemplateInstance"
|
|
27
|
+
|
|
28
|
+
const toHtml = (r: RenderEvent) => (r as HtmlRenderEvent).html
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* @since 1.0.0
|
|
32
|
+
*/
|
|
33
|
+
export function renderToHtml<R, E>(
|
|
34
|
+
fx: Fx.Fx<R, E, RenderEvent>
|
|
35
|
+
): Fx.Fx<Exclude<R, RenderTemplate> | RenderContext, E, string> {
|
|
36
|
+
return Fx.fromFxEffect(
|
|
37
|
+
RenderContext.with((ctx) =>
|
|
38
|
+
fx.pipe(
|
|
39
|
+
Fx.provide(RenderTemplate.layer(renderHtml(ctx))),
|
|
40
|
+
Fx.map(toHtml),
|
|
41
|
+
Fx.startWith(TYPED_START),
|
|
42
|
+
Fx.endWith(TYPED_END)
|
|
43
|
+
)
|
|
44
|
+
)
|
|
45
|
+
)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* @since 1.0.0
|
|
50
|
+
*/
|
|
51
|
+
export function renderToHtmlString<R, E>(
|
|
52
|
+
fx: Fx.Fx<R, E, RenderEvent>
|
|
53
|
+
): Effect.Effect<Exclude<R, RenderTemplate> | RenderContext, E, string> {
|
|
54
|
+
return Effect.map(Fx.toReadonlyArray(renderToHtml(fx)), join(""))
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function renderHtml(ctx: RenderContext) {
|
|
58
|
+
return <Values extends ReadonlyArray<Renderable<any, any>>, T extends Rendered = Rendered>(
|
|
59
|
+
templateStrings: TemplateStringsArray,
|
|
60
|
+
values: Values,
|
|
61
|
+
providedRef?: ElementRef.ElementRef<T>
|
|
62
|
+
): Effect.Effect<
|
|
63
|
+
Scope.Scope | Placeholder.Context<readonly [] extends Values ? never : Values[number]>,
|
|
64
|
+
never,
|
|
65
|
+
TemplateInstance<
|
|
66
|
+
Placeholder.Error<Values[number]>,
|
|
67
|
+
T
|
|
68
|
+
>
|
|
69
|
+
> => {
|
|
70
|
+
return Effect.gen(function*(_) {
|
|
71
|
+
const ref = providedRef || (yield* _(ElementRef.make()))
|
|
72
|
+
const entry = getServerEntry(templateStrings, ctx.templateCache)
|
|
73
|
+
|
|
74
|
+
if (values.length === 0) {
|
|
75
|
+
return TemplateInstance(Fx.succeed(HtmlRenderEvent((entry.chunks[0] as TextChunk).value)), ref as any)
|
|
76
|
+
} else {
|
|
77
|
+
return TemplateInstance(
|
|
78
|
+
Fx.filter(
|
|
79
|
+
Fx.mergeBuffer(
|
|
80
|
+
entry.chunks.map((chunk) =>
|
|
81
|
+
renderChunk<
|
|
82
|
+
Placeholder.Context<readonly [] extends Values ? never : Values[number]>,
|
|
83
|
+
Placeholder.Error<Values[number]>
|
|
84
|
+
>(chunk, values)
|
|
85
|
+
)
|
|
86
|
+
),
|
|
87
|
+
(x) => (x.valueOf() as string).length > 0
|
|
88
|
+
) as any,
|
|
89
|
+
ref as any
|
|
90
|
+
)
|
|
91
|
+
}
|
|
92
|
+
})
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function renderChunk<R, E>(
|
|
97
|
+
chunk: HtmlChunk,
|
|
98
|
+
values: ReadonlyArray<Renderable<any, any>>
|
|
99
|
+
): Fx.Fx<R, E, RenderEvent> {
|
|
100
|
+
if (chunk._tag === "text") {
|
|
101
|
+
return Fx.succeed(HtmlRenderEvent(chunk.value))
|
|
102
|
+
} else if (chunk._tag === "part") {
|
|
103
|
+
return renderPart<R, E>(chunk, values)
|
|
104
|
+
} else {
|
|
105
|
+
return renderSparsePart<R, E>(chunk, values) as Fx.Fx<R, E, RenderEvent>
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function renderNode<R, E>(renderable: Renderable<any, any>): Fx.Fx<R, E, RenderEvent> {
|
|
110
|
+
switch (typeof renderable) {
|
|
111
|
+
case "string":
|
|
112
|
+
case "number":
|
|
113
|
+
case "boolean":
|
|
114
|
+
case "bigint":
|
|
115
|
+
return Fx.succeed(HtmlRenderEvent(TEXT_START + renderable.toString()))
|
|
116
|
+
case "undefined":
|
|
117
|
+
case "object":
|
|
118
|
+
return renderObject(renderable)
|
|
119
|
+
default:
|
|
120
|
+
return Fx.empty
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function renderObject<R, E>(renderable: object | null | undefined) {
|
|
125
|
+
if (renderable === null || renderable === undefined) {
|
|
126
|
+
return Fx.succeed(HtmlRenderEvent(TEXT_START))
|
|
127
|
+
} else if (Array.isArray(renderable)) {
|
|
128
|
+
return Fx.mergeBuffer(renderable.map(renderNode)) as any
|
|
129
|
+
} else if (Fx.isFx<R, E, Renderable>(renderable)) {
|
|
130
|
+
return Fx.concatMap(takeOneIfNotRenderEvent(renderable), renderNode as any)
|
|
131
|
+
} else if (Effect.isEffect(renderable)) {
|
|
132
|
+
return Fx.switchMap(Fx.fromEffect(renderable as Effect.Effect<R, E, Renderable>), renderNode<R, E>)
|
|
133
|
+
} else if (isRenderEvent(renderable)) {
|
|
134
|
+
return Fx.succeed(renderable)
|
|
135
|
+
} else {
|
|
136
|
+
return Fx.empty
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function renderPart<R, E>(
|
|
141
|
+
chunk: PartChunk,
|
|
142
|
+
values: ReadonlyArray<Renderable<any, any>>
|
|
143
|
+
): Fx.Fx<R, E, RenderEvent> {
|
|
144
|
+
const { node, render } = chunk
|
|
145
|
+
const renderable: Renderable<any, any> = values[node.index]
|
|
146
|
+
|
|
147
|
+
// Refs and events are not rendered into HTML
|
|
148
|
+
if (isDirective<R, E>(renderable)) {
|
|
149
|
+
return Fx.fromSink((sink: Sink<E, RenderEvent>) => {
|
|
150
|
+
const part = partNodeToPart(
|
|
151
|
+
node,
|
|
152
|
+
(value) => sink.onSuccess(HtmlRenderEvent(render(value)))
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
return Effect.catchAllCause(renderable(part), sink.onFailure)
|
|
156
|
+
})
|
|
157
|
+
} else if (node._tag === "node") {
|
|
158
|
+
return Fx.endWith(renderNode<R, E>(renderable), HtmlRenderEvent(TYPED_HOLE(node.index)))
|
|
159
|
+
} else {
|
|
160
|
+
const html = Fx.filterMap(Fx.take(unwrapRenderable<R, E>(renderable), 1), (value) => {
|
|
161
|
+
const s = render(value)
|
|
162
|
+
|
|
163
|
+
return s ? Option.some(HtmlRenderEvent(s)) : Option.none()
|
|
164
|
+
})
|
|
165
|
+
|
|
166
|
+
if (node._tag === "text-part") {
|
|
167
|
+
return Fx.endWith(Fx.startWith(html, HtmlRenderEvent(TEXT_START)), HtmlRenderEvent(TYPED_HOLE(node.index)))
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
return html
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function renderSparsePart<R, E>(
|
|
175
|
+
chunk: SparsePartChunk,
|
|
176
|
+
values: ReadonlyArray<Renderable<any, any>>
|
|
177
|
+
): Fx.Fx<R, E, RenderEvent> {
|
|
178
|
+
const { node, render } = chunk
|
|
179
|
+
|
|
180
|
+
return Fx.map(
|
|
181
|
+
Fx.take(
|
|
182
|
+
Fx.combine(
|
|
183
|
+
node.nodes.map((node) => {
|
|
184
|
+
if (node._tag === "text") return Fx.succeed(node.value)
|
|
185
|
+
|
|
186
|
+
const renderable: Renderable<any, any> = (values as any)[node.index]
|
|
187
|
+
|
|
188
|
+
if (isDirective<R, E>(renderable)) {
|
|
189
|
+
return Fx.fromSink<R, E, unknown>((sink: Sink<E, unknown>) =>
|
|
190
|
+
Effect.catchAllCause(
|
|
191
|
+
renderable(partNodeToPart(node, (value) => sink.onSuccess(value))),
|
|
192
|
+
sink.onFailure
|
|
193
|
+
)
|
|
194
|
+
)
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
return unwrapRenderable<R, E>(renderable)
|
|
198
|
+
})
|
|
199
|
+
),
|
|
200
|
+
1
|
|
201
|
+
),
|
|
202
|
+
(value) => HtmlRenderEvent(render(value))
|
|
203
|
+
)
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function takeOneIfNotRenderEvent<R, E, A>(fx: Fx.Fx<R, E, A>): Fx.Fx<R, E, A> {
|
|
207
|
+
return Fx.withEarlyExit(({ fork, sink }) =>
|
|
208
|
+
Fx.run(
|
|
209
|
+
fx,
|
|
210
|
+
Sink(
|
|
211
|
+
sink.onFailure,
|
|
212
|
+
(event) => isRenderEvent(event) ? sink.onSuccess(event) : Effect.zipRight(sink.onSuccess(event), sink.earlyExit)
|
|
213
|
+
)
|
|
214
|
+
).pipe(
|
|
215
|
+
fork,
|
|
216
|
+
Effect.fromFiberEffect
|
|
217
|
+
)
|
|
218
|
+
)
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function getServerEntry(
|
|
222
|
+
templateStrings: TemplateStringsArray,
|
|
223
|
+
templateCache: RenderContext["templateCache"]
|
|
224
|
+
): ServerEntry {
|
|
225
|
+
const cached = templateCache.get(templateStrings)
|
|
226
|
+
|
|
227
|
+
if (cached === undefined || cached._tag === "Browser") {
|
|
228
|
+
const template = parse(templateStrings)
|
|
229
|
+
|
|
230
|
+
const entry: ServerEntry = {
|
|
231
|
+
_tag: "Server",
|
|
232
|
+
template,
|
|
233
|
+
chunks: templateToHtmlChunks(template)
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
return entry
|
|
237
|
+
} else {
|
|
238
|
+
return cached
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function unwrapRenderable<R, E>(renderable: Renderable<any, any>): Fx.Fx<R, E, any> {
|
|
243
|
+
switch (typeof renderable) {
|
|
244
|
+
case "undefined":
|
|
245
|
+
case "object": {
|
|
246
|
+
if (renderable === null || renderable === undefined) return Fx.succeed(null)
|
|
247
|
+
else if (Array.isArray(renderable)) {
|
|
248
|
+
return Fx.combine(renderable.map(unwrapRenderable)) as any
|
|
249
|
+
} else if (TypeId in renderable) {
|
|
250
|
+
return renderable as any
|
|
251
|
+
} else if (Effect.EffectTypeId in renderable) {
|
|
252
|
+
return Fx.fromFxEffect(Effect.map(renderable as any, unwrapRenderable<any, any>))
|
|
253
|
+
} else return Fx.succeed(renderable as any)
|
|
254
|
+
}
|
|
255
|
+
default:
|
|
256
|
+
return Fx.succeed(renderable)
|
|
257
|
+
}
|
|
258
|
+
}
|
package/src/HtmlChunk.ts
ADDED
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @since 1.0.0
|
|
3
|
+
*/
|
|
4
|
+
import { TYPED_HASH } from "./Meta"
|
|
5
|
+
import type {
|
|
6
|
+
Attribute,
|
|
7
|
+
ElementNode,
|
|
8
|
+
Node,
|
|
9
|
+
PartNode,
|
|
10
|
+
SelfClosingElementNode,
|
|
11
|
+
SparseAttrNode,
|
|
12
|
+
SparseClassNameNode,
|
|
13
|
+
Template,
|
|
14
|
+
Text,
|
|
15
|
+
TextOnlyElement
|
|
16
|
+
} from "./Template"
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* @since 1.0.0
|
|
20
|
+
*/
|
|
21
|
+
export type HtmlChunk = TextChunk | PartChunk | SparsePartChunk
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* @since 1.0.0
|
|
25
|
+
*/
|
|
26
|
+
export class TextChunk {
|
|
27
|
+
readonly _tag = "text"
|
|
28
|
+
constructor(readonly value: string) {}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* @since 1.0.0
|
|
33
|
+
*/
|
|
34
|
+
export class PartChunk {
|
|
35
|
+
readonly _tag = "part"
|
|
36
|
+
|
|
37
|
+
constructor(
|
|
38
|
+
readonly node: PartNode,
|
|
39
|
+
readonly render: (value: unknown) => string
|
|
40
|
+
) {}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* @since 1.0.0
|
|
45
|
+
*/
|
|
46
|
+
export class SparsePartChunk {
|
|
47
|
+
readonly _tag = "sparse-part"
|
|
48
|
+
|
|
49
|
+
constructor(
|
|
50
|
+
readonly node: SparseAttrNode | SparseClassNameNode,
|
|
51
|
+
readonly render: (value: AttrValue) => string
|
|
52
|
+
) {}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* @since 1.0.0
|
|
57
|
+
*/
|
|
58
|
+
export type AttrValue = string | null | undefined | ReadonlyArray<AttrValue>
|
|
59
|
+
|
|
60
|
+
// TODO: Should we escape more things?
|
|
61
|
+
// TODO: We should manually optimize the text fusion
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* @since 1.0.0
|
|
65
|
+
*/
|
|
66
|
+
export function templateToHtmlChunks({ hash, nodes }: Template) {
|
|
67
|
+
return fuseTextChunks(nodes.flatMap((node) => nodeToHtmlChunk(node, hash)))
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function fuseTextChunks(chunks: Array<HtmlChunk>): ReadonlyArray<HtmlChunk> {
|
|
71
|
+
const output: Array<HtmlChunk> = []
|
|
72
|
+
|
|
73
|
+
for (let i = 0; i < chunks.length; i++) {
|
|
74
|
+
if (i > 0) {
|
|
75
|
+
const prevIndex = output.length - 1
|
|
76
|
+
const prev = output[prevIndex]
|
|
77
|
+
const curr = chunks[i]
|
|
78
|
+
|
|
79
|
+
if (prev._tag === "text" && curr._tag === "text") {
|
|
80
|
+
output[prevIndex] = new TextChunk(prev.value + curr.value)
|
|
81
|
+
} else {
|
|
82
|
+
output.push(curr)
|
|
83
|
+
}
|
|
84
|
+
} else {
|
|
85
|
+
output.push(chunks[i])
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return output
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
type NodeMap = {
|
|
93
|
+
readonly [K in Node["_tag"]]: (node: Extract<Node, { _tag: K }>, hash?: string) => Array<HtmlChunk>
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const nodeMap: NodeMap = {
|
|
97
|
+
element: elementToHtmlChunks,
|
|
98
|
+
node: (node) => [new PartChunk(node, String)],
|
|
99
|
+
"self-closing-element": selfClosingElementToHtmlChunks,
|
|
100
|
+
text: (node) => [textToHtmlChunks(node)],
|
|
101
|
+
"text-only-element": textOnlyElementToHtmlChunks,
|
|
102
|
+
comment: (node) => [new TextChunk(`<!--${node.value}-->`)],
|
|
103
|
+
"comment-part": (node) => [
|
|
104
|
+
new PartChunk(node, (value) => `<!--${value}-->`)
|
|
105
|
+
],
|
|
106
|
+
"sparse-comment": (node) => [
|
|
107
|
+
new TextChunk("<!--"),
|
|
108
|
+
...node.nodes.map((node) => {
|
|
109
|
+
if (node._tag === "text") {
|
|
110
|
+
return textToHtmlChunks(node)
|
|
111
|
+
} else {
|
|
112
|
+
return new PartChunk(node, (value) => `${value}`)
|
|
113
|
+
}
|
|
114
|
+
}),
|
|
115
|
+
new TextChunk("-->")
|
|
116
|
+
]
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function nodeToHtmlChunk(node: Node, hash?: string): Array<HtmlChunk> {
|
|
120
|
+
return nodeMap[node._tag](node as any, hash)
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function elementToHtmlChunks(
|
|
124
|
+
{ attributes, children, tagName }: ElementNode,
|
|
125
|
+
hash?: string
|
|
126
|
+
): Array<HtmlChunk> {
|
|
127
|
+
if (attributes.length === 0) {
|
|
128
|
+
return [
|
|
129
|
+
new TextChunk(openTag(tagName, hash) + ">"),
|
|
130
|
+
...children.flatMap((c) => nodeToHtmlChunk(c)),
|
|
131
|
+
new TextChunk(closeTag(tagName))
|
|
132
|
+
]
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const chunks: Array<HtmlChunk> = [
|
|
136
|
+
new TextChunk(openTag(tagName, hash)),
|
|
137
|
+
...attributes.map((a) => attributeToHtmlChunk(a)),
|
|
138
|
+
new TextChunk(">"),
|
|
139
|
+
...children.flatMap((c) => nodeToHtmlChunk(c)),
|
|
140
|
+
new TextChunk(closeTag(tagName))
|
|
141
|
+
]
|
|
142
|
+
|
|
143
|
+
return chunks
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function selfClosingElementToHtmlChunks(
|
|
147
|
+
{ attributes, tagName }: SelfClosingElementNode,
|
|
148
|
+
hash?: string
|
|
149
|
+
): Array<HtmlChunk> {
|
|
150
|
+
if (attributes.length === 0) {
|
|
151
|
+
return [new TextChunk(openTag(tagName, hash) + "/>")]
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const chunks: Array<HtmlChunk> = [
|
|
155
|
+
new TextChunk(openTag(tagName, hash)),
|
|
156
|
+
...attributes.map((a) => attributeToHtmlChunk(a)),
|
|
157
|
+
new TextChunk(`/>`)
|
|
158
|
+
]
|
|
159
|
+
|
|
160
|
+
return chunks
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function textToHtmlChunks(text: Text): HtmlChunk {
|
|
164
|
+
return text._tag === "text" ? new TextChunk(text.value) : new PartChunk(text, String)
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function textOnlyElementToHtmlChunks(
|
|
168
|
+
{ attributes, children, tagName }: TextOnlyElement,
|
|
169
|
+
hash?: string
|
|
170
|
+
): Array<HtmlChunk> {
|
|
171
|
+
if (attributes.length === 0) {
|
|
172
|
+
return [
|
|
173
|
+
new TextChunk(openTag(tagName, hash) + ">"),
|
|
174
|
+
...children.map((c) => textToHtmlChunks(c)),
|
|
175
|
+
new TextChunk(closeTag(tagName))
|
|
176
|
+
]
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const chunks: Array<HtmlChunk> = [
|
|
180
|
+
new TextChunk(openTag(tagName, hash)),
|
|
181
|
+
...attributes.map((a) => attributeToHtmlChunk(a)),
|
|
182
|
+
new TextChunk(">"),
|
|
183
|
+
...children.map((c) => textToHtmlChunks(c)),
|
|
184
|
+
new TextChunk(closeTag(tagName))
|
|
185
|
+
]
|
|
186
|
+
|
|
187
|
+
return chunks
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
type AttrMap = {
|
|
191
|
+
[K in Attribute["_tag"]]: (attr: Extract<Attribute, { readonly _tag: K }>) => HtmlChunk
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const attrMap: AttrMap = {
|
|
195
|
+
attribute: (attr) => new TextChunk(` ${attr.name}="${attr.value}"`),
|
|
196
|
+
attr: (attr) => new PartChunk(attr, (value) => (value == null ? `` : ` ${attr.name}="${value}"`)),
|
|
197
|
+
boolean: (attr) => new TextChunk(" " + attr.name),
|
|
198
|
+
"boolean-part": (attr) => new PartChunk(attr, (value) => (value ? ` ${attr.name}` : "")),
|
|
199
|
+
"className-part": (attr) => new PartChunk(attr, (value) => (value ? ` class="${value}"` : "")),
|
|
200
|
+
data: (attr) =>
|
|
201
|
+
new PartChunk(attr, (value) => value == null ? `` : datasetToString(value as Readonly<Record<string, string>>)),
|
|
202
|
+
event: () => new TextChunk(""),
|
|
203
|
+
property: (attr) => new PartChunk(attr, (value) => (value == null ? `` : ` ${attr.name}="${escape(value)}"`)),
|
|
204
|
+
ref: () => new TextChunk(""),
|
|
205
|
+
"sparse-attr": (attr) =>
|
|
206
|
+
new SparsePartChunk(attr, (values) => {
|
|
207
|
+
return values == null
|
|
208
|
+
? ``
|
|
209
|
+
: ` ${attr.name}="${Array.isArray(values) ? values.filter(isString).join("") : values}"`
|
|
210
|
+
}),
|
|
211
|
+
"sparse-class-name": (attr) =>
|
|
212
|
+
new SparsePartChunk(attr, (values) => {
|
|
213
|
+
return values == null
|
|
214
|
+
? ``
|
|
215
|
+
: ` class="${Array.isArray(values) ? values.filter(isString).join(" ") : values}"`
|
|
216
|
+
}),
|
|
217
|
+
text: (attr) => new TextChunk(attr.value)
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function attributeToHtmlChunk(attr: Attribute): HtmlChunk {
|
|
221
|
+
return attrMap[attr._tag](attr as any)
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function isString(value: unknown): value is string {
|
|
225
|
+
return typeof value === "string"
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function datasetToString(dataset: Readonly<Record<string, string | undefined>>) {
|
|
229
|
+
const s = Object.entries(dataset)
|
|
230
|
+
.map(([key, value]) => (value === undefined ? `data-${key}` : `data-${key}="${value}"`))
|
|
231
|
+
.join(" ")
|
|
232
|
+
|
|
233
|
+
return s.length === 0 ? "" : " " + s
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function openTag(tagName: string, hash?: string): string {
|
|
237
|
+
if (hash === undefined) return `<${tagName}`
|
|
238
|
+
|
|
239
|
+
return `<${tagName} ${TYPED_HASH(hash)}`
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function closeTag(tagName: string): string {
|
|
243
|
+
return `</${tagName}>`
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* @since 1.0.0
|
|
248
|
+
*/
|
|
249
|
+
export function escape(s: unknown) {
|
|
250
|
+
switch (typeof s) {
|
|
251
|
+
case "string":
|
|
252
|
+
case "number":
|
|
253
|
+
case "boolean":
|
|
254
|
+
return escapeHtml(String(s))
|
|
255
|
+
default:
|
|
256
|
+
return escapeHtml(JSON.stringify(s))
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* @since 1.0.0
|
|
262
|
+
*/
|
|
263
|
+
export function unescape(s: string) {
|
|
264
|
+
const unescaped = unescapeHtml(s)
|
|
265
|
+
const couldBeJson = unescaped[0] === "[" || unescaped === "{"
|
|
266
|
+
if (couldBeJson) {
|
|
267
|
+
try {
|
|
268
|
+
return JSON.parse(unescaped)
|
|
269
|
+
} catch {
|
|
270
|
+
return unescaped
|
|
271
|
+
}
|
|
272
|
+
} else {
|
|
273
|
+
return unescaped
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
const unescapeHtmlRules = [
|
|
278
|
+
[/"/g, "\""],
|
|
279
|
+
[/'/g, "'"],
|
|
280
|
+
[/:/g, ":"],
|
|
281
|
+
[/</g, "<"],
|
|
282
|
+
[/>/g, ">"],
|
|
283
|
+
[/&/g, "&"]
|
|
284
|
+
] as const
|
|
285
|
+
|
|
286
|
+
const matchHtmlRegExp = /["'&<>]/
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* @since 1.0.0
|
|
290
|
+
*/
|
|
291
|
+
export function escapeHtml(str: string): string {
|
|
292
|
+
const match = matchHtmlRegExp.exec(str)
|
|
293
|
+
|
|
294
|
+
if (!match) {
|
|
295
|
+
return str
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
let escape
|
|
299
|
+
let html = ""
|
|
300
|
+
let index = 0
|
|
301
|
+
let lastIndex = 0
|
|
302
|
+
|
|
303
|
+
for (index = match.index; index < str.length; index++) {
|
|
304
|
+
switch (str.charCodeAt(index)) {
|
|
305
|
+
case 34: // "
|
|
306
|
+
escape = """
|
|
307
|
+
break
|
|
308
|
+
case 38: // &
|
|
309
|
+
escape = "&"
|
|
310
|
+
break
|
|
311
|
+
case 39: // '
|
|
312
|
+
escape = "'"
|
|
313
|
+
break
|
|
314
|
+
case 60: // <
|
|
315
|
+
escape = "<"
|
|
316
|
+
break
|
|
317
|
+
case 62: // >
|
|
318
|
+
escape = ">"
|
|
319
|
+
break
|
|
320
|
+
default:
|
|
321
|
+
continue
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
if (lastIndex !== index) {
|
|
325
|
+
html += str.substring(lastIndex, index)
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
lastIndex = index + 1
|
|
329
|
+
html += escape
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
return lastIndex !== index
|
|
333
|
+
? html + str.substring(lastIndex, index)
|
|
334
|
+
: html
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* @since 1.0.0
|
|
339
|
+
*/
|
|
340
|
+
export function unescapeHtml(html: string) {
|
|
341
|
+
for (const [from, to] of unescapeHtmlRules) {
|
|
342
|
+
html = html.replace(from, to)
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
return html
|
|
346
|
+
}
|
package/src/Hydrate.ts
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @since 1.0.0
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import * as Context from "@typed/context"
|
|
6
|
+
import { Document } from "@typed/dom/Document"
|
|
7
|
+
import { RootElement } from "@typed/dom/RootElement"
|
|
8
|
+
import * as Fx from "@typed/fx/Fx"
|
|
9
|
+
import * as Effect from "effect/Effect"
|
|
10
|
+
import * as Layer from "effect/Layer"
|
|
11
|
+
import { findRootParentChildNodes, hydrateTemplate } from "./internal/hydrate"
|
|
12
|
+
import { HydrateContext } from "./internal/HydrateContext"
|
|
13
|
+
import { attachRoot } from "./internal/render"
|
|
14
|
+
import type { ToRendered } from "./Render"
|
|
15
|
+
import { RenderContext } from "./RenderContext"
|
|
16
|
+
import { type RenderEvent } from "./RenderEvent"
|
|
17
|
+
import { RenderTemplate } from "./RenderTemplate"
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* @since 1.0.0
|
|
21
|
+
*/
|
|
22
|
+
export function hydrate<R, E, T extends RenderEvent | null>(
|
|
23
|
+
rendered: Fx.Fx<R, E, T>
|
|
24
|
+
): Fx.Fx<Exclude<R, RenderTemplate> | Document | RenderContext | RootElement, E, ToRendered<T>> {
|
|
25
|
+
return Fx.fromFxEffect(Effect.contextWith((context) => {
|
|
26
|
+
const [document, renderContext, { rootElement }] = Context.getMany(context, Document, RenderContext, RootElement)
|
|
27
|
+
const ctx: HydrateContext = {
|
|
28
|
+
where: findRootParentChildNodes(rootElement),
|
|
29
|
+
rootIndex: -1,
|
|
30
|
+
parentTemplate: null,
|
|
31
|
+
hydrate: true
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const layer = Layer.provideMerge(
|
|
35
|
+
HydrateContext.layer(ctx),
|
|
36
|
+
RenderTemplate.layer(hydrateTemplate(document, renderContext))
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
return Fx.provide(
|
|
40
|
+
Fx.mapEffect(rendered, (what) => attachRoot(renderContext.renderCache, rootElement, what)),
|
|
41
|
+
layer
|
|
42
|
+
)
|
|
43
|
+
})) as Fx.Fx<Exclude<R, RenderTemplate> | Document | RenderContext | RootElement, E, ToRendered<T>>
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* @since 1.0.0
|
|
48
|
+
*/
|
|
49
|
+
export function hydrateLayer<R, E, T extends RenderEvent | null>(
|
|
50
|
+
rendered: Fx.Fx<R, E, T>
|
|
51
|
+
) {
|
|
52
|
+
return Fx.drainLayer(Fx.switchMapCause(hydrate(rendered), Effect.logError))
|
|
53
|
+
}
|