kiru 0.54.0-preview.0 → 0.54.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/derive.d.ts +1 -1
- package/dist/components/derive.d.ts.map +1 -1
- package/dist/components/derive.js +3 -2
- package/dist/components/derive.js.map +1 -1
- package/dist/dom.d.ts.map +1 -1
- package/dist/dom.js +6 -2
- package/dist/dom.js.map +1 -1
- package/dist/hooks/usePromise.d.ts +2 -1
- package/dist/hooks/usePromise.d.ts.map +1 -1
- package/dist/hooks/usePromise.js +31 -62
- package/dist/hooks/usePromise.js.map +1 -1
- package/dist/index.js +1 -2
- package/dist/index.js.map +1 -1
- package/dist/router/client/index.d.ts.map +1 -1
- package/dist/router/client/index.js +12 -4
- package/dist/router/client/index.js.map +1 -1
- package/dist/router/constants.d.ts +2 -0
- package/dist/router/constants.d.ts.map +1 -0
- package/dist/router/constants.js +2 -0
- package/dist/router/constants.js.map +1 -0
- package/dist/router/fileRouterController.d.ts.map +1 -1
- package/dist/router/fileRouterController.js +123 -106
- package/dist/router/fileRouterController.js.map +1 -1
- package/dist/router/ssg/index.js +1 -1
- package/dist/router/ssg/index.js.map +1 -1
- package/dist/router/ssr/index.d.ts.map +1 -1
- package/dist/router/ssr/index.js +29 -26
- package/dist/router/ssr/index.js.map +1 -1
- package/dist/router/types.d.ts +5 -12
- package/dist/router/types.d.ts.map +1 -1
- package/dist/scheduler.d.ts +14 -3
- package/dist/scheduler.d.ts.map +1 -1
- package/dist/scheduler.js +3 -4
- package/dist/scheduler.js.map +1 -1
- package/dist/ssr/server.d.ts +8 -1
- package/dist/ssr/server.d.ts.map +1 -1
- package/dist/ssr/server.js +28 -18
- package/dist/ssr/server.js.map +1 -1
- package/dist/utils/index.d.ts +1 -1
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +1 -1
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/promise.d.ts +2 -0
- package/dist/utils/promise.d.ts.map +1 -1
- package/dist/utils/promise.js +45 -1
- package/dist/utils/promise.js.map +1 -1
- package/package.json +1 -1
- package/src/components/derive.ts +5 -3
- package/src/dom.ts +5 -1
- package/src/hooks/usePromise.ts +57 -77
- package/src/index.ts +1 -1
- package/src/router/client/index.ts +16 -4
- package/src/router/constants.ts +1 -0
- package/src/router/fileRouterController.ts +180 -137
- package/src/router/ssg/index.ts +1 -1
- package/src/router/ssr/index.ts +38 -33
- package/src/router/types.ts +5 -12
- package/src/scheduler.ts +20 -3
- package/src/ssr/server.ts +48 -22
- package/src/utils/index.ts +1 -1
- package/src/utils/promise.ts +70 -1
package/src/hooks/usePromise.ts
CHANGED
|
@@ -1,16 +1,21 @@
|
|
|
1
|
-
import { STREAMED_DATA_EVENT } from "../constants.js"
|
|
2
1
|
import { __DEV__ } from "../env.js"
|
|
3
2
|
import { hydrationMode, renderMode } from "../globals.js"
|
|
4
3
|
import { Signal, useSignal } from "../signals/base.js"
|
|
5
|
-
import {
|
|
4
|
+
import { depsRequireChange, useHook } from "./utils.js"
|
|
6
5
|
import { useId } from "./useId.js"
|
|
6
|
+
import {
|
|
7
|
+
createStatefulPromise,
|
|
8
|
+
resolveStreamedPromise,
|
|
9
|
+
} from "../utils/promise.js"
|
|
7
10
|
|
|
8
11
|
export { usePromise }
|
|
9
12
|
|
|
10
13
|
const nodeToPromiseIndex = new WeakMap<Kiru.VNode, number>()
|
|
11
14
|
|
|
15
|
+
type PromiseMutator = (signal: AbortSignal) => unknown | Promise<unknown>
|
|
16
|
+
|
|
12
17
|
type UsePromiseResult<T> = Kiru.StatefulPromise<T> & {
|
|
13
|
-
refresh: () => void
|
|
18
|
+
refresh: (mutator?: PromiseMutator) => void
|
|
14
19
|
isPending: Signal<boolean>
|
|
15
20
|
}
|
|
16
21
|
|
|
@@ -18,7 +23,7 @@ function usePromise<T>(
|
|
|
18
23
|
callback: (signal: AbortSignal) => Promise<T>,
|
|
19
24
|
deps: unknown[]
|
|
20
25
|
): UsePromiseResult<T> {
|
|
21
|
-
const
|
|
26
|
+
const vNodeId = useId()
|
|
22
27
|
const isPending = useSignal(true)
|
|
23
28
|
|
|
24
29
|
return useHook(
|
|
@@ -27,24 +32,54 @@ function usePromise<T>(
|
|
|
27
32
|
promise: UsePromiseResult<T>
|
|
28
33
|
abortController?: AbortController
|
|
29
34
|
deps?: unknown[]
|
|
35
|
+
refresh: (mutator?: PromiseMutator) => void
|
|
36
|
+
promiseId: string
|
|
37
|
+
epoch: number
|
|
30
38
|
},
|
|
31
39
|
({ hook, isInit, vNode, update }) => {
|
|
32
|
-
if (isInit
|
|
33
|
-
|
|
34
|
-
hook.
|
|
35
|
-
cleanupHook(hook)
|
|
36
|
-
|
|
37
|
-
const controller = (hook.abortController = new AbortController())
|
|
38
|
-
hook.cleanup = () => controller.abort()
|
|
40
|
+
if (isInit) {
|
|
41
|
+
hook.epoch = 0
|
|
42
|
+
hook.cleanup = () => hook.abortController?.abort("aborted")
|
|
39
43
|
|
|
40
44
|
const index = nodeToPromiseIndex.get(vNode) ?? 0
|
|
41
45
|
nodeToPromiseIndex.set(vNode, index + 1)
|
|
46
|
+
const promiseId = (hook.promiseId = `${vNodeId}:data:${index}`)
|
|
47
|
+
|
|
48
|
+
const refresh = (hook.refresh = (mutator?: PromiseMutator) => {
|
|
49
|
+
if (typeof mutator !== "function") {
|
|
50
|
+
delete hook.deps
|
|
51
|
+
return update()
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
hook.cleanup!()
|
|
55
|
+
const signal = (hook.abortController = new AbortController()).signal
|
|
56
|
+
const promise = Promise.try(mutator, signal).then(() =>
|
|
57
|
+
callback(signal)
|
|
58
|
+
)
|
|
59
|
+
const epoch = ++hook.epoch
|
|
60
|
+
hook.promise = createStatefulPromise(
|
|
61
|
+
promiseId,
|
|
62
|
+
promise,
|
|
63
|
+
{ isPending, refresh },
|
|
64
|
+
() => epoch === hook.epoch && (isPending.value = false)
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
isPending.value = true
|
|
68
|
+
update()
|
|
69
|
+
})
|
|
70
|
+
}
|
|
42
71
|
|
|
43
|
-
|
|
72
|
+
if (isInit || depsRequireChange(deps, hook.deps)) {
|
|
73
|
+
isPending.value = true
|
|
74
|
+
hook.deps = deps
|
|
75
|
+
hook.cleanup!()
|
|
76
|
+
|
|
77
|
+
const signal = (hook.abortController = new AbortController()).signal
|
|
78
|
+
const { promiseId, refresh } = hook
|
|
44
79
|
|
|
45
80
|
let promise: Promise<T>
|
|
46
81
|
if (renderMode.current === "string") {
|
|
47
|
-
// if we're rendering to
|
|
82
|
+
// if we're rendering to string, there's no need to fire the callback
|
|
48
83
|
promise = Promise.resolve() as Promise<T>
|
|
49
84
|
} else if (
|
|
50
85
|
renderMode.current === "hydrate" &&
|
|
@@ -52,76 +87,21 @@ function usePromise<T>(
|
|
|
52
87
|
) {
|
|
53
88
|
// if we're hydrating and the hydration mode is not static,
|
|
54
89
|
// we need to resolve the promise from cache/event
|
|
55
|
-
promise =
|
|
90
|
+
promise = resolveStreamedPromise<T>(promiseId, signal)
|
|
56
91
|
} else {
|
|
57
92
|
// dom / stream / (hydrate + static)
|
|
58
|
-
promise = callback(
|
|
93
|
+
promise = callback(signal)
|
|
59
94
|
}
|
|
60
95
|
|
|
61
|
-
const
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
},
|
|
69
|
-
}))
|
|
70
|
-
|
|
71
|
-
statefulPromise
|
|
72
|
-
.then((value) => {
|
|
73
|
-
statefulPromise.state = "fulfilled"
|
|
74
|
-
statefulPromise.value = value
|
|
75
|
-
isPending.value = false
|
|
76
|
-
})
|
|
77
|
-
.catch((error) => {
|
|
78
|
-
statefulPromise.state = "rejected"
|
|
79
|
-
statefulPromise.error =
|
|
80
|
-
error instanceof Error ? error : new Error(error)
|
|
81
|
-
})
|
|
96
|
+
const epoch = ++hook.epoch
|
|
97
|
+
hook.promise = createStatefulPromise(
|
|
98
|
+
promiseId,
|
|
99
|
+
promise,
|
|
100
|
+
{ isPending, refresh },
|
|
101
|
+
() => epoch === hook.epoch && (isPending.value = false)
|
|
102
|
+
)
|
|
82
103
|
}
|
|
83
104
|
return hook.promise
|
|
84
105
|
}
|
|
85
106
|
)
|
|
86
107
|
}
|
|
87
|
-
|
|
88
|
-
interface DeferredPromiseEventDetail<T> {
|
|
89
|
-
id: string
|
|
90
|
-
data?: T
|
|
91
|
-
error?: string
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
function resolveDeferredPromise<T>(
|
|
95
|
-
id: string,
|
|
96
|
-
signal: AbortSignal
|
|
97
|
-
): Promise<T> {
|
|
98
|
-
return new Promise<T>((resolve, reject) => {
|
|
99
|
-
const deferralCache: Map<string, { data?: T; error?: string }> = // @ts-ignore
|
|
100
|
-
(window[STREAMED_DATA_EVENT] ??= new Map())
|
|
101
|
-
|
|
102
|
-
const existing = deferralCache.get(id)
|
|
103
|
-
if (existing) {
|
|
104
|
-
const { data, error } = existing
|
|
105
|
-
deferralCache.delete(id)
|
|
106
|
-
if (error) return reject(error)
|
|
107
|
-
return resolve(data!)
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
const onDataEvent = (event: Event) => {
|
|
111
|
-
const { detail } = event as CustomEvent<DeferredPromiseEventDetail<T>>
|
|
112
|
-
if (detail.id === id) {
|
|
113
|
-
deferralCache.delete(id)
|
|
114
|
-
window.removeEventListener(STREAMED_DATA_EVENT, onDataEvent)
|
|
115
|
-
const { data, error } = detail
|
|
116
|
-
if (error) return reject(error)
|
|
117
|
-
resolve(data!)
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
window.addEventListener(STREAMED_DATA_EVENT, onDataEvent)
|
|
122
|
-
signal.addEventListener("abort", () => {
|
|
123
|
-
window.removeEventListener(STREAMED_DATA_EVENT, onDataEvent)
|
|
124
|
-
reject()
|
|
125
|
-
})
|
|
126
|
-
})
|
|
127
|
-
}
|
package/src/index.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { createElement } from "../../element.js"
|
|
|
2
2
|
import { hydrate } from "../../ssr/client.js"
|
|
3
3
|
import { FileRouter } from "../fileRouter.js"
|
|
4
4
|
import { toArray } from "../../utils/format.js"
|
|
5
|
+
import { resolveStreamedPromise } from "../../utils/promise.js"
|
|
5
6
|
import {
|
|
6
7
|
matchModules,
|
|
7
8
|
matchRoute,
|
|
@@ -28,6 +29,8 @@ import { FileRouterDataLoadError } from "../errors.js"
|
|
|
28
29
|
import { __DEV__ } from "../../env.js"
|
|
29
30
|
import { RouterCache } from "../cache.js"
|
|
30
31
|
import { RequestContext } from "../context.js"
|
|
32
|
+
import { PAGE_DATA_PROMISE_ID } from "../constants.js"
|
|
33
|
+
import { AsyncTaskState } from "../../types.js"
|
|
31
34
|
|
|
32
35
|
interface InitClientOptions {
|
|
33
36
|
dir: string
|
|
@@ -73,6 +76,7 @@ export async function initClient(options: InitClientOptions) {
|
|
|
73
76
|
}
|
|
74
77
|
|
|
75
78
|
const children = createElement(FileRouter, { config })
|
|
79
|
+
|
|
76
80
|
const app =
|
|
77
81
|
hydrationMode === "static"
|
|
78
82
|
? children
|
|
@@ -91,7 +95,7 @@ export async function initClient(options: InitClientOptions) {
|
|
|
91
95
|
async function preparePreloadConfig(
|
|
92
96
|
options: InitClientOptions,
|
|
93
97
|
isStatic404 = false,
|
|
94
|
-
|
|
98
|
+
isSSR = false
|
|
95
99
|
): Promise<FileRouterPreloadConfig> {
|
|
96
100
|
let pageProps = {}
|
|
97
101
|
let cacheData: null | { value: unknown } = null
|
|
@@ -145,7 +149,7 @@ async function preparePreloadConfig(
|
|
|
145
149
|
{ ...requestContext.current },
|
|
146
150
|
url.pathname
|
|
147
151
|
)
|
|
148
|
-
if (redirectPath) {
|
|
152
|
+
if (redirectPath !== null) {
|
|
149
153
|
window.location.href = redirectPath
|
|
150
154
|
// @ts-ignore
|
|
151
155
|
return
|
|
@@ -154,6 +158,10 @@ async function preparePreloadConfig(
|
|
|
154
158
|
|
|
155
159
|
const query = parseQuery(window.location.search)
|
|
156
160
|
|
|
161
|
+
let pagePropsPromise:
|
|
162
|
+
| Promise<AsyncTaskState<unknown, FileRouterDataLoadError>>
|
|
163
|
+
| undefined
|
|
164
|
+
|
|
157
165
|
// Check if page has static props pre-loaded at build time
|
|
158
166
|
if (page.__KIRU_STATIC_PROPS__) {
|
|
159
167
|
const staticProps = page.__KIRU_STATIC_PROPS__[window.location.pathname]
|
|
@@ -172,8 +180,11 @@ async function preparePreloadConfig(
|
|
|
172
180
|
pageProps = { loading: true, data: null, error: null }
|
|
173
181
|
|
|
174
182
|
const loader = page.config.loader
|
|
175
|
-
|
|
176
|
-
|
|
183
|
+
if (isSSR) {
|
|
184
|
+
pagePropsPromise = resolveStreamedPromise(PAGE_DATA_PROMISE_ID)
|
|
185
|
+
.then((data) => ({ data, error: null, loading: false } as const))
|
|
186
|
+
.catch((error) => ({ data: null, error, loading: false } as const))
|
|
187
|
+
} else if (!loader.static && loader.cache) {
|
|
177
188
|
const cacheKey = {
|
|
178
189
|
path: window.location.pathname,
|
|
179
190
|
params: routeMatch.params,
|
|
@@ -198,6 +209,7 @@ async function preparePreloadConfig(
|
|
|
198
209
|
guards: options.guards,
|
|
199
210
|
page: page,
|
|
200
211
|
pageProps: pageProps,
|
|
212
|
+
pagePropsPromise,
|
|
201
213
|
pageLayouts: layouts,
|
|
202
214
|
params: routeMatch.params,
|
|
203
215
|
query: query,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const PAGE_DATA_PROMISE_ID = "__KIRU_PAGE_DATA__"
|