kiru 0.44.4
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/LICENSE +7 -0
- package/README.md +5 -0
- package/package.json +81 -0
- package/src/appContext.ts +186 -0
- package/src/cloneVNode.ts +14 -0
- package/src/constants.ts +146 -0
- package/src/context.ts +56 -0
- package/src/dom.ts +712 -0
- package/src/element.ts +54 -0
- package/src/env.ts +6 -0
- package/src/error.ts +85 -0
- package/src/flags.ts +15 -0
- package/src/form/index.ts +662 -0
- package/src/form/types.ts +261 -0
- package/src/form/utils.ts +19 -0
- package/src/generateId.ts +19 -0
- package/src/globalContext.ts +161 -0
- package/src/globals.ts +21 -0
- package/src/hmr.ts +178 -0
- package/src/hooks/index.ts +14 -0
- package/src/hooks/useAsync.ts +136 -0
- package/src/hooks/useCallback.ts +31 -0
- package/src/hooks/useContext.ts +79 -0
- package/src/hooks/useEffect.ts +44 -0
- package/src/hooks/useEffectEvent.ts +24 -0
- package/src/hooks/useId.ts +42 -0
- package/src/hooks/useLayoutEffect.ts +47 -0
- package/src/hooks/useMemo.ts +33 -0
- package/src/hooks/useReducer.ts +50 -0
- package/src/hooks/useRef.ts +40 -0
- package/src/hooks/useState.ts +62 -0
- package/src/hooks/useSyncExternalStore.ts +59 -0
- package/src/hooks/useViewTransition.ts +26 -0
- package/src/hooks/utils.ts +259 -0
- package/src/hydration.ts +67 -0
- package/src/index.ts +61 -0
- package/src/jsx.ts +11 -0
- package/src/lazy.ts +238 -0
- package/src/memo.ts +48 -0
- package/src/portal.ts +43 -0
- package/src/profiling.ts +105 -0
- package/src/props.ts +36 -0
- package/src/reconciler.ts +531 -0
- package/src/renderToString.ts +91 -0
- package/src/router/index.ts +2 -0
- package/src/router/route.ts +51 -0
- package/src/router/router.ts +275 -0
- package/src/router/routerUtils.ts +49 -0
- package/src/scheduler.ts +522 -0
- package/src/signals/base.ts +237 -0
- package/src/signals/computed.ts +139 -0
- package/src/signals/effect.ts +60 -0
- package/src/signals/globals.ts +11 -0
- package/src/signals/index.ts +12 -0
- package/src/signals/jsx.ts +45 -0
- package/src/signals/types.ts +10 -0
- package/src/signals/utils.ts +12 -0
- package/src/signals/watch.ts +151 -0
- package/src/ssr/client.ts +29 -0
- package/src/ssr/hydrationBoundary.ts +63 -0
- package/src/ssr/index.ts +1 -0
- package/src/ssr/server.ts +124 -0
- package/src/store.ts +241 -0
- package/src/swr.ts +360 -0
- package/src/transition.ts +80 -0
- package/src/types.dom.ts +1250 -0
- package/src/types.ts +209 -0
- package/src/types.utils.ts +39 -0
- package/src/utils.ts +581 -0
- package/src/warning.ts +9 -0
package/src/scheduler.ts
ADDED
|
@@ -0,0 +1,522 @@
|
|
|
1
|
+
import type { AppContext } from "./appContext"
|
|
2
|
+
import type {
|
|
3
|
+
ContextProviderNode,
|
|
4
|
+
DomVNode,
|
|
5
|
+
FunctionVNode,
|
|
6
|
+
} from "./types.utils"
|
|
7
|
+
import { flags } from "./flags.js"
|
|
8
|
+
import {
|
|
9
|
+
$CONTEXT_PROVIDER,
|
|
10
|
+
CONSECUTIVE_DIRTY_LIMIT,
|
|
11
|
+
FLAG,
|
|
12
|
+
} from "./constants.js"
|
|
13
|
+
import { commitWork, createDom, hydrateDom } from "./dom.js"
|
|
14
|
+
import { __DEV__ } from "./env.js"
|
|
15
|
+
import { KaiokenError } from "./error.js"
|
|
16
|
+
import { ctx, hookIndex, node, nodeToCtxMap, renderMode } from "./globals.js"
|
|
17
|
+
import { hydrationStack } from "./hydration.js"
|
|
18
|
+
import { assertValidElementProps } from "./props.js"
|
|
19
|
+
import { reconcileChildren } from "./reconciler.js"
|
|
20
|
+
import {
|
|
21
|
+
willMemoBlockUpdate,
|
|
22
|
+
latest,
|
|
23
|
+
traverseApply,
|
|
24
|
+
vNodeContains,
|
|
25
|
+
isExoticType,
|
|
26
|
+
} from "./utils.js"
|
|
27
|
+
import { Signal } from "./signals/base.js"
|
|
28
|
+
|
|
29
|
+
type VNode = Kaioken.VNode
|
|
30
|
+
|
|
31
|
+
export interface Scheduler {
|
|
32
|
+
clear(): void
|
|
33
|
+
wake(): void
|
|
34
|
+
sleep(): void
|
|
35
|
+
nextIdle(fn: (scheduler: Scheduler) => void, wakeUpIfIdle?: boolean): void
|
|
36
|
+
flushSync(): void
|
|
37
|
+
queueUpdate(vNode: VNode): void
|
|
38
|
+
queueDelete(vNode: VNode): void
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function createScheduler(
|
|
42
|
+
appCtx: AppContext<any>,
|
|
43
|
+
maxFrameMs = 50
|
|
44
|
+
): Scheduler {
|
|
45
|
+
let nextUnitOfWork: VNode | null = null
|
|
46
|
+
let treesInProgress: VNode[] = []
|
|
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
|
|
64
|
+
|
|
65
|
+
const timeRemaining = () => frameDeadline - window.performance.now()
|
|
66
|
+
const deadline = {
|
|
67
|
+
didTimeout: false,
|
|
68
|
+
timeRemaining,
|
|
69
|
+
}
|
|
70
|
+
const channel = new MessageChannel()
|
|
71
|
+
channel.port2.onmessage = () => {
|
|
72
|
+
if (typeof pendingCallback === "function") {
|
|
73
|
+
pendingCallback(deadline)
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function clear() {
|
|
78
|
+
nextUnitOfWork = null
|
|
79
|
+
treesInProgress = []
|
|
80
|
+
currentTreeIndex = 0
|
|
81
|
+
nextIdleEffects = []
|
|
82
|
+
deletions = []
|
|
83
|
+
effectCallbacks = { pre: [], post: [] }
|
|
84
|
+
frameDeadline = 0
|
|
85
|
+
pendingCallback = null
|
|
86
|
+
sleep()
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function wake() {
|
|
90
|
+
if (isRunning) return
|
|
91
|
+
isRunning = true
|
|
92
|
+
requestIdleCallback(workLoop)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function sleep() {
|
|
96
|
+
isRunning = false
|
|
97
|
+
if (frameHandle !== null) {
|
|
98
|
+
globalThis.cancelAnimationFrame(frameHandle)
|
|
99
|
+
frameHandle = null
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function nextIdle(fn: (scheduler: Scheduler) => void, wakeUpIfIdle = true) {
|
|
104
|
+
nextIdleEffects.push(fn)
|
|
105
|
+
if (wakeUpIfIdle) wake()
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function flushSync() {
|
|
109
|
+
if (frameHandle !== null) {
|
|
110
|
+
globalThis.cancelAnimationFrame(frameHandle)
|
|
111
|
+
frameHandle = null
|
|
112
|
+
}
|
|
113
|
+
workLoop()
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function queueUpdate(vNode: VNode) {
|
|
117
|
+
// In immediate effect mode (useLayoutEffect), immediately mark the render as dirty
|
|
118
|
+
if (isImmediateEffectsMode) {
|
|
119
|
+
immediateEffectDirtiedRender = true
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// If this node is currently being rendered, just mark it dirty
|
|
123
|
+
if (node.current === vNode) {
|
|
124
|
+
if (__DEV__) {
|
|
125
|
+
window.__kaioken?.profilingContext?.emit("updateDirtied", appCtx)
|
|
126
|
+
}
|
|
127
|
+
isRenderDirtied = true
|
|
128
|
+
return
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// If it's already the next unit of work, no need to queue again
|
|
132
|
+
if (nextUnitOfWork === vNode) {
|
|
133
|
+
return
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (nextUnitOfWork === null) {
|
|
137
|
+
treesInProgress.push(vNode)
|
|
138
|
+
nextUnitOfWork = vNode
|
|
139
|
+
return wake()
|
|
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
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const nodeDepth = vNode.depth
|
|
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
|
+
}
|
|
176
|
+
|
|
177
|
+
return
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Check if this node contains any of the currently queued trees
|
|
181
|
+
let didReplaceTree = false
|
|
182
|
+
let shouldQueueAtEnd = false
|
|
183
|
+
for (let i = 0; i < treesInProgress.length; ) {
|
|
184
|
+
const tree = treesInProgress[i]
|
|
185
|
+
if (tree.depth < nodeDepth || !vNodeContains(vNode, tree)) {
|
|
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
|
|
209
|
+
} else {
|
|
210
|
+
// i > currentTreeIndex
|
|
211
|
+
treesInProgress.splice(i, 1)
|
|
212
|
+
if (!didReplaceTree) {
|
|
213
|
+
shouldQueueAtEnd = true
|
|
214
|
+
didReplaceTree = true
|
|
215
|
+
}
|
|
216
|
+
// no increment
|
|
217
|
+
}
|
|
218
|
+
}
|
|
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
|
+
}
|
|
225
|
+
|
|
226
|
+
function queueDelete(vNode: VNode) {
|
|
227
|
+
traverseApply(vNode, (n) => (n.flags = flags.set(n.flags, FLAG.DELETION)))
|
|
228
|
+
deletions.push(vNode)
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function isFlushReady() {
|
|
232
|
+
return !nextUnitOfWork && (deletions.length || treesInProgress.length)
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function workLoop(deadline?: IdleDeadline): void {
|
|
236
|
+
if (__DEV__) {
|
|
237
|
+
window.__kaioken?.profilingContext?.beginTick(appCtx)
|
|
238
|
+
}
|
|
239
|
+
ctx.current = appCtx
|
|
240
|
+
while (nextUnitOfWork) {
|
|
241
|
+
nextUnitOfWork =
|
|
242
|
+
performUnitOfWork(nextUnitOfWork) ??
|
|
243
|
+
treesInProgress[++currentTreeIndex] ??
|
|
244
|
+
queueBlockedContextDependencyRoots()
|
|
245
|
+
|
|
246
|
+
if ((deadline?.timeRemaining() ?? 1) < 1) break
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
if (isFlushReady()) {
|
|
250
|
+
while (deletions.length) {
|
|
251
|
+
commitWork(deletions.shift()!)
|
|
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.__kaioken?.profilingContext?.endTick(appCtx)
|
|
271
|
+
window.__kaioken?.profilingContext?.emit("updateDirtied", appCtx)
|
|
272
|
+
}
|
|
273
|
+
return workLoop()
|
|
274
|
+
}
|
|
275
|
+
consecutiveDirtyCount = 0
|
|
276
|
+
|
|
277
|
+
flushEffects(effectCallbacks.post)
|
|
278
|
+
window.__kaioken!.emit("update", appCtx)
|
|
279
|
+
if (__DEV__) {
|
|
280
|
+
window.__kaioken?.profilingContext?.emit("update", appCtx)
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
if (!nextUnitOfWork) {
|
|
285
|
+
sleep()
|
|
286
|
+
while (nextIdleEffects.length) {
|
|
287
|
+
nextIdleEffects.shift()!(scheduler)
|
|
288
|
+
}
|
|
289
|
+
if (__DEV__) {
|
|
290
|
+
window.__kaioken?.profilingContext?.endTick(appCtx)
|
|
291
|
+
}
|
|
292
|
+
return
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
requestIdleCallback(workLoop)
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
function requestIdleCallback(callback: IdleRequestCallback) {
|
|
299
|
+
frameHandle = globalThis.requestAnimationFrame((time) => {
|
|
300
|
+
frameDeadline = time + maxFrameMs
|
|
301
|
+
pendingCallback = callback
|
|
302
|
+
channel.port1.postMessage(null)
|
|
303
|
+
})
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
function queueBlockedContextDependencyRoots(): VNode | null {
|
|
307
|
+
if (pendingContextChanges.size === 0) return null
|
|
308
|
+
|
|
309
|
+
// TODO: it's possible that a 'job' created by this process is
|
|
310
|
+
// blocked by a parent memo after a queueUpdate -> replaceTree action.
|
|
311
|
+
// To prevent this, we might need to add these to a distinct queue.
|
|
312
|
+
const jobRoots: VNode[] = []
|
|
313
|
+
pendingContextChanges.forEach((provider) => {
|
|
314
|
+
provider.props.dependents.forEach((dep) => {
|
|
315
|
+
if (!willMemoBlockUpdate(provider, dep)) return
|
|
316
|
+
const depDepth = dep.depth
|
|
317
|
+
for (let i = 0; i < jobRoots.length; i++) {
|
|
318
|
+
const root = jobRoots[i]
|
|
319
|
+
const rootDepth = root.depth
|
|
320
|
+
if (depDepth > rootDepth && vNodeContains(root, dep)) {
|
|
321
|
+
if (willMemoBlockUpdate(root, dep)) {
|
|
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
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
jobRoots.push(dep)
|
|
333
|
+
})
|
|
334
|
+
})
|
|
335
|
+
|
|
336
|
+
pendingContextChanges.clear()
|
|
337
|
+
treesInProgress.push(...jobRoots)
|
|
338
|
+
return jobRoots[0] ?? null
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
function performUnitOfWork(vNode: VNode): VNode | void {
|
|
342
|
+
let renderChild = true
|
|
343
|
+
try {
|
|
344
|
+
const { props } = vNode
|
|
345
|
+
if (typeof vNode.type === "string") {
|
|
346
|
+
updateHostComponent(vNode as DomVNode)
|
|
347
|
+
} else if (isExoticType(vNode.type)) {
|
|
348
|
+
if (vNode.type === $CONTEXT_PROVIDER) {
|
|
349
|
+
const asProvider = vNode as ContextProviderNode<any>
|
|
350
|
+
const { dependents, value } = asProvider.props
|
|
351
|
+
if (
|
|
352
|
+
dependents.size &&
|
|
353
|
+
asProvider.prev &&
|
|
354
|
+
asProvider.prev.props.value !== value
|
|
355
|
+
) {
|
|
356
|
+
pendingContextChanges.add(asProvider)
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
vNode.child = reconcileChildren(vNode, props.children)
|
|
360
|
+
vNode.deletions?.forEach((d) => queueDelete(d))
|
|
361
|
+
} else {
|
|
362
|
+
renderChild = updateFunctionComponent(vNode as FunctionVNode)
|
|
363
|
+
}
|
|
364
|
+
} catch (error) {
|
|
365
|
+
window.__kaioken?.emit(
|
|
366
|
+
"error",
|
|
367
|
+
appCtx,
|
|
368
|
+
error instanceof Error ? error : new Error(String(error))
|
|
369
|
+
)
|
|
370
|
+
if (KaiokenError.isKaiokenError(error)) {
|
|
371
|
+
if (error.customNodeStack) {
|
|
372
|
+
setTimeout(() => {
|
|
373
|
+
throw new Error(error.customNodeStack)
|
|
374
|
+
})
|
|
375
|
+
}
|
|
376
|
+
if (error.fatal) {
|
|
377
|
+
throw error
|
|
378
|
+
}
|
|
379
|
+
console.error(error)
|
|
380
|
+
return
|
|
381
|
+
}
|
|
382
|
+
setTimeout(() => {
|
|
383
|
+
throw error
|
|
384
|
+
})
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
if (renderChild && vNode.child) {
|
|
388
|
+
return vNode.child
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
let nextNode: VNode | null = vNode
|
|
392
|
+
while (nextNode) {
|
|
393
|
+
// queue effects upon ascent
|
|
394
|
+
if (nextNode.immediateEffects) {
|
|
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
|
+
}
|
|
406
|
+
|
|
407
|
+
nextNode = nextNode.parent
|
|
408
|
+
if (renderMode.current === "hydrate" && nextNode?.dom) {
|
|
409
|
+
hydrationStack.pop()
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
function updateFunctionComponent(vNode: FunctionVNode) {
|
|
415
|
+
const { type, props, subs, prev, isMemoized } = vNode
|
|
416
|
+
if (isMemoized) {
|
|
417
|
+
vNode.memoizedProps = props
|
|
418
|
+
if (
|
|
419
|
+
prev?.memoizedProps &&
|
|
420
|
+
vNode.arePropsEqual!(prev.memoizedProps, props) &&
|
|
421
|
+
!vNode.hmrUpdated
|
|
422
|
+
) {
|
|
423
|
+
return false
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
try {
|
|
427
|
+
node.current = vNode
|
|
428
|
+
nodeToCtxMap.set(vNode, appCtx)
|
|
429
|
+
let newChild
|
|
430
|
+
let renderTryCount = 0
|
|
431
|
+
do {
|
|
432
|
+
isRenderDirtied = false
|
|
433
|
+
hookIndex.current = 0
|
|
434
|
+
|
|
435
|
+
/**
|
|
436
|
+
* remove previous signal subscriptions (if any) every render.
|
|
437
|
+
* this prevents no-longer-observed signals from triggering updates
|
|
438
|
+
* in components that are not currently using them.
|
|
439
|
+
*
|
|
440
|
+
* TODO: in future, we might be able to optimize this by
|
|
441
|
+
* only clearing the subscriptions that are no longer needed
|
|
442
|
+
* and not clearing the entire set.
|
|
443
|
+
*/
|
|
444
|
+
if (subs) {
|
|
445
|
+
for (const sub of subs) {
|
|
446
|
+
Signal.unsubscribe(vNode, sub)
|
|
447
|
+
}
|
|
448
|
+
subs.clear()
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
if (__DEV__) {
|
|
452
|
+
newChild = latest(type)(props)
|
|
453
|
+
delete vNode.hmrUpdated
|
|
454
|
+
if (++renderTryCount > CONSECUTIVE_DIRTY_LIMIT) {
|
|
455
|
+
throw new KaiokenError({
|
|
456
|
+
message:
|
|
457
|
+
"Too many re-renders. Kaioken limits the number of renders to prevent an infinite loop.",
|
|
458
|
+
fatal: true,
|
|
459
|
+
vNode,
|
|
460
|
+
})
|
|
461
|
+
}
|
|
462
|
+
continue
|
|
463
|
+
}
|
|
464
|
+
newChild = type(props)
|
|
465
|
+
} while (isRenderDirtied)
|
|
466
|
+
vNode.child = reconcileChildren(vNode, newChild)
|
|
467
|
+
vNode.deletions?.forEach((d) => queueDelete(d))
|
|
468
|
+
return true
|
|
469
|
+
} finally {
|
|
470
|
+
node.current = null
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
function updateHostComponent(vNode: DomVNode) {
|
|
475
|
+
const { props } = vNode
|
|
476
|
+
if (__DEV__) {
|
|
477
|
+
assertValidElementProps(vNode)
|
|
478
|
+
}
|
|
479
|
+
if (!vNode.dom) {
|
|
480
|
+
if (renderMode.current === "hydrate") {
|
|
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
|
|
487
|
+
vNode.dom.__kaiokenNode = vNode
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
// text should _never_ have children
|
|
491
|
+
if (vNode.type !== "#text") {
|
|
492
|
+
vNode.child = reconcileChildren(vNode, props.children)
|
|
493
|
+
vNode.deletions?.forEach((d) => queueDelete(d))
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
if (vNode.child && renderMode.current === "hydrate") {
|
|
497
|
+
hydrationStack.push(vNode.dom!)
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
function checkForTooManyConsecutiveDirtyRenders() {
|
|
502
|
+
if (consecutiveDirtyCount > CONSECUTIVE_DIRTY_LIMIT) {
|
|
503
|
+
throw new KaiokenError(
|
|
504
|
+
"Maximum update depth exceeded. This can happen when a component repeatedly calls setState during render or in useLayoutEffect. Kaioken limits the number of nested updates to prevent infinite loops."
|
|
505
|
+
)
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
function flushEffects(effectArr: Function[]) {
|
|
510
|
+
while (effectArr.length) effectArr.shift()!()
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
return (scheduler = {
|
|
514
|
+
clear,
|
|
515
|
+
wake,
|
|
516
|
+
sleep,
|
|
517
|
+
nextIdle,
|
|
518
|
+
flushSync,
|
|
519
|
+
queueUpdate,
|
|
520
|
+
queueDelete,
|
|
521
|
+
})
|
|
522
|
+
}
|