kiru 0.47.0 → 0.48.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 (85) hide show
  1. package/dist/constants.d.ts +4 -2
  2. package/dist/constants.d.ts.map +1 -1
  3. package/dist/constants.js +5 -3
  4. package/dist/constants.js.map +1 -1
  5. package/dist/dom.d.ts.map +1 -1
  6. package/dist/dom.js +124 -52
  7. package/dist/dom.js.map +1 -1
  8. package/dist/element.js +2 -2
  9. package/dist/element.js.map +1 -1
  10. package/dist/hmr.d.ts +3 -2
  11. package/dist/hmr.d.ts.map +1 -1
  12. package/dist/hmr.js.map +1 -1
  13. package/dist/index.d.ts +1 -1
  14. package/dist/index.d.ts.map +1 -1
  15. package/dist/index.js +1 -1
  16. package/dist/index.js.map +1 -1
  17. package/dist/memo.d.ts.map +1 -1
  18. package/dist/memo.js +1 -3
  19. package/dist/memo.js.map +1 -1
  20. package/dist/props.js +1 -1
  21. package/dist/props.js.map +1 -1
  22. package/dist/reconciler.d.ts.map +1 -1
  23. package/dist/reconciler.js +14 -11
  24. package/dist/reconciler.js.map +1 -1
  25. package/dist/renderToString.js +1 -1
  26. package/dist/renderToString.js.map +1 -1
  27. package/dist/scheduler.d.ts.map +1 -1
  28. package/dist/scheduler.js +37 -142
  29. package/dist/scheduler.js.map +1 -1
  30. package/dist/signals/base.d.ts +1 -1
  31. package/dist/signals/base.d.ts.map +1 -1
  32. package/dist/signals/base.js +2 -2
  33. package/dist/signals/base.js.map +1 -1
  34. package/dist/signals/computed.d.ts +1 -1
  35. package/dist/signals/computed.d.ts.map +1 -1
  36. package/dist/signals/computed.js +3 -3
  37. package/dist/signals/computed.js.map +1 -1
  38. package/dist/signals/effect.d.ts +1 -1
  39. package/dist/signals/effect.d.ts.map +1 -1
  40. package/dist/signals/effect.js +3 -2
  41. package/dist/signals/effect.js.map +1 -1
  42. package/dist/signals/globals.d.ts +1 -1
  43. package/dist/signals/globals.d.ts.map +1 -1
  44. package/dist/signals/jsx.d.ts +1 -1
  45. package/dist/signals/jsx.d.ts.map +1 -1
  46. package/dist/signals/types.d.ts +1 -1
  47. package/dist/signals/types.d.ts.map +1 -1
  48. package/dist/signals/utils.d.ts +1 -1
  49. package/dist/signals/utils.d.ts.map +1 -1
  50. package/dist/signals/utils.js +1 -1
  51. package/dist/signals/utils.js.map +1 -1
  52. package/dist/ssr/server.js +1 -1
  53. package/dist/ssr/server.js.map +1 -1
  54. package/dist/swr.d.ts +1 -1
  55. package/dist/swr.d.ts.map +1 -1
  56. package/dist/swr.js +1 -1
  57. package/dist/swr.js.map +1 -1
  58. package/dist/types.d.ts +0 -1
  59. package/dist/types.d.ts.map +1 -1
  60. package/dist/utils.d.ts +1 -10
  61. package/dist/utils.d.ts.map +1 -1
  62. package/dist/utils.js +3 -47
  63. package/dist/utils.js.map +1 -1
  64. package/package.json +1 -1
  65. package/src/constants.ts +7 -3
  66. package/src/dom.ts +130 -56
  67. package/src/element.ts +2 -2
  68. package/src/hmr.ts +4 -3
  69. package/src/index.ts +1 -1
  70. package/src/memo.ts +1 -3
  71. package/src/props.ts +1 -1
  72. package/src/reconciler.ts +14 -16
  73. package/src/renderToString.ts +1 -1
  74. package/src/scheduler.ts +47 -144
  75. package/src/signals/base.ts +4 -4
  76. package/src/signals/computed.ts +4 -4
  77. package/src/signals/effect.ts +4 -3
  78. package/src/signals/globals.ts +1 -1
  79. package/src/signals/jsx.ts +1 -1
  80. package/src/signals/types.ts +1 -1
  81. package/src/signals/utils.ts +1 -1
  82. package/src/ssr/server.ts +1 -1
  83. package/src/swr.ts +1 -1
  84. package/src/types.ts +0 -1
  85. package/src/utils.ts +1 -66
package/src/scheduler.ts CHANGED
@@ -7,6 +7,9 @@ import {
7
7
  $CONTEXT_PROVIDER,
8
8
  CONSECUTIVE_DIRTY_LIMIT,
9
9
  FLAG_DELETION,
10
+ FLAG_DIRTY,
11
+ FLAG_MEMO,
12
+ FLAG_NOOP,
10
13
  } from "./constants.js"
11
14
  import { commitDeletion, commitWork, createDom, hydrateDom } from "./dom.js"
12
15
  import { __DEV__ } from "./env.js"
@@ -16,10 +19,8 @@ import { hydrationStack } from "./hydration.js"
16
19
  import { assertValidElementProps } from "./props.js"
17
20
  import { reconcileChildren } from "./reconciler.js"
18
21
  import {
19
- willMemoBlockUpdate,
20
22
  latest,
21
23
  traverseApply,
22
- vNodeContains,
23
24
  isExoticType,
24
25
  getVNodeAppContext,
25
26
  } from "./utils.js"
@@ -28,9 +29,7 @@ import type { AppContext } from "./appContext"
28
29
  type VNode = Kiru.VNode
29
30
 
30
31
  let appCtx: AppContext | null
31
- let nextUnitOfWork: VNode | null = null
32
32
  let treesInProgress: VNode[] = []
33
- let currentTreeIndex = 0
34
33
  let isRunningOrQueued = false
35
34
  let nextIdleEffects: (() => void)[] = []
36
35
  let deletions: VNode[] = []
@@ -38,7 +37,6 @@ let isImmediateEffectsMode = false
38
37
  let immediateEffectDirtiedRender = false
39
38
  let isRenderDirtied = false
40
39
  let consecutiveDirtyCount = 0
41
- let pendingContextChanges = new Set<ContextProviderNode<any>>()
42
40
  let preEffects: Array<Function> = []
43
41
  let postEffects: Array<Function> = []
44
42
  let animationFrameHandle = -1
@@ -105,91 +103,14 @@ function queueUpdate(vNode: VNode) {
105
103
  return
106
104
  }
107
105
 
108
- // If it's already the next unit of work, no need to queue again
109
- if (nextUnitOfWork === vNode) {
110
- return
111
- }
106
+ if (vNode.flags & (FLAG_DIRTY | FLAG_DELETION)) return
107
+ vNode.flags |= FLAG_DIRTY
112
108
 
113
- if (nextUnitOfWork === null) {
109
+ if (!treesInProgress.length) {
114
110
  treesInProgress.push(vNode)
115
- nextUnitOfWork = vNode
116
111
  return queueBeginWork()
117
112
  }
118
113
 
119
- for (let i = 0; i < treesInProgress.length; i++) {
120
- const tree = treesInProgress[i]
121
- if (tree !== vNode) continue
122
- if (i < currentTreeIndex) {
123
- // It was already processed; requeue it to the end
124
- currentTreeIndex--
125
- treesInProgress.splice(i, 1)
126
- treesInProgress.push(tree)
127
- }
128
- return
129
- }
130
-
131
- // Check if this node is a descendant of any trees already queued
132
- for (let i = 0; i < treesInProgress.length; i++) {
133
- const tree = treesInProgress[i]
134
- if (!vNodeContains(tree, vNode)) continue
135
-
136
- if (i === currentTreeIndex) {
137
- // It's a child of the currently worked-on tree
138
- // If it's deeper within the same tree, we can skip
139
- if (vNodeContains(nextUnitOfWork, vNode)) return
140
- // If it's not in the current work subtree, move back up to it
141
- nextUnitOfWork = vNode
142
- } else if (i < currentTreeIndex) {
143
- // It's a descendant of an already processed tree; treat as a new update
144
- treesInProgress.push(vNode)
145
- }
146
-
147
- return
148
- }
149
-
150
- // Check if this node contains any of the currently queued trees
151
- let didReplaceTree = false
152
- let shouldQueueAtEnd = false
153
- for (let i = 0; i < treesInProgress.length; ) {
154
- const tree = treesInProgress[i]
155
- if (!vNodeContains(vNode, tree)) {
156
- i++
157
- continue
158
- }
159
- // This node contains another update root, replace it
160
-
161
- if (i === currentTreeIndex) {
162
- if (!didReplaceTree) {
163
- treesInProgress.splice(i, 1, vNode)
164
- nextUnitOfWork = vNode
165
- didReplaceTree = true
166
- i++ // advance past replaced node
167
- } else {
168
- treesInProgress.splice(i, 1)
169
- // no increment
170
- }
171
- } else if (i < currentTreeIndex) {
172
- currentTreeIndex--
173
- treesInProgress.splice(i, 1)
174
- if (!didReplaceTree) {
175
- shouldQueueAtEnd = true
176
- didReplaceTree = true
177
- }
178
- // no increment
179
- } else {
180
- // i > currentTreeIndex
181
- treesInProgress.splice(i, 1)
182
- if (!didReplaceTree) {
183
- shouldQueueAtEnd = true
184
- didReplaceTree = true
185
- }
186
- // no increment
187
- }
188
- }
189
- if (!shouldQueueAtEnd && didReplaceTree) {
190
- return
191
- }
192
- // If it doesn't overlap with any queued tree, queue as new independent update root
193
114
  treesInProgress.push(vNode)
194
115
  }
195
116
 
@@ -198,9 +119,13 @@ function queueDelete(vNode: VNode) {
198
119
  deletions.push(vNode)
199
120
  }
200
121
 
122
+ const depthSort = (a: VNode, b: VNode) => b.depth - a.depth
123
+
124
+ let currentWorkRoot: VNode | null = null
125
+
201
126
  function doWork(): void {
202
127
  if (__DEV__) {
203
- const n = nextUnitOfWork ?? deletions[0] ?? treesInProgress[0]
128
+ const n = deletions[0] ?? treesInProgress[0]
204
129
  if (n) {
205
130
  appCtx = getVNodeAppContext(n)!
206
131
  window.__kiru?.profilingContext?.beginTick(appCtx)
@@ -209,21 +134,29 @@ function doWork(): void {
209
134
  }
210
135
  }
211
136
 
212
- while (nextUnitOfWork) {
213
- nextUnitOfWork =
214
- performUnitOfWork(nextUnitOfWork) ??
215
- treesInProgress[++currentTreeIndex] ??
216
- queueBlockedContextDependencyRoots()
217
- }
137
+ let len = 1
218
138
 
219
- while (deletions.length) {
220
- commitDeletion(deletions.shift()!)
221
- }
222
- const workRoots = [...treesInProgress]
223
- treesInProgress.length = 0
224
- currentTreeIndex = 0
225
- for (const root of workRoots) {
226
- commitWork(root)
139
+ while (treesInProgress.length) {
140
+ if (treesInProgress.length > len) {
141
+ treesInProgress.sort(depthSort)
142
+ }
143
+
144
+ currentWorkRoot = treesInProgress.shift()!
145
+ len = treesInProgress.length
146
+
147
+ const flags = currentWorkRoot.flags
148
+ if (flags & FLAG_DELETION) continue
149
+ if (flags & FLAG_DIRTY) {
150
+ let n: VNode | void = currentWorkRoot
151
+ while ((n = performUnitOfWork(n))) {}
152
+
153
+ while (deletions.length) {
154
+ commitDeletion(deletions.pop()!)
155
+ }
156
+ commitWork(currentWorkRoot)
157
+
158
+ currentWorkRoot.flags &= ~FLAG_DIRTY
159
+ }
227
160
  }
228
161
 
229
162
  isImmediateEffectsMode = true
@@ -252,39 +185,6 @@ function doWork(): void {
252
185
  }
253
186
  }
254
187
 
255
- function queueBlockedContextDependencyRoots(): VNode | null {
256
- if (pendingContextChanges.size === 0) return null
257
-
258
- // TODO: it's possible that a 'job' created by this process is
259
- // blocked by a parent memo after a queueUpdate -> replaceTree action.
260
- // To prevent this, we might need to add these to a distinct queue.
261
- const jobRoots: VNode[] = []
262
- pendingContextChanges.forEach((provider) => {
263
- provider.props.dependents.forEach((dep) => {
264
- if (!willMemoBlockUpdate(provider, dep)) return
265
- for (let i = 0; i < jobRoots.length; i++) {
266
- const root = jobRoots[i]
267
- if (vNodeContains(root, dep)) {
268
- if (willMemoBlockUpdate(root, dep)) {
269
- // root is a parent of dep and there's a memo between them, prevent consolidation and queue as new root
270
- break
271
- }
272
- return
273
- }
274
- if (vNodeContains(dep, root)) {
275
- jobRoots[i] = dep
276
- return
277
- }
278
- }
279
- jobRoots.push(dep)
280
- })
281
- })
282
-
283
- pendingContextChanges.clear()
284
- treesInProgress.push(...jobRoots)
285
- return jobRoots[0] ?? null
286
- }
287
-
288
188
  function performUnitOfWork(vNode: VNode): VNode | void {
289
189
  let renderChild = true
290
190
  try {
@@ -292,15 +192,15 @@ function performUnitOfWork(vNode: VNode): VNode | void {
292
192
  if (typeof vNode.type === "string") {
293
193
  updateHostComponent(vNode as DomVNode)
294
194
  } else if (isExoticType(vNode.type)) {
295
- if (vNode.type === $CONTEXT_PROVIDER) {
195
+ if (vNode?.type === $CONTEXT_PROVIDER) {
296
196
  const asProvider = vNode as ContextProviderNode<any>
297
- const { dependents, value } = asProvider.props
298
- if (
299
- dependents.size &&
300
- asProvider.prev &&
301
- asProvider.prev.props.value !== value
302
- ) {
303
- pendingContextChanges.add(asProvider)
197
+ const {
198
+ props: { dependents, value },
199
+ prev,
200
+ } = asProvider
201
+
202
+ if (dependents.size && prev && prev.props.value !== value) {
203
+ dependents.forEach(queueUpdate)
304
204
  }
305
205
  }
306
206
  vNode.child = reconcileChildren(vNode, props.children)
@@ -350,7 +250,7 @@ function performUnitOfWork(vNode: VNode): VNode | void {
350
250
  nextNode.effects = undefined
351
251
  }
352
252
 
353
- if (nextNode === treesInProgress[currentTreeIndex]) return
253
+ if (nextNode === currentWorkRoot) return
354
254
  if (nextNode.sibling) {
355
255
  return nextNode.sibling
356
256
  }
@@ -363,22 +263,25 @@ function performUnitOfWork(vNode: VNode): VNode | void {
363
263
  }
364
264
 
365
265
  function updateFunctionComponent(vNode: FunctionVNode) {
366
- const { type, props, subs, prev, isMemoized } = vNode
367
- if (isMemoized) {
266
+ const { type, props, subs, prev, flags } = vNode
267
+ if (flags & FLAG_MEMO) {
368
268
  vNode.memoizedProps = props
369
269
  if (
370
270
  prev?.memoizedProps &&
371
271
  vNode.arePropsEqual!(prev.memoizedProps, props) &&
372
272
  !vNode.hmrUpdated
373
273
  ) {
274
+ vNode.flags |= FLAG_NOOP
374
275
  return false
375
276
  }
277
+ vNode.flags &= ~FLAG_NOOP
376
278
  }
377
279
  try {
378
280
  node.current = vNode
379
281
  let newChild
380
282
  let renderTryCount = 0
381
283
  do {
284
+ vNode.flags &= ~FLAG_DIRTY
382
285
  isRenderDirtied = false
383
286
  hookIndex.current = 0
384
287
 
@@ -1,13 +1,13 @@
1
+ import { latest, safeStringify, sideEffectsEnabled } from "../utils.js"
1
2
  import { $HMR_ACCEPT, $SIGNAL } from "../constants.js"
2
3
  import { __DEV__ } from "../env.js"
3
- import type { HMRAccept } from "../hmr.js"
4
- import { latest, safeStringify, sideEffectsEnabled } from "../utils.js"
5
- import { tracking, signalSubsMap } from "./globals.js"
6
- import { type SignalSubscriber, ReadonlySignal } from "./types.js"
7
4
  import { node } from "../globals.js"
8
5
  import { useHook } from "../hooks/utils.js"
9
6
  import { generateRandomID } from "../generateId.js"
10
7
  import { requestUpdate } from "../scheduler.js"
8
+ import { tracking, signalSubsMap } from "./globals.js"
9
+ import type { SignalSubscriber, ReadonlySignal } from "./types.js"
10
+ import type { HMRAccept } from "../hmr.js"
11
11
 
12
12
  export class Signal<T> {
13
13
  [$SIGNAL] = true;
@@ -1,11 +1,11 @@
1
1
  import { __DEV__ } from "../env.js"
2
- import { Signal } from "./index.js"
3
- import { effectQueue, signalSubsMap } from "./globals.js"
4
2
  import { $HMR_ACCEPT } from "../constants.js"
5
- import type { HMRAccept } from "../hmr.js"
6
3
  import { useHook } from "../hooks/utils.js"
7
- import { executeWithTracking } from "./effect.js"
8
4
  import { latest } from "../utils.js"
5
+ import { effectQueue, signalSubsMap } from "./globals.js"
6
+ import { executeWithTracking } from "./effect.js"
7
+ import { Signal } from "./base.js"
8
+ import type { HMRAccept } from "../hmr.js"
9
9
 
10
10
  export class ComputedSignal<T> extends Signal<T> {
11
11
  protected $getter: (prev?: T) => T
@@ -1,7 +1,8 @@
1
1
  import { node } from "../globals.js"
2
2
  import { sideEffectsEnabled } from "../utils.js"
3
3
  import { tracking, effectQueue } from "./globals.js"
4
- import type { Signal } from "./index.js"
4
+ import { tick } from "./utils.js"
5
+ import type { Signal } from "./base.js"
5
6
  import type { SignalValues } from "./types.js"
6
7
 
7
8
  type TrackedExecutionContext<T, Deps extends readonly Signal<unknown>[]> = {
@@ -42,8 +43,8 @@ export function executeWithTracking<T, Deps extends readonly Signal<unknown>[]>(
42
43
  }
43
44
 
44
45
  const effect = () => {
45
- if (!effectQueue.has(id)) {
46
- queueMicrotask(() => effectQueue.get(id)?.())
46
+ if (!effectQueue.size) {
47
+ queueMicrotask(tick)
47
48
  }
48
49
  effectQueue.set(id, onDepChanged)
49
50
  }
@@ -1,4 +1,4 @@
1
- import type { Signal } from "./index.js"
1
+ import type { Signal } from "./base.js"
2
2
  import type { SignalSubscriber } from "./types.js"
3
3
 
4
4
  export const tracking = {
@@ -1,4 +1,4 @@
1
- import type { Signal } from "./index.js"
1
+ import type { Signal } from "./base.js"
2
2
  import type { SignalValues } from "./types.js"
3
3
 
4
4
  type InferArraySignalItemType<T extends Signal<any[]>> = T extends Signal<
@@ -1,4 +1,4 @@
1
- import { type Signal } from "./index.js"
1
+ import type { Signal } from "./base.js"
2
2
 
3
3
  export type ReadonlySignal<T> = Signal<T> & {
4
4
  readonly value: T
@@ -1,4 +1,4 @@
1
- import { Signal } from "./index.js"
1
+ import { Signal } from "./base.js"
2
2
  import { effectQueue } from "./globals.js"
3
3
 
4
4
  export function unwrap<T>(value: T | Signal<T>, reactive = false): T {
package/src/ssr/server.ts CHANGED
@@ -7,7 +7,7 @@ import {
7
7
  propsToElementAttributes,
8
8
  isExoticType,
9
9
  } from "../utils.js"
10
- import { Signal } from "../signals/index.js"
10
+ import { Signal } from "../signals/base.js"
11
11
  import { $HYDRATION_BOUNDARY, voidElements } from "../constants.js"
12
12
  import { assertValidElementProps } from "../props.js"
13
13
  import { HYDRATION_BOUNDARY_MARKER } from "./hydrationBoundary.js"
package/src/swr.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { __DEV__ } from "./env.js"
2
2
  import { useHook } from "./hooks/utils.js"
3
- import { Signal } from "./signals/index.js"
3
+ import { Signal } from "./signals/base.js"
4
4
  import {
5
5
  noop,
6
6
  deepCompare,
package/src/types.ts CHANGED
@@ -194,7 +194,6 @@ declare global {
194
194
  effects?: Array<Function>
195
195
  immediateEffects?: Array<Function>
196
196
  memoizedProps?: Record<string, any>
197
- isMemoized?: boolean
198
197
  arePropsEqual?: (
199
198
  prev: Record<string, any>,
200
199
  next: Record<string, any>
package/src/utils.ts CHANGED
@@ -5,12 +5,11 @@ import {
5
5
  $HYDRATION_BOUNDARY,
6
6
  booleanAttributes,
7
7
  FLAG_DELETION,
8
- FLAG_HAS_MEMO_ANCESTOR,
9
8
  FLAG_PLACEMENT,
10
9
  FLAG_UPDATE,
11
10
  REGEX_UNIT,
12
11
  } from "./constants.js"
13
- import { unwrap } from "./signals/index.js"
12
+ import { unwrap } from "./signals/utils.js"
14
13
  import { __DEV__ } from "./env.js"
15
14
  import type { AppContext } from "./appContext"
16
15
 
@@ -24,12 +23,10 @@ export {
24
23
  isExoticType,
25
24
  isVNodeDeleted,
26
25
  vNodeContains,
27
- willMemoBlockUpdate,
28
26
  getCurrentVNode,
29
27
  getVNodeAppContext,
30
28
  commitSnapshot,
31
29
  traverseApply,
32
- postOrderApply,
33
30
  findParent,
34
31
  propToHtmlAttr,
35
32
  propValueToHtmlAttrValue,
@@ -157,25 +154,6 @@ function vNodeContains(haystack: VNode, needle: VNode): boolean {
157
154
  return false
158
155
  }
159
156
 
160
- function willMemoBlockUpdate(root: VNode, target: VNode): boolean {
161
- let node: VNode | null = target
162
-
163
- while (node && node !== root && node.flags & FLAG_HAS_MEMO_ANCESTOR) {
164
- const parent = node.parent
165
- if (
166
- parent?.isMemoized &&
167
- parent.prev?.memoizedProps &&
168
- parent.arePropsEqual!(parent.prev.memoizedProps, parent.props)
169
- ) {
170
- return true
171
- }
172
-
173
- node = node.parent
174
- }
175
-
176
- return false
177
- }
178
-
179
157
  function traverseApply(vNode: VNode, func: (node: VNode) => void): void {
180
158
  let applyToSiblings = false
181
159
  const nodes: VNode[] = [vNode]
@@ -188,49 +166,6 @@ function traverseApply(vNode: VNode, func: (node: VNode) => void): void {
188
166
  while (nodes.length) apply(nodes.shift()!)
189
167
  }
190
168
 
191
- function postOrderApply(
192
- tree: VNode,
193
- callbacks: {
194
- /** called upon traversing to the next parent, and on the root */
195
- onAscent: (vNode: VNode) => void
196
- /** called before traversing to the next parent */
197
- onBeforeAscent?: (vNode: VNode) => void
198
- /** called before traversing to the next child */
199
- onDescent?: (vNode: VNode) => void
200
- }
201
- ): void {
202
- const root = tree
203
- const rootChild = root.child
204
- if (!rootChild) {
205
- callbacks.onAscent(root)
206
- return
207
- }
208
-
209
- callbacks.onDescent?.(root)
210
- let branch = rootChild
211
- while (branch) {
212
- let c = branch
213
- while (c) {
214
- if (!c.child) break
215
- callbacks.onDescent?.(c)
216
- c = c.child
217
- }
218
-
219
- while (c && c !== root) {
220
- callbacks.onAscent(c)
221
- if (c.sibling) {
222
- branch = c.sibling
223
- break
224
- }
225
- callbacks.onBeforeAscent?.(c)
226
- c = c.parent!
227
- }
228
- if (c === root) break
229
- }
230
-
231
- callbacks.onAscent(root)
232
- }
233
-
234
169
  function findParent(vNode: Kiru.VNode, predicate: (n: Kiru.VNode) => boolean) {
235
170
  let n: Kiru.VNode | null = vNode.parent
236
171
  while (n) {