kiru 1.0.1 → 1.1.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 (145) hide show
  1. package/dist/appHandle.js +2 -2
  2. package/dist/appHandle.js.map +1 -1
  3. package/dist/components/lazy.d.ts.map +1 -1
  4. package/dist/components/lazy.js +2 -2
  5. package/dist/components/lazy.js.map +1 -1
  6. package/dist/components/transition.js +1 -5
  7. package/dist/components/transition.js.map +1 -1
  8. package/dist/devtools.d.ts.map +1 -1
  9. package/dist/devtools.js +6 -2
  10. package/dist/devtools.js.map +1 -1
  11. package/dist/dom/commit.d.ts +5 -0
  12. package/dist/dom/commit.d.ts.map +1 -0
  13. package/dist/dom/commit.js +94 -0
  14. package/dist/dom/commit.js.map +1 -0
  15. package/dist/dom/focus.d.ts +4 -0
  16. package/dist/dom/focus.d.ts.map +1 -0
  17. package/dist/dom/focus.js +32 -0
  18. package/dist/dom/focus.js.map +1 -0
  19. package/dist/dom/index.d.ts +4 -0
  20. package/dist/dom/index.d.ts.map +1 -0
  21. package/dist/dom/index.js +4 -0
  22. package/dist/dom/index.js.map +1 -0
  23. package/dist/dom/nodes.d.ts +12 -0
  24. package/dist/dom/nodes.d.ts.map +1 -0
  25. package/dist/dom/nodes.js +165 -0
  26. package/dist/dom/nodes.js.map +1 -0
  27. package/dist/dom/props.d.ts +8 -0
  28. package/dist/dom/props.d.ts.map +1 -0
  29. package/dist/dom/props.js +675 -0
  30. package/dist/dom/props.js.map +1 -0
  31. package/dist/env.d.ts +2 -0
  32. package/dist/env.d.ts.map +1 -1
  33. package/dist/env.js +2 -0
  34. package/dist/env.js.map +1 -1
  35. package/dist/globalContext.d.ts +3 -8
  36. package/dist/globalContext.d.ts.map +1 -1
  37. package/dist/globalContext.js +4 -16
  38. package/dist/globalContext.js.map +1 -1
  39. package/dist/globals.d.ts +21 -1
  40. package/dist/globals.d.ts.map +1 -1
  41. package/dist/globals.js +22 -2
  42. package/dist/globals.js.map +1 -1
  43. package/dist/hmr.d.ts +17 -2
  44. package/dist/hmr.d.ts.map +1 -1
  45. package/dist/hmr.js +31 -5
  46. package/dist/hmr.js.map +1 -1
  47. package/dist/hooks/index.d.ts +1 -0
  48. package/dist/hooks/index.d.ts.map +1 -1
  49. package/dist/hooks/index.js +1 -0
  50. package/dist/hooks/index.js.map +1 -1
  51. package/dist/hooks/onBeforeMount.d.ts +1 -1
  52. package/dist/hooks/onBeforeMount.d.ts.map +1 -1
  53. package/dist/hooks/onBeforeMount.js +10 -3
  54. package/dist/hooks/onBeforeMount.js.map +1 -1
  55. package/dist/hooks/onCleanup.d.ts +1 -1
  56. package/dist/hooks/onCleanup.d.ts.map +1 -1
  57. package/dist/hooks/onCleanup.js +7 -4
  58. package/dist/hooks/onCleanup.js.map +1 -1
  59. package/dist/hooks/onMount.d.ts +2 -2
  60. package/dist/hooks/onMount.d.ts.map +1 -1
  61. package/dist/hooks/onMount.js +11 -4
  62. package/dist/hooks/onMount.js.map +1 -1
  63. package/dist/hooks/setup.d.ts +13 -0
  64. package/dist/hooks/setup.d.ts.map +1 -0
  65. package/dist/hooks/setup.js +54 -0
  66. package/dist/hooks/setup.js.map +1 -0
  67. package/dist/hooks/utils.d.ts +2 -3
  68. package/dist/hooks/utils.d.ts.map +1 -1
  69. package/dist/hooks/utils.js +9 -14
  70. package/dist/hooks/utils.js.map +1 -1
  71. package/dist/index.d.ts +1 -0
  72. package/dist/index.d.ts.map +1 -1
  73. package/dist/index.js +4 -3
  74. package/dist/index.js.map +1 -1
  75. package/dist/reconciler.js +3 -3
  76. package/dist/reconciler.js.map +1 -1
  77. package/dist/router/head.js +2 -2
  78. package/dist/router/head.js.map +1 -1
  79. package/dist/router/pageConfig.js +2 -2
  80. package/dist/router/pageConfig.js.map +1 -1
  81. package/dist/scheduler.js +62 -57
  82. package/dist/scheduler.js.map +1 -1
  83. package/dist/signals/base.js +3 -3
  84. package/dist/signals/base.js.map +1 -1
  85. package/dist/signals/effect.d.ts.map +1 -1
  86. package/dist/signals/effect.js +6 -6
  87. package/dist/signals/effect.js.map +1 -1
  88. package/dist/signals/tracking.d.ts +3 -2
  89. package/dist/signals/tracking.d.ts.map +1 -1
  90. package/dist/signals/tracking.js.map +1 -1
  91. package/dist/statefulPromise.js +2 -2
  92. package/dist/statefulPromise.js.map +1 -1
  93. package/dist/types.d.ts +5 -1
  94. package/dist/types.d.ts.map +1 -1
  95. package/dist/types.dom.d.ts +1 -1
  96. package/dist/types.dom.d.ts.map +1 -1
  97. package/dist/utils/format.d.ts.map +1 -1
  98. package/dist/utils/format.js +4 -1
  99. package/dist/utils/format.js.map +1 -1
  100. package/dist/utils/vdom.d.ts +2 -2
  101. package/dist/utils/vdom.d.ts.map +1 -1
  102. package/dist/utils/vdom.js +2 -2
  103. package/dist/utils/vdom.js.map +1 -1
  104. package/dist/viewTransitions.d.ts.map +1 -1
  105. package/dist/viewTransitions.js +2 -1
  106. package/dist/viewTransitions.js.map +1 -1
  107. package/package.json +1 -1
  108. package/src/appHandle.ts +2 -2
  109. package/src/components/lazy.ts +5 -6
  110. package/src/components/transition.ts +2 -6
  111. package/src/devtools.ts +4 -2
  112. package/src/dom/commit.ts +133 -0
  113. package/src/dom/focus.ts +34 -0
  114. package/src/dom/index.ts +3 -0
  115. package/src/dom/nodes.ts +204 -0
  116. package/src/dom/props.ts +818 -0
  117. package/src/env.ts +3 -0
  118. package/src/globalContext.ts +7 -24
  119. package/src/globals.ts +25 -2
  120. package/src/hmr.ts +32 -5
  121. package/src/hooks/index.ts +1 -0
  122. package/src/hooks/onBeforeMount.ts +9 -3
  123. package/src/hooks/onCleanup.ts +10 -4
  124. package/src/hooks/onMount.ts +10 -4
  125. package/src/hooks/setup.ts +70 -0
  126. package/src/hooks/utils.ts +14 -19
  127. package/src/index.ts +4 -2
  128. package/src/reconciler.ts +3 -3
  129. package/src/router/head.ts +2 -2
  130. package/src/router/pageConfig.ts +2 -2
  131. package/src/scheduler.ts +79 -64
  132. package/src/signals/base.ts +3 -3
  133. package/src/signals/effect.ts +5 -7
  134. package/src/signals/tracking.ts +3 -2
  135. package/src/statefulPromise.ts +2 -2
  136. package/src/types.dom.ts +2 -2
  137. package/src/types.ts +7 -1
  138. package/src/utils/format.ts +3 -1
  139. package/src/utils/vdom.ts +2 -2
  140. package/src/viewTransitions.ts +2 -1
  141. package/dist/dom.d.ts +0 -10
  142. package/dist/dom.d.ts.map +0 -1
  143. package/dist/dom.js +0 -601
  144. package/dist/dom.js.map +0 -1
  145. package/src/dom.ts +0 -775
package/src/env.ts CHANGED
@@ -4,3 +4,6 @@ if (NODE_ENV !== "development" && NODE_ENV !== "production") {
4
4
  }
5
5
 
6
6
  export const __DEV__ = NODE_ENV === "development"
7
+
8
+ export const isBrowser = "window" in globalThis && typeof window !== "undefined"
9
+ export const isServer = !isBrowser
@@ -1,17 +1,16 @@
1
1
  import { __DEV__ } from "./env.js"
2
- import { createHMRContext } from "./hmr.js"
2
+ import { createHmrContext } from "./hmr.js"
3
3
  import { createProfilingContext } from "./profiling.js"
4
4
  import { fileRouterInstance } from "./router/globals.js"
5
5
  import type { FileRouterController } from "./router/fileRouterController"
6
6
  import type { AppHandle } from "./appHandle"
7
- import type { requestUpdate } from "./index.js"
8
7
 
9
8
  export { createKiruGlobalContext, type GlobalKiruEvent, type KiruGlobalContext }
10
9
 
11
10
  type Evt =
12
11
  | {
13
12
  name: "mount"
14
- data?: typeof requestUpdate
13
+ data?: undefined
15
14
  }
16
15
  | {
17
16
  name: "unmount"
@@ -28,10 +27,6 @@ type Evt =
28
27
 
29
28
  type GlobalKiruEvent = Evt["name"]
30
29
 
31
- interface SchedulerInterface {
32
- requestUpdate: (vNode: Kiru.VNode) => void
33
- }
34
-
35
30
  export type DebuggerEntry = {
36
31
  label: string
37
32
  signal: Kiru.Signal<unknown>
@@ -53,17 +48,15 @@ interface KiruGlobalContext {
53
48
  untrack: (signal: Kiru.Signal<unknown>) => void
54
49
  subscribe: (callback: (entries: Set<DebuggerEntry>) => void) => () => void
55
50
  }
56
- HMRContext?: ReturnType<typeof createHMRContext>
51
+ HMRContext?: ReturnType<typeof createHmrContext>
57
52
  profilingContext?: ReturnType<typeof createProfilingContext>
58
53
  fileRouterInstance?: {
59
54
  current: FileRouterController | null
60
55
  }
61
- getSchedulerInterface?: (app: AppHandle) => SchedulerInterface | null
62
56
  }
63
57
 
64
58
  function createKiruGlobalContext(): KiruGlobalContext {
65
59
  const apps = new Set<AppHandle>()
66
- const appToSchedulerInterface = new WeakMap<AppHandle, SchedulerInterface>()
67
60
  const listeners = new Map<
68
61
  GlobalKiruEvent,
69
62
  Set<(app: AppHandle, data?: Evt["data"]) => void>
@@ -103,23 +96,13 @@ function createKiruGlobalContext(): KiruGlobalContext {
103
96
  }
104
97
 
105
98
  // Initialize event listeners
106
- on("mount", (app, requestUpdate) => {
107
- apps.add(app)
108
- if (requestUpdate && typeof requestUpdate === "function") {
109
- appToSchedulerInterface.set(app, { requestUpdate })
110
- }
111
- })
112
- on("unmount", (app) => {
113
- apps.delete(app)
114
- appToSchedulerInterface.delete(app)
115
- })
99
+ on("mount", (app) => apps.add(app))
100
+ on("unmount", (app) => apps.delete(app))
101
+
116
102
  if (__DEV__) {
117
- globalContext.HMRContext = createHMRContext()
103
+ globalContext.HMRContext = createHmrContext()
118
104
  globalContext.profilingContext = createProfilingContext()
119
105
  globalContext.fileRouterInstance = fileRouterInstance
120
- globalContext.getSchedulerInterface = (app) => {
121
- return appToSchedulerInterface.get(app) ?? null
122
- }
123
106
 
124
107
  const debuggerEntries = new Set<DebuggerEntry>()
125
108
  const subscribers = new Set<(debuggerEntries: Set<DebuggerEntry>) => void>()
package/src/globals.ts CHANGED
@@ -1,13 +1,36 @@
1
- export { node, renderMode, hydrationMode }
1
+ import { Setup } from "./hooks/index.js"
2
+ import { isBrowser } from "./env.js"
3
+ export { node, renderMode, hydrationMode, setups, postEffectCleanups }
2
4
 
5
+ /**
6
+ * A reference to the current VNode (always a component) being rendered.
7
+ */
3
8
  const node = {
4
9
  current: null as Kiru.VNode | null,
5
10
  }
6
11
 
12
+ /**
13
+ * The current render mode. Can be "dom" "string", "stream", or "hydrate".
14
+ */
7
15
  const renderMode = {
8
- current: ("window" in globalThis ? "dom" : "string") as Kiru.RenderMode,
16
+ current: (isBrowser ? "dom" : "string") as Kiru.RenderMode,
9
17
  }
10
18
 
19
+ /**
20
+ * The current hydration mode. Can be "static" or "dynamic".
21
+ * Used to indicate whether the page being hydrated will contain streamed content.
22
+ */
11
23
  const hydrationMode = {
12
24
  current: "dynamic" as "static" | "dynamic",
13
25
  }
26
+
27
+ /**
28
+ * A map of VNodes (components) to their setup functions.
29
+ */
30
+ const setups: WeakMap<Kiru.VNode, Setup<any>> = new WeakMap()
31
+
32
+ /**
33
+ * Cleanup functions from onMount() that run after components
34
+ * have been unmounted and the browser has painted.
35
+ */
36
+ const postEffectCleanups: (() => void)[] = []
package/src/hmr.ts CHANGED
@@ -13,7 +13,7 @@ export type HMRAccept<T = {}> = {
13
13
  export type GenericHMRAcceptor<T = {}> = {
14
14
  [$HMR_ACCEPT]: HMRAccept<T>
15
15
  }
16
- type HotVar = Kiru.FC | Signal<any> | Kiru.ContextBase<any>
16
+ type HotVar = Kiru.FC | Signal<any> | Kiru.Context<any>
17
17
 
18
18
  type HotVarDesc = {
19
19
  type: string
@@ -42,6 +42,7 @@ export function isGenericHmrAcceptor(
42
42
  type ModuleMemory = {
43
43
  hotVars: Map<string, HotVarDesc>
44
44
  unnamedEffects: Array<Effect>
45
+ hmrCallbacks: Array<() => void>
45
46
  }
46
47
 
47
48
  type HotVarRegistrationEntry = {
@@ -50,7 +51,7 @@ type HotVarRegistrationEntry = {
50
51
  link: string
51
52
  }
52
53
 
53
- export function createHMRContext() {
54
+ export function createHmrContext() {
54
55
  type FilePath = string
55
56
  const moduleMap = new Map<FilePath, ModuleMemory>()
56
57
  let currentModuleFilePath: string | null = null
@@ -59,9 +60,13 @@ export function createHMRContext() {
59
60
  const isReplacement = () => isModuleReplacementExecution
60
61
  let isWaitingForNextEffect = false
61
62
 
62
- const onHmrCallbacks: Array<() => void> = []
63
+ const globalHmrCallbacks: Array<() => void> = []
63
64
  const onHmr = (callback: () => void) => {
64
- onHmrCallbacks.push(callback)
65
+ if (currentModuleMemory) {
66
+ currentModuleMemory.hmrCallbacks.push(callback)
67
+ return
68
+ }
69
+ globalHmrCallbacks.push(callback)
65
70
  }
66
71
 
67
72
  const prepare = (filePath: string) => {
@@ -71,10 +76,12 @@ export function createHMRContext() {
71
76
  mod = {
72
77
  hotVars: new Map(),
73
78
  unnamedEffects: [],
79
+ hmrCallbacks: [],
74
80
  }
75
81
  moduleMap.set(filePath, mod)
76
82
  } else {
77
- while (onHmrCallbacks.length) onHmrCallbacks.shift()!()
83
+ while (mod.hmrCallbacks.length) mod.hmrCallbacks.shift()!()
84
+ while (globalHmrCallbacks.length) globalHmrCallbacks.shift()!()
78
85
  for (const effect of mod.unnamedEffects) {
79
86
  effect.stop()
80
87
  }
@@ -165,3 +172,23 @@ export function createHMRContext() {
165
172
  },
166
173
  }
167
174
  }
175
+
176
+ /**
177
+ * Queues a callback to be fired when HMR is triggered. This is a no-op in non-browser environments or in production.
178
+ * - If called during current module evaluation, the callback will be fired the next time the current module is evaluated.
179
+ * - If called at any other time, the callback will be fired the next time HMR is triggered.
180
+ * @see https://kirujs.dev/docs/api/lifecycles#onHmr
181
+ *
182
+ * ```ts
183
+ * import { onHmr } from "kiru"
184
+ * // start an interval in the module scope
185
+ * const interval = setInterval(() => {...}, 1000)
186
+ * // stop the interval when this file is reloaded
187
+ * onHmr(() => clearInterval(interval))
188
+ ```
189
+ */
190
+ export function onHmr(callback: () => void): void {
191
+ if ("window" in globalThis && window.__kiru.HMRContext) {
192
+ window.__kiru.HMRContext.onHmr(callback)
193
+ }
194
+ }
@@ -1,4 +1,5 @@
1
1
  export * from "./onCleanup.js"
2
2
  export * from "./onMount.js"
3
3
  export * from "./onBeforeMount.js"
4
+ export * from "./setup.js"
4
5
  export * from "./utils.js"
@@ -1,12 +1,18 @@
1
- import { queueSetupEffect } from "./utils.js"
1
+ import { sideEffectsEnabled } from "../utils/index.js"
2
+ import { getVNodeLifecycleHooks, wrapLifecycleHookCallback } from "./utils.js"
2
3
 
3
4
  /**
4
5
  * Registers a callback that runs after the component is first mounted to the DOM, but before the DOM is painted.
5
6
  * Optionally returns a cleanup function that will run when the component unmounts.
6
7
  * Intended for use during component setup when the component returns a render function.
7
8
  *
8
- * @see https://kirujs.dev/docs/hooks/onBeforeMount
9
+ * @see https://kirujs.dev/docs/api/lifecycles#onBeforeMount
9
10
  */
10
11
  export function onBeforeMount(fn: () => (() => void) | void): void {
11
- queueSetupEffect(fn, { immediate: true })
12
+ if (!sideEffectsEnabled()) return
13
+ const hooks = getVNodeLifecycleHooks()
14
+ if (!hooks) {
15
+ throw new Error("Cannot queue beforeMount effect outside of a component")
16
+ }
17
+ hooks.pre.push(wrapLifecycleHookCallback(fn, hooks.preCleanups))
12
18
  }
@@ -1,15 +1,21 @@
1
1
  import { node } from "../globals.js"
2
- import { generateRandomID, registerVNodeCleanup } from "../utils/index.js"
2
+ import {
3
+ generateRandomID,
4
+ registerVNodeCleanup,
5
+ sideEffectsEnabled,
6
+ } from "../utils/index.js"
3
7
 
4
8
  /**
5
9
  * Registers a cleanup function that runs when the component unmounts.
6
10
  * Intended for use during component setup when the component returns a render function.
7
11
  *
8
- * @see https://kirujs.dev/docs/hooks/onCleanup
12
+ * @see https://kirujs.dev/docs/api/lifecycles#onCleanup
9
13
  */
10
14
  export function onCleanup(fn: () => void): void {
15
+ if (!sideEffectsEnabled()) return
11
16
  const vNode = node.current!
12
- if (!vNode)
13
- throw new Error("Cannot queue cleanup effect outside of a component")
17
+ if (!vNode) {
18
+ throw new Error("Cannot queue onCleanup effect outside of a component")
19
+ }
14
20
  registerVNodeCleanup(vNode, generateRandomID(10), fn)
15
21
  }
@@ -1,12 +1,18 @@
1
- import { queueSetupEffect } from "./utils.js"
1
+ import { sideEffectsEnabled } from "../utils/index.js"
2
+ import { getVNodeLifecycleHooks, wrapLifecycleHookCallback } from "./utils.js"
2
3
 
3
4
  /**
4
5
  * Registers a callback that runs after the component is first mounted to the DOM.
5
- * Optionally returns a cleanup function that will run when the component unmounts.
6
+ * Optionally returns a cleanup function that will run after the component unmounts.
6
7
  * Intended for use during component setup when the component returns a render function.
7
8
  *
8
- * @see https://kirujs.dev/docs/hooks/onMount
9
+ * @see https://kirujs.dev/docs/api/lifecycles#onMount
9
10
  */
10
11
  export function onMount(fn: () => (() => void) | void): void {
11
- queueSetupEffect(fn)
12
+ if (!sideEffectsEnabled()) return
13
+ const hooks = getVNodeLifecycleHooks()
14
+ if (!hooks) {
15
+ throw new Error("Cannot queue onMount effect outside of a component")
16
+ }
17
+ hooks.post.push(wrapLifecycleHookCallback(fn, hooks.postCleanups))
12
18
  }
@@ -0,0 +1,70 @@
1
+ import { node, setups } from "../globals.js"
2
+ import { signal } from "../signals/index.js"
3
+ import type { Signal } from "../signals/base.js"
4
+ import { createVNodeId } from "../utils/vdom.js"
5
+ import { __DEV__ } from "../env.js"
6
+
7
+ export interface Setup<Props extends {}> {
8
+ readonly derive: <T>(
9
+ selector: (props: Props extends Kiru.FC<infer P> ? P : Props) => T
10
+ ) => Signal<T>
11
+ readonly id: Signal<string>
12
+ }
13
+
14
+ /**
15
+ * Creates a per‑VNode setup context that can be used during
16
+ * component setup to derive props into signals.
17
+ *
18
+ * @see https://kirujs.dev/docs/api/lifecycles#setup
19
+ */
20
+ export function setup<Props extends {}>(): Setup<Props> {
21
+ const vNode = node.current!
22
+ if (__DEV__) {
23
+ if (!vNode) {
24
+ throw new Error("setup() must be called inside a Kiru component")
25
+ }
26
+ if (vNode.render) {
27
+ throw new Error("setup() cannot be used inside a render function")
28
+ }
29
+ }
30
+
31
+ if (setups.has(vNode)) {
32
+ return setups.get(vNode)!
33
+ }
34
+
35
+ const setup = createSetup<Props>(vNode)
36
+ setups.set(vNode, setup)
37
+ return setup
38
+ }
39
+
40
+ function createSetup<Props extends {}>(vNode: Kiru.VNode): Setup<Props> {
41
+ let id: Signal<string>
42
+
43
+ type InferredProps = Props extends Kiru.FC<infer R> ? R : Props
44
+ const propSyncs = (vNode.propSyncs = []) as ((props: InferredProps) => void)[]
45
+
46
+ let prevIndex = -1
47
+
48
+ return {
49
+ derive(selector) {
50
+ const props = { ...vNode.props } as InferredProps
51
+ const sig = signal(selector(props))
52
+ propSyncs.push((p) => (sig.value = selector(p)))
53
+ return sig
54
+ },
55
+ get id() {
56
+ if (!id) {
57
+ id = signal(createVNodeId(vNode))
58
+ prevIndex = vNode.index
59
+ propSyncs.push(() => {
60
+ if (prevIndex !== vNode.index) {
61
+ id.value = createVNodeId(vNode)
62
+ prevIndex = vNode.index
63
+ }
64
+ })
65
+ }
66
+
67
+ return id
68
+ },
69
+ }
70
+ }
@@ -1,32 +1,27 @@
1
1
  import { node } from "../globals.js"
2
- import { sideEffectsEnabled } from "../utils/index.js"
3
2
 
4
- export function queueSetupEffect(
5
- effect: Kiru.LifecycleHookCallback,
6
- opts?: { immediate?: boolean }
7
- ): void {
8
- if (!sideEffectsEnabled()) return
3
+ export function getVNodeLifecycleHooks(): null | NonNullable<
4
+ Kiru.VNode["hooks"]
5
+ > {
9
6
  const vNode = node.current!
10
- if (!vNode)
11
- throw new Error("Cannot queue setup effect outside of a component")
7
+ if (!vNode) return null
12
8
 
13
- const hooks = (vNode.hooks ??= {
9
+ return (vNode.hooks ??= {
14
10
  pre: [],
15
11
  preCleanups: [],
16
12
  post: [],
17
13
  postCleanups: [],
18
14
  })
15
+ }
19
16
 
20
- const [bag, cleanups] = opts?.immediate
21
- ? [hooks.pre, hooks.preCleanups]
22
- : [hooks.post, hooks.postCleanups]
23
-
24
- const wrapped = () => {
25
- const ret = effect()
26
- if (typeof ret === "function") {
27
- cleanups.push(ret)
17
+ export function wrapLifecycleHookCallback(
18
+ callback: Kiru.LifecycleHookCallback,
19
+ cleanups: (() => void)[]
20
+ ): Kiru.LifecycleHookCallback {
21
+ return () => {
22
+ const cleanup = callback()
23
+ if (typeof cleanup === "function") {
24
+ cleanups.push(cleanup)
28
25
  }
29
26
  }
30
-
31
- bag.push(wrapped)
32
27
  }
package/src/index.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { createKiruGlobalContext } from "./globalContext.js"
2
+ import { isBrowser } from "./env.js"
2
3
 
3
4
  export type * from "./types"
4
5
  export * from "./signals/index.js"
@@ -10,6 +11,7 @@ export * from "./customEvents.js"
10
11
  export * from "./devtools.js"
11
12
  export * from "./element.js"
12
13
  export * from "./error.js"
14
+ export { onHmr } from "./hmr.js"
13
15
  export * from "./hooks/index.js"
14
16
  export type { ProfilingEvent, AppStats } from "./profiling.js"
15
17
  export * from "./renderToString.js"
@@ -23,6 +25,6 @@ export {
23
25
  export * from "./statefulPromise.js"
24
26
  export * from "./viewTransitions.js"
25
27
 
26
- if ("window" in globalThis) {
27
- globalThis.window.__kiru ??= createKiruGlobalContext()
28
+ if (isBrowser) {
29
+ window.__kiru ??= createKiruGlobalContext()
28
30
  }
package/src/reconciler.ts CHANGED
@@ -7,7 +7,7 @@ import {
7
7
  propsChanged,
8
8
  } from "./utils/index.js"
9
9
  import { Signal } from "./signals/base.js"
10
- import { __DEV__ } from "./env.js"
10
+ import { __DEV__, isBrowser } from "./env.js"
11
11
  import type { AppHandle } from "./appHandle.js"
12
12
  import { createVNode as createBaseVNode } from "./vNode.js"
13
13
 
@@ -376,7 +376,7 @@ function updateFromMap(
376
376
  }
377
377
 
378
378
  function dev_emitUpdateNode() {
379
- if (!("window" in globalThis)) return
379
+ if (!isBrowser) return
380
380
  window.__kiru.profilingContext?.emit("updateNode", app)
381
381
  }
382
382
 
@@ -479,7 +479,7 @@ function createVNode(
479
479
  const node = createBaseVNode(type, parent, props, key, index)
480
480
  node.flags |= FLAG_PLACEMENT
481
481
 
482
- if (__DEV__ && "window" in globalThis) {
482
+ if (__DEV__ && isBrowser) {
483
483
  window.__kiru.profilingContext?.emit("createNode", app)
484
484
  }
485
485
  return node
@@ -1,7 +1,7 @@
1
1
  import { Signal } from "../signals/base.js"
2
2
  import { isValidTextChild, isVNode } from "../utils/index.js"
3
3
  import { createElement } from "../element.js"
4
- import { __DEV__ } from "../env.js"
4
+ import { __DEV__, isBrowser } from "../env.js"
5
5
  import { KiruError } from "../error.js"
6
6
  import { node } from "../globals.js"
7
7
 
@@ -28,7 +28,7 @@ function HeadContent({ children }: { children: JSX.Children }): JSX.Element {
28
28
  })
29
29
  }
30
30
  }
31
- if ("window" in globalThis) {
31
+ if (isBrowser) {
32
32
  const asArray = Array.isArray(children) ? children : [children]
33
33
  const titleNode = asArray.find(
34
34
  (c) => isVNode(c) && c.type === "title"
@@ -1,9 +1,9 @@
1
- import { __DEV__ } from "../env.js"
1
+ import { __DEV__, isBrowser } from "../env.js"
2
2
  import { fileRouterInstance } from "./globals.js"
3
3
  import type { PageConfig } from "./types"
4
4
 
5
5
  export function definePageConfig<T>(config: PageConfig<T>): PageConfig<T> {
6
- if (__DEV__ && "window" in globalThis) {
6
+ if (__DEV__ && isBrowser) {
7
7
  const filePath = window.__kiru?.HMRContext?.getCurrentFilePath()
8
8
  const fileRouter = fileRouterInstance.current
9
9
  if (filePath && fileRouter) {