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.
Files changed (61) hide show
  1. package/dist/components/derive.d.ts +1 -1
  2. package/dist/components/derive.d.ts.map +1 -1
  3. package/dist/components/derive.js +3 -2
  4. package/dist/components/derive.js.map +1 -1
  5. package/dist/dom.d.ts.map +1 -1
  6. package/dist/dom.js +6 -2
  7. package/dist/dom.js.map +1 -1
  8. package/dist/hooks/usePromise.d.ts +2 -1
  9. package/dist/hooks/usePromise.d.ts.map +1 -1
  10. package/dist/hooks/usePromise.js +31 -62
  11. package/dist/hooks/usePromise.js.map +1 -1
  12. package/dist/index.js +1 -2
  13. package/dist/index.js.map +1 -1
  14. package/dist/router/client/index.d.ts.map +1 -1
  15. package/dist/router/client/index.js +12 -4
  16. package/dist/router/client/index.js.map +1 -1
  17. package/dist/router/constants.d.ts +2 -0
  18. package/dist/router/constants.d.ts.map +1 -0
  19. package/dist/router/constants.js +2 -0
  20. package/dist/router/constants.js.map +1 -0
  21. package/dist/router/fileRouterController.d.ts.map +1 -1
  22. package/dist/router/fileRouterController.js +123 -106
  23. package/dist/router/fileRouterController.js.map +1 -1
  24. package/dist/router/ssg/index.js +1 -1
  25. package/dist/router/ssg/index.js.map +1 -1
  26. package/dist/router/ssr/index.d.ts.map +1 -1
  27. package/dist/router/ssr/index.js +29 -26
  28. package/dist/router/ssr/index.js.map +1 -1
  29. package/dist/router/types.d.ts +5 -12
  30. package/dist/router/types.d.ts.map +1 -1
  31. package/dist/scheduler.d.ts +14 -3
  32. package/dist/scheduler.d.ts.map +1 -1
  33. package/dist/scheduler.js +3 -4
  34. package/dist/scheduler.js.map +1 -1
  35. package/dist/ssr/server.d.ts +8 -1
  36. package/dist/ssr/server.d.ts.map +1 -1
  37. package/dist/ssr/server.js +28 -18
  38. package/dist/ssr/server.js.map +1 -1
  39. package/dist/utils/index.d.ts +1 -1
  40. package/dist/utils/index.d.ts.map +1 -1
  41. package/dist/utils/index.js +1 -1
  42. package/dist/utils/index.js.map +1 -1
  43. package/dist/utils/promise.d.ts +2 -0
  44. package/dist/utils/promise.d.ts.map +1 -1
  45. package/dist/utils/promise.js +45 -1
  46. package/dist/utils/promise.js.map +1 -1
  47. package/package.json +1 -1
  48. package/src/components/derive.ts +5 -3
  49. package/src/dom.ts +5 -1
  50. package/src/hooks/usePromise.ts +57 -77
  51. package/src/index.ts +1 -1
  52. package/src/router/client/index.ts +16 -4
  53. package/src/router/constants.ts +1 -0
  54. package/src/router/fileRouterController.ts +180 -137
  55. package/src/router/ssg/index.ts +1 -1
  56. package/src/router/ssr/index.ts +38 -33
  57. package/src/router/types.ts +5 -12
  58. package/src/scheduler.ts +20 -3
  59. package/src/ssr/server.ts +48 -22
  60. package/src/utils/index.ts +1 -1
  61. 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(void 0, void 0, true)
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(void 0, void 0, options?.transition)
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.mode !== "static" && loader.transition) ??
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.mode !== "static" && loader.cache) {
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(this.state.signal, transition, () => {
151
- this.currentPageProps.value = props
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(this.state.signal, transition, () => {
162
- this.currentPageProps.value = props
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
- if (__DEV__) {
252
- validateRoutes(this.pages)
253
- }
292
+
254
293
  const loader = page.config?.loader
255
- if (
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.mode !== "static" && pageProps.loading === true) || __DEV__)
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
- const props = {
268
- ...pageProps,
269
- data: cacheData.value,
270
- error: null,
271
- loading: false,
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
- this.loadRoute().then(() => {
369
- if (e.state != null) {
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
- path: string = window.location.pathname,
427
- props: Record<string, unknown> = {},
428
- enableTransition = this.enableTransitions,
429
- isStatic404 = false
430
- ): Promise<void> {
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
- if (loader) {
539
- if (loader.mode !== "static" || __DEV__) {
540
- // Check cache first if caching is enabled
541
- let cachedData = null
542
- if (loader.mode !== "static" && loader.cache) {
543
- const cacheKey: CacheKey = {
544
- path: routerState.pathname,
545
- params: routerState.params,
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
- if (cachedData !== null) {
552
- // Use cached data immediately - no loading state needed
553
- props = {
554
- ...props,
555
- data: cachedData.value,
556
- error: null,
557
- loading: false,
558
- } satisfies PageProps<PageConfig<unknown>>
559
- } else {
560
- // No cached data - show loading state and load data
561
- props = {
562
- ...props,
563
- loading: true,
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
- } satisfies PageProps<PageConfig<unknown>>
567
-
568
- this.loadRouteData(
569
- config as PageConfigWithLoader,
570
- props,
571
- routerState,
572
- enableTransition
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
- const { data, error } = staticProps
607
+ if (cachedData !== null) {
582
608
  props = {
583
- ...props,
584
- data: data,
585
- error: error ? new FileRouterDataLoadError(error) : null,
609
+ data: cachedData.value,
610
+ error: null,
586
611
  loading: false,
587
- } as PageProps<PageConfig<unknown>>
612
+ } satisfies PageProps<PageConfig<unknown>>
588
613
  }
589
614
  }
590
615
 
591
- return handleStateTransition(signal, enableTransition, () => {
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.mode !== "static" && loader.cache) {
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.mode !== "static" && loader.transition) ?? enableTransition
696
+ (!loader.static && loader.transition) ?? enableTransition
659
697
 
660
- handleStateTransition(routerState.signal, transition, () => {
661
- this.currentPageProps.value = {
662
- ...props,
663
- ...state,
664
- } satisfies PageProps<PageConfig<unknown>>
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 (nextHash === prevHash && nextPath === prevPath) {
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.loadRoute(
703
- void 0,
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 vt = document.startViewTransition(() => {
870
+ const transition = document.startViewTransition(() => {
828
871
  callback()
829
872
  flushSync()
830
873
  })
831
-
832
- signal.addEventListener("abort", () => vt.skipTransition())
833
- await vt.ready
874
+ currentTransition = { id, transition }
875
+ await transition.finished
876
+ currentTransition = null
834
877
  }
835
878
 
836
879
  function validateRoutes(pageMap: FormattedViteImportMap) {
@@ -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.mode !== "static" || __DEV__) {
89
+ if (!config.loader.static || __DEV__) {
90
90
  props = { loading: true, data: null, error: null }
91
91
  } else {
92
92
  const routerState: RouterState = {
@@ -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 abortController = new AbortController()
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
- // PageConfig loaders don't run on the server
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
- let documentShell = renderToString(createElement(ctx.Document))
158
-
159
- if (
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: routerContextValue,
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
 
@@ -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
- * The mode to use for the page data loader
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
- mode?: "client"
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
- * The mode to use for the page data loader
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
- mode: "static"
167
+ static: true
175
168
  }
176
169
  )
177
170