@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
|
@@ -0,0 +1,637 @@
|
|
|
1
|
+
import * as Chunk from "effect/Chunk"
|
|
2
|
+
import { globalValue } from "effect/GlobalValue"
|
|
3
|
+
import * as Option from "effect/Option"
|
|
4
|
+
import * as Template from "../Template"
|
|
5
|
+
import { SELF_CLOSING_TAGS, TEXT_ONLY_NODES_REGEX } from "../Token"
|
|
6
|
+
import type { TextChunk } from "./chunks"
|
|
7
|
+
import { getPart, getStrictPart, getTextUntilCloseBrace, getTextUntilPart, getWhitespace, PART_STRING } from "./chunks"
|
|
8
|
+
|
|
9
|
+
// TODO: Consider ways to surface useful errors and warnings.
|
|
10
|
+
// TODO: Profile for performance optimization
|
|
11
|
+
|
|
12
|
+
export interface Parser {
|
|
13
|
+
parse(templateStrings: ReadonlyArray<string>): Template.Template
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function parse(templateStrings: ReadonlyArray<string>): Template.Template {
|
|
17
|
+
return parser.parse(templateStrings)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const SPACE_REGEX = /\s/
|
|
21
|
+
const isPartToken: TextPredicate = (input, pos) => input[pos] === "{" && input.slice(pos, pos + 8) === "{{__PART"
|
|
22
|
+
const isPartEndToken: TextPredicate = (input, pos) => input[pos] === "_" && input.slice(pos, pos + 4) === "__}}"
|
|
23
|
+
const isElementOpenToken: TextPredicate = (input, pos) => input[pos] === "<" && input[pos + 1] !== "/"
|
|
24
|
+
const isElementCloseToken: TextPredicate = (input, pos) => input[pos] === "<" && input[pos + 1] === "/"
|
|
25
|
+
const isEqualsToken: TextPredicate = (input, pos) => input[pos] === "="
|
|
26
|
+
const isQuoteToken: TextPredicate = (input, pos) => input[pos] === `"`
|
|
27
|
+
const isSingleQuoteToken: TextPredicate = (input, pos) => input[pos] === "'"
|
|
28
|
+
const isWhitespaceToken: TextPredicate = (input, pos) => SPACE_REGEX.test(input[pos])
|
|
29
|
+
const isOpenTagEndToken: TextPredicate = (input, pos) => input[pos] === ">"
|
|
30
|
+
const isSelfClosingTagEndToken: TextPredicate = (input, pos) => input[pos] === "/" && input[pos + 1] === ">"
|
|
31
|
+
const isCommentEndToken: TextPredicate = (input, pos) =>
|
|
32
|
+
input[pos] === "-" && input[pos + 1] === "-" && input[pos + 2] === ">"
|
|
33
|
+
|
|
34
|
+
type Context = "unknown" | "element"
|
|
35
|
+
|
|
36
|
+
type TextPredicate = (input: string, pos: number) => boolean
|
|
37
|
+
|
|
38
|
+
type LoopDecision<A> = Continue<A> | Break<A> | Skip
|
|
39
|
+
|
|
40
|
+
type Continue<A> = ["continue", A]
|
|
41
|
+
const Continue = <A>(a: A): Continue<A> => ["continue", a]
|
|
42
|
+
|
|
43
|
+
type Break<A> = ["break", Option.Option<A>]
|
|
44
|
+
const Break = <A>(a?: A): Break<A> => ["break", Option.fromNullable(a)]
|
|
45
|
+
|
|
46
|
+
type Skip = ["skip"]
|
|
47
|
+
const Skip: Skip = ["skip"]
|
|
48
|
+
|
|
49
|
+
type Predicates = {
|
|
50
|
+
[key: string]: (char: string, pos: number) => boolean
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const BREAK_ATTR = Break<Array<Template.Attribute>>()
|
|
54
|
+
|
|
55
|
+
const tagNameMatches = {
|
|
56
|
+
whitespace: isWhitespaceToken,
|
|
57
|
+
openTagEnd: isOpenTagEndToken,
|
|
58
|
+
selfClosingTagEnd: isSelfClosingTagEndToken
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const attributeMatches = {
|
|
62
|
+
whitespace: isWhitespaceToken,
|
|
63
|
+
equals: isEqualsToken,
|
|
64
|
+
closingTag: isElementCloseToken,
|
|
65
|
+
openTagEnd: isOpenTagEndToken,
|
|
66
|
+
selfClosingTagEnd: isSelfClosingTagEndToken
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const attributeValueMatches = {
|
|
70
|
+
base: isWhitespaceToken,
|
|
71
|
+
openTagEnd: isOpenTagEndToken,
|
|
72
|
+
selfClosingTagEnd: isSelfClosingTagEndToken
|
|
73
|
+
} satisfies Predicates
|
|
74
|
+
|
|
75
|
+
const textChildMatches = {
|
|
76
|
+
part: isPartToken,
|
|
77
|
+
elementOpen: isElementOpenToken,
|
|
78
|
+
elementClose: isElementCloseToken
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
class PathStack {
|
|
82
|
+
chunk: Chunk.Chunk<number> = Chunk.empty()
|
|
83
|
+
count = 0
|
|
84
|
+
|
|
85
|
+
inc() {
|
|
86
|
+
this.count++
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
push(): void {
|
|
90
|
+
this.chunk = this.toChunk()
|
|
91
|
+
this.count = 0
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
pop(): void {
|
|
95
|
+
this.count = Chunk.unsafeLast(this.chunk)
|
|
96
|
+
this.chunk = Chunk.dropRight(this.chunk, 1)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
toChunk(): Chunk.Chunk<number> {
|
|
100
|
+
return Chunk.append(this.chunk, this.count)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
previousChunk() {
|
|
104
|
+
return this.chunk
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const predicatesCache = new WeakMap<Predicates, readonly [ReadonlyArray<string>, number]>()
|
|
109
|
+
|
|
110
|
+
function getPredicatesCache(predicates: Predicates) {
|
|
111
|
+
const cached = predicatesCache.get(predicates)
|
|
112
|
+
|
|
113
|
+
if (cached === undefined) {
|
|
114
|
+
const keys = Object.keys(predicates)
|
|
115
|
+
const length = keys.length
|
|
116
|
+
const toCache = [keys, length] as const
|
|
117
|
+
|
|
118
|
+
predicatesCache.set(predicates, toCache)
|
|
119
|
+
|
|
120
|
+
return toCache
|
|
121
|
+
} else {
|
|
122
|
+
return cached
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
class ParserImpl implements Parser {
|
|
127
|
+
context!: Context
|
|
128
|
+
input!: string
|
|
129
|
+
length!: number
|
|
130
|
+
parts!: Array<[Template.PartNode | Template.SparsePartNode, Chunk.Chunk<number>]>
|
|
131
|
+
pos!: number
|
|
132
|
+
path!: PathStack
|
|
133
|
+
_skipWhitespace!: boolean
|
|
134
|
+
|
|
135
|
+
parse(templateStrings: ReadonlyArray<string>): Template.Template {
|
|
136
|
+
this.init(templateStrings)
|
|
137
|
+
|
|
138
|
+
return this.parseTemplate(templateHash(templateStrings))
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
private parseTemplate(hash: string) {
|
|
142
|
+
return new Template.Template(this.parseTemplateChildren(), hash, this.parts)
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
private parseTemplateChildren() {
|
|
146
|
+
const nodes: Array<Template.Node> = []
|
|
147
|
+
|
|
148
|
+
while (this.pos < this.length) {
|
|
149
|
+
const node = this.parseNodeFromContext(this.context)
|
|
150
|
+
if (node === undefined) {
|
|
151
|
+
return nodes
|
|
152
|
+
} else {
|
|
153
|
+
nodes.push(...node)
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return nodes
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
protected parseNodeFromContext(ctx: Context): Array<Template.Node> | undefined {
|
|
161
|
+
if (ctx === "element") {
|
|
162
|
+
return [this.parseElement()]
|
|
163
|
+
} else {
|
|
164
|
+
return this.parseUnknown()
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
private parseUnknown(): Array<Template.Node> | undefined {
|
|
169
|
+
if (this.nextChar() === "<") { // Open tag / comment / self-closing tag
|
|
170
|
+
return this.openBracket()
|
|
171
|
+
} else {
|
|
172
|
+
return this.unknownChunk()
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
private openBracket() {
|
|
177
|
+
this.consumeAmount(1)
|
|
178
|
+
this.skipWhitespace()
|
|
179
|
+
|
|
180
|
+
const nextChar = this.nextChar()
|
|
181
|
+
|
|
182
|
+
if (nextChar === "!") { // Comment
|
|
183
|
+
this.consumeAmount(3)
|
|
184
|
+
|
|
185
|
+
return [this.parseComment()]
|
|
186
|
+
} else if (nextChar === "/") { // Self-closing tag
|
|
187
|
+
return this.selfClosingTagEnd()
|
|
188
|
+
} else { // Elements
|
|
189
|
+
return [this.parseElement()]
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
private selfClosingTagEnd() {
|
|
194
|
+
this.consumeAmount(1)
|
|
195
|
+
this.parseTagName()
|
|
196
|
+
this.skipWhitespace()
|
|
197
|
+
this.consumeAmount(1)
|
|
198
|
+
this.context = "unknown"
|
|
199
|
+
return undefined
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
private unknownChunk() {
|
|
203
|
+
let next: TextChunk | undefined
|
|
204
|
+
|
|
205
|
+
if ((next = this.chunk(getPart))) { // Parts
|
|
206
|
+
this._skipWhitespace = false
|
|
207
|
+
return [this.addPartWithPrevious(new Template.NodePart(parseInt(next.match[2], 10)))]
|
|
208
|
+
} else if ((next = this.chunk(getWhitespace))) { // Whitespace
|
|
209
|
+
return this._skipWhitespace ? [] : [new Template.TextNode(next.match[1])]
|
|
210
|
+
} else if ((next = this.chunk(getTextUntilCloseBrace))) { // Text and parts
|
|
211
|
+
return parseTextAndParts(next.match[1], (i) => this.addPartWithPrevious(new Template.NodePart(i)))
|
|
212
|
+
} else {
|
|
213
|
+
return [new Template.TextNode(this.nextChar())]
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
private parseElement(): Template.ParentNode {
|
|
218
|
+
const node = this.parseElementKind()
|
|
219
|
+
this.path.inc()
|
|
220
|
+
|
|
221
|
+
this.context = "unknown"
|
|
222
|
+
this._skipWhitespace = true
|
|
223
|
+
|
|
224
|
+
return node
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
private parseElementKind() {
|
|
228
|
+
this.context = "element"
|
|
229
|
+
|
|
230
|
+
const [tagName, matched] = this.parseTagName()
|
|
231
|
+
|
|
232
|
+
if (matched === "whitespace") {
|
|
233
|
+
this.skipWhitespace()
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
if (SELF_CLOSING_TAGS.has(tagName)) {
|
|
237
|
+
return this.parseSelfClosingElement(tagName)
|
|
238
|
+
} else if (TEXT_ONLY_NODES_REGEX.has(tagName)) {
|
|
239
|
+
return this.parseTextOnlyElement(tagName)
|
|
240
|
+
} else {
|
|
241
|
+
const attributes = this.parseAttributes()
|
|
242
|
+
this.path.push()
|
|
243
|
+
const children = this.parseTemplateChildren()
|
|
244
|
+
this.path.pop()
|
|
245
|
+
const element = new Template.ElementNode(tagName, attributes, children)
|
|
246
|
+
|
|
247
|
+
return element
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
private parseSelfClosingElement(tagName: string): Template.SelfClosingElementNode {
|
|
252
|
+
const attributes = this.parseAttributes()
|
|
253
|
+
|
|
254
|
+
return new Template.SelfClosingElementNode(tagName, attributes)
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
private parseTextOnlyElement(tagName: string): Template.TextOnlyElement {
|
|
258
|
+
const attributes = this.parseAttributes()
|
|
259
|
+
this.path.push()
|
|
260
|
+
const children = this.parseTextChildren()
|
|
261
|
+
this.path.pop()
|
|
262
|
+
|
|
263
|
+
return new Template.TextOnlyElement(tagName, attributes, children || [])
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
private parseComment(): Template.Comment {
|
|
267
|
+
const text = this.parseTextUntil(isCommentEndToken)
|
|
268
|
+
this.consumeAmount(3)
|
|
269
|
+
|
|
270
|
+
const textAndParts = parseTextAndParts(text, (i) => new Template.CommentPartNode(i))
|
|
271
|
+
|
|
272
|
+
if (textAndParts.length === 1) {
|
|
273
|
+
const part = textAndParts[0]
|
|
274
|
+
|
|
275
|
+
if (part._tag === "text") {
|
|
276
|
+
return new Template.CommentNode(part.value)
|
|
277
|
+
} else {
|
|
278
|
+
return this.addPart(new Template.CommentPartNode(part.index))
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
return this.addPart(new Template.SparseCommentNode(textAndParts))
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
private parseTagName() {
|
|
286
|
+
return this.parseTextUntilMany(tagNameMatches)
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
private parseAttributes(): Array<Template.Attribute> {
|
|
290
|
+
return this.parseArray<Template.Attribute>(() => this.parseAttribute()) || []
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
private parseAttribute(): LoopDecision<Array<Template.Attribute>> {
|
|
294
|
+
const [name, matched] = this.parseTextUntilMany(attributeMatches)
|
|
295
|
+
|
|
296
|
+
switch (matched) {
|
|
297
|
+
case null:
|
|
298
|
+
return Skip
|
|
299
|
+
case "whitespace":
|
|
300
|
+
return Continue([new Template.BooleanNode(name)])
|
|
301
|
+
case "equals": {
|
|
302
|
+
this.consumeAmount(1)
|
|
303
|
+
return Continue([this.parseAttributeValue(name)])
|
|
304
|
+
}
|
|
305
|
+
case "openTagEnd": {
|
|
306
|
+
this.consumeAmount(1)
|
|
307
|
+
this.context = "unknown"
|
|
308
|
+
return Break<Array<Template.Attribute>>(name ? [new Template.BooleanNode(name)] : undefined)
|
|
309
|
+
}
|
|
310
|
+
case "selfClosingTagEnd": {
|
|
311
|
+
this.consumeAmount(2)
|
|
312
|
+
this.context = "unknown"
|
|
313
|
+
|
|
314
|
+
return BREAK_ATTR
|
|
315
|
+
}
|
|
316
|
+
case "closingTag": {
|
|
317
|
+
this.consumeAmount(name.length)
|
|
318
|
+
this.context = "unknown"
|
|
319
|
+
|
|
320
|
+
return BREAK_ATTR
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
private parseAttributeValue(name: string): Template.Attribute {
|
|
326
|
+
this.skipWhitespace()
|
|
327
|
+
|
|
328
|
+
const nextChar = this.nextChar()
|
|
329
|
+
|
|
330
|
+
const isDoubleQuoted = nextChar === `"`
|
|
331
|
+
const isSingleQuoted = nextChar === "'"
|
|
332
|
+
const isQuoted = isDoubleQuoted || isSingleQuoted
|
|
333
|
+
|
|
334
|
+
if (isQuoted) {
|
|
335
|
+
attributeValueMatches.base = isDoubleQuoted
|
|
336
|
+
? isQuoteToken
|
|
337
|
+
: isSingleQuoteToken
|
|
338
|
+
this.consumeAmount(1)
|
|
339
|
+
} else {
|
|
340
|
+
attributeValueMatches.base = isWhitespaceToken
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
const matched = this.parseTextUntilMany(attributeValueMatches)
|
|
344
|
+
const text = matched[0]
|
|
345
|
+
|
|
346
|
+
if (isQuoted) {
|
|
347
|
+
this.consumeAmount(1)
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
this.skipWhitespace()
|
|
351
|
+
|
|
352
|
+
if (text === "") {
|
|
353
|
+
return new Template.BooleanNode(name)
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
switch (name[0]) {
|
|
357
|
+
case "?":
|
|
358
|
+
return this.addPart(new Template.BooleanPartNode(name.slice(1), unsafeParsePartIndex(text)))
|
|
359
|
+
case ".": {
|
|
360
|
+
const property = name.slice(1)
|
|
361
|
+
|
|
362
|
+
return this.addPart(
|
|
363
|
+
property === "data"
|
|
364
|
+
? new Template.DataPartNode(unsafeParsePartIndex(text))
|
|
365
|
+
: new Template.PropertyPartNode(property, unsafeParsePartIndex(text))
|
|
366
|
+
)
|
|
367
|
+
}
|
|
368
|
+
case "@":
|
|
369
|
+
return this.addPart(new Template.EventPartNode(name.slice(1), unsafeParsePartIndex(text)))
|
|
370
|
+
case "o": {
|
|
371
|
+
if (name[1] === "n") {
|
|
372
|
+
return this.addPart(new Template.EventPartNode(name.slice(2), unsafeParsePartIndex(text)))
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
const lowerCaseName = name.toLowerCase()
|
|
378
|
+
|
|
379
|
+
if (lowerCaseName === "ref") {
|
|
380
|
+
return this.addPart(new Template.RefPartNode(unsafeParsePartIndex(text)))
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
const isClass = lowerCaseName === "class" || lowerCaseName === "classname"
|
|
384
|
+
const textAndParts = parseTextAndParts(
|
|
385
|
+
text,
|
|
386
|
+
(i) => isClass ? new Template.ClassNamePartNode(i) : new Template.AttrPartNode(name, i)
|
|
387
|
+
)
|
|
388
|
+
|
|
389
|
+
if (textAndParts.length === 1) {
|
|
390
|
+
const part = textAndParts[0]
|
|
391
|
+
|
|
392
|
+
if (part._tag === "text") {
|
|
393
|
+
return new Template.AttributeNode(name, part.value)
|
|
394
|
+
} else {
|
|
395
|
+
return this.addPart(
|
|
396
|
+
isClass ? new Template.ClassNamePartNode(part.index) : new Template.AttrPartNode(name, part.index)
|
|
397
|
+
)
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
return this.addPart(
|
|
402
|
+
isClass
|
|
403
|
+
? new Template.SparseClassNameNode(
|
|
404
|
+
// We don't need empty text spaces to generate the correct class namesq
|
|
405
|
+
textAndParts.filter((t) => t._tag === "text" ? t.value.trim().length > 0 : true) as any
|
|
406
|
+
)
|
|
407
|
+
: new Template.SparseAttrNode(name, textAndParts as any)
|
|
408
|
+
)
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
private parseTextChildren(): Array<Template.Text> | null {
|
|
412
|
+
return this.parseArray(() => this.parseTextChild())
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
private parseTextChild(): LoopDecision<Array<Template.Text>> {
|
|
416
|
+
const [parsed, matched] = this.parseTextUntilMany(textChildMatches)
|
|
417
|
+
const text = parsed.trim()
|
|
418
|
+
|
|
419
|
+
switch (matched) {
|
|
420
|
+
case null:
|
|
421
|
+
return Skip
|
|
422
|
+
case "part": {
|
|
423
|
+
this.consumeAmount(8)
|
|
424
|
+
const part = this.parsePartToken((i) => this.addPartWithPrevious(new Template.TextPartNode(i)))
|
|
425
|
+
|
|
426
|
+
return text === "" ? Continue([part]) : Continue([new Template.TextNode(text), part])
|
|
427
|
+
}
|
|
428
|
+
case "elementClose":
|
|
429
|
+
case "elementOpen": // In this case we make the assumption that you forgot to close this element
|
|
430
|
+
return Break(
|
|
431
|
+
text ? [new Template.TextNode(text)] : undefined
|
|
432
|
+
)
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
private parsePartToken<T extends Template.PartNode>(f: (index: number) => T): T {
|
|
437
|
+
const text = this.parseTextUntil(isPartEndToken)
|
|
438
|
+
const index = Number(text)
|
|
439
|
+
|
|
440
|
+
this.consumeAmount(4)
|
|
441
|
+
|
|
442
|
+
return f(index)
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
private parseTextUntil(predicate: (char: string, pos: number) => boolean) {
|
|
446
|
+
let text = ""
|
|
447
|
+
|
|
448
|
+
while (this.pos < this.length) {
|
|
449
|
+
const char = this.nextChar()
|
|
450
|
+
|
|
451
|
+
if (predicate(this.input, this.pos)) {
|
|
452
|
+
break
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
text += char
|
|
456
|
+
this.consumeAmount(1)
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
return text
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
private parseTextUntilMany<const T extends Predicates>(
|
|
463
|
+
predicates: T
|
|
464
|
+
): readonly [string, keyof T] | readonly [string, null] {
|
|
465
|
+
const [keys, length] = getPredicatesCache(predicates)
|
|
466
|
+
|
|
467
|
+
let text = ""
|
|
468
|
+
let i = 0
|
|
469
|
+
|
|
470
|
+
while (this.pos < this.length) {
|
|
471
|
+
const char = this.nextChar()
|
|
472
|
+
|
|
473
|
+
for (; i < length; i++) {
|
|
474
|
+
if (predicates[keys[i]](this.input, this.pos)) {
|
|
475
|
+
return [text, keys[i]] as const
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
i = 0
|
|
479
|
+
|
|
480
|
+
text += char
|
|
481
|
+
this.consumeAmount(1)
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
return [text, null] as const
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
private parseArray<T>(parser: () => LoopDecision<Array<T>>): Array<T> | null {
|
|
488
|
+
const children: Array<T> = []
|
|
489
|
+
|
|
490
|
+
while (this.pos < this.length) {
|
|
491
|
+
const [decision, value] = parser()
|
|
492
|
+
|
|
493
|
+
if (decision === "continue") {
|
|
494
|
+
children.push(...value)
|
|
495
|
+
} else if (decision === "break") {
|
|
496
|
+
if (Option.isSome(value)) {
|
|
497
|
+
children.push(...value.value)
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
return children
|
|
501
|
+
} else {
|
|
502
|
+
return null
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
return children
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
private skipWhitespace() {
|
|
510
|
+
while (this.pos < this.length) {
|
|
511
|
+
const char = this.nextChar()
|
|
512
|
+
|
|
513
|
+
if (SPACE_REGEX.test(char)) {
|
|
514
|
+
this.consumeAmount(1)
|
|
515
|
+
} else {
|
|
516
|
+
break
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
private nextChar() {
|
|
522
|
+
return this.input[this.pos]
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
private consumeAmount(amount: number) {
|
|
526
|
+
this.pos += amount
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
private chunk(f: (str: string, pos: number) => TextChunk | undefined): TextChunk | undefined {
|
|
530
|
+
const chunk = f(this.input, this.pos)
|
|
531
|
+
|
|
532
|
+
if (chunk) {
|
|
533
|
+
this.pos += chunk.length
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
return chunk
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
private addPart<T extends Template.PartNode | Template.SparsePartNode>(part: T): T {
|
|
540
|
+
this.parts.push([part, this.path.toChunk()])
|
|
541
|
+
return part
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
private addPartWithPrevious<T extends Template.PartNode | Template.SparsePartNode>(part: T): T {
|
|
545
|
+
this.parts.push([part, this.path.previousChunk()])
|
|
546
|
+
this.path.inc() // Nodes will be inserted as a comment
|
|
547
|
+
return part
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
private init(templateStrings: ReadonlyArray<string>): void {
|
|
551
|
+
this.context = "unknown"
|
|
552
|
+
this.input = templateWithParts(templateStrings)
|
|
553
|
+
this.length = this.input.length
|
|
554
|
+
this.parts = []
|
|
555
|
+
this.pos = 0
|
|
556
|
+
this.path = new PathStack()
|
|
557
|
+
this._skipWhitespace = true
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
function templateWithParts(template: ReadonlyArray<string>): string {
|
|
562
|
+
const length = template.length
|
|
563
|
+
const lastIndex = length - 1
|
|
564
|
+
|
|
565
|
+
let output = ""
|
|
566
|
+
|
|
567
|
+
for (let i = 0; i < length; i++) {
|
|
568
|
+
const str = template[i]
|
|
569
|
+
|
|
570
|
+
if (i === lastIndex) {
|
|
571
|
+
output += str
|
|
572
|
+
} else {
|
|
573
|
+
output += str + PART_STRING(i)
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
return output
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
function unsafeParsePartIndex(text: string): number {
|
|
581
|
+
const next = getStrictPart(text, 0)
|
|
582
|
+
|
|
583
|
+
if (!next) {
|
|
584
|
+
throw new SyntaxError(`Could not parse part index from ${text}`)
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
return parseInt(next.match[2], 10)
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
function parseTextAndParts<T>(s: string, f: (index: number) => T): Array<Template.TextNode | T> {
|
|
591
|
+
let next: TextChunk | undefined
|
|
592
|
+
let pos: number = 0
|
|
593
|
+
const out: Array<Template.TextNode | T> = []
|
|
594
|
+
|
|
595
|
+
while (pos < s.length) {
|
|
596
|
+
if ((next = getPart(s, pos))) {
|
|
597
|
+
out.push(f(parseInt(next.match[2], 10)))
|
|
598
|
+
pos += next.length
|
|
599
|
+
} else if ((next = getTextUntilPart(s, pos))) {
|
|
600
|
+
out.push(new Template.TextNode(next.match[1]))
|
|
601
|
+
|
|
602
|
+
pos += next.length
|
|
603
|
+
} else {
|
|
604
|
+
out.push(new Template.TextNode(s.substring(pos)))
|
|
605
|
+
|
|
606
|
+
return out
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
return out
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
export const parser: Parser = globalValue(Symbol.for("../Parser2"), () => new ParserImpl())
|
|
614
|
+
|
|
615
|
+
const digestSize = 2
|
|
616
|
+
const multiplier = 33
|
|
617
|
+
const fill = 5381
|
|
618
|
+
|
|
619
|
+
/**
|
|
620
|
+
* Generates a hash for an ordered list of strings. Intended for the purposes
|
|
621
|
+
* of server-side rendering with hydration.
|
|
622
|
+
*/
|
|
623
|
+
export function templateHash(strings: ReadonlyArray<string>) {
|
|
624
|
+
const hashes = new Uint32Array(digestSize).fill(fill)
|
|
625
|
+
|
|
626
|
+
for (let i = 0; i < strings.length; i++) {
|
|
627
|
+
const s = strings[i]
|
|
628
|
+
|
|
629
|
+
for (let j = 0; j < s.length; j++) {
|
|
630
|
+
const key = j % digestSize
|
|
631
|
+
|
|
632
|
+
hashes[key] = (hashes[key] * multiplier) ^ s.charCodeAt(j)
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
return btoa(String.fromCharCode(...new Uint8Array(hashes.buffer)))
|
|
637
|
+
}
|