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