@typed/template 0.11.0 → 0.13.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/compiler-tools/package.json +6 -0
- package/dist/cjs/Html.js +1 -1
- package/dist/cjs/Html.js.map +1 -1
- package/dist/cjs/Hydrate.js +0 -14
- package/dist/cjs/Hydrate.js.map +1 -1
- package/dist/cjs/Template.js.map +1 -1
- package/dist/cjs/Test.js +1 -1
- package/dist/cjs/Test.js.map +1 -1
- package/dist/cjs/compiler-tools.js +100 -0
- package/dist/cjs/compiler-tools.js.map +1 -0
- package/dist/cjs/internal/HydrateContext.js.map +1 -1
- package/dist/cjs/internal/browser.js +1 -1
- package/dist/cjs/internal/browser.js.map +1 -1
- package/dist/cjs/internal/v2/render-sync-parts.js +1 -1
- package/dist/cjs/internal/v2/render-sync-parts.js.map +1 -1
- package/dist/cjs/internal/v2/render.js +241 -66
- package/dist/cjs/internal/v2/render.js.map +1 -1
- package/dist/dts/Hydrate.d.ts +2 -9
- package/dist/dts/Hydrate.d.ts.map +1 -1
- package/dist/dts/Template.d.ts +3 -3
- package/dist/dts/Template.d.ts.map +1 -1
- package/dist/dts/compiler-tools.d.ts +143 -0
- package/dist/dts/compiler-tools.d.ts.map +1 -0
- package/dist/dts/internal/v2/render.d.ts +31 -10
- package/dist/dts/internal/v2/render.d.ts.map +1 -1
- package/dist/esm/Html.js +2 -2
- package/dist/esm/Html.js.map +1 -1
- package/dist/esm/Hydrate.js +0 -12
- package/dist/esm/Hydrate.js.map +1 -1
- package/dist/esm/Template.js.map +1 -1
- package/dist/esm/Test.js +2 -2
- package/dist/esm/Test.js.map +1 -1
- package/dist/esm/compiler-tools.js +91 -0
- package/dist/esm/compiler-tools.js.map +1 -0
- package/dist/esm/internal/HydrateContext.js.map +1 -1
- package/dist/esm/internal/v2/render.js +231 -63
- package/dist/esm/internal/v2/render.js.map +1 -1
- package/package.json +17 -9
- package/src/Html.ts +2 -2
- package/src/Hydrate.ts +2 -37
- package/src/Template.ts +4 -2
- package/src/Test.ts +2 -2
- package/src/compiler-tools.ts +250 -0
- package/src/internal/HydrateContext.ts +0 -2
- package/src/internal/v2/render.ts +279 -54
- package/dist/cjs/internal/v2/hydrate.js +0 -202
- package/dist/cjs/internal/v2/hydrate.js.map +0 -1
- package/dist/dts/internal/v2/hydrate.d.ts +0 -7
- package/dist/dts/internal/v2/hydrate.d.ts.map +0 -1
- package/dist/esm/internal/v2/hydrate.js +0 -195
- package/dist/esm/internal/v2/hydrate.js.map +0 -1
- package/src/internal/v2/hydrate.ts +0 -289
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import * as Context from "@typed/context"
|
|
2
2
|
import * as Fx from "@typed/fx"
|
|
3
3
|
import type { Rendered } from "@typed/wire"
|
|
4
|
-
import { persistent } from "@typed/wire"
|
|
5
|
-
import
|
|
6
|
-
import
|
|
4
|
+
import { isText, persistent } from "@typed/wire"
|
|
5
|
+
import { Option } from "effect"
|
|
6
|
+
import * as Cause from "effect/Cause"
|
|
7
|
+
import * as Chunk from "effect/Chunk"
|
|
7
8
|
import * as Effect from "effect/Effect"
|
|
8
9
|
import * as ExecutionStrategy from "effect/ExecutionStrategy"
|
|
9
10
|
import { flow } from "effect/Function"
|
|
@@ -20,12 +21,24 @@ import { DomRenderEvent, type RenderEvent } from "../../RenderEvent.js"
|
|
|
20
21
|
import { DEFAULT_PRIORITY, RenderQueue } from "../../RenderQueue.js"
|
|
21
22
|
import type { RenderTemplate } from "../../RenderTemplate.js"
|
|
22
23
|
import type * as Template from "../../Template.js"
|
|
24
|
+
import { CouldNotFindCommentError, isHydrationError } from "../errors.js"
|
|
23
25
|
import type { EventSource } from "../EventSource.js"
|
|
24
26
|
import { makeEventSource } from "../EventSource.js"
|
|
27
|
+
import { HydrateContext } from "../HydrateContext.js"
|
|
25
28
|
import type { IndexRefCounter } from "../indexRefCounter.js"
|
|
26
29
|
import { makeRefCounter } from "../indexRefCounter.js"
|
|
27
|
-
import {
|
|
30
|
+
import type { ParentChildNodes } from "../utils.js"
|
|
31
|
+
import { findHoleComment, findHydratePath, findPath, isCommentWithValue, keyToPartType } from "../utils.js"
|
|
28
32
|
import { isNullOrUndefined } from "./helpers.js"
|
|
33
|
+
import type { HydrationHole, HydrationNode, HydrationTemplate } from "./hydration-template.js"
|
|
34
|
+
import {
|
|
35
|
+
findHydrationHole,
|
|
36
|
+
findHydrationMany,
|
|
37
|
+
findHydrationTemplate,
|
|
38
|
+
getChildNodes,
|
|
39
|
+
getNodes,
|
|
40
|
+
getPreviousNodes
|
|
41
|
+
} from "./hydration-template.js"
|
|
29
42
|
import { EventPartImpl, RefPartImpl, syncPartToPart } from "./parts.js"
|
|
30
43
|
import { getBrowserEntry } from "./render-entry.js"
|
|
31
44
|
import * as SyncPartsInternal from "./render-sync-parts.js"
|
|
@@ -41,7 +54,6 @@ export type TemplateContext = {
|
|
|
41
54
|
*/
|
|
42
55
|
spreadIndex: number
|
|
43
56
|
|
|
44
|
-
readonly content: DocumentFragment
|
|
45
57
|
readonly context: Context.Context<Scope.Scope>
|
|
46
58
|
readonly document: Document
|
|
47
59
|
readonly eventSource: EventSource
|
|
@@ -52,6 +64,9 @@ export type TemplateContext = {
|
|
|
52
64
|
readonly scope: Scope.CloseableScope
|
|
53
65
|
readonly values: ReadonlyArray<Renderable<any, any>>
|
|
54
66
|
readonly onCause: (cause: Cause.Cause<any>) => Effect.Effect<unknown>
|
|
67
|
+
readonly manyKey: string | undefined
|
|
68
|
+
|
|
69
|
+
readonly hydrateContext: Option.Option<HydrateContext>
|
|
55
70
|
}
|
|
56
71
|
|
|
57
72
|
export const renderTemplate: (
|
|
@@ -63,28 +78,48 @@ export const renderTemplate: (
|
|
|
63
78
|
values: Values
|
|
64
79
|
) => {
|
|
65
80
|
const entry = getBrowserEntry(document, renderContext, templateStrings)
|
|
66
|
-
if (entry.template.parts.length === 0) {
|
|
67
|
-
return Fx.succeed(DomRenderEvent(persistent(document, document.importNode(entry.content, true))))
|
|
68
|
-
}
|
|
69
81
|
|
|
70
82
|
return Fx.make<
|
|
71
83
|
RenderEvent,
|
|
72
84
|
Placeholder.Error<Values[number]>,
|
|
73
85
|
Scope.Scope | RenderQueue | Placeholder.Context<Values[number]>
|
|
74
|
-
>((
|
|
75
|
-
|
|
86
|
+
>(function render(
|
|
87
|
+
sink
|
|
88
|
+
): Effect.Effect<unknown, never, Scope.Scope | RenderQueue | Placeholder.Context<Values[number]>> {
|
|
89
|
+
return Effect.catchAllCause(
|
|
76
90
|
Effect.gen(function*() {
|
|
77
91
|
// Create a context for rendering our template
|
|
78
92
|
const ctx = yield* makeTemplateContext<Values>(
|
|
79
|
-
entry.content,
|
|
80
93
|
document,
|
|
81
94
|
renderContext,
|
|
82
95
|
values,
|
|
83
96
|
sink.onFailure
|
|
84
97
|
)
|
|
85
98
|
|
|
86
|
-
|
|
87
|
-
|
|
99
|
+
const hydration = attemptHydration(ctx, entry.template.hash)
|
|
100
|
+
|
|
101
|
+
let effects: Array<Effect.Effect<void, any, any>>
|
|
102
|
+
let content: DocumentFragment
|
|
103
|
+
let wire: Rendered | undefined
|
|
104
|
+
|
|
105
|
+
if (Option.isSome(hydration)) {
|
|
106
|
+
const { hydrateCtx, where } = hydration.value
|
|
107
|
+
effects = setupHydrateParts(entry.template.parts, {
|
|
108
|
+
...ctx,
|
|
109
|
+
where,
|
|
110
|
+
manyKey: hydrateCtx.manyKey,
|
|
111
|
+
makeHydrateContext: (where: HydrationNode): HydrateContext => ({
|
|
112
|
+
where,
|
|
113
|
+
hydrate: true
|
|
114
|
+
})
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
wire = getWire(where)
|
|
118
|
+
} else {
|
|
119
|
+
content = ctx.document.importNode(entry.content, true)
|
|
120
|
+
effects = setupRenderParts(entry.template.parts, content, ctx)
|
|
121
|
+
}
|
|
122
|
+
|
|
88
123
|
if (effects.length > 0) {
|
|
89
124
|
yield* Effect.forEach(effects, flow(Effect.catchAllCause(ctx.onCause), Effect.forkIn(ctx.scope)))
|
|
90
125
|
}
|
|
@@ -95,14 +130,21 @@ export const renderTemplate: (
|
|
|
95
130
|
yield* ctx.refCounter.wait
|
|
96
131
|
}
|
|
97
132
|
|
|
98
|
-
//
|
|
99
|
-
|
|
133
|
+
// If we're not hydrating, we need to create our wire from our content
|
|
134
|
+
if (wire === undefined) {
|
|
135
|
+
wire = persistent(ctx.document, content!)
|
|
136
|
+
}
|
|
100
137
|
|
|
101
138
|
// Setup our event listeners for our wire.
|
|
102
139
|
// We use the parentScope to allow event listeners to exist
|
|
103
140
|
// beyond the lifetime of the current Fiber, but no further than its parent template.
|
|
104
141
|
yield* ctx.eventSource.setup(wire, ctx.parentScope)
|
|
105
142
|
|
|
143
|
+
// If we're hydrating, we need to mark this part of the stack as hydrated
|
|
144
|
+
if (Option.isSome(hydration)) {
|
|
145
|
+
hydration.value.hydrateCtx.hydrate = false
|
|
146
|
+
}
|
|
147
|
+
|
|
106
148
|
// Emit our DomRenderEvent
|
|
107
149
|
yield* sink.onSuccess(DomRenderEvent(wire)).pipe(
|
|
108
150
|
// Ensure our templates last forever in the DOM environment
|
|
@@ -112,13 +154,22 @@ export const renderTemplate: (
|
|
|
112
154
|
Effect.onExit((exit) => Scope.close(ctx.scope, exit))
|
|
113
155
|
)
|
|
114
156
|
}),
|
|
115
|
-
|
|
157
|
+
(cause) => {
|
|
158
|
+
const hydrationFailure = Chunk.findFirst(Cause.defects(cause), isHydrationError)
|
|
159
|
+
if (Option.isSome(hydrationFailure)) {
|
|
160
|
+
return HydrateContext.pipe(
|
|
161
|
+
Effect.tap((ctx) => ctx.hydrate = false),
|
|
162
|
+
Effect.flatMap(() => render(sink))
|
|
163
|
+
)
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return sink.onFailure(cause)
|
|
167
|
+
}
|
|
116
168
|
)
|
|
117
|
-
)
|
|
169
|
+
})
|
|
118
170
|
}
|
|
119
171
|
|
|
120
172
|
export function makeTemplateContext<Values extends ReadonlyArray<Renderable<any, any>>>(
|
|
121
|
-
entry: DocumentFragment,
|
|
122
173
|
document: Document,
|
|
123
174
|
renderContext: RenderContext,
|
|
124
175
|
values: ReadonlyArray<Renderable<any, any>>,
|
|
@@ -130,15 +181,16 @@ export function makeTemplateContext<Values extends ReadonlyArray<Renderable<any,
|
|
|
130
181
|
const queue = Context.get(context, RenderQueue)
|
|
131
182
|
const parentScope = Context.get(context, Scope.Scope)
|
|
132
183
|
const eventSource = makeEventSource()
|
|
133
|
-
const content = document.importNode(entry, true)
|
|
134
184
|
const scope = yield* Scope.fork(parentScope, ExecutionStrategy.sequential)
|
|
185
|
+
const hydrateContext = Context.getOption(context, HydrateContext)
|
|
135
186
|
const templateContext: TemplateContext = {
|
|
136
187
|
context: Context.add(context, Scope.Scope, scope),
|
|
137
188
|
expected: 0,
|
|
138
|
-
content,
|
|
139
189
|
document,
|
|
140
190
|
eventSource,
|
|
191
|
+
hydrateContext,
|
|
141
192
|
parentScope,
|
|
193
|
+
manyKey: undefined,
|
|
142
194
|
queue,
|
|
143
195
|
refCounter,
|
|
144
196
|
renderContext,
|
|
@@ -152,11 +204,33 @@ export function makeTemplateContext<Values extends ReadonlyArray<Renderable<any,
|
|
|
152
204
|
})
|
|
153
205
|
}
|
|
154
206
|
|
|
155
|
-
function
|
|
207
|
+
export function attemptHydration(
|
|
208
|
+
ctx: TemplateContext,
|
|
209
|
+
hash: string
|
|
210
|
+
): Option.Option<{ readonly where: HydrationTemplate; readonly hydrateCtx: HydrateContext }> {
|
|
211
|
+
if (Option.isSome(ctx.hydrateContext) && ctx.hydrateContext.value.hydrate) {
|
|
212
|
+
const hydrateCtx = ctx.hydrateContext.value
|
|
213
|
+
const where = findHydrationTemplateByHash(hydrateCtx, hash)
|
|
214
|
+
if (where === null) {
|
|
215
|
+
hydrateCtx.hydrate = false
|
|
216
|
+
return Option.none()
|
|
217
|
+
} else {
|
|
218
|
+
return Option.some({ where, hydrateCtx })
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
return Option.none()
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function setupRenderParts(
|
|
226
|
+
parts: Template.Template["parts"],
|
|
227
|
+
content: ParentChildNodes,
|
|
228
|
+
ctx: TemplateContext
|
|
229
|
+
) {
|
|
156
230
|
const effects: Array<Effect.Effect<void, any, any>> = []
|
|
157
231
|
|
|
158
232
|
for (const [part, path] of parts) {
|
|
159
|
-
const effect =
|
|
233
|
+
const effect = setupRenderPart(part, content, path, ctx)
|
|
160
234
|
if (effect) {
|
|
161
235
|
effects.push(effect)
|
|
162
236
|
}
|
|
@@ -165,58 +239,59 @@ function setupParts(parts: Template.Template["parts"], ctx: TemplateContext) {
|
|
|
165
239
|
return effects
|
|
166
240
|
}
|
|
167
241
|
|
|
168
|
-
function
|
|
242
|
+
function setupRenderPart(
|
|
169
243
|
part: Template.PartNode | Template.SparsePartNode,
|
|
170
|
-
|
|
244
|
+
content: ParentChildNodes,
|
|
245
|
+
path: Chunk.Chunk<number>,
|
|
171
246
|
ctx: TemplateContext
|
|
172
247
|
) {
|
|
173
248
|
switch (part._tag) {
|
|
174
249
|
case "attr":
|
|
175
|
-
return setupAttrPart(part, findPath(
|
|
250
|
+
return setupAttrPart(part, findPath(content, path) as HTMLElement | SVGElement, ctx, ctx.values[part.index])
|
|
176
251
|
case "boolean-part":
|
|
177
252
|
return setupBooleanPart(
|
|
178
253
|
part,
|
|
179
|
-
findPath(
|
|
254
|
+
findPath(content, path) as HTMLElement | SVGElement,
|
|
180
255
|
ctx,
|
|
181
256
|
ctx.values[part.index]
|
|
182
257
|
)
|
|
183
258
|
case "className-part":
|
|
184
259
|
return setupClassNamePart(
|
|
185
260
|
part,
|
|
186
|
-
findPath(
|
|
261
|
+
findPath(content, path) as HTMLElement | SVGElement,
|
|
187
262
|
ctx,
|
|
188
263
|
ctx.values[part.index]
|
|
189
264
|
)
|
|
190
265
|
case "comment-part":
|
|
191
|
-
return setupCommentPart(part, findPath(
|
|
266
|
+
return setupCommentPart(part, findPath(content, path) as Comment, ctx, ctx.values[part.index])
|
|
192
267
|
case "data":
|
|
193
|
-
return setupDataPart(part, findPath(
|
|
268
|
+
return setupDataPart(part, findPath(content, path) as HTMLElement | SVGElement, ctx, ctx.values[part.index])
|
|
194
269
|
case "event":
|
|
195
|
-
return setupEventPart(part, findPath(
|
|
270
|
+
return setupEventPart(part, findPath(content, path) as HTMLElement | SVGElement, ctx, ctx.values[part.index])
|
|
196
271
|
case "node": {
|
|
197
|
-
const parent = findPath(
|
|
272
|
+
const parent = findPath(content, path) as Element
|
|
198
273
|
const comment = findHoleComment(parent, part.index)
|
|
199
274
|
return setupNodePart(part, comment, ctx, null, [])
|
|
200
275
|
}
|
|
201
276
|
case "properties":
|
|
202
|
-
return setupPropertiesPart(
|
|
277
|
+
return setupPropertiesPart(findPath(content, path) as HTMLElement | SVGElement, ctx, ctx.values[part.index])
|
|
203
278
|
case "property":
|
|
204
279
|
return setupPropertyPart(
|
|
205
280
|
part,
|
|
206
|
-
findPath(
|
|
281
|
+
findPath(content, path) as HTMLElement | SVGElement,
|
|
207
282
|
ctx,
|
|
208
283
|
ctx.values[part.index]
|
|
209
284
|
)
|
|
210
285
|
case "ref":
|
|
211
|
-
return setupRefPart(part, findPath(
|
|
286
|
+
return setupRefPart(part, findPath(content, path) as HTMLElement | SVGElement, ctx)
|
|
212
287
|
case "sparse-attr":
|
|
213
|
-
return setupSparseAttrPart(part, findPath(
|
|
288
|
+
return setupSparseAttrPart(part, findPath(content, path) as HTMLElement | SVGElement, ctx)
|
|
214
289
|
case "sparse-class-name":
|
|
215
|
-
return setupSparseClassNamePart(part, findPath(
|
|
290
|
+
return setupSparseClassNamePart(part, findPath(content, path) as HTMLElement | SVGElement, ctx)
|
|
216
291
|
case "sparse-comment":
|
|
217
|
-
return setupSparseCommentPart(part, findPath(
|
|
292
|
+
return setupSparseCommentPart(part, findPath(content, path) as Comment, ctx)
|
|
218
293
|
case "text-part": {
|
|
219
|
-
const parent = findPath(
|
|
294
|
+
const parent = findPath(content, path) as Element
|
|
220
295
|
const comment = findHoleComment(parent, part.index)
|
|
221
296
|
return setupTextPart(part, comment, ctx)
|
|
222
297
|
}
|
|
@@ -257,10 +332,10 @@ export function setupClassNamePart(
|
|
|
257
332
|
export function setupCommentPart(
|
|
258
333
|
{ index }: Pick<Template.CommentPartNode, "index">,
|
|
259
334
|
comment: Comment,
|
|
260
|
-
ctx: TemplateContext
|
|
335
|
+
ctx: TemplateContext,
|
|
336
|
+
renderable: Renderable<any, any>
|
|
261
337
|
) {
|
|
262
338
|
const part = SyncPartsInternal.makeCommentPart(index, comment)
|
|
263
|
-
const renderable = ctx.values[index]
|
|
264
339
|
return matchSyncPart(renderable, ctx, part)
|
|
265
340
|
}
|
|
266
341
|
|
|
@@ -348,10 +423,8 @@ export function setupPropertyPart(
|
|
|
348
423
|
export function setupRefPart(
|
|
349
424
|
{ index }: Pick<Template.RefPartNode, "index">,
|
|
350
425
|
element: HTMLElement | SVGElement,
|
|
351
|
-
|
|
426
|
+
renderable: Renderable<any, any>
|
|
352
427
|
) {
|
|
353
|
-
const renderable = ctx.values[index]
|
|
354
|
-
|
|
355
428
|
if (isNullOrUndefined(renderable)) return null
|
|
356
429
|
else if (isDirective(renderable)) {
|
|
357
430
|
return renderable(
|
|
@@ -366,11 +439,10 @@ export function setupRefPart(
|
|
|
366
439
|
}
|
|
367
440
|
|
|
368
441
|
export function setupPropertiesPart(
|
|
369
|
-
{ index }: Pick<Template.PropertiesPartNode, "index">,
|
|
370
442
|
element: HTMLElement | SVGElement,
|
|
371
|
-
ctx: TemplateContext
|
|
443
|
+
ctx: TemplateContext,
|
|
444
|
+
renderable: Renderable<any, any>
|
|
372
445
|
) {
|
|
373
|
-
const renderable = ctx.values[index]
|
|
374
446
|
if (renderable && typeof renderable === "object") {
|
|
375
447
|
const effects: Array<Effect.Effect<void, any, any>> = []
|
|
376
448
|
const addEffect = (effect: Effect.Effect<void, any, any> | null | undefined) => {
|
|
@@ -410,7 +482,7 @@ export function setupPropertiesPart(
|
|
|
410
482
|
}
|
|
411
483
|
|
|
412
484
|
export function setupSparseAttrPart(
|
|
413
|
-
{ name, nodes }: Template.SparseAttrNode,
|
|
485
|
+
{ name, nodes }: Pick<Template.SparseAttrNode, "name" | "nodes">,
|
|
414
486
|
element: HTMLElement | SVGElement,
|
|
415
487
|
ctx: TemplateContext
|
|
416
488
|
) {
|
|
@@ -427,7 +499,7 @@ export function setupSparseAttrPart(
|
|
|
427
499
|
}
|
|
428
500
|
|
|
429
501
|
export function setupSparseClassNamePart(
|
|
430
|
-
{ nodes }: Template.SparseClassNameNode,
|
|
502
|
+
{ nodes }: Pick<Template.SparseClassNameNode, "nodes">,
|
|
431
503
|
element: HTMLElement | SVGElement,
|
|
432
504
|
ctx: TemplateContext
|
|
433
505
|
) {
|
|
@@ -442,7 +514,7 @@ export function setupSparseClassNamePart(
|
|
|
442
514
|
}
|
|
443
515
|
|
|
444
516
|
export function setupSparseCommentPart(
|
|
445
|
-
{ nodes }: Template.SparseCommentNode,
|
|
517
|
+
{ nodes }: Pick<Template.SparseCommentNode, "nodes">,
|
|
446
518
|
comment: Comment,
|
|
447
519
|
ctx: TemplateContext
|
|
448
520
|
) {
|
|
@@ -499,9 +571,8 @@ function unwrapRenderable<E, R>(renderable: unknown): Fx.Fx<any, E, R> {
|
|
|
499
571
|
else if (Array.isArray(renderable)) {
|
|
500
572
|
return renderable.length === 0
|
|
501
573
|
? Fx.succeed(null)
|
|
502
|
-
// TODO: We need to ensure the ordering of these values in server environments
|
|
503
574
|
: Fx.map(Fx.tuple(renderable.map(unwrapRenderable)), (xs) => xs.flat()) as any
|
|
504
|
-
} else if (Fx.
|
|
575
|
+
} else if (Fx.FxTypeId in renderable) {
|
|
505
576
|
return renderable as any
|
|
506
577
|
} else if (Effect.EffectTypeId in renderable) {
|
|
507
578
|
return Fx.fromFxEffect(Effect.map(renderable as any, unwrapRenderable<E, R>))
|
|
@@ -573,16 +644,170 @@ export function attachRoot<T extends RenderEvent | null>(
|
|
|
573
644
|
}
|
|
574
645
|
|
|
575
646
|
export function removeChildren(where: HTMLElement, previous: Rendered) {
|
|
576
|
-
for (const node of
|
|
647
|
+
for (const node of getNodesFromRendered(previous)) {
|
|
577
648
|
where.removeChild(node)
|
|
578
649
|
}
|
|
579
650
|
}
|
|
580
651
|
|
|
581
652
|
export function replaceChildren(where: HTMLElement, wire: Rendered) {
|
|
582
|
-
where.replaceChildren(...
|
|
653
|
+
where.replaceChildren(...getNodesFromRendered(wire))
|
|
583
654
|
}
|
|
584
655
|
|
|
585
|
-
export function
|
|
656
|
+
export function getNodesFromRendered(rendered: Rendered): Array<globalThis.Node> {
|
|
586
657
|
const value = rendered.valueOf() as globalThis.Node | Array<globalThis.Node>
|
|
587
658
|
return Array.isArray(value) ? value : [value]
|
|
588
659
|
}
|
|
660
|
+
|
|
661
|
+
export type HydrateTemplateContext = TemplateContext & {
|
|
662
|
+
readonly where: HydrationNode
|
|
663
|
+
readonly makeHydrateContext: (where: HydrationNode, index: number) => HydrateContext
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
export function findHydrationTemplateByHash(hydrateCtx: HydrateContext, hash: string): HydrationTemplate | null {
|
|
667
|
+
// If there is not a manyKey, we can just find the template by its hash
|
|
668
|
+
if (hydrateCtx.manyKey === undefined) {
|
|
669
|
+
return findHydrationTemplate(getChildNodes(hydrateCtx.where), hash)
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
// If there is a manyKey, we need to find the many node first
|
|
673
|
+
const many = findHydrationMany(getChildNodes(hydrateCtx.where), hydrateCtx.manyKey)
|
|
674
|
+
|
|
675
|
+
if (many === null) return null
|
|
676
|
+
|
|
677
|
+
// Then we can find the template by its hash
|
|
678
|
+
return findHydrationTemplate(getChildNodes(many), hash)
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
export function setupHydrateParts(parts: Template.Template["parts"], ctx: HydrateTemplateContext) {
|
|
682
|
+
const effects: Array<Effect.Effect<void, any, any>> = []
|
|
683
|
+
|
|
684
|
+
for (const [part, path] of parts) {
|
|
685
|
+
const effect = setupHydratePart(part, path, ctx)
|
|
686
|
+
if (effect) {
|
|
687
|
+
effects.push(effect)
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
return effects
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
export function setupHydratePart(
|
|
695
|
+
part: Template.PartNode | Template.SparsePartNode,
|
|
696
|
+
path: Chunk.Chunk<number>,
|
|
697
|
+
ctx: HydrateTemplateContext
|
|
698
|
+
) {
|
|
699
|
+
switch (part._tag) {
|
|
700
|
+
case "attr":
|
|
701
|
+
return setupAttrPart(part, findHydratePath(ctx.where, path) as any, ctx, ctx.values[part.index])
|
|
702
|
+
case "boolean-part":
|
|
703
|
+
return setupBooleanPart(part, findHydratePath(ctx.where, path) as any, ctx, ctx.values[part.index])
|
|
704
|
+
case "className-part":
|
|
705
|
+
return setupClassNamePart(part, findHydratePath(ctx.where, path) as any, ctx, ctx.values[part.index])
|
|
706
|
+
case "comment-part":
|
|
707
|
+
return setupCommentPart(part, findHydratePath(ctx.where, path) as any, ctx, ctx.values[part.index])
|
|
708
|
+
case "data":
|
|
709
|
+
return setupDataPart(part, findHydratePath(ctx.where, path) as any, ctx, ctx.values[part.index])
|
|
710
|
+
case "event":
|
|
711
|
+
return setupEventPart(part, findHydratePath(ctx.where, path) as any, ctx, ctx.values[part.index])
|
|
712
|
+
case "node": {
|
|
713
|
+
const hole = findHydrationHole(getChildNodes(ctx.where), part.index)
|
|
714
|
+
if (hole === null) {
|
|
715
|
+
throw new CouldNotFindCommentError(part.index)
|
|
716
|
+
}
|
|
717
|
+
return setupHydratedNodePart(part, hole, ctx)
|
|
718
|
+
}
|
|
719
|
+
case "properties":
|
|
720
|
+
return setupPropertiesPart(findHydratePath(ctx.where, path) as any, ctx, ctx.values[part.index])
|
|
721
|
+
case "property":
|
|
722
|
+
return setupPropertyPart(part, findHydratePath(ctx.where, path) as any, ctx, ctx.values[part.index])
|
|
723
|
+
case "ref":
|
|
724
|
+
return setupRefPart(part, findHydratePath(ctx.where, path) as any, ctx.values[part.index])
|
|
725
|
+
case "sparse-attr":
|
|
726
|
+
return setupSparseAttrPart(part, findHydratePath(ctx.where, path) as any, ctx)
|
|
727
|
+
case "sparse-class-name":
|
|
728
|
+
return setupSparseClassNamePart(part, findHydratePath(ctx.where, path) as any, ctx)
|
|
729
|
+
case "sparse-comment":
|
|
730
|
+
return setupSparseCommentPart(part, findHydratePath(ctx.where, path) as any, ctx)
|
|
731
|
+
case "text-part": {
|
|
732
|
+
const hole = findHydrationHole(getChildNodes(ctx.where), part.index)
|
|
733
|
+
if (hole === null) throw new CouldNotFindCommentError(part.index)
|
|
734
|
+
return setupTextPart(part, hole.endComment, ctx)
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
export function setupHydratedNodePart(
|
|
740
|
+
part: Template.NodePart,
|
|
741
|
+
hole: HydrationHole,
|
|
742
|
+
ctx: HydrateTemplateContext
|
|
743
|
+
) {
|
|
744
|
+
const nestedCtx = ctx.makeHydrateContext(hole, part.index)
|
|
745
|
+
const previousNodes = getPreviousNodes(hole)
|
|
746
|
+
const text = previousNodes.length === 2 && isCommentWithValue(previousNodes[0], "text") && isText(previousNodes[1])
|
|
747
|
+
? previousNodes[1]
|
|
748
|
+
: null
|
|
749
|
+
const effect = setupNodePart(part, hole.endComment, ctx, text, text === null ? previousNodes : [text])
|
|
750
|
+
if (effect === null) return null
|
|
751
|
+
return Effect.provideService(effect, HydrateContext, nestedCtx)
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
export function findRootParentChildNodes(where: HTMLElement): ParentChildNodes {
|
|
755
|
+
const childNodes = findRootChildNodes(where)
|
|
756
|
+
|
|
757
|
+
return {
|
|
758
|
+
parentNode: where,
|
|
759
|
+
childNodes
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
const START = "typed-start"
|
|
764
|
+
const END = "typed-end"
|
|
765
|
+
|
|
766
|
+
// Finds all of the childNodes between the "typed-start" and "typed-end" comments
|
|
767
|
+
export function findRootChildNodes(where: HTMLElement): Array<Node> {
|
|
768
|
+
let start = -1
|
|
769
|
+
let end = -1
|
|
770
|
+
|
|
771
|
+
const { childNodes } = where
|
|
772
|
+
const length = childNodes.length
|
|
773
|
+
|
|
774
|
+
for (let i = 0; i < length; i++) {
|
|
775
|
+
const node = childNodes[i]
|
|
776
|
+
|
|
777
|
+
if (node.nodeType === node.COMMENT_NODE && node.nodeValue === START) {
|
|
778
|
+
start = i
|
|
779
|
+
break
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
for (let i = length - 1; i >= Math.max(start, 0); i--) {
|
|
784
|
+
const node = childNodes[i]
|
|
785
|
+
|
|
786
|
+
if (node.nodeType === node.COMMENT_NODE && node.nodeValue === END) {
|
|
787
|
+
end = i
|
|
788
|
+
break
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
// If we can't find the start and end comments, just return all childNodes
|
|
793
|
+
if (start === -1 && end === -1) {
|
|
794
|
+
return Array.from(childNodes)
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
start = start === -1 ? 0 : start
|
|
798
|
+
end = end === -1 ? length - 1 : end
|
|
799
|
+
|
|
800
|
+
const rootChildNodes: Array<Node> = Array(end - start)
|
|
801
|
+
|
|
802
|
+
for (let i = start + 1, j = 0; i <= end; i++) {
|
|
803
|
+
rootChildNodes[j++] = childNodes[i]
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
return rootChildNodes
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
export function getWire(where: HydrationNode) {
|
|
810
|
+
const nodes = getNodes(where)
|
|
811
|
+
if (nodes.length === 1) return nodes[0]
|
|
812
|
+
return nodes
|
|
813
|
+
}
|