@typed/template 0.9.6 → 0.10.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/RenderQueue/package.json +6 -0
- package/dist/cjs/Directive.js +1 -1
- package/dist/cjs/Directive.js.map +1 -1
- package/dist/cjs/ElementRef.js +1 -1
- package/dist/cjs/ElementRef.js.map +1 -1
- package/dist/cjs/ElementSource.js +1 -1
- package/dist/cjs/ElementSource.js.map +1 -1
- package/dist/cjs/EventHandler.js +11 -4
- package/dist/cjs/EventHandler.js.map +1 -1
- package/dist/cjs/Html.js +84 -44
- package/dist/cjs/Html.js.map +1 -1
- package/dist/cjs/HtmlChunk.js +67 -21
- package/dist/cjs/HtmlChunk.js.map +1 -1
- package/dist/cjs/Hydrate.js +6 -6
- package/dist/cjs/Hydrate.js.map +1 -1
- package/dist/cjs/Many.js +4 -4
- package/dist/cjs/Many.js.map +1 -1
- package/dist/cjs/Meta.js +10 -3
- package/dist/cjs/Meta.js.map +1 -1
- package/dist/cjs/Parser.js +1 -1
- package/dist/cjs/Placeholder.js +5 -9
- package/dist/cjs/Placeholder.js.map +1 -1
- package/dist/cjs/Platform.js +7 -5
- package/dist/cjs/Platform.js.map +1 -1
- package/dist/cjs/Render.js +8 -7
- package/dist/cjs/Render.js.map +1 -1
- package/dist/cjs/RenderContext.js +8 -92
- package/dist/cjs/RenderContext.js.map +1 -1
- package/dist/cjs/RenderEvent.js +9 -1
- package/dist/cjs/RenderEvent.js.map +1 -1
- package/dist/cjs/RenderQueue.js +341 -0
- package/dist/cjs/RenderQueue.js.map +1 -0
- package/dist/cjs/RenderTemplate.js +1 -1
- package/dist/cjs/RenderTemplate.js.map +1 -1
- package/dist/cjs/Template.js +12 -0
- package/dist/cjs/Template.js.map +1 -1
- package/dist/cjs/Test.js +64 -33
- package/dist/cjs/Test.js.map +1 -1
- package/dist/cjs/Vitest.js +12 -20
- package/dist/cjs/Vitest.js.map +1 -1
- package/dist/cjs/index.js +6 -3
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/internal/EventSource.js +16 -9
- package/dist/cjs/internal/EventSource.js.map +1 -1
- package/dist/cjs/internal/HydrateContext.js.map +1 -1
- package/dist/cjs/internal/browser.js +11 -10
- package/dist/cjs/internal/browser.js.map +1 -1
- package/dist/cjs/internal/character-entities.js +2141 -0
- package/dist/cjs/internal/character-entities.js.map +1 -0
- package/dist/cjs/internal/errors.js +19 -2
- package/dist/cjs/internal/errors.js.map +1 -1
- package/dist/cjs/internal/indexRefCounter.js +36 -63
- package/dist/cjs/internal/indexRefCounter.js.map +1 -1
- package/dist/cjs/internal/parser.js +18 -17
- package/dist/cjs/internal/parser.js.map +1 -1
- package/dist/cjs/internal/parser2.js +382 -0
- package/dist/cjs/internal/parser2.js.map +1 -0
- package/dist/cjs/internal/server-parts.js +124 -0
- package/dist/cjs/internal/server-parts.js.map +1 -0
- package/dist/cjs/internal/server.js +15 -185
- package/dist/cjs/internal/server.js.map +1 -1
- package/dist/cjs/internal/utils.js +73 -9
- package/dist/cjs/internal/utils.js.map +1 -1
- package/dist/cjs/internal/v2/SyncPart.js +6 -0
- package/dist/cjs/internal/v2/SyncPart.js.map +1 -0
- package/dist/cjs/internal/v2/helpers.js +15 -0
- package/dist/cjs/internal/v2/helpers.js.map +1 -0
- package/dist/cjs/internal/v2/hydrate.js +202 -0
- package/dist/cjs/internal/v2/hydrate.js.map +1 -0
- package/dist/cjs/internal/v2/hydration-template.js +269 -0
- package/dist/cjs/internal/v2/hydration-template.js.map +1 -0
- package/dist/cjs/internal/v2/parts.js +169 -0
- package/dist/cjs/internal/v2/parts.js.map +1 -0
- package/dist/cjs/internal/v2/render-entry.js +110 -0
- package/dist/cjs/internal/v2/render-entry.js.map +1 -0
- package/dist/cjs/internal/v2/render-sync-parts.js +318 -0
- package/dist/cjs/internal/v2/render-sync-parts.js.map +1 -0
- package/dist/cjs/internal/v2/render.js +417 -0
- package/dist/cjs/internal/v2/render.js.map +1 -0
- package/dist/cjs/internal/v2/sync-parts.js +115 -0
- package/dist/cjs/internal/v2/sync-parts.js.map +1 -0
- package/dist/dts/ElementRef.d.ts +1 -1
- package/dist/dts/ElementRef.d.ts.map +1 -1
- package/dist/dts/ElementSource.d.ts +1 -1
- package/dist/dts/ElementSource.d.ts.map +1 -1
- package/dist/dts/EventHandler.d.ts +12 -8
- package/dist/dts/EventHandler.d.ts.map +1 -1
- package/dist/dts/Html.d.ts +6 -5
- package/dist/dts/Html.d.ts.map +1 -1
- package/dist/dts/HtmlChunk.d.ts.map +1 -1
- package/dist/dts/Hydrate.d.ts +1 -3
- package/dist/dts/Hydrate.d.ts.map +1 -1
- package/dist/dts/Many.d.ts +9 -11
- package/dist/dts/Many.d.ts.map +1 -1
- package/dist/dts/Meta.d.ts +5 -1
- package/dist/dts/Meta.d.ts.map +1 -1
- package/dist/dts/Parser.d.ts +1 -1
- package/dist/dts/Parser.d.ts.map +1 -1
- package/dist/dts/Part.d.ts +20 -56
- package/dist/dts/Part.d.ts.map +1 -1
- package/dist/dts/Placeholder.d.ts +6 -10
- package/dist/dts/Placeholder.d.ts.map +1 -1
- package/dist/dts/Platform.d.ts +2 -4
- package/dist/dts/Platform.d.ts.map +1 -1
- package/dist/dts/Render.d.ts +4 -8
- package/dist/dts/Render.d.ts.map +1 -1
- package/dist/dts/RenderContext.d.ts +3 -22
- package/dist/dts/RenderContext.d.ts.map +1 -1
- package/dist/dts/RenderEvent.d.ts +6 -1
- package/dist/dts/RenderEvent.d.ts.map +1 -1
- package/dist/dts/RenderQueue.d.ts +103 -0
- package/dist/dts/RenderQueue.d.ts.map +1 -0
- package/dist/dts/RenderTemplate.d.ts +3 -2
- package/dist/dts/RenderTemplate.d.ts.map +1 -1
- package/dist/dts/Renderable.d.ts +1 -1
- package/dist/dts/Template.d.ts +14 -1
- package/dist/dts/Template.d.ts.map +1 -1
- package/dist/dts/Test.d.ts +14 -1
- package/dist/dts/Test.d.ts.map +1 -1
- package/dist/dts/Vitest.d.ts +11 -8
- package/dist/dts/Vitest.d.ts.map +1 -1
- package/dist/dts/index.d.ts +4 -0
- package/dist/dts/index.d.ts.map +1 -1
- package/dist/dts/internal/EventSource.d.ts +2 -1
- package/dist/dts/internal/EventSource.d.ts.map +1 -1
- package/dist/dts/internal/browser.d.ts +3 -3
- package/dist/dts/internal/browser.d.ts.map +1 -1
- package/dist/dts/internal/character-entities.d.ts +2133 -0
- package/dist/dts/internal/character-entities.d.ts.map +1 -0
- package/dist/dts/internal/errors.d.ts +9 -1
- package/dist/dts/internal/errors.d.ts.map +1 -1
- package/dist/dts/internal/indexRefCounter.d.ts +0 -4
- package/dist/dts/internal/indexRefCounter.d.ts.map +1 -1
- package/dist/dts/internal/parser.d.ts +13 -0
- package/dist/dts/internal/parser.d.ts.map +1 -1
- package/dist/dts/internal/parser2.d.ts +12 -0
- package/dist/dts/internal/parser2.d.ts.map +1 -0
- package/dist/dts/internal/server-parts.d.ts +223 -0
- package/dist/dts/internal/server-parts.d.ts.map +1 -0
- package/dist/dts/internal/server.d.ts +2 -28
- package/dist/dts/internal/server.d.ts.map +1 -1
- package/dist/dts/internal/utils.d.ts +4 -1
- package/dist/dts/internal/utils.d.ts.map +1 -1
- package/dist/dts/internal/v2/SyncPart.d.ts +87 -0
- package/dist/dts/internal/v2/SyncPart.d.ts.map +1 -0
- package/dist/dts/internal/v2/helpers.d.ts +3 -0
- package/dist/dts/internal/v2/helpers.d.ts.map +1 -0
- package/dist/dts/internal/v2/hydrate.d.ts +7 -0
- package/dist/dts/internal/v2/hydrate.d.ts.map +1 -0
- package/dist/dts/internal/v2/hydration-template.d.ts +54 -0
- package/dist/dts/internal/v2/hydration-template.d.ts.map +1 -0
- package/dist/dts/internal/v2/parts.d.ts +245 -0
- package/dist/dts/internal/v2/parts.d.ts.map +1 -0
- package/dist/dts/internal/v2/render-entry.d.ts +6 -0
- package/dist/dts/internal/v2/render-entry.d.ts.map +1 -0
- package/dist/dts/internal/v2/render-sync-parts.d.ts +22 -0
- package/dist/dts/internal/v2/render-sync-parts.d.ts.map +1 -0
- package/dist/dts/internal/v2/render.d.ts +62 -0
- package/dist/dts/internal/v2/render.d.ts.map +1 -0
- package/dist/dts/internal/v2/sync-parts.d.ts +129 -0
- package/dist/dts/internal/v2/sync-parts.d.ts.map +1 -0
- package/dist/esm/ElementRef.js.map +1 -1
- package/dist/esm/EventHandler.js +14 -4
- package/dist/esm/EventHandler.js.map +1 -1
- package/dist/esm/Html.js +91 -50
- package/dist/esm/Html.js.map +1 -1
- package/dist/esm/HtmlChunk.js +75 -24
- package/dist/esm/HtmlChunk.js.map +1 -1
- package/dist/esm/Hydrate.js +5 -5
- package/dist/esm/Hydrate.js.map +1 -1
- package/dist/esm/Many.js +3 -3
- package/dist/esm/Many.js.map +1 -1
- package/dist/esm/Meta.js +7 -1
- package/dist/esm/Meta.js.map +1 -1
- package/dist/esm/Parser.js +1 -1
- package/dist/esm/Parser.js.map +1 -1
- package/dist/esm/Placeholder.js +4 -8
- package/dist/esm/Placeholder.js.map +1 -1
- package/dist/esm/Platform.js +3 -1
- package/dist/esm/Platform.js.map +1 -1
- package/dist/esm/Render.js +6 -5
- package/dist/esm/Render.js.map +1 -1
- package/dist/esm/RenderContext.js +5 -85
- package/dist/esm/RenderContext.js.map +1 -1
- package/dist/esm/RenderEvent.js +8 -1
- package/dist/esm/RenderEvent.js.map +1 -1
- package/dist/esm/RenderQueue.js +336 -0
- package/dist/esm/RenderQueue.js.map +1 -0
- package/dist/esm/RenderTemplate.js.map +1 -1
- package/dist/esm/Template.js +12 -0
- package/dist/esm/Template.js.map +1 -1
- package/dist/esm/Test.js +71 -30
- package/dist/esm/Test.js.map +1 -1
- package/dist/esm/Vitest.js +11 -8
- package/dist/esm/Vitest.js.map +1 -1
- package/dist/esm/index.js +4 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/internal/EventSource.js +12 -7
- package/dist/esm/internal/EventSource.js.map +1 -1
- package/dist/esm/internal/HydrateContext.js.map +1 -1
- package/dist/esm/internal/browser.js +10 -9
- package/dist/esm/internal/browser.js.map +1 -1
- package/dist/esm/internal/character-entities.js +2134 -0
- package/dist/esm/internal/character-entities.js.map +1 -0
- package/dist/esm/internal/errors.js +22 -2
- package/dist/esm/internal/errors.js.map +1 -1
- package/dist/esm/internal/indexRefCounter.js +36 -61
- package/dist/esm/internal/indexRefCounter.js.map +1 -1
- package/dist/esm/internal/parser.js +18 -18
- package/dist/esm/internal/parser.js.map +1 -1
- package/dist/esm/internal/parser2.js +393 -0
- package/dist/esm/internal/parser2.js.map +1 -0
- package/dist/esm/internal/server-parts.js +109 -0
- package/dist/esm/internal/server-parts.js.map +1 -0
- package/dist/esm/internal/server.js +12 -161
- package/dist/esm/internal/server.js.map +1 -1
- package/dist/esm/internal/utils.js +71 -7
- package/dist/esm/internal/utils.js.map +1 -1
- package/dist/esm/internal/v2/SyncPart.js +5 -0
- package/dist/esm/internal/v2/SyncPart.js.map +1 -0
- package/dist/esm/internal/v2/helpers.js +12 -0
- package/dist/esm/internal/v2/helpers.js.map +1 -0
- package/dist/esm/internal/v2/hydrate.js +195 -0
- package/dist/esm/internal/v2/hydrate.js.map +1 -0
- package/dist/esm/internal/v2/hydration-template.js +265 -0
- package/dist/esm/internal/v2/hydration-template.js.map +1 -0
- package/dist/esm/internal/v2/parts.js +150 -0
- package/dist/esm/internal/v2/parts.js.map +1 -0
- package/dist/esm/internal/v2/render-entry.js +102 -0
- package/dist/esm/internal/v2/render-entry.js.map +1 -0
- package/dist/esm/internal/v2/render-sync-parts.js +265 -0
- package/dist/esm/internal/v2/render-sync-parts.js.map +1 -0
- package/dist/esm/internal/v2/render.js +353 -0
- package/dist/esm/internal/v2/render.js.map +1 -0
- package/dist/esm/internal/v2/sync-parts.js +102 -0
- package/dist/esm/internal/v2/sync-parts.js.map +1 -0
- package/package.json +20 -13
- package/src/ElementRef.ts +1 -1
- package/src/ElementSource.ts +1 -1
- package/src/EventHandler.ts +29 -11
- package/src/Html.ts +199 -90
- package/src/HtmlChunk.ts +77 -30
- package/src/Hydrate.ts +20 -14
- package/src/Many.ts +17 -14
- package/src/Meta.ts +8 -1
- package/src/Parser.ts +1 -1
- package/src/Part.ts +22 -66
- package/src/Placeholder.ts +17 -15
- package/src/Platform.ts +5 -5
- package/src/Render.ts +23 -26
- package/src/RenderContext.ts +14 -142
- package/src/RenderEvent.ts +10 -1
- package/src/RenderQueue.ts +445 -0
- package/src/RenderTemplate.ts +7 -2
- package/src/Renderable.ts +1 -1
- package/src/Template.ts +15 -1
- package/src/Test.ts +122 -38
- package/src/Vitest.ts +20 -10
- package/src/index.ts +4 -0
- package/src/internal/EventSource.ts +14 -8
- package/src/internal/HydrateContext.ts +3 -4
- package/src/internal/browser.ts +26 -21
- package/src/internal/character-entities.ts +2136 -0
- package/src/internal/errors.ts +30 -3
- package/src/internal/indexRefCounter.ts +38 -70
- package/src/internal/parser.ts +19 -19
- package/src/internal/parser2.ts +468 -0
- package/src/internal/server-parts.ts +161 -0
- package/src/internal/server.ts +16 -272
- package/src/internal/utils.ts +83 -7
- package/src/internal/v2/SyncPart.ts +112 -0
- package/src/internal/v2/helpers.ts +13 -0
- package/src/internal/v2/hydrate.ts +289 -0
- package/src/internal/v2/hydration-template.ts +308 -0
- package/src/internal/v2/parts.ts +254 -0
- package/src/internal/v2/render-entry.ts +131 -0
- package/src/internal/v2/render-sync-parts.ts +440 -0
- package/src/internal/v2/render.ts +588 -0
- package/src/internal/v2/sync-parts.ts +133 -0
- package/dist/cjs/internal/hydrate.js +0 -274
- package/dist/cjs/internal/hydrate.js.map +0 -1
- package/dist/cjs/internal/parts.js +0 -451
- package/dist/cjs/internal/parts.js.map +0 -1
- package/dist/cjs/internal/render.js +0 -704
- package/dist/cjs/internal/render.js.map +0 -1
- package/dist/dts/internal/hydrate.d.ts +0 -33
- package/dist/dts/internal/hydrate.d.ts.map +0 -1
- package/dist/dts/internal/parts.d.ts +0 -314
- package/dist/dts/internal/parts.d.ts.map +0 -1
- package/dist/dts/internal/render.d.ts +0 -16
- package/dist/dts/internal/render.d.ts.map +0 -1
- package/dist/esm/internal/hydrate.js +0 -239
- package/dist/esm/internal/hydrate.js.map +0 -1
- package/dist/esm/internal/parts.js +0 -373
- package/dist/esm/internal/parts.js.map +0 -1
- package/dist/esm/internal/render.js +0 -689
- package/dist/esm/internal/render.js.map +0 -1
- package/src/internal/hydrate.ts +0 -366
- package/src/internal/parts.ts +0 -609
- package/src/internal/render.ts +0 -971
|
@@ -0,0 +1,468 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/consistent-type-imports */
|
|
2
|
+
import * as Chunk from "effect/Chunk"
|
|
3
|
+
import type { IToken } from "html5parser"
|
|
4
|
+
import { tokenize } from "html5parser"
|
|
5
|
+
import * as Template from "../Template.js"
|
|
6
|
+
import { convertCharacterEntities } from "./character-entities.js"
|
|
7
|
+
import { PART_REGEX, PART_STRING } from "./chunks.js"
|
|
8
|
+
import { PathStack, templateHash, unsafeParsePartIndex } from "./parser.js"
|
|
9
|
+
import { keyToPartType } from "./utils.js"
|
|
10
|
+
|
|
11
|
+
export { templateHash } from "./parser.js"
|
|
12
|
+
|
|
13
|
+
// Unfortunately these are compiled as `const enum` and cannot be exported
|
|
14
|
+
enum TokenKind {
|
|
15
|
+
Literal = 0 as import("html5parser").TokenKind.Literal,
|
|
16
|
+
OpenTag = 1 as import("html5parser").TokenKind.OpenTag,
|
|
17
|
+
OpenTagEnd = 2 as import("html5parser").TokenKind.OpenTagEnd,
|
|
18
|
+
CloseTag = 3 as import("html5parser").TokenKind.CloseTag,
|
|
19
|
+
Whitespace = 4 as import("html5parser").TokenKind.Whitespace,
|
|
20
|
+
AttrValueEq = 5 as import("html5parser").TokenKind.AttrValueEq,
|
|
21
|
+
AttrValueNq = 6 as import("html5parser").TokenKind.AttrValueNq,
|
|
22
|
+
AttrValueSq = 7 as import("html5parser").TokenKind.AttrValueSq,
|
|
23
|
+
AttrValueDq = 8 as import("html5parser").TokenKind.AttrValueDq
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* @since 1.0.0
|
|
28
|
+
*/
|
|
29
|
+
export const TEXT_ONLY_NODES_REGEX = new Set([
|
|
30
|
+
"textarea",
|
|
31
|
+
"script",
|
|
32
|
+
"style",
|
|
33
|
+
"title",
|
|
34
|
+
"plaintext",
|
|
35
|
+
"xmp"
|
|
36
|
+
])
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* @since 1.0.0
|
|
40
|
+
*/
|
|
41
|
+
export const SELF_CLOSING_TAGS = new Set([
|
|
42
|
+
"area",
|
|
43
|
+
"base",
|
|
44
|
+
"br",
|
|
45
|
+
"col",
|
|
46
|
+
"command",
|
|
47
|
+
"embed",
|
|
48
|
+
"hr",
|
|
49
|
+
"img",
|
|
50
|
+
"input",
|
|
51
|
+
"keygen",
|
|
52
|
+
"link",
|
|
53
|
+
"meta",
|
|
54
|
+
"param",
|
|
55
|
+
"source",
|
|
56
|
+
"track",
|
|
57
|
+
"wbr"
|
|
58
|
+
])
|
|
59
|
+
|
|
60
|
+
class Parser {
|
|
61
|
+
protected html!: string
|
|
62
|
+
protected tokens!: Array<IToken>
|
|
63
|
+
protected index: number = 0
|
|
64
|
+
protected parts!: Array<readonly [part: Template.PartNode | Template.SparsePartNode, path: Chunk.Chunk<number>]>
|
|
65
|
+
protected path!: PathStack
|
|
66
|
+
|
|
67
|
+
parse(templateStrings: ReadonlyArray<string>): Template.Template {
|
|
68
|
+
this.init(templateStrings)
|
|
69
|
+
return new Template.Template(this.parseNodes(), templateHash(templateStrings), this.parts)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
private init(templateStrings: ReadonlyArray<string>) {
|
|
73
|
+
this.html = templateWithParts(templateStrings)
|
|
74
|
+
this.tokens = tokenize(this.html)
|
|
75
|
+
this.index = 0
|
|
76
|
+
this.parts = []
|
|
77
|
+
this.path = new PathStack()
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
private peek(): IToken | undefined {
|
|
81
|
+
return this.tokens[this.index]
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
private consumeNextTokenOfKind(kind: TokenKind) {
|
|
85
|
+
const token = this.tokens[this.index]
|
|
86
|
+
// @ts-expect-error
|
|
87
|
+
if (token.type !== kind) {
|
|
88
|
+
throw new Error(`Expected ${kind} but got ${token.type}`)
|
|
89
|
+
}
|
|
90
|
+
this.index++
|
|
91
|
+
return token
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
private consumeWhitespace() {
|
|
95
|
+
// @ts-expect-error
|
|
96
|
+
while (this.tokens[this.index]?.type === TokenKind.Whitespace) {
|
|
97
|
+
this.index++
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
private consumeNextTokenOfKinds(...kinds: Array<TokenKind>) {
|
|
102
|
+
const token = this.tokens[this.index]
|
|
103
|
+
if (!kinds.includes(token.type as any)) {
|
|
104
|
+
throw new Error(`Expected ${kinds.join(" or ")} but got ${token.type}`)
|
|
105
|
+
}
|
|
106
|
+
this.index++
|
|
107
|
+
return token
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
private parseNodes(): Array<Template.Node> {
|
|
111
|
+
const nodes: Array<Template.Node> = []
|
|
112
|
+
|
|
113
|
+
while (this.index < this.tokens.length) {
|
|
114
|
+
const token = this.peek()
|
|
115
|
+
if (token === undefined) {
|
|
116
|
+
break
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// @ts-expect-error
|
|
120
|
+
if (token.type === TokenKind.Literal) {
|
|
121
|
+
nodes.push(...this.parseNodeParts())
|
|
122
|
+
// @ts-expect-error
|
|
123
|
+
} else if (token.type === TokenKind.OpenTag) {
|
|
124
|
+
nodes.push(this.parseOpenTag())
|
|
125
|
+
this.path.inc()
|
|
126
|
+
// @ts-expect-error
|
|
127
|
+
} else if (token.type === TokenKind.CloseTag) {
|
|
128
|
+
this.index++
|
|
129
|
+
this.consumeWhitespace()
|
|
130
|
+
break
|
|
131
|
+
// @ts-expect-error
|
|
132
|
+
} else if (token.type === TokenKind.Whitespace) {
|
|
133
|
+
if (nodes.length > 0) {
|
|
134
|
+
this.path.inc()
|
|
135
|
+
nodes.push(new Template.TextNode(token.value))
|
|
136
|
+
}
|
|
137
|
+
this.index++
|
|
138
|
+
} else {
|
|
139
|
+
throw new Error(`Unexpected token ${token.type}`)
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return nodes
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
private parseNodeParts(): Array<Template.Node> {
|
|
147
|
+
const token = this.consumeNextTokenOfKind(TokenKind.Literal)
|
|
148
|
+
const parts = parseTextAndParts(
|
|
149
|
+
token.value,
|
|
150
|
+
(index) => new Template.NodePart(index)
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
return parts.map((p) => {
|
|
154
|
+
if (p._tag === "text") {
|
|
155
|
+
this.path.inc()
|
|
156
|
+
return p
|
|
157
|
+
} else {
|
|
158
|
+
return this.addPartWithPrevious(p)
|
|
159
|
+
}
|
|
160
|
+
})
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
private parseOpenTag(): Template.Node {
|
|
164
|
+
const { value: name } = this.consumeNextTokenOfKind(TokenKind.OpenTag)
|
|
165
|
+
|
|
166
|
+
// Comments
|
|
167
|
+
if (name === "!--") {
|
|
168
|
+
const node = this.parseCommentNode()
|
|
169
|
+
this.path.inc()
|
|
170
|
+
|
|
171
|
+
return node
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Doctype
|
|
175
|
+
if (name === "!doctype") {
|
|
176
|
+
this.consumeWhitespace()
|
|
177
|
+
const next = this.peek()
|
|
178
|
+
// @ts-expect-error
|
|
179
|
+
if (next && next.type === TokenKind.AttrValueNq) {
|
|
180
|
+
this.index++
|
|
181
|
+
this.consumeWhitespace()
|
|
182
|
+
this.consumeNextTokenOfKind(TokenKind.OpenTagEnd)
|
|
183
|
+
return new Template.DocType(next.value)
|
|
184
|
+
}
|
|
185
|
+
this.consumeNextTokenOfKind(TokenKind.OpenTagEnd)
|
|
186
|
+
return new Template.DocType("html")
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Self-closing tags
|
|
190
|
+
if (SELF_CLOSING_TAGS.has(name)) {
|
|
191
|
+
return this.parseSelfClosingElementNode(name)
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// Text-only nodes, e.g. <script>, <style>, <textarea>
|
|
195
|
+
if (TEXT_ONLY_NODES_REGEX.has(name)) {
|
|
196
|
+
return this.parseTextOnlyElementNode(name)
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const next = this.peek()
|
|
200
|
+
|
|
201
|
+
if (next === undefined) {
|
|
202
|
+
throw new Error(`Unexpected end of template at element node ${name}`)
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// @ts-expect-error No Attributes
|
|
206
|
+
if (next.type === TokenKind.OpenTagEnd) {
|
|
207
|
+
this.index++
|
|
208
|
+
this.path.push()
|
|
209
|
+
const children = this.parseNodes()
|
|
210
|
+
this.path.pop()
|
|
211
|
+
|
|
212
|
+
return new Template.ElementNode(name, [], children)
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
this.consumeWhitespace()
|
|
216
|
+
|
|
217
|
+
const attributes = this.parseAttributes()
|
|
218
|
+
this.path.push()
|
|
219
|
+
const children = this.parseNodes()
|
|
220
|
+
this.path.pop()
|
|
221
|
+
|
|
222
|
+
return new Template.ElementNode(name, attributes, children)
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
private parseCommentNode(): Template.Node {
|
|
226
|
+
const { value } = this.consumeNextTokenOfKind(TokenKind.Literal)
|
|
227
|
+
this.consumeNextTokenOfKind(TokenKind.OpenTagEnd)
|
|
228
|
+
|
|
229
|
+
const parts = parseTextAndParts(
|
|
230
|
+
value,
|
|
231
|
+
(index) => new Template.CommentPartNode(index)
|
|
232
|
+
)
|
|
233
|
+
|
|
234
|
+
if (parts.length === 1) {
|
|
235
|
+
if (parts[0]._tag === "text") {
|
|
236
|
+
return new Template.CommentNode(parts[0].value)
|
|
237
|
+
} else {
|
|
238
|
+
return this.addPart(parts[0])
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
return this.addPart(new Template.SparseCommentNode(parts))
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
private parseSelfClosingElementNode(name: string): Template.Node {
|
|
246
|
+
return new Template.SelfClosingElementNode(name, this.parseAttributes())
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
private parseTextOnlyElementNode(name: string): Template.Node {
|
|
250
|
+
const attributes = this.parseAttributes()
|
|
251
|
+
this.path.push()
|
|
252
|
+
const children = this.parseTextOnlyChildren()
|
|
253
|
+
this.path.pop()
|
|
254
|
+
|
|
255
|
+
return new Template.TextOnlyElement(name, attributes, children)
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
private parseAttributes(): Array<Template.Attribute> {
|
|
259
|
+
const attributes: Array<Template.Attribute> = []
|
|
260
|
+
|
|
261
|
+
this.consumeWhitespace()
|
|
262
|
+
|
|
263
|
+
while (this.index < this.tokens.length) {
|
|
264
|
+
const token = this.peek()
|
|
265
|
+
|
|
266
|
+
if (token === undefined) {
|
|
267
|
+
throw new Error("Unexpected end of template in attributes")
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
if (
|
|
271
|
+
// @ts-expect-error
|
|
272
|
+
token.type === TokenKind.Whitespace
|
|
273
|
+
) {
|
|
274
|
+
this.index++
|
|
275
|
+
continue
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
if (
|
|
279
|
+
// @ts-expect-error
|
|
280
|
+
token.type === TokenKind.OpenTagEnd
|
|
281
|
+
) {
|
|
282
|
+
this.index++
|
|
283
|
+
break
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
if (
|
|
287
|
+
// @ts-expect-error
|
|
288
|
+
token.type === TokenKind.CloseTag
|
|
289
|
+
) {
|
|
290
|
+
break
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
const [shouldContinue, attr] = this.parseAttribute()
|
|
294
|
+
|
|
295
|
+
attributes.push(attr)
|
|
296
|
+
|
|
297
|
+
if (shouldContinue === false) {
|
|
298
|
+
break
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
return attributes
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
private parseAttribute(): [boolean, Template.Attribute] {
|
|
306
|
+
const { value: rawName } = this.consumeNextTokenOfKind(TokenKind.AttrValueNq)
|
|
307
|
+
|
|
308
|
+
if (rawName.startsWith("...")) {
|
|
309
|
+
return [true, this.addPart(new Template.PropertiesPartNode(unsafeParsePartIndex(rawName.slice(3))))]
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
const [match, name] = keyToPartType(rawName)
|
|
313
|
+
const next = this.peek()
|
|
314
|
+
|
|
315
|
+
// @ts-expect-error
|
|
316
|
+
if (next.type === TokenKind.AttrValueEq) {
|
|
317
|
+
this.consumeNextTokenOfKind(TokenKind.AttrValueEq)
|
|
318
|
+
const { type, value: rawValue } = this.consumeNextTokenOfKinds(
|
|
319
|
+
TokenKind.AttrValueDq,
|
|
320
|
+
TokenKind.AttrValueSq,
|
|
321
|
+
TokenKind.AttrValueNq
|
|
322
|
+
)
|
|
323
|
+
|
|
324
|
+
// @ts-expect-error
|
|
325
|
+
const value = type === TokenKind.AttrValueNq ? rawValue : rawValue.slice(1, -1)
|
|
326
|
+
|
|
327
|
+
switch (match) {
|
|
328
|
+
case "attr": {
|
|
329
|
+
const parts = parseTextAndParts(value, (index) => new Template.AttrPartNode(name, index))
|
|
330
|
+
|
|
331
|
+
if (parts.length === 0) return [true, new Template.AttributeNode(name, "")]
|
|
332
|
+
|
|
333
|
+
if (parts.length === 1) {
|
|
334
|
+
if (parts[0]._tag === "text") {
|
|
335
|
+
return [true, new Template.AttributeNode(name, parts[0].value)]
|
|
336
|
+
} else {
|
|
337
|
+
return [true, this.addPart(new Template.AttrPartNode(name, parts[0].index))]
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
return [true, this.addPart(new Template.SparseAttrNode(name, parts))]
|
|
342
|
+
}
|
|
343
|
+
case "boolean": {
|
|
344
|
+
const parts = parseTextAndParts(value, (index) => new Template.BooleanPartNode(name, index))
|
|
345
|
+
if (parts.length === 1) {
|
|
346
|
+
if (parts[0]._tag === "text") {
|
|
347
|
+
return [true, new Template.BooleanNode(name)]
|
|
348
|
+
} else {
|
|
349
|
+
return [true, this.addPart(parts[0])]
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
throw new Error("Boolean attributes cannot have multiple parts")
|
|
354
|
+
}
|
|
355
|
+
case "class": {
|
|
356
|
+
const parts = parseTextAndParts(value, (index) => new Template.ClassNamePartNode(index))
|
|
357
|
+
if (parts.length === 1) {
|
|
358
|
+
if (parts[0]._tag === "text") {
|
|
359
|
+
return [true, new Template.AttributeNode("class", parts[0].value.trim())]
|
|
360
|
+
} else {
|
|
361
|
+
return [true, this.addPart(parts[0])]
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
return [true, this.addPart(new Template.SparseClassNameNode(parts))]
|
|
366
|
+
}
|
|
367
|
+
case "data":
|
|
368
|
+
return [true, this.addPart(new Template.DataPartNode(unsafeParsePartIndex(value)))]
|
|
369
|
+
case "event":
|
|
370
|
+
return [true, this.addPart(new Template.EventPartNode(name, unsafeParsePartIndex(value)))]
|
|
371
|
+
case "properties":
|
|
372
|
+
return [true, this.addPart(new Template.PropertiesPartNode(unsafeParsePartIndex(value)))]
|
|
373
|
+
case "property":
|
|
374
|
+
return [true, this.addPart(new Template.PropertyPartNode(name, unsafeParsePartIndex(value)))]
|
|
375
|
+
case "ref":
|
|
376
|
+
return [true, this.addPart(new Template.RefPartNode(unsafeParsePartIndex(value)))]
|
|
377
|
+
}
|
|
378
|
+
// @ts-expect-error
|
|
379
|
+
} else if (next.type === TokenKind.Whitespace) {
|
|
380
|
+
this.index++
|
|
381
|
+
return [true, new Template.BooleanNode(name!)]
|
|
382
|
+
// @ts-expect-error
|
|
383
|
+
} else if (next.type === TokenKind.OpenTagEnd) {
|
|
384
|
+
this.index++
|
|
385
|
+
this.consumeWhitespace()
|
|
386
|
+
return [false, new Template.BooleanNode(name!)]
|
|
387
|
+
} else {
|
|
388
|
+
if (next === undefined) {
|
|
389
|
+
throw new Error(`Unexpected end of template at attribute ${name}`)
|
|
390
|
+
}
|
|
391
|
+
throw new Error(`Unexpected token ${TokenKind[next.type]} in place of attribute`)
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
private parseTextOnlyChildren(): Array<Template.Text> {
|
|
396
|
+
const { type, value } = this.consumeNextTokenOfKinds(TokenKind.Literal, TokenKind.CloseTag)
|
|
397
|
+
|
|
398
|
+
// @ts-expect-error
|
|
399
|
+
if (type === TokenKind.Literal) {
|
|
400
|
+
this.consumeNextTokenOfKind(TokenKind.CloseTag)
|
|
401
|
+
return parseTextAndParts(value, (index) => this.addPartWithPrevious(new Template.TextPartNode(index)))
|
|
402
|
+
}
|
|
403
|
+
this.consumeWhitespace()
|
|
404
|
+
return []
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
private addPart<T extends Template.PartNode | Template.SparsePartNode>(part: T): T {
|
|
408
|
+
this.parts.push([part, this.path.toChunk()])
|
|
409
|
+
return part
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
private addPartWithPrevious<T extends Template.PartNode | Template.SparsePartNode>(part: T): T {
|
|
413
|
+
this.parts.push([part, this.path.previousChunk()])
|
|
414
|
+
this.path.inc() // Nodes will be inserted as a comment
|
|
415
|
+
return part
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
const parser = new Parser()
|
|
420
|
+
|
|
421
|
+
export function parse(template: ReadonlyArray<string>): Template.Template {
|
|
422
|
+
return parser.parse(template)
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
function templateWithParts(template: ReadonlyArray<string>): string {
|
|
426
|
+
const length = template.length
|
|
427
|
+
const lastIndex = length - 1
|
|
428
|
+
|
|
429
|
+
let output = ""
|
|
430
|
+
|
|
431
|
+
for (let i = 0; i < length; i++) {
|
|
432
|
+
const str = template[i]
|
|
433
|
+
|
|
434
|
+
if (i === lastIndex) {
|
|
435
|
+
output += str
|
|
436
|
+
} else {
|
|
437
|
+
output += str + PART_STRING(i)
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
return output
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
function parseTextAndParts<T>(
|
|
445
|
+
s: string,
|
|
446
|
+
f: (index: number) => T
|
|
447
|
+
): Array<Template.TextNode | T> {
|
|
448
|
+
let skipWhitespace = false
|
|
449
|
+
const out: Array<Template.TextNode | T> = []
|
|
450
|
+
const parts = s.split(PART_REGEX)
|
|
451
|
+
const last = parts.length - 2
|
|
452
|
+
|
|
453
|
+
for (let i = 0; i < parts.length; i++) {
|
|
454
|
+
const part = parts[i]
|
|
455
|
+
|
|
456
|
+
if (part[0] === "{" && part[1] === "{") {
|
|
457
|
+
out.push(f(parseInt(parts[++i], 10)))
|
|
458
|
+
// If we encounter a part, we should not skip whitespace
|
|
459
|
+
skipWhitespace = i === last
|
|
460
|
+
} else if (((skipWhitespace || i === 0) ? part.trim() : part) === "") {
|
|
461
|
+
continue
|
|
462
|
+
} else {
|
|
463
|
+
out.push(new Template.TextNode(convertCharacterEntities(part)))
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
return out
|
|
468
|
+
}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import type { Cause } from "effect/Cause"
|
|
2
|
+
import * as Effect from "effect/Effect"
|
|
3
|
+
import { equals } from "effect/Equal"
|
|
4
|
+
import * as Equivalence from "effect/Equivalence"
|
|
5
|
+
import type { Scope } from "effect/Scope"
|
|
6
|
+
import type { ElementSource } from "../ElementSource.js"
|
|
7
|
+
import type { EventHandler } from "../EventHandler.js"
|
|
8
|
+
import type {
|
|
9
|
+
AttributePart,
|
|
10
|
+
BooleanPart,
|
|
11
|
+
ClassNamePart,
|
|
12
|
+
CommentPart,
|
|
13
|
+
DataPart,
|
|
14
|
+
EventPart,
|
|
15
|
+
NodePart,
|
|
16
|
+
Part,
|
|
17
|
+
PropertiesPart,
|
|
18
|
+
PropertyPart,
|
|
19
|
+
RefPart,
|
|
20
|
+
TextPart
|
|
21
|
+
} from "../Part.js"
|
|
22
|
+
import { DEFAULT_PRIORITY } from "../RenderQueue.js"
|
|
23
|
+
|
|
24
|
+
const strictEq = Equivalence.strict<any>()
|
|
25
|
+
|
|
26
|
+
const base = <T extends Part["_tag"]>(tag: T) => (class Base {
|
|
27
|
+
readonly _tag: T = tag
|
|
28
|
+
|
|
29
|
+
constructor(
|
|
30
|
+
readonly index: number,
|
|
31
|
+
readonly commit: (
|
|
32
|
+
params: {
|
|
33
|
+
previous: Extract<Part, { readonly _tag: T }>["value"]
|
|
34
|
+
value: Extract<Part, { readonly _tag: T }>["value"]
|
|
35
|
+
part: Extract<Part, { readonly _tag: T }>
|
|
36
|
+
},
|
|
37
|
+
priority: number
|
|
38
|
+
) => Effect.Effect<void, never, Scope>,
|
|
39
|
+
public value: Extract<Part, { readonly _tag: T }>["value"],
|
|
40
|
+
readonly eq: Equivalence.Equivalence<Extract<Part, { readonly _tag: T }>["value"]> = equals
|
|
41
|
+
) {
|
|
42
|
+
this.update = this.update.bind(this)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
update(input: this["value"], priority: number = DEFAULT_PRIORITY) {
|
|
46
|
+
const previous = this.value as any
|
|
47
|
+
const value = this.getValue(input) as any
|
|
48
|
+
|
|
49
|
+
if (this.eq(previous as any, value as any)) {
|
|
50
|
+
return Effect.void
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return Effect.flatMap(
|
|
54
|
+
this.commit.call(this, {
|
|
55
|
+
previous,
|
|
56
|
+
value,
|
|
57
|
+
part: this as any
|
|
58
|
+
}, priority),
|
|
59
|
+
() => Effect.sync(() => this.value = value)
|
|
60
|
+
)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
getValue(value: unknown) {
|
|
64
|
+
return value
|
|
65
|
+
}
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
export class AttributePartImpl extends base("attribute") implements AttributePart {
|
|
69
|
+
constructor(
|
|
70
|
+
readonly name: string,
|
|
71
|
+
index: number,
|
|
72
|
+
commit: AttributePartImpl["commit"],
|
|
73
|
+
value: AttributePart["value"]
|
|
74
|
+
) {
|
|
75
|
+
super(index, commit, value, strictEq)
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export class BooleanPartImpl extends base("boolean") implements BooleanPart {
|
|
80
|
+
constructor(
|
|
81
|
+
readonly name: string,
|
|
82
|
+
index: number,
|
|
83
|
+
commit: BooleanPartImpl["commit"],
|
|
84
|
+
value: BooleanPart["value"]
|
|
85
|
+
) {
|
|
86
|
+
super(index, commit, value, strictEq)
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const isString = (x: unknown): x is string => typeof x === "string"
|
|
91
|
+
|
|
92
|
+
export class ClassNamePartImpl extends base("className") implements ClassNamePart {
|
|
93
|
+
constructor(
|
|
94
|
+
index: number,
|
|
95
|
+
commit: ClassNamePartImpl["commit"],
|
|
96
|
+
value: ClassNamePart["value"]
|
|
97
|
+
) {
|
|
98
|
+
super(index, commit, value, strictEq)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
getValue(value: unknown): ReadonlyArray<string> {
|
|
102
|
+
if (isString(value)) {
|
|
103
|
+
return value.split(" ").filter((x) => isString(x) && x.trim() !== "")
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (Array.isArray(value)) {
|
|
107
|
+
return value.filter((x) => isString(x) && x.trim() !== "")
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return []
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export class CommentPartImpl extends base("comment") implements CommentPart {}
|
|
115
|
+
|
|
116
|
+
export class DataPartImpl extends base("data") implements DataPart {}
|
|
117
|
+
|
|
118
|
+
export class EventPartImpl implements EventPart {
|
|
119
|
+
readonly _tag = "event"
|
|
120
|
+
readonly value: EventPart["value"] = null
|
|
121
|
+
|
|
122
|
+
constructor(
|
|
123
|
+
readonly name: string,
|
|
124
|
+
readonly index: number,
|
|
125
|
+
readonly source: ElementSource<any>,
|
|
126
|
+
readonly onCause: <E>(cause: Cause<E>) => Effect.Effect<unknown>,
|
|
127
|
+
readonly addEventListener: <Ev extends Event>(handler: EventHandler<Ev>) => void
|
|
128
|
+
) {
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export class NodePartImpl extends base("node") implements NodePart {}
|
|
133
|
+
|
|
134
|
+
export class PropertyPartImpl extends base("property") implements PropertyPart {
|
|
135
|
+
constructor(
|
|
136
|
+
readonly name: string,
|
|
137
|
+
index: number,
|
|
138
|
+
commit: PropertyPartImpl["commit"],
|
|
139
|
+
value: PropertyPartImpl["value"]
|
|
140
|
+
) {
|
|
141
|
+
super(index, commit, value, strictEq)
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export class PropertiesPartImpl extends base("properties") implements PropertiesPart {
|
|
146
|
+
constructor(
|
|
147
|
+
index: number,
|
|
148
|
+
commit: PropertiesPartImpl["commit"],
|
|
149
|
+
value: PropertiesPartImpl["value"]
|
|
150
|
+
) {
|
|
151
|
+
super(index, commit, value, strictEq)
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export class RefPartImpl implements RefPart {
|
|
156
|
+
readonly _tag = "ref"
|
|
157
|
+
|
|
158
|
+
constructor(readonly value: ElementSource<any>, readonly index: number) {}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export class TextPartImpl extends base("text") implements TextPart {}
|