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
|
@@ -47,6 +47,19 @@ interface PageConfigWithLoader<T = unknown> extends PageConfig {
|
|
|
47
47
|
loader: PageDataLoaderConfig<T>
|
|
48
48
|
}
|
|
49
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
|
+
|
|
50
63
|
export class FileRouterController {
|
|
51
64
|
public contextValue: FileRouterContextType
|
|
52
65
|
public devtools?: DevtoolsInterface
|
|
@@ -92,7 +105,7 @@ export class FileRouterController {
|
|
|
92
105
|
this.contextValue = {
|
|
93
106
|
invalidate: async (...paths: string[]) => {
|
|
94
107
|
if (this.invalidate(...paths)) {
|
|
95
|
-
return this.loadRoute(
|
|
108
|
+
return this.loadRoute()
|
|
96
109
|
}
|
|
97
110
|
},
|
|
98
111
|
get state() {
|
|
@@ -104,7 +117,7 @@ export class FileRouterController {
|
|
|
104
117
|
if (options?.invalidate ?? true) {
|
|
105
118
|
this.invalidate(this.state.pathname)
|
|
106
119
|
}
|
|
107
|
-
return this.loadRoute(
|
|
120
|
+
return this.loadRoute({ transition: options?.transition })
|
|
108
121
|
},
|
|
109
122
|
setQuery: this.setQuery.bind(this),
|
|
110
123
|
setHash: this.setHash.bind(this),
|
|
@@ -125,12 +138,11 @@ export class FileRouterController {
|
|
|
125
138
|
if (curPage?.route === existing.route && loader) {
|
|
126
139
|
const p = this.currentPageProps.value
|
|
127
140
|
const transition =
|
|
128
|
-
(loader.
|
|
129
|
-
this.enableTransitions
|
|
141
|
+
(!loader.static && loader.transition) ?? this.enableTransitions
|
|
130
142
|
|
|
131
143
|
// Check cache first if caching is enabled
|
|
132
144
|
let cachedData = null
|
|
133
|
-
if (loader.
|
|
145
|
+
if (!loader.static && loader.cache) {
|
|
134
146
|
const cacheKey: CacheKey = {
|
|
135
147
|
path: this.state.pathname,
|
|
136
148
|
params: this.state.params,
|
|
@@ -147,9 +159,11 @@ export class FileRouterController {
|
|
|
147
159
|
error: null,
|
|
148
160
|
loading: false,
|
|
149
161
|
}
|
|
150
|
-
handleStateTransition(
|
|
151
|
-
|
|
152
|
-
|
|
162
|
+
handleStateTransition(
|
|
163
|
+
transition,
|
|
164
|
+
transitionId,
|
|
165
|
+
() => (this.currentPageProps.value = props)
|
|
166
|
+
)
|
|
153
167
|
} else {
|
|
154
168
|
// No cached data - show loading state and load data
|
|
155
169
|
const props = {
|
|
@@ -158,13 +172,14 @@ export class FileRouterController {
|
|
|
158
172
|
data: null,
|
|
159
173
|
error: null,
|
|
160
174
|
}
|
|
161
|
-
handleStateTransition(
|
|
162
|
-
|
|
163
|
-
|
|
175
|
+
handleStateTransition(
|
|
176
|
+
transition,
|
|
177
|
+
transitionId,
|
|
178
|
+
() => (this.currentPageProps.value = props)
|
|
179
|
+
)
|
|
164
180
|
|
|
165
181
|
this.loadRouteData(
|
|
166
182
|
config as PageConfigWithLoader,
|
|
167
|
-
props,
|
|
168
183
|
this.state,
|
|
169
184
|
transition
|
|
170
185
|
)
|
|
@@ -212,13 +227,38 @@ export class FileRouterController {
|
|
|
212
227
|
normalizePrefixPath(baseUrl),
|
|
213
228
|
]
|
|
214
229
|
|
|
215
|
-
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 {
|
|
216
255
|
const {
|
|
217
256
|
pages,
|
|
218
257
|
layouts,
|
|
219
258
|
guards,
|
|
220
259
|
page,
|
|
221
260
|
pageProps,
|
|
261
|
+
pagePropsPromise,
|
|
222
262
|
pageLayouts,
|
|
223
263
|
route,
|
|
224
264
|
params,
|
|
@@ -244,64 +284,46 @@ export class FileRouterController {
|
|
|
244
284
|
this.guards = (guards ??
|
|
245
285
|
{}) as unknown as FormattedViteImportMap<GuardModule>
|
|
246
286
|
if (__DEV__) {
|
|
287
|
+
validateRoutes(this.pages)
|
|
247
288
|
if (page.config) {
|
|
248
289
|
this.dev_onPageConfigDefined!(route, page.config)
|
|
249
290
|
}
|
|
250
291
|
}
|
|
251
|
-
|
|
252
|
-
validateRoutes(this.pages)
|
|
253
|
-
}
|
|
292
|
+
|
|
254
293
|
const loader = page.config?.loader
|
|
255
|
-
|
|
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 (
|
|
256
310
|
loader &&
|
|
257
|
-
((loader.
|
|
311
|
+
((!loader.static && pageProps.loading === true) || __DEV__)
|
|
258
312
|
) {
|
|
259
313
|
if (cacheData === null) {
|
|
260
|
-
this.loadRouteData(
|
|
261
|
-
page.config as PageConfigWithLoader,
|
|
262
|
-
pageProps,
|
|
263
|
-
this.state
|
|
264
|
-
)
|
|
314
|
+
this.loadRouteData(page.config as PageConfigWithLoader, this.state)
|
|
265
315
|
} else {
|
|
266
316
|
nextIdle(() => {
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
// @ts-ignore
|
|
274
|
-
const transition = loader.transition ?? this.enableTransitions
|
|
275
|
-
handleStateTransition(this.state.signal, transition, () => {
|
|
276
|
-
this.currentPageProps.value = props
|
|
317
|
+
handleStateTransition(transition, transitionId, () => {
|
|
318
|
+
this.currentPageProps.value = {
|
|
319
|
+
data: cacheData.value,
|
|
320
|
+
error: null,
|
|
321
|
+
loading: false,
|
|
322
|
+
}
|
|
277
323
|
})
|
|
278
324
|
})
|
|
279
325
|
}
|
|
280
326
|
}
|
|
281
|
-
} else {
|
|
282
|
-
this.pages = formatViteImportMap(
|
|
283
|
-
pages as ViteImportMap,
|
|
284
|
-
normalizedDir,
|
|
285
|
-
normalizedBaseUrl
|
|
286
|
-
)
|
|
287
|
-
|
|
288
|
-
this.layouts = formatViteImportMap(
|
|
289
|
-
layouts as ViteImportMap,
|
|
290
|
-
normalizedDir,
|
|
291
|
-
normalizedBaseUrl
|
|
292
|
-
)
|
|
293
|
-
this.guards = !guards
|
|
294
|
-
? {}
|
|
295
|
-
: (formatViteImportMap(
|
|
296
|
-
guards as ViteImportMap,
|
|
297
|
-
normalizedDir,
|
|
298
|
-
normalizedBaseUrl
|
|
299
|
-
) as unknown as FormattedViteImportMap<GuardModule>)
|
|
300
|
-
|
|
301
|
-
if (__DEV__) {
|
|
302
|
-
validateRoutes(this.pages)
|
|
303
|
-
}
|
|
304
|
-
this.loadRoute()
|
|
305
327
|
}
|
|
306
328
|
|
|
307
329
|
window.history.scrollRestoration = "manual"
|
|
@@ -365,15 +387,21 @@ export class FileRouterController {
|
|
|
365
387
|
}
|
|
366
388
|
|
|
367
389
|
scrollStack.replace(this.historyIndex, window.scrollX, window.scrollY)
|
|
368
|
-
|
|
369
|
-
|
|
390
|
+
|
|
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 = () => {
|
|
370
396
|
this.historyIndex = e.state.index
|
|
371
397
|
const offset = scrollStack.getItem(e.state.index)
|
|
372
398
|
if (offset !== undefined) {
|
|
373
399
|
window.scrollTo(...offset)
|
|
374
400
|
}
|
|
375
401
|
}
|
|
376
|
-
}
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
this.loadRoute({ onPaint })
|
|
377
405
|
})
|
|
378
406
|
}
|
|
379
407
|
|
|
@@ -422,12 +450,14 @@ export class FileRouterController {
|
|
|
422
450
|
fileRouterInstance.current = null
|
|
423
451
|
}
|
|
424
452
|
|
|
425
|
-
private async loadRoute(
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
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
|
+
|
|
431
461
|
this.abortController?.abort()
|
|
432
462
|
const signal = (this.abortController = new AbortController()).signal
|
|
433
463
|
|
|
@@ -526,7 +556,7 @@ See https://kirujs.dev/docs/api/file-router#404 for more information.`
|
|
|
526
556
|
path,
|
|
527
557
|
fromPath
|
|
528
558
|
)
|
|
529
|
-
if (redirectPath) {
|
|
559
|
+
if (redirectPath !== null) {
|
|
530
560
|
this.state.pathname = path
|
|
531
561
|
return this.navigate(redirectPath, {
|
|
532
562
|
replace: true,
|
|
@@ -535,60 +565,58 @@ See https://kirujs.dev/docs/api/file-router#404 for more information.`
|
|
|
535
565
|
}
|
|
536
566
|
}
|
|
537
567
|
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
query: routerState.query,
|
|
547
|
-
}
|
|
548
|
-
cachedData = routerCache.current!.get(cacheKey, loader.cache)
|
|
549
|
-
}
|
|
568
|
+
let props: Record<string, unknown> = {}
|
|
569
|
+
if (!!loader) {
|
|
570
|
+
props = {
|
|
571
|
+
data: null,
|
|
572
|
+
error: null,
|
|
573
|
+
loading: true,
|
|
574
|
+
}
|
|
575
|
+
}
|
|
550
576
|
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
}
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
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
|
+
? {
|
|
564
590
|
data: null,
|
|
591
|
+
error: new FileRouterDataLoadError(error),
|
|
592
|
+
loading: false,
|
|
593
|
+
}
|
|
594
|
+
: {
|
|
595
|
+
data: data,
|
|
565
596
|
error: null,
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
} else {
|
|
576
|
-
const staticProps = page.__KIRU_STATIC_PROPS__?.[path]
|
|
577
|
-
if (!staticProps) {
|
|
578
|
-
return this.loadRoute(path, props, enableTransition, true)
|
|
579
|
-
}
|
|
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)
|
|
580
606
|
|
|
581
|
-
|
|
607
|
+
if (cachedData !== null) {
|
|
582
608
|
props = {
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
error: error ? new FileRouterDataLoadError(error) : null,
|
|
609
|
+
data: cachedData.value,
|
|
610
|
+
error: null,
|
|
586
611
|
loading: false,
|
|
587
|
-
}
|
|
612
|
+
} satisfies PageProps<PageConfig<unknown>>
|
|
588
613
|
}
|
|
589
614
|
}
|
|
590
615
|
|
|
591
|
-
|
|
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, () => {
|
|
592
620
|
this.state = routerState
|
|
593
621
|
this.currentPage.value = {
|
|
594
622
|
component: page.default,
|
|
@@ -607,6 +635,16 @@ See https://kirujs.dev/docs/api/file-router#404 for more information.`
|
|
|
607
635
|
path,
|
|
608
636
|
fromPath
|
|
609
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
|
+
}
|
|
610
648
|
})
|
|
611
649
|
})
|
|
612
650
|
} catch (error) {
|
|
@@ -617,19 +655,19 @@ See https://kirujs.dev/docs/api/file-router#404 for more information.`
|
|
|
617
655
|
|
|
618
656
|
private async loadRouteData(
|
|
619
657
|
config: PageConfigWithLoader,
|
|
620
|
-
props: Record<string, unknown>,
|
|
621
658
|
routerState: RouterState,
|
|
622
|
-
enableTransition = this.enableTransitions
|
|
659
|
+
enableTransition = this.enableTransitions,
|
|
660
|
+
id = transitionId
|
|
623
661
|
) {
|
|
624
662
|
const { loader } = config
|
|
625
663
|
|
|
626
664
|
// Load data from loader (cache check is now done earlier in loadRoute)
|
|
627
|
-
loader
|
|
665
|
+
return loader
|
|
628
666
|
.load({ ...routerState, context: { ...requestContext.current } })
|
|
629
667
|
.then(
|
|
630
668
|
(data) => {
|
|
631
669
|
// Cache the data if caching is enabled
|
|
632
|
-
if (loader.
|
|
670
|
+
if (!loader.static && loader.cache) {
|
|
633
671
|
const cacheKey: CacheKey = {
|
|
634
672
|
path: routerState.pathname,
|
|
635
673
|
params: routerState.params,
|
|
@@ -655,14 +693,13 @@ See https://kirujs.dev/docs/api/file-router#404 for more information.`
|
|
|
655
693
|
if (routerState.signal.aborted) return
|
|
656
694
|
|
|
657
695
|
const transition =
|
|
658
|
-
(loader.
|
|
696
|
+
(!loader.static && loader.transition) ?? enableTransition
|
|
659
697
|
|
|
660
|
-
handleStateTransition(
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
})
|
|
698
|
+
return handleStateTransition(
|
|
699
|
+
transition,
|
|
700
|
+
id,
|
|
701
|
+
() => (this.currentPageProps.value = state)
|
|
702
|
+
)
|
|
666
703
|
})
|
|
667
704
|
}
|
|
668
705
|
|
|
@@ -690,20 +727,17 @@ See https://kirujs.dev/docs/api/file-router#404 for more information.`
|
|
|
690
727
|
const url = new URL(path, "http://localhost")
|
|
691
728
|
const { hash: nextHash, pathname: nextPath } = url
|
|
692
729
|
const { hash: prevHash, pathname: prevPath } = this.state
|
|
693
|
-
if (
|
|
730
|
+
if (
|
|
731
|
+
(nextHash === prevHash && nextPath === prevPath) ||
|
|
732
|
+
this.onBeforeLeave(prevPath) === false
|
|
733
|
+
) {
|
|
694
734
|
return
|
|
695
735
|
}
|
|
696
736
|
|
|
697
|
-
if (this.onBeforeLeave(prevPath) === false) {
|
|
698
|
-
return
|
|
699
|
-
}
|
|
700
737
|
this.updateHistoryState(path, options)
|
|
701
738
|
|
|
702
|
-
this.
|
|
703
|
-
|
|
704
|
-
void 0,
|
|
705
|
-
options?.transition ?? this.enableTransitions
|
|
706
|
-
).then(() => {
|
|
739
|
+
const transition = options?.transition ?? this.enableTransitions
|
|
740
|
+
this.loadRoute({ transition }).then(() => {
|
|
707
741
|
if (nextHash !== prevHash) {
|
|
708
742
|
window.dispatchEvent(new HashChangeEvent("hashchange"))
|
|
709
743
|
}
|
|
@@ -814,23 +848,32 @@ function buildQueryString(
|
|
|
814
848
|
}
|
|
815
849
|
|
|
816
850
|
async function handleStateTransition(
|
|
817
|
-
signal: AbortSignal,
|
|
818
851
|
enableTransition: boolean,
|
|
852
|
+
id: number,
|
|
819
853
|
callback: () => void
|
|
820
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
|
+
}
|
|
821
864
|
if (!enableTransition || typeof document.startViewTransition !== "function") {
|
|
822
865
|
return new Promise<void>((resolve) => {
|
|
823
866
|
callback()
|
|
824
867
|
nextIdle(resolve)
|
|
825
868
|
})
|
|
826
869
|
}
|
|
827
|
-
const
|
|
870
|
+
const transition = document.startViewTransition(() => {
|
|
828
871
|
callback()
|
|
829
872
|
flushSync()
|
|
830
873
|
})
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
874
|
+
currentTransition = { id, transition }
|
|
875
|
+
await transition.finished
|
|
876
|
+
currentTransition = null
|
|
834
877
|
}
|
|
835
878
|
|
|
836
879
|
function validateRoutes(pageMap: FormattedViteImportMap) {
|
package/src/router/ssg/index.ts
CHANGED
|
@@ -86,7 +86,7 @@ export async function render(
|
|
|
86
86
|
const abortController = new AbortController()
|
|
87
87
|
|
|
88
88
|
if (config.loader) {
|
|
89
|
-
if (config.loader.
|
|
89
|
+
if (!config.loader.static || __DEV__) {
|
|
90
90
|
props = { loading: true, data: null, error: null }
|
|
91
91
|
} else {
|
|
92
92
|
const routerState: RouterState = {
|
package/src/router/ssr/index.ts
CHANGED
|
@@ -21,6 +21,8 @@ import type {
|
|
|
21
21
|
GuardModule,
|
|
22
22
|
PageModule,
|
|
23
23
|
} from "../types.internal.js"
|
|
24
|
+
import { createStatefulPromise } from "../../utils/promise.js"
|
|
25
|
+
import { PAGE_DATA_PROMISE_ID } from "../constants.js"
|
|
24
26
|
|
|
25
27
|
export interface SSRRenderContext {
|
|
26
28
|
pages: FormattedViteImportMap<PageModule>
|
|
@@ -48,14 +50,11 @@ export async function render(
|
|
|
48
50
|
): Promise<SSRRenderResult> {
|
|
49
51
|
const extName = path.extname(url)
|
|
50
52
|
if (extName && extName.length > 0) {
|
|
51
|
-
return {
|
|
52
|
-
httpResponse: null,
|
|
53
|
-
}
|
|
53
|
+
return { httpResponse: null }
|
|
54
54
|
} else if (url.startsWith("/@")) {
|
|
55
|
-
return {
|
|
56
|
-
httpResponse: null,
|
|
57
|
-
}
|
|
55
|
+
return { httpResponse: null }
|
|
58
56
|
}
|
|
57
|
+
|
|
59
58
|
const u = new URL(url, "http://localhost")
|
|
60
59
|
const pathSegments = u.pathname.split("/").filter(Boolean)
|
|
61
60
|
let routeMatch = matchRoute(ctx.pages, pathSegments)
|
|
@@ -134,35 +133,48 @@ export async function render(
|
|
|
134
133
|
{ ...ctx.userContext },
|
|
135
134
|
u.pathname
|
|
136
135
|
)
|
|
137
|
-
if (redirectPath) {
|
|
136
|
+
if (redirectPath !== null) {
|
|
138
137
|
return createRedirectResult(redirectPath)
|
|
139
138
|
}
|
|
140
139
|
}
|
|
141
140
|
|
|
141
|
+
let documentShell = renderToString(createElement(ctx.Document))
|
|
142
|
+
|
|
143
|
+
if (
|
|
144
|
+
documentShell.includes("</body>") ||
|
|
145
|
+
!documentShell.includes("<kiru-body-outlet>")
|
|
146
|
+
) {
|
|
147
|
+
throw new Error(
|
|
148
|
+
"[kiru/router]: Document is expected to contain a <Body.Outlet> element. See https://kirujs.dev/docs/api/file-router#general-usage"
|
|
149
|
+
)
|
|
150
|
+
}
|
|
151
|
+
|
|
142
152
|
const query = parseQuery(u.search)
|
|
143
153
|
|
|
144
154
|
let props = {} as PageProps<PageConfig>
|
|
145
155
|
const config = page.config ?? {}
|
|
146
|
-
const
|
|
156
|
+
const abortSignal = new AbortController().signal
|
|
157
|
+
|
|
158
|
+
const routerState: RouterState = {
|
|
159
|
+
params,
|
|
160
|
+
query,
|
|
161
|
+
pathname: u.pathname,
|
|
162
|
+
hash: "",
|
|
163
|
+
signal: abortSignal,
|
|
164
|
+
}
|
|
147
165
|
|
|
148
|
-
|
|
149
|
-
if (config.loader) {
|
|
166
|
+
let pageDataPromise: Kiru.StatefulPromise<unknown> | null = null
|
|
167
|
+
if (config.loader && !config.loader.static) {
|
|
150
168
|
props = {
|
|
151
169
|
data: null,
|
|
152
170
|
error: null,
|
|
153
171
|
loading: true,
|
|
154
172
|
}
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
documentShell.includes("</body>") ||
|
|
161
|
-
!documentShell.includes("<kiru-body-outlet>")
|
|
162
|
-
) {
|
|
163
|
-
throw new Error(
|
|
164
|
-
"[kiru/router]: Document is expected to contain a <Body.Outlet> element. See https://kirujs.dev/docs/api/file-router#general-usage"
|
|
165
|
-
)
|
|
173
|
+
const promise = config.loader.load({
|
|
174
|
+
...routerState,
|
|
175
|
+
context: { ...ctx.userContext },
|
|
176
|
+
})
|
|
177
|
+
pageDataPromise = createStatefulPromise(PAGE_DATA_PROMISE_ID, promise)
|
|
166
178
|
}
|
|
167
179
|
|
|
168
180
|
const children = wrapWithLayouts(
|
|
@@ -173,25 +185,18 @@ export async function render(
|
|
|
173
185
|
props
|
|
174
186
|
)
|
|
175
187
|
|
|
176
|
-
const routerContextValue = {
|
|
177
|
-
state: {
|
|
178
|
-
params,
|
|
179
|
-
query,
|
|
180
|
-
pathname: u.pathname,
|
|
181
|
-
hash: "",
|
|
182
|
-
signal: abortController.signal,
|
|
183
|
-
} satisfies RouterState,
|
|
184
|
-
}
|
|
185
|
-
|
|
186
188
|
const app = createElement(RouterContext.Provider, {
|
|
187
189
|
children: createElement(RequestContext.Provider, {
|
|
188
190
|
children: Fragment({ children }),
|
|
189
191
|
value: ctx.userContext,
|
|
190
192
|
}),
|
|
191
|
-
value:
|
|
193
|
+
value: { state: routerState },
|
|
194
|
+
})
|
|
195
|
+
|
|
196
|
+
let { immediate: pageOutletContent, stream } = renderToReadableStream(app, {
|
|
197
|
+
data: pageDataPromise ? [pageDataPromise] : [],
|
|
192
198
|
})
|
|
193
199
|
|
|
194
|
-
let { immediate: pageOutletContent, stream } = renderToReadableStream(app)
|
|
195
200
|
const hasHeadContent = pageOutletContent.includes("<kiru-head-content>")
|
|
196
201
|
const hasHeadOutlet = documentShell.includes("<kiru-head-outlet>")
|
|
197
202
|
|
package/src/router/types.ts
CHANGED
|
@@ -13,6 +13,7 @@ export interface FileRouterPreloadConfig {
|
|
|
13
13
|
guards?: FormattedViteImportMap<GuardModule>
|
|
14
14
|
page: PageModule
|
|
15
15
|
pageProps: Record<string, unknown>
|
|
16
|
+
pagePropsPromise?: Promise<AsyncTaskState<unknown, FileRouterDataLoadError>>
|
|
16
17
|
pageLayouts: DefaultComponentModule[]
|
|
17
18
|
params: RouteParams
|
|
18
19
|
query: RouteQuery
|
|
@@ -139,13 +140,9 @@ export type PageDataLoaderConfig<T = unknown> = {
|
|
|
139
140
|
} & (
|
|
140
141
|
| {
|
|
141
142
|
/**
|
|
142
|
-
*
|
|
143
|
-
* @default "client"
|
|
144
|
-
* @description
|
|
145
|
-
* - **static**: The page data is loaded at build time and never updated
|
|
146
|
-
* - **client**: The page data is loaded upon navigation and updated on subsequent navigations
|
|
143
|
+
* Indicates that the page data should only be loaded at build time
|
|
147
144
|
*/
|
|
148
|
-
|
|
145
|
+
static?: false
|
|
149
146
|
/**
|
|
150
147
|
* Enable transitions when swapping between "load", "error" and "data" states
|
|
151
148
|
*/
|
|
@@ -165,13 +162,9 @@ export type PageDataLoaderConfig<T = unknown> = {
|
|
|
165
162
|
}
|
|
166
163
|
| {
|
|
167
164
|
/**
|
|
168
|
-
*
|
|
169
|
-
* @default "client"
|
|
170
|
-
* @description
|
|
171
|
-
* - **static**: The page data is loaded at build time and never updated
|
|
172
|
-
* - **client**: The page data is loaded upon navigation and updated on subsequent navigations
|
|
165
|
+
* Indicates that the page data should only be loaded at build time
|
|
173
166
|
*/
|
|
174
|
-
|
|
167
|
+
static: true
|
|
175
168
|
}
|
|
176
169
|
)
|
|
177
170
|
|