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