kiru 0.52.4 → 0.53.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 (141) hide show
  1. package/dist/appContext.d.ts.map +1 -1
  2. package/dist/appContext.js +14 -11
  3. package/dist/appContext.js.map +1 -1
  4. package/dist/components/derive.d.ts +20 -0
  5. package/dist/components/derive.d.ts.map +1 -0
  6. package/dist/components/derive.js +57 -0
  7. package/dist/components/derive.js.map +1 -0
  8. package/dist/components/errorBoundary.d.ts +1 -1
  9. package/dist/components/errorBoundary.d.ts.map +1 -1
  10. package/dist/components/index.d.ts +1 -1
  11. package/dist/components/index.d.ts.map +1 -1
  12. package/dist/components/index.js +1 -1
  13. package/dist/components/index.js.map +1 -1
  14. package/dist/components/memo.d.ts +6 -3
  15. package/dist/components/memo.d.ts.map +1 -1
  16. package/dist/components/memo.js.map +1 -1
  17. package/dist/constants.d.ts +3 -3
  18. package/dist/constants.d.ts.map +1 -1
  19. package/dist/constants.js +3 -3
  20. package/dist/constants.js.map +1 -1
  21. package/dist/dom.d.ts.map +1 -1
  22. package/dist/dom.js +69 -66
  23. package/dist/dom.js.map +1 -1
  24. package/dist/element.d.ts +2 -2
  25. package/dist/element.d.ts.map +1 -1
  26. package/dist/element.js +20 -32
  27. package/dist/element.js.map +1 -1
  28. package/dist/globalContext.d.ts +6 -1
  29. package/dist/globalContext.d.ts.map +1 -1
  30. package/dist/globalContext.js +14 -2
  31. package/dist/globalContext.js.map +1 -1
  32. package/dist/hooks/usePromise.d.ts +4 -9
  33. package/dist/hooks/usePromise.d.ts.map +1 -1
  34. package/dist/hooks/usePromise.js +25 -28
  35. package/dist/hooks/usePromise.js.map +1 -1
  36. package/dist/hooks/useRef.d.ts +2 -2
  37. package/dist/hooks/useRef.d.ts.map +1 -1
  38. package/dist/jsx.d.ts +1 -1
  39. package/dist/jsx.d.ts.map +1 -1
  40. package/dist/reconciler.d.ts +1 -1
  41. package/dist/reconciler.d.ts.map +1 -1
  42. package/dist/reconciler.js +57 -117
  43. package/dist/reconciler.js.map +1 -1
  44. package/dist/recursiveRender.d.ts +4 -3
  45. package/dist/recursiveRender.d.ts.map +1 -1
  46. package/dist/recursiveRender.js +20 -19
  47. package/dist/recursiveRender.js.map +1 -1
  48. package/dist/renderToString.js +2 -2
  49. package/dist/renderToString.js.map +1 -1
  50. package/dist/router/fileRouterController.d.ts +1 -1
  51. package/dist/router/index.d.ts +1 -1
  52. package/dist/router/index.d.ts.map +1 -1
  53. package/dist/router/utils/index.d.ts +1 -1
  54. package/dist/router/utils/index.d.ts.map +1 -1
  55. package/dist/signals/base.d.ts +1 -3
  56. package/dist/signals/base.d.ts.map +1 -1
  57. package/dist/signals/base.js +3 -3
  58. package/dist/signals/base.js.map +1 -1
  59. package/dist/signals/computed.d.ts +4 -2
  60. package/dist/signals/computed.d.ts.map +1 -1
  61. package/dist/signals/computed.js +29 -6
  62. package/dist/signals/computed.js.map +1 -1
  63. package/dist/signals/for.d.ts +10 -0
  64. package/dist/signals/for.d.ts.map +1 -0
  65. package/dist/signals/for.js +7 -0
  66. package/dist/signals/for.js.map +1 -0
  67. package/dist/signals/index.d.ts +1 -1
  68. package/dist/signals/index.js +1 -1
  69. package/dist/signals/types.d.ts +1 -1
  70. package/dist/signals/types.d.ts.map +1 -1
  71. package/dist/ssr/server.js +13 -13
  72. package/dist/ssr/server.js.map +1 -1
  73. package/dist/types.d.ts +10 -11
  74. package/dist/types.d.ts.map +1 -1
  75. package/dist/types.dom.d.ts +33 -32
  76. package/dist/types.dom.d.ts.map +1 -1
  77. package/dist/types.utils.d.ts +5 -1
  78. package/dist/types.utils.d.ts.map +1 -1
  79. package/dist/utils/format.d.ts +2 -2
  80. package/dist/utils/format.d.ts.map +1 -1
  81. package/dist/utils/format.js +4 -3
  82. package/dist/utils/format.js.map +1 -1
  83. package/dist/utils/index.d.ts +0 -1
  84. package/dist/utils/index.d.ts.map +1 -1
  85. package/dist/utils/index.js +0 -1
  86. package/dist/utils/index.js.map +1 -1
  87. package/dist/utils/promise.d.ts +16 -0
  88. package/dist/utils/promise.d.ts.map +1 -0
  89. package/dist/utils/promise.js +14 -0
  90. package/dist/utils/promise.js.map +1 -0
  91. package/dist/utils/runtime.d.ts +10 -1
  92. package/dist/utils/runtime.d.ts.map +1 -1
  93. package/dist/utils/runtime.js +25 -1
  94. package/dist/utils/runtime.js.map +1 -1
  95. package/dist/utils/vdom.d.ts +4 -4
  96. package/dist/utils/vdom.d.ts.map +1 -1
  97. package/dist/utils/vdom.js +18 -17
  98. package/dist/utils/vdom.js.map +1 -1
  99. package/dist/vNode.d.ts +4 -0
  100. package/dist/vNode.d.ts.map +1 -0
  101. package/dist/vNode.js +22 -0
  102. package/dist/vNode.js.map +1 -0
  103. package/package.json +1 -1
  104. package/src/appContext.ts +15 -13
  105. package/src/components/derive.ts +121 -0
  106. package/src/components/index.ts +1 -1
  107. package/src/components/memo.ts +3 -2
  108. package/src/constants.ts +4 -4
  109. package/src/dom.ts +71 -66
  110. package/src/element.ts +22 -35
  111. package/src/globalContext.ts +24 -3
  112. package/src/hooks/usePromise.ts +32 -41
  113. package/src/hooks/useRef.ts +2 -2
  114. package/src/reconciler.ts +87 -125
  115. package/src/recursiveRender.ts +25 -23
  116. package/src/renderToString.ts +3 -3
  117. package/src/signals/base.ts +3 -3
  118. package/src/signals/computed.ts +43 -6
  119. package/src/signals/for.ts +25 -0
  120. package/src/signals/index.ts +1 -1
  121. package/src/signals/types.ts +5 -1
  122. package/src/ssr/server.ts +15 -15
  123. package/src/types.dom.ts +40 -40
  124. package/src/types.ts +11 -11
  125. package/src/types.utils.ts +11 -1
  126. package/src/utils/format.ts +7 -4
  127. package/src/utils/index.ts +0 -2
  128. package/src/utils/promise.ts +26 -0
  129. package/src/utils/runtime.ts +29 -1
  130. package/src/utils/vdom.ts +20 -23
  131. package/src/vNode.ts +30 -0
  132. package/dist/components/suspense.d.ts +0 -24
  133. package/dist/components/suspense.d.ts.map +0 -1
  134. package/dist/components/suspense.js +0 -36
  135. package/dist/components/suspense.js.map +0 -1
  136. package/dist/signals/jsx.d.ts +0 -17
  137. package/dist/signals/jsx.d.ts.map +0 -1
  138. package/dist/signals/jsx.js +0 -11
  139. package/dist/signals/jsx.js.map +0 -1
  140. package/src/components/suspense.ts +0 -72
  141. package/src/signals/jsx.ts +0 -46
@@ -1,43 +1,36 @@
1
- import { PREFETCHED_DATA_EVENT } from "../constants.js"
1
+ import { STREAMED_DATA_EVENT } from "../constants.js"
2
2
  import { __DEV__ } from "../env.js"
3
3
  import { hydrationMode, renderMode } from "../globals.js"
4
- import { requestUpdate } from "../scheduler.js"
5
4
  import { Signal, useSignal } from "../signals/base.js"
6
5
  import { cleanupHook, depsRequireChange, useHook } from "./utils.js"
7
6
  import { useId } from "./useId.js"
8
7
 
9
8
  export { usePromise }
10
- export type { UsePromiseCallbackContext, UsePromiseState }
11
9
 
12
10
  const nodeToPromiseIndex = new WeakMap<Kiru.VNode, number>()
13
11
 
14
- interface UsePromiseCallbackContext {
15
- signal: AbortSignal
16
- }
17
-
18
- interface UsePromiseState<T> {
19
- data: Kiru.StatefulPromise<T>
12
+ type UsePromiseResult<T> = Kiru.StatefulPromise<T> & {
20
13
  refresh: () => void
21
- pending: Signal<boolean>
14
+ isPending: Signal<boolean>
22
15
  }
23
16
 
24
17
  function usePromise<T>(
25
- callback: (ctx: UsePromiseCallbackContext) => Promise<T>,
18
+ callback: (signal: AbortSignal) => Promise<T>,
26
19
  deps: unknown[]
27
- ): UsePromiseState<T> {
20
+ ): UsePromiseResult<T> {
28
21
  const id = useId()
29
- const pending = useSignal(true)
22
+ const isPending = useSignal(true)
30
23
 
31
24
  return useHook(
32
25
  "usePromise",
33
26
  {} as {
34
- promise: Kiru.StatefulPromise<T>
27
+ promise: UsePromiseResult<T>
35
28
  abortController?: AbortController
36
29
  deps?: unknown[]
37
30
  },
38
- ({ hook, isInit, vNode }) => {
31
+ ({ hook, isInit, vNode, update }) => {
39
32
  if (isInit || depsRequireChange(deps, hook.deps)) {
40
- pending.value = true
33
+ isPending.value = true
41
34
  hook.deps = deps
42
35
  cleanupHook(hook)
43
36
 
@@ -59,77 +52,75 @@ function usePromise<T>(
59
52
  ) {
60
53
  // if we're hydrating and the hydration mode is not static,
61
54
  // we need to resolve the promise from cache/event
62
- promise = resolvePrefetchedPromise<T>(promiseId, controller.signal)
55
+ promise = resolveDeferredPromise<T>(promiseId, controller.signal)
63
56
  } else {
64
57
  // dom / stream / (hydrate + static)
65
- promise = callback({ signal: controller.signal })
58
+ promise = callback(controller.signal)
66
59
  }
67
60
 
68
61
  const state: Kiru.PromiseState<T> = { id: promiseId, state: "pending" }
69
- const statefulPromise = (hook.promise = Object.assign(promise, state))
62
+ const statefulPromise: Kiru.StatefulPromise<T> = (hook.promise =
63
+ Object.assign(promise, state, {
64
+ isPending,
65
+ refresh: () => {
66
+ hook.deps = undefined
67
+ update()
68
+ },
69
+ }))
70
70
 
71
71
  statefulPromise
72
72
  .then((value) => {
73
73
  statefulPromise.state = "fulfilled"
74
74
  statefulPromise.value = value
75
+ isPending.value = false
75
76
  })
76
77
  .catch((error) => {
77
78
  statefulPromise.state = "rejected"
78
79
  statefulPromise.error =
79
80
  error instanceof Error ? error : new Error(error)
80
81
  })
81
- .finally(() => {
82
- pending.value = false
83
- })
84
- }
85
- return {
86
- data: hook.promise,
87
- refresh: () => {
88
- hook.deps = undefined
89
- requestUpdate(vNode)
90
- },
91
- pending,
92
82
  }
83
+ return hook.promise
93
84
  }
94
85
  )
95
86
  }
96
87
 
97
- interface PrefetchedPromiseEventDetail<T> {
88
+ interface DeferredPromiseEventDetail<T> {
98
89
  id: string
99
90
  data?: T
100
91
  error?: string
101
92
  }
102
93
 
103
- function resolvePrefetchedPromise<T>(
94
+ function resolveDeferredPromise<T>(
104
95
  id: string,
105
96
  signal: AbortSignal
106
97
  ): Promise<T> {
107
98
  return new Promise<T>((resolve, reject) => {
108
- const prefetchCache: Map<string, { data?: T; error?: string }> = // @ts-ignore
109
- (window[PREFETCHED_DATA_EVENT] ??= new Map())
99
+ const deferralCache: Map<string, { data?: T; error?: string }> = // @ts-ignore
100
+ (window[STREAMED_DATA_EVENT] ??= new Map())
110
101
 
111
- const existing = prefetchCache.get(id)
102
+ const existing = deferralCache.get(id)
112
103
  if (existing) {
113
104
  const { data, error } = existing
114
- prefetchCache.delete(id)
105
+ deferralCache.delete(id)
115
106
  if (error) return reject(error)
116
107
  return resolve(data!)
117
108
  }
118
109
 
119
110
  const onDataEvent = (event: Event) => {
120
- const { detail } = event as CustomEvent<PrefetchedPromiseEventDetail<T>>
111
+ const { detail } = event as CustomEvent<DeferredPromiseEventDetail<T>>
121
112
  if (detail.id === id) {
122
- prefetchCache.delete(id)
123
- window.removeEventListener(PREFETCHED_DATA_EVENT, onDataEvent)
113
+ deferralCache.delete(id)
114
+ window.removeEventListener(STREAMED_DATA_EVENT, onDataEvent)
124
115
  const { data, error } = detail
125
116
  if (error) return reject(error)
126
117
  resolve(data!)
127
118
  }
128
119
  }
129
120
 
130
- window.addEventListener(PREFETCHED_DATA_EVENT, onDataEvent)
121
+ window.addEventListener(STREAMED_DATA_EVENT, onDataEvent)
131
122
  signal.addEventListener("abort", () => {
132
- window.removeEventListener(PREFETCHED_DATA_EVENT, onDataEvent)
123
+ window.removeEventListener(STREAMED_DATA_EVENT, onDataEvent)
133
124
  reject()
134
125
  })
135
126
  })
@@ -8,9 +8,9 @@ import { useHook } from "./utils.js"
8
8
  *
9
9
  * @see https://kirujs.dev/docs/hooks/useRef
10
10
  */
11
- export function useRef<T>(initialValue: T): Kiru.MutableRefObject<T>
11
+ export function useRef<T>(initialValue: T): Kiru.RefObject<T>
12
12
  export function useRef<T>(initialValue: T | null): Kiru.RefObject<T>
13
- export function useRef<T = undefined>(): Kiru.MutableRefObject<T | undefined>
13
+ export function useRef<T = undefined>(): Kiru.RefObject<T | undefined>
14
14
  export function useRef<T>(initialValue?: T | null) {
15
15
  if (!sideEffectsEnabled()) return { current: initialValue }
16
16
  return useHook(
package/src/reconciler.ts CHANGED
@@ -1,19 +1,30 @@
1
- import { $FRAGMENT, FLAG_PLACEMENT, FLAG_UPDATE } from "./constants.js"
1
+ import {
2
+ $FRAGMENT,
3
+ $MEMO,
4
+ FLAG_MEMO,
5
+ FLAG_PLACEMENT,
6
+ FLAG_UPDATE,
7
+ } from "./constants.js"
2
8
  import {
3
9
  getVNodeAppContext,
10
+ isElement,
4
11
  isValidTextChild,
5
- isVNode,
6
12
  latest,
7
13
  } from "./utils/index.js"
8
14
  import { Signal } from "./signals/base.js"
9
15
  import { __DEV__ } from "./env.js"
10
- import { createElement, Fragment } from "./element.js"
11
16
  import type { AppContext } from "./appContext.js"
17
+ import { isMemoFn } from "./components/memo.js"
18
+ import { createVNode as createBaseVNode } from "./vNode.js"
12
19
 
13
20
  type VNode = Kiru.VNode
21
+ type KElement = Kiru.Element
14
22
  let appCtx: AppContext
15
23
 
16
- export function reconcileChildren(parent: VNode, children: unknown) {
24
+ export function reconcileChildren(
25
+ parent: VNode,
26
+ children: unknown
27
+ ): VNode | null {
17
28
  if (__DEV__) {
18
29
  appCtx = getVNodeAppContext(parent)!
19
30
  }
@@ -30,7 +41,7 @@ export function reconcileChildren(parent: VNode, children: unknown) {
30
41
  return reconcileSingleChild(parent, children)
31
42
  }
32
43
 
33
- function reconcileSingleChild(parent: VNode, child: unknown) {
44
+ function reconcileSingleChild(parent: VNode, child: unknown): VNode | null {
34
45
  const oldChild = parent.child
35
46
  if (oldChild === null) {
36
47
  return createChild(parent, child)
@@ -52,9 +63,9 @@ function reconcileSingleChild(parent: VNode, child: unknown) {
52
63
  if (newNode !== null) {
53
64
  const prev = newNode.prev
54
65
  if (prev !== null) {
55
- const key = prev.props.key
66
+ const key = prev.key
56
67
  // node persisted, remove it from the list so it doesn't get deleted
57
- existingChildren.delete(key === undefined ? prev.index : key)
68
+ existingChildren.delete(key === null ? prev.index : key)
58
69
  }
59
70
  placeChild(newNode, 0, 0)
60
71
  }
@@ -134,9 +145,9 @@ function reconcileChildrenArray(parent: VNode, children: unknown[]) {
134
145
  if (newNode !== null) {
135
146
  const prev = newNode.prev
136
147
  if (prev !== null) {
137
- const key = prev.props.key
148
+ const key = prev.key
138
149
  // node persisted, remove it from the list so it doesn't get deleted
139
- existingChildren.delete(key === undefined ? prev.index : key)
150
+ existingChildren.delete(key === null ? prev.index : key)
140
151
  }
141
152
  lastPlacedIndex = placeChild(newNode, lastPlacedIndex, newIdx)
142
153
  if (prevNewChild === null) {
@@ -152,11 +163,15 @@ function reconcileChildrenArray(parent: VNode, children: unknown[]) {
152
163
  return resultingChild
153
164
  }
154
165
 
155
- function updateSlot(parent: VNode, oldChild: VNode | null, child: unknown) {
166
+ function updateSlot(
167
+ parent: VNode,
168
+ oldChild: VNode | null,
169
+ child: unknown
170
+ ): VNode | null {
156
171
  // Update the node if the keys match, otherwise return null.
157
- const key = oldChild?.props.key
172
+ const key = oldChild === null ? null : oldChild.key
158
173
  if (isValidTextChild(child)) {
159
- if (key !== undefined) return null
174
+ if (key !== null) return null
160
175
  if (
161
176
  oldChild?.type === "#text" &&
162
177
  Signal.isSignal(oldChild.props.nodeValue)
@@ -169,12 +184,12 @@ function updateSlot(parent: VNode, oldChild: VNode | null, child: unknown) {
169
184
  if (!!oldChild && oldChild.props.nodeValue !== child) return null
170
185
  return updateTextNode(parent, oldChild, child)
171
186
  }
172
- if (isVNode(child)) {
173
- if (child.props.key !== key) return null
187
+ if (isElement(child)) {
188
+ if (child.key !== key) return null
174
189
  return updateNode(parent, oldChild, child)
175
190
  }
176
191
  if (Array.isArray(child)) {
177
- if (key !== undefined) return null
192
+ if (key !== null) return null
178
193
  if (__DEV__) {
179
194
  markListChild(child)
180
195
  }
@@ -187,14 +202,9 @@ function updateTextNode(
187
202
  parent: VNode,
188
203
  oldChild: VNode | null,
189
204
  content: string | Signal<JSX.PrimitiveChild>
190
- ) {
205
+ ): VNode {
191
206
  if (oldChild === null || oldChild.type !== "#text") {
192
- if (__DEV__) {
193
- dev_emitCreateNode()
194
- }
195
- const newChild = createElement("#text", { nodeValue: content })
196
- setParent(newChild, parent)
197
- return newChild
207
+ return createVNode(parent, "#text", { nodeValue: content })
198
208
  }
199
209
 
200
210
  if (__DEV__) {
@@ -209,44 +219,38 @@ function updateTextNode(
209
219
  return oldChild
210
220
  }
211
221
 
212
- function updateNode(parent: VNode, oldChild: VNode | null, newChild: VNode) {
213
- let { type: nodeType, props: newProps } = newChild
222
+ function updateNode(parent: VNode, oldChild: VNode | null, newChild: KElement) {
223
+ let { type, props, key } = newChild
214
224
  if (__DEV__) {
215
- if (typeof nodeType === "function") {
216
- nodeType = latest(nodeType)
225
+ if (typeof type === "function") {
226
+ type = latest(type)
217
227
  }
218
228
  }
219
- if (nodeType === $FRAGMENT) {
229
+ if (type === $FRAGMENT) {
220
230
  return updateFragment(
221
231
  parent,
222
232
  oldChild,
223
- (newProps.children as VNode[]) || [],
224
- newProps
233
+ (props.children as VNode[]) || [],
234
+ props
225
235
  )
226
236
  }
227
- if (oldChild?.type === nodeType) {
237
+ if (oldChild?.type === type) {
228
238
  if (__DEV__) {
229
239
  dev_emitUpdateNode()
230
240
  }
231
241
  oldChild.index = 0
232
242
  oldChild.sibling = null
233
- if (typeof nodeType === "string") {
234
- if (propsChanged(oldChild.props, newProps)) {
243
+ if (typeof type === "string") {
244
+ if (propsChanged(oldChild.props, props)) {
235
245
  oldChild.flags |= FLAG_UPDATE
236
246
  }
237
247
  } else {
238
248
  oldChild.flags |= FLAG_UPDATE
239
249
  }
240
- oldChild.props = newProps
241
- oldChild.memoizedProps = newChild.memoizedProps
250
+ oldChild.props = props
242
251
  return oldChild
243
252
  }
244
- if (__DEV__) {
245
- dev_emitCreateNode()
246
- }
247
- const created = createElement(nodeType, newProps)
248
- setParent(created, parent)
249
- return created
253
+ return createVNode(parent, type, props, key)
250
254
  }
251
255
 
252
256
  function updateFragment(
@@ -256,12 +260,7 @@ function updateFragment(
256
260
  newProps = {}
257
261
  ) {
258
262
  if (oldChild === null || oldChild.type !== $FRAGMENT) {
259
- if (__DEV__) {
260
- dev_emitCreateNode()
261
- }
262
- const el = createElement($FRAGMENT, { children, ...newProps })
263
- setParent(el, parent)
264
- return el
263
+ return createVNode(parent, $FRAGMENT, { children, ...newProps })
265
264
  }
266
265
  if (__DEV__) {
267
266
  dev_emitUpdateNode()
@@ -274,45 +273,22 @@ function updateFragment(
274
273
 
275
274
  function createChild(parent: VNode, child: unknown): VNode | null {
276
275
  if (isValidTextChild(child)) {
277
- if (__DEV__) {
278
- dev_emitCreateNode()
279
- }
280
- const el = createElement("#text", {
281
- nodeValue: "" + child,
282
- })
283
- setParent(el, parent)
284
- return el
276
+ return createVNode(parent, "#text", { nodeValue: "" + child })
285
277
  }
286
278
 
287
279
  if (Signal.isSignal(child)) {
288
- if (__DEV__) {
289
- dev_emitCreateNode()
290
- }
291
- const el = createElement("#text", {
292
- nodeValue: child,
293
- })
294
- setParent(el, parent)
295
- return el
280
+ return createVNode(parent, "#text", { nodeValue: child })
296
281
  }
297
282
 
298
- if (isVNode(child)) {
299
- if (__DEV__) {
300
- dev_emitCreateNode()
301
- }
302
- const newNode = createElement(child.type, child.props)
303
- setParent(newNode, parent)
304
- newNode.flags |= FLAG_PLACEMENT
305
- return newNode
283
+ if (isElement(child)) {
284
+ return createVNode(parent, child.type, child.props, child.key)
306
285
  }
307
286
 
308
287
  if (Array.isArray(child)) {
309
288
  if (__DEV__) {
310
- dev_emitCreateNode()
311
289
  markListChild(child)
312
290
  }
313
- const el = Fragment({ children: child })
314
- setParent(el, parent)
315
- return el
291
+ return createVNode(parent, $FRAGMENT, { children: child })
316
292
  }
317
293
 
318
294
  return null
@@ -360,47 +336,30 @@ function updateFromMap(
360
336
  }
361
337
  }
362
338
 
363
- if (__DEV__) {
364
- dev_emitCreateNode()
365
- }
366
- const newChild = createElement("#text", {
367
- nodeValue: child,
368
- })
369
- setParent(newChild, parent)
370
- newChild.flags |= FLAG_PLACEMENT
371
- newChild.index = index
372
- return newChild
373
- }
374
-
375
- if (isVNode(child)) {
376
- const { type, props: newProps } = child
377
- const key = newProps.key
378
- const oldChild = existingChildren.get(key === undefined ? index : key)
339
+ return createVNode(parent, "#text", { nodeValue: child }, null, index)
340
+ }
341
+
342
+ if (isElement(child)) {
343
+ const { type, props, key } = child
344
+ const oldChild = existingChildren.get(key === null ? index : key)
379
345
  if (oldChild?.type === type) {
380
346
  if (__DEV__) {
381
347
  dev_emitUpdateNode()
382
348
  }
383
349
  if (typeof type === "string") {
384
- if (propsChanged(oldChild.props, newProps)) {
350
+ if (propsChanged(oldChild.props, props)) {
385
351
  oldChild.flags |= FLAG_UPDATE
386
352
  }
387
353
  } else {
388
354
  oldChild.flags |= FLAG_UPDATE
389
355
  }
390
- oldChild.props = newProps
356
+ oldChild.props = props
391
357
  oldChild.sibling = null
392
358
  oldChild.index = index
393
359
  return oldChild
394
360
  }
395
361
 
396
- if (__DEV__) {
397
- dev_emitCreateNode()
398
- }
399
- const newChild = createElement(child.type, child.props)
400
- setParent(newChild, parent)
401
- newChild.flags |= FLAG_PLACEMENT
402
- newChild.index = index
403
- return newChild
362
+ return createVNode(parent, type, props, key, index)
404
363
  }
405
364
 
406
365
  if (Array.isArray(child)) {
@@ -417,14 +376,7 @@ function updateFromMap(
417
376
  return oldChild
418
377
  }
419
378
 
420
- if (__DEV__) {
421
- dev_emitCreateNode()
422
- }
423
- const newChild = Fragment({ children: child })
424
- setParent(newChild, parent)
425
- newChild.flags |= FLAG_PLACEMENT
426
- newChild.index = index
427
- return newChild
379
+ return createVNode(parent, $FRAGMENT, { children: child }, null, index)
428
380
  }
429
381
 
430
382
  return null
@@ -441,21 +393,11 @@ function propsChanged(oldProps: VNode["props"], newProps: VNode["props"]) {
441
393
  return false
442
394
  }
443
395
 
444
- function setParent(child: VNode, parent: VNode) {
445
- child.parent = parent
446
- child.depth = parent.depth + 1
447
- }
448
-
449
396
  function dev_emitUpdateNode() {
450
397
  if (!("window" in globalThis)) return
451
398
  window.__kiru.profilingContext?.emit("updateNode", appCtx)
452
399
  }
453
400
 
454
- function dev_emitCreateNode() {
455
- if (!("window" in globalThis)) return
456
- window.__kiru.profilingContext?.emit("createNode", appCtx)
457
- }
458
-
459
401
  const $LIST_CHILD = Symbol("kiru:marked-list-child")
460
402
  function markListChild(children: unknown[]) {
461
403
  Object.assign(children, { [$LIST_CHILD]: true })
@@ -464,8 +406,8 @@ function markListChild(children: unknown[]) {
464
406
  function mapRemainingChildren(child: VNode | null) {
465
407
  const map: Map<JSX.ElementKey, VNode> = new Map()
466
408
  while (child) {
467
- const key = child.props.key
468
- map.set(key === undefined ? child.index : key, child)
409
+ const key = child.key
410
+ map.set(key === null ? child.index : key, child)
469
411
  child = child.sibling
470
412
  }
471
413
  return map
@@ -490,8 +432,8 @@ function checkForDuplicateKeys(parent: VNode, children: unknown[]) {
490
432
  const keys = new Set<string>()
491
433
  let warned = false
492
434
  for (const child of children) {
493
- if (!isVNode(child)) continue
494
- const key = child.props.key
435
+ if (!isElement(child)) continue
436
+ const key = child.key
495
437
  if (typeof key === "string") {
496
438
  if (!warned && keys.has(key)) {
497
439
  const fn = getNearestParentFcTag(parent)
@@ -509,9 +451,8 @@ function checkForMissingKeys(parent: VNode, children: unknown[]) {
509
451
  let hasKey = false
510
452
  let hasMissingKey = false
511
453
  for (const child of children) {
512
- if (!isVNode(child)) continue
513
- const key = child.props.key
514
- if (typeof key === "string") {
454
+ if (!isElement(child)) continue
455
+ if (typeof child.key === "string") {
515
456
  hasKey = true
516
457
  } else {
517
458
  hasMissingKey = true
@@ -545,3 +486,24 @@ function getNearestParentFcTag(vNode: VNode) {
545
486
  parentFcTagLookups.set(vNode, tag)
546
487
  return tag
547
488
  }
489
+
490
+ function createVNode(
491
+ parent: VNode,
492
+ type: VNode["type"],
493
+ props: VNode["props"],
494
+ key: VNode["key"] = null,
495
+ index = 0
496
+ ): VNode {
497
+ const node = createBaseVNode(type, parent, props, key, index)
498
+ node.flags |= FLAG_PLACEMENT
499
+
500
+ if (typeof type === "function" && isMemoFn(type)) {
501
+ node.flags |= FLAG_MEMO
502
+ node.arePropsEqual = type[$MEMO].arePropsEqual
503
+ }
504
+
505
+ if (__DEV__ && "window" in globalThis) {
506
+ window.__kiru.profilingContext?.emit("createNode", appCtx)
507
+ }
508
+ return node
509
+ }
@@ -7,19 +7,21 @@ import {
7
7
  assertValidElementProps,
8
8
  isPrimitiveChild,
9
9
  } from "./utils/index.js"
10
+ import { isStreamDataThrowValue } from "./utils/promise.js"
10
11
  import { Signal } from "./signals/base.js"
11
- import { $ERROR_BOUNDARY, voidElements, $SUSPENSE_THROW } from "./constants.js"
12
+ import { $ERROR_BOUNDARY, voidElements, $STREAM_DATA } from "./constants.js"
12
13
  import { __DEV__ } from "./env.js"
13
- import { isSuspenseThrowValue } from "./components/suspense.js"
14
14
  import type { ErrorBoundaryNode } from "./types.utils"
15
15
 
16
- export interface RecursiveRenderContext {
16
+ export interface HeadlessRenderContext {
17
17
  write(chunk: string): void
18
- onPending?: (data: Kiru.StatefulPromise<unknown>[]) => void
18
+ onStreamData?: (data: Kiru.StatefulPromise<unknown>[]) => void
19
19
  }
20
20
 
21
- export function recursiveRender(
22
- ctx: RecursiveRenderContext,
21
+ export { render as headlessRender }
22
+
23
+ function render(
24
+ ctx: HeadlessRenderContext,
23
25
  el: unknown,
24
26
  parent: Kiru.VNode | null,
25
27
  idx: number
@@ -34,7 +36,7 @@ export function recursiveRender(
34
36
  return ctx.write(el.toString())
35
37
  }
36
38
  if (el instanceof Array) {
37
- return el.forEach((c, i) => recursiveRender(ctx, c, parent, i))
39
+ return el.forEach((c, i) => render(ctx, c, parent, i))
38
40
  }
39
41
  if (Signal.isSignal(el)) {
40
42
  const value = el.peek()
@@ -65,22 +67,22 @@ export function recursiveRender(
65
67
  if (isExoticType(type)) {
66
68
  if (type === $ERROR_BOUNDARY) {
67
69
  let boundaryBuffer = ""
68
- const pending = new Set<Kiru.StatefulPromise<unknown>>()
69
- const boundaryCtx: RecursiveRenderContext = {
70
+ const streamPromises = new Set<Kiru.StatefulPromise<unknown>>()
71
+ const boundaryCtx: HeadlessRenderContext = {
70
72
  write(chunk) {
71
73
  boundaryBuffer += chunk
72
74
  },
73
- onPending(data) {
74
- data.forEach((p) => pending.add(p))
75
+ onStreamData(data) {
76
+ data.forEach((p) => streamPromises.add(p))
75
77
  },
76
78
  }
77
79
  try {
78
- recursiveRender(boundaryCtx, children, el, idx)
80
+ render(boundaryCtx, children, el, idx)
79
81
  // flush successful render
80
82
  ctx.write(boundaryBuffer)
81
- ctx.onPending?.([...pending])
83
+ ctx.onStreamData?.([...streamPromises])
82
84
  } catch (error) {
83
- if (isSuspenseThrowValue(error)) {
85
+ if (isStreamDataThrowValue(error)) {
84
86
  throw error
85
87
  }
86
88
  const e = error instanceof Error ? error : new Error(String(error))
@@ -88,12 +90,12 @@ export function recursiveRender(
88
90
  onError?.(e)
89
91
  const fallbackContent =
90
92
  typeof fallback === "function" ? fallback(e) : fallback
91
- recursiveRender(ctx, fallbackContent, el, 0)
93
+ render(ctx, fallbackContent, el, 0)
92
94
  }
93
95
  return
94
96
  }
95
97
 
96
- recursiveRender(ctx, children, el, idx)
98
+ render(ctx, children, el, idx)
97
99
  return
98
100
  }
99
101
 
@@ -102,13 +104,13 @@ export function recursiveRender(
102
104
  hookIndex.current = 0
103
105
  node.current = el
104
106
  const res = type(props)
105
- recursiveRender(ctx, res, el, idx)
107
+ render(ctx, res, el, idx)
106
108
  return
107
109
  } catch (error) {
108
- if (isSuspenseThrowValue(error)) {
109
- const { fallback, pending } = error[$SUSPENSE_THROW]
110
- ctx.onPending?.(pending)
111
- return recursiveRender(ctx, fallback, el, 0)
110
+ if (isStreamDataThrowValue(error)) {
111
+ const { fallback, data } = error[$STREAM_DATA]
112
+ ctx.onStreamData?.(data)
113
+ return render(ctx, fallback, el, 0)
112
114
  }
113
115
  throw error
114
116
  } finally {
@@ -131,9 +133,9 @@ export function recursiveRender(
131
133
  )
132
134
  )
133
135
  } else if (Array.isArray(children)) {
134
- children.forEach((c, i) => recursiveRender(ctx, c, el, i))
136
+ children.forEach((c, i) => render(ctx, c, el, i))
135
137
  } else {
136
- recursiveRender(ctx, children, el, 0)
138
+ render(ctx, children, el, 0)
137
139
  }
138
140
  ctx.write(`</${type}>`)
139
141
  }
@@ -1,18 +1,18 @@
1
1
  import { renderMode } from "./globals.js"
2
2
  import { Fragment } from "./element.js"
3
3
  import { __DEV__ } from "./env.js"
4
- import { recursiveRender, RecursiveRenderContext } from "./recursiveRender.js"
4
+ import { headlessRender, HeadlessRenderContext } from "./recursiveRender.js"
5
5
 
6
6
  export function renderToString(element: JSX.Element) {
7
7
  const prev = renderMode.current
8
8
  renderMode.current = "string"
9
9
  let result = ""
10
- const ctx: RecursiveRenderContext = {
10
+ const ctx: HeadlessRenderContext = {
11
11
  write(chunk) {
12
12
  result += chunk
13
13
  },
14
14
  }
15
- recursiveRender(ctx, Fragment({ children: element }), null, 0)
15
+ headlessRender(ctx, Fragment({ children: element }), null, 0)
16
16
  renderMode.current = prev
17
17
  return result
18
18
  }