kiru 0.54.0-preview.1 → 0.54.1

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 (204) hide show
  1. package/dist/components/derive.d.ts +1 -1
  2. package/dist/components/derive.d.ts.map +1 -1
  3. package/dist/components/derive.js +2 -3
  4. package/dist/components/derive.js.map +1 -1
  5. package/dist/components/memo.d.ts +1 -3
  6. package/dist/components/memo.d.ts.map +1 -1
  7. package/dist/components/memo.js +2 -2
  8. package/dist/components/memo.js.map +1 -1
  9. package/dist/context.d.ts.map +1 -1
  10. package/dist/context.js +1 -23
  11. package/dist/context.js.map +1 -1
  12. package/dist/dom.d.ts.map +1 -1
  13. package/dist/dom.js +111 -78
  14. package/dist/dom.js.map +1 -1
  15. package/dist/error.d.ts.map +1 -1
  16. package/dist/error.js +2 -4
  17. package/dist/error.js.map +1 -1
  18. package/dist/form/index.d.ts.map +1 -1
  19. package/dist/form/index.js +6 -10
  20. package/dist/form/index.js.map +1 -1
  21. package/dist/globals.d.ts +1 -1
  22. package/dist/globals.d.ts.map +1 -1
  23. package/dist/globals.js.map +1 -1
  24. package/dist/hmr.d.ts +1 -0
  25. package/dist/hmr.d.ts.map +1 -1
  26. package/dist/hmr.js +11 -3
  27. package/dist/hmr.js.map +1 -1
  28. package/dist/hooks/useEffectEvent.d.ts.map +1 -1
  29. package/dist/hooks/useEffectEvent.js.map +1 -1
  30. package/dist/hooks/usePromise.d.ts +1 -2
  31. package/dist/hooks/usePromise.d.ts.map +1 -1
  32. package/dist/hooks/usePromise.js +62 -31
  33. package/dist/hooks/usePromise.js.map +1 -1
  34. package/dist/hooks/utils.d.ts.map +1 -1
  35. package/dist/hooks/utils.js +10 -10
  36. package/dist/hooks/utils.js.map +1 -1
  37. package/dist/hydration.d.ts +6 -13
  38. package/dist/hydration.d.ts.map +1 -1
  39. package/dist/hydration.js +20 -50
  40. package/dist/hydration.js.map +1 -1
  41. package/dist/index.js +2 -1
  42. package/dist/index.js.map +1 -1
  43. package/dist/reconciler.d.ts.map +1 -1
  44. package/dist/reconciler.js +3 -6
  45. package/dist/reconciler.js.map +1 -1
  46. package/dist/recursiveRender.d.ts.map +1 -1
  47. package/dist/recursiveRender.js +9 -8
  48. package/dist/recursiveRender.js.map +1 -1
  49. package/dist/renderToString.d.ts.map +1 -1
  50. package/dist/renderToString.js.map +1 -1
  51. package/dist/router/client/index.d.ts +2 -4
  52. package/dist/router/client/index.d.ts.map +1 -1
  53. package/dist/router/client/index.js +13 -59
  54. package/dist/router/client/index.js.map +1 -1
  55. package/dist/router/context.d.ts +5 -2
  56. package/dist/router/context.d.ts.map +1 -1
  57. package/dist/router/context.js +1 -5
  58. package/dist/router/context.js.map +1 -1
  59. package/dist/router/fileRouter.d.ts.map +1 -1
  60. package/dist/router/fileRouter.js +2 -4
  61. package/dist/router/fileRouter.js.map +1 -1
  62. package/dist/router/fileRouterController.d.ts +2 -2
  63. package/dist/router/fileRouterController.d.ts.map +1 -1
  64. package/dist/router/fileRouterController.js +135 -214
  65. package/dist/router/fileRouterController.js.map +1 -1
  66. package/dist/router/globals.d.ts +0 -3
  67. package/dist/router/globals.d.ts.map +1 -1
  68. package/dist/router/globals.js +0 -3
  69. package/dist/router/globals.js.map +1 -1
  70. package/dist/router/head.d.ts.map +1 -1
  71. package/dist/router/head.js +7 -5
  72. package/dist/router/head.js.map +1 -1
  73. package/dist/router/index.d.ts +1 -2
  74. package/dist/router/index.d.ts.map +1 -1
  75. package/dist/router/index.js +1 -2
  76. package/dist/router/index.js.map +1 -1
  77. package/dist/router/link.js +3 -3
  78. package/dist/router/link.js.map +1 -1
  79. package/dist/router/{ssg → server}/index.d.ts +4 -4
  80. package/dist/router/server/index.d.ts.map +1 -0
  81. package/dist/router/{ssg → server}/index.js +7 -9
  82. package/dist/router/server/index.js.map +1 -0
  83. package/dist/router/types.d.ts +16 -42
  84. package/dist/router/types.d.ts.map +1 -1
  85. package/dist/router/types.internal.d.ts +0 -4
  86. package/dist/router/types.internal.d.ts.map +1 -1
  87. package/dist/router/utils/index.d.ts +3 -8
  88. package/dist/router/utils/index.d.ts.map +1 -1
  89. package/dist/router/utils/index.js +8 -40
  90. package/dist/router/utils/index.js.map +1 -1
  91. package/dist/scheduler.d.ts +3 -14
  92. package/dist/scheduler.d.ts.map +1 -1
  93. package/dist/scheduler.js +64 -56
  94. package/dist/scheduler.js.map +1 -1
  95. package/dist/signals/base.d.ts +0 -2
  96. package/dist/signals/base.d.ts.map +1 -1
  97. package/dist/signals/base.js +0 -6
  98. package/dist/signals/base.js.map +1 -1
  99. package/dist/signals/computed.d.ts +3 -0
  100. package/dist/signals/computed.d.ts.map +1 -1
  101. package/dist/signals/computed.js +29 -20
  102. package/dist/signals/computed.js.map +1 -1
  103. package/dist/signals/for.d.ts +3 -3
  104. package/dist/signals/for.d.ts.map +1 -1
  105. package/dist/signals/for.js +2 -1
  106. package/dist/signals/for.js.map +1 -1
  107. package/dist/signals/utils.d.ts.map +1 -1
  108. package/dist/signals/utils.js +2 -1
  109. package/dist/signals/utils.js.map +1 -1
  110. package/dist/signals/watch.d.ts.map +1 -1
  111. package/dist/signals/watch.js +18 -22
  112. package/dist/signals/watch.js.map +1 -1
  113. package/dist/ssr/client.d.ts +1 -1
  114. package/dist/ssr/client.d.ts.map +1 -1
  115. package/dist/ssr/client.js +0 -2
  116. package/dist/ssr/client.js.map +1 -1
  117. package/dist/ssr/server.d.ts +3 -9
  118. package/dist/ssr/server.d.ts.map +1 -1
  119. package/dist/ssr/server.js +30 -37
  120. package/dist/ssr/server.js.map +1 -1
  121. package/dist/types.d.ts +0 -7
  122. package/dist/types.d.ts.map +1 -1
  123. package/dist/types.dom.d.ts +3 -3
  124. package/dist/types.dom.d.ts.map +1 -1
  125. package/dist/utils/format.d.ts +1 -2
  126. package/dist/utils/format.d.ts.map +1 -1
  127. package/dist/utils/format.js +1 -4
  128. package/dist/utils/format.js.map +1 -1
  129. package/dist/utils/index.d.ts +1 -1
  130. package/dist/utils/index.d.ts.map +1 -1
  131. package/dist/utils/index.js +1 -1
  132. package/dist/utils/index.js.map +1 -1
  133. package/dist/utils/promise.d.ts +0 -2
  134. package/dist/utils/promise.d.ts.map +1 -1
  135. package/dist/utils/promise.js +1 -45
  136. package/dist/utils/promise.js.map +1 -1
  137. package/dist/utils/runtime.d.ts +3 -2
  138. package/dist/utils/runtime.d.ts.map +1 -1
  139. package/dist/utils/runtime.js +5 -2
  140. package/dist/utils/runtime.js.map +1 -1
  141. package/dist/utils/vdom.d.ts.map +1 -1
  142. package/dist/utils/vdom.js +2 -2
  143. package/dist/utils/vdom.js.map +1 -1
  144. package/package.json +4 -8
  145. package/src/components/derive.ts +3 -5
  146. package/src/components/memo.ts +3 -11
  147. package/src/context.ts +1 -24
  148. package/src/dom.ts +146 -101
  149. package/src/error.ts +2 -4
  150. package/src/form/index.ts +6 -9
  151. package/src/globals.ts +1 -1
  152. package/src/hmr.ts +14 -5
  153. package/src/hooks/useEffectEvent.ts +0 -1
  154. package/src/hooks/usePromise.ts +77 -58
  155. package/src/hooks/utils.ts +12 -12
  156. package/src/hydration.ts +21 -57
  157. package/src/index.ts +1 -1
  158. package/src/reconciler.ts +2 -6
  159. package/src/recursiveRender.ts +10 -9
  160. package/src/renderToString.ts +0 -1
  161. package/src/router/client/index.ts +16 -114
  162. package/src/router/context.ts +6 -7
  163. package/src/router/fileRouter.ts +2 -6
  164. package/src/router/fileRouterController.ts +161 -324
  165. package/src/router/globals.ts +0 -4
  166. package/src/router/head.ts +7 -5
  167. package/src/router/index.ts +1 -12
  168. package/src/router/link.ts +3 -3
  169. package/src/router/{ssg → server}/index.ts +13 -18
  170. package/src/router/types.internal.ts +0 -5
  171. package/src/router/types.ts +16 -53
  172. package/src/router/utils/index.ts +16 -79
  173. package/src/scheduler.ts +85 -89
  174. package/src/signals/base.ts +0 -8
  175. package/src/signals/computed.ts +30 -18
  176. package/src/signals/for.ts +15 -10
  177. package/src/signals/utils.ts +2 -1
  178. package/src/signals/watch.ts +27 -22
  179. package/src/ssr/client.ts +1 -4
  180. package/src/ssr/server.ts +34 -59
  181. package/src/types.dom.ts +4 -5
  182. package/src/types.ts +0 -10
  183. package/src/utils/format.ts +0 -5
  184. package/src/utils/index.ts +1 -1
  185. package/src/utils/promise.ts +1 -70
  186. package/src/utils/runtime.ts +6 -2
  187. package/src/utils/vdom.ts +2 -7
  188. package/dist/router/constants.d.ts +0 -2
  189. package/dist/router/constants.d.ts.map +0 -1
  190. package/dist/router/constants.js +0 -2
  191. package/dist/router/constants.js.map +0 -1
  192. package/dist/router/guard.d.ts +0 -17
  193. package/dist/router/guard.d.ts.map +0 -1
  194. package/dist/router/guard.js +0 -45
  195. package/dist/router/guard.js.map +0 -1
  196. package/dist/router/ssg/index.d.ts.map +0 -1
  197. package/dist/router/ssg/index.js.map +0 -1
  198. package/dist/router/ssr/index.d.ts +0 -20
  199. package/dist/router/ssr/index.d.ts.map +0 -1
  200. package/dist/router/ssr/index.js +0 -163
  201. package/dist/router/ssr/index.js.map +0 -1
  202. package/src/router/constants.ts +0 -1
  203. package/src/router/guard.ts +0 -72
  204. package/src/router/ssr/index.ts +0 -252
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
 
@@ -52,27 +56,10 @@ let postEffects: Array<Function> = []
52
56
  let animationFrameHandle = -1
53
57
 
54
58
  /**
55
- * Defers work until the scheduler becomes idle.
56
- *
57
- * This works in two modes:
58
- * - `await nextIdle()` resolves once idle, allowing async/await usage.
59
- * - `nextIdle(fn)` schedules a callback to run when idle.
60
- *
61
- * Callbacks are executed before promises resolve,
62
- * and multiple calls queue until the scheduler becomes idle.
59
+ * Runs a function after any existing work has been completed,
60
+ * or immediately if the scheduler is already idle.
63
61
  */
64
- export function nextIdle(): Promise<void>
65
-
66
- /**
67
- * Schedules `fn` to run once the scheduler becomes idle.
68
- * If already idle, `fn` executes immediately.
69
- */
70
- export function nextIdle<T extends () => void>(fn: T): void
71
-
72
- export function nextIdle(fn?: () => void): void | Promise<void> {
73
- if (!fn) {
74
- return new Promise<void>(nextIdle)
75
- }
62
+ export function nextIdle(fn: () => void): void {
76
63
  if (isRunningOrQueued) {
77
64
  nextIdleEffects.push(fn)
78
65
  return
@@ -83,13 +70,13 @@ export function nextIdle(fn?: () => void): void | Promise<void> {
83
70
  /**
84
71
  * Syncronously flushes any pending work.
85
72
  */
86
- export function flushSync() {
73
+ export function flushSync(): void {
87
74
  if (!isRunningOrQueued) return
88
75
  window.cancelAnimationFrame(animationFrameHandle)
89
76
  doWork()
90
77
  }
91
78
 
92
- export function renderRootSync(rootNode: VNode) {
79
+ export function renderRootSync(rootNode: VNode): void {
93
80
  rootNode.flags |= FLAG_DIRTY
94
81
  treesInProgress.push(rootNode)
95
82
 
@@ -107,20 +94,20 @@ export function requestUpdate(vNode: VNode): void {
107
94
  queueUpdate(vNode)
108
95
  }
109
96
 
110
- function queueBeginWork() {
97
+ function queueBeginWork(): void {
111
98
  if (isRunningOrQueued) return
112
99
  isRunningOrQueued = true
113
100
  animationFrameHandle = window.requestAnimationFrame(doWork)
114
101
  }
115
102
 
116
- function onWorkFinished() {
103
+ function onWorkFinished(): void {
117
104
  isRunningOrQueued = false
118
105
  while (nextIdleEffects.length) {
119
106
  nextIdleEffects.shift()!()
120
107
  }
121
108
  }
122
109
 
123
- function queueUpdate(vNode: VNode) {
110
+ function queueUpdate(vNode: VNode): void {
124
111
  // In immediate effect mode (useLayoutEffect), immediately mark the render as dirty
125
112
  if (isImmediateEffectsMode) {
126
113
  immediateEffectDirtiedRender = true
@@ -146,12 +133,12 @@ function queueUpdate(vNode: VNode) {
146
133
  treesInProgress.push(vNode)
147
134
  }
148
135
 
149
- function queueDelete(vNode: VNode) {
136
+ function queueDelete(vNode: VNode): void {
150
137
  traverseApply(vNode, (n) => (n.flags |= FLAG_DELETION))
151
138
  deletions.push(vNode)
152
139
  }
153
140
 
154
- const depthSort = (a: VNode, b: VNode) => b.depth - a.depth
141
+ const depthSort = (a: VNode, b: VNode): number => b.depth - a.depth
155
142
 
156
143
  let currentWorkRoot: VNode | null = null
157
144
 
@@ -180,7 +167,7 @@ function doWork(): void {
180
167
  const flags = currentWorkRoot.flags
181
168
  if (flags & FLAG_DELETION) continue
182
169
  if (flags & FLAG_DIRTY) {
183
- let n: VNode | void = currentWorkRoot
170
+ let n: VNode | null = currentWorkRoot
184
171
  while ((n = performUnitOfWork(n))) {}
185
172
 
186
173
  while (deletions.length) {
@@ -211,7 +198,7 @@ function doWork(): void {
211
198
  consecutiveDirtyCount = 0
212
199
 
213
200
  onWorkFinished()
214
- flushEffects(postEffects)
201
+ queueMicrotask(() => flushEffects(postEffects))
215
202
  if (__DEV__) {
216
203
  window.__kiru.emit("update", appCtx!)
217
204
  window.__kiru.profilingContext?.emit("update", appCtx!)
@@ -219,15 +206,57 @@ function doWork(): void {
219
206
  }
220
207
  }
221
208
 
222
- function performUnitOfWork(vNode: VNode): VNode | void {
223
- 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
+ }
224
253
  try {
225
- if (typeof vNode.type === "string") {
226
- updateHostComponent(vNode as DomVNode)
227
- } else if (isExoticType(vNode.type)) {
228
- updateExoticComponent(vNode)
254
+ if (typeof type === "string") {
255
+ return updateHostComponent(vNode as DomVNode)
256
+ } else if (isExoticType(type)) {
257
+ return updateExoticComponent(vNode)
229
258
  } else {
230
- renderChild = updateFunctionComponent(vNode as FunctionVNode)
259
+ return updateFunctionComponent(vNode as FunctionVNode)
231
260
  }
232
261
  } catch (error) {
233
262
  if (__DEV__) {
@@ -260,47 +289,16 @@ function performUnitOfWork(vNode: VNode): VNode | void {
260
289
  throw error
261
290
  }
262
291
  console.error(error)
263
- return
292
+ return vNode.child
264
293
  }
265
294
  setTimeout(() => {
266
295
  throw error
267
296
  })
268
297
  }
269
-
270
- if (vNode.deletions !== null) {
271
- vNode.deletions.forEach(queueDelete)
272
- vNode.deletions = null
273
- }
274
-
275
- if (renderChild && vNode.child) {
276
- return vNode.child
277
- }
278
-
279
- let nextNode: VNode | null = vNode
280
- while (nextNode) {
281
- // queue effects upon ascent
282
- if (nextNode.immediateEffects) {
283
- preEffects.push(...nextNode.immediateEffects)
284
- nextNode.immediateEffects = undefined
285
- }
286
- if (nextNode.effects) {
287
- postEffects.push(...nextNode.effects)
288
- nextNode.effects = undefined
289
- }
290
-
291
- if (nextNode === currentWorkRoot) return
292
- if (nextNode.sibling) {
293
- return nextNode.sibling
294
- }
295
-
296
- nextNode = nextNode.parent
297
- if (renderMode.current === "hydrate" && nextNode?.dom) {
298
- hydrationStack.pop()
299
- }
300
- }
298
+ return null
301
299
  }
302
300
 
303
- function updateExoticComponent(vNode: VNode) {
301
+ function updateExoticComponent(vNode: VNode): VNode | null {
304
302
  const { props, type } = vNode
305
303
  let children = props.children
306
304
 
@@ -326,20 +324,19 @@ function updateExoticComponent(vNode: VNode) {
326
324
  }
327
325
  }
328
326
 
329
- vNode.child = reconcileChildren(vNode, children)
327
+ return (vNode.child = reconcileChildren(vNode, children))
330
328
  }
331
329
 
332
- function updateFunctionComponent(vNode: FunctionVNode) {
330
+ function updateFunctionComponent(vNode: FunctionVNode): VNode | null {
333
331
  const { type, props, subs, prev, flags } = vNode
334
332
  if (flags & FLAG_MEMO) {
335
- vNode.memoizedProps = props
336
333
  if (
337
- prev?.memoizedProps &&
338
- vNode.arePropsEqual!(prev.memoizedProps, props) &&
339
- !vNode.hmrUpdated
334
+ prev &&
335
+ (type as MemoFn)[$MEMO](prev.props, props) &&
336
+ !(__DEV__ && isHmrUpdate())
340
337
  ) {
341
338
  vNode.flags |= FLAG_NOOP
342
- return false
339
+ return null
343
340
  }
344
341
  vNode.flags &= ~FLAG_NOOP
345
342
  }
@@ -362,14 +359,14 @@ function updateFunctionComponent(vNode: FunctionVNode) {
362
359
  * and not clearing the entire set.
363
360
  */
364
361
  if (subs) {
365
- subs.forEach((unsub) => unsub())
362
+ subs.forEach(call)
366
363
  subs.clear()
367
364
  }
368
365
 
369
366
  if (__DEV__) {
370
367
  newChild = latest(type)(props)
371
368
 
372
- if (vNode.hmrUpdated && vNode.hooks && vNode.hookSig) {
369
+ if (isHmrUpdate() && vNode.hooks && vNode.hookSig) {
373
370
  const len = vNode.hooks.length
374
371
  if (hookIndex.current < len) {
375
372
  // clean up any hooks that were removed
@@ -382,7 +379,6 @@ function updateFunctionComponent(vNode: FunctionVNode) {
382
379
  }
383
380
  }
384
381
 
385
- delete vNode.hmrUpdated
386
382
  if (++renderTryCount > CONSECUTIVE_DIRTY_LIMIT) {
387
383
  throw new KiruError({
388
384
  message:
@@ -395,14 +391,14 @@ function updateFunctionComponent(vNode: FunctionVNode) {
395
391
  }
396
392
  newChild = type(props)
397
393
  } while (isRenderDirtied)
398
- vNode.child = reconcileChildren(vNode, newChild)
399
- return true
394
+
395
+ return (vNode.child = reconcileChildren(vNode, newChild))
400
396
  } finally {
401
397
  node.current = null
402
398
  }
403
399
  }
404
400
 
405
- function updateHostComponent(vNode: DomVNode) {
401
+ function updateHostComponent(vNode: DomVNode): VNode | null {
406
402
  const { props, type } = vNode
407
403
  if (__DEV__) {
408
404
  assertValidElementProps(vNode)
@@ -413,10 +409,8 @@ function updateHostComponent(vNode: DomVNode) {
413
409
  } else {
414
410
  vNode.dom = createDom(vNode)
415
411
  }
416
- if (__DEV__) {
417
- if (vNode.dom instanceof Element) {
418
- vNode.dom.__kiruNode = vNode
419
- }
412
+ if (__DEV__ && vNode.dom instanceof Element) {
413
+ vNode.dom.__kiruNode = vNode
420
414
  }
421
415
  }
422
416
  // text should _never_ have children
@@ -426,9 +420,11 @@ function updateHostComponent(vNode: DomVNode) {
426
420
  hydrationStack.push(vNode.dom!)
427
421
  }
428
422
  }
423
+
424
+ return vNode.child
429
425
  }
430
426
 
431
- function checkForTooManyConsecutiveDirtyRenders() {
427
+ function checkForTooManyConsecutiveDirtyRenders(): void {
432
428
  if (consecutiveDirtyCount > CONSECUTIVE_DIRTY_LIMIT) {
433
429
  throw new KiruError(
434
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."
@@ -436,7 +432,7 @@ function checkForTooManyConsecutiveDirtyRenders() {
436
432
  }
437
433
  }
438
434
 
439
- function flushEffects(effectArr: Function[]) {
435
+ function flushEffects(effectArr: Function[]): void {
440
436
  for (let i = 0; i < effectArr.length; i++) {
441
437
  effectArr[i]()
442
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
  }
@@ -2,7 +2,12 @@ import { __DEV__ } from "../env.js"
2
2
  import { useHook } from "../hooks/utils.js"
3
3
  import { effectQueue } from "./globals.js"
4
4
  import { executeWithTracking } from "./effect.js"
5
- import { latest, sideEffectsEnabled, generateRandomID } from "../utils/index.js"
5
+ import {
6
+ latest,
7
+ sideEffectsEnabled,
8
+ generateRandomID,
9
+ call,
10
+ } from "../utils/index.js"
6
11
  import type { Signal } from "./base.js"
7
12
  import type { SignalValues } from "./types.js"
8
13
 
@@ -26,12 +31,12 @@ export class WatchEffect<const Deps extends readonly Signal<unknown>[] = []> {
26
31
  this.unsubs = new Map()
27
32
  this.isRunning = false
28
33
  this.cleanup = null
29
- if (__DEV__) {
30
- if ("window" in globalThis) {
31
- const signals = window.__kiru.HMRContext!.signals
32
- if (signals.isWaitingForNextWatchCall()) {
33
- signals.pushWatch(this as WatchEffect)
34
- }
34
+ if (__DEV__ && "window" in globalThis) {
35
+ const { isWaitingForNextWatchCall, pushWatch } =
36
+ window.__kiru.HMRContext!.signals
37
+
38
+ if (isWaitingForNextWatchCall()) {
39
+ pushWatch(this as WatchEffect)
35
40
  }
36
41
  }
37
42
  this.start()
@@ -44,22 +49,24 @@ export class WatchEffect<const Deps extends readonly Signal<unknown>[] = []> {
44
49
 
45
50
  this.isRunning = true
46
51
 
47
- if (__DEV__) {
48
- // postpone execution during HMR
49
- if ("window" in globalThis && window.__kiru.HMRContext?.isReplacement()) {
50
- return queueMicrotask(() => {
51
- if (this.isRunning) {
52
- WatchEffect.run(this as WatchEffect)
53
- }
54
- })
55
- }
52
+ // postpone execution during HMR
53
+ if (
54
+ __DEV__ &&
55
+ "window" in globalThis &&
56
+ window.__kiru.HMRContext?.isReplacement()
57
+ ) {
58
+ return queueMicrotask(() => {
59
+ if (this.isRunning) {
60
+ WatchEffect.run(this as WatchEffect)
61
+ }
62
+ })
56
63
  }
57
64
  WatchEffect.run(this as WatchEffect)
58
65
  }
59
66
 
60
67
  stop() {
61
68
  effectQueue.delete(this.id)
62
- this.unsubs.forEach((fn) => fn())
69
+ this.unsubs.forEach(call)
63
70
  this.unsubs.clear()
64
71
  this.cleanup?.()
65
72
  this.cleanup = null
@@ -117,11 +124,9 @@ export function useWatch<const Deps extends readonly Signal<unknown>[]>(
117
124
  "useWatch",
118
125
  { watcher: null as any as WatchEffect<Deps> },
119
126
  ({ hook, isInit, isHMR }) => {
120
- if (__DEV__) {
121
- if (isHMR) {
122
- hook.cleanup?.()
123
- isInit = true
124
- }
127
+ if (__DEV__ && isHMR) {
128
+ hook.cleanup?.()
129
+ isInit = true
125
130
  }
126
131
  if (isInit) {
127
132
  const watcher = (hook.watcher = watch(depsOrGetter as Deps, getter!))
package/src/ssr/client.ts CHANGED
@@ -10,7 +10,7 @@ interface HydrationAppContextOptions extends AppContextOptions {
10
10
  * - "dynamic": SSR with lazy promise hydration
11
11
  * @default "dynamic"
12
12
  */
13
- hydrationMode?: Kiru.HydrationMode
13
+ hydrationMode?: "static" | "dynamic"
14
14
  }
15
15
 
16
16
  export function hydrate(
@@ -22,7 +22,6 @@ export function hydrate(
22
22
 
23
23
  const prevRenderMode = renderMode.current
24
24
  renderMode.current = "hydrate"
25
- hydrationStack.captureEvents(container)
26
25
 
27
26
  const prevHydrationMode = hydrationMode.current
28
27
  hydrationMode.current = options?.hydrationMode ?? "dynamic"
@@ -30,8 +29,6 @@ export function hydrate(
30
29
  const app = mount(children, container, options)
31
30
 
32
31
  renderMode.current = prevRenderMode
33
- hydrationStack.releaseEvents(container)
34
-
35
32
  hydrationMode.current = prevHydrationMode
36
33
 
37
34
  return app