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.
- package/dist/components/lazy.d.ts.map +1 -1
- package/dist/components/lazy.js +11 -136
- package/dist/components/lazy.js.map +1 -1
- package/dist/components/suspense.d.ts +7 -6
- package/dist/components/suspense.d.ts.map +1 -1
- package/dist/components/suspense.js +1 -4
- package/dist/components/suspense.js.map +1 -1
- package/dist/constants.d.ts +1 -2
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +1 -2
- package/dist/constants.js.map +1 -1
- package/dist/hmr.d.ts +1 -0
- package/dist/hmr.d.ts.map +1 -1
- package/dist/hmr.js +7 -0
- package/dist/hmr.js.map +1 -1
- package/dist/recursiveRender.d.ts +6 -0
- package/dist/recursiveRender.d.ts.map +1 -0
- package/dist/recursiveRender.js +109 -0
- package/dist/recursiveRender.js.map +1 -0
- package/dist/renderToString.d.ts.map +1 -1
- package/dist/renderToString.js +6 -112
- package/dist/renderToString.js.map +1 -1
- package/dist/router/config.d.ts +3 -0
- package/dist/router/config.d.ts.map +1 -0
- package/dist/router/config.js +13 -0
- package/dist/router/config.js.map +1 -0
- package/dist/router/context.d.ts +15 -0
- package/dist/router/context.d.ts.map +1 -0
- package/dist/router/context.js +11 -0
- package/dist/router/context.js.map +1 -0
- package/dist/router/errors.d.ts +4 -0
- package/dist/router/errors.d.ts.map +1 -0
- package/dist/router/errors.js +7 -0
- package/dist/router/errors.js.map +1 -0
- package/dist/router/fileRouter.d.ts +48 -0
- package/dist/router/fileRouter.d.ts.map +1 -0
- package/dist/router/fileRouter.js +311 -0
- package/dist/router/fileRouter.js.map +1 -0
- package/dist/router/globals.d.ts +5 -0
- package/dist/router/globals.d.ts.map +1 -0
- package/dist/router/globals.js +4 -0
- package/dist/router/globals.js.map +1 -0
- package/dist/router/index.d.ts +7 -0
- package/dist/router/index.d.ts.map +1 -0
- package/dist/router/index.js +7 -0
- package/dist/router/index.js.map +1 -0
- package/dist/router/link.d.ts +8 -0
- package/dist/router/link.d.ts.map +1 -0
- package/dist/router/link.js +15 -0
- package/dist/router/link.js.map +1 -0
- package/dist/router/types.d.ts +63 -0
- package/dist/router/types.d.ts.map +1 -0
- package/dist/router/types.internal.d.ts +12 -0
- package/dist/router/types.internal.d.ts.map +1 -0
- package/dist/router/types.internal.js +2 -0
- package/dist/router/types.internal.js.map +1 -0
- package/dist/router/types.js +2 -0
- package/dist/router/types.js.map +1 -0
- package/dist/ssr/server.d.ts.map +1 -1
- package/dist/ssr/server.js +5 -109
- package/dist/ssr/server.js.map +1 -1
- package/dist/types.d.ts +2 -2
- package/dist/types.d.ts.map +1 -1
- package/dist/types.utils.d.ts +1 -8
- package/dist/types.utils.d.ts.map +1 -1
- package/dist/utils/vdom.d.ts.map +1 -1
- package/dist/utils/vdom.js +2 -5
- package/dist/utils/vdom.js.map +1 -1
- package/package.json +4 -8
- package/src/components/lazy.ts +12 -169
- package/src/components/suspense.ts +8 -10
- package/src/constants.ts +0 -2
- package/src/hmr.ts +8 -0
- package/src/recursiveRender.ts +127 -0
- package/src/renderToString.ts +7 -137
- package/src/router/config.ts +15 -0
- package/src/router/context.ts +23 -0
- package/src/router/errors.ts +6 -0
- package/src/router/fileRouter.ts +475 -0
- package/src/router/globals.ts +5 -0
- package/src/router/index.ts +6 -0
- package/src/router/link.ts +32 -0
- package/src/router/types.internal.ts +14 -0
- package/src/router/types.ts +81 -0
- package/src/ssr/server.ts +6 -136
- package/src/types.ts +0 -2
- package/src/types.utils.ts +1 -14
- package/src/utils/vdom.ts +1 -5
- package/dist/components/router/index.d.ts +0 -3
- package/dist/components/router/index.d.ts.map +0 -1
- package/dist/components/router/index.js +0 -3
- package/dist/components/router/index.js.map +0 -1
- package/dist/components/router/route.d.ts +0 -46
- package/dist/components/router/route.d.ts.map +0 -1
- package/dist/components/router/route.js +0 -8
- package/dist/components/router/route.js.map +0 -1
- package/dist/components/router/router.d.ts +0 -62
- package/dist/components/router/router.d.ts.map +0 -1
- package/dist/components/router/router.js +0 -177
- package/dist/components/router/router.js.map +0 -1
- package/dist/components/router/routerUtils.d.ts +0 -5
- package/dist/components/router/routerUtils.d.ts.map +0 -1
- package/dist/components/router/routerUtils.js +0 -39
- package/dist/components/router/routerUtils.js.map +0 -1
- package/dist/ssr/hydrationBoundary.d.ts +0 -27
- package/dist/ssr/hydrationBoundary.d.ts.map +0 -1
- package/dist/ssr/hydrationBoundary.js +0 -30
- package/dist/ssr/hydrationBoundary.js.map +0 -1
- package/dist/ssr/index.d.ts +0 -2
- package/dist/ssr/index.d.ts.map +0 -1
- package/dist/ssr/index.js +0 -2
- package/dist/ssr/index.js.map +0 -1
- package/src/components/router/index.ts +0 -2
- package/src/components/router/route.ts +0 -51
- package/src/components/router/router.ts +0 -273
- package/src/components/router/routerUtils.ts +0 -49
- package/src/ssr/hydrationBoundary.ts +0 -63
- package/src/ssr/index.ts +0 -1
package/src/components/lazy.ts
CHANGED
|
@@ -1,18 +1,7 @@
|
|
|
1
1
|
import { createElement } from "../element.js"
|
|
2
2
|
import { __DEV__ } from "../env.js"
|
|
3
|
-
import {
|
|
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
|
|
105
|
-
|
|
106
|
-
if (!cachedState
|
|
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(
|
|
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 (
|
|
223
|
-
|
|
65
|
+
if (__DEV__) {
|
|
66
|
+
return createElement(cachedState.result, rest)
|
|
224
67
|
}
|
|
225
68
|
return createElement(cachedState.result, rest)
|
|
226
69
|
}
|
|
227
|
-
LazyComponent.displayName = "
|
|
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
|
-
|
|
72
|
-
|
|
73
|
-
|
|
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
|
|
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:
|
|
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
|
+
}
|
package/src/renderToString.ts
CHANGED
|
@@ -1,148 +1,18 @@
|
|
|
1
|
-
import {
|
|
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 {
|
|
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
|
-
|
|
31
|
-
const ctx:
|
|
9
|
+
let result = ""
|
|
10
|
+
const ctx: RecursiveRenderContext = {
|
|
32
11
|
write(chunk) {
|
|
33
|
-
|
|
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
|
-
|
|
15
|
+
recursiveRender(ctx, Fragment({ children: element }), null, 0)
|
|
44
16
|
renderMode.current = prev
|
|
45
|
-
return
|
|
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
|
+
}
|