kiru 0.45.3 → 0.46.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 (119) hide show
  1. package/dist/appContext.d.ts +0 -13
  2. package/dist/appContext.d.ts.map +1 -1
  3. package/dist/appContext.js +15 -55
  4. package/dist/appContext.js.map +1 -1
  5. package/dist/constants.d.ts +5 -7
  6. package/dist/constants.d.ts.map +1 -1
  7. package/dist/constants.js +5 -7
  8. package/dist/constants.js.map +1 -1
  9. package/dist/context.d.ts.map +1 -1
  10. package/dist/context.js +2 -4
  11. package/dist/context.js.map +1 -1
  12. package/dist/dom.d.ts +2 -2
  13. package/dist/dom.d.ts.map +1 -1
  14. package/dist/dom.js +36 -32
  15. package/dist/dom.js.map +1 -1
  16. package/dist/globals.d.ts +1 -6
  17. package/dist/globals.d.ts.map +1 -1
  18. package/dist/globals.js +1 -5
  19. package/dist/globals.js.map +1 -1
  20. package/dist/hmr.d.ts +1 -1
  21. package/dist/hmr.d.ts.map +1 -1
  22. package/dist/hmr.js +2 -4
  23. package/dist/hmr.js.map +1 -1
  24. package/dist/hooks/useViewTransition.d.ts.map +1 -1
  25. package/dist/hooks/useViewTransition.js +3 -3
  26. package/dist/hooks/useViewTransition.js.map +1 -1
  27. package/dist/hooks/utils.d.ts +1 -5
  28. package/dist/hooks/utils.d.ts.map +1 -1
  29. package/dist/hooks/utils.js +8 -20
  30. package/dist/hooks/utils.js.map +1 -1
  31. package/dist/index.d.ts +1 -0
  32. package/dist/index.d.ts.map +1 -1
  33. package/dist/index.js +2 -3
  34. package/dist/index.js.map +1 -1
  35. package/dist/lazy.d.ts.map +1 -1
  36. package/dist/lazy.js +4 -4
  37. package/dist/lazy.js.map +1 -1
  38. package/dist/portal.d.ts.map +1 -1
  39. package/dist/portal.js +2 -3
  40. package/dist/portal.js.map +1 -1
  41. package/dist/props.js +1 -1
  42. package/dist/props.js.map +1 -1
  43. package/dist/reconciler.d.ts.map +1 -1
  44. package/dist/reconciler.js +105 -79
  45. package/dist/reconciler.js.map +1 -1
  46. package/dist/renderToString.d.ts.map +1 -1
  47. package/dist/renderToString.js +5 -8
  48. package/dist/renderToString.js.map +1 -1
  49. package/dist/router/router.d.ts.map +1 -1
  50. package/dist/router/router.js +5 -6
  51. package/dist/router/router.js.map +1 -1
  52. package/dist/scheduler.d.ts +4 -11
  53. package/dist/scheduler.d.ts.map +1 -1
  54. package/dist/scheduler.js +365 -390
  55. package/dist/scheduler.js.map +1 -1
  56. package/dist/signals/base.d.ts +4 -4
  57. package/dist/signals/base.d.ts.map +1 -1
  58. package/dist/signals/base.js +38 -24
  59. package/dist/signals/base.js.map +1 -1
  60. package/dist/signals/computed.d.ts +2 -2
  61. package/dist/signals/computed.d.ts.map +1 -1
  62. package/dist/signals/computed.js +11 -3
  63. package/dist/signals/computed.js.map +1 -1
  64. package/dist/signals/effect.d.ts +1 -1
  65. package/dist/signals/effect.d.ts.map +1 -1
  66. package/dist/signals/globals.d.ts +2 -2
  67. package/dist/signals/globals.d.ts.map +1 -1
  68. package/dist/signals/globals.js.map +1 -1
  69. package/dist/signals/jsx.d.ts +4 -3
  70. package/dist/signals/jsx.d.ts.map +1 -1
  71. package/dist/signals/jsx.js +1 -1
  72. package/dist/signals/jsx.js.map +1 -1
  73. package/dist/signals/types.d.ts +2 -2
  74. package/dist/signals/types.d.ts.map +1 -1
  75. package/dist/signals/utils.d.ts +1 -1
  76. package/dist/signals/utils.d.ts.map +1 -1
  77. package/dist/signals/utils.js +1 -1
  78. package/dist/signals/utils.js.map +1 -1
  79. package/dist/ssr/server.d.ts.map +1 -1
  80. package/dist/ssr/server.js +1 -5
  81. package/dist/ssr/server.js.map +1 -1
  82. package/dist/types.d.ts +9 -2
  83. package/dist/types.d.ts.map +1 -1
  84. package/dist/utils.d.ts +1 -1
  85. package/dist/utils.d.ts.map +1 -1
  86. package/dist/utils.js +17 -17
  87. package/dist/utils.js.map +1 -1
  88. package/package.json +1 -1
  89. package/src/appContext.ts +21 -67
  90. package/src/constants.ts +8 -7
  91. package/src/context.ts +2 -4
  92. package/src/dom.ts +50 -29
  93. package/src/globals.ts +1 -9
  94. package/src/hmr.ts +3 -5
  95. package/src/hooks/useViewTransition.ts +3 -3
  96. package/src/hooks/utils.ts +7 -22
  97. package/src/index.ts +3 -3
  98. package/src/lazy.ts +4 -4
  99. package/src/portal.ts +2 -3
  100. package/src/props.ts +1 -1
  101. package/src/reconciler.ts +116 -90
  102. package/src/renderToString.ts +5 -8
  103. package/src/router/router.ts +4 -6
  104. package/src/scheduler.ts +369 -407
  105. package/src/signals/base.ts +40 -33
  106. package/src/signals/computed.ts +8 -3
  107. package/src/signals/effect.ts +1 -1
  108. package/src/signals/globals.ts +2 -2
  109. package/src/signals/jsx.ts +12 -11
  110. package/src/signals/types.ts +2 -2
  111. package/src/signals/utils.ts +1 -1
  112. package/src/ssr/server.ts +1 -5
  113. package/src/types.ts +9 -2
  114. package/src/utils.ts +26 -21
  115. package/dist/flags.d.ts +0 -6
  116. package/dist/flags.d.ts.map +0 -1
  117. package/dist/flags.js +0 -16
  118. package/dist/flags.js.map +0 -1
  119. package/src/flags.ts +0 -15
package/dist/scheduler.js CHANGED
@@ -1,457 +1,432 @@
1
- import { flags } from "./flags.js";
2
- import { $CONTEXT_PROVIDER, CONSECUTIVE_DIRTY_LIMIT, FLAG, } from "./constants.js";
3
- import { commitWork, createDom, hydrateDom } from "./dom.js";
1
+ import { $CONTEXT_PROVIDER, CONSECUTIVE_DIRTY_LIMIT, FLAG_DELETION, } from "./constants.js";
2
+ import { commitDeletion, commitWork, createDom, hydrateDom } from "./dom.js";
4
3
  import { __DEV__ } from "./env.js";
5
4
  import { KiruError } from "./error.js";
6
- import { ctx, hookIndex, node, nodeToCtxMap, renderMode } from "./globals.js";
5
+ import { hookIndex, node, renderMode } from "./globals.js";
7
6
  import { hydrationStack } from "./hydration.js";
8
7
  import { assertValidElementProps } from "./props.js";
9
8
  import { reconcileChildren } from "./reconciler.js";
10
- import { willMemoBlockUpdate, latest, traverseApply, vNodeContains, isExoticType, } from "./utils.js";
11
- import { Signal } from "./signals/base.js";
12
- export function createScheduler(appCtx, maxFrameMs = 50) {
13
- let nextUnitOfWork = null;
14
- let treesInProgress = [];
15
- let currentTreeIndex = 0;
16
- let isRunning = false;
17
- let nextIdleEffects = [];
18
- let deletions = [];
19
- let frameDeadline = 0;
20
- let pendingCallback = null;
21
- let frameHandle = null;
22
- let isImmediateEffectsMode = false;
23
- let immediateEffectDirtiedRender = false;
24
- let isRenderDirtied = false;
25
- let consecutiveDirtyCount = 0;
26
- let pendingContextChanges = new Set();
27
- let effectCallbacks = {
28
- pre: [],
29
- post: [],
30
- };
31
- let scheduler;
32
- const timeRemaining = () => frameDeadline - window.performance.now();
33
- const deadline = {
34
- didTimeout: false,
35
- timeRemaining,
36
- };
37
- const channel = new MessageChannel();
38
- channel.port2.onmessage = () => {
39
- if (typeof pendingCallback === "function") {
40
- pendingCallback(deadline);
41
- }
42
- };
43
- function clear() {
44
- nextUnitOfWork = null;
45
- treesInProgress = [];
46
- currentTreeIndex = 0;
47
- nextIdleEffects = [];
48
- deletions = [];
49
- effectCallbacks = { pre: [], post: [] };
50
- frameDeadline = 0;
51
- pendingCallback = null;
52
- sleep();
9
+ import { willMemoBlockUpdate, latest, traverseApply, vNodeContains, isExoticType, getVNodeAppContext, } from "./utils.js";
10
+ let appCtx;
11
+ let nextUnitOfWork = null;
12
+ let treesInProgress = [];
13
+ let currentTreeIndex = 0;
14
+ let isRunning = false;
15
+ let nextIdleEffects = [];
16
+ let deletions = [];
17
+ let isImmediateEffectsMode = false;
18
+ let immediateEffectDirtiedRender = false;
19
+ let isRenderDirtied = false;
20
+ let consecutiveDirtyCount = 0;
21
+ let pendingContextChanges = new Set();
22
+ let preEffects = [];
23
+ let postEffects = [];
24
+ export function nextIdle(fn, wakeUpIfIdle = true) {
25
+ nextIdleEffects.push(fn);
26
+ if (wakeUpIfIdle)
27
+ wake();
28
+ }
29
+ export function flushSync() {
30
+ workLoop();
31
+ }
32
+ export function requestUpdate(vNode) {
33
+ if (vNode.flags & FLAG_DELETION)
34
+ return;
35
+ if (__DEV__) {
36
+ // if (options?.debug?.onRequestUpdate) {
37
+ // options.debug.onRequestUpdate(vNode)
38
+ // }
53
39
  }
54
- function wake() {
55
- if (isRunning)
56
- return;
57
- isRunning = true;
58
- requestIdleCallback(workLoop);
40
+ if (renderMode.current === "hydrate") {
41
+ return nextIdle(() => {
42
+ vNode.flags & FLAG_DELETION || queueUpdate(vNode);
43
+ });
59
44
  }
60
- function sleep() {
61
- isRunning = false;
62
- if (frameHandle !== null) {
63
- globalThis.cancelAnimationFrame(frameHandle);
64
- frameHandle = null;
65
- }
45
+ queueUpdate(vNode);
46
+ }
47
+ export function requestDelete(vNode) {
48
+ if (vNode.flags & FLAG_DELETION)
49
+ return;
50
+ if (renderMode.current === "hydrate") {
51
+ return nextIdle(() => {
52
+ vNode.flags & FLAG_DELETION || queueDelete(vNode);
53
+ });
66
54
  }
67
- function nextIdle(fn, wakeUpIfIdle = true) {
68
- nextIdleEffects.push(fn);
69
- if (wakeUpIfIdle)
70
- wake();
55
+ queueDelete(vNode);
56
+ }
57
+ function queueWorkLoop() {
58
+ queueMicrotask(workLoop);
59
+ }
60
+ function wake() {
61
+ if (isRunning)
62
+ return;
63
+ isRunning = true;
64
+ queueWorkLoop();
65
+ }
66
+ function sleep() {
67
+ isRunning = false;
68
+ }
69
+ function queueUpdate(vNode) {
70
+ // In immediate effect mode (useLayoutEffect), immediately mark the render as dirty
71
+ if (isImmediateEffectsMode) {
72
+ immediateEffectDirtiedRender = true;
71
73
  }
72
- function flushSync() {
73
- if (frameHandle !== null) {
74
- globalThis.cancelAnimationFrame(frameHandle);
75
- frameHandle = null;
74
+ // If this node is currently being rendered, just mark it dirty
75
+ if (node.current === vNode) {
76
+ if (__DEV__) {
77
+ window.__kiru?.profilingContext?.emit("updateDirtied", appCtx);
76
78
  }
77
- workLoop();
79
+ isRenderDirtied = true;
80
+ return;
78
81
  }
79
- function queueUpdate(vNode) {
80
- // In immediate effect mode (useLayoutEffect), immediately mark the render as dirty
81
- if (isImmediateEffectsMode) {
82
- immediateEffectDirtiedRender = true;
83
- }
84
- // If this node is currently being rendered, just mark it dirty
85
- if (node.current === vNode) {
86
- if (__DEV__) {
87
- window.__kiru?.profilingContext?.emit("updateDirtied", appCtx);
88
- }
89
- isRenderDirtied = true;
90
- return;
82
+ // If it's already the next unit of work, no need to queue again
83
+ if (nextUnitOfWork === vNode) {
84
+ return;
85
+ }
86
+ if (nextUnitOfWork === null) {
87
+ treesInProgress.push(vNode);
88
+ nextUnitOfWork = vNode;
89
+ return wake();
90
+ }
91
+ for (let i = 0; i < treesInProgress.length; i++) {
92
+ const tree = treesInProgress[i];
93
+ if (tree !== vNode)
94
+ continue;
95
+ if (i < currentTreeIndex) {
96
+ // It was already processed; requeue it to the end
97
+ currentTreeIndex--;
98
+ treesInProgress.splice(i, 1);
99
+ treesInProgress.push(tree);
91
100
  }
92
- // If it's already the next unit of work, no need to queue again
93
- if (nextUnitOfWork === vNode) {
94
- return;
101
+ return;
102
+ }
103
+ // Check if this node is a descendant of any trees already queued
104
+ for (let i = 0; i < treesInProgress.length; i++) {
105
+ const tree = treesInProgress[i];
106
+ if (!vNodeContains(tree, vNode))
107
+ continue;
108
+ if (i === currentTreeIndex) {
109
+ // It's a child of the currently worked-on tree
110
+ // If it's deeper within the same tree, we can skip
111
+ if (vNodeContains(nextUnitOfWork, vNode))
112
+ return;
113
+ // If it's not in the current work subtree, move back up to it
114
+ nextUnitOfWork = vNode;
95
115
  }
96
- if (nextUnitOfWork === null) {
116
+ else if (i < currentTreeIndex) {
117
+ // It's a descendant of an already processed tree; treat as a new update
97
118
  treesInProgress.push(vNode);
98
- nextUnitOfWork = vNode;
99
- return wake();
100
119
  }
101
- // Check if the node is already in the treesInProgress queue
102
- const treeIdx = treesInProgress.indexOf(vNode);
103
- if (treeIdx !== -1) {
104
- if (treeIdx === currentTreeIndex) {
105
- // Replace current node if it's being worked on now
106
- treesInProgress[treeIdx] = vNode;
107
- nextUnitOfWork = vNode;
108
- }
109
- else if (treeIdx < currentTreeIndex) {
110
- // It was already processed; requeue it to the end
111
- currentTreeIndex--;
112
- treesInProgress.splice(treeIdx, 1);
113
- treesInProgress.push(vNode);
114
- }
115
- return;
120
+ return;
121
+ }
122
+ // Check if this node contains any of the currently queued trees
123
+ let didReplaceTree = false;
124
+ let shouldQueueAtEnd = false;
125
+ for (let i = 0; i < treesInProgress.length;) {
126
+ const tree = treesInProgress[i];
127
+ if (!vNodeContains(vNode, tree)) {
128
+ i++;
129
+ continue;
116
130
  }
117
- const nodeDepth = vNode.depth;
118
- // Check if this node is a descendant of any trees already queued
119
- for (let i = 0; i < treesInProgress.length; i++) {
120
- const tree = treesInProgress[i];
121
- if (tree.depth > nodeDepth)
122
- continue; // Can't be an ancestor
123
- if (!vNodeContains(tree, vNode))
124
- continue;
125
- if (i === currentTreeIndex) {
126
- // It's a child of the currently worked-on tree
127
- // If it's deeper within the same tree, we can skip
128
- if (vNodeContains(nextUnitOfWork, vNode))
129
- return;
130
- // If it's not in the current work subtree, move back up to it
131
+ // This node contains another update root, replace it
132
+ if (i === currentTreeIndex) {
133
+ if (!didReplaceTree) {
134
+ treesInProgress.splice(i, 1, vNode);
131
135
  nextUnitOfWork = vNode;
132
- }
133
- else if (i < currentTreeIndex) {
134
- // It's a descendant of an already processed tree; treat as a new update
135
- treesInProgress.push(vNode);
136
- }
137
- return;
138
- }
139
- // Check if this node contains any of the currently queued trees
140
- let didReplaceTree = false;
141
- let shouldQueueAtEnd = false;
142
- for (let i = 0; i < treesInProgress.length;) {
143
- const tree = treesInProgress[i];
144
- if (tree.depth < nodeDepth || !vNodeContains(vNode, tree)) {
145
- i++;
146
- continue;
147
- }
148
- // This node contains another update root, replace it
149
- if (i === currentTreeIndex) {
150
- if (!didReplaceTree) {
151
- treesInProgress.splice(i, 1, vNode);
152
- nextUnitOfWork = vNode;
153
- didReplaceTree = true;
154
- i++; // advance past replaced node
155
- }
156
- else {
157
- treesInProgress.splice(i, 1);
158
- // no increment
159
- }
160
- }
161
- else if (i < currentTreeIndex) {
162
- currentTreeIndex--;
163
- treesInProgress.splice(i, 1);
164
- if (!didReplaceTree) {
165
- shouldQueueAtEnd = true;
166
- didReplaceTree = true;
167
- }
168
- // no increment
136
+ didReplaceTree = true;
137
+ i++; // advance past replaced node
169
138
  }
170
139
  else {
171
- // i > currentTreeIndex
172
140
  treesInProgress.splice(i, 1);
173
- if (!didReplaceTree) {
174
- shouldQueueAtEnd = true;
175
- didReplaceTree = true;
176
- }
177
141
  // no increment
178
142
  }
179
143
  }
180
- if (!shouldQueueAtEnd && didReplaceTree) {
181
- return;
144
+ else if (i < currentTreeIndex) {
145
+ currentTreeIndex--;
146
+ treesInProgress.splice(i, 1);
147
+ if (!didReplaceTree) {
148
+ shouldQueueAtEnd = true;
149
+ didReplaceTree = true;
150
+ }
151
+ // no increment
152
+ }
153
+ else {
154
+ // i > currentTreeIndex
155
+ treesInProgress.splice(i, 1);
156
+ if (!didReplaceTree) {
157
+ shouldQueueAtEnd = true;
158
+ didReplaceTree = true;
159
+ }
160
+ // no increment
182
161
  }
183
- // If it doesn't overlap with any queued tree, queue as new independent update root
184
- treesInProgress.push(vNode);
185
- }
186
- function queueDelete(vNode) {
187
- traverseApply(vNode, (n) => (n.flags = flags.set(n.flags, FLAG.DELETION)));
188
- deletions.push(vNode);
189
162
  }
190
- function isFlushReady() {
191
- return !nextUnitOfWork && (deletions.length || treesInProgress.length);
163
+ if (!shouldQueueAtEnd && didReplaceTree) {
164
+ return;
192
165
  }
193
- function workLoop(deadline) {
194
- if (__DEV__) {
166
+ // If it doesn't overlap with any queued tree, queue as new independent update root
167
+ treesInProgress.push(vNode);
168
+ }
169
+ function queueDelete(vNode) {
170
+ traverseApply(vNode, (n) => (n.flags |= FLAG_DELETION));
171
+ deletions.push(vNode);
172
+ }
173
+ function workLoop() {
174
+ if (__DEV__) {
175
+ const n = nextUnitOfWork ?? deletions[0] ?? treesInProgress[0];
176
+ if (n) {
177
+ appCtx = getVNodeAppContext(n);
195
178
  window.__kiru?.profilingContext?.beginTick(appCtx);
196
179
  }
197
- ctx.current = appCtx;
198
- while (nextUnitOfWork) {
199
- nextUnitOfWork =
200
- performUnitOfWork(nextUnitOfWork) ??
201
- treesInProgress[++currentTreeIndex] ??
202
- queueBlockedContextDependencyRoots();
203
- if ((deadline?.timeRemaining() ?? 1) < 1)
204
- break;
180
+ else {
181
+ appCtx = null;
205
182
  }
206
- if (isFlushReady()) {
207
- while (deletions.length) {
208
- commitWork(deletions.shift());
209
- }
210
- const treesInProgressCopy = [...treesInProgress];
211
- treesInProgress = [];
212
- currentTreeIndex = 0;
213
- for (const tree of treesInProgressCopy) {
214
- commitWork(tree);
215
- }
216
- isImmediateEffectsMode = true;
217
- flushEffects(effectCallbacks.pre);
218
- isImmediateEffectsMode = false;
219
- if (immediateEffectDirtiedRender) {
220
- checkForTooManyConsecutiveDirtyRenders();
221
- flushEffects(effectCallbacks.post);
222
- immediateEffectDirtiedRender = false;
223
- consecutiveDirtyCount++;
224
- if (__DEV__) {
225
- window.__kiru?.profilingContext?.endTick(appCtx);
226
- window.__kiru?.profilingContext?.emit("updateDirtied", appCtx);
227
- }
228
- return workLoop();
229
- }
230
- consecutiveDirtyCount = 0;
231
- flushEffects(effectCallbacks.post);
232
- window.__kiru.emit("update", appCtx);
233
- if (__DEV__) {
234
- window.__kiru?.profilingContext?.emit("update", appCtx);
235
- }
183
+ }
184
+ while (nextUnitOfWork) {
185
+ nextUnitOfWork =
186
+ performUnitOfWork(nextUnitOfWork) ??
187
+ treesInProgress[++currentTreeIndex] ??
188
+ queueBlockedContextDependencyRoots();
189
+ }
190
+ if (!nextUnitOfWork && (deletions.length || treesInProgress.length)) {
191
+ while (deletions.length) {
192
+ commitDeletion(deletions.shift());
236
193
  }
237
- if (!nextUnitOfWork) {
238
- sleep();
239
- while (nextIdleEffects.length) {
240
- nextIdleEffects.shift()(scheduler);
241
- }
194
+ const workRoots = [...treesInProgress];
195
+ treesInProgress.length = 0;
196
+ currentTreeIndex = 0;
197
+ for (const root of workRoots) {
198
+ commitWork(root);
199
+ }
200
+ isImmediateEffectsMode = true;
201
+ flushEffects(preEffects);
202
+ isImmediateEffectsMode = false;
203
+ if (immediateEffectDirtiedRender) {
204
+ checkForTooManyConsecutiveDirtyRenders();
205
+ flushEffects(postEffects);
206
+ immediateEffectDirtiedRender = false;
207
+ consecutiveDirtyCount++;
242
208
  if (__DEV__) {
243
209
  window.__kiru?.profilingContext?.endTick(appCtx);
210
+ window.__kiru?.profilingContext?.emit("updateDirtied", appCtx);
244
211
  }
245
- return;
212
+ return flushSync();
213
+ }
214
+ consecutiveDirtyCount = 0;
215
+ flushEffects(postEffects);
216
+ if (__DEV__) {
217
+ window.__kiru.emit("update", appCtx);
218
+ window.__kiru?.profilingContext?.emit("update", appCtx);
246
219
  }
247
- requestIdleCallback(workLoop);
248
220
  }
249
- function requestIdleCallback(callback) {
250
- frameHandle = globalThis.requestAnimationFrame((time) => {
251
- frameDeadline = time + maxFrameMs;
252
- pendingCallback = callback;
253
- channel.port1.postMessage(null);
254
- });
221
+ if (!nextUnitOfWork) {
222
+ sleep();
223
+ while (nextIdleEffects.length) {
224
+ nextIdleEffects.shift()();
225
+ }
226
+ if (__DEV__) {
227
+ if (appCtx) {
228
+ window.__kiru?.profilingContext?.endTick(appCtx);
229
+ }
230
+ }
231
+ return;
255
232
  }
256
- function queueBlockedContextDependencyRoots() {
257
- if (pendingContextChanges.size === 0)
258
- return null;
259
- // TODO: it's possible that a 'job' created by this process is
260
- // blocked by a parent memo after a queueUpdate -> replaceTree action.
261
- // To prevent this, we might need to add these to a distinct queue.
262
- const jobRoots = [];
263
- pendingContextChanges.forEach((provider) => {
264
- provider.props.dependents.forEach((dep) => {
265
- if (!willMemoBlockUpdate(provider, dep))
266
- return;
267
- const depDepth = dep.depth;
268
- for (let i = 0; i < jobRoots.length; i++) {
269
- const root = jobRoots[i];
270
- const rootDepth = root.depth;
271
- if (depDepth > rootDepth && vNodeContains(root, dep)) {
272
- if (willMemoBlockUpdate(root, dep)) {
273
- // root is a parent of dep and there's a memo between them, prevent consolidation and queue as new root
274
- break;
275
- }
276
- return;
277
- }
278
- if (depDepth < rootDepth && vNodeContains(dep, root)) {
279
- jobRoots[i] = dep;
280
- return;
233
+ queueWorkLoop();
234
+ }
235
+ function queueBlockedContextDependencyRoots() {
236
+ if (pendingContextChanges.size === 0)
237
+ return null;
238
+ // TODO: it's possible that a 'job' created by this process is
239
+ // blocked by a parent memo after a queueUpdate -> replaceTree action.
240
+ // To prevent this, we might need to add these to a distinct queue.
241
+ const jobRoots = [];
242
+ pendingContextChanges.forEach((provider) => {
243
+ provider.props.dependents.forEach((dep) => {
244
+ if (!willMemoBlockUpdate(provider, dep))
245
+ return;
246
+ for (let i = 0; i < jobRoots.length; i++) {
247
+ const root = jobRoots[i];
248
+ if (vNodeContains(root, dep)) {
249
+ if (willMemoBlockUpdate(root, dep)) {
250
+ // root is a parent of dep and there's a memo between them, prevent consolidation and queue as new root
251
+ break;
281
252
  }
253
+ return;
282
254
  }
283
- jobRoots.push(dep);
284
- });
285
- });
286
- pendingContextChanges.clear();
287
- treesInProgress.push(...jobRoots);
288
- return jobRoots[0] ?? null;
289
- }
290
- function performUnitOfWork(vNode) {
291
- let renderChild = true;
292
- try {
293
- const { props } = vNode;
294
- if (typeof vNode.type === "string") {
295
- updateHostComponent(vNode);
296
- }
297
- else if (isExoticType(vNode.type)) {
298
- if (vNode.type === $CONTEXT_PROVIDER) {
299
- const asProvider = vNode;
300
- const { dependents, value } = asProvider.props;
301
- if (dependents.size &&
302
- asProvider.prev &&
303
- asProvider.prev.props.value !== value) {
304
- pendingContextChanges.add(asProvider);
305
- }
255
+ if (vNodeContains(dep, root)) {
256
+ jobRoots[i] = dep;
257
+ return;
306
258
  }
307
- vNode.child = reconcileChildren(vNode, props.children);
308
- vNode.deletions?.forEach((d) => queueDelete(d));
309
- }
310
- else {
311
- renderChild = updateFunctionComponent(vNode);
312
259
  }
260
+ jobRoots.push(dep);
261
+ });
262
+ });
263
+ pendingContextChanges.clear();
264
+ treesInProgress.push(...jobRoots);
265
+ return jobRoots[0] ?? null;
266
+ }
267
+ function performUnitOfWork(vNode) {
268
+ let renderChild = true;
269
+ try {
270
+ const { props } = vNode;
271
+ if (typeof vNode.type === "string") {
272
+ updateHostComponent(vNode);
313
273
  }
314
- catch (error) {
315
- window.__kiru?.emit("error", appCtx, error instanceof Error ? error : new Error(String(error)));
316
- if (KiruError.isKiruError(error)) {
317
- if (error.customNodeStack) {
318
- setTimeout(() => {
319
- throw new Error(error.customNodeStack);
320
- });
321
- }
322
- if (error.fatal) {
323
- throw error;
274
+ else if (isExoticType(vNode.type)) {
275
+ if (vNode.type === $CONTEXT_PROVIDER) {
276
+ const asProvider = vNode;
277
+ const { dependents, value } = asProvider.props;
278
+ if (dependents.size &&
279
+ asProvider.prev &&
280
+ asProvider.prev.props.value !== value) {
281
+ pendingContextChanges.add(asProvider);
324
282
  }
325
- console.error(error);
326
- return;
327
283
  }
328
- setTimeout(() => {
329
- throw error;
330
- });
284
+ vNode.child = reconcileChildren(vNode, props.children);
285
+ queueNodeChildDeletions(vNode);
331
286
  }
332
- if (renderChild && vNode.child) {
333
- return vNode.child;
287
+ else {
288
+ renderChild = updateFunctionComponent(vNode);
334
289
  }
335
- let nextNode = vNode;
336
- while (nextNode) {
337
- // queue effects upon ascent
338
- if (nextNode.immediateEffects) {
339
- effectCallbacks.pre.push(...nextNode.immediateEffects);
340
- nextNode.immediateEffects = undefined;
341
- }
342
- if (nextNode.effects) {
343
- effectCallbacks.post.push(...nextNode.effects);
344
- nextNode.effects = undefined;
345
- }
346
- if (nextNode === treesInProgress[currentTreeIndex])
347
- return;
348
- if (nextNode.sibling) {
349
- return nextNode.sibling;
290
+ }
291
+ catch (error) {
292
+ if (__DEV__) {
293
+ window.__kiru?.emit("error", appCtx, error instanceof Error ? error : new Error(String(error)));
294
+ }
295
+ if (KiruError.isKiruError(error)) {
296
+ if (error.customNodeStack) {
297
+ setTimeout(() => {
298
+ throw new Error(error.customNodeStack);
299
+ });
350
300
  }
351
- nextNode = nextNode.parent;
352
- if (renderMode.current === "hydrate" && nextNode?.dom) {
353
- hydrationStack.pop();
301
+ if (error.fatal) {
302
+ throw error;
354
303
  }
304
+ console.error(error);
305
+ return;
355
306
  }
307
+ setTimeout(() => {
308
+ throw error;
309
+ });
356
310
  }
357
- function updateFunctionComponent(vNode) {
358
- const { type, props, subs, prev, isMemoized } = vNode;
359
- if (isMemoized) {
360
- vNode.memoizedProps = props;
361
- if (prev?.memoizedProps &&
362
- vNode.arePropsEqual(prev.memoizedProps, props) &&
363
- !vNode.hmrUpdated) {
364
- return false;
365
- }
311
+ if (renderChild && vNode.child) {
312
+ return vNode.child;
313
+ }
314
+ let nextNode = vNode;
315
+ while (nextNode) {
316
+ // queue effects upon ascent
317
+ if (nextNode.immediateEffects) {
318
+ preEffects.push(...nextNode.immediateEffects);
319
+ nextNode.immediateEffects = undefined;
366
320
  }
367
- try {
368
- node.current = vNode;
369
- nodeToCtxMap.set(vNode, appCtx);
370
- let newChild;
371
- let renderTryCount = 0;
372
- do {
373
- isRenderDirtied = false;
374
- hookIndex.current = 0;
375
- /**
376
- * remove previous signal subscriptions (if any) every render.
377
- * this prevents no-longer-observed signals from triggering updates
378
- * in components that are not currently using them.
379
- *
380
- * TODO: in future, we might be able to optimize this by
381
- * only clearing the subscriptions that are no longer needed
382
- * and not clearing the entire set.
383
- */
384
- if (subs) {
385
- for (const sub of subs) {
386
- Signal.unsubscribe(vNode, sub);
387
- }
388
- subs.clear();
389
- }
390
- if (__DEV__) {
391
- newChild = latest(type)(props);
392
- delete vNode.hmrUpdated;
393
- if (++renderTryCount > CONSECUTIVE_DIRTY_LIMIT) {
394
- throw new KiruError({
395
- message: "Too many re-renders. Kiru limits the number of renders to prevent an infinite loop.",
396
- fatal: true,
397
- vNode,
398
- });
399
- }
400
- continue;
401
- }
402
- newChild = type(props);
403
- } while (isRenderDirtied);
404
- vNode.child = reconcileChildren(vNode, newChild);
405
- vNode.deletions?.forEach((d) => queueDelete(d));
406
- return true;
321
+ if (nextNode.effects) {
322
+ postEffects.push(...nextNode.effects);
323
+ nextNode.effects = undefined;
324
+ }
325
+ if (nextNode === treesInProgress[currentTreeIndex])
326
+ return;
327
+ if (nextNode.sibling) {
328
+ return nextNode.sibling;
407
329
  }
408
- finally {
409
- node.current = null;
330
+ nextNode = nextNode.parent;
331
+ if (renderMode.current === "hydrate" && nextNode?.dom) {
332
+ hydrationStack.pop();
410
333
  }
411
334
  }
412
- function updateHostComponent(vNode) {
413
- const { props } = vNode;
414
- if (__DEV__) {
415
- assertValidElementProps(vNode);
335
+ }
336
+ function updateFunctionComponent(vNode) {
337
+ const { type, props, subs, prev, isMemoized } = vNode;
338
+ if (isMemoized) {
339
+ vNode.memoizedProps = props;
340
+ if (prev?.memoizedProps &&
341
+ vNode.arePropsEqual(prev.memoizedProps, props) &&
342
+ !vNode.hmrUpdated) {
343
+ return false;
416
344
  }
417
- if (!vNode.dom) {
418
- if (renderMode.current === "hydrate") {
419
- hydrateDom(vNode);
420
- }
421
- else {
422
- vNode.dom = createDom(vNode);
345
+ }
346
+ try {
347
+ node.current = vNode;
348
+ let newChild;
349
+ let renderTryCount = 0;
350
+ do {
351
+ isRenderDirtied = false;
352
+ hookIndex.current = 0;
353
+ /**
354
+ * remove previous signal subscriptions (if any) every render.
355
+ * this prevents no-longer-observed signals from triggering updates
356
+ * in components that are not currently using them.
357
+ *
358
+ * TODO: in future, we might be able to optimize this by
359
+ * only clearing the subscriptions that are no longer needed
360
+ * and not clearing the entire set.
361
+ */
362
+ if (subs) {
363
+ subs.forEach((unsub) => unsub());
364
+ subs.clear();
423
365
  }
424
366
  if (__DEV__) {
425
- // @ts-expect-error we apply vNode to the dom node
426
- vNode.dom.__kiruNode = vNode;
367
+ newChild = latest(type)(props);
368
+ delete vNode.hmrUpdated;
369
+ if (++renderTryCount > CONSECUTIVE_DIRTY_LIMIT) {
370
+ throw new KiruError({
371
+ message: "Too many re-renders. Kiru limits the number of renders to prevent an infinite loop.",
372
+ fatal: true,
373
+ vNode,
374
+ });
375
+ }
376
+ continue;
427
377
  }
378
+ newChild = type(props);
379
+ } while (isRenderDirtied);
380
+ vNode.child = reconcileChildren(vNode, newChild);
381
+ queueNodeChildDeletions(vNode);
382
+ return true;
383
+ }
384
+ finally {
385
+ node.current = null;
386
+ }
387
+ }
388
+ function updateHostComponent(vNode) {
389
+ const { props, type } = vNode;
390
+ if (__DEV__) {
391
+ assertValidElementProps(vNode);
392
+ }
393
+ if (!vNode.dom) {
394
+ if (renderMode.current === "hydrate") {
395
+ hydrateDom(vNode);
428
396
  }
429
- // text should _never_ have children
430
- if (vNode.type !== "#text") {
431
- vNode.child = reconcileChildren(vNode, props.children);
432
- vNode.deletions?.forEach((d) => queueDelete(d));
397
+ else {
398
+ vNode.dom = createDom(vNode);
399
+ }
400
+ if (__DEV__) {
401
+ if (vNode.dom instanceof Element) {
402
+ vNode.dom.__kiruNode = vNode;
403
+ }
433
404
  }
405
+ }
406
+ // text should _never_ have children
407
+ if (type !== "#text") {
408
+ vNode.child = reconcileChildren(vNode, props.children);
409
+ queueNodeChildDeletions(vNode);
434
410
  if (vNode.child && renderMode.current === "hydrate") {
435
411
  hydrationStack.push(vNode.dom);
436
412
  }
437
413
  }
438
- function checkForTooManyConsecutiveDirtyRenders() {
439
- if (consecutiveDirtyCount > CONSECUTIVE_DIRTY_LIMIT) {
440
- throw new KiruError("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.");
441
- }
414
+ }
415
+ function queueNodeChildDeletions(vNode) {
416
+ if (vNode.deletions) {
417
+ vNode.deletions.forEach(queueDelete);
418
+ vNode.deletions = null;
442
419
  }
443
- function flushEffects(effectArr) {
444
- while (effectArr.length)
445
- effectArr.shift()();
420
+ }
421
+ function checkForTooManyConsecutiveDirtyRenders() {
422
+ if (consecutiveDirtyCount > CONSECUTIVE_DIRTY_LIMIT) {
423
+ throw new KiruError("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.");
446
424
  }
447
- return (scheduler = {
448
- clear,
449
- wake,
450
- sleep,
451
- nextIdle,
452
- flushSync,
453
- queueUpdate,
454
- queueDelete,
455
- });
425
+ }
426
+ function flushEffects(effectArr) {
427
+ for (let i = 0; i < effectArr.length; i++) {
428
+ effectArr[i]();
429
+ }
430
+ effectArr.length = 0;
456
431
  }
457
432
  //# sourceMappingURL=scheduler.js.map