kiru 0.54.0-preview.0 → 0.54.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 (178) hide show
  1. package/dist/components/memo.d.ts +1 -3
  2. package/dist/components/memo.d.ts.map +1 -1
  3. package/dist/components/memo.js +2 -2
  4. package/dist/components/memo.js.map +1 -1
  5. package/dist/context.d.ts.map +1 -1
  6. package/dist/context.js +1 -23
  7. package/dist/context.js.map +1 -1
  8. package/dist/dom.d.ts.map +1 -1
  9. package/dist/dom.js +109 -72
  10. package/dist/dom.js.map +1 -1
  11. package/dist/error.d.ts.map +1 -1
  12. package/dist/error.js +2 -4
  13. package/dist/error.js.map +1 -1
  14. package/dist/form/index.d.ts.map +1 -1
  15. package/dist/form/index.js +6 -10
  16. package/dist/form/index.js.map +1 -1
  17. package/dist/globals.d.ts +1 -1
  18. package/dist/globals.d.ts.map +1 -1
  19. package/dist/globals.js.map +1 -1
  20. package/dist/hmr.d.ts +1 -0
  21. package/dist/hmr.d.ts.map +1 -1
  22. package/dist/hmr.js +11 -3
  23. package/dist/hmr.js.map +1 -1
  24. package/dist/hooks/useEffectEvent.d.ts.map +1 -1
  25. package/dist/hooks/useEffectEvent.js.map +1 -1
  26. package/dist/hooks/usePromise.d.ts.map +1 -1
  27. package/dist/hooks/usePromise.js.map +1 -1
  28. package/dist/hooks/utils.d.ts.map +1 -1
  29. package/dist/hooks/utils.js +10 -10
  30. package/dist/hooks/utils.js.map +1 -1
  31. package/dist/hydration.d.ts +6 -13
  32. package/dist/hydration.d.ts.map +1 -1
  33. package/dist/hydration.js +20 -50
  34. package/dist/hydration.js.map +1 -1
  35. package/dist/reconciler.d.ts.map +1 -1
  36. package/dist/reconciler.js +3 -6
  37. package/dist/reconciler.js.map +1 -1
  38. package/dist/recursiveRender.d.ts.map +1 -1
  39. package/dist/recursiveRender.js +9 -8
  40. package/dist/recursiveRender.js.map +1 -1
  41. package/dist/renderToString.d.ts.map +1 -1
  42. package/dist/renderToString.js.map +1 -1
  43. package/dist/router/client/index.d.ts +2 -4
  44. package/dist/router/client/index.d.ts.map +1 -1
  45. package/dist/router/client/index.js +11 -49
  46. package/dist/router/client/index.js.map +1 -1
  47. package/dist/router/context.d.ts +5 -2
  48. package/dist/router/context.d.ts.map +1 -1
  49. package/dist/router/context.js +1 -5
  50. package/dist/router/context.js.map +1 -1
  51. package/dist/router/fileRouter.d.ts.map +1 -1
  52. package/dist/router/fileRouter.js +2 -4
  53. package/dist/router/fileRouter.js.map +1 -1
  54. package/dist/router/fileRouterController.d.ts +2 -2
  55. package/dist/router/fileRouterController.d.ts.map +1 -1
  56. package/dist/router/fileRouterController.js +39 -101
  57. package/dist/router/fileRouterController.js.map +1 -1
  58. package/dist/router/globals.d.ts +0 -3
  59. package/dist/router/globals.d.ts.map +1 -1
  60. package/dist/router/globals.js +0 -3
  61. package/dist/router/globals.js.map +1 -1
  62. package/dist/router/head.d.ts.map +1 -1
  63. package/dist/router/head.js +7 -5
  64. package/dist/router/head.js.map +1 -1
  65. package/dist/router/index.d.ts +1 -2
  66. package/dist/router/index.d.ts.map +1 -1
  67. package/dist/router/index.js +1 -2
  68. package/dist/router/index.js.map +1 -1
  69. package/dist/router/link.js +3 -3
  70. package/dist/router/link.js.map +1 -1
  71. package/dist/router/{ssg → server}/index.d.ts +3 -4
  72. package/dist/router/server/index.d.ts.map +1 -0
  73. package/dist/router/{ssg → server}/index.js +5 -8
  74. package/dist/router/server/index.js.map +1 -0
  75. package/dist/router/types.d.ts +4 -37
  76. package/dist/router/types.d.ts.map +1 -1
  77. package/dist/router/types.internal.d.ts +0 -4
  78. package/dist/router/types.internal.d.ts.map +1 -1
  79. package/dist/router/utils/index.d.ts +3 -8
  80. package/dist/router/utils/index.d.ts.map +1 -1
  81. package/dist/router/utils/index.js +8 -40
  82. package/dist/router/utils/index.js.map +1 -1
  83. package/dist/scheduler.d.ts.map +1 -1
  84. package/dist/scheduler.js +60 -53
  85. package/dist/scheduler.js.map +1 -1
  86. package/dist/signals/base.d.ts +0 -2
  87. package/dist/signals/base.d.ts.map +1 -1
  88. package/dist/signals/base.js +0 -6
  89. package/dist/signals/base.js.map +1 -1
  90. package/dist/signals/computed.d.ts +3 -0
  91. package/dist/signals/computed.d.ts.map +1 -1
  92. package/dist/signals/computed.js +29 -20
  93. package/dist/signals/computed.js.map +1 -1
  94. package/dist/signals/for.d.ts +3 -3
  95. package/dist/signals/for.d.ts.map +1 -1
  96. package/dist/signals/for.js +2 -1
  97. package/dist/signals/for.js.map +1 -1
  98. package/dist/signals/utils.d.ts.map +1 -1
  99. package/dist/signals/utils.js +2 -1
  100. package/dist/signals/utils.js.map +1 -1
  101. package/dist/signals/watch.d.ts.map +1 -1
  102. package/dist/signals/watch.js +18 -22
  103. package/dist/signals/watch.js.map +1 -1
  104. package/dist/ssr/client.d.ts +1 -1
  105. package/dist/ssr/client.d.ts.map +1 -1
  106. package/dist/ssr/client.js +0 -2
  107. package/dist/ssr/client.js.map +1 -1
  108. package/dist/ssr/server.d.ts +2 -1
  109. package/dist/ssr/server.d.ts.map +1 -1
  110. package/dist/ssr/server.js +19 -16
  111. package/dist/ssr/server.js.map +1 -1
  112. package/dist/types.d.ts +0 -7
  113. package/dist/types.d.ts.map +1 -1
  114. package/dist/types.dom.d.ts +3 -3
  115. package/dist/types.dom.d.ts.map +1 -1
  116. package/dist/utils/format.d.ts +1 -2
  117. package/dist/utils/format.d.ts.map +1 -1
  118. package/dist/utils/format.js +1 -4
  119. package/dist/utils/format.js.map +1 -1
  120. package/dist/utils/runtime.d.ts +3 -2
  121. package/dist/utils/runtime.d.ts.map +1 -1
  122. package/dist/utils/runtime.js +5 -2
  123. package/dist/utils/runtime.js.map +1 -1
  124. package/dist/utils/vdom.d.ts.map +1 -1
  125. package/dist/utils/vdom.js +2 -2
  126. package/dist/utils/vdom.js.map +1 -1
  127. package/package.json +4 -8
  128. package/src/components/memo.ts +3 -11
  129. package/src/context.ts +1 -24
  130. package/src/dom.ts +145 -96
  131. package/src/error.ts +2 -4
  132. package/src/form/index.ts +6 -9
  133. package/src/globals.ts +1 -1
  134. package/src/hmr.ts +14 -5
  135. package/src/hooks/useEffectEvent.ts +0 -1
  136. package/src/hooks/usePromise.ts +0 -1
  137. package/src/hooks/utils.ts +12 -12
  138. package/src/hydration.ts +21 -57
  139. package/src/reconciler.ts +2 -6
  140. package/src/recursiveRender.ts +10 -9
  141. package/src/renderToString.ts +0 -1
  142. package/src/router/client/index.ts +14 -100
  143. package/src/router/context.ts +6 -7
  144. package/src/router/fileRouter.ts +2 -6
  145. package/src/router/fileRouterController.ts +39 -159
  146. package/src/router/globals.ts +0 -4
  147. package/src/router/head.ts +7 -5
  148. package/src/router/index.ts +1 -12
  149. package/src/router/link.ts +3 -3
  150. package/src/router/{ssg → server}/index.ts +10 -17
  151. package/src/router/types.internal.ts +0 -5
  152. package/src/router/types.ts +4 -48
  153. package/src/router/utils/index.ts +16 -79
  154. package/src/scheduler.ts +83 -70
  155. package/src/signals/base.ts +0 -8
  156. package/src/signals/computed.ts +30 -18
  157. package/src/signals/for.ts +15 -10
  158. package/src/signals/utils.ts +2 -1
  159. package/src/signals/watch.ts +27 -22
  160. package/src/ssr/client.ts +1 -4
  161. package/src/ssr/server.ts +21 -20
  162. package/src/types.dom.ts +4 -5
  163. package/src/types.ts +0 -10
  164. package/src/utils/format.ts +0 -5
  165. package/src/utils/runtime.ts +6 -2
  166. package/src/utils/vdom.ts +2 -7
  167. package/dist/router/guard.d.ts +0 -17
  168. package/dist/router/guard.d.ts.map +0 -1
  169. package/dist/router/guard.js +0 -45
  170. package/dist/router/guard.js.map +0 -1
  171. package/dist/router/ssg/index.d.ts.map +0 -1
  172. package/dist/router/ssg/index.js.map +0 -1
  173. package/dist/router/ssr/index.d.ts +0 -20
  174. package/dist/router/ssr/index.d.ts.map +0 -1
  175. package/dist/router/ssr/index.js +0 -160
  176. package/dist/router/ssr/index.js.map +0 -1
  177. package/src/router/guard.ts +0 -72
  178. package/src/router/ssr/index.ts +0 -247
@@ -1,27 +1,19 @@
1
1
  import { createElement } from "../../element.js"
2
2
  import { __DEV__ } from "../../env.js"
3
- import { resolveNavguard } from "../guard.js"
4
3
  import type {
5
- DefaultComponentModule,
6
4
  FormattedViteImportMap,
7
- GuardModule,
8
5
  RouteMatch,
9
6
  ViteImportMap,
10
7
  } from "../types.internal"
11
- import { OnBeforeEnterHook, OnBeforeLeaveHook } from "../types.js"
12
8
 
13
9
  export {
14
10
  formatViteImportMap,
15
11
  matchRoute,
16
12
  match404Route,
17
- matchModules,
13
+ matchLayouts,
18
14
  normalizePrefixPath,
19
15
  parseQuery,
20
16
  wrapWithLayouts,
21
- runBeforeLeaveHooks,
22
- runBeforeEnterHooks,
23
- runBeforeEachGuards,
24
- runAfterEachGuards,
25
17
  }
26
18
 
27
19
  function formatViteImportMap(
@@ -41,7 +33,7 @@ function formatViteImportMap(
41
33
  }
42
34
 
43
35
  let specificity = 0
44
- let k = key.slice(dirIndex + dir.length)
36
+ let k = baseUrl + key.slice(dirIndex + dir.length)
45
37
  while (k.startsWith("/")) {
46
38
  k = k.slice(1)
47
39
  }
@@ -95,7 +87,7 @@ function formatViteImportMap(
95
87
 
96
88
  return {
97
89
  ...acc,
98
- [baseUrl + segments.join("/")]: value,
90
+ [segments.join("/")]: value,
99
91
  }
100
92
  }, {})
101
93
  }
@@ -185,20 +177,23 @@ function match404Route(
185
177
  return null
186
178
  }
187
179
 
188
- function matchModules<T = DefaultComponentModule>(
189
- modules: FormattedViteImportMap<T>,
180
+ function matchLayouts(
181
+ layouts: FormattedViteImportMap,
190
182
  routeSegments: string[]
191
183
  ) {
192
- return ["/", ...routeSegments].reduce((acc, _, i) => {
193
- const modulePath = "/" + routeSegments.slice(0, i).join("/")
194
- const module = modules[modulePath]
184
+ return ["/", ...routeSegments].reduce(
185
+ (acc, _, i) => {
186
+ const layoutPath = routeSegments.slice(0, i).join("/")
187
+ const layout = layouts[layoutPath]
195
188
 
196
- if (!module) {
197
- return acc
198
- }
189
+ if (!layout) {
190
+ return acc
191
+ }
199
192
 
200
- return [...acc, module]
201
- }, [] as FormattedViteImportMap<T>[string][])
193
+ return [...acc, layout]
194
+ },
195
+ [] as FormattedViteImportMap[string][]
196
+ )
202
197
  }
203
198
 
204
199
  function normalizePrefixPath(path: string) {
@@ -247,61 +242,3 @@ function wrapWithLayouts(
247
242
  createElement(page, props)
248
243
  )
249
244
  }
250
-
251
- function runBeforeLeaveHooks(
252
- hooks: OnBeforeLeaveHook[],
253
- context: Kiru.RequestContext,
254
- to: string,
255
- from: string = to
256
- ): false | void {
257
- for (const hook of hooks) {
258
- const res = hook(context, to, from)
259
- if (res === false) {
260
- return false
261
- }
262
- }
263
- }
264
-
265
- async function runBeforeEnterHooks(
266
- hooks: OnBeforeEnterHook[],
267
- context: Kiru.RequestContext,
268
- to: string,
269
- from: string = to
270
- ) {
271
- for (const hook of hooks) {
272
- const result = await hook(context, to, from)
273
- if (typeof result === "string") {
274
- return result
275
- }
276
- }
277
-
278
- return null
279
- }
280
-
281
- async function runBeforeEachGuards(
282
- guardModules: GuardModule[],
283
- context: Kiru.RequestContext,
284
- to: string,
285
- from: string = to
286
- ): Promise<string | null> {
287
- const beforeHooks = guardModules
288
- .map((guardModule) => resolveNavguard(guardModule)?.beforeEach)
289
- .filter((x) => typeof x === "function")
290
-
291
- return runBeforeEnterHooks(beforeHooks, context, to, from)
292
- }
293
-
294
- async function runAfterEachGuards(
295
- guardModules: GuardModule[],
296
- context: Kiru.RequestContext,
297
- to: string,
298
- from: string = to
299
- ): Promise<void> {
300
- const afterHooks = guardModules
301
- .map((guardModule) => resolveNavguard(guardModule)?.afterEach)
302
- .filter((x) => typeof x === "function")
303
-
304
- for (const hook of afterHooks) {
305
- await hook(context, to, from)
306
- }
307
- }
package/src/scheduler.ts CHANGED
@@ -7,6 +7,7 @@ import type {
7
7
  import {
8
8
  $CONTEXT_PROVIDER,
9
9
  $ERROR_BOUNDARY,
10
+ $MEMO,
10
11
  CONSECUTIVE_DIRTY_LIMIT,
11
12
  FLAG_DELETION,
12
13
  FLAG_DIRTY,
@@ -33,8 +34,11 @@ import {
33
34
  isExoticType,
34
35
  getVNodeAppContext,
35
36
  findParentErrorBoundary,
37
+ call,
36
38
  } from "./utils/index.js"
37
39
  import type { AppContext } from "./appContext"
40
+ import type { MemoFn } from "./components/memo"
41
+ import { isHmrUpdate } from "./hmr.js"
38
42
 
39
43
  type VNode = Kiru.VNode
40
44
 
@@ -55,7 +59,7 @@ let animationFrameHandle = -1
55
59
  * Runs a function after any existing work has been completed,
56
60
  * or immediately if the scheduler is already idle.
57
61
  */
58
- export function nextIdle(fn: () => void) {
62
+ export function nextIdle(fn: () => void): void {
59
63
  if (isRunningOrQueued) {
60
64
  nextIdleEffects.push(fn)
61
65
  return
@@ -66,13 +70,13 @@ export function nextIdle(fn: () => void) {
66
70
  /**
67
71
  * Syncronously flushes any pending work.
68
72
  */
69
- export function flushSync() {
73
+ export function flushSync(): void {
70
74
  if (!isRunningOrQueued) return
71
75
  window.cancelAnimationFrame(animationFrameHandle)
72
76
  doWork()
73
77
  }
74
78
 
75
- export function renderRootSync(rootNode: VNode) {
79
+ export function renderRootSync(rootNode: VNode): void {
76
80
  rootNode.flags |= FLAG_DIRTY
77
81
  treesInProgress.push(rootNode)
78
82
 
@@ -90,20 +94,20 @@ export function requestUpdate(vNode: VNode): void {
90
94
  queueUpdate(vNode)
91
95
  }
92
96
 
93
- function queueBeginWork() {
97
+ function queueBeginWork(): void {
94
98
  if (isRunningOrQueued) return
95
99
  isRunningOrQueued = true
96
100
  animationFrameHandle = window.requestAnimationFrame(doWork)
97
101
  }
98
102
 
99
- function onWorkFinished() {
103
+ function onWorkFinished(): void {
100
104
  isRunningOrQueued = false
101
105
  while (nextIdleEffects.length) {
102
106
  nextIdleEffects.shift()!()
103
107
  }
104
108
  }
105
109
 
106
- function queueUpdate(vNode: VNode) {
110
+ function queueUpdate(vNode: VNode): void {
107
111
  // In immediate effect mode (useLayoutEffect), immediately mark the render as dirty
108
112
  if (isImmediateEffectsMode) {
109
113
  immediateEffectDirtiedRender = true
@@ -129,12 +133,12 @@ function queueUpdate(vNode: VNode) {
129
133
  treesInProgress.push(vNode)
130
134
  }
131
135
 
132
- function queueDelete(vNode: VNode) {
136
+ function queueDelete(vNode: VNode): void {
133
137
  traverseApply(vNode, (n) => (n.flags |= FLAG_DELETION))
134
138
  deletions.push(vNode)
135
139
  }
136
140
 
137
- const depthSort = (a: VNode, b: VNode) => b.depth - a.depth
141
+ const depthSort = (a: VNode, b: VNode): number => b.depth - a.depth
138
142
 
139
143
  let currentWorkRoot: VNode | null = null
140
144
 
@@ -163,7 +167,7 @@ function doWork(): void {
163
167
  const flags = currentWorkRoot.flags
164
168
  if (flags & FLAG_DELETION) continue
165
169
  if (flags & FLAG_DIRTY) {
166
- let n: VNode | void = currentWorkRoot
170
+ let n: VNode | null = currentWorkRoot
167
171
  while ((n = performUnitOfWork(n))) {}
168
172
 
169
173
  while (deletions.length) {
@@ -194,7 +198,7 @@ function doWork(): void {
194
198
  consecutiveDirtyCount = 0
195
199
 
196
200
  onWorkFinished()
197
- flushEffects(postEffects)
201
+ queueMicrotask(() => flushEffects(postEffects))
198
202
  if (__DEV__) {
199
203
  window.__kiru.emit("update", appCtx!)
200
204
  window.__kiru.profilingContext?.emit("update", appCtx!)
@@ -202,15 +206,57 @@ function doWork(): void {
202
206
  }
203
207
  }
204
208
 
205
- function performUnitOfWork(vNode: VNode): VNode | void {
206
- let renderChild = true
209
+ function performUnitOfWork(vNode: VNode): VNode | null {
210
+ const next = updateVNode(vNode)
211
+
212
+ if (vNode.deletions !== null) {
213
+ vNode.deletions.forEach(queueDelete)
214
+ vNode.deletions = null
215
+ }
216
+
217
+ if (next) {
218
+ return next
219
+ }
220
+
221
+ let nextNode: VNode | null = vNode
222
+ while (nextNode) {
223
+ // queue effects upon ascent
224
+ if (nextNode.immediateEffects) {
225
+ preEffects.push(...nextNode.immediateEffects)
226
+ nextNode.immediateEffects = undefined
227
+ }
228
+ if (nextNode.effects) {
229
+ postEffects.push(...nextNode.effects)
230
+ nextNode.effects = undefined
231
+ }
232
+
233
+ if (nextNode === currentWorkRoot) return null
234
+ if (nextNode.sibling) {
235
+ return nextNode.sibling
236
+ }
237
+
238
+ nextNode = nextNode.parent
239
+ if (renderMode.current === "hydrate" && nextNode?.dom) {
240
+ hydrationStack.pop()
241
+ }
242
+ }
243
+
244
+ return null
245
+ }
246
+
247
+ function updateVNode(vNode: VNode): VNode | null {
248
+ const { type, props, prev, flags } = vNode
249
+ if (__DEV__ && isHmrUpdate()) {
250
+ } else if ((flags & FLAG_DIRTY) === 0 && props === prev?.props) {
251
+ return null
252
+ }
207
253
  try {
208
- if (typeof vNode.type === "string") {
209
- updateHostComponent(vNode as DomVNode)
210
- } else if (isExoticType(vNode.type)) {
211
- updateExoticComponent(vNode)
254
+ if (typeof type === "string") {
255
+ return updateHostComponent(vNode as DomVNode)
256
+ } else if (isExoticType(type)) {
257
+ return updateExoticComponent(vNode)
212
258
  } else {
213
- renderChild = updateFunctionComponent(vNode as FunctionVNode)
259
+ return updateFunctionComponent(vNode as FunctionVNode)
214
260
  }
215
261
  } catch (error) {
216
262
  if (__DEV__) {
@@ -243,47 +289,16 @@ function performUnitOfWork(vNode: VNode): VNode | void {
243
289
  throw error
244
290
  }
245
291
  console.error(error)
246
- return
292
+ return vNode.child
247
293
  }
248
294
  setTimeout(() => {
249
295
  throw error
250
296
  })
251
297
  }
252
-
253
- if (vNode.deletions !== null) {
254
- vNode.deletions.forEach(queueDelete)
255
- vNode.deletions = null
256
- }
257
-
258
- if (renderChild && vNode.child) {
259
- return vNode.child
260
- }
261
-
262
- let nextNode: VNode | null = vNode
263
- while (nextNode) {
264
- // queue effects upon ascent
265
- if (nextNode.immediateEffects) {
266
- preEffects.push(...nextNode.immediateEffects)
267
- nextNode.immediateEffects = undefined
268
- }
269
- if (nextNode.effects) {
270
- postEffects.push(...nextNode.effects)
271
- nextNode.effects = undefined
272
- }
273
-
274
- if (nextNode === currentWorkRoot) return
275
- if (nextNode.sibling) {
276
- return nextNode.sibling
277
- }
278
-
279
- nextNode = nextNode.parent
280
- if (renderMode.current === "hydrate" && nextNode?.dom) {
281
- hydrationStack.pop()
282
- }
283
- }
298
+ return null
284
299
  }
285
300
 
286
- function updateExoticComponent(vNode: VNode) {
301
+ function updateExoticComponent(vNode: VNode): VNode | null {
287
302
  const { props, type } = vNode
288
303
  let children = props.children
289
304
 
@@ -309,20 +324,19 @@ function updateExoticComponent(vNode: VNode) {
309
324
  }
310
325
  }
311
326
 
312
- vNode.child = reconcileChildren(vNode, children)
327
+ return (vNode.child = reconcileChildren(vNode, children))
313
328
  }
314
329
 
315
- function updateFunctionComponent(vNode: FunctionVNode) {
330
+ function updateFunctionComponent(vNode: FunctionVNode): VNode | null {
316
331
  const { type, props, subs, prev, flags } = vNode
317
332
  if (flags & FLAG_MEMO) {
318
- vNode.memoizedProps = props
319
333
  if (
320
- prev?.memoizedProps &&
321
- vNode.arePropsEqual!(prev.memoizedProps, props) &&
322
- !vNode.hmrUpdated
334
+ prev &&
335
+ (type as MemoFn)[$MEMO](prev.props, props) &&
336
+ !(__DEV__ && isHmrUpdate())
323
337
  ) {
324
338
  vNode.flags |= FLAG_NOOP
325
- return false
339
+ return null
326
340
  }
327
341
  vNode.flags &= ~FLAG_NOOP
328
342
  }
@@ -345,14 +359,14 @@ function updateFunctionComponent(vNode: FunctionVNode) {
345
359
  * and not clearing the entire set.
346
360
  */
347
361
  if (subs) {
348
- subs.forEach((unsub) => unsub())
362
+ subs.forEach(call)
349
363
  subs.clear()
350
364
  }
351
365
 
352
366
  if (__DEV__) {
353
367
  newChild = latest(type)(props)
354
368
 
355
- if (vNode.hmrUpdated && vNode.hooks && vNode.hookSig) {
369
+ if (isHmrUpdate() && vNode.hooks && vNode.hookSig) {
356
370
  const len = vNode.hooks.length
357
371
  if (hookIndex.current < len) {
358
372
  // clean up any hooks that were removed
@@ -365,7 +379,6 @@ function updateFunctionComponent(vNode: FunctionVNode) {
365
379
  }
366
380
  }
367
381
 
368
- delete vNode.hmrUpdated
369
382
  if (++renderTryCount > CONSECUTIVE_DIRTY_LIMIT) {
370
383
  throw new KiruError({
371
384
  message:
@@ -378,14 +391,14 @@ function updateFunctionComponent(vNode: FunctionVNode) {
378
391
  }
379
392
  newChild = type(props)
380
393
  } while (isRenderDirtied)
381
- vNode.child = reconcileChildren(vNode, newChild)
382
- return true
394
+
395
+ return (vNode.child = reconcileChildren(vNode, newChild))
383
396
  } finally {
384
397
  node.current = null
385
398
  }
386
399
  }
387
400
 
388
- function updateHostComponent(vNode: DomVNode) {
401
+ function updateHostComponent(vNode: DomVNode): VNode | null {
389
402
  const { props, type } = vNode
390
403
  if (__DEV__) {
391
404
  assertValidElementProps(vNode)
@@ -396,10 +409,8 @@ function updateHostComponent(vNode: DomVNode) {
396
409
  } else {
397
410
  vNode.dom = createDom(vNode)
398
411
  }
399
- if (__DEV__) {
400
- if (vNode.dom instanceof Element) {
401
- vNode.dom.__kiruNode = vNode
402
- }
412
+ if (__DEV__ && vNode.dom instanceof Element) {
413
+ vNode.dom.__kiruNode = vNode
403
414
  }
404
415
  }
405
416
  // text should _never_ have children
@@ -409,9 +420,11 @@ function updateHostComponent(vNode: DomVNode) {
409
420
  hydrationStack.push(vNode.dom!)
410
421
  }
411
422
  }
423
+
424
+ return vNode.child
412
425
  }
413
426
 
414
- function checkForTooManyConsecutiveDirtyRenders() {
427
+ function checkForTooManyConsecutiveDirtyRenders(): void {
415
428
  if (consecutiveDirtyCount > CONSECUTIVE_DIRTY_LIMIT) {
416
429
  throw new KiruError(
417
430
  "Maximum update depth exceeded. This can happen when a component repeatedly calls setState during render or in useLayoutEffect. Kiru limits the number of nested updates to prevent infinite loops."
@@ -419,7 +432,7 @@ function checkForTooManyConsecutiveDirtyRenders() {
419
432
  }
420
433
  }
421
434
 
422
- function flushEffects(effectArr: Function[]) {
435
+ function flushEffects(effectArr: Function[]): void {
423
436
  for (let i = 0; i < effectArr.length; i++) {
424
437
  effectArr[i]()
425
438
  }
@@ -17,7 +17,6 @@ export class Signal<T> {
17
17
  [$SIGNAL] = true;
18
18
  [$HMR_ACCEPT]?: HMRAccept<Signal<any>>
19
19
  displayName?: string
20
- private onBeforeRead?: () => void
21
20
  protected $subs?: Set<SignalSubscriber<any>>
22
21
  protected $id: string
23
22
  protected $value: T
@@ -60,7 +59,6 @@ export class Signal<T> {
60
59
  }
61
60
 
62
61
  get value() {
63
- this.onBeforeRead?.()
64
62
  if (__DEV__) {
65
63
  const tgt = latest(this)
66
64
  Signal.entangle(tgt)
@@ -86,7 +84,6 @@ export class Signal<T> {
86
84
  }
87
85
 
88
86
  peek() {
89
- this.onBeforeRead?.()
90
87
  if (__DEV__) {
91
88
  return latest(this).$value
92
89
  }
@@ -105,7 +102,6 @@ export class Signal<T> {
105
102
  }
106
103
 
107
104
  toString() {
108
- this.onBeforeRead?.()
109
105
  if (__DEV__) {
110
106
  const tgt = latest(this)
111
107
  Signal.entangle(tgt)
@@ -192,10 +188,6 @@ export class Signal<T> {
192
188
  ;(vNode.subs ??= new Set()).add(unsub)
193
189
  }
194
190
 
195
- static configure(signal: Signal<any>, onBeforeRead?: () => void) {
196
- signal.onBeforeRead = onBeforeRead
197
- }
198
-
199
191
  static dispose(signal: Signal<any>) {
200
192
  signal.$isDisposed = true
201
193
  if (__DEV__) {
@@ -1,7 +1,7 @@
1
1
  import { __DEV__ } from "../env.js"
2
2
  import { $HMR_ACCEPT } from "../constants.js"
3
3
  import { depsRequireChange, useHook } from "../hooks/utils.js"
4
- import { latest } from "../utils/index.js"
4
+ import { call, latest } from "../utils/index.js"
5
5
  import { effectQueue, signalSubsMap } from "./globals.js"
6
6
  import { executeWithTracking } from "./effect.js"
7
7
  import { Signal } from "./base.js"
@@ -32,28 +32,23 @@ export class ComputedSignal<T> extends Signal<T> {
32
32
  destroy: () => {},
33
33
  } satisfies HMRAccept<ComputedSignal<T>>
34
34
  }
35
- Signal.configure(this, () => {
36
- if (!this.$isDirty) return
37
- if (__DEV__) {
38
- /**
39
- * This is a safeguard for dev-mode only, where a 'read' on an
40
- * already-disposed signal during HMR update => `dom.setSignalProp`
41
- * would throw due to invalid subs-map access.
42
- *
43
- * Perhaps in future we could handle this better by carrying over
44
- * the previous signal's ID and not disposing it / deleting the
45
- * map entry.
46
- */
47
- if (this.$isDisposed) return
48
- }
49
- ComputedSignal.run(this)
50
- })
51
35
  }
52
36
 
53
37
  get value() {
38
+ this.ensureNotDirty()
54
39
  return super.value
55
40
  }
56
41
 
42
+ toString() {
43
+ this.ensureNotDirty()
44
+ return super.toString()
45
+ }
46
+
47
+ peek() {
48
+ this.ensureNotDirty()
49
+ return super.peek()
50
+ }
51
+
57
52
  // @ts-expect-error
58
53
  set value(next: T) {}
59
54
 
@@ -83,7 +78,7 @@ export class ComputedSignal<T> extends Signal<T> {
83
78
  const { $id, $unsubs } = latest(computed)
84
79
 
85
80
  effectQueue.delete($id)
86
- $unsubs.forEach((unsub) => unsub())
81
+ $unsubs.forEach(call)
87
82
  $unsubs.clear()
88
83
  computed.$isDirty = true
89
84
  }
@@ -111,6 +106,23 @@ export class ComputedSignal<T> extends Signal<T> {
111
106
  $computed.sneak(value)
112
107
  $computed.$isDirty = false
113
108
  }
109
+
110
+ private ensureNotDirty() {
111
+ if (!this.$isDirty) return
112
+ if (__DEV__) {
113
+ /**
114
+ * This is a safeguard for dev-mode only, where a 'read' on an
115
+ * already-disposed signal during HMR update => `dom.setSignalProp`
116
+ * would throw due to invalid subs-map access.
117
+ *
118
+ * Perhaps in future we could handle this better by carrying over
119
+ * the previous signal's ID and not disposing it / deleting the
120
+ * map entry.
121
+ */
122
+ if (this.$isDisposed) return
123
+ }
124
+ ComputedSignal.run(this)
125
+ }
114
126
  }
115
127
 
116
128
  export function computed<T>(
@@ -1,25 +1,30 @@
1
1
  import type { Signal } from "./base.js"
2
+ import { unwrap } from "./utils.js"
2
3
 
3
- type InferArraySignalItemType<T extends Signal<any[]>> = T extends Signal<
4
- infer V
5
- >
6
- ? V extends Array<infer W>
7
- ? W
8
- : never
9
- : never
4
+ type InferArraySignalItemType<T extends Signal<any[]> | readonly unknown[]> =
5
+ T extends Signal<infer V>
6
+ ? V extends Array<infer W>
7
+ ? W
8
+ : never
9
+ : T extends unknown[]
10
+ ? T[number]
11
+ : never
10
12
 
11
- type ForProps<T extends Signal<any[]>, U = InferArraySignalItemType<T>> = {
13
+ type ForProps<
14
+ T extends Signal<any[]> | readonly unknown[],
15
+ U = InferArraySignalItemType<T>,
16
+ > = {
12
17
  each: T
13
18
  fallback?: JSX.Element
14
19
  children: (value: U, index: number, array: U[]) => JSX.Element
15
20
  }
16
21
 
17
- export function For<T extends Signal<any[]>>({
22
+ export function For<T extends Signal<any[]> | unknown[]>({
18
23
  each,
19
24
  fallback,
20
25
  children,
21
26
  }: ForProps<T>) {
22
- const items = each.value
27
+ const items = unwrap(each, true)
23
28
  if (items.length === 0) return fallback
24
29
  return items.map(children)
25
30
  }
@@ -1,3 +1,4 @@
1
+ import { call } from "../utils/index.js"
1
2
  import { Signal } from "./base.js"
2
3
  import { effectQueue } from "./globals.js"
3
4
 
@@ -7,6 +8,6 @@ export function unwrap<T>(value: T | Signal<T>, reactive = false): T {
7
8
  }
8
9
 
9
10
  export const tick = () => {
10
- effectQueue.forEach((fn) => fn())
11
+ effectQueue.forEach(call)
11
12
  effectQueue.clear()
12
13
  }