kiru 0.45.3 → 0.46.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (119) hide show
  1. package/dist/appContext.d.ts +0 -13
  2. package/dist/appContext.d.ts.map +1 -1
  3. package/dist/appContext.js +15 -55
  4. package/dist/appContext.js.map +1 -1
  5. package/dist/constants.d.ts +5 -7
  6. package/dist/constants.d.ts.map +1 -1
  7. package/dist/constants.js +5 -7
  8. package/dist/constants.js.map +1 -1
  9. package/dist/context.d.ts.map +1 -1
  10. package/dist/context.js +2 -4
  11. package/dist/context.js.map +1 -1
  12. package/dist/dom.d.ts +2 -2
  13. package/dist/dom.d.ts.map +1 -1
  14. package/dist/dom.js +36 -32
  15. package/dist/dom.js.map +1 -1
  16. package/dist/globals.d.ts +1 -6
  17. package/dist/globals.d.ts.map +1 -1
  18. package/dist/globals.js +1 -5
  19. package/dist/globals.js.map +1 -1
  20. package/dist/hmr.d.ts +1 -1
  21. package/dist/hmr.d.ts.map +1 -1
  22. package/dist/hmr.js +2 -4
  23. package/dist/hmr.js.map +1 -1
  24. package/dist/hooks/useViewTransition.d.ts.map +1 -1
  25. package/dist/hooks/useViewTransition.js +3 -3
  26. package/dist/hooks/useViewTransition.js.map +1 -1
  27. package/dist/hooks/utils.d.ts +1 -5
  28. package/dist/hooks/utils.d.ts.map +1 -1
  29. package/dist/hooks/utils.js +8 -20
  30. package/dist/hooks/utils.js.map +1 -1
  31. package/dist/index.d.ts +1 -0
  32. package/dist/index.d.ts.map +1 -1
  33. package/dist/index.js +2 -3
  34. package/dist/index.js.map +1 -1
  35. package/dist/lazy.d.ts.map +1 -1
  36. package/dist/lazy.js +4 -4
  37. package/dist/lazy.js.map +1 -1
  38. package/dist/portal.d.ts.map +1 -1
  39. package/dist/portal.js +2 -3
  40. package/dist/portal.js.map +1 -1
  41. package/dist/props.js +1 -1
  42. package/dist/props.js.map +1 -1
  43. package/dist/reconciler.d.ts.map +1 -1
  44. package/dist/reconciler.js +105 -79
  45. package/dist/reconciler.js.map +1 -1
  46. package/dist/renderToString.d.ts.map +1 -1
  47. package/dist/renderToString.js +5 -8
  48. package/dist/renderToString.js.map +1 -1
  49. package/dist/router/router.d.ts.map +1 -1
  50. package/dist/router/router.js +5 -6
  51. package/dist/router/router.js.map +1 -1
  52. package/dist/scheduler.d.ts +4 -11
  53. package/dist/scheduler.d.ts.map +1 -1
  54. package/dist/scheduler.js +365 -390
  55. package/dist/scheduler.js.map +1 -1
  56. package/dist/signals/base.d.ts +4 -4
  57. package/dist/signals/base.d.ts.map +1 -1
  58. package/dist/signals/base.js +38 -24
  59. package/dist/signals/base.js.map +1 -1
  60. package/dist/signals/computed.d.ts +2 -2
  61. package/dist/signals/computed.d.ts.map +1 -1
  62. package/dist/signals/computed.js +11 -3
  63. package/dist/signals/computed.js.map +1 -1
  64. package/dist/signals/effect.d.ts +1 -1
  65. package/dist/signals/effect.d.ts.map +1 -1
  66. package/dist/signals/globals.d.ts +2 -2
  67. package/dist/signals/globals.d.ts.map +1 -1
  68. package/dist/signals/globals.js.map +1 -1
  69. package/dist/signals/jsx.d.ts +4 -3
  70. package/dist/signals/jsx.d.ts.map +1 -1
  71. package/dist/signals/jsx.js +1 -1
  72. package/dist/signals/jsx.js.map +1 -1
  73. package/dist/signals/types.d.ts +2 -2
  74. package/dist/signals/types.d.ts.map +1 -1
  75. package/dist/signals/utils.d.ts +1 -1
  76. package/dist/signals/utils.d.ts.map +1 -1
  77. package/dist/signals/utils.js +1 -1
  78. package/dist/signals/utils.js.map +1 -1
  79. package/dist/ssr/server.d.ts.map +1 -1
  80. package/dist/ssr/server.js +1 -5
  81. package/dist/ssr/server.js.map +1 -1
  82. package/dist/types.d.ts +9 -2
  83. package/dist/types.d.ts.map +1 -1
  84. package/dist/utils.d.ts +1 -1
  85. package/dist/utils.d.ts.map +1 -1
  86. package/dist/utils.js +17 -17
  87. package/dist/utils.js.map +1 -1
  88. package/package.json +1 -1
  89. package/src/appContext.ts +21 -67
  90. package/src/constants.ts +8 -7
  91. package/src/context.ts +2 -4
  92. package/src/dom.ts +50 -29
  93. package/src/globals.ts +1 -9
  94. package/src/hmr.ts +3 -5
  95. package/src/hooks/useViewTransition.ts +3 -3
  96. package/src/hooks/utils.ts +7 -22
  97. package/src/index.ts +3 -3
  98. package/src/lazy.ts +4 -4
  99. package/src/portal.ts +2 -3
  100. package/src/props.ts +1 -1
  101. package/src/reconciler.ts +116 -90
  102. package/src/renderToString.ts +5 -8
  103. package/src/router/router.ts +4 -6
  104. package/src/scheduler.ts +369 -407
  105. package/src/signals/base.ts +40 -33
  106. package/src/signals/computed.ts +8 -3
  107. package/src/signals/effect.ts +1 -1
  108. package/src/signals/globals.ts +2 -2
  109. package/src/signals/jsx.ts +12 -11
  110. package/src/signals/types.ts +2 -2
  111. package/src/signals/utils.ts +1 -1
  112. package/src/ssr/server.ts +1 -5
  113. package/src/types.ts +9 -2
  114. package/src/utils.ts +26 -21
  115. package/dist/flags.d.ts +0 -6
  116. package/dist/flags.d.ts.map +0 -1
  117. package/dist/flags.js +0 -16
  118. package/dist/flags.js.map +0 -1
  119. package/src/flags.ts +0 -15
package/src/hmr.ts CHANGED
@@ -2,8 +2,9 @@ import type { Store } from "./store"
2
2
  import type { WatchEffect } from "./signals/watch"
3
3
  import { $HMR_ACCEPT } from "./constants.js"
4
4
  import { __DEV__ } from "./env.js"
5
- import { Signal } from "./signals/base.js"
6
5
  import { traverseApply } from "./utils.js"
6
+ import { requestUpdate } from "./scheduler.js"
7
+ import { Signal } from "./signals/index.js"
7
8
  import type { AppContext } from "./appContext"
8
9
 
9
10
  export type HMRAccept<T = {}> = {
@@ -124,15 +125,12 @@ export function createHMRContext() {
124
125
  vNode.type = newEntry.value as any
125
126
  dirtiedApps.add(ctx)
126
127
  vNode.hmrUpdated = true
127
- if (vNode.prev) {
128
- vNode.prev.type = newEntry.value as any
129
- }
130
128
  }
131
129
  })
132
130
  })
133
131
  }
134
132
  }
135
- dirtiedApps.forEach((ctx) => ctx.requestUpdate())
133
+ dirtiedApps.forEach((ctx) => ctx.rootNode && requestUpdate(ctx.rootNode))
136
134
  isModuleReplacementExecution = false
137
135
 
138
136
  if (tmpUnnamedWatchers.length) {
@@ -1,4 +1,5 @@
1
- import { ctx, node } from "../globals.js"
1
+ import { flushSync } from "../scheduler.js"
2
+ import { node } from "../globals.js"
2
3
  import { noop } from "../utils.js"
3
4
  import { sideEffectsEnabled } from "./utils.js"
4
5
 
@@ -12,7 +13,6 @@ import { sideEffectsEnabled } from "./utils.js"
12
13
  */
13
14
  export function useViewTransition() {
14
15
  if (!sideEffectsEnabled()) return noop
15
- const appCtx = ctx.current
16
16
  return (callback: () => void) => {
17
17
  if (node.current) {
18
18
  throw new Error("useViewTransition can't be called during rendering.")
@@ -20,7 +20,7 @@ export function useViewTransition() {
20
20
  if (!document.startViewTransition) return callback()
21
21
  document.startViewTransition(() => {
22
22
  callback()
23
- appCtx.flushSync()
23
+ flushSync()
24
24
  })
25
25
  }
26
26
  }
@@ -1,14 +1,14 @@
1
1
  import { KiruError } from "../error.js"
2
2
  import { __DEV__ } from "../env.js"
3
- import { ctx, hookIndex, node, nodeToCtxMap } from "../globals.js"
4
- import { getVNodeAppContext, noop } from "../utils.js"
3
+ import { hookIndex, node } from "../globals.js"
4
+ import { noop } from "../utils.js"
5
+ import { requestUpdate } from "../scheduler.js"
5
6
  export { sideEffectsEnabled } from "../utils.js"
6
7
  export {
7
8
  cleanupHook,
8
9
  depsRequireChange,
9
10
  useHook,
10
11
  useVNode,
11
- useAppContext,
12
12
  useHookDebugGroup,
13
13
  useRequestUpdate,
14
14
  HookDebugGroupAction,
@@ -46,19 +46,7 @@ const useHookDebugGroup = (name: string, action: HookDebugGroupAction) => {
46
46
  const useRequestUpdate = () => {
47
47
  const n = node.current
48
48
  if (!n) error_hookMustBeCalledTopLevel("useRequestUpdate")
49
- const ctx = getVNodeAppContext(n)
50
- return () => ctx.requestUpdate(n)
51
- }
52
-
53
- /**
54
- * Used to obtain the 'AppContext' for the current component.
55
- */
56
- const useAppContext = () => {
57
- if (!node.current) error_hookMustBeCalledTopLevel("useAppContext")
58
- const ctx = nodeToCtxMap.get(node.current)
59
- if (!ctx)
60
- error_hookMustBeCalledTopLevel("[kiru]: unable to find node's AppContext")
61
- return ctx
49
+ return () => requestUpdate(n)
62
50
  }
63
51
 
64
52
  /**
@@ -153,12 +141,9 @@ function useHook<
153
141
  ;(vNode.effects ??= []).push(callback)
154
142
  }
155
143
 
156
- const appCtx = ctx.current
157
144
  const index = hookIndex.current++
158
145
 
159
- let oldHook = (
160
- vNode.prev ? vNode.prev.hooks?.at(index) : vNode.hooks?.at(index)
161
- ) as HookState<T> | undefined
146
+ let oldHook = vNode.hooks?.at(index) as HookState<T> | undefined
162
147
 
163
148
  if (__DEV__) {
164
149
  currentHookName = hookName
@@ -197,7 +182,7 @@ function useHook<
197
182
  hook,
198
183
  isInit: !oldHook,
199
184
  isHMR: vNode.hmrUpdated,
200
- update: () => appCtx.requestUpdate(vNode),
185
+ update: () => requestUpdate(vNode),
201
186
  queueEffect,
202
187
  vNode,
203
188
  index,
@@ -223,7 +208,7 @@ function useHook<
223
208
  const res = (callback as HookCallback<T>)({
224
209
  hook,
225
210
  isInit: !oldHook,
226
- update: () => appCtx.requestUpdate(vNode),
211
+ update: () => requestUpdate(vNode),
227
212
  queueEffect,
228
213
  vNode,
229
214
  index,
package/src/index.ts CHANGED
@@ -3,7 +3,6 @@ import {
3
3
  type AppContext,
4
4
  type AppContextOptions,
5
5
  } from "./appContext.js"
6
- import { ctx } from "./globals.js"
7
6
  import { createKiruGlobalContext } from "./globalContext.js"
8
7
  import { __DEV__ } from "./env.js"
9
8
  import { KiruError } from "./error.js"
@@ -19,6 +18,7 @@ export { memo } from "./memo.js"
19
18
  export * from "./portal.js"
20
19
  export * from "./renderToString.js"
21
20
  export * from "./signals/index.js"
21
+ export { nextIdle, flushSync, requestUpdate } from "./scheduler.js"
22
22
  export * from "./store.js"
23
23
  export * from "./transition.js"
24
24
 
@@ -56,6 +56,6 @@ export function mount<T extends Record<string, unknown>>(
56
56
  }
57
57
  }
58
58
  }
59
- ctx.current = createAppContext<T>(appFunc, appProps, opts)
60
- return ctx.current.mount()
59
+
60
+ return createAppContext<T>(appFunc, appProps, opts).mount()
61
61
  }
package/src/lazy.ts CHANGED
@@ -4,8 +4,9 @@ import { KiruError } from "./error.js"
4
4
  import { node, renderMode } from "./globals.js"
5
5
  import { useContext } from "./hooks/useContext.js"
6
6
  import { useRef } from "./hooks/useRef.js"
7
- import { useAppContext, useRequestUpdate } from "./hooks/utils.js"
7
+ import { useRequestUpdate } from "./hooks/utils.js"
8
8
  import { hydrationStack } from "./hydration.js"
9
+ import { flushSync, nextIdle } from "./scheduler.js"
9
10
  import {
10
11
  HYDRATION_BOUNDARY_MARKER,
11
12
  HydrationBoundaryContext,
@@ -85,7 +86,6 @@ export function lazy<T extends LazyImportValue>(
85
86
  ): Kiru.FC<LazyComponentProps<T>> {
86
87
  function LazyComponent(props: LazyComponentProps<T>) {
87
88
  const { fallback = null, ...rest } = props
88
- const appCtx = useAppContext()
89
89
  const hydrationCtx = useContext(HydrationBoundaryContext, false)
90
90
  const needsHydration = useRef(
91
91
  hydrationCtx && renderMode.current === "hydrate"
@@ -154,7 +154,7 @@ export function lazy<T extends LazyImportValue>(
154
154
  const hydrate = () => {
155
155
  if (needsHydration.current === false) return
156
156
 
157
- appCtx.scheduler?.nextIdle(() => {
157
+ nextIdle(() => {
158
158
  delete thisNode.lastChildDom
159
159
  needsHydration.current = false
160
160
  hydrationStack.push(parent)
@@ -169,7 +169,7 @@ export function lazy<T extends LazyImportValue>(
169
169
  */
170
170
  requestUpdate()
171
171
  renderMode.current = "hydrate"
172
- appCtx.flushSync()
172
+ flushSync()
173
173
  renderMode.current = prev
174
174
  for (const child of childNodes) {
175
175
  if (child instanceof Element) {
package/src/portal.ts CHANGED
@@ -2,7 +2,7 @@ import { __DEV__ } from "./env.js"
2
2
  import { KiruError } from "./error.js"
3
3
  import { renderMode } from "./globals.js"
4
4
  import { useVNode } from "./hooks/utils.js"
5
- import { getVNodeAppContext } from "./utils.js"
5
+ import { nextIdle, requestUpdate } from "./scheduler.js"
6
6
 
7
7
  export { Portal, isPortal }
8
8
 
@@ -27,8 +27,7 @@ function Portal({ children, container }: PortalProps) {
27
27
  }
28
28
  return children
29
29
  case "hydrate":
30
- const ctx = getVNodeAppContext(vNode)
31
- ctx.scheduler?.nextIdle(() => ctx.requestUpdate(vNode))
30
+ nextIdle(() => requestUpdate(vNode))
32
31
  return null
33
32
  case "stream":
34
33
  case "string":
package/src/props.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { KiruError } from "./error.js"
2
- import { Signal } from "./signals/base.js"
2
+ import { Signal } from "./signals/index.js"
3
3
 
4
4
  export function assertValidElementProps(vNode: Kiru.VNode) {
5
5
  if ("children" in vNode.props && vNode.props.innerHTML) {
package/src/reconciler.ts CHANGED
@@ -1,14 +1,22 @@
1
- import { FLAG, $FRAGMENT } from "./constants.js"
2
- import { isVNode, latest } from "./utils.js"
3
- import { Signal } from "./signals/base.js"
1
+ import {
2
+ $FRAGMENT,
3
+ FLAG_HAS_MEMO_ANCESTOR,
4
+ FLAG_PLACEMENT,
5
+ FLAG_UPDATE,
6
+ } from "./constants.js"
7
+ import { getVNodeAppContext, isVNode, latest } from "./utils.js"
8
+ import { Signal } from "./signals/index.js"
4
9
  import { __DEV__ } from "./env.js"
5
10
  import { createElement, Fragment } from "./element.js"
6
- import { flags } from "./flags.js"
7
- import { ctx } from "./globals.js"
11
+ import type { AppContext } from "./appContext.js"
8
12
 
9
13
  type VNode = Kiru.VNode
14
+ let appCtx: AppContext
10
15
 
11
16
  export function reconcileChildren(parent: VNode, children: unknown) {
17
+ if (__DEV__) {
18
+ appCtx = getVNodeAppContext(parent)!
19
+ }
12
20
  if (Array.isArray(children)) {
13
21
  if (__DEV__) {
14
22
  // array children are 'tagged' during parent reconciliation pass
@@ -43,13 +51,11 @@ function reconcileSingleChild(parent: VNode, child: unknown) {
43
51
  const existingChildren = mapRemainingChildren(oldChild)
44
52
  const newNode = updateFromMap(existingChildren, parent, 0, child)
45
53
  if (newNode !== null) {
46
- if (newNode.prev !== null) {
54
+ const prev = newNode.prev
55
+ if (prev !== null) {
56
+ const key = prev.props.key
47
57
  // 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
- )
58
+ existingChildren.delete(key === undefined ? prev.index : key)
53
59
  }
54
60
  placeChild(newNode, 0, 0)
55
61
  }
@@ -68,7 +74,7 @@ function reconcileChildrenArray(parent: VNode, children: unknown[]) {
68
74
  let lastPlacedIndex = 0
69
75
  let newIdx = 0
70
76
 
71
- for (; !!oldChild && newIdx < children.length; newIdx++) {
77
+ for (; oldChild !== null && newIdx < children.length; newIdx++) {
72
78
  if (oldChild.index > newIdx) {
73
79
  nextOldChild = oldChild
74
80
  oldChild = null
@@ -128,13 +134,11 @@ function reconcileChildrenArray(parent: VNode, children: unknown[]) {
128
134
  children[newIdx]
129
135
  )
130
136
  if (newNode !== null) {
131
- if (newNode.prev !== null) {
137
+ const prev = newNode.prev
138
+ if (prev !== null) {
139
+ const key = prev.props.key
132
140
  // 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
- )
141
+ existingChildren.delete(key === undefined ? prev.index : key)
138
142
  }
139
143
  lastPlacedIndex = placeChild(newNode, lastPlacedIndex, newIdx)
140
144
  if (prevNewChild === null) {
@@ -192,24 +196,27 @@ function updateTextNode(
192
196
  ) {
193
197
  if (oldChild === null || oldChild.type !== "#text") {
194
198
  if (__DEV__) {
195
- emitCreateNode()
199
+ dev_emitCreateNode()
196
200
  }
197
201
  const newChild = createElement("#text", { nodeValue: content })
198
202
  setParent(newChild, parent)
199
203
  return newChild
200
- } else {
201
- if (__DEV__) {
202
- emitUpdateNode()
203
- }
204
+ }
205
+
206
+ if (__DEV__) {
207
+ dev_emitUpdateNode()
208
+ }
209
+ const prev = oldChild.props.nodeValue
210
+ if (prev !== content) {
204
211
  oldChild.props.nodeValue = content
205
- oldChild.flags = flags.set(oldChild.flags, FLAG.UPDATE)
206
- oldChild.sibling = null
207
- return oldChild
212
+ oldChild.flags |= FLAG_UPDATE
208
213
  }
214
+ oldChild.sibling = null
215
+ return oldChild
209
216
  }
210
217
 
211
218
  function updateNode(parent: VNode, oldChild: VNode | null, newChild: VNode) {
212
- let nodeType = newChild.type
219
+ let { type: nodeType, props: newProps } = newChild
213
220
  if (__DEV__) {
214
221
  if (typeof nodeType === "function") {
215
222
  nodeType = latest(nodeType)
@@ -219,25 +226,31 @@ function updateNode(parent: VNode, oldChild: VNode | null, newChild: VNode) {
219
226
  return updateFragment(
220
227
  parent,
221
228
  oldChild,
222
- (newChild.props.children as VNode[]) || [],
223
- newChild.props
229
+ (newProps.children as VNode[]) || [],
230
+ newProps
224
231
  )
225
232
  }
226
233
  if (oldChild?.type === nodeType) {
227
234
  if (__DEV__) {
228
- emitUpdateNode()
235
+ dev_emitUpdateNode()
229
236
  }
230
237
  oldChild.index = 0
231
- oldChild.props = newChild.props
232
238
  oldChild.sibling = null
233
- oldChild.flags = flags.set(oldChild.flags, FLAG.UPDATE)
239
+ if (typeof nodeType === "string") {
240
+ if (propsChanged(oldChild.props, newProps)) {
241
+ oldChild.flags |= FLAG_UPDATE
242
+ }
243
+ } else {
244
+ oldChild.flags |= FLAG_UPDATE
245
+ }
246
+ oldChild.props = newProps
234
247
  oldChild.memoizedProps = newChild.memoizedProps
235
248
  return oldChild
236
249
  }
237
250
  if (__DEV__) {
238
- emitCreateNode()
251
+ dev_emitCreateNode()
239
252
  }
240
- const created = createElement(nodeType, newChild.props)
253
+ const created = createElement(nodeType, newProps)
241
254
  setParent(created, parent)
242
255
  return created
243
256
  }
@@ -250,17 +263,17 @@ function updateFragment(
250
263
  ) {
251
264
  if (oldChild === null || oldChild.type !== $FRAGMENT) {
252
265
  if (__DEV__) {
253
- emitCreateNode()
266
+ dev_emitCreateNode()
254
267
  }
255
268
  const el = createElement($FRAGMENT, { children, ...newProps })
256
269
  setParent(el, parent)
257
270
  return el
258
271
  }
259
272
  if (__DEV__) {
260
- emitUpdateNode()
273
+ dev_emitUpdateNode()
261
274
  }
262
275
  oldChild.props = { ...oldChild.props, ...newProps, children }
263
- oldChild.flags = flags.set(oldChild.flags, FLAG.UPDATE)
276
+ oldChild.flags |= FLAG_UPDATE
264
277
  oldChild.sibling = null
265
278
  return oldChild
266
279
  }
@@ -272,7 +285,7 @@ function createChild(parent: VNode, child: unknown): VNode | null {
272
285
  typeof child === "bigint"
273
286
  ) {
274
287
  if (__DEV__) {
275
- emitCreateNode()
288
+ dev_emitCreateNode()
276
289
  }
277
290
  const el = createElement("#text", {
278
291
  nodeValue: "" + child,
@@ -283,7 +296,7 @@ function createChild(parent: VNode, child: unknown): VNode | null {
283
296
 
284
297
  if (Signal.isSignal(child)) {
285
298
  if (__DEV__) {
286
- emitCreateNode()
299
+ dev_emitCreateNode()
287
300
  }
288
301
  const el = createElement("#text", {
289
302
  nodeValue: child,
@@ -294,17 +307,17 @@ function createChild(parent: VNode, child: unknown): VNode | null {
294
307
 
295
308
  if (isVNode(child)) {
296
309
  if (__DEV__) {
297
- emitCreateNode()
310
+ dev_emitCreateNode()
298
311
  }
299
312
  const newNode = createElement(child.type, child.props)
300
313
  setParent(newNode, parent)
301
- newNode.flags = flags.set(newNode.flags, FLAG.PLACEMENT)
314
+ newNode.flags |= FLAG_PLACEMENT
302
315
  return newNode
303
316
  }
304
317
 
305
318
  if (Array.isArray(child)) {
306
319
  if (__DEV__) {
307
- emitCreateNode()
320
+ dev_emitCreateNode()
308
321
  markListChild(child)
309
322
  }
310
323
  const el = Fragment({ children: child })
@@ -316,22 +329,22 @@ function createChild(parent: VNode, child: unknown): VNode | null {
316
329
  }
317
330
 
318
331
  function placeChild(
319
- child: VNode | null,
332
+ child: VNode,
320
333
  lastPlacedIndex: number,
321
334
  newIndex: number
322
335
  ): number {
323
- if (child === null) return lastPlacedIndex
324
336
  child.index = newIndex
325
- if (child.prev !== null) {
326
- const oldIndex = child.prev.index
337
+ const prev = child.prev
338
+ if (prev !== null) {
339
+ const oldIndex = prev.index
327
340
  if (oldIndex < lastPlacedIndex) {
328
- child.flags = flags.set(child.flags, FLAG.PLACEMENT)
341
+ child.flags |= FLAG_PLACEMENT
329
342
  return lastPlacedIndex
330
343
  } else {
331
344
  return oldIndex
332
345
  }
333
346
  } else {
334
- child.flags = flags.set(child.flags, FLAG.PLACEMENT)
347
+ child.flags |= FLAG_PLACEMENT
335
348
  return lastPlacedIndex
336
349
  }
337
350
  }
@@ -352,8 +365,6 @@ function updateFromMap(
352
365
  const oldChild = existingChildren.get(index)
353
366
  if (oldChild) {
354
367
  if (oldChild.props.nodeValue === child) {
355
- oldChild.flags = flags.set(oldChild.flags, FLAG.UPDATE)
356
- oldChild.props.nodeValue = child
357
368
  return oldChild
358
369
  }
359
370
  if (
@@ -365,40 +376,46 @@ function updateFromMap(
365
376
  }
366
377
 
367
378
  if (__DEV__) {
368
- emitCreateNode()
379
+ dev_emitCreateNode()
369
380
  }
370
381
  const newChild = createElement("#text", {
371
382
  nodeValue: child,
372
383
  })
373
384
  setParent(newChild, parent)
374
- newChild.flags = flags.set(newChild.flags, FLAG.PLACEMENT)
385
+ newChild.flags |= FLAG_PLACEMENT
375
386
  newChild.index = index
376
387
  return newChild
377
388
  }
378
389
 
379
390
  if (isVNode(child)) {
380
- const oldChild = existingChildren.get(
381
- child.props.key === undefined ? index : child.props.key
382
- )
383
- if (oldChild?.type === child.type) {
391
+ const { type, props: newProps } = child
392
+ const key = newProps.key
393
+ const oldChild = existingChildren.get(key === undefined ? index : key)
394
+ if (oldChild?.type === type) {
384
395
  if (__DEV__) {
385
- emitUpdateNode()
396
+ dev_emitUpdateNode()
386
397
  }
387
- oldChild.flags = flags.set(oldChild.flags, FLAG.UPDATE)
388
- oldChild.props = child.props
398
+ if (typeof type === "string") {
399
+ if (propsChanged(oldChild.props, newProps)) {
400
+ oldChild.flags |= FLAG_UPDATE
401
+ }
402
+ } else {
403
+ oldChild.flags |= FLAG_UPDATE
404
+ }
405
+ oldChild.props = newProps
389
406
  oldChild.sibling = null
390
407
  oldChild.index = index
391
408
  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
409
  }
410
+
411
+ if (__DEV__) {
412
+ dev_emitCreateNode()
413
+ }
414
+ const newChild = createElement(child.type, child.props)
415
+ setParent(newChild, parent)
416
+ newChild.flags |= FLAG_PLACEMENT
417
+ newChild.index = index
418
+ return newChild
402
419
  }
403
420
 
404
421
  if (Array.isArray(child)) {
@@ -408,42 +425,53 @@ function updateFromMap(
408
425
  }
409
426
  if (oldChild) {
410
427
  if (__DEV__) {
411
- emitUpdateNode()
428
+ dev_emitUpdateNode()
412
429
  }
413
- oldChild.flags = flags.set(oldChild.flags, FLAG.UPDATE)
430
+ oldChild.flags |= FLAG_UPDATE
414
431
  oldChild.props.children = child
415
432
  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
433
  }
434
+
435
+ if (__DEV__) {
436
+ dev_emitCreateNode()
437
+ }
438
+ const newChild = Fragment({ children: child })
439
+ setParent(newChild, parent)
440
+ newChild.flags |= FLAG_PLACEMENT
441
+ newChild.index = index
442
+ return newChild
426
443
  }
427
444
 
428
445
  return null
429
446
  }
430
447
 
431
- function setParent(child: Kiru.VNode, parent: Kiru.VNode) {
448
+ function propsChanged(oldProps: VNode["props"], newProps: VNode["props"]) {
449
+ const aKeys = Object.keys(oldProps)
450
+ const bKeys = Object.keys(newProps)
451
+ if (aKeys.length !== bKeys.length) return true
452
+ for (let key of aKeys) {
453
+ if (key === "children" || key === "key") continue
454
+ if (oldProps[key] !== newProps[key]) return true
455
+ }
456
+ return false
457
+ }
458
+
459
+ function setParent(child: VNode, parent: VNode) {
432
460
  child.parent = parent
433
461
  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)
462
+ if (parent.isMemoized || parent.flags & FLAG_HAS_MEMO_ANCESTOR) {
463
+ child.flags |= FLAG_HAS_MEMO_ANCESTOR
436
464
  }
437
465
  }
438
466
 
439
- function emitUpdateNode() {
467
+ function dev_emitUpdateNode() {
440
468
  if (!("window" in globalThis)) return
441
- window.__kiru?.profilingContext?.emit("updateNode", ctx.current)
469
+ window.__kiru?.profilingContext?.emit("updateNode", appCtx)
442
470
  }
443
471
 
444
- function emitCreateNode() {
472
+ function dev_emitCreateNode() {
445
473
  if (!("window" in globalThis)) return
446
- window.__kiru?.profilingContext?.emit("createNode", ctx.current)
474
+ window.__kiru?.profilingContext?.emit("createNode", appCtx)
447
475
  }
448
476
 
449
477
  const $LIST_CHILD = Symbol("kiru:marked-list-child")
@@ -454,10 +482,8 @@ function markListChild(children: unknown[]) {
454
482
  function mapRemainingChildren(child: VNode | null) {
455
483
  const map: Map<JSX.ElementKey, VNode> = new Map()
456
484
  while (child) {
457
- map.set(
458
- child.props.key === undefined ? child.index : child.props.key,
459
- child
460
- )
485
+ const key = child.props.key
486
+ map.set(key === undefined ? child.index : key, child)
461
487
  child = child.sibling
462
488
  }
463
489
  return map
@@ -1,4 +1,4 @@
1
- import { ctx, node, nodeToCtxMap, renderMode } from "./globals.js"
1
+ import { node, renderMode } from "./globals.js"
2
2
  import { createAppContext } from "./appContext.js"
3
3
  import { Fragment } from "./element.js"
4
4
  import {
@@ -7,7 +7,7 @@ import {
7
7
  propsToElementAttributes,
8
8
  isExoticType,
9
9
  } from "./utils.js"
10
- import { Signal } from "./signals/base.js"
10
+ import { Signal } from "./signals/index.js"
11
11
  import { $HYDRATION_BOUNDARY, voidElements } from "./constants.js"
12
12
  import { assertValidElementProps } from "./props.js"
13
13
  import { HYDRATION_BOUNDARY_MARKER } from "./ssr/hydrationBoundary.js"
@@ -19,13 +19,11 @@ export function renderToString<T extends Record<string, unknown>>(
19
19
  ) {
20
20
  const prev = renderMode.current
21
21
  renderMode.current = "string"
22
- const prevCtx = ctx.current
23
- const c = (ctx.current = createAppContext(appFunc, appProps, {
22
+ const ctx = createAppContext(appFunc, appProps, {
24
23
  rootType: Fragment,
25
- }))
26
- const res = renderToString_internal(c.rootNode, null, 0)
24
+ })
25
+ const res = renderToString_internal(ctx.rootNode, null, 0)
27
26
  renderMode.current = prev
28
- ctx.current = prevCtx
29
27
  return res
30
28
  }
31
29
 
@@ -65,7 +63,6 @@ function renderToString_internal(
65
63
  }
66
64
 
67
65
  if (typeof type !== "string") {
68
- nodeToCtxMap.set(el, ctx.current)
69
66
  node.current = el
70
67
  const res = type(props)
71
68
  node.current = null