kiru 0.50.0-preview.0 → 0.50.0-preview.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (118) hide show
  1. package/dist/components/lazy.d.ts.map +1 -1
  2. package/dist/components/lazy.js +11 -136
  3. package/dist/components/lazy.js.map +1 -1
  4. package/dist/components/suspense.d.ts +7 -6
  5. package/dist/components/suspense.d.ts.map +1 -1
  6. package/dist/components/suspense.js +1 -4
  7. package/dist/components/suspense.js.map +1 -1
  8. package/dist/constants.d.ts +1 -2
  9. package/dist/constants.d.ts.map +1 -1
  10. package/dist/constants.js +1 -2
  11. package/dist/constants.js.map +1 -1
  12. package/dist/hmr.d.ts +1 -0
  13. package/dist/hmr.d.ts.map +1 -1
  14. package/dist/hmr.js +7 -0
  15. package/dist/hmr.js.map +1 -1
  16. package/dist/recursiveRender.d.ts +6 -0
  17. package/dist/recursiveRender.d.ts.map +1 -0
  18. package/dist/recursiveRender.js +109 -0
  19. package/dist/recursiveRender.js.map +1 -0
  20. package/dist/renderToString.d.ts.map +1 -1
  21. package/dist/renderToString.js +6 -112
  22. package/dist/renderToString.js.map +1 -1
  23. package/dist/router/config.d.ts +3 -0
  24. package/dist/router/config.d.ts.map +1 -0
  25. package/dist/router/config.js +13 -0
  26. package/dist/router/config.js.map +1 -0
  27. package/dist/router/context.d.ts +15 -0
  28. package/dist/router/context.d.ts.map +1 -0
  29. package/dist/router/context.js +11 -0
  30. package/dist/router/context.js.map +1 -0
  31. package/dist/router/errors.d.ts +4 -0
  32. package/dist/router/errors.d.ts.map +1 -0
  33. package/dist/router/errors.js +7 -0
  34. package/dist/router/errors.js.map +1 -0
  35. package/dist/router/fileRouter.d.ts +48 -0
  36. package/dist/router/fileRouter.d.ts.map +1 -0
  37. package/dist/router/fileRouter.js +311 -0
  38. package/dist/router/fileRouter.js.map +1 -0
  39. package/dist/router/globals.d.ts +5 -0
  40. package/dist/router/globals.d.ts.map +1 -0
  41. package/dist/router/globals.js +4 -0
  42. package/dist/router/globals.js.map +1 -0
  43. package/dist/router/index.d.ts +7 -0
  44. package/dist/router/index.d.ts.map +1 -0
  45. package/dist/router/index.js +7 -0
  46. package/dist/router/index.js.map +1 -0
  47. package/dist/router/link.d.ts +8 -0
  48. package/dist/router/link.d.ts.map +1 -0
  49. package/dist/router/link.js +15 -0
  50. package/dist/router/link.js.map +1 -0
  51. package/dist/router/types.d.ts +63 -0
  52. package/dist/router/types.d.ts.map +1 -0
  53. package/dist/router/types.internal.d.ts +12 -0
  54. package/dist/router/types.internal.d.ts.map +1 -0
  55. package/dist/router/types.internal.js +2 -0
  56. package/dist/router/types.internal.js.map +1 -0
  57. package/dist/router/types.js +2 -0
  58. package/dist/router/types.js.map +1 -0
  59. package/dist/ssr/server.d.ts.map +1 -1
  60. package/dist/ssr/server.js +5 -109
  61. package/dist/ssr/server.js.map +1 -1
  62. package/dist/types.d.ts +2 -2
  63. package/dist/types.d.ts.map +1 -1
  64. package/dist/types.utils.d.ts +1 -8
  65. package/dist/types.utils.d.ts.map +1 -1
  66. package/dist/utils/vdom.d.ts.map +1 -1
  67. package/dist/utils/vdom.js +2 -5
  68. package/dist/utils/vdom.js.map +1 -1
  69. package/package.json +4 -8
  70. package/src/components/lazy.ts +12 -169
  71. package/src/components/suspense.ts +8 -10
  72. package/src/constants.ts +0 -2
  73. package/src/hmr.ts +8 -0
  74. package/src/recursiveRender.ts +127 -0
  75. package/src/renderToString.ts +7 -137
  76. package/src/router/config.ts +15 -0
  77. package/src/router/context.ts +23 -0
  78. package/src/router/errors.ts +6 -0
  79. package/src/router/fileRouter.ts +475 -0
  80. package/src/router/globals.ts +5 -0
  81. package/src/router/index.ts +6 -0
  82. package/src/router/link.ts +32 -0
  83. package/src/router/types.internal.ts +14 -0
  84. package/src/router/types.ts +81 -0
  85. package/src/ssr/server.ts +6 -136
  86. package/src/types.ts +0 -2
  87. package/src/types.utils.ts +1 -14
  88. package/src/utils/vdom.ts +1 -5
  89. package/dist/components/router/index.d.ts +0 -3
  90. package/dist/components/router/index.d.ts.map +0 -1
  91. package/dist/components/router/index.js +0 -3
  92. package/dist/components/router/index.js.map +0 -1
  93. package/dist/components/router/route.d.ts +0 -46
  94. package/dist/components/router/route.d.ts.map +0 -1
  95. package/dist/components/router/route.js +0 -8
  96. package/dist/components/router/route.js.map +0 -1
  97. package/dist/components/router/router.d.ts +0 -62
  98. package/dist/components/router/router.d.ts.map +0 -1
  99. package/dist/components/router/router.js +0 -177
  100. package/dist/components/router/router.js.map +0 -1
  101. package/dist/components/router/routerUtils.d.ts +0 -5
  102. package/dist/components/router/routerUtils.d.ts.map +0 -1
  103. package/dist/components/router/routerUtils.js +0 -39
  104. package/dist/components/router/routerUtils.js.map +0 -1
  105. package/dist/ssr/hydrationBoundary.d.ts +0 -27
  106. package/dist/ssr/hydrationBoundary.d.ts.map +0 -1
  107. package/dist/ssr/hydrationBoundary.js +0 -30
  108. package/dist/ssr/hydrationBoundary.js.map +0 -1
  109. package/dist/ssr/index.d.ts +0 -2
  110. package/dist/ssr/index.d.ts.map +0 -1
  111. package/dist/ssr/index.js +0 -2
  112. package/dist/ssr/index.js.map +0 -1
  113. package/src/components/router/index.ts +0 -2
  114. package/src/components/router/route.ts +0 -51
  115. package/src/components/router/router.ts +0 -273
  116. package/src/components/router/routerUtils.ts +0 -49
  117. package/src/ssr/hydrationBoundary.ts +0 -63
  118. package/src/ssr/index.ts +0 -1
@@ -1,18 +1,7 @@
1
1
  import { createElement } from "../element.js"
2
2
  import { __DEV__ } from "../env.js"
3
- import { KiruError } from "../error.js"
4
- import { node, renderMode } from "../globals.js"
5
- import { useContext } from "../hooks/useContext.js"
6
- import { useRef } from "../hooks/useRef.js"
3
+ import { renderMode } from "../globals.js"
7
4
  import { useRequestUpdate } from "../hooks/utils.js"
8
- import { hydrationStack } from "../hydration.js"
9
- import { flushSync, nextIdle } from "../scheduler.js"
10
- import {
11
- HYDRATION_BOUNDARY_MARKER,
12
- HydrationBoundaryContext,
13
- } from "../ssr/hydrationBoundary.js"
14
- import { noop } from "../utils/index.js"
15
- import type { SomeDom } from "../types.utils"
16
5
 
17
6
  interface FCModule {
18
7
  default: Kiru.FC<any>
@@ -25,7 +14,6 @@ type InferLazyImportProps<T extends LazyImportValue> = T extends FCModule
25
14
  : Kiru.InferProps<T>
26
15
 
27
16
  interface LazyState {
28
- fn: string
29
17
  promise: Promise<LazyImportValue>
30
18
  result: Kiru.FC | null
31
19
  }
@@ -40,191 +28,46 @@ const lazyCache: Map<string, LazyState> =
40
28
  (window.__KIRU_LAZY_CACHE ??= new Map<string, LazyState>())
41
29
  : new Map<string, LazyState>()
42
30
 
43
- function consumeHydrationBoundaryChildren(parentNode: Kiru.VNode): {
44
- parent: HTMLElement
45
- childNodes: Node[]
46
- startIndex: number
47
- } {
48
- const boundaryStart = hydrationStack.currentChild()
49
- if (
50
- boundaryStart?.nodeType !== Node.COMMENT_NODE ||
51
- boundaryStart.nodeValue !== HYDRATION_BOUNDARY_MARKER
52
- ) {
53
- throw new KiruError({
54
- message: "Invalid HydrationBoundary node. This is likely a bug in Kiru.",
55
- fatal: true,
56
- vNode: parentNode,
57
- })
58
- }
59
- const parent = boundaryStart.parentElement!
60
- const childNodes: Node[] = []
61
- const isBoundaryEnd = (n: Node) => {
62
- return (
63
- n.nodeType === Node.COMMENT_NODE &&
64
- n.nodeValue === "/" + HYDRATION_BOUNDARY_MARKER
65
- )
66
- }
67
- let n = boundaryStart.nextSibling
68
- boundaryStart.remove()
69
- const startIndex =
70
- hydrationStack.childIdxStack[hydrationStack.childIdxStack.length - 1]
71
- while (n && !isBoundaryEnd(n)) {
72
- childNodes.push(n)
73
- hydrationStack.bumpChildIndex()
74
- n = n.nextSibling
75
- }
76
- const boundaryEnd = hydrationStack.currentChild()
77
- if (!isBoundaryEnd(boundaryEnd)) {
78
- throw new KiruError({
79
- message: "Invalid HydrationBoundary node. This is likely a bug in Kiru.",
80
- fatal: true,
81
- vNode: parentNode,
82
- })
83
- }
84
- boundaryEnd.remove()
85
- return { parent, childNodes, startIndex }
86
- }
87
-
88
31
  export function lazy<T extends LazyImportValue>(
89
32
  componentPromiseFn: () => Promise<T>
90
33
  ): Kiru.FC<LazyComponentProps<T>> {
91
34
  function LazyComponent(props: LazyComponentProps<T>) {
92
35
  const { fallback = null, ...rest } = props
93
- const hydrationCtx = useContext(HydrationBoundaryContext, false)
94
- const needsHydration = useRef(
95
- hydrationCtx && renderMode.current === "hydrate"
96
- )
97
- const abortHydration = useRef(noop)
98
36
  const requestUpdate = useRequestUpdate()
99
37
  if (renderMode.current === "string" || renderMode.current === "stream") {
100
38
  return fallback
101
39
  }
102
40
 
103
- const fn = componentPromiseFn.toString()
104
- const withoutQuery = removeQueryString(fn)
105
- const cachedState = lazyCache.get(withoutQuery)
106
- if (!cachedState || cachedState.fn !== fn) {
41
+ const fn = removeQueryString(componentPromiseFn.toString())
42
+ const cachedState = lazyCache.get(fn)
43
+
44
+ if (!cachedState) {
107
45
  const promise = componentPromiseFn()
108
46
  const state: LazyState = {
109
- fn,
110
47
  promise,
111
48
  result: null,
112
49
  }
113
- lazyCache.set(withoutQuery, state)
114
-
115
- const ready = promise.then((componentOrModule) => {
50
+ lazyCache.set(fn, state)
51
+ promise.then((componentOrModule) => {
116
52
  state.result =
117
53
  typeof componentOrModule === "function"
118
54
  ? componentOrModule
119
55
  : componentOrModule.default
56
+ requestUpdate()
120
57
  })
121
-
122
- if (!needsHydration.current) {
123
- ready.then(() => requestUpdate())
124
- return fallback
125
- }
126
-
127
- const thisNode = node.current!
128
-
129
- abortHydration.current = () => {
130
- for (const child of childNodes) {
131
- if (child instanceof Element) {
132
- hydrationStack.resetEvents(child)
133
- }
134
- child.parentNode?.removeChild(child)
135
- }
136
- needsHydration.current = false
137
- delete thisNode.lastChildDom
138
- }
139
-
140
- if (__DEV__) {
141
- window.__kiru?.HMRContext?.onHmr(() => {
142
- if (needsHydration.current) {
143
- abortHydration.current()
144
- }
145
- })
146
- }
147
-
148
- const { parent, childNodes, startIndex } =
149
- consumeHydrationBoundaryChildren(thisNode)
150
-
151
- thisNode.lastChildDom = childNodes[childNodes.length - 1] as SomeDom
152
-
153
- for (const child of childNodes) {
154
- if (child instanceof Element) {
155
- hydrationStack.captureEvents(child)
156
- }
157
- }
158
- const hydrate = () => {
159
- if (needsHydration.current === false) return
160
-
161
- nextIdle(() => {
162
- delete thisNode.lastChildDom
163
- needsHydration.current = false
164
- hydrationStack.push(parent)
165
- hydrationStack.childIdxStack[
166
- hydrationStack.childIdxStack.length - 1
167
- ] = startIndex
168
- const prev = renderMode.current
169
- /**
170
- * must call requestUpdate before setting renderMode
171
- * to hydrate, otherwise the update will be postponed
172
- * and flushSync will have no effect
173
- */
174
- requestUpdate()
175
- renderMode.current = "hydrate"
176
- flushSync()
177
- renderMode.current = prev
178
- for (const child of childNodes) {
179
- if (child instanceof Element) {
180
- hydrationStack.releaseEvents(child)
181
- }
182
- }
183
- })
184
- }
185
-
186
- /**
187
- * once the promise resolves, we need to act according
188
- * to the HydrationBoundaryContext 'mode'.
189
- *
190
- * - with 'eager', we just hydrate the children immediately
191
- * - with 'lazy', we'll wait for user interaction before hydrating
192
- */
193
-
194
- if (hydrationCtx.mode === "eager") {
195
- ready.then(hydrate)
196
- return null
197
- }
198
- const interactionEvents = hydrationCtx.events
199
- const onInteraction = (e: Event) => {
200
- const tgt = e.target
201
- if (
202
- tgt instanceof Element &&
203
- childNodes.some((child) => child.contains(tgt))
204
- ) {
205
- interactionEvents.forEach((evtName) => {
206
- window.removeEventListener(evtName, onInteraction)
207
- })
208
- ready.then(hydrate)
209
- }
210
- }
211
- interactionEvents.forEach((evtName) => {
212
- window.addEventListener(evtName, onInteraction)
213
- })
214
-
215
- return null
58
+ return fallback
216
59
  }
217
60
 
218
61
  if (cachedState.result === null) {
219
62
  cachedState.promise.then(requestUpdate)
220
63
  return fallback
221
64
  }
222
- if (needsHydration.current) {
223
- abortHydration.current()
65
+ if (__DEV__) {
66
+ return createElement(cachedState.result, rest)
224
67
  }
225
68
  return createElement(cachedState.result, rest)
226
69
  }
227
- LazyComponent.displayName = "Kiru.lazy"
70
+ LazyComponent.displayName = "Kaioken.lazy"
228
71
  return LazyComponent
229
72
  }
230
73
 
@@ -11,7 +11,7 @@ import { getCurrentVNode } from "../utils/index.js"
11
11
  import { $SUSPENSE_THROW, PREFETCHED_DATA_EVENT } from "../constants.js"
12
12
  import { Signal, useSignal } from "../signals/index.js"
13
13
 
14
- export type { SuspenseProps, UsePromiseState }
14
+ export type { SuspenseProps, UsePromiseCallbackContext, UsePromiseState }
15
15
  export { Suspense, isSuspenseThrowValue, usePromise }
16
16
 
17
17
  type StatefulPromiseValues<T extends readonly Kiru.StatefulPromise<unknown>[]> =
@@ -46,9 +46,7 @@ function Suspense<
46
46
  case "stream":
47
47
  case "string":
48
48
  throw {
49
- fallback,
50
- pendingData: promiseArray,
51
- [$SUSPENSE_THROW]: true,
49
+ [$SUSPENSE_THROW]: { fallback, pending: promiseArray },
52
50
  } satisfies SuspenseThrowValue
53
51
 
54
52
  case "dom":
@@ -68,9 +66,10 @@ function Suspense<
68
66
  }
69
67
 
70
68
  interface SuspenseThrowValue {
71
- fallback?: JSX.Element
72
- pendingData?: Kiru.StatefulPromise<unknown>[]
73
- [$SUSPENSE_THROW]: true
69
+ [$SUSPENSE_THROW]: {
70
+ fallback?: JSX.Element
71
+ pending: Kiru.StatefulPromise<unknown>[]
72
+ }
74
73
  }
75
74
 
76
75
  /**
@@ -113,7 +112,6 @@ function resolveHydrationPromise<T>(
113
112
  }
114
113
  }
115
114
 
116
- console.log("listening for prefetch event")
117
115
  window.addEventListener(PREFETCHED_DATA_EVENT, onDataEvent)
118
116
  signal.addEventListener("abort", () => {
119
117
  window.removeEventListener(PREFETCHED_DATA_EVENT, onDataEvent)
@@ -124,7 +122,7 @@ function resolveHydrationPromise<T>(
124
122
 
125
123
  const nodeToPromiseIndex = new WeakMap<Kiru.VNode, number>()
126
124
 
127
- interface UsePromiseContext {
125
+ interface UsePromiseCallbackContext {
128
126
  signal: AbortSignal
129
127
  }
130
128
 
@@ -135,7 +133,7 @@ interface UsePromiseState<T> {
135
133
  }
136
134
 
137
135
  function usePromise<T>(
138
- callback: (ctx: UsePromiseContext) => Promise<T>,
136
+ callback: (ctx: UsePromiseCallbackContext) => Promise<T>,
139
137
  deps: unknown[]
140
138
  ): UsePromiseState<T> {
141
139
  const id = useId()
package/src/constants.ts CHANGED
@@ -7,7 +7,6 @@ export {
7
7
  $HMR_ACCEPT,
8
8
  $MEMO,
9
9
  $ERROR_BOUNDARY,
10
- $HYDRATION_BOUNDARY,
11
10
  $SUSPENSE_THROW,
12
11
  CONSECUTIVE_DIRTY_LIMIT,
13
12
  PREFETCHED_DATA_EVENT,
@@ -31,7 +30,6 @@ const $KIRU_ERROR = Symbol.for("kiru.error")
31
30
  const $HMR_ACCEPT = Symbol.for("kiru.hmrAccept")
32
31
  const $MEMO = Symbol.for("kiru.memo")
33
32
  const $ERROR_BOUNDARY = Symbol.for("kiru.errorBoundary")
34
- const $HYDRATION_BOUNDARY = Symbol.for("kiru.hydrationBoundary")
35
33
  const $SUSPENSE_THROW = Symbol.for("kiru.suspenseThrow")
36
34
 
37
35
  const CONSECUTIVE_DIRTY_LIMIT = 50
package/src/hmr.ts CHANGED
@@ -51,6 +51,7 @@ type HotVarRegistrationEntry = {
51
51
  export function createHMRContext() {
52
52
  type FilePath = string
53
53
  const moduleMap = new Map<FilePath, ModuleMemory>()
54
+ let currentModuleFilePath: string | null = null
54
55
  let currentModuleMemory: ModuleMemory | null = null
55
56
  let isModuleReplacementExecution = false
56
57
  const isReplacement = () => isModuleReplacementExecution
@@ -75,6 +76,7 @@ export function createHMRContext() {
75
76
  while (onHmrCallbacks.length) onHmrCallbacks.shift()!()
76
77
  }
77
78
  currentModuleMemory = mod!
79
+ currentModuleFilePath = filePath
78
80
  }
79
81
 
80
82
  const register = (
@@ -150,6 +152,9 @@ export function createHMRContext() {
150
152
  currentModuleMemory.unnamedWatchers.length = tmpUnnamedWatchers.length
151
153
  tmpUnnamedWatchers.length = 0
152
154
  }
155
+
156
+ currentModuleMemory = null
157
+ currentModuleFilePath = null
153
158
  }
154
159
 
155
160
  const signals = {
@@ -171,5 +176,8 @@ export function createHMRContext() {
171
176
  isReplacement,
172
177
  signals,
173
178
  onHmr,
179
+ getCurrentFilePath() {
180
+ return currentModuleFilePath
181
+ },
174
182
  }
175
183
  }
@@ -0,0 +1,127 @@
1
+ import { node, hookIndex } from "./globals.js"
2
+ import {
3
+ isVNode,
4
+ encodeHtmlEntities,
5
+ propsToElementAttributes,
6
+ isExoticType,
7
+ assertValidElementProps,
8
+ } from "./utils/index.js"
9
+ import { Signal } from "./signals/base.js"
10
+ import { $ERROR_BOUNDARY, voidElements, $SUSPENSE_THROW } from "./constants.js"
11
+ import { __DEV__ } from "./env.js"
12
+ import { isSuspenseThrowValue } from "./components/suspense.js"
13
+ import type { ErrorBoundaryNode } from "./types.utils"
14
+
15
+ export interface RecursiveRenderContext {
16
+ write(chunk: string): void
17
+ onPending?: (data: Kiru.StatefulPromise<unknown>[]) => void
18
+ }
19
+
20
+ export function recursiveRender(
21
+ ctx: RecursiveRenderContext,
22
+ el: unknown,
23
+ parent: Kiru.VNode | null,
24
+ idx: number
25
+ ): void {
26
+ if (el === null) return
27
+ if (el === undefined) return
28
+ if (typeof el === "boolean") return
29
+ if (typeof el === "string") {
30
+ return ctx.write(encodeHtmlEntities(el))
31
+ }
32
+ if (typeof el === "number" || typeof el === "bigint") {
33
+ return ctx.write(el.toString())
34
+ }
35
+ if (el instanceof Array) {
36
+ return el.forEach((c, i) => recursiveRender(ctx, c, parent, i))
37
+ }
38
+ if (Signal.isSignal(el)) {
39
+ return ctx.write(String(el.peek()))
40
+ }
41
+ if (!isVNode(el)) {
42
+ return ctx.write(String(el))
43
+ }
44
+ el.parent = parent
45
+ el.depth = (parent?.depth ?? -1) + 1
46
+ el.index = idx
47
+ const { type, props = {} } = el
48
+ if (type === "#text") {
49
+ return ctx.write(encodeHtmlEntities(props.nodeValue ?? ""))
50
+ }
51
+
52
+ const children = props.children
53
+ if (isExoticType(type)) {
54
+ if (type === $ERROR_BOUNDARY) {
55
+ let boundaryBuffer = ""
56
+ const pending = new Set<Kiru.StatefulPromise<unknown>>()
57
+ const boundaryCtx: RecursiveRenderContext = {
58
+ write(chunk) {
59
+ boundaryBuffer += chunk
60
+ },
61
+ onPending(data) {
62
+ data.forEach((p) => pending.add(p))
63
+ },
64
+ }
65
+ try {
66
+ recursiveRender(boundaryCtx, children, el, idx)
67
+ // flush successful render
68
+ ctx.write(boundaryBuffer)
69
+ ctx.onPending?.([...pending])
70
+ } catch (error) {
71
+ if (isSuspenseThrowValue(error)) {
72
+ throw error
73
+ }
74
+ const e = error instanceof Error ? error : new Error(String(error))
75
+ const { fallback, onError } = props as ErrorBoundaryNode["props"]
76
+ onError?.(e)
77
+ const fallbackContent =
78
+ typeof fallback === "function" ? fallback(e) : fallback
79
+ recursiveRender(ctx, fallbackContent, el, 0)
80
+ }
81
+ return
82
+ }
83
+
84
+ recursiveRender(ctx, children, el, idx)
85
+ return
86
+ }
87
+
88
+ if (typeof type !== "string") {
89
+ try {
90
+ hookIndex.current = 0
91
+ node.current = el
92
+ const res = type(props)
93
+ recursiveRender(ctx, res, el, idx)
94
+ return
95
+ } catch (error) {
96
+ if (isSuspenseThrowValue(error)) {
97
+ const { fallback, pending } = error[$SUSPENSE_THROW]
98
+ ctx.onPending?.(pending)
99
+ return recursiveRender(ctx, fallback, el, 0)
100
+ }
101
+ throw error
102
+ } finally {
103
+ node.current = null
104
+ }
105
+ }
106
+
107
+ if (__DEV__) assertValidElementProps(el)
108
+ const attrs = propsToElementAttributes(props)
109
+ ctx.write(`<${type}${attrs.length ? ` ${attrs}` : ""}>`)
110
+
111
+ if (voidElements.has(type)) return
112
+
113
+ if ("innerHTML" in props) {
114
+ ctx.write(
115
+ String(
116
+ Signal.isSignal(props.innerHTML)
117
+ ? props.innerHTML.peek()
118
+ : props.innerHTML
119
+ )
120
+ )
121
+ } else if (Array.isArray(children)) {
122
+ children.forEach((c, i) => recursiveRender(ctx, c, el, i))
123
+ } else {
124
+ recursiveRender(ctx, children, el, 0)
125
+ }
126
+ ctx.write(`</${type}>`)
127
+ }
@@ -1,148 +1,18 @@
1
- import { node, renderMode } from "./globals.js"
1
+ import { renderMode } from "./globals.js"
2
2
  import { Fragment } from "./element.js"
3
- import {
4
- isVNode,
5
- encodeHtmlEntities,
6
- propsToElementAttributes,
7
- isExoticType,
8
- assertValidElementProps,
9
- } from "./utils/index.js"
10
- import { Signal } from "./signals/base.js"
11
- import {
12
- $ERROR_BOUNDARY,
13
- $HYDRATION_BOUNDARY,
14
- voidElements,
15
- } from "./constants.js"
16
- import { HYDRATION_BOUNDARY_MARKER } from "./ssr/hydrationBoundary.js"
17
3
  import { __DEV__ } from "./env.js"
18
- import { ErrorBoundaryNode } from "./types.utils.js"
19
- import { isSuspenseThrowValue } from "./components/suspense.js"
20
-
21
- interface StringRenderContext {
22
- write(chunk: string): void
23
- beginNewBoundary(): number
24
- resetBoundary(idx: number): void
25
- }
4
+ import { recursiveRender, RecursiveRenderContext } from "./recursiveRender.js"
26
5
 
27
6
  export function renderToString(element: JSX.Element) {
28
7
  const prev = renderMode.current
29
8
  renderMode.current = "string"
30
- const parts: string[] = [""]
31
- const ctx: StringRenderContext = {
9
+ let result = ""
10
+ const ctx: RecursiveRenderContext = {
32
11
  write(chunk) {
33
- parts[parts.length - 1] += chunk
34
- },
35
- beginNewBoundary() {
36
- parts.push("")
37
- return parts.length - 1
38
- },
39
- resetBoundary(idx) {
40
- parts[idx] = ""
12
+ result += chunk
41
13
  },
42
14
  }
43
- renderToString_internal(ctx, Fragment({ children: element }), null, 0)
15
+ recursiveRender(ctx, Fragment({ children: element }), null, 0)
44
16
  renderMode.current = prev
45
- return parts.join("")
46
- }
47
-
48
- function renderToString_internal(
49
- ctx: StringRenderContext,
50
- el: unknown,
51
- parent: Kiru.VNode | null,
52
- idx: number
53
- ): void {
54
- if (el === null) return
55
- if (el === undefined) return
56
- if (typeof el === "boolean") return
57
- if (typeof el === "string") {
58
- return ctx.write(encodeHtmlEntities(el))
59
- }
60
- if (typeof el === "number" || typeof el === "bigint") {
61
- return ctx.write(el.toString())
62
- }
63
- if (el instanceof Array) {
64
- return el.forEach((c, i) => renderToString_internal(ctx, c, parent, i))
65
- }
66
- if (Signal.isSignal(el)) {
67
- return ctx.write(String(el.peek()))
68
- }
69
- if (!isVNode(el)) {
70
- return ctx.write(String(el))
71
- }
72
- el.parent = parent
73
- el.depth = (parent?.depth ?? -1) + 1
74
- el.index = idx
75
- const { type, props = {} } = el
76
- if (type === "#text") {
77
- return ctx.write(encodeHtmlEntities(props.nodeValue ?? ""))
78
- }
79
-
80
- const children = props.children
81
- if (isExoticType(type)) {
82
- if (type === $HYDRATION_BOUNDARY) {
83
- ctx.write(`<!--${HYDRATION_BOUNDARY_MARKER}-->`)
84
- renderToString_internal(ctx, children, el, idx)
85
- ctx.write(`<!--/${HYDRATION_BOUNDARY_MARKER}-->`)
86
- return
87
- }
88
-
89
- if (type === $ERROR_BOUNDARY) {
90
- const boundaryIdx = ctx.beginNewBoundary()
91
- try {
92
- renderToString_internal(ctx, children, el, idx)
93
- } catch (error) {
94
- if (isSuspenseThrowValue(error)) {
95
- throw error
96
- }
97
- ctx.resetBoundary(boundaryIdx)
98
- const e = error instanceof Error ? error : new Error(String(error))
99
- const { fallback, onError } = props as ErrorBoundaryNode["props"]
100
- onError?.(e)
101
- const fallbackContent =
102
- typeof fallback === "function" ? fallback(e) : fallback
103
- renderToString_internal(ctx, fallbackContent, el, 0)
104
- }
105
- return
106
- }
107
-
108
- renderToString_internal(ctx, children, el, idx)
109
- return
110
- }
111
-
112
- if (typeof type !== "string") {
113
- try {
114
- node.current = el
115
- const res = type(props)
116
- renderToString_internal(ctx, res, el, idx)
117
- return
118
- } catch (error) {
119
- if (isSuspenseThrowValue(error)) {
120
- return renderToString_internal(ctx, error.fallback, el, 0)
121
- }
122
- throw error
123
- } finally {
124
- node.current = null
125
- }
126
- }
127
-
128
- if (__DEV__) assertValidElementProps(el)
129
- const attrs = propsToElementAttributes(props)
130
- ctx.write(`<${type}${attrs.length ? ` ${attrs}` : ""}>`)
131
-
132
- if (voidElements.has(type)) return
133
-
134
- if ("innerHTML" in props) {
135
- ctx.write(
136
- String(
137
- Signal.isSignal(props.innerHTML)
138
- ? props.innerHTML.peek()
139
- : props.innerHTML
140
- )
141
- )
142
- } else if (Array.isArray(children)) {
143
- children.forEach((c, i) => renderToString_internal(ctx, c, el, i))
144
- } else {
145
- renderToString_internal(ctx, children, el, 0)
146
- }
147
- ctx.write(`</${type}>`)
17
+ return result
148
18
  }
@@ -0,0 +1,15 @@
1
+ import { __DEV__ } from "../env.js"
2
+ import { fileRouterInstance } from "./globals.js"
3
+ import type { PageConfig } from "./types"
4
+
5
+ export function definePageConfig<T extends PageConfig>(config: T): T {
6
+ if (__DEV__) {
7
+ const filePath = window.__kiru?.HMRContext?.getCurrentFilePath()
8
+ const fileRouter = fileRouterInstance.current
9
+ if (filePath && fileRouter) {
10
+ fileRouter.onPageConfigDefined(filePath, config)
11
+ }
12
+ }
13
+
14
+ return config
15
+ }
@@ -0,0 +1,23 @@
1
+ import { createContext } from "../context.js"
2
+ import { __DEV__ } from "../env.js"
3
+ import { useContext } from "../hooks/index.js"
4
+ import type { RouteQuery, RouterState } from "./types.js"
5
+
6
+ export interface FileRouterContextType {
7
+ state: RouterState
8
+ navigate: (
9
+ path: string,
10
+ options?: { replace?: boolean; transition?: boolean }
11
+ ) => Promise<void>
12
+ reload: (options?: { transition?: boolean }) => Promise<void>
13
+ setQuery: (query: RouteQuery) => void
14
+ }
15
+
16
+ export const RouterContext = createContext<FileRouterContextType>(null!)
17
+ if (__DEV__) {
18
+ RouterContext.displayName = "RouterContext"
19
+ }
20
+
21
+ export function useFileRouter(): FileRouterContextType {
22
+ return useContext(RouterContext)
23
+ }
@@ -0,0 +1,6 @@
1
+ export class FileRouterDataLoadError extends Error {
2
+ constructor(cause: unknown) {
3
+ super("An error occurred while loading route data")
4
+ this.cause = cause
5
+ }
6
+ }