@typed/template 0.3.6 → 0.3.8
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/cjs/Many.js +22 -2
- package/dist/cjs/Many.js.map +1 -1
- package/dist/cjs/Meta.js +7 -1
- package/dist/cjs/Meta.js.map +1 -1
- package/dist/cjs/Test.js +53 -0
- package/dist/cjs/Test.js.map +1 -1
- package/dist/cjs/internal/HydrateContext.js.map +1 -1
- package/dist/cjs/internal/errors.js +10 -2
- package/dist/cjs/internal/errors.js.map +1 -1
- package/dist/cjs/internal/hydrate.js +44 -10
- package/dist/cjs/internal/hydrate.js.map +1 -1
- package/dist/cjs/internal/parser.js +1 -1
- package/dist/cjs/internal/parser.js.map +1 -1
- package/dist/cjs/internal/render.js.map +1 -1
- package/dist/cjs/internal/utils.js +4 -0
- package/dist/cjs/internal/utils.js.map +1 -1
- package/dist/dts/Many.d.ts.map +1 -1
- package/dist/dts/Meta.d.ts +5 -0
- package/dist/dts/Meta.d.ts.map +1 -1
- package/dist/dts/Test.d.ts +17 -0
- package/dist/dts/Test.d.ts.map +1 -1
- package/dist/dts/internal/errors.d.ts +4 -0
- package/dist/dts/internal/errors.d.ts.map +1 -1
- package/dist/dts/internal/hydrate.d.ts +8 -5
- package/dist/dts/internal/hydrate.d.ts.map +1 -1
- package/dist/dts/internal/parser.d.ts.map +1 -1
- package/dist/dts/internal/render.d.ts.map +1 -1
- package/dist/dts/internal/utils.d.ts +1 -0
- package/dist/dts/internal/utils.d.ts.map +1 -1
- package/dist/esm/Many.js +19 -2
- package/dist/esm/Many.js.map +1 -1
- package/dist/esm/Meta.js +5 -0
- package/dist/esm/Meta.js.map +1 -1
- package/dist/esm/Test.js +49 -1
- package/dist/esm/Test.js.map +1 -1
- package/dist/esm/internal/HydrateContext.js.map +1 -1
- package/dist/esm/internal/errors.js +9 -1
- package/dist/esm/internal/errors.js.map +1 -1
- package/dist/esm/internal/hydrate.js +42 -13
- package/dist/esm/internal/hydrate.js.map +1 -1
- package/dist/esm/internal/parser.js +1 -1
- package/dist/esm/internal/parser.js.map +1 -1
- package/dist/esm/internal/render.js.map +1 -1
- package/dist/esm/internal/utils.js +3 -0
- package/dist/esm/internal/utils.js.map +1 -1
- package/package.json +10 -10
- package/src/Many.ts +23 -4
- package/src/Meta.ts +6 -0
- package/src/Test.ts +90 -1
- package/src/internal/HydrateContext.ts +3 -1
- package/src/internal/errors.ts +8 -1
- package/src/internal/hydrate.ts +60 -15
- package/src/internal/parser.ts +1 -1
- package/src/internal/render.ts +0 -2
- package/src/internal/utils.ts +4 -0
package/src/Test.ts
CHANGED
|
@@ -10,6 +10,7 @@ import * as Fx from "@typed/fx/Fx"
|
|
|
10
10
|
import * as RefArray from "@typed/fx/RefArray"
|
|
11
11
|
import * as RefSubject from "@typed/fx/RefSubject"
|
|
12
12
|
import * as Sink from "@typed/fx/Sink"
|
|
13
|
+
import { type Rendered } from "@typed/wire"
|
|
13
14
|
import * as Cause from "effect/Cause"
|
|
14
15
|
import * as Effect from "effect/Effect"
|
|
15
16
|
import * as Either from "effect/Either"
|
|
@@ -17,7 +18,9 @@ import * as Fiber from "effect/Fiber"
|
|
|
17
18
|
import type * as Scope from "effect/Scope"
|
|
18
19
|
import * as ElementRef from "./ElementRef.js"
|
|
19
20
|
import { ROOT_CSS_SELECTOR } from "./ElementSource.js"
|
|
20
|
-
import {
|
|
21
|
+
import { renderToHtmlString } from "./Html.js"
|
|
22
|
+
import { hydrate } from "./Hydrate.js"
|
|
23
|
+
import { adjustTime, isCommentWithValue } from "./internal/utils.js"
|
|
21
24
|
import { render } from "./Render.js"
|
|
22
25
|
import * as RenderContext from "./RenderContext.js"
|
|
23
26
|
import type { RenderEvent } from "./RenderEvent.js"
|
|
@@ -172,3 +175,89 @@ function getOrMakeWindow(
|
|
|
172
175
|
import("happy-dom").then((happyDOM) => new happyDOM.Window(options) as any as Window & GlobalThis)
|
|
173
176
|
)
|
|
174
177
|
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* @since 1.0.0
|
|
181
|
+
*/
|
|
182
|
+
export interface TestHydrate<E, Elements> extends TestRender<E> {
|
|
183
|
+
readonly elements: Elements
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* @since 1.0.0
|
|
188
|
+
*/
|
|
189
|
+
export function testHydrate<R, E, Elements>(
|
|
190
|
+
fx: Fx.Fx<R, E, RenderEvent>,
|
|
191
|
+
f: (rendered: Rendered, window: Window & GlobalThis) => Elements,
|
|
192
|
+
options?:
|
|
193
|
+
& HappyDOMOptions
|
|
194
|
+
& { readonly [K in keyof DomServicesElementParams]?: (document: Document) => DomServicesElementParams[K] }
|
|
195
|
+
) {
|
|
196
|
+
return Effect.gen(function*(_) {
|
|
197
|
+
const window = yield* _(getOrMakeWindow(options))
|
|
198
|
+
const { body } = window.document
|
|
199
|
+
|
|
200
|
+
const html = yield* _(
|
|
201
|
+
renderToHtmlString(fx),
|
|
202
|
+
Effect.provide(RenderContext.server)
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
body.innerHTML = html
|
|
206
|
+
|
|
207
|
+
const rendered = Array.from(body.childNodes)
|
|
208
|
+
|
|
209
|
+
// Remove the typed-start
|
|
210
|
+
if (isCommentWithValue(rendered[0], "typed-start")) {
|
|
211
|
+
rendered.shift()
|
|
212
|
+
}
|
|
213
|
+
// Remove the typed-end
|
|
214
|
+
if (isCommentWithValue(rendered[rendered.length - 1], "typed-end")) {
|
|
215
|
+
rendered.pop()
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
const elements = f(rendered.length === 1 ? rendered[0] : rendered, window)
|
|
219
|
+
|
|
220
|
+
const elementRef = yield* _(ElementRef.make())
|
|
221
|
+
const errors = yield* _(RefSubject.make<never, never, ReadonlyArray<E>>(Effect.succeed([])))
|
|
222
|
+
const fiber = yield* _(
|
|
223
|
+
fx,
|
|
224
|
+
hydrate,
|
|
225
|
+
(x) =>
|
|
226
|
+
x.run(Sink.make(
|
|
227
|
+
(cause) =>
|
|
228
|
+
Cause.failureOrCause(cause).pipe(
|
|
229
|
+
Either.match({
|
|
230
|
+
onLeft: (error) => RefArray.append(errors, error),
|
|
231
|
+
onRight: (cause) => errors.onFailure(cause)
|
|
232
|
+
})
|
|
233
|
+
),
|
|
234
|
+
(rendered) => ElementRef.set(elementRef, rendered)
|
|
235
|
+
)),
|
|
236
|
+
Effect.forkScoped,
|
|
237
|
+
Effect.provide(RenderContext.dom(window, { skipRenderScheduling: true }))
|
|
238
|
+
)
|
|
239
|
+
|
|
240
|
+
const test: TestHydrate<E, Elements> = {
|
|
241
|
+
elements,
|
|
242
|
+
window,
|
|
243
|
+
document: window.document,
|
|
244
|
+
elementRef,
|
|
245
|
+
errors,
|
|
246
|
+
lastError: RefArray.last(errors),
|
|
247
|
+
interrupt: Fiber.interrupt(fiber),
|
|
248
|
+
makeEvent: (type, init) => new window.Event(type, init),
|
|
249
|
+
makeCustomEvent: (type, init) => new window.CustomEvent(type, init),
|
|
250
|
+
dispatchEvent: (options) => dispatchEvent(test, options),
|
|
251
|
+
click: (options) => click(test, options)
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// Allow our fibers to start
|
|
255
|
+
yield* _(adjustTime(1))
|
|
256
|
+
yield* _(adjustTime(1))
|
|
257
|
+
|
|
258
|
+
// Await the first render
|
|
259
|
+
yield* _(Fx.first(elementRef), Effect.race(Effect.delay(Effect.dieMessage(`Rendering taking too long`), 1000)))
|
|
260
|
+
|
|
261
|
+
return test
|
|
262
|
+
})
|
|
263
|
+
}
|
|
@@ -10,7 +10,9 @@ export type HydrateContext = {
|
|
|
10
10
|
readonly where: ParentChildNodes
|
|
11
11
|
readonly rootIndex: number
|
|
12
12
|
readonly parentTemplate: Template | null
|
|
13
|
-
|
|
13
|
+
|
|
14
|
+
// Used to match sibling components using many() to the correct elements
|
|
15
|
+
readonly manyIndex?: string
|
|
14
16
|
|
|
15
17
|
/**@internal */
|
|
16
18
|
hydrate: boolean
|
package/src/internal/errors.ts
CHANGED
|
@@ -10,6 +10,13 @@ export class CouldNotFindRootElement extends Error {
|
|
|
10
10
|
}
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
+
export class CouldNotFindManyCommentError extends Error {
|
|
14
|
+
constructor(readonly manyIndex: string) {
|
|
15
|
+
super(`Could not find comment for many part ${manyIndex}`)
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
13
19
|
export function isHydrationError(e: unknown): e is CouldNotFindCommentError | CouldNotFindRootElement {
|
|
14
|
-
return e instanceof CouldNotFindCommentError || e instanceof CouldNotFindRootElement
|
|
20
|
+
return e instanceof CouldNotFindCommentError || e instanceof CouldNotFindRootElement ||
|
|
21
|
+
e instanceof CouldNotFindManyCommentError
|
|
15
22
|
}
|
package/src/internal/hydrate.ts
CHANGED
|
@@ -13,7 +13,7 @@ import { unsafeGet } from "@typed/context"
|
|
|
13
13
|
import { Either, ExecutionStrategy, Exit } from "effect"
|
|
14
14
|
import * as Scope from "effect/Scope"
|
|
15
15
|
import type { Template } from "../Template.js"
|
|
16
|
-
import { CouldNotFindCommentError, CouldNotFindRootElement } from "./errors.js"
|
|
16
|
+
import { CouldNotFindCommentError, CouldNotFindManyCommentError, CouldNotFindRootElement } from "./errors.js"
|
|
17
17
|
import { makeEventSource } from "./EventSource.js"
|
|
18
18
|
import { HydrateContext } from "./HydrateContext.js"
|
|
19
19
|
import type { RenderPartContext } from "./render.js"
|
|
@@ -22,8 +22,8 @@ import {
|
|
|
22
22
|
findPath,
|
|
23
23
|
getPreviousNodes,
|
|
24
24
|
isComment,
|
|
25
|
+
isCommentStartingWithValue,
|
|
25
26
|
isCommentWithValue,
|
|
26
|
-
isHtmlElement,
|
|
27
27
|
type ParentChildNodes
|
|
28
28
|
} from "./utils.js"
|
|
29
29
|
|
|
@@ -193,20 +193,36 @@ export function findTemplateResultPartChildNodes(
|
|
|
193
193
|
parentTemplate: Template,
|
|
194
194
|
childTemplate: Template,
|
|
195
195
|
partIndex: number,
|
|
196
|
-
|
|
197
|
-
): Either.Either<CouldNotFindRootElement | CouldNotFindCommentError, ParentChildNodes> {
|
|
196
|
+
manyIndex?: string
|
|
197
|
+
): Either.Either<CouldNotFindRootElement | CouldNotFindManyCommentError | CouldNotFindCommentError, ParentChildNodes> {
|
|
198
198
|
const [, path] = parentTemplate.parts[partIndex]
|
|
199
199
|
const parentNode = findPath(where, path) as HTMLElement
|
|
200
200
|
const childNodesEither = findPartChildNodes(parentNode, childTemplate.hash, partIndex)
|
|
201
201
|
if (Either.isLeft(childNodesEither)) return Either.left(childNodesEither.left)
|
|
202
202
|
|
|
203
203
|
const childNodes = childNodesEither.right
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
204
|
+
|
|
205
|
+
if (manyIndex) {
|
|
206
|
+
const manyChildNodes = findManyChildNodes(childNodes, manyIndex)
|
|
207
|
+
if (Either.isLeft(manyChildNodes)) return Either.left(manyChildNodes.left)
|
|
208
|
+
return Either.right<ParentChildNodes>({ parentNode, childNodes: manyChildNodes.right })
|
|
207
209
|
}
|
|
208
210
|
|
|
209
|
-
return Either.right(
|
|
211
|
+
return Either.right<ParentChildNodes>({
|
|
212
|
+
parentNode,
|
|
213
|
+
childNodes
|
|
214
|
+
})
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
export function findManyChildNodes(
|
|
218
|
+
childNodes: Array<Node>,
|
|
219
|
+
manyIndex: string
|
|
220
|
+
): Either.Either<CouldNotFindManyCommentError, Array<Node>> {
|
|
221
|
+
const either = findManyComment(childNodes, manyIndex)
|
|
222
|
+
if (Either.isLeft(either)) return Either.left(either.left)
|
|
223
|
+
|
|
224
|
+
const [, index] = either.right
|
|
225
|
+
return Either.right(findPreviousManyComment(childNodes.slice(0, index)))
|
|
210
226
|
}
|
|
211
227
|
|
|
212
228
|
export function findPartChildNodes(
|
|
@@ -224,11 +240,10 @@ export function findPartChildNodes(
|
|
|
224
240
|
for (let i = index; i > -1; --i) {
|
|
225
241
|
const node = childNodes[i]
|
|
226
242
|
|
|
227
|
-
if (
|
|
228
|
-
nodes.unshift(node)
|
|
229
|
-
} else if (partIndex > 0 && isCommentWithValue(node, previousHoleValue)) {
|
|
243
|
+
if (partIndex > 0 && isCommentWithValue(node, previousHoleValue)) {
|
|
230
244
|
break
|
|
231
245
|
}
|
|
246
|
+
nodes.unshift(node)
|
|
232
247
|
}
|
|
233
248
|
} else {
|
|
234
249
|
return Either.right([...getPreviousNodes(comment, partIndex), comment])
|
|
@@ -260,9 +275,39 @@ export function findPartComment(
|
|
|
260
275
|
return Either.left(new CouldNotFindCommentError(partIndex))
|
|
261
276
|
}
|
|
262
277
|
|
|
278
|
+
export function findManyComment(
|
|
279
|
+
childNodes: ArrayLike<Node>,
|
|
280
|
+
manyIndex: string
|
|
281
|
+
): Either.Either<CouldNotFindManyCommentError, readonly [Comment, number]> {
|
|
282
|
+
const search = `many${manyIndex}`
|
|
283
|
+
|
|
284
|
+
for (let i = 0; i < childNodes.length; ++i) {
|
|
285
|
+
const node = childNodes[i]
|
|
286
|
+
|
|
287
|
+
if (isCommentWithValue(node, search)) {
|
|
288
|
+
return Either.right([node, i] as const)
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
return Either.left(new CouldNotFindManyCommentError(manyIndex))
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
export function findPreviousManyComment(
|
|
296
|
+
childNodes: Array<Node>
|
|
297
|
+
) {
|
|
298
|
+
for (let i = childNodes.length - 1; i > -1; --i) {
|
|
299
|
+
const node = childNodes[i]
|
|
300
|
+
|
|
301
|
+
if (isCommentStartingWithValue(node, "many")) {
|
|
302
|
+
return childNodes.slice(i + 1)
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
return childNodes
|
|
306
|
+
}
|
|
307
|
+
|
|
263
308
|
export function getHydrateEntry({
|
|
264
|
-
childIndex,
|
|
265
309
|
document,
|
|
310
|
+
manyIndex,
|
|
266
311
|
parentTemplate,
|
|
267
312
|
renderContext,
|
|
268
313
|
rootIndex,
|
|
@@ -275,15 +320,15 @@ export function getHydrateEntry({
|
|
|
275
320
|
rootIndex: number
|
|
276
321
|
parentTemplate: Template | null
|
|
277
322
|
strings: TemplateStringsArray
|
|
278
|
-
|
|
323
|
+
manyIndex?: string
|
|
279
324
|
}): Either.Either<
|
|
280
|
-
CouldNotFindRootElement | CouldNotFindCommentError,
|
|
325
|
+
CouldNotFindRootElement | CouldNotFindCommentError | CouldNotFindManyCommentError,
|
|
281
326
|
{ readonly template: Template; readonly wire: Node | Array<Node>; readonly where: ParentChildNodes }
|
|
282
327
|
> {
|
|
283
328
|
const { template } = getBrowserEntry(document, renderContext, strings)
|
|
284
329
|
|
|
285
330
|
if (parentTemplate) {
|
|
286
|
-
const either = findTemplateResultPartChildNodes(where, parentTemplate, template, rootIndex,
|
|
331
|
+
const either = findTemplateResultPartChildNodes(where, parentTemplate, template, rootIndex, manyIndex)
|
|
287
332
|
if (Either.isLeft(either)) {
|
|
288
333
|
return Either.left(either.left)
|
|
289
334
|
}
|
package/src/internal/parser.ts
CHANGED
|
@@ -715,7 +715,7 @@ function parseTextAndParts<T>(s: string, f: (index: number) => T): Array<Templat
|
|
|
715
715
|
return out
|
|
716
716
|
}
|
|
717
717
|
|
|
718
|
-
export const parser: Parser = globalValue(Symbol.for("@typed/template/
|
|
718
|
+
export const parser: Parser = globalValue(Symbol.for("@typed/template/Parser"), () => new ParserImpl())
|
|
719
719
|
|
|
720
720
|
const digestSize = 2
|
|
721
721
|
const multiplier = 33
|
package/src/internal/render.ts
CHANGED
|
@@ -43,9 +43,7 @@ import {
|
|
|
43
43
|
import type { ParentChildNodes } from "./utils.js"
|
|
44
44
|
import { findHoleComment, findPath } from "./utils.js"
|
|
45
45
|
|
|
46
|
-
// TODO: We need to add support for hydration of templates
|
|
47
46
|
// TODO: We need to re-think hydration for dynamic lists, probably just markers should be fine
|
|
48
|
-
// TODO: We need to make Parts synchronous
|
|
49
47
|
|
|
50
48
|
/**
|
|
51
49
|
* @internal
|
package/src/internal/utils.ts
CHANGED
|
@@ -14,6 +14,10 @@ export function isCommentWithValue(node: Node, value: string): node is Comment {
|
|
|
14
14
|
return isComment(node) && node.nodeValue === value
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
+
export function isCommentStartingWithValue(node: Node, value: string): node is Comment {
|
|
18
|
+
return isComment(node) && (node.nodeValue?.startsWith(value) ?? false)
|
|
19
|
+
}
|
|
20
|
+
|
|
17
21
|
export function isHtmlElement(node: Node): node is HTMLElement {
|
|
18
22
|
return node.nodeType === node.ELEMENT_NODE
|
|
19
23
|
}
|