kiru 0.53.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/globals.d.ts +1 -1
- package/dist/globals.d.ts.map +1 -1
- package/dist/globals.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 +4 -2
- package/dist/router/client/index.d.ts.map +1 -1
- package/dist/router/client/index.js +59 -13
- 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/context.d.ts +2 -0
- package/dist/router/context.d.ts.map +1 -1
- package/dist/router/context.js +5 -1
- package/dist/router/context.js.map +1 -1
- package/dist/router/fileRouterController.d.ts +2 -0
- package/dist/router/fileRouterController.d.ts.map +1 -1
- package/dist/router/fileRouterController.js +195 -107
- package/dist/router/fileRouterController.js.map +1 -1
- package/dist/router/globals.d.ts +3 -0
- package/dist/router/globals.d.ts.map +1 -1
- package/dist/router/globals.js +3 -0
- package/dist/router/globals.js.map +1 -1
- package/dist/router/guard.d.ts +17 -0
- package/dist/router/guard.d.ts.map +1 -0
- package/dist/router/guard.js +45 -0
- package/dist/router/guard.js.map +1 -0
- package/dist/router/head.d.ts.map +1 -1
- package/dist/router/head.js +5 -7
- package/dist/router/head.js.map +1 -1
- package/dist/router/index.d.ts +2 -1
- package/dist/router/index.d.ts.map +1 -1
- package/dist/router/index.js +2 -1
- package/dist/router/index.js.map +1 -1
- package/dist/router/{server → ssg}/index.d.ts +4 -3
- package/dist/router/ssg/index.d.ts.map +1 -0
- package/dist/router/{server → ssg}/index.js +8 -5
- package/dist/router/ssg/index.js.map +1 -0
- package/dist/router/ssr/index.d.ts +20 -0
- package/dist/router/ssr/index.d.ts.map +1 -0
- package/dist/router/ssr/index.js +163 -0
- package/dist/router/ssr/index.js.map +1 -0
- package/dist/router/types.d.ts +42 -16
- package/dist/router/types.d.ts.map +1 -1
- package/dist/router/types.internal.d.ts +4 -0
- package/dist/router/types.internal.d.ts.map +1 -1
- package/dist/router/utils/index.d.ts +8 -3
- package/dist/router/utils/index.d.ts.map +1 -1
- package/dist/router/utils/index.js +38 -6
- package/dist/router/utils/index.js.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/client.d.ts +1 -1
- package/dist/ssr/client.d.ts.map +1 -1
- package/dist/ssr/server.d.ts +9 -3
- package/dist/ssr/server.d.ts.map +1 -1
- package/dist/ssr/server.js +37 -30
- package/dist/ssr/server.js.map +1 -1
- package/dist/types.d.ts +3 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/format.d.ts +2 -1
- package/dist/utils/format.d.ts.map +1 -1
- package/dist/utils/format.js +4 -1
- package/dist/utils/format.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/dist/utils/runtime.d.ts +1 -1
- package/dist/utils/runtime.js +1 -1
- package/package.json +8 -4
- package/src/components/derive.ts +5 -3
- package/src/dom.ts +5 -1
- package/src/globals.ts +1 -1
- package/src/hooks/usePromise.ts +57 -77
- package/src/index.ts +1 -1
- package/src/router/client/index.ts +114 -16
- package/src/router/constants.ts +1 -0
- package/src/router/context.ts +7 -1
- package/src/router/fileRouterController.ts +304 -132
- package/src/router/globals.ts +4 -0
- package/src/router/guard.ts +72 -0
- package/src/router/head.ts +5 -7
- package/src/router/index.ts +12 -1
- package/src/router/{server → ssg}/index.ts +17 -10
- package/src/router/ssr/index.ts +252 -0
- package/src/router/types.internal.ts +5 -0
- package/src/router/types.ts +53 -16
- package/src/router/utils/index.ts +74 -8
- package/src/scheduler.ts +20 -3
- package/src/ssr/client.ts +1 -1
- package/src/ssr/server.ts +58 -34
- package/src/types.ts +3 -0
- package/src/utils/format.ts +5 -0
- package/src/utils/index.ts +1 -1
- package/src/utils/promise.ts +70 -1
- package/src/utils/runtime.ts +1 -1
- package/dist/router/server/index.d.ts.map +0 -1
- package/dist/router/server/index.js.map +0 -1
|
@@ -2,9 +2,15 @@ import { Signal } from "../signals/base.js"
|
|
|
2
2
|
import { watch } from "../signals/watch.js"
|
|
3
3
|
import { __DEV__ } from "../env.js"
|
|
4
4
|
import { flushSync, nextIdle } from "../scheduler.js"
|
|
5
|
+
import { toArray } from "../utils/format.js"
|
|
5
6
|
import { ReloadOptions, type FileRouterContextType } from "./context.js"
|
|
6
7
|
import { FileRouterDataLoadError } from "./errors.js"
|
|
7
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
fileRouterInstance,
|
|
10
|
+
fileRouterRoute,
|
|
11
|
+
requestContext,
|
|
12
|
+
routerCache,
|
|
13
|
+
} from "./globals.js"
|
|
8
14
|
import type {
|
|
9
15
|
FileRouterConfig,
|
|
10
16
|
PageConfig,
|
|
@@ -17,17 +23,22 @@ import type {
|
|
|
17
23
|
CurrentPage,
|
|
18
24
|
DevtoolsInterface,
|
|
19
25
|
FormattedViteImportMap,
|
|
26
|
+
GuardModule,
|
|
20
27
|
PageModule,
|
|
21
28
|
ViteImportMap,
|
|
22
29
|
} from "./types.internal.js"
|
|
23
30
|
import {
|
|
24
31
|
formatViteImportMap,
|
|
25
|
-
|
|
32
|
+
matchModules,
|
|
26
33
|
matchRoute,
|
|
27
34
|
match404Route,
|
|
28
35
|
normalizePrefixPath,
|
|
29
36
|
parseQuery,
|
|
30
37
|
wrapWithLayouts,
|
|
38
|
+
runAfterEachGuards,
|
|
39
|
+
runBeforeEachGuards,
|
|
40
|
+
runBeforeEnterHooks,
|
|
41
|
+
runBeforeLeaveHooks,
|
|
31
42
|
} from "./utils/index.js"
|
|
32
43
|
import { RouterCache, type CacheKey } from "./cache.js"
|
|
33
44
|
import { scrollStack } from "./scrollStack.js"
|
|
@@ -36,6 +47,19 @@ interface PageConfigWithLoader<T = unknown> extends PageConfig {
|
|
|
36
47
|
loader: PageDataLoaderConfig<T>
|
|
37
48
|
}
|
|
38
49
|
|
|
50
|
+
interface LoadRouteOptions {
|
|
51
|
+
path?: string
|
|
52
|
+
transition?: boolean
|
|
53
|
+
isStatic404?: boolean
|
|
54
|
+
onPaint?: () => void
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
let transitionId = 0
|
|
58
|
+
let currentTransition = null as null | {
|
|
59
|
+
transition: ViewTransition
|
|
60
|
+
id: number
|
|
61
|
+
}
|
|
62
|
+
|
|
39
63
|
export class FileRouterController {
|
|
40
64
|
public contextValue: FileRouterContextType
|
|
41
65
|
public devtools?: DevtoolsInterface
|
|
@@ -55,6 +79,7 @@ export class FileRouterController {
|
|
|
55
79
|
private historyIndex: number
|
|
56
80
|
private layouts: FormattedViteImportMap
|
|
57
81
|
private pages: FormattedViteImportMap<PageModule>
|
|
82
|
+
private guards: FormattedViteImportMap<GuardModule>
|
|
58
83
|
private pageRouteToConfig?: Map<string, PageConfig>
|
|
59
84
|
private state: RouterState
|
|
60
85
|
|
|
@@ -68,6 +93,7 @@ export class FileRouterController {
|
|
|
68
93
|
this.historyIndex = 0
|
|
69
94
|
this.layouts = {}
|
|
70
95
|
this.pages = {}
|
|
96
|
+
this.guards = {}
|
|
71
97
|
this.state = {
|
|
72
98
|
pathname: window.location.pathname,
|
|
73
99
|
hash: window.location.hash,
|
|
@@ -79,7 +105,7 @@ export class FileRouterController {
|
|
|
79
105
|
this.contextValue = {
|
|
80
106
|
invalidate: async (...paths: string[]) => {
|
|
81
107
|
if (this.invalidate(...paths)) {
|
|
82
|
-
return this.loadRoute(
|
|
108
|
+
return this.loadRoute()
|
|
83
109
|
}
|
|
84
110
|
},
|
|
85
111
|
get state() {
|
|
@@ -91,7 +117,7 @@ export class FileRouterController {
|
|
|
91
117
|
if (options?.invalidate ?? true) {
|
|
92
118
|
this.invalidate(this.state.pathname)
|
|
93
119
|
}
|
|
94
|
-
return this.loadRoute(
|
|
120
|
+
return this.loadRoute({ transition: options?.transition })
|
|
95
121
|
},
|
|
96
122
|
setQuery: this.setQuery.bind(this),
|
|
97
123
|
setHash: this.setHash.bind(this),
|
|
@@ -112,12 +138,11 @@ export class FileRouterController {
|
|
|
112
138
|
if (curPage?.route === existing.route && loader) {
|
|
113
139
|
const p = this.currentPageProps.value
|
|
114
140
|
const transition =
|
|
115
|
-
(loader.
|
|
116
|
-
this.enableTransitions
|
|
141
|
+
(!loader.static && loader.transition) ?? this.enableTransitions
|
|
117
142
|
|
|
118
143
|
// Check cache first if caching is enabled
|
|
119
144
|
let cachedData = null
|
|
120
|
-
if (loader.
|
|
145
|
+
if (!loader.static && loader.cache) {
|
|
121
146
|
const cacheKey: CacheKey = {
|
|
122
147
|
path: this.state.pathname,
|
|
123
148
|
params: this.state.params,
|
|
@@ -134,9 +159,11 @@ export class FileRouterController {
|
|
|
134
159
|
error: null,
|
|
135
160
|
loading: false,
|
|
136
161
|
}
|
|
137
|
-
handleStateTransition(
|
|
138
|
-
|
|
139
|
-
|
|
162
|
+
handleStateTransition(
|
|
163
|
+
transition,
|
|
164
|
+
transitionId,
|
|
165
|
+
() => (this.currentPageProps.value = props)
|
|
166
|
+
)
|
|
140
167
|
} else {
|
|
141
168
|
// No cached data - show loading state and load data
|
|
142
169
|
const props = {
|
|
@@ -145,13 +172,14 @@ export class FileRouterController {
|
|
|
145
172
|
data: null,
|
|
146
173
|
error: null,
|
|
147
174
|
}
|
|
148
|
-
handleStateTransition(
|
|
149
|
-
|
|
150
|
-
|
|
175
|
+
handleStateTransition(
|
|
176
|
+
transition,
|
|
177
|
+
transitionId,
|
|
178
|
+
() => (this.currentPageProps.value = props)
|
|
179
|
+
)
|
|
151
180
|
|
|
152
181
|
this.loadRouteData(
|
|
153
182
|
config as PageConfigWithLoader,
|
|
154
|
-
props,
|
|
155
183
|
this.state,
|
|
156
184
|
transition
|
|
157
185
|
)
|
|
@@ -187,6 +215,7 @@ export class FileRouterController {
|
|
|
187
215
|
const {
|
|
188
216
|
pages,
|
|
189
217
|
layouts,
|
|
218
|
+
guards,
|
|
190
219
|
dir = "/pages",
|
|
191
220
|
baseUrl = "/",
|
|
192
221
|
transition,
|
|
@@ -198,12 +227,38 @@ export class FileRouterController {
|
|
|
198
227
|
normalizePrefixPath(baseUrl),
|
|
199
228
|
]
|
|
200
229
|
|
|
201
|
-
if (preloaded) {
|
|
230
|
+
if (!preloaded) {
|
|
231
|
+
this.pages = formatViteImportMap(
|
|
232
|
+
pages as ViteImportMap,
|
|
233
|
+
normalizedDir,
|
|
234
|
+
normalizedBaseUrl
|
|
235
|
+
)
|
|
236
|
+
|
|
237
|
+
this.layouts = formatViteImportMap(
|
|
238
|
+
layouts as ViteImportMap,
|
|
239
|
+
normalizedDir,
|
|
240
|
+
normalizedBaseUrl
|
|
241
|
+
)
|
|
242
|
+
this.guards = !guards
|
|
243
|
+
? {}
|
|
244
|
+
: (formatViteImportMap(
|
|
245
|
+
guards as ViteImportMap,
|
|
246
|
+
normalizedDir,
|
|
247
|
+
normalizedBaseUrl
|
|
248
|
+
) as unknown as FormattedViteImportMap<GuardModule>)
|
|
249
|
+
|
|
250
|
+
if (__DEV__) {
|
|
251
|
+
validateRoutes(this.pages)
|
|
252
|
+
}
|
|
253
|
+
this.loadRoute()
|
|
254
|
+
} else {
|
|
202
255
|
const {
|
|
203
256
|
pages,
|
|
204
257
|
layouts,
|
|
258
|
+
guards,
|
|
205
259
|
page,
|
|
206
260
|
pageProps,
|
|
261
|
+
pagePropsPromise,
|
|
207
262
|
pageLayouts,
|
|
208
263
|
route,
|
|
209
264
|
params,
|
|
@@ -226,57 +281,49 @@ export class FileRouterController {
|
|
|
226
281
|
this.currentLayouts.value = pageLayouts.map((l) => l.default)
|
|
227
282
|
this.pages = pages
|
|
228
283
|
this.layouts = layouts
|
|
284
|
+
this.guards = (guards ??
|
|
285
|
+
{}) as unknown as FormattedViteImportMap<GuardModule>
|
|
229
286
|
if (__DEV__) {
|
|
287
|
+
validateRoutes(this.pages)
|
|
230
288
|
if (page.config) {
|
|
231
289
|
this.dev_onPageConfigDefined!(route, page.config)
|
|
232
290
|
}
|
|
233
291
|
}
|
|
234
|
-
|
|
235
|
-
validateRoutes(this.pages)
|
|
236
|
-
}
|
|
292
|
+
|
|
237
293
|
const loader = page.config?.loader
|
|
238
|
-
|
|
294
|
+
const transition =
|
|
295
|
+
(!loader?.static && loader?.transition) ?? this.enableTransitions
|
|
296
|
+
|
|
297
|
+
if (loader && pagePropsPromise) {
|
|
298
|
+
const prevState = this.state
|
|
299
|
+
pagePropsPromise.then(({ data, error }) => {
|
|
300
|
+
if (this.state !== prevState) return
|
|
301
|
+
|
|
302
|
+
handleStateTransition(
|
|
303
|
+
transition,
|
|
304
|
+
transitionId,
|
|
305
|
+
() =>
|
|
306
|
+
(this.currentPageProps.value = { loading: false, data, error })
|
|
307
|
+
)
|
|
308
|
+
})
|
|
309
|
+
} else if (
|
|
239
310
|
loader &&
|
|
240
|
-
((loader.
|
|
311
|
+
((!loader.static && pageProps.loading === true) || __DEV__)
|
|
241
312
|
) {
|
|
242
313
|
if (cacheData === null) {
|
|
243
|
-
this.loadRouteData(
|
|
244
|
-
page.config as PageConfigWithLoader,
|
|
245
|
-
pageProps,
|
|
246
|
-
this.state
|
|
247
|
-
)
|
|
314
|
+
this.loadRouteData(page.config as PageConfigWithLoader, this.state)
|
|
248
315
|
} else {
|
|
249
316
|
nextIdle(() => {
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
// @ts-ignore
|
|
257
|
-
const transition = loader.transition ?? this.enableTransitions
|
|
258
|
-
handleStateTransition(this.state.signal, transition, () => {
|
|
259
|
-
this.currentPageProps.value = props
|
|
317
|
+
handleStateTransition(transition, transitionId, () => {
|
|
318
|
+
this.currentPageProps.value = {
|
|
319
|
+
data: cacheData.value,
|
|
320
|
+
error: null,
|
|
321
|
+
loading: false,
|
|
322
|
+
}
|
|
260
323
|
})
|
|
261
324
|
})
|
|
262
325
|
}
|
|
263
326
|
}
|
|
264
|
-
} else {
|
|
265
|
-
this.pages = formatViteImportMap(
|
|
266
|
-
pages as ViteImportMap,
|
|
267
|
-
normalizedDir,
|
|
268
|
-
normalizedBaseUrl
|
|
269
|
-
)
|
|
270
|
-
|
|
271
|
-
this.layouts = formatViteImportMap(
|
|
272
|
-
layouts as ViteImportMap,
|
|
273
|
-
normalizedDir,
|
|
274
|
-
normalizedBaseUrl
|
|
275
|
-
)
|
|
276
|
-
if (__DEV__) {
|
|
277
|
-
validateRoutes(this.pages)
|
|
278
|
-
}
|
|
279
|
-
this.loadRoute()
|
|
280
327
|
}
|
|
281
328
|
|
|
282
329
|
window.history.scrollRestoration = "manual"
|
|
@@ -315,22 +362,74 @@ export class FileRouterController {
|
|
|
315
362
|
window.history.scrollRestoration = "auto"
|
|
316
363
|
})
|
|
317
364
|
|
|
365
|
+
let ignorePopState = false
|
|
366
|
+
|
|
318
367
|
window.addEventListener("popstate", (e) => {
|
|
319
368
|
e.preventDefault()
|
|
369
|
+
|
|
370
|
+
if (
|
|
371
|
+
!ignorePopState &&
|
|
372
|
+
this.onBeforeLeave(window.location.pathname) === false
|
|
373
|
+
) {
|
|
374
|
+
ignorePopState = true
|
|
375
|
+
if (e.state !== null) {
|
|
376
|
+
if (e.state.index > this.historyIndex) {
|
|
377
|
+
window.history.go(-1)
|
|
378
|
+
} else if (e.state.index < this.historyIndex) {
|
|
379
|
+
window.history.go(1)
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
return
|
|
383
|
+
}
|
|
384
|
+
if (ignorePopState) {
|
|
385
|
+
ignorePopState = false
|
|
386
|
+
return
|
|
387
|
+
}
|
|
388
|
+
|
|
320
389
|
scrollStack.replace(this.historyIndex, window.scrollX, window.scrollY)
|
|
321
390
|
|
|
322
|
-
|
|
323
|
-
|
|
391
|
+
// prep 'on painted' callback for scroll-to-offset action
|
|
392
|
+
// this will fire once the page has rendered but before (loader?) kicks off.
|
|
393
|
+
let onPaint
|
|
394
|
+
if (e.state != null) {
|
|
395
|
+
onPaint = () => {
|
|
324
396
|
this.historyIndex = e.state.index
|
|
325
397
|
const offset = scrollStack.getItem(e.state.index)
|
|
326
398
|
if (offset !== undefined) {
|
|
327
399
|
window.scrollTo(...offset)
|
|
328
400
|
}
|
|
329
401
|
}
|
|
330
|
-
}
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
this.loadRoute({ onPaint })
|
|
331
405
|
})
|
|
332
406
|
}
|
|
333
407
|
|
|
408
|
+
private onBeforeLeave(to: string) {
|
|
409
|
+
const currentPage = this.currentPage.peek()
|
|
410
|
+
if (!currentPage) {
|
|
411
|
+
return true
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
let config = currentPage.config ?? ({} as PageConfig)
|
|
415
|
+
if (__DEV__) {
|
|
416
|
+
if (this.pageRouteToConfig?.has(currentPage.route)) {
|
|
417
|
+
config = this.pageRouteToConfig.get(currentPage.route)!
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
const onBeforeLeave = config.hooks?.onBeforeLeave
|
|
422
|
+
if (onBeforeLeave) {
|
|
423
|
+
return runBeforeLeaveHooks(
|
|
424
|
+
toArray(onBeforeLeave),
|
|
425
|
+
{ ...requestContext.current },
|
|
426
|
+
to,
|
|
427
|
+
this.state.pathname
|
|
428
|
+
)
|
|
429
|
+
}
|
|
430
|
+
return true
|
|
431
|
+
}
|
|
432
|
+
|
|
334
433
|
public getChildren() {
|
|
335
434
|
const page = this.currentPage.value
|
|
336
435
|
if (!page) return null
|
|
@@ -351,12 +450,14 @@ export class FileRouterController {
|
|
|
351
450
|
fileRouterInstance.current = null
|
|
352
451
|
}
|
|
353
452
|
|
|
354
|
-
private async loadRoute(
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
453
|
+
private async loadRoute(options?: LoadRouteOptions): Promise<void> {
|
|
454
|
+
const {
|
|
455
|
+
transition: enableTransition = this.enableTransitions,
|
|
456
|
+
isStatic404 = false,
|
|
457
|
+
path = window.location.pathname,
|
|
458
|
+
onPaint,
|
|
459
|
+
} = options ?? {}
|
|
460
|
+
|
|
360
461
|
this.abortController?.abort()
|
|
361
462
|
const signal = (this.abortController = new AbortController()).signal
|
|
362
463
|
|
|
@@ -381,10 +482,38 @@ See https://kirujs.dev/docs/api/file-router#404 for more information.`
|
|
|
381
482
|
|
|
382
483
|
const { route, pageEntry, params, routeSegments } = routeMatch
|
|
383
484
|
|
|
485
|
+
// Apply beforeEach guards before loading route
|
|
486
|
+
const guardEntries = matchModules(
|
|
487
|
+
this.guards as unknown as FormattedViteImportMap,
|
|
488
|
+
routeSegments
|
|
489
|
+
)
|
|
490
|
+
const guardModules = await Promise.all(
|
|
491
|
+
guardEntries.map(
|
|
492
|
+
(entry) => entry.load() as unknown as Promise<GuardModule>
|
|
493
|
+
)
|
|
494
|
+
)
|
|
495
|
+
|
|
496
|
+
const fromPath = this.state.pathname
|
|
497
|
+
const redirectPath = await runBeforeEachGuards(
|
|
498
|
+
guardModules,
|
|
499
|
+
{ ...requestContext.current },
|
|
500
|
+
path,
|
|
501
|
+
fromPath
|
|
502
|
+
)
|
|
503
|
+
|
|
504
|
+
// If redirect was requested, navigate to that path instead
|
|
505
|
+
if (redirectPath !== null) {
|
|
506
|
+
this.state.pathname = path
|
|
507
|
+
return this.navigate(redirectPath, {
|
|
508
|
+
replace: true,
|
|
509
|
+
transition: enableTransition,
|
|
510
|
+
})
|
|
511
|
+
}
|
|
512
|
+
|
|
384
513
|
fileRouterRoute.current = route
|
|
385
514
|
const pagePromise = pageEntry.load()
|
|
386
515
|
|
|
387
|
-
const layoutPromises =
|
|
516
|
+
const layoutPromises = matchModules(this.layouts, routeSegments).map(
|
|
388
517
|
(layoutEntry) => layoutEntry.load()
|
|
389
518
|
)
|
|
390
519
|
|
|
@@ -418,62 +547,76 @@ See https://kirujs.dev/docs/api/file-router#404 for more information.`
|
|
|
418
547
|
}
|
|
419
548
|
}
|
|
420
549
|
|
|
421
|
-
const { loader } = config
|
|
550
|
+
const { loader, hooks } = config
|
|
422
551
|
|
|
423
|
-
if (
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
552
|
+
if (hooks?.onBeforeEnter) {
|
|
553
|
+
const redirectPath = await runBeforeEnterHooks(
|
|
554
|
+
toArray(hooks.onBeforeEnter),
|
|
555
|
+
requestContext,
|
|
556
|
+
path,
|
|
557
|
+
fromPath
|
|
558
|
+
)
|
|
559
|
+
if (redirectPath !== null) {
|
|
560
|
+
this.state.pathname = path
|
|
561
|
+
return this.navigate(redirectPath, {
|
|
562
|
+
replace: true,
|
|
563
|
+
transition: enableTransition,
|
|
564
|
+
})
|
|
565
|
+
}
|
|
566
|
+
}
|
|
435
567
|
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
568
|
+
let props: Record<string, unknown> = {}
|
|
569
|
+
if (!!loader) {
|
|
570
|
+
props = {
|
|
571
|
+
data: null,
|
|
572
|
+
error: null,
|
|
573
|
+
loading: true,
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
if (loader?.static && !__DEV__) {
|
|
578
|
+
const staticProps = page.__KIRU_STATIC_PROPS__?.[path]
|
|
579
|
+
if (!staticProps) {
|
|
580
|
+
// 404
|
|
581
|
+
return this.loadRoute({
|
|
582
|
+
path,
|
|
583
|
+
transition: enableTransition,
|
|
584
|
+
isStatic404: true,
|
|
585
|
+
})
|
|
586
|
+
}
|
|
587
|
+
const { data, error } = staticProps
|
|
588
|
+
props = error
|
|
589
|
+
? {
|
|
449
590
|
data: null,
|
|
591
|
+
error: new FileRouterDataLoadError(error),
|
|
592
|
+
loading: false,
|
|
593
|
+
}
|
|
594
|
+
: {
|
|
595
|
+
data: data,
|
|
450
596
|
error: null,
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
} else {
|
|
461
|
-
const staticProps = page.__KIRU_STATIC_PROPS__?.[path]
|
|
462
|
-
if (!staticProps) {
|
|
463
|
-
return this.loadRoute(path, props, enableTransition, true)
|
|
464
|
-
}
|
|
597
|
+
loading: false,
|
|
598
|
+
}
|
|
599
|
+
} else if (!loader?.static && loader?.cache) {
|
|
600
|
+
const cacheKey: CacheKey = {
|
|
601
|
+
path: routerState.pathname,
|
|
602
|
+
params: routerState.params,
|
|
603
|
+
query: routerState.query,
|
|
604
|
+
}
|
|
605
|
+
const cachedData = routerCache.current!.get(cacheKey, loader.cache)
|
|
465
606
|
|
|
466
|
-
|
|
607
|
+
if (cachedData !== null) {
|
|
467
608
|
props = {
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
error: error ? new FileRouterDataLoadError(error) : null,
|
|
609
|
+
data: cachedData.value,
|
|
610
|
+
error: null,
|
|
471
611
|
loading: false,
|
|
472
|
-
}
|
|
612
|
+
} satisfies PageProps<PageConfig<unknown>>
|
|
473
613
|
}
|
|
474
614
|
}
|
|
475
615
|
|
|
476
|
-
|
|
616
|
+
// loader transition must use the same id as page transition in order to prevent skipping it.
|
|
617
|
+
let tId = transitionId++
|
|
618
|
+
|
|
619
|
+
return await handleStateTransition(enableTransition, tId, () => {
|
|
477
620
|
this.state = routerState
|
|
478
621
|
this.currentPage.value = {
|
|
479
622
|
component: page.default,
|
|
@@ -484,6 +627,25 @@ See https://kirujs.dev/docs/api/file-router#404 for more information.`
|
|
|
484
627
|
this.currentLayouts.value = layouts
|
|
485
628
|
.filter((m) => typeof m.default === "function")
|
|
486
629
|
.map((m) => m.default)
|
|
630
|
+
|
|
631
|
+
nextIdle(() => {
|
|
632
|
+
runAfterEachGuards(
|
|
633
|
+
guardModules,
|
|
634
|
+
{ ...requestContext.current },
|
|
635
|
+
path,
|
|
636
|
+
fromPath
|
|
637
|
+
)
|
|
638
|
+
if (props.loading) {
|
|
639
|
+
this.loadRouteData(
|
|
640
|
+
config as PageConfigWithLoader,
|
|
641
|
+
routerState,
|
|
642
|
+
enableTransition,
|
|
643
|
+
tId
|
|
644
|
+
).then(() => signal.aborted || onPaint?.())
|
|
645
|
+
} else {
|
|
646
|
+
onPaint?.()
|
|
647
|
+
}
|
|
648
|
+
})
|
|
487
649
|
})
|
|
488
650
|
} catch (error) {
|
|
489
651
|
console.error("[kiru/router]: Failed to load route component:", error)
|
|
@@ -493,19 +655,19 @@ See https://kirujs.dev/docs/api/file-router#404 for more information.`
|
|
|
493
655
|
|
|
494
656
|
private async loadRouteData(
|
|
495
657
|
config: PageConfigWithLoader,
|
|
496
|
-
props: Record<string, unknown>,
|
|
497
658
|
routerState: RouterState,
|
|
498
|
-
enableTransition = this.enableTransitions
|
|
659
|
+
enableTransition = this.enableTransitions,
|
|
660
|
+
id = transitionId
|
|
499
661
|
) {
|
|
500
662
|
const { loader } = config
|
|
501
663
|
|
|
502
664
|
// Load data from loader (cache check is now done earlier in loadRoute)
|
|
503
|
-
loader
|
|
504
|
-
.load(routerState)
|
|
665
|
+
return loader
|
|
666
|
+
.load({ ...routerState, context: { ...requestContext.current } })
|
|
505
667
|
.then(
|
|
506
668
|
(data) => {
|
|
507
669
|
// Cache the data if caching is enabled
|
|
508
|
-
if (loader.
|
|
670
|
+
if (!loader.static && loader.cache) {
|
|
509
671
|
const cacheKey: CacheKey = {
|
|
510
672
|
path: routerState.pathname,
|
|
511
673
|
params: routerState.params,
|
|
@@ -531,14 +693,13 @@ See https://kirujs.dev/docs/api/file-router#404 for more information.`
|
|
|
531
693
|
if (routerState.signal.aborted) return
|
|
532
694
|
|
|
533
695
|
const transition =
|
|
534
|
-
(loader.
|
|
696
|
+
(!loader.static && loader.transition) ?? enableTransition
|
|
535
697
|
|
|
536
|
-
handleStateTransition(
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
})
|
|
698
|
+
return handleStateTransition(
|
|
699
|
+
transition,
|
|
700
|
+
id,
|
|
701
|
+
() => (this.currentPageProps.value = state)
|
|
702
|
+
)
|
|
542
703
|
})
|
|
543
704
|
}
|
|
544
705
|
|
|
@@ -566,17 +727,17 @@ See https://kirujs.dev/docs/api/file-router#404 for more information.`
|
|
|
566
727
|
const url = new URL(path, "http://localhost")
|
|
567
728
|
const { hash: nextHash, pathname: nextPath } = url
|
|
568
729
|
const { hash: prevHash, pathname: prevPath } = this.state
|
|
569
|
-
if (
|
|
730
|
+
if (
|
|
731
|
+
(nextHash === prevHash && nextPath === prevPath) ||
|
|
732
|
+
this.onBeforeLeave(prevPath) === false
|
|
733
|
+
) {
|
|
570
734
|
return
|
|
571
735
|
}
|
|
572
736
|
|
|
573
737
|
this.updateHistoryState(path, options)
|
|
574
738
|
|
|
575
|
-
this.
|
|
576
|
-
|
|
577
|
-
void 0,
|
|
578
|
-
options?.transition ?? this.enableTransitions
|
|
579
|
-
).then(() => {
|
|
739
|
+
const transition = options?.transition ?? this.enableTransitions
|
|
740
|
+
this.loadRoute({ transition }).then(() => {
|
|
580
741
|
if (nextHash !== prevHash) {
|
|
581
742
|
window.dispatchEvent(new HashChangeEvent("hashchange"))
|
|
582
743
|
}
|
|
@@ -603,7 +764,7 @@ See https://kirujs.dev/docs/api/file-router#404 for more information.`
|
|
|
603
764
|
const { pageEntry, route } = routeMatch
|
|
604
765
|
fileRouterRoute.current = route
|
|
605
766
|
const pagePromise = pageEntry.load()
|
|
606
|
-
const layoutPromises =
|
|
767
|
+
const layoutPromises = matchModules(this.layouts, route.split("/")).map(
|
|
607
768
|
(layoutEntry) => layoutEntry.load()
|
|
608
769
|
)
|
|
609
770
|
await Promise.all([pagePromise, ...layoutPromises])
|
|
@@ -649,13 +810,15 @@ See https://kirujs.dev/docs/api/file-router#404 for more information.`
|
|
|
649
810
|
path
|
|
650
811
|
)
|
|
651
812
|
} else {
|
|
813
|
+
const current = scrollStack.get()
|
|
814
|
+
|
|
652
815
|
// if we've gone back and are now going forward, we need to
|
|
653
816
|
// truncate the scroll stack so it doesn't just permanently grow.
|
|
654
817
|
// this should keep it at the same length as the history stack.
|
|
655
|
-
const current = scrollStack.get()
|
|
656
818
|
if (this.historyIndex < window.history.length - 1) {
|
|
657
819
|
current.length = this.historyIndex
|
|
658
820
|
}
|
|
821
|
+
|
|
659
822
|
scrollStack.save([...current, [window.scrollX, window.scrollY]])
|
|
660
823
|
window.history.pushState(
|
|
661
824
|
{ ...window.history.state, index: ++this.historyIndex },
|
|
@@ -685,23 +848,32 @@ function buildQueryString(
|
|
|
685
848
|
}
|
|
686
849
|
|
|
687
850
|
async function handleStateTransition(
|
|
688
|
-
signal: AbortSignal,
|
|
689
851
|
enableTransition: boolean,
|
|
852
|
+
id: number,
|
|
690
853
|
callback: () => void
|
|
691
854
|
) {
|
|
855
|
+
if (currentTransition) {
|
|
856
|
+
const { id: currentId, transition } = currentTransition
|
|
857
|
+
// for cross-page navigations, we skip any existing transitions.
|
|
858
|
+
// otherwise (eg. loaders), we wait for the existing transition to finish
|
|
859
|
+
if (id !== currentId) {
|
|
860
|
+
transition.skipTransition()
|
|
861
|
+
}
|
|
862
|
+
await transition.finished
|
|
863
|
+
}
|
|
692
864
|
if (!enableTransition || typeof document.startViewTransition !== "function") {
|
|
693
865
|
return new Promise<void>((resolve) => {
|
|
694
866
|
callback()
|
|
695
867
|
nextIdle(resolve)
|
|
696
868
|
})
|
|
697
869
|
}
|
|
698
|
-
const
|
|
870
|
+
const transition = document.startViewTransition(() => {
|
|
699
871
|
callback()
|
|
700
872
|
flushSync()
|
|
701
873
|
})
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
874
|
+
currentTransition = { id, transition }
|
|
875
|
+
await transition.finished
|
|
876
|
+
currentTransition = null
|
|
705
877
|
}
|
|
706
878
|
|
|
707
879
|
function validateRoutes(pageMap: FormattedViteImportMap) {
|