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/portal.ts
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { __DEV__ } from "./env.js"
|
|
2
|
+
import { KaiokenError } from "./error.js"
|
|
3
|
+
import { renderMode } from "./globals.js"
|
|
4
|
+
import { useVNode } from "./hooks/utils.js"
|
|
5
|
+
import { getVNodeAppContext } from "./utils.js"
|
|
6
|
+
|
|
7
|
+
export { Portal, isPortal }
|
|
8
|
+
|
|
9
|
+
type PortalProps = {
|
|
10
|
+
children?: JSX.Children
|
|
11
|
+
container: HTMLElement | (() => HTMLElement)
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function Portal({ children, container }: PortalProps) {
|
|
15
|
+
const vNode = useVNode()
|
|
16
|
+
switch (renderMode.current) {
|
|
17
|
+
case "dom":
|
|
18
|
+
vNode.dom = typeof container === "function" ? container() : container
|
|
19
|
+
if (!(vNode.dom instanceof HTMLElement)) {
|
|
20
|
+
if (__DEV__) {
|
|
21
|
+
throw new KaiokenError({
|
|
22
|
+
message: `Invalid portal container, expected HTMLElement, got ${vNode.dom}`,
|
|
23
|
+
vNode: vNode,
|
|
24
|
+
})
|
|
25
|
+
}
|
|
26
|
+
return null
|
|
27
|
+
}
|
|
28
|
+
return children
|
|
29
|
+
case "hydrate":
|
|
30
|
+
const ctx = getVNodeAppContext(vNode)
|
|
31
|
+
ctx.scheduler?.nextIdle(() => ctx.requestUpdate(vNode))
|
|
32
|
+
return null
|
|
33
|
+
case "stream":
|
|
34
|
+
case "string":
|
|
35
|
+
return null
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function isPortal(
|
|
40
|
+
node: Kaioken.VNode
|
|
41
|
+
): node is Kaioken.VNode & { type: typeof Portal } {
|
|
42
|
+
return node.type === Portal
|
|
43
|
+
}
|
package/src/profiling.ts
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import type { AppContext } from "./appContext"
|
|
2
|
+
|
|
3
|
+
const MAX_TICKS = 100
|
|
4
|
+
|
|
5
|
+
const ProfilingEvents = [
|
|
6
|
+
"updateNode",
|
|
7
|
+
"createNode",
|
|
8
|
+
"removeNode",
|
|
9
|
+
"update",
|
|
10
|
+
"updateDirtied",
|
|
11
|
+
"signalTextUpdate",
|
|
12
|
+
"signalAttrUpdate",
|
|
13
|
+
] as const
|
|
14
|
+
|
|
15
|
+
export type ProfilingEvent = (typeof ProfilingEvents)[number]
|
|
16
|
+
|
|
17
|
+
type TickTS = {
|
|
18
|
+
start: number
|
|
19
|
+
end: number
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
type ProfilingEventListener = (app: AppContext) => void
|
|
23
|
+
|
|
24
|
+
export function createProfilingContext() {
|
|
25
|
+
const eventListeners = new Map<ProfilingEvent, Set<ProfilingEventListener>>()
|
|
26
|
+
const appStats: Map<
|
|
27
|
+
AppContext,
|
|
28
|
+
{
|
|
29
|
+
timestamps: TickTS[]
|
|
30
|
+
mountDuration: number
|
|
31
|
+
totalTicks: number
|
|
32
|
+
}
|
|
33
|
+
> = new Map()
|
|
34
|
+
return {
|
|
35
|
+
appStats,
|
|
36
|
+
emit: (event: ProfilingEvent, app: AppContext) => {
|
|
37
|
+
eventListeners.get(event)?.forEach((listener) => listener(app))
|
|
38
|
+
},
|
|
39
|
+
addEventListener: (
|
|
40
|
+
event: ProfilingEvent,
|
|
41
|
+
listener: ProfilingEventListener
|
|
42
|
+
) => {
|
|
43
|
+
if (!eventListeners.has(event)) {
|
|
44
|
+
eventListeners.set(event, new Set())
|
|
45
|
+
}
|
|
46
|
+
eventListeners.get(event)!.add(listener)
|
|
47
|
+
},
|
|
48
|
+
removeEventListener: (
|
|
49
|
+
event: ProfilingEvent,
|
|
50
|
+
listener: ProfilingEventListener
|
|
51
|
+
) => {
|
|
52
|
+
if (!eventListeners.has(event)) return
|
|
53
|
+
eventListeners.get(event)!.delete(listener)
|
|
54
|
+
},
|
|
55
|
+
mountDuration: (app: AppContext) => {
|
|
56
|
+
const stats = appStats.get(app)
|
|
57
|
+
if (!stats) return 0
|
|
58
|
+
return stats.mountDuration
|
|
59
|
+
},
|
|
60
|
+
totalTicks: (app: AppContext) => {
|
|
61
|
+
const stats = appStats.get(app)
|
|
62
|
+
if (!stats) return 0
|
|
63
|
+
return stats.totalTicks
|
|
64
|
+
},
|
|
65
|
+
lastTickDuration: (app: AppContext) => {
|
|
66
|
+
const stats = appStats.get(app)
|
|
67
|
+
if (!stats) return 0
|
|
68
|
+
const last = stats.timestamps[stats.timestamps.length - 1]
|
|
69
|
+
return last.end - last.start
|
|
70
|
+
},
|
|
71
|
+
averageTickDuration: (app: AppContext) => {
|
|
72
|
+
const stats = appStats.get(app)
|
|
73
|
+
if (!stats) return 0
|
|
74
|
+
const completeTicks = stats.timestamps.filter((ts) => ts.end !== Infinity)
|
|
75
|
+
return (
|
|
76
|
+
completeTicks.reduce((a, b) => a + (b.end - b.start), 0) /
|
|
77
|
+
completeTicks.length
|
|
78
|
+
)
|
|
79
|
+
},
|
|
80
|
+
beginTick: (app: AppContext) => {
|
|
81
|
+
if (!appStats.has(app)) {
|
|
82
|
+
appStats.set(app, {
|
|
83
|
+
mountDuration: Infinity,
|
|
84
|
+
timestamps: [],
|
|
85
|
+
totalTicks: 0,
|
|
86
|
+
})
|
|
87
|
+
}
|
|
88
|
+
const stats = appStats.get(app)!
|
|
89
|
+
stats.totalTicks++
|
|
90
|
+
stats.timestamps.push({ start: performance.now(), end: Infinity })
|
|
91
|
+
},
|
|
92
|
+
endTick: (app: AppContext) => {
|
|
93
|
+
if (!appStats.has(app)) return
|
|
94
|
+
const stats = appStats.get(app)!
|
|
95
|
+
|
|
96
|
+
const last = stats.timestamps[stats.timestamps.length - 1]
|
|
97
|
+
last.end = performance.now()
|
|
98
|
+
|
|
99
|
+
if (stats.mountDuration === Infinity) {
|
|
100
|
+
stats.mountDuration = last.end - last.start
|
|
101
|
+
}
|
|
102
|
+
if (stats.timestamps.length > MAX_TICKS) stats.timestamps.shift()
|
|
103
|
+
},
|
|
104
|
+
}
|
|
105
|
+
}
|
package/src/props.ts
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { KaiokenError } from "./error.js"
|
|
2
|
+
import { Signal } from "./signals/base.js"
|
|
3
|
+
|
|
4
|
+
export function assertValidElementProps(vNode: Kaioken.VNode) {
|
|
5
|
+
if ("children" in vNode.props && vNode.props.innerHTML) {
|
|
6
|
+
throw new KaiokenError({
|
|
7
|
+
message: "Cannot use both children and innerHTML on an element",
|
|
8
|
+
vNode,
|
|
9
|
+
})
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
for (const key in vNode.props) {
|
|
13
|
+
if ("bind:" + key in vNode.props) {
|
|
14
|
+
throw new KaiokenError({
|
|
15
|
+
message: `Cannot use both bind:${key} and ${key} on an element`,
|
|
16
|
+
vNode,
|
|
17
|
+
})
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function isValidElementKeyProp(
|
|
23
|
+
thing: unknown
|
|
24
|
+
): thing is string | number {
|
|
25
|
+
return typeof thing === "string" || typeof thing === "number"
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function isValidElementRefProp(
|
|
29
|
+
thing: unknown
|
|
30
|
+
): thing is Kaioken.Ref<any> {
|
|
31
|
+
return (
|
|
32
|
+
typeof thing === "function" ||
|
|
33
|
+
(typeof thing === "object" && !!thing && "current" in thing) ||
|
|
34
|
+
Signal.isSignal(thing)
|
|
35
|
+
)
|
|
36
|
+
}
|
|
@@ -0,0 +1,531 @@
|
|
|
1
|
+
import { FLAG, $FRAGMENT } from "./constants.js"
|
|
2
|
+
import { isVNode, latest } from "./utils.js"
|
|
3
|
+
import { Signal } from "./signals/base.js"
|
|
4
|
+
import { __DEV__ } from "./env.js"
|
|
5
|
+
import { createElement, Fragment } from "./element.js"
|
|
6
|
+
import { flags } from "./flags.js"
|
|
7
|
+
import { ctx } from "./globals.js"
|
|
8
|
+
|
|
9
|
+
type VNode = Kaioken.VNode
|
|
10
|
+
|
|
11
|
+
export function reconcileChildren(parent: VNode, children: unknown) {
|
|
12
|
+
if (Array.isArray(children)) {
|
|
13
|
+
if (__DEV__) {
|
|
14
|
+
// array children are 'tagged' during parent reconciliation pass
|
|
15
|
+
if ($LIST_CHILD in children) {
|
|
16
|
+
checkForMissingKeys(parent, children)
|
|
17
|
+
}
|
|
18
|
+
checkForDuplicateKeys(parent, children)
|
|
19
|
+
}
|
|
20
|
+
return reconcileChildrenArray(parent, children)
|
|
21
|
+
}
|
|
22
|
+
return reconcileSingleChild(parent, children)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function reconcileSingleChild(parent: VNode, child: unknown) {
|
|
26
|
+
const deletions: VNode[] = (parent.deletions = [])
|
|
27
|
+
const oldChild = parent.child
|
|
28
|
+
if (oldChild === null) {
|
|
29
|
+
return createChild(parent, child)
|
|
30
|
+
}
|
|
31
|
+
const oldSibling = oldChild.sibling
|
|
32
|
+
const newNode = updateSlot(parent, oldChild, child)
|
|
33
|
+
if (newNode !== null) {
|
|
34
|
+
if (oldChild && oldChild !== newNode && !newNode.prev) {
|
|
35
|
+
deleteRemainingChildren(parent, oldChild)
|
|
36
|
+
} else if (oldSibling) {
|
|
37
|
+
deleteRemainingChildren(parent, oldSibling)
|
|
38
|
+
}
|
|
39
|
+
return newNode
|
|
40
|
+
}
|
|
41
|
+
{
|
|
42
|
+
// handle keyed children array -> keyed child
|
|
43
|
+
const existingChildren = mapRemainingChildren(oldChild)
|
|
44
|
+
const newNode = updateFromMap(existingChildren, parent, 0, child)
|
|
45
|
+
if (newNode !== null) {
|
|
46
|
+
if (newNode.prev !== null) {
|
|
47
|
+
// node persisted, remove it from the list so it doesn't get deleted
|
|
48
|
+
existingChildren.delete(
|
|
49
|
+
newNode.prev.props.key === undefined
|
|
50
|
+
? newNode.prev.index
|
|
51
|
+
: newNode.prev.props.key
|
|
52
|
+
)
|
|
53
|
+
}
|
|
54
|
+
placeChild(newNode, 0, 0)
|
|
55
|
+
}
|
|
56
|
+
existingChildren.forEach((child) => deletions.push(child))
|
|
57
|
+
return newNode
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function reconcileChildrenArray(parent: VNode, children: unknown[]) {
|
|
62
|
+
const deletions: VNode[] = (parent.deletions = [])
|
|
63
|
+
let resultingChild: VNode | null = null
|
|
64
|
+
let prevNewChild: VNode | null = null
|
|
65
|
+
|
|
66
|
+
let oldChild = parent.child
|
|
67
|
+
let nextOldChild = null
|
|
68
|
+
let lastPlacedIndex = 0
|
|
69
|
+
let newIdx = 0
|
|
70
|
+
|
|
71
|
+
for (; !!oldChild && newIdx < children.length; newIdx++) {
|
|
72
|
+
if (oldChild.index > newIdx) {
|
|
73
|
+
nextOldChild = oldChild
|
|
74
|
+
oldChild = null
|
|
75
|
+
} else {
|
|
76
|
+
nextOldChild = oldChild.sibling
|
|
77
|
+
}
|
|
78
|
+
const newChild = updateSlot(parent, oldChild, children[newIdx])
|
|
79
|
+
if (newChild === null) {
|
|
80
|
+
if (oldChild === null) {
|
|
81
|
+
oldChild = nextOldChild
|
|
82
|
+
}
|
|
83
|
+
break
|
|
84
|
+
}
|
|
85
|
+
if (oldChild && !newChild.prev) {
|
|
86
|
+
deletions.push(oldChild)
|
|
87
|
+
}
|
|
88
|
+
lastPlacedIndex = placeChild(newChild, lastPlacedIndex, newIdx)
|
|
89
|
+
if (prevNewChild === null) {
|
|
90
|
+
resultingChild = newChild
|
|
91
|
+
} else {
|
|
92
|
+
prevNewChild.sibling = newChild
|
|
93
|
+
}
|
|
94
|
+
prevNewChild = newChild
|
|
95
|
+
oldChild = nextOldChild
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// matched all children?
|
|
99
|
+
if (newIdx === children.length) {
|
|
100
|
+
deleteRemainingChildren(parent, oldChild)
|
|
101
|
+
return resultingChild
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// just some good ol' insertions, baby
|
|
105
|
+
if (oldChild === null) {
|
|
106
|
+
for (; newIdx < children.length; newIdx++) {
|
|
107
|
+
const newNode = createChild(parent, children[newIdx])
|
|
108
|
+
if (newNode === null) continue
|
|
109
|
+
lastPlacedIndex = placeChild(newNode, lastPlacedIndex, newIdx)
|
|
110
|
+
if (prevNewChild === null) {
|
|
111
|
+
resultingChild = newNode
|
|
112
|
+
} else {
|
|
113
|
+
prevNewChild.sibling = newNode
|
|
114
|
+
}
|
|
115
|
+
prevNewChild = newNode
|
|
116
|
+
}
|
|
117
|
+
return resultingChild
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// deal with mismatched keys / unmatched children
|
|
121
|
+
const existingChildren = mapRemainingChildren(oldChild)
|
|
122
|
+
|
|
123
|
+
for (; newIdx < children.length; newIdx++) {
|
|
124
|
+
const newNode = updateFromMap(
|
|
125
|
+
existingChildren,
|
|
126
|
+
parent,
|
|
127
|
+
newIdx,
|
|
128
|
+
children[newIdx]
|
|
129
|
+
)
|
|
130
|
+
if (newNode !== null) {
|
|
131
|
+
if (newNode.prev !== null) {
|
|
132
|
+
// node persisted, remove it from the list so it doesn't get deleted
|
|
133
|
+
existingChildren.delete(
|
|
134
|
+
newNode.prev.props.key === undefined
|
|
135
|
+
? newNode.prev.index
|
|
136
|
+
: newNode.prev.props.key
|
|
137
|
+
)
|
|
138
|
+
}
|
|
139
|
+
lastPlacedIndex = placeChild(newNode, lastPlacedIndex, newIdx)
|
|
140
|
+
if (prevNewChild === null) {
|
|
141
|
+
resultingChild = newNode
|
|
142
|
+
} else {
|
|
143
|
+
prevNewChild.sibling = newNode
|
|
144
|
+
}
|
|
145
|
+
prevNewChild = newNode
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
existingChildren.forEach((child) => deletions.push(child))
|
|
150
|
+
return resultingChild
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function updateSlot(parent: VNode, oldChild: VNode | null, child: unknown) {
|
|
154
|
+
// Update the node if the keys match, otherwise return null.
|
|
155
|
+
const key = oldChild?.props.key
|
|
156
|
+
if (
|
|
157
|
+
(typeof child === "string" && child !== "") ||
|
|
158
|
+
typeof child === "number" ||
|
|
159
|
+
typeof child === "bigint"
|
|
160
|
+
) {
|
|
161
|
+
if (key !== undefined) return null
|
|
162
|
+
if (
|
|
163
|
+
oldChild?.type === "#text" &&
|
|
164
|
+
Signal.isSignal(oldChild.props.nodeValue)
|
|
165
|
+
) {
|
|
166
|
+
return null
|
|
167
|
+
}
|
|
168
|
+
return updateTextNode(parent, oldChild, "" + child)
|
|
169
|
+
}
|
|
170
|
+
if (Signal.isSignal(child)) {
|
|
171
|
+
if (!!oldChild && oldChild.props.nodeValue !== child) return null
|
|
172
|
+
return updateTextNode(parent, oldChild, child)
|
|
173
|
+
}
|
|
174
|
+
if (isVNode(child)) {
|
|
175
|
+
if (child.props.key !== key) return null
|
|
176
|
+
return updateNode(parent, oldChild, child)
|
|
177
|
+
}
|
|
178
|
+
if (Array.isArray(child)) {
|
|
179
|
+
if (key !== undefined) return null
|
|
180
|
+
if (__DEV__) {
|
|
181
|
+
markListChild(child)
|
|
182
|
+
}
|
|
183
|
+
return updateFragment(parent, oldChild, child)
|
|
184
|
+
}
|
|
185
|
+
return null
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function updateTextNode(
|
|
189
|
+
parent: VNode,
|
|
190
|
+
oldChild: VNode | null,
|
|
191
|
+
content: string | Signal<JSX.PrimitiveChild>
|
|
192
|
+
) {
|
|
193
|
+
if (oldChild === null || oldChild.type !== "#text") {
|
|
194
|
+
if (__DEV__) {
|
|
195
|
+
emitCreateNode()
|
|
196
|
+
}
|
|
197
|
+
const newChild = createElement("#text", { nodeValue: content })
|
|
198
|
+
setParent(newChild, parent)
|
|
199
|
+
return newChild
|
|
200
|
+
} else {
|
|
201
|
+
if (__DEV__) {
|
|
202
|
+
emitUpdateNode()
|
|
203
|
+
}
|
|
204
|
+
oldChild.props.nodeValue = content
|
|
205
|
+
oldChild.flags = flags.set(oldChild.flags, FLAG.UPDATE)
|
|
206
|
+
oldChild.sibling = null
|
|
207
|
+
return oldChild
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function updateNode(parent: VNode, oldChild: VNode | null, newChild: VNode) {
|
|
212
|
+
let nodeType = newChild.type
|
|
213
|
+
if (__DEV__) {
|
|
214
|
+
if (typeof nodeType === "function") {
|
|
215
|
+
nodeType = latest(nodeType)
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
if (nodeType === $FRAGMENT) {
|
|
219
|
+
return updateFragment(
|
|
220
|
+
parent,
|
|
221
|
+
oldChild,
|
|
222
|
+
(newChild.props.children as VNode[]) || [],
|
|
223
|
+
newChild.props
|
|
224
|
+
)
|
|
225
|
+
}
|
|
226
|
+
if (oldChild?.type === nodeType) {
|
|
227
|
+
if (__DEV__) {
|
|
228
|
+
emitUpdateNode()
|
|
229
|
+
}
|
|
230
|
+
oldChild.index = 0
|
|
231
|
+
oldChild.props = newChild.props
|
|
232
|
+
oldChild.sibling = null
|
|
233
|
+
oldChild.flags = flags.set(oldChild.flags, FLAG.UPDATE)
|
|
234
|
+
oldChild.memoizedProps = newChild.memoizedProps
|
|
235
|
+
return oldChild
|
|
236
|
+
}
|
|
237
|
+
if (__DEV__) {
|
|
238
|
+
emitCreateNode()
|
|
239
|
+
}
|
|
240
|
+
const created = createElement(nodeType, newChild.props)
|
|
241
|
+
setParent(created, parent)
|
|
242
|
+
return created
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function updateFragment(
|
|
246
|
+
parent: VNode,
|
|
247
|
+
oldChild: VNode | null,
|
|
248
|
+
children: unknown[],
|
|
249
|
+
newProps = {}
|
|
250
|
+
) {
|
|
251
|
+
if (oldChild === null || oldChild.type !== $FRAGMENT) {
|
|
252
|
+
if (__DEV__) {
|
|
253
|
+
emitCreateNode()
|
|
254
|
+
}
|
|
255
|
+
const el = createElement($FRAGMENT, { children, ...newProps })
|
|
256
|
+
setParent(el, parent)
|
|
257
|
+
return el
|
|
258
|
+
}
|
|
259
|
+
if (__DEV__) {
|
|
260
|
+
emitUpdateNode()
|
|
261
|
+
}
|
|
262
|
+
oldChild.props = { ...oldChild.props, ...newProps, children }
|
|
263
|
+
oldChild.flags = flags.set(oldChild.flags, FLAG.UPDATE)
|
|
264
|
+
oldChild.sibling = null
|
|
265
|
+
return oldChild
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
function createChild(parent: VNode, child: unknown): VNode | null {
|
|
269
|
+
if (
|
|
270
|
+
(typeof child === "string" && child !== "") ||
|
|
271
|
+
typeof child === "number" ||
|
|
272
|
+
typeof child === "bigint"
|
|
273
|
+
) {
|
|
274
|
+
if (__DEV__) {
|
|
275
|
+
emitCreateNode()
|
|
276
|
+
}
|
|
277
|
+
const el = createElement("#text", {
|
|
278
|
+
nodeValue: "" + child,
|
|
279
|
+
})
|
|
280
|
+
setParent(el, parent)
|
|
281
|
+
return el
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
if (Signal.isSignal(child)) {
|
|
285
|
+
if (__DEV__) {
|
|
286
|
+
emitCreateNode()
|
|
287
|
+
}
|
|
288
|
+
const el = createElement("#text", {
|
|
289
|
+
nodeValue: child,
|
|
290
|
+
})
|
|
291
|
+
setParent(el, parent)
|
|
292
|
+
return el
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
if (isVNode(child)) {
|
|
296
|
+
if (__DEV__) {
|
|
297
|
+
emitCreateNode()
|
|
298
|
+
}
|
|
299
|
+
const newNode = createElement(child.type, child.props)
|
|
300
|
+
setParent(newNode, parent)
|
|
301
|
+
newNode.flags = flags.set(newNode.flags, FLAG.PLACEMENT)
|
|
302
|
+
return newNode
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
if (Array.isArray(child)) {
|
|
306
|
+
if (__DEV__) {
|
|
307
|
+
emitCreateNode()
|
|
308
|
+
markListChild(child)
|
|
309
|
+
}
|
|
310
|
+
const el = Fragment({ children: child })
|
|
311
|
+
setParent(el, parent)
|
|
312
|
+
return el
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
return null
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
function placeChild(
|
|
319
|
+
child: VNode | null,
|
|
320
|
+
lastPlacedIndex: number,
|
|
321
|
+
newIndex: number
|
|
322
|
+
): number {
|
|
323
|
+
if (child === null) return lastPlacedIndex
|
|
324
|
+
child.index = newIndex
|
|
325
|
+
if (child.prev !== null) {
|
|
326
|
+
const oldIndex = child.prev.index
|
|
327
|
+
if (oldIndex < lastPlacedIndex) {
|
|
328
|
+
child.flags = flags.set(child.flags, FLAG.PLACEMENT)
|
|
329
|
+
return lastPlacedIndex
|
|
330
|
+
} else {
|
|
331
|
+
return oldIndex
|
|
332
|
+
}
|
|
333
|
+
} else {
|
|
334
|
+
child.flags = flags.set(child.flags, FLAG.PLACEMENT)
|
|
335
|
+
return lastPlacedIndex
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
function updateFromMap(
|
|
340
|
+
existingChildren: Map<JSX.ElementKey, VNode>,
|
|
341
|
+
parent: VNode,
|
|
342
|
+
index: number,
|
|
343
|
+
child: any
|
|
344
|
+
): VNode | null {
|
|
345
|
+
const isSig = Signal.isSignal(child)
|
|
346
|
+
if (
|
|
347
|
+
isSig ||
|
|
348
|
+
(typeof child === "string" && child !== "") ||
|
|
349
|
+
typeof child === "number" ||
|
|
350
|
+
typeof child === "bigint"
|
|
351
|
+
) {
|
|
352
|
+
const oldChild = existingChildren.get(index)
|
|
353
|
+
if (oldChild) {
|
|
354
|
+
if (oldChild.props.nodeValue === child) {
|
|
355
|
+
oldChild.flags = flags.set(oldChild.flags, FLAG.UPDATE)
|
|
356
|
+
oldChild.props.nodeValue = child
|
|
357
|
+
return oldChild
|
|
358
|
+
}
|
|
359
|
+
if (
|
|
360
|
+
oldChild.type === "#text" &&
|
|
361
|
+
Signal.isSignal(oldChild.props.nodeValue)
|
|
362
|
+
) {
|
|
363
|
+
oldChild.cleanups?.["nodeValue"]?.()
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
if (__DEV__) {
|
|
368
|
+
emitCreateNode()
|
|
369
|
+
}
|
|
370
|
+
const newChild = createElement("#text", {
|
|
371
|
+
nodeValue: child,
|
|
372
|
+
})
|
|
373
|
+
setParent(newChild, parent)
|
|
374
|
+
newChild.flags = flags.set(newChild.flags, FLAG.PLACEMENT)
|
|
375
|
+
newChild.index = index
|
|
376
|
+
return newChild
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
if (isVNode(child)) {
|
|
380
|
+
const oldChild = existingChildren.get(
|
|
381
|
+
child.props.key === undefined ? index : child.props.key
|
|
382
|
+
)
|
|
383
|
+
if (oldChild?.type === child.type) {
|
|
384
|
+
if (__DEV__) {
|
|
385
|
+
emitUpdateNode()
|
|
386
|
+
}
|
|
387
|
+
oldChild.flags = flags.set(oldChild.flags, FLAG.UPDATE)
|
|
388
|
+
oldChild.props = child.props
|
|
389
|
+
oldChild.sibling = null
|
|
390
|
+
oldChild.index = index
|
|
391
|
+
return oldChild
|
|
392
|
+
} else {
|
|
393
|
+
if (__DEV__) {
|
|
394
|
+
emitCreateNode()
|
|
395
|
+
}
|
|
396
|
+
const newChild = createElement(child.type, child.props)
|
|
397
|
+
setParent(newChild, parent)
|
|
398
|
+
newChild.flags = flags.set(newChild.flags, FLAG.PLACEMENT)
|
|
399
|
+
newChild.index = index
|
|
400
|
+
return newChild
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
if (Array.isArray(child)) {
|
|
405
|
+
const oldChild = existingChildren.get(index)
|
|
406
|
+
if (__DEV__) {
|
|
407
|
+
markListChild(child)
|
|
408
|
+
}
|
|
409
|
+
if (oldChild) {
|
|
410
|
+
if (__DEV__) {
|
|
411
|
+
emitUpdateNode()
|
|
412
|
+
}
|
|
413
|
+
oldChild.flags = flags.set(oldChild.flags, FLAG.UPDATE)
|
|
414
|
+
oldChild.props.children = child
|
|
415
|
+
return oldChild
|
|
416
|
+
} else {
|
|
417
|
+
if (__DEV__) {
|
|
418
|
+
emitCreateNode()
|
|
419
|
+
}
|
|
420
|
+
const newChild = Fragment({ children: child })
|
|
421
|
+
setParent(newChild, parent)
|
|
422
|
+
newChild.flags = flags.set(newChild.flags, FLAG.PLACEMENT)
|
|
423
|
+
newChild.index = index
|
|
424
|
+
return newChild
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
return null
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
function setParent(child: Kaioken.VNode, parent: Kaioken.VNode) {
|
|
432
|
+
child.parent = parent
|
|
433
|
+
child.depth = parent.depth + 1
|
|
434
|
+
if (parent.isMemoized || flags.get(parent.flags, FLAG.HAS_MEMO_ANCESTOR)) {
|
|
435
|
+
child.flags = flags.set(child.flags, FLAG.HAS_MEMO_ANCESTOR)
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
function emitUpdateNode() {
|
|
440
|
+
if (!("window" in globalThis)) return
|
|
441
|
+
window.__kaioken?.profilingContext?.emit("updateNode", ctx.current)
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
function emitCreateNode() {
|
|
445
|
+
if (!("window" in globalThis)) return
|
|
446
|
+
window.__kaioken?.profilingContext?.emit("createNode", ctx.current)
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
const $LIST_CHILD = Symbol("kaioken:marked-list-child")
|
|
450
|
+
function markListChild(children: unknown[]) {
|
|
451
|
+
Object.assign(children, { [$LIST_CHILD]: true })
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
function mapRemainingChildren(child: VNode | null) {
|
|
455
|
+
const map: Map<JSX.ElementKey, VNode> = new Map()
|
|
456
|
+
while (child) {
|
|
457
|
+
map.set(
|
|
458
|
+
child.props.key === undefined ? child.index : child.props.key,
|
|
459
|
+
child
|
|
460
|
+
)
|
|
461
|
+
child = child.sibling
|
|
462
|
+
}
|
|
463
|
+
return map
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
function deleteRemainingChildren(parent: VNode, child: VNode | null) {
|
|
467
|
+
while (child) {
|
|
468
|
+
parent.deletions!.push(child)
|
|
469
|
+
child = child.sibling
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
function checkForDuplicateKeys(parent: VNode, children: unknown[]) {
|
|
474
|
+
const keys = new Set<string>()
|
|
475
|
+
let warned = false
|
|
476
|
+
for (const child of children) {
|
|
477
|
+
if (!isVNode(child)) continue
|
|
478
|
+
const key = child.props.key
|
|
479
|
+
if (typeof key === "string") {
|
|
480
|
+
if (!warned && keys.has(key)) {
|
|
481
|
+
const fn = getNearestParentFcTag(parent)
|
|
482
|
+
keyWarning(
|
|
483
|
+
`${fn} component produced a child in a list with a duplicate key prop: "${key}". Keys should be unique so that components maintain their identity across updates`
|
|
484
|
+
)
|
|
485
|
+
warned = true
|
|
486
|
+
}
|
|
487
|
+
keys.add(key)
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
function checkForMissingKeys(parent: VNode, children: unknown[]) {
|
|
493
|
+
let hasKey = false
|
|
494
|
+
let hasMissingKey = false
|
|
495
|
+
for (const child of children) {
|
|
496
|
+
if (!isVNode(child)) continue
|
|
497
|
+
const key = child.props.key
|
|
498
|
+
if (typeof key === "string") {
|
|
499
|
+
hasKey = true
|
|
500
|
+
} else {
|
|
501
|
+
hasMissingKey = true
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
if (hasMissingKey && hasKey) {
|
|
505
|
+
const fn = getNearestParentFcTag(parent)
|
|
506
|
+
keyWarning(
|
|
507
|
+
`${fn} component produced a child in a list without a valid key prop`
|
|
508
|
+
)
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
function keyWarning(str: string) {
|
|
513
|
+
const formatted = `[kaioken]: ${str}. See https://kaioken.dev/keys-warning for more information.`
|
|
514
|
+
console.error(formatted)
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
const parentFcTagLookups = new WeakMap<VNode, string>()
|
|
518
|
+
function getNearestParentFcTag(vNode: VNode) {
|
|
519
|
+
if (parentFcTagLookups.has(vNode)) {
|
|
520
|
+
return parentFcTagLookups.get(vNode)
|
|
521
|
+
}
|
|
522
|
+
let p: VNode | null = vNode.parent
|
|
523
|
+
let fn: (Function & { displayName?: string }) | undefined
|
|
524
|
+
while (!fn && p) {
|
|
525
|
+
if (typeof p.type === "function") fn = p.type
|
|
526
|
+
p = p.parent
|
|
527
|
+
}
|
|
528
|
+
const tag = `<${fn?.displayName || fn?.name || "Anonymous Function"} />`
|
|
529
|
+
parentFcTagLookups.set(vNode, tag)
|
|
530
|
+
return tag
|
|
531
|
+
}
|