@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.
Files changed (285) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +5 -0
  3. package/dist/cjs/Directive.js +76 -0
  4. package/dist/cjs/Directive.js.map +1 -0
  5. package/dist/cjs/ElementRef.js +83 -0
  6. package/dist/cjs/ElementRef.js.map +1 -0
  7. package/dist/cjs/ElementSource.js +244 -0
  8. package/dist/cjs/ElementSource.js.map +1 -0
  9. package/dist/cjs/Entry.js +6 -0
  10. package/dist/cjs/Entry.js.map +1 -0
  11. package/dist/cjs/EventHandler.js +65 -0
  12. package/dist/cjs/EventHandler.js.map +1 -0
  13. package/dist/cjs/Html.js +169 -0
  14. package/dist/cjs/Html.js.map +1 -0
  15. package/dist/cjs/HtmlChunk.js +257 -0
  16. package/dist/cjs/HtmlChunk.js.map +1 -0
  17. package/dist/cjs/Hydrate.js +49 -0
  18. package/dist/cjs/Hydrate.js.map +1 -0
  19. package/dist/cjs/Many.js +45 -0
  20. package/dist/cjs/Many.js.map +1 -0
  21. package/dist/cjs/Meta.js +37 -0
  22. package/dist/cjs/Meta.js.map +1 -0
  23. package/dist/cjs/Parser.js +331 -0
  24. package/dist/cjs/Parser.js.map +1 -0
  25. package/dist/cjs/Part.js +6 -0
  26. package/dist/cjs/Part.js.map +1 -0
  27. package/dist/cjs/Placeholder.js +38 -0
  28. package/dist/cjs/Placeholder.js.map +1 -0
  29. package/dist/cjs/Platform.js +64 -0
  30. package/dist/cjs/Platform.js.map +1 -0
  31. package/dist/cjs/Render.js +39 -0
  32. package/dist/cjs/Render.js.map +1 -0
  33. package/dist/cjs/RenderContext.js +130 -0
  34. package/dist/cjs/RenderContext.js.map +1 -0
  35. package/dist/cjs/RenderEvent.js +44 -0
  36. package/dist/cjs/RenderEvent.js.map +1 -0
  37. package/dist/cjs/RenderTemplate.js +41 -0
  38. package/dist/cjs/RenderTemplate.js.map +1 -0
  39. package/dist/cjs/Renderable.js +6 -0
  40. package/dist/cjs/Renderable.js.map +1 -0
  41. package/dist/cjs/Template.js +266 -0
  42. package/dist/cjs/Template.js.map +1 -0
  43. package/dist/cjs/TemplateInstance.js +51 -0
  44. package/dist/cjs/TemplateInstance.js.map +1 -0
  45. package/dist/cjs/Test.js +90 -0
  46. package/dist/cjs/Test.js.map +1 -0
  47. package/dist/cjs/Token.js +270 -0
  48. package/dist/cjs/Token.js.map +1 -0
  49. package/dist/cjs/Tokenizer.js +18 -0
  50. package/dist/cjs/Tokenizer.js.map +1 -0
  51. package/dist/cjs/Vitest.js +44 -0
  52. package/dist/cjs/Vitest.js.map +1 -0
  53. package/dist/cjs/index.js +147 -0
  54. package/dist/cjs/index.js.map +1 -0
  55. package/dist/cjs/internal/HydrateContext.js +13 -0
  56. package/dist/cjs/internal/HydrateContext.js.map +1 -0
  57. package/dist/cjs/internal/browser.js +109 -0
  58. package/dist/cjs/internal/browser.js.map +1 -0
  59. package/dist/cjs/internal/chunks.js +54 -0
  60. package/dist/cjs/internal/chunks.js.map +1 -0
  61. package/dist/cjs/internal/errors.js +23 -0
  62. package/dist/cjs/internal/errors.js.map +1 -0
  63. package/dist/cjs/internal/hydrate.js +197 -0
  64. package/dist/cjs/internal/hydrate.js.map +1 -0
  65. package/dist/cjs/internal/indexRefCounter.js +32 -0
  66. package/dist/cjs/internal/indexRefCounter.js.map +1 -0
  67. package/dist/cjs/internal/module-augmentation.js +6 -0
  68. package/dist/cjs/internal/module-augmentation.js.map +1 -0
  69. package/dist/cjs/internal/parser.js +492 -0
  70. package/dist/cjs/internal/parser.js.map +1 -0
  71. package/dist/cjs/internal/parts.js +350 -0
  72. package/dist/cjs/internal/parts.js.map +1 -0
  73. package/dist/cjs/internal/readAttribute.js +34 -0
  74. package/dist/cjs/internal/readAttribute.js.map +1 -0
  75. package/dist/cjs/internal/render.js +332 -0
  76. package/dist/cjs/internal/render.js.map +1 -0
  77. package/dist/cjs/internal/server.js +219 -0
  78. package/dist/cjs/internal/server.js.map +1 -0
  79. package/dist/cjs/internal/tokenizer.js +264 -0
  80. package/dist/cjs/internal/tokenizer.js.map +1 -0
  81. package/dist/cjs/internal/utils.js +68 -0
  82. package/dist/cjs/internal/utils.js.map +1 -0
  83. package/dist/dts/Directive.d.ts +70 -0
  84. package/dist/dts/Directive.d.ts.map +1 -0
  85. package/dist/dts/ElementRef.d.ts +40 -0
  86. package/dist/dts/ElementRef.d.ts.map +1 -0
  87. package/dist/dts/ElementSource.d.ts +72 -0
  88. package/dist/dts/ElementSource.d.ts.map +1 -0
  89. package/dist/dts/Entry.d.ts +26 -0
  90. package/dist/dts/Entry.d.ts.map +1 -0
  91. package/dist/dts/EventHandler.d.ts +61 -0
  92. package/dist/dts/EventHandler.d.ts.map +1 -0
  93. package/dist/dts/Html.d.ts +17 -0
  94. package/dist/dts/Html.d.ts.map +1 -0
  95. package/dist/dts/HtmlChunk.d.ts +56 -0
  96. package/dist/dts/HtmlChunk.d.ts.map +1 -0
  97. package/dist/dts/Hydrate.d.ts +20 -0
  98. package/dist/dts/Hydrate.d.ts.map +1 -0
  99. package/dist/dts/Many.d.ts +32 -0
  100. package/dist/dts/Many.d.ts.map +1 -0
  101. package/dist/dts/Meta.d.ts +24 -0
  102. package/dist/dts/Meta.d.ts.map +1 -0
  103. package/dist/dts/Parser.d.ts +16 -0
  104. package/dist/dts/Parser.d.ts.map +1 -0
  105. package/dist/dts/Part.d.ts +147 -0
  106. package/dist/dts/Part.d.ts.map +1 -0
  107. package/dist/dts/Placeholder.d.ts +51 -0
  108. package/dist/dts/Placeholder.d.ts.map +1 -0
  109. package/dist/dts/Platform.d.ts +23 -0
  110. package/dist/dts/Platform.d.ts.map +1 -0
  111. package/dist/dts/Render.d.ts +23 -0
  112. package/dist/dts/Render.d.ts.map +1 -0
  113. package/dist/dts/RenderContext.d.ts +88 -0
  114. package/dist/dts/RenderContext.d.ts.map +1 -0
  115. package/dist/dts/RenderEvent.d.ts +37 -0
  116. package/dist/dts/RenderEvent.d.ts.map +1 -0
  117. package/dist/dts/RenderTemplate.d.ts +38 -0
  118. package/dist/dts/RenderTemplate.d.ts.map +1 -0
  119. package/dist/dts/Renderable.d.ts +28 -0
  120. package/dist/dts/Renderable.d.ts.map +1 -0
  121. package/dist/dts/Template.d.ts +218 -0
  122. package/dist/dts/Template.d.ts.map +1 -0
  123. package/dist/dts/TemplateInstance.d.ts +32 -0
  124. package/dist/dts/TemplateInstance.d.ts.map +1 -0
  125. package/dist/dts/Test.d.ts +58 -0
  126. package/dist/dts/Test.d.ts.map +1 -0
  127. package/dist/dts/Token.d.ts +202 -0
  128. package/dist/dts/Token.d.ts.map +1 -0
  129. package/dist/dts/Tokenizer.d.ts +6 -0
  130. package/dist/dts/Tokenizer.d.ts.map +1 -0
  131. package/dist/dts/Vitest.d.ts +28 -0
  132. package/dist/dts/Vitest.d.ts.map +1 -0
  133. package/dist/dts/index.d.ts +65 -0
  134. package/dist/dts/index.d.ts.map +1 -0
  135. package/dist/dts/internal/HydrateContext.d.ts +2 -0
  136. package/dist/dts/internal/HydrateContext.d.ts.map +1 -0
  137. package/dist/dts/internal/browser.d.ts +8 -0
  138. package/dist/dts/internal/browser.d.ts.map +1 -0
  139. package/dist/dts/internal/chunks.d.ts +22 -0
  140. package/dist/dts/internal/chunks.d.ts.map +1 -0
  141. package/dist/dts/internal/errors.d.ts +9 -0
  142. package/dist/dts/internal/errors.d.ts.map +1 -0
  143. package/dist/dts/internal/hydrate.d.ts +37 -0
  144. package/dist/dts/internal/hydrate.d.ts.map +1 -0
  145. package/dist/dts/internal/indexRefCounter.d.ts +6 -0
  146. package/dist/dts/internal/indexRefCounter.d.ts.map +1 -0
  147. package/dist/dts/internal/module-augmentation.d.ts +36 -0
  148. package/dist/dts/internal/module-augmentation.d.ts.map +1 -0
  149. package/dist/dts/internal/parser.d.ts +12 -0
  150. package/dist/dts/internal/parser.d.ts.map +1 -0
  151. package/dist/dts/internal/parts.d.ts +304 -0
  152. package/dist/dts/internal/parts.d.ts.map +1 -0
  153. package/dist/dts/internal/readAttribute.d.ts +9 -0
  154. package/dist/dts/internal/readAttribute.d.ts.map +1 -0
  155. package/dist/dts/internal/render.d.ts +30 -0
  156. package/dist/dts/internal/render.d.ts.map +1 -0
  157. package/dist/dts/internal/server.d.ts +31 -0
  158. package/dist/dts/internal/server.d.ts.map +1 -0
  159. package/dist/dts/internal/tokenizer.d.ts +3 -0
  160. package/dist/dts/internal/tokenizer.d.ts.map +1 -0
  161. package/dist/dts/internal/utils.d.ts +15 -0
  162. package/dist/dts/internal/utils.d.ts.map +1 -0
  163. package/dist/esm/Directive.js +64 -0
  164. package/dist/esm/Directive.js.map +1 -0
  165. package/dist/esm/ElementRef.js +72 -0
  166. package/dist/esm/ElementRef.js.map +1 -0
  167. package/dist/esm/ElementSource.js +237 -0
  168. package/dist/esm/ElementSource.js.map +1 -0
  169. package/dist/esm/Entry.js +2 -0
  170. package/dist/esm/Entry.js.map +1 -0
  171. package/dist/esm/EventHandler.js +52 -0
  172. package/dist/esm/EventHandler.js.map +1 -0
  173. package/dist/esm/Html.js +167 -0
  174. package/dist/esm/Html.js.map +1 -0
  175. package/dist/esm/HtmlChunk.js +274 -0
  176. package/dist/esm/HtmlChunk.js.map +1 -0
  177. package/dist/esm/Hydrate.js +37 -0
  178. package/dist/esm/Hydrate.js.map +1 -0
  179. package/dist/esm/Many.js +33 -0
  180. package/dist/esm/Many.js.map +1 -0
  181. package/dist/esm/Meta.js +29 -0
  182. package/dist/esm/Meta.js.map +1 -0
  183. package/dist/esm/Parser.js +342 -0
  184. package/dist/esm/Parser.js.map +1 -0
  185. package/dist/esm/Part.js +5 -0
  186. package/dist/esm/Part.js.map +1 -0
  187. package/dist/esm/Placeholder.js +30 -0
  188. package/dist/esm/Placeholder.js.map +1 -0
  189. package/dist/esm/Platform.js +41 -0
  190. package/dist/esm/Platform.js.map +1 -0
  191. package/dist/esm/Render.js +27 -0
  192. package/dist/esm/Render.js.map +1 -0
  193. package/dist/esm/RenderContext.js +113 -0
  194. package/dist/esm/RenderContext.js.map +1 -0
  195. package/dist/esm/RenderEvent.js +36 -0
  196. package/dist/esm/RenderEvent.js.map +1 -0
  197. package/dist/esm/RenderTemplate.js +26 -0
  198. package/dist/esm/RenderTemplate.js.map +1 -0
  199. package/dist/esm/Renderable.js +2 -0
  200. package/dist/esm/Renderable.js.map +1 -0
  201. package/dist/esm/Template.js +239 -0
  202. package/dist/esm/Template.js.map +1 -0
  203. package/dist/esm/TemplateInstance.js +43 -0
  204. package/dist/esm/TemplateInstance.js.map +1 -0
  205. package/dist/esm/Test.js +68 -0
  206. package/dist/esm/Test.js.map +1 -0
  207. package/dist/esm/Token.js +264 -0
  208. package/dist/esm/Token.js.map +1 -0
  209. package/dist/esm/Tokenizer.js +9 -0
  210. package/dist/esm/Tokenizer.js.map +1 -0
  211. package/dist/esm/Vitest.js +29 -0
  212. package/dist/esm/Vitest.js.map +1 -0
  213. package/dist/esm/index.js +65 -0
  214. package/dist/esm/index.js.map +1 -0
  215. package/dist/esm/internal/HydrateContext.js +7 -0
  216. package/dist/esm/internal/HydrateContext.js.map +1 -0
  217. package/dist/esm/internal/browser.js +102 -0
  218. package/dist/esm/internal/browser.js.map +1 -0
  219. package/dist/esm/internal/chunks.js +47 -0
  220. package/dist/esm/internal/chunks.js.map +1 -0
  221. package/dist/esm/internal/errors.js +15 -0
  222. package/dist/esm/internal/errors.js.map +1 -0
  223. package/dist/esm/internal/hydrate.js +165 -0
  224. package/dist/esm/internal/hydrate.js.map +1 -0
  225. package/dist/esm/internal/indexRefCounter.js +24 -0
  226. package/dist/esm/internal/indexRefCounter.js.map +1 -0
  227. package/dist/esm/internal/module-augmentation.js +2 -0
  228. package/dist/esm/internal/module-augmentation.js.map +1 -0
  229. package/dist/esm/internal/parser.js +493 -0
  230. package/dist/esm/internal/parser.js.map +1 -0
  231. package/dist/esm/internal/parts.js +291 -0
  232. package/dist/esm/internal/parts.js.map +1 -0
  233. package/dist/esm/internal/readAttribute.js +24 -0
  234. package/dist/esm/internal/readAttribute.js.map +1 -0
  235. package/dist/esm/internal/render.js +329 -0
  236. package/dist/esm/internal/render.js.map +1 -0
  237. package/dist/esm/internal/server.js +174 -0
  238. package/dist/esm/internal/server.js.map +1 -0
  239. package/dist/esm/internal/tokenizer.js +296 -0
  240. package/dist/esm/internal/tokenizer.js.map +1 -0
  241. package/dist/esm/internal/utils.js +52 -0
  242. package/dist/esm/internal/utils.js.map +1 -0
  243. package/dist/esm/package.json +4 -0
  244. package/package.json +242 -0
  245. package/src/Directive.ts +114 -0
  246. package/src/ElementRef.ts +123 -0
  247. package/src/ElementSource.ts +417 -0
  248. package/src/Entry.ts +28 -0
  249. package/src/EventHandler.ts +104 -0
  250. package/src/Html.ts +258 -0
  251. package/src/HtmlChunk.ts +346 -0
  252. package/src/Hydrate.ts +53 -0
  253. package/src/Many.ts +128 -0
  254. package/src/Meta.ts +32 -0
  255. package/src/Parser.ts +457 -0
  256. package/src/Part.ts +186 -0
  257. package/src/Placeholder.ts +70 -0
  258. package/src/Platform.ts +71 -0
  259. package/src/Render.ts +45 -0
  260. package/src/RenderContext.ts +221 -0
  261. package/src/RenderEvent.ts +67 -0
  262. package/src/RenderTemplate.ts +88 -0
  263. package/src/Renderable.ts +34 -0
  264. package/src/Template.ts +284 -0
  265. package/src/TemplateInstance.ts +83 -0
  266. package/src/Test.ts +151 -0
  267. package/src/Token.ts +269 -0
  268. package/src/Tokenizer.ts +10 -0
  269. package/src/Vitest.ts +61 -0
  270. package/src/index.ts +66 -0
  271. package/src/internal/HydrateContext.ts +23 -0
  272. package/src/internal/browser.ts +132 -0
  273. package/src/internal/chunks.ts +73 -0
  274. package/src/internal/errors.ts +11 -0
  275. package/src/internal/external.d.ts +11 -0
  276. package/src/internal/hydrate.ts +262 -0
  277. package/src/internal/indexRefCounter.ts +33 -0
  278. package/src/internal/module-augmentation.ts +48 -0
  279. package/src/internal/parser.ts +637 -0
  280. package/src/internal/parts.ts +527 -0
  281. package/src/internal/readAttribute.ts +28 -0
  282. package/src/internal/render.ts +529 -0
  283. package/src/internal/server.ts +293 -0
  284. package/src/internal/tokenizer.ts +338 -0
  285. 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
+ }