@typed/template 0.9.6 → 0.11.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 (300) hide show
  1. package/RenderQueue/package.json +6 -0
  2. package/dist/cjs/Directive.js +1 -1
  3. package/dist/cjs/Directive.js.map +1 -1
  4. package/dist/cjs/ElementRef.js +1 -1
  5. package/dist/cjs/ElementRef.js.map +1 -1
  6. package/dist/cjs/ElementSource.js +1 -1
  7. package/dist/cjs/ElementSource.js.map +1 -1
  8. package/dist/cjs/EventHandler.js +15 -4
  9. package/dist/cjs/EventHandler.js.map +1 -1
  10. package/dist/cjs/Html.js +84 -44
  11. package/dist/cjs/Html.js.map +1 -1
  12. package/dist/cjs/HtmlChunk.js +67 -21
  13. package/dist/cjs/HtmlChunk.js.map +1 -1
  14. package/dist/cjs/Hydrate.js +6 -6
  15. package/dist/cjs/Hydrate.js.map +1 -1
  16. package/dist/cjs/Many.js +4 -4
  17. package/dist/cjs/Many.js.map +1 -1
  18. package/dist/cjs/Meta.js +10 -3
  19. package/dist/cjs/Meta.js.map +1 -1
  20. package/dist/cjs/Parser.js +1 -1
  21. package/dist/cjs/Placeholder.js +5 -9
  22. package/dist/cjs/Placeholder.js.map +1 -1
  23. package/dist/cjs/Platform.js +7 -5
  24. package/dist/cjs/Platform.js.map +1 -1
  25. package/dist/cjs/Render.js +8 -7
  26. package/dist/cjs/Render.js.map +1 -1
  27. package/dist/cjs/RenderContext.js +8 -92
  28. package/dist/cjs/RenderContext.js.map +1 -1
  29. package/dist/cjs/RenderEvent.js +9 -1
  30. package/dist/cjs/RenderEvent.js.map +1 -1
  31. package/dist/cjs/RenderQueue.js +341 -0
  32. package/dist/cjs/RenderQueue.js.map +1 -0
  33. package/dist/cjs/RenderTemplate.js +1 -1
  34. package/dist/cjs/RenderTemplate.js.map +1 -1
  35. package/dist/cjs/Template.js +12 -0
  36. package/dist/cjs/Template.js.map +1 -1
  37. package/dist/cjs/Test.js +64 -33
  38. package/dist/cjs/Test.js.map +1 -1
  39. package/dist/cjs/Vitest.js +12 -20
  40. package/dist/cjs/Vitest.js.map +1 -1
  41. package/dist/cjs/index.js +6 -3
  42. package/dist/cjs/index.js.map +1 -1
  43. package/dist/cjs/internal/EventSource.js +16 -9
  44. package/dist/cjs/internal/EventSource.js.map +1 -1
  45. package/dist/cjs/internal/HydrateContext.js.map +1 -1
  46. package/dist/cjs/internal/browser.js +11 -10
  47. package/dist/cjs/internal/browser.js.map +1 -1
  48. package/dist/cjs/internal/character-entities.js +2141 -0
  49. package/dist/cjs/internal/character-entities.js.map +1 -0
  50. package/dist/cjs/internal/errors.js +19 -2
  51. package/dist/cjs/internal/errors.js.map +1 -1
  52. package/dist/cjs/internal/indexRefCounter.js +36 -63
  53. package/dist/cjs/internal/indexRefCounter.js.map +1 -1
  54. package/dist/cjs/internal/parser.js +18 -17
  55. package/dist/cjs/internal/parser.js.map +1 -1
  56. package/dist/cjs/internal/parser2.js +382 -0
  57. package/dist/cjs/internal/parser2.js.map +1 -0
  58. package/dist/cjs/internal/server-parts.js +124 -0
  59. package/dist/cjs/internal/server-parts.js.map +1 -0
  60. package/dist/cjs/internal/server.js +15 -185
  61. package/dist/cjs/internal/server.js.map +1 -1
  62. package/dist/cjs/internal/utils.js +73 -9
  63. package/dist/cjs/internal/utils.js.map +1 -1
  64. package/dist/cjs/internal/v2/SyncPart.js +6 -0
  65. package/dist/cjs/internal/v2/SyncPart.js.map +1 -0
  66. package/dist/cjs/internal/v2/helpers.js +15 -0
  67. package/dist/cjs/internal/v2/helpers.js.map +1 -0
  68. package/dist/cjs/internal/v2/hydrate.js +202 -0
  69. package/dist/cjs/internal/v2/hydrate.js.map +1 -0
  70. package/dist/cjs/internal/v2/hydration-template.js +269 -0
  71. package/dist/cjs/internal/v2/hydration-template.js.map +1 -0
  72. package/dist/cjs/internal/v2/parts.js +169 -0
  73. package/dist/cjs/internal/v2/parts.js.map +1 -0
  74. package/dist/cjs/internal/v2/render-entry.js +110 -0
  75. package/dist/cjs/internal/v2/render-entry.js.map +1 -0
  76. package/dist/cjs/internal/v2/render-sync-parts.js +318 -0
  77. package/dist/cjs/internal/v2/render-sync-parts.js.map +1 -0
  78. package/dist/cjs/internal/v2/render.js +417 -0
  79. package/dist/cjs/internal/v2/render.js.map +1 -0
  80. package/dist/cjs/internal/v2/sync-parts.js +115 -0
  81. package/dist/cjs/internal/v2/sync-parts.js.map +1 -0
  82. package/dist/dts/ElementRef.d.ts +1 -1
  83. package/dist/dts/ElementRef.d.ts.map +1 -1
  84. package/dist/dts/ElementSource.d.ts +1 -1
  85. package/dist/dts/ElementSource.d.ts.map +1 -1
  86. package/dist/dts/EventHandler.d.ts +20 -8
  87. package/dist/dts/EventHandler.d.ts.map +1 -1
  88. package/dist/dts/Html.d.ts +6 -5
  89. package/dist/dts/Html.d.ts.map +1 -1
  90. package/dist/dts/HtmlChunk.d.ts.map +1 -1
  91. package/dist/dts/Hydrate.d.ts +1 -3
  92. package/dist/dts/Hydrate.d.ts.map +1 -1
  93. package/dist/dts/Many.d.ts +9 -11
  94. package/dist/dts/Many.d.ts.map +1 -1
  95. package/dist/dts/Meta.d.ts +5 -1
  96. package/dist/dts/Meta.d.ts.map +1 -1
  97. package/dist/dts/Parser.d.ts +1 -1
  98. package/dist/dts/Parser.d.ts.map +1 -1
  99. package/dist/dts/Part.d.ts +20 -56
  100. package/dist/dts/Part.d.ts.map +1 -1
  101. package/dist/dts/Placeholder.d.ts +6 -10
  102. package/dist/dts/Placeholder.d.ts.map +1 -1
  103. package/dist/dts/Platform.d.ts +2 -4
  104. package/dist/dts/Platform.d.ts.map +1 -1
  105. package/dist/dts/Render.d.ts +4 -8
  106. package/dist/dts/Render.d.ts.map +1 -1
  107. package/dist/dts/RenderContext.d.ts +3 -22
  108. package/dist/dts/RenderContext.d.ts.map +1 -1
  109. package/dist/dts/RenderEvent.d.ts +6 -1
  110. package/dist/dts/RenderEvent.d.ts.map +1 -1
  111. package/dist/dts/RenderQueue.d.ts +103 -0
  112. package/dist/dts/RenderQueue.d.ts.map +1 -0
  113. package/dist/dts/RenderTemplate.d.ts +3 -2
  114. package/dist/dts/RenderTemplate.d.ts.map +1 -1
  115. package/dist/dts/Renderable.d.ts +1 -1
  116. package/dist/dts/Template.d.ts +14 -1
  117. package/dist/dts/Template.d.ts.map +1 -1
  118. package/dist/dts/Test.d.ts +14 -1
  119. package/dist/dts/Test.d.ts.map +1 -1
  120. package/dist/dts/Vitest.d.ts +11 -8
  121. package/dist/dts/Vitest.d.ts.map +1 -1
  122. package/dist/dts/index.d.ts +4 -0
  123. package/dist/dts/index.d.ts.map +1 -1
  124. package/dist/dts/internal/EventSource.d.ts +2 -1
  125. package/dist/dts/internal/EventSource.d.ts.map +1 -1
  126. package/dist/dts/internal/browser.d.ts +3 -3
  127. package/dist/dts/internal/browser.d.ts.map +1 -1
  128. package/dist/dts/internal/character-entities.d.ts +2133 -0
  129. package/dist/dts/internal/character-entities.d.ts.map +1 -0
  130. package/dist/dts/internal/errors.d.ts +9 -1
  131. package/dist/dts/internal/errors.d.ts.map +1 -1
  132. package/dist/dts/internal/indexRefCounter.d.ts +0 -4
  133. package/dist/dts/internal/indexRefCounter.d.ts.map +1 -1
  134. package/dist/dts/internal/parser.d.ts +13 -0
  135. package/dist/dts/internal/parser.d.ts.map +1 -1
  136. package/dist/dts/internal/parser2.d.ts +12 -0
  137. package/dist/dts/internal/parser2.d.ts.map +1 -0
  138. package/dist/dts/internal/server-parts.d.ts +223 -0
  139. package/dist/dts/internal/server-parts.d.ts.map +1 -0
  140. package/dist/dts/internal/server.d.ts +2 -28
  141. package/dist/dts/internal/server.d.ts.map +1 -1
  142. package/dist/dts/internal/utils.d.ts +4 -1
  143. package/dist/dts/internal/utils.d.ts.map +1 -1
  144. package/dist/dts/internal/v2/SyncPart.d.ts +87 -0
  145. package/dist/dts/internal/v2/SyncPart.d.ts.map +1 -0
  146. package/dist/dts/internal/v2/helpers.d.ts +3 -0
  147. package/dist/dts/internal/v2/helpers.d.ts.map +1 -0
  148. package/dist/dts/internal/v2/hydrate.d.ts +7 -0
  149. package/dist/dts/internal/v2/hydrate.d.ts.map +1 -0
  150. package/dist/dts/internal/v2/hydration-template.d.ts +54 -0
  151. package/dist/dts/internal/v2/hydration-template.d.ts.map +1 -0
  152. package/dist/dts/internal/v2/parts.d.ts +245 -0
  153. package/dist/dts/internal/v2/parts.d.ts.map +1 -0
  154. package/dist/dts/internal/v2/render-entry.d.ts +6 -0
  155. package/dist/dts/internal/v2/render-entry.d.ts.map +1 -0
  156. package/dist/dts/internal/v2/render-sync-parts.d.ts +22 -0
  157. package/dist/dts/internal/v2/render-sync-parts.d.ts.map +1 -0
  158. package/dist/dts/internal/v2/render.d.ts +62 -0
  159. package/dist/dts/internal/v2/render.d.ts.map +1 -0
  160. package/dist/dts/internal/v2/sync-parts.d.ts +129 -0
  161. package/dist/dts/internal/v2/sync-parts.d.ts.map +1 -0
  162. package/dist/esm/ElementRef.js.map +1 -1
  163. package/dist/esm/EventHandler.js +20 -4
  164. package/dist/esm/EventHandler.js.map +1 -1
  165. package/dist/esm/Html.js +91 -50
  166. package/dist/esm/Html.js.map +1 -1
  167. package/dist/esm/HtmlChunk.js +75 -24
  168. package/dist/esm/HtmlChunk.js.map +1 -1
  169. package/dist/esm/Hydrate.js +5 -5
  170. package/dist/esm/Hydrate.js.map +1 -1
  171. package/dist/esm/Many.js +3 -3
  172. package/dist/esm/Many.js.map +1 -1
  173. package/dist/esm/Meta.js +7 -1
  174. package/dist/esm/Meta.js.map +1 -1
  175. package/dist/esm/Parser.js +1 -1
  176. package/dist/esm/Parser.js.map +1 -1
  177. package/dist/esm/Placeholder.js +4 -8
  178. package/dist/esm/Placeholder.js.map +1 -1
  179. package/dist/esm/Platform.js +3 -1
  180. package/dist/esm/Platform.js.map +1 -1
  181. package/dist/esm/Render.js +6 -5
  182. package/dist/esm/Render.js.map +1 -1
  183. package/dist/esm/RenderContext.js +5 -85
  184. package/dist/esm/RenderContext.js.map +1 -1
  185. package/dist/esm/RenderEvent.js +8 -1
  186. package/dist/esm/RenderEvent.js.map +1 -1
  187. package/dist/esm/RenderQueue.js +336 -0
  188. package/dist/esm/RenderQueue.js.map +1 -0
  189. package/dist/esm/RenderTemplate.js.map +1 -1
  190. package/dist/esm/Template.js +12 -0
  191. package/dist/esm/Template.js.map +1 -1
  192. package/dist/esm/Test.js +71 -30
  193. package/dist/esm/Test.js.map +1 -1
  194. package/dist/esm/Vitest.js +11 -8
  195. package/dist/esm/Vitest.js.map +1 -1
  196. package/dist/esm/index.js +4 -0
  197. package/dist/esm/index.js.map +1 -1
  198. package/dist/esm/internal/EventSource.js +12 -7
  199. package/dist/esm/internal/EventSource.js.map +1 -1
  200. package/dist/esm/internal/HydrateContext.js.map +1 -1
  201. package/dist/esm/internal/browser.js +10 -9
  202. package/dist/esm/internal/browser.js.map +1 -1
  203. package/dist/esm/internal/character-entities.js +2134 -0
  204. package/dist/esm/internal/character-entities.js.map +1 -0
  205. package/dist/esm/internal/errors.js +22 -2
  206. package/dist/esm/internal/errors.js.map +1 -1
  207. package/dist/esm/internal/indexRefCounter.js +36 -61
  208. package/dist/esm/internal/indexRefCounter.js.map +1 -1
  209. package/dist/esm/internal/parser.js +18 -18
  210. package/dist/esm/internal/parser.js.map +1 -1
  211. package/dist/esm/internal/parser2.js +393 -0
  212. package/dist/esm/internal/parser2.js.map +1 -0
  213. package/dist/esm/internal/server-parts.js +109 -0
  214. package/dist/esm/internal/server-parts.js.map +1 -0
  215. package/dist/esm/internal/server.js +12 -161
  216. package/dist/esm/internal/server.js.map +1 -1
  217. package/dist/esm/internal/utils.js +71 -7
  218. package/dist/esm/internal/utils.js.map +1 -1
  219. package/dist/esm/internal/v2/SyncPart.js +5 -0
  220. package/dist/esm/internal/v2/SyncPart.js.map +1 -0
  221. package/dist/esm/internal/v2/helpers.js +12 -0
  222. package/dist/esm/internal/v2/helpers.js.map +1 -0
  223. package/dist/esm/internal/v2/hydrate.js +195 -0
  224. package/dist/esm/internal/v2/hydrate.js.map +1 -0
  225. package/dist/esm/internal/v2/hydration-template.js +265 -0
  226. package/dist/esm/internal/v2/hydration-template.js.map +1 -0
  227. package/dist/esm/internal/v2/parts.js +150 -0
  228. package/dist/esm/internal/v2/parts.js.map +1 -0
  229. package/dist/esm/internal/v2/render-entry.js +102 -0
  230. package/dist/esm/internal/v2/render-entry.js.map +1 -0
  231. package/dist/esm/internal/v2/render-sync-parts.js +265 -0
  232. package/dist/esm/internal/v2/render-sync-parts.js.map +1 -0
  233. package/dist/esm/internal/v2/render.js +353 -0
  234. package/dist/esm/internal/v2/render.js.map +1 -0
  235. package/dist/esm/internal/v2/sync-parts.js +102 -0
  236. package/dist/esm/internal/v2/sync-parts.js.map +1 -0
  237. package/package.json +20 -13
  238. package/src/ElementRef.ts +1 -1
  239. package/src/ElementSource.ts +1 -1
  240. package/src/EventHandler.ts +50 -12
  241. package/src/Html.ts +207 -98
  242. package/src/HtmlChunk.ts +77 -30
  243. package/src/Hydrate.ts +20 -14
  244. package/src/Many.ts +17 -14
  245. package/src/Meta.ts +8 -1
  246. package/src/Parser.ts +1 -1
  247. package/src/Part.ts +22 -66
  248. package/src/Placeholder.ts +17 -15
  249. package/src/Platform.ts +5 -5
  250. package/src/Render.ts +23 -26
  251. package/src/RenderContext.ts +14 -142
  252. package/src/RenderEvent.ts +10 -1
  253. package/src/RenderQueue.ts +445 -0
  254. package/src/RenderTemplate.ts +7 -2
  255. package/src/Renderable.ts +1 -1
  256. package/src/Template.ts +15 -1
  257. package/src/Test.ts +122 -38
  258. package/src/Vitest.ts +20 -10
  259. package/src/index.ts +4 -0
  260. package/src/internal/EventSource.ts +14 -8
  261. package/src/internal/HydrateContext.ts +3 -4
  262. package/src/internal/browser.ts +26 -21
  263. package/src/internal/character-entities.ts +2136 -0
  264. package/src/internal/errors.ts +30 -3
  265. package/src/internal/indexRefCounter.ts +38 -70
  266. package/src/internal/parser.ts +19 -19
  267. package/src/internal/parser2.ts +468 -0
  268. package/src/internal/server-parts.ts +161 -0
  269. package/src/internal/server.ts +16 -272
  270. package/src/internal/utils.ts +83 -7
  271. package/src/internal/v2/SyncPart.ts +112 -0
  272. package/src/internal/v2/helpers.ts +13 -0
  273. package/src/internal/v2/hydrate.ts +289 -0
  274. package/src/internal/v2/hydration-template.ts +308 -0
  275. package/src/internal/v2/parts.ts +254 -0
  276. package/src/internal/v2/render-entry.ts +131 -0
  277. package/src/internal/v2/render-sync-parts.ts +440 -0
  278. package/src/internal/v2/render.ts +588 -0
  279. package/src/internal/v2/sync-parts.ts +133 -0
  280. package/dist/cjs/internal/hydrate.js +0 -274
  281. package/dist/cjs/internal/hydrate.js.map +0 -1
  282. package/dist/cjs/internal/parts.js +0 -451
  283. package/dist/cjs/internal/parts.js.map +0 -1
  284. package/dist/cjs/internal/render.js +0 -704
  285. package/dist/cjs/internal/render.js.map +0 -1
  286. package/dist/dts/internal/hydrate.d.ts +0 -33
  287. package/dist/dts/internal/hydrate.d.ts.map +0 -1
  288. package/dist/dts/internal/parts.d.ts +0 -314
  289. package/dist/dts/internal/parts.d.ts.map +0 -1
  290. package/dist/dts/internal/render.d.ts +0 -16
  291. package/dist/dts/internal/render.d.ts.map +0 -1
  292. package/dist/esm/internal/hydrate.js +0 -239
  293. package/dist/esm/internal/hydrate.js.map +0 -1
  294. package/dist/esm/internal/parts.js +0 -373
  295. package/dist/esm/internal/parts.js.map +0 -1
  296. package/dist/esm/internal/render.js +0 -689
  297. package/dist/esm/internal/render.js.map +0 -1
  298. package/src/internal/hydrate.ts +0 -366
  299. package/src/internal/parts.ts +0 -609
  300. package/src/internal/render.ts +0 -971
package/src/Html.ts CHANGED
@@ -6,51 +6,62 @@ import type { CurrentEnvironment } from "@typed/environment"
6
6
  import * as Fx from "@typed/fx/Fx"
7
7
  import * as Sink from "@typed/fx/Sink"
8
8
  import { TypeId } from "@typed/fx/TypeId"
9
+ import { join } from "effect/Array"
9
10
  import * as Effect from "effect/Effect"
10
11
  import * as Layer from "effect/Layer"
11
12
  import * as Option from "effect/Option"
12
- import { join } from "effect/ReadonlyArray"
13
+ import type * as Record from "effect/Record"
13
14
  import type * as Scope from "effect/Scope"
14
15
  import { isDirective } from "./Directive.js"
15
16
  import type { ServerEntry } from "./Entry.js"
16
17
  import type { HtmlChunk, PartChunk, SparsePartChunk, TextChunk } from "./HtmlChunk.js"
17
18
  import { templateToHtmlChunks } from "./HtmlChunk.js"
18
- import { parse } from "./internal/parser.js"
19
+ import { parse } from "./internal/parser2.js"
19
20
  import { partNodeToPart } from "./internal/server.js"
20
- import { TEXT_START, TYPED_HOLE } from "./Meta.js"
21
+ import { isNullOrUndefined } from "./internal/v2/helpers.js"
22
+ import { TEXT_START, TYPED_HOLE_END, TYPED_HOLE_START } from "./Meta.js"
21
23
  import type { Placeholder } from "./Placeholder.js"
22
24
  import type { Renderable } from "./Renderable.js"
23
25
  import * as RenderContext from "./RenderContext.js"
24
- import { HtmlRenderEvent, isRenderEvent } from "./RenderEvent.js"
26
+ import { HtmlRenderEvent, isHtmlRenderEvent } from "./RenderEvent.js"
25
27
  import type { RenderEvent } from "./RenderEvent.js"
28
+ import * as RenderQueue from "./RenderQueue.js"
26
29
  import { RenderTemplate } from "./RenderTemplate.js"
27
30
 
28
- const toHtml = (r: RenderEvent) => (r as HtmlRenderEvent).html
31
+ const toHtml = (r: RenderEvent | null) => r === null ? "" : (r as HtmlRenderEvent).html
29
32
 
30
33
  /**
31
34
  * @since 1.0.0
32
35
  */
33
- export const serverLayer: Layer.Layer<RenderContext.RenderContext | RenderTemplate | CurrentEnvironment> = Layer
34
- .provideMerge(
35
- RenderTemplate.layer(RenderContext.RenderContext.with(renderHtmlTemplate)),
36
- RenderContext.server
37
- )
36
+ export const serverLayer: Layer.Layer<
37
+ | RenderContext.RenderContext
38
+ | RenderQueue.RenderQueue
39
+ | RenderTemplate
40
+ | CurrentEnvironment
41
+ > = Layer.provideMerge(
42
+ RenderTemplate.layer(RenderContext.RenderContext.with(renderHtmlTemplate)),
43
+ RenderContext.server
44
+ ).pipe(Layer.provideMerge(RenderQueue.sync))
38
45
 
39
46
  /**
40
47
  * @since 1.0.0
41
48
  */
42
- export const staticLayer: Layer.Layer<RenderContext.RenderContext | RenderTemplate | CurrentEnvironment> = Layer
43
- .provideMerge(
44
- RenderTemplate.layer(RenderContext.RenderContext.with(renderHtmlTemplate)),
45
- RenderContext.static
46
- )
49
+ export const staticLayer: Layer.Layer<
50
+ | RenderContext.RenderContext
51
+ | RenderQueue.RenderQueue
52
+ | RenderTemplate
53
+ | CurrentEnvironment
54
+ > = Layer.provideMerge(
55
+ RenderTemplate.layer(RenderContext.RenderContext.with(renderHtmlTemplate)),
56
+ RenderContext.static
57
+ ).pipe(Layer.provideMerge(RenderQueue.sync))
47
58
 
48
59
  /**
49
60
  * @since 1.0.0
50
61
  */
51
62
  export function renderToHtml<E, R>(
52
- fx: Fx.Fx<RenderEvent, E, R>
53
- ): Fx.Fx<string, E, R | RenderTemplate | RenderContext.RenderContext> {
63
+ fx: Fx.Fx<RenderEvent | null, E, R>
64
+ ): Fx.Fx<string, E, R> {
54
65
  return Fx.map(fx, toHtml)
55
66
  }
56
67
 
@@ -58,8 +69,8 @@ export function renderToHtml<E, R>(
58
69
  * @since 1.0.0
59
70
  */
60
71
  export function renderToHtmlString<E, R>(
61
- fx: Fx.Fx<RenderEvent, E, R>
62
- ): Effect.Effect<string, E, R | RenderTemplate | RenderContext.RenderContext> {
72
+ fx: Fx.Fx<RenderEvent | null, E, R>
73
+ ): Effect.Effect<string, E, R> {
63
74
  return Effect.map(Fx.toReadonlyArray(renderToHtml(fx)), join(""))
64
75
  }
65
76
 
@@ -73,24 +84,28 @@ export function renderHtmlTemplate(ctx: RenderContext.RenderContext) {
73
84
  ): Fx.Fx<
74
85
  RenderEvent,
75
86
  Placeholder.Error<Values[number]>,
76
- Scope.Scope | Placeholder.Context<readonly [] extends Values ? never : Values[number]>
87
+ | Scope.Scope
88
+ | Placeholder.Context<Values[number]>
77
89
  > => {
78
90
  const isStatic = ctx.environment === "static" || ctx.environment === "test:static"
79
91
  const entry = getServerEntry(templateStrings, ctx.templateCache, isStatic)
80
92
 
81
93
  if (values.length === 0) {
82
- return Fx.succeed(HtmlRenderEvent((entry.chunks[0] as TextChunk).value))
94
+ return Fx.succeed(
95
+ HtmlRenderEvent((entry.chunks[0] as TextChunk).value, true)
96
+ )
83
97
  } else {
84
- return Fx.filter(
85
- Fx.mergeOrdered(
86
- entry.chunks.map((chunk) =>
87
- renderChunk<
88
- Placeholder.Error<Values[number]>,
89
- Placeholder.Context<readonly [] extends Values ? never : Values[number]>
90
- >(chunk, values, isStatic)
91
- )
92
- ),
93
- (x) => (x.valueOf() as string).length > 0
98
+ const lastIndex = entry.chunks.length - 1
99
+ return Fx.mergeOrdered(
100
+ entry.chunks.map((chunk, i) =>
101
+ renderChunk<
102
+ Placeholder.Error<Values[number]>,
103
+ Placeholder.Context<Values[number]>
104
+ >(chunk, values, isStatic, i === lastIndex)
105
+ )
106
+ ).pipe(
107
+ Fx.filter((x) => x.html.length > 0),
108
+ Fx.dropAfter((x) => x.done)
94
109
  )
95
110
  }
96
111
  }
@@ -99,47 +114,70 @@ export function renderHtmlTemplate(ctx: RenderContext.RenderContext) {
99
114
  function renderChunk<E, R>(
100
115
  chunk: HtmlChunk,
101
116
  values: ReadonlyArray<Renderable<any, any>>,
102
- isStatic: boolean
103
- ): Fx.Fx<RenderEvent, E, R> {
117
+ isStatic: boolean,
118
+ done: boolean
119
+ ): Fx.Fx<HtmlRenderEvent, E, R | Scope.Scope> {
104
120
  if (chunk._tag === "text") {
105
- return Fx.succeed(HtmlRenderEvent(chunk.value))
121
+ return Fx.succeed(HtmlRenderEvent(chunk.value, done))
106
122
  } else if (chunk._tag === "part") {
107
- return renderPart<E, R>(chunk, values, isStatic)
123
+ return renderPart<E, R>(chunk, values, isStatic, done)
108
124
  } else {
109
- return renderSparsePart<E, R>(chunk, values) as Fx.Fx<RenderEvent, E, R>
125
+ return renderSparsePart<E, R>(chunk, values, done) as Fx.Fx<
126
+ HtmlRenderEvent,
127
+ E,
128
+ R
129
+ >
110
130
  }
111
131
  }
112
132
 
113
- function renderNode<E, R>(renderable: Renderable<any, any>, isStatic: boolean): Fx.Fx<RenderEvent, E, R> {
133
+ function renderNode<E, R>(
134
+ renderable: Renderable<any, any>,
135
+ isStatic: boolean,
136
+ done: boolean
137
+ ): Fx.Fx<HtmlRenderEvent, E, R | Scope.Scope> {
114
138
  switch (typeof renderable) {
115
139
  case "string":
116
140
  case "number":
117
141
  case "boolean":
118
142
  case "bigint":
119
- return Fx.succeed(HtmlRenderEvent((isStatic ? "" : TEXT_START) + renderable.toString()))
143
+ return Fx.succeed(
144
+ HtmlRenderEvent(
145
+ (isStatic ? "" : TEXT_START) + renderable.toString(),
146
+ done
147
+ )
148
+ )
120
149
  case "undefined":
121
150
  case "object":
122
- return renderObject(renderable, isStatic)
151
+ return renderObject(renderable, isStatic, done)
123
152
  default:
124
153
  return Fx.empty
125
154
  }
126
155
  }
127
156
 
128
- function renderObject<E, R>(renderable: object | null | undefined, isStatic: boolean) {
129
- if (renderable === null || renderable === undefined) {
130
- return isStatic ? Fx.empty : Fx.succeed(HtmlRenderEvent(TEXT_START))
157
+ function renderObject<E, R>(
158
+ renderable: object | null | undefined,
159
+ isStatic: boolean,
160
+ done: boolean
161
+ ): Fx.Fx<HtmlRenderEvent, E, R | Scope.Scope> {
162
+ if (isNullOrUndefined(renderable)) {
163
+ return isStatic ? Fx.empty : Fx.succeed(HtmlRenderEvent(TEXT_START, done))
131
164
  } else if (Array.isArray(renderable)) {
132
- return Fx.mergeOrdered(renderable.map((r) => renderNode(r, isStatic))) as any
133
- } else if (Fx.isFx<R, E, Renderable>(renderable)) {
134
- // @ts-ignore Types are to deep to infer
135
- return Fx.concatMap(takeOneIfNotRenderEvent(renderable), (r) => renderNode(r, isStatic) as any)
165
+ const lastIndex = renderable.length - 1
166
+ return Fx.mergeOrdered(
167
+ renderable.map((r, i) => renderNode(r, isStatic, done && i === lastIndex))
168
+ ) as any
136
169
  } else if (Effect.isEffect(renderable)) {
137
- return Fx.switchMap(
138
- Fx.fromEffect(renderable as Effect.Effect<Renderable, E, R>),
139
- (r) => renderNode<E, R>(r, isStatic)
170
+ return Fx.fromFxEffect(
171
+ Effect.map(renderable as Effect.Effect<Renderable, E, R>, (r) => renderNode<E, R>(r, isStatic, done))
140
172
  )
141
- } else if (isRenderEvent(renderable)) {
142
- return Fx.succeed(renderable)
173
+ } else if (Fx.isFx<RenderEvent, E, R>(renderable)) {
174
+ return takeOneIfNotRenderEvent(renderable, isStatic, done)
175
+ } else if (isHtmlRenderEvent(renderable)) {
176
+ if (done) {
177
+ return Fx.succeed(renderable)
178
+ } else {
179
+ return Fx.succeed(HtmlRenderEvent(renderable.html, done))
180
+ }
143
181
  } else {
144
182
  return Fx.empty
145
183
  }
@@ -148,55 +186,95 @@ function renderObject<E, R>(renderable: object | null | undefined, isStatic: boo
148
186
  function renderPart<E, R>(
149
187
  chunk: PartChunk,
150
188
  values: ReadonlyArray<Renderable<any, any>>,
151
- isStatic: boolean
152
- ): Fx.Fx<RenderEvent, E, R> {
189
+ isStatic: boolean,
190
+ done: boolean
191
+ ): Fx.Fx<HtmlRenderEvent, E, R | Scope.Scope> {
153
192
  const { node, render } = chunk
154
193
  const renderable: Renderable<any, any> = values[node.index]
155
194
 
156
195
  // Refs and events are not rendered into HTML
157
196
  if (isDirective<E, R>(renderable)) {
158
- return Fx.make<RenderEvent, E, R>((sink: Sink.Sink<RenderEvent, E>) => {
159
- const part = partNodeToPart(
160
- node,
161
- (value) => sink.onSuccess(HtmlRenderEvent(render(value)))
162
- )
197
+ return Fx.make<HtmlRenderEvent, E, R>(
198
+ (sink: Sink.Sink<HtmlRenderEvent, E>) => {
199
+ const part = partNodeToPart(node, (value) => sink.onSuccess(HtmlRenderEvent(render(value), done)))
163
200
 
164
- return Effect.catchAllCause(renderable(part), sink.onFailure)
165
- })
201
+ return Effect.catchAllCause(renderable(part), sink.onFailure)
202
+ }
203
+ )
166
204
  } else if (node._tag === "node") {
167
- if (isStatic) return renderNode<E, R>(renderable, isStatic)
168
- return Fx.append(renderNode<E, R>(renderable, isStatic), HtmlRenderEvent(TYPED_HOLE(node.index)))
205
+ if (isStatic) return renderNode<E, R>(renderable, isStatic, done)
206
+ let first = true
207
+ return Fx.continueWith(
208
+ Fx.map(renderNode<E, R>(renderable, isStatic, true), (x) => {
209
+ if (x.done) {
210
+ const y = HtmlRenderEvent(
211
+ (first ? TYPED_HOLE_START(node.index) : "") +
212
+ x.html +
213
+ TYPED_HOLE_END(node.index),
214
+ done
215
+ )
216
+ first = false
217
+ return y
218
+ } else {
219
+ if (first) {
220
+ first = false
221
+ return HtmlRenderEvent(
222
+ TYPED_HOLE_START(node.index) + x.html,
223
+ false
224
+ )
225
+ }
226
+ return x
227
+ }
228
+ }),
229
+ () =>
230
+ first
231
+ ? Fx.succeed(
232
+ HtmlRenderEvent(
233
+ TYPED_HOLE_START(node.index) + TYPED_HOLE_END(node.index),
234
+ done
235
+ )
236
+ )
237
+ : Fx.empty
238
+ )
169
239
  } else if (node._tag === "properties") {
170
240
  if (renderable == null) return Fx.empty
171
- return Fx.map(
172
- Fx.take(
173
- Fx.struct(
174
- Object.fromEntries(Object.entries(renderable).map(([k, v]) => [k, unwrapRenderable(v)] as const))
175
- ),
176
- 1
177
- ),
178
- render
179
- ) as any
180
- } else {
181
- if (renderable === null) return Fx.succeed(HtmlRenderEvent(render(renderable)))
182
241
 
183
- const html = Fx.filterMap(Fx.take(unwrapRenderable<E, R>(renderable), 1), (value) => {
184
- const s = render(value)
242
+ return Fx.mergeAll(
243
+ Object.entries(renderable as Record<string, Renderable<any, any>>).map(
244
+ ([key, renderable]) => {
245
+ return Fx.filterMap(
246
+ Fx.take(unwrapRenderable<E, R>(renderable), 1),
247
+ (value) => {
248
+ const s = render({ [key]: value })
185
249
 
186
- return s ? Option.some(HtmlRenderEvent(s)) : Option.none()
187
- })
188
-
189
- if (isStatic === false && node._tag === "text-part") {
190
- return Fx.append(Fx.prepend(html, HtmlRenderEvent(TEXT_START)), HtmlRenderEvent(TYPED_HOLE(node.index)))
250
+ return s ? Option.some(HtmlRenderEvent(s, done)) : Option.none()
251
+ }
252
+ )
253
+ }
254
+ )
255
+ )
256
+ } else {
257
+ if (renderable === null) {
258
+ return Fx.succeed(HtmlRenderEvent(render(renderable), done))
191
259
  }
192
260
 
261
+ const html = Fx.filterMap(
262
+ Fx.take(unwrapRenderable<E, R>(renderable), 1),
263
+ (value) => {
264
+ const s = render(value)
265
+
266
+ return s ? Option.some(HtmlRenderEvent(s, done)) : Option.none()
267
+ }
268
+ )
269
+
193
270
  return html
194
271
  }
195
272
  }
196
273
 
197
274
  function renderSparsePart<E, R>(
198
275
  chunk: SparsePartChunk,
199
- values: ReadonlyArray<Renderable<any, any>>
276
+ values: ReadonlyArray<Renderable<any, any>>,
277
+ done: boolean
200
278
  ): Fx.Fx<RenderEvent, E, R> {
201
279
  const { node, render } = chunk
202
280
 
@@ -211,7 +289,9 @@ function renderSparsePart<E, R>(
211
289
  if (isDirective<E, R>(renderable)) {
212
290
  return Fx.make<unknown, E, R>((sink: Sink.Sink<unknown, E>) =>
213
291
  Effect.catchAllCause(
214
- renderable(partNodeToPart(node, (value) => sink.onSuccess(value))),
292
+ renderable(
293
+ partNodeToPart(node, (value) => sink.onSuccess(value))
294
+ ),
215
295
  sink.onFailure
216
296
  )
217
297
  )
@@ -222,20 +302,42 @@ function renderSparsePart<E, R>(
222
302
  ),
223
303
  1
224
304
  ),
225
- (value) => HtmlRenderEvent(render(value))
305
+ (value) => HtmlRenderEvent(render(value), done)
226
306
  )
227
307
  }
228
308
 
229
- function takeOneIfNotRenderEvent<A, E, R>(fx: Fx.Fx<A, E, R>): Fx.Fx<A, E, R> {
230
- return Fx.make<A, E, R>((sink) =>
231
- Sink.withEarlyExit(sink, (sink) =>
309
+ function takeOneIfNotRenderEvent<A, E, R>(
310
+ fx: Fx.Fx<A, E, R>,
311
+ isStatic: boolean,
312
+ done: boolean
313
+ ): Fx.Fx<HtmlRenderEvent, E, R> {
314
+ return Fx.make<HtmlRenderEvent, E, R>((sink) =>
315
+ Effect.uninterruptible(Sink.withEarlyExit(sink, (sink) =>
232
316
  fx.run(
233
- Sink.make(
234
- sink.onFailure,
235
- (event) =>
236
- isRenderEvent(event) ? sink.onSuccess(event) : Effect.zipRight(sink.onSuccess(event), sink.earlyExit)
237
- )
238
- ))
317
+ Sink.make(sink.onFailure, (event) => {
318
+ if (isHtmlRenderEvent(event)) {
319
+ if (done) {
320
+ return sink.onSuccess(event)
321
+ } else {
322
+ return sink.onSuccess(HtmlRenderEvent(event.html, false))
323
+ }
324
+ }
325
+
326
+ if (isNullOrUndefined(event)) {
327
+ return sink.earlyExit
328
+ }
329
+
330
+ return Effect.zipRight(
331
+ sink.onSuccess(
332
+ HtmlRenderEvent(
333
+ (isStatic ? "" : TEXT_START) + String(event),
334
+ done
335
+ )
336
+ ),
337
+ sink.earlyExit
338
+ )
339
+ })
340
+ )))
239
341
  )
240
342
  }
241
343
 
@@ -262,17 +364,24 @@ function getServerEntry(
262
364
  }
263
365
  }
264
366
 
265
- function unwrapRenderable<E, R>(renderable: Renderable<any, any>): Fx.Fx<any, E, R> {
367
+ function unwrapRenderable<E, R>(
368
+ renderable: Renderable<any, any>
369
+ ): Fx.Fx<any, E, R> {
266
370
  switch (typeof renderable) {
267
371
  case "undefined":
268
372
  case "object": {
269
- if (renderable === null || renderable === undefined) return Fx.succeed(null)
270
- else if (Array.isArray(renderable)) {
271
- return Fx.tuple(renderable.map(unwrapRenderable)) as any
373
+ if (isNullOrUndefined(renderable)) {
374
+ return Fx.null
375
+ } else if (Array.isArray(renderable)) {
376
+ return Fx.mergeOrdered(
377
+ renderable.map((r) => takeOneIfNotRenderEvent(unwrapRenderable(r), true, false))
378
+ ) as any
379
+ } else if (Effect.EffectTypeId in renderable) {
380
+ return Fx.fromFxEffect(
381
+ Effect.map(renderable as any, unwrapRenderable<any, any>)
382
+ )
272
383
  } else if (TypeId in renderable) {
273
384
  return renderable as any
274
- } else if (Effect.EffectTypeId in renderable) {
275
- return Fx.fromFxEffect(Effect.map(renderable as any, unwrapRenderable<any, any>))
276
385
  } else return Fx.succeed(renderable as any)
277
386
  }
278
387
  default:
package/src/HtmlChunk.ts CHANGED
@@ -1,7 +1,8 @@
1
1
  /**
2
2
  * @since 1.0.0
3
3
  */
4
- import { TYPED_HASH } from "./Meta.js"
4
+ import { keyToPartType } from "./internal/utils.js"
5
+ import { isNullOrUndefined } from "./internal/v2/helpers.js"
5
6
  import type {
6
7
  Attribute,
7
8
  ElementNode,
@@ -64,11 +65,27 @@ export type AttrValue = string | null | undefined | ReadonlyArray<AttrValue>
64
65
  * @since 1.0.0
65
66
  */
66
67
  export function templateToHtmlChunks({ hash, nodes }: Template, isStatic: boolean) {
67
- const chunks = fuseTextChunks(nodes.flatMap((node) => nodeToHtmlChunk(node, isStatic ? undefined : hash)))
68
+ if (isStatic) {
69
+ return fuseTextChunks(nodes.flatMap((node) => nodeToHtmlChunk(node)))
70
+ }
71
+
72
+ const chunks = fuseTextChunks([
73
+ templateStart(hash),
74
+ ...nodes.flatMap((node) => nodeToHtmlChunk(node, hash)),
75
+ templateEnd(hash)
76
+ ])
68
77
 
69
78
  return chunks
70
79
  }
71
80
 
81
+ function templateStart(hash: string): HtmlChunk {
82
+ return new TextChunk(`<!--typed-${hash}-->`)
83
+ }
84
+
85
+ function templateEnd(hash: string): HtmlChunk {
86
+ return new TextChunk(`<!--/typed-${hash}-->`)
87
+ }
88
+
72
89
  function fuseTextChunks(chunks: Array<HtmlChunk>): ReadonlyArray<HtmlChunk> {
73
90
  const output: Array<HtmlChunk> = []
74
91
 
@@ -124,19 +141,18 @@ function nodeToHtmlChunk(node: Node, hash?: string): Array<HtmlChunk> {
124
141
  }
125
142
 
126
143
  function elementToHtmlChunks(
127
- { attributes, children, tagName }: ElementNode,
128
- hash?: string
144
+ { attributes, children, tagName }: ElementNode
129
145
  ): Array<HtmlChunk> {
130
146
  if (attributes.length === 0) {
131
147
  return [
132
- new TextChunk(openTag(tagName, hash) + ">"),
148
+ new TextChunk(openTag(tagName) + ">"),
133
149
  ...children.flatMap((c) => nodeToHtmlChunk(c)),
134
150
  new TextChunk(closeTag(tagName))
135
151
  ]
136
152
  }
137
153
 
138
154
  const chunks: Array<HtmlChunk> = [
139
- new TextChunk(openTag(tagName, hash)),
155
+ new TextChunk(openTag(tagName)),
140
156
  ...attributes.map((a) => attributeToHtmlChunk(a)),
141
157
  new TextChunk(">"),
142
158
  ...children.flatMap((c) => nodeToHtmlChunk(c)),
@@ -147,15 +163,14 @@ function elementToHtmlChunks(
147
163
  }
148
164
 
149
165
  function selfClosingElementToHtmlChunks(
150
- { attributes, tagName }: SelfClosingElementNode,
151
- hash?: string
166
+ { attributes, tagName }: SelfClosingElementNode
152
167
  ): Array<HtmlChunk> {
153
168
  if (attributes.length === 0) {
154
- return [new TextChunk(openTag(tagName, hash) + "/>")]
169
+ return [new TextChunk(openTag(tagName) + " />")]
155
170
  }
156
171
 
157
172
  const chunks: Array<HtmlChunk> = [
158
- new TextChunk(openTag(tagName, hash)),
173
+ new TextChunk(openTag(tagName)),
159
174
  ...attributes.map((a) => attributeToHtmlChunk(a)),
160
175
  new TextChunk(`/>`)
161
176
  ]
@@ -168,19 +183,18 @@ function textToHtmlChunks(text: Text): HtmlChunk {
168
183
  }
169
184
 
170
185
  function textOnlyElementToHtmlChunks(
171
- { attributes, children, tagName }: TextOnlyElement,
172
- hash?: string
186
+ { attributes, children, tagName }: TextOnlyElement
173
187
  ): Array<HtmlChunk> {
174
188
  if (attributes.length === 0) {
175
189
  return [
176
- new TextChunk(openTag(tagName, hash) + ">"),
190
+ new TextChunk(openTag(tagName) + ">"),
177
191
  ...children.map((c) => textToHtmlChunks(c)),
178
192
  new TextChunk(closeTag(tagName))
179
193
  ]
180
194
  }
181
195
 
182
196
  const chunks: Array<HtmlChunk> = [
183
- new TextChunk(openTag(tagName, hash)),
197
+ new TextChunk(openTag(tagName)),
184
198
  ...attributes.map((a) => attributeToHtmlChunk(a)),
185
199
  new TextChunk(">"),
186
200
  ...children.map((c) => textToHtmlChunks(c)),
@@ -195,11 +209,11 @@ type AttrMap = {
195
209
  }
196
210
 
197
211
  const attrMap: AttrMap = {
198
- attribute: (attr) => new TextChunk(` ${attr.name}="${attr.value}"`),
199
- attr: (attr) => new PartChunk(attr, (value) => (value == null ? `` : ` ${attr.name}="${value}"`)),
212
+ attribute: (attr) => new TextChunk(` ${attr.name}="${escape(attr.value)}"`),
213
+ attr: (attr) => new PartChunk(attr, (value) => (value == null ? `` : ` ${attr.name}="${escape(value)}"`)),
200
214
  boolean: (attr) => new TextChunk(" " + attr.name),
201
215
  "boolean-part": (attr) => new PartChunk(attr, (value) => (value ? ` ${attr.name}` : "")),
202
- "className-part": (attr) => new PartChunk(attr, (value) => (value ? ` class="${value}"` : "")),
216
+ "className-part": (attr) => new PartChunk(attr, (value) => (value ? ` class="${escape(value)}"` : "")),
203
217
  data: (attr) =>
204
218
  new PartChunk(attr, (value) => value == null ? `` : datasetToString(value as Readonly<Record<string, string>>)),
205
219
  event: () => new TextChunk(""),
@@ -209,24 +223,45 @@ const attrMap: AttrMap = {
209
223
  attr,
210
224
  (
211
225
  value
212
- ) => (value == null
213
- ? ``
214
- : " " + Object.entries(value).map(([key, value]) =>
215
- value === true ? key : value === false ? "" : `${key}="${escape(value)}"`
216
- ).join(" "))
226
+ ) => {
227
+ if (value == null) return ""
228
+
229
+ // Each call has only 1 key-value pair
230
+ const [k, v] = Object.entries(value)[0]
231
+
232
+ if (value == null) return ""
233
+
234
+ const [type, key] = keyToPartType(k)
235
+
236
+ switch (type) {
237
+ case "attr":
238
+ case "property":
239
+ return v == null ? "" : ` ${key}="${escape(v)}"`
240
+ case "boolean":
241
+ return v ? key : ""
242
+ case "class":
243
+ return ` class="${escape(v)}"`
244
+ case "data": {
245
+ const d = datasetToString(v)
246
+ return d.length === 0 ? "" : ` ${d}`
247
+ }
248
+ default:
249
+ return ""
250
+ }
251
+ }
217
252
  ),
218
253
  ref: () => new TextChunk(""),
219
254
  "sparse-attr": (attr) =>
220
255
  new SparsePartChunk(attr, (values) => {
221
256
  return values == null
222
257
  ? ``
223
- : ` ${attr.name}="${Array.isArray(values) ? values.filter(isString).join("") : values}"`
258
+ : ` ${attr.name}="${Array.isArray(values) ? escape(values.filter(isString).join("")) : escape(values)}"`
224
259
  }),
225
260
  "sparse-class-name": (attr) =>
226
261
  new SparsePartChunk(attr, (values) => {
227
262
  return values == null
228
263
  ? ``
229
- : ` class="${Array.isArray(values) ? values.filter(isString).join(" ") : values}"`
264
+ : ` class="${Array.isArray(values) ? escape(values.filter(isString).join("")) : escape(values)}"`
230
265
  }),
231
266
  text: (attr) => new TextChunk(attr.value)
232
267
  }
@@ -241,16 +276,14 @@ function isString(value: unknown): value is string {
241
276
 
242
277
  function datasetToString(dataset: Readonly<Record<string, string | undefined>>) {
243
278
  const s = Object.entries(dataset)
244
- .map(([key, value]) => (value === undefined ? `data-${key}` : `data-${key}="${value}"`))
279
+ .map(([key, value]) => (value === undefined ? `data-${key}` : `data-${key}="${escape(value)}"`))
245
280
  .join(" ")
246
281
 
247
282
  return s.length === 0 ? "" : " " + s
248
283
  }
249
284
 
250
- function openTag(tagName: string, hash?: string): string {
251
- if (hash === undefined) return `<${tagName}`
252
-
253
- return `<${tagName} ${TYPED_HASH(hash)}`
285
+ function openTag(tagName: string): string {
286
+ return `<${tagName}`
254
287
  }
255
288
 
256
289
  function closeTag(tagName: string): string {
@@ -260,12 +293,26 @@ function closeTag(tagName: string): string {
260
293
  /**
261
294
  * @since 1.0.0
262
295
  */
263
- export function escape(s: unknown) {
296
+ export function escape(s: unknown): string {
264
297
  switch (typeof s) {
265
298
  case "string":
266
299
  case "number":
267
300
  case "boolean":
301
+ case "bigint":
268
302
  return escapeHtml(String(s))
303
+ case "object": {
304
+ if (isNullOrUndefined(s)) {
305
+ return ""
306
+ } else if (Array.isArray(s)) {
307
+ return s.map(escape).join("")
308
+ } else if (s instanceof Date) {
309
+ return escapeHtml(s.toISOString())
310
+ } else if (s instanceof RegExp) {
311
+ return escapeHtml(s.toString())
312
+ } else {
313
+ return escapeHtml(JSON.stringify(s))
314
+ }
315
+ }
269
316
  default:
270
317
  return escapeHtml(JSON.stringify(s))
271
318
  }