kiru 0.50.0-preview.0 → 0.50.0-preview.2

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 (118) hide show
  1. package/dist/components/lazy.d.ts.map +1 -1
  2. package/dist/components/lazy.js +11 -136
  3. package/dist/components/lazy.js.map +1 -1
  4. package/dist/components/suspense.d.ts +7 -6
  5. package/dist/components/suspense.d.ts.map +1 -1
  6. package/dist/components/suspense.js +1 -4
  7. package/dist/components/suspense.js.map +1 -1
  8. package/dist/constants.d.ts +1 -2
  9. package/dist/constants.d.ts.map +1 -1
  10. package/dist/constants.js +1 -2
  11. package/dist/constants.js.map +1 -1
  12. package/dist/hmr.d.ts +1 -0
  13. package/dist/hmr.d.ts.map +1 -1
  14. package/dist/hmr.js +7 -0
  15. package/dist/hmr.js.map +1 -1
  16. package/dist/recursiveRender.d.ts +6 -0
  17. package/dist/recursiveRender.d.ts.map +1 -0
  18. package/dist/recursiveRender.js +109 -0
  19. package/dist/recursiveRender.js.map +1 -0
  20. package/dist/renderToString.d.ts.map +1 -1
  21. package/dist/renderToString.js +6 -112
  22. package/dist/renderToString.js.map +1 -1
  23. package/dist/router/config.d.ts +3 -0
  24. package/dist/router/config.d.ts.map +1 -0
  25. package/dist/router/config.js +13 -0
  26. package/dist/router/config.js.map +1 -0
  27. package/dist/router/context.d.ts +15 -0
  28. package/dist/router/context.d.ts.map +1 -0
  29. package/dist/router/context.js +11 -0
  30. package/dist/router/context.js.map +1 -0
  31. package/dist/router/errors.d.ts +4 -0
  32. package/dist/router/errors.d.ts.map +1 -0
  33. package/dist/router/errors.js +7 -0
  34. package/dist/router/errors.js.map +1 -0
  35. package/dist/router/fileRouter.d.ts +48 -0
  36. package/dist/router/fileRouter.d.ts.map +1 -0
  37. package/dist/router/fileRouter.js +311 -0
  38. package/dist/router/fileRouter.js.map +1 -0
  39. package/dist/router/globals.d.ts +5 -0
  40. package/dist/router/globals.d.ts.map +1 -0
  41. package/dist/router/globals.js +4 -0
  42. package/dist/router/globals.js.map +1 -0
  43. package/dist/router/index.d.ts +7 -0
  44. package/dist/router/index.d.ts.map +1 -0
  45. package/dist/router/index.js +7 -0
  46. package/dist/router/index.js.map +1 -0
  47. package/dist/router/link.d.ts +8 -0
  48. package/dist/router/link.d.ts.map +1 -0
  49. package/dist/router/link.js +15 -0
  50. package/dist/router/link.js.map +1 -0
  51. package/dist/router/types.d.ts +63 -0
  52. package/dist/router/types.d.ts.map +1 -0
  53. package/dist/router/types.internal.d.ts +12 -0
  54. package/dist/router/types.internal.d.ts.map +1 -0
  55. package/dist/router/types.internal.js +2 -0
  56. package/dist/router/types.internal.js.map +1 -0
  57. package/dist/router/types.js +2 -0
  58. package/dist/router/types.js.map +1 -0
  59. package/dist/ssr/server.d.ts.map +1 -1
  60. package/dist/ssr/server.js +5 -109
  61. package/dist/ssr/server.js.map +1 -1
  62. package/dist/types.d.ts +2 -2
  63. package/dist/types.d.ts.map +1 -1
  64. package/dist/types.utils.d.ts +1 -8
  65. package/dist/types.utils.d.ts.map +1 -1
  66. package/dist/utils/vdom.d.ts.map +1 -1
  67. package/dist/utils/vdom.js +2 -5
  68. package/dist/utils/vdom.js.map +1 -1
  69. package/package.json +4 -8
  70. package/src/components/lazy.ts +12 -169
  71. package/src/components/suspense.ts +8 -10
  72. package/src/constants.ts +0 -2
  73. package/src/hmr.ts +8 -0
  74. package/src/recursiveRender.ts +127 -0
  75. package/src/renderToString.ts +7 -137
  76. package/src/router/config.ts +15 -0
  77. package/src/router/context.ts +23 -0
  78. package/src/router/errors.ts +6 -0
  79. package/src/router/fileRouter.ts +474 -0
  80. package/src/router/globals.ts +5 -0
  81. package/src/router/index.ts +6 -0
  82. package/src/router/link.ts +32 -0
  83. package/src/router/types.internal.ts +14 -0
  84. package/src/router/types.ts +81 -0
  85. package/src/ssr/server.ts +6 -136
  86. package/src/types.ts +0 -2
  87. package/src/types.utils.ts +1 -14
  88. package/src/utils/vdom.ts +1 -5
  89. package/dist/components/router/index.d.ts +0 -3
  90. package/dist/components/router/index.d.ts.map +0 -1
  91. package/dist/components/router/index.js +0 -3
  92. package/dist/components/router/index.js.map +0 -1
  93. package/dist/components/router/route.d.ts +0 -46
  94. package/dist/components/router/route.d.ts.map +0 -1
  95. package/dist/components/router/route.js +0 -8
  96. package/dist/components/router/route.js.map +0 -1
  97. package/dist/components/router/router.d.ts +0 -62
  98. package/dist/components/router/router.d.ts.map +0 -1
  99. package/dist/components/router/router.js +0 -177
  100. package/dist/components/router/router.js.map +0 -1
  101. package/dist/components/router/routerUtils.d.ts +0 -5
  102. package/dist/components/router/routerUtils.d.ts.map +0 -1
  103. package/dist/components/router/routerUtils.js +0 -39
  104. package/dist/components/router/routerUtils.js.map +0 -1
  105. package/dist/ssr/hydrationBoundary.d.ts +0 -27
  106. package/dist/ssr/hydrationBoundary.d.ts.map +0 -1
  107. package/dist/ssr/hydrationBoundary.js +0 -30
  108. package/dist/ssr/hydrationBoundary.js.map +0 -1
  109. package/dist/ssr/index.d.ts +0 -2
  110. package/dist/ssr/index.d.ts.map +0 -1
  111. package/dist/ssr/index.js +0 -2
  112. package/dist/ssr/index.js.map +0 -1
  113. package/src/components/router/index.ts +0 -2
  114. package/src/components/router/route.ts +0 -51
  115. package/src/components/router/router.ts +0 -273
  116. package/src/components/router/routerUtils.ts +0 -49
  117. package/src/ssr/hydrationBoundary.ts +0 -63
  118. package/src/ssr/index.ts +0 -1
@@ -0,0 +1,474 @@
1
+ import { Signal, computed, flushSync } from "../index.js"
2
+ import { __DEV__ } from "../env.js"
3
+ import { createElement } from "../element.js"
4
+ import { useState, useEffect } from "../hooks/index.js"
5
+ import { RouterContext, type FileRouterContextType } from "./context.js"
6
+ import { FileRouterDataLoadError } from "./errors.js"
7
+ import { fileRouterInstance } from "./globals.js"
8
+ import type {
9
+ ErrorPageProps,
10
+ FileRouterConfig,
11
+ PageConfig,
12
+ PageProps,
13
+ RouteQuery,
14
+ RouterState,
15
+ } from "./types.js"
16
+ import type {
17
+ DefaultComponentModule,
18
+ PageModule,
19
+ ViteImportMap,
20
+ } from "./types.internal.js"
21
+
22
+ export class FileRouterController {
23
+ private enableTransitions: boolean
24
+ private pages: ViteImportMap
25
+ private layouts: ViteImportMap
26
+ private abortController: AbortController
27
+ private currentPage: Signal<{
28
+ component: Kiru.FC<any>
29
+ config?: PageConfig
30
+ route: string
31
+ } | null>
32
+ private currentPageProps: Signal<PageProps<PageConfig>>
33
+ private currentLayouts: Signal<Kiru.FC[]>
34
+ private loading: Signal<boolean>
35
+ private state: Signal<RouterState>
36
+ private contextValue: Signal<FileRouterContextType>
37
+ private cleanups: (() => void)[] = []
38
+ private filePathToPageRoute: Map<
39
+ string,
40
+ {
41
+ route: string
42
+ config: PageConfig
43
+ }
44
+ >
45
+ private pageRouteToConfig: Map<string, PageConfig>
46
+ private currentRoute: string | null
47
+
48
+ constructor(props: FileRouterProps) {
49
+ fileRouterInstance.current = this
50
+ this.pages = {}
51
+ this.layouts = {}
52
+ this.abortController = new AbortController()
53
+ this.currentPage = new Signal(null)
54
+ this.currentPageProps = new Signal({})
55
+ this.currentLayouts = new Signal([])
56
+ this.loading = new Signal(true)
57
+ this.state = new Signal<RouterState>({
58
+ path: window.location.pathname,
59
+ params: {},
60
+ query: {},
61
+ signal: this.abortController.signal,
62
+ })
63
+ this.contextValue = computed<FileRouterContextType>(() => ({
64
+ state: this.state.value,
65
+ navigate: this.navigate.bind(this),
66
+ setQuery: this.setQuery.bind(this),
67
+ reload: (options?: { transition?: boolean }) =>
68
+ this.loadRoute(void 0, void 0, options?.transition),
69
+ }))
70
+ this.filePathToPageRoute = new Map()
71
+ this.pageRouteToConfig = new Map()
72
+ this.currentRoute = null
73
+
74
+ const {
75
+ pages,
76
+ layouts,
77
+ dir = "/pages",
78
+ baseUrl = "/",
79
+ transition,
80
+ } = props.config
81
+ this.enableTransitions = !!transition
82
+ const [normalizedDir, normalizedBaseUrl] = [
83
+ normalizePrefixPath(dir),
84
+ normalizePrefixPath(baseUrl),
85
+ ]
86
+ this.pages = formatViteImportMap(
87
+ pages as ViteImportMap,
88
+ normalizedDir,
89
+ normalizedBaseUrl
90
+ )
91
+ this.layouts = formatViteImportMap(
92
+ layouts as ViteImportMap,
93
+ normalizedDir,
94
+ normalizedBaseUrl
95
+ )
96
+
97
+ this.loadRoute()
98
+
99
+ const handlePopState = () => this.loadRoute()
100
+ window.addEventListener("popstate", handlePopState)
101
+ this.cleanups.push(() =>
102
+ window.removeEventListener("popstate", handlePopState)
103
+ )
104
+ }
105
+
106
+ public onPageConfigDefined<T extends PageConfig>(fp: string, config: T) {
107
+ const existing = this.filePathToPageRoute.get(fp)
108
+ if (existing === undefined) {
109
+ const route = this.currentRoute
110
+ if (!route) return
111
+ this.filePathToPageRoute.set(fp, { route, config })
112
+ return
113
+ }
114
+ const curPage = this.currentPage.value
115
+ if (curPage?.route === existing.route && config.loader) {
116
+ const p = this.currentPageProps.value
117
+ let transition = this.enableTransitions
118
+ if (config.loader.transition !== undefined) {
119
+ transition = config.loader.transition
120
+ }
121
+ const props = {
122
+ ...p,
123
+ loading: true,
124
+ data: null,
125
+ error: null,
126
+ }
127
+ handleStateTransition(this.state.value.signal, transition, () => {
128
+ this.currentPageProps.value = props
129
+ })
130
+
131
+ this.loadRouteData(config.loader, props, this.state.value, transition)
132
+ }
133
+
134
+ this.pageRouteToConfig.set(existing.route, config)
135
+ }
136
+
137
+ public getContextValue() {
138
+ return this.contextValue.value
139
+ }
140
+
141
+ public getChildren() {
142
+ const page = this.currentPage.value,
143
+ props = this.currentPageProps.value,
144
+ layouts = this.currentLayouts.value
145
+
146
+ if (page) {
147
+ // Wrap component with layouts (outermost first)
148
+ return layouts.reduceRight(
149
+ (children, Layout) => createElement(Layout, { children }),
150
+ createElement(page.component, props)
151
+ )
152
+ }
153
+
154
+ return null
155
+ }
156
+
157
+ public dispose() {
158
+ this.cleanups.forEach((cleanup) => cleanup())
159
+ }
160
+
161
+ private matchRoute(pathSegments: string[]) {
162
+ outer: for (const [route, pageModuleLoader] of Object.entries(this.pages)) {
163
+ const routeSegments = route.split("/").filter(Boolean)
164
+
165
+ const pathMatchingSegments = routeSegments.filter(
166
+ (seg) => !seg.startsWith("(") && !seg.endsWith(")")
167
+ )
168
+
169
+ if (pathMatchingSegments.length !== pathSegments.length) {
170
+ continue
171
+ }
172
+ const params: Record<string, string> = {}
173
+
174
+ for (let i = 0; i < pathMatchingSegments.length; i++) {
175
+ const routeSeg = pathMatchingSegments[i]
176
+ if (routeSeg.startsWith(":")) {
177
+ const key = routeSeg.slice(1)
178
+ params[key] = pathSegments[i]
179
+ continue
180
+ }
181
+ if (routeSeg !== pathSegments[i]) {
182
+ continue outer
183
+ }
184
+ }
185
+
186
+ return { route, pageModuleLoader, params, routeSegments }
187
+ }
188
+
189
+ return null
190
+ }
191
+
192
+ private async loadRoute(
193
+ path: string = window.location.pathname,
194
+ props: PageProps<PageConfig> = {},
195
+ enableTransition = this.enableTransitions
196
+ ): Promise<void> {
197
+ this.loading.value = true
198
+ this.abortController?.abort()
199
+
200
+ const query = parseQuery(window.location.search)
201
+ const controller = (this.abortController = new AbortController())
202
+ const signal = controller.signal
203
+
204
+ try {
205
+ const pathSegments = path.split("/").filter(Boolean)
206
+ const routeMatch = this.matchRoute(pathSegments)
207
+
208
+ if (!routeMatch) {
209
+ const _404 = this.matchRoute(["404"])
210
+ if (!_404) {
211
+ if (__DEV__) {
212
+ console.error(
213
+ `No 404 route defined (path: ${path}). See https://kirujs.dev/404 for more information.`
214
+ )
215
+ }
216
+ return
217
+ }
218
+ const errorProps = {
219
+ source: { path },
220
+ } satisfies ErrorPageProps
221
+
222
+ return this.navigate("/404", { replace: true, props: errorProps })
223
+ }
224
+
225
+ const { route, pageModuleLoader, params, routeSegments } = routeMatch
226
+
227
+ this.currentRoute = route
228
+ const pagePromise = pageModuleLoader()
229
+
230
+ const layoutPromises = ["/", ...routeSegments].reduce((acc, _, i) => {
231
+ const layoutPath = "/" + routeSegments.slice(0, i).join("/")
232
+ const layoutLoad = this.layouts[layoutPath]
233
+
234
+ if (!layoutLoad) {
235
+ return acc
236
+ }
237
+
238
+ return [...acc, layoutLoad()]
239
+ }, [] as Promise<DefaultComponentModule>[])
240
+
241
+ const [page, ...layouts] = await Promise.all([
242
+ pagePromise,
243
+ ...layoutPromises,
244
+ ])
245
+
246
+ this.currentRoute = null
247
+ if (controller.signal.aborted) return
248
+
249
+ if (typeof page.default !== "function") {
250
+ throw new Error("Route component must be a default exported function")
251
+ }
252
+
253
+ const routerState: RouterState = {
254
+ path,
255
+ params,
256
+ query,
257
+ signal,
258
+ }
259
+
260
+ let config = (page as unknown as PageModule).config
261
+ if (this.pageRouteToConfig.has(route)) {
262
+ config = this.pageRouteToConfig.get(route)
263
+ }
264
+
265
+ if (config?.loader) {
266
+ props = { ...props, loading: true, data: null, error: null }
267
+ this.loadRouteData(config.loader, props, routerState, enableTransition)
268
+ }
269
+
270
+ handleStateTransition(signal, enableTransition, () => {
271
+ this.currentPage.value = {
272
+ component: page.default,
273
+ config,
274
+ route: "/" + routeSegments.join("/"),
275
+ }
276
+ this.state.value = routerState
277
+ this.currentPageProps.value = props
278
+ this.currentLayouts.value = layouts
279
+ .filter((m) => typeof m.default === "function")
280
+ .map((m) => m.default)
281
+ })
282
+ } catch (error) {
283
+ console.error("Failed to load route component:", error)
284
+ this.currentPage.value = null
285
+ } finally {
286
+ this.loading.value = false
287
+ }
288
+ }
289
+
290
+ private async loadRouteData(
291
+ loader: NonNullable<PageConfig["loader"]>,
292
+ props: PageProps<PageConfig>,
293
+ routerState: RouterState,
294
+ enableTransition = this.enableTransitions
295
+ ) {
296
+ loader
297
+ .load(routerState)
298
+ .then(
299
+ (data) => ({ data, error: null }),
300
+ (error) => ({
301
+ data: null,
302
+ error: new FileRouterDataLoadError(error),
303
+ })
304
+ )
305
+ .then(({ data, error }) => {
306
+ if (routerState.signal.aborted) return
307
+
308
+ let transition = enableTransition
309
+ if (loader.transition !== undefined) {
310
+ transition = loader.transition
311
+ }
312
+
313
+ handleStateTransition(routerState.signal, transition, () => {
314
+ this.currentPageProps.value = {
315
+ ...props,
316
+ loading: false,
317
+ data,
318
+ error,
319
+ }
320
+ })
321
+ })
322
+ }
323
+
324
+ private async navigate(
325
+ path: string,
326
+ options?: {
327
+ replace?: boolean
328
+ transition?: boolean
329
+ props?: Record<string, unknown>
330
+ }
331
+ ) {
332
+ const f = options?.replace ? "replaceState" : "pushState"
333
+ window.history[f]({}, "", path)
334
+ window.dispatchEvent(new PopStateEvent("popstate", { state: {} }))
335
+ return this.loadRoute(path, options?.props, options?.transition)
336
+ }
337
+
338
+ private setQuery(query: RouteQuery) {
339
+ const queryString = buildQueryString(query)
340
+ const newUrl = `${this.state.value.path}${
341
+ queryString ? `?${queryString}` : ""
342
+ }`
343
+ window.history.pushState(null, "", newUrl)
344
+ this.state.value = { ...this.state.value, query }
345
+ }
346
+ }
347
+
348
+ export interface FileRouterProps {
349
+ /**
350
+ * The router configuration
351
+ * @example
352
+ * ```ts
353
+ *<FileRouter
354
+ config={{
355
+ dir: "/fbr-app", // optional, defaults to "/pages"
356
+ baseUrl: "/app", // optional, defaults to "/"
357
+ pages: import.meta.glob("/∗∗/index.tsx"),
358
+ layouts: import.meta.glob("/∗∗/layout.tsx"),
359
+ transition: true
360
+ }}
361
+ />
362
+ * ```
363
+ */
364
+ config: FileRouterConfig
365
+ }
366
+
367
+ export function FileRouter(props: FileRouterProps): JSX.Element {
368
+ const [controller] = useState(() => new FileRouterController(props))
369
+ useEffect(() => () => controller.dispose(), [controller])
370
+
371
+ return createElement(
372
+ RouterContext.Provider,
373
+ { value: controller.getContextValue() },
374
+ controller.getChildren()
375
+ )
376
+ }
377
+
378
+ // Utility functions
379
+
380
+ function parseQuery(
381
+ search: string
382
+ ): Record<string, string | string[] | undefined> {
383
+ const params = new URLSearchParams(search)
384
+ const query: Record<string, string | string[] | undefined> = {}
385
+
386
+ for (const [key, value] of params.entries()) {
387
+ if (query[key]) {
388
+ // Convert to array if multiple values
389
+ if (Array.isArray(query[key])) {
390
+ ;(query[key] as string[]).push(value)
391
+ } else {
392
+ query[key] = [query[key] as string, value]
393
+ }
394
+ } else {
395
+ query[key] = value
396
+ }
397
+ }
398
+
399
+ return query
400
+ }
401
+
402
+ function buildQueryString(
403
+ query: Record<string, string | string[] | undefined>
404
+ ): string {
405
+ const params = new URLSearchParams()
406
+
407
+ for (const [key, value] of Object.entries(query)) {
408
+ if (value !== undefined) {
409
+ if (Array.isArray(value)) {
410
+ value.forEach((v) => params.append(key, v))
411
+ } else {
412
+ params.set(key, value)
413
+ }
414
+ }
415
+ }
416
+
417
+ return params.toString()
418
+ }
419
+
420
+ function formatViteImportMap(
421
+ map: ViteImportMap,
422
+ dir: string,
423
+ baseUrl: string
424
+ ): ViteImportMap {
425
+ return Object.keys(map).reduce((acc, key) => {
426
+ let k = key
427
+ const dirIndex = k.indexOf(dir)
428
+ if (dirIndex === -1) {
429
+ return acc
430
+ }
431
+
432
+ k = k.slice(dirIndex + dir.length)
433
+ while (k.startsWith("/")) {
434
+ k = k.slice(1)
435
+ }
436
+ k = k.split("/").slice(0, -1).join("/") // remove filename
437
+ k = k.replace(/\[([^\]]+)\]/g, ":$1") // replace [param] with :param
438
+
439
+ return {
440
+ ...acc,
441
+ [baseUrl + k]: map[key],
442
+ }
443
+ }, {})
444
+ }
445
+
446
+ function normalizePrefixPath(path: string) {
447
+ while (path.startsWith(".")) {
448
+ path = path.slice(1)
449
+ }
450
+ path = `/${path}/`
451
+ while (path.startsWith("//")) {
452
+ path = path.slice(1)
453
+ }
454
+ while (path.endsWith("//")) {
455
+ path = path.slice(0, -1)
456
+ }
457
+ return path
458
+ }
459
+
460
+ function handleStateTransition(
461
+ signal: AbortSignal,
462
+ enableTransition: boolean,
463
+ callback: () => void
464
+ ) {
465
+ if (!enableTransition || typeof document.startViewTransition !== "function") {
466
+ return callback()
467
+ }
468
+ const vt = document.startViewTransition(() => {
469
+ callback()
470
+ flushSync()
471
+ })
472
+
473
+ signal.addEventListener("abort", () => vt.skipTransition())
474
+ }
@@ -0,0 +1,5 @@
1
+ import type { FileRouterController } from "./fileRouter"
2
+
3
+ export const fileRouterInstance = {
4
+ current: null as FileRouterController | null,
5
+ }
@@ -0,0 +1,6 @@
1
+ export * from "./config.js"
2
+ export * from "./errors.js"
3
+ export { FileRouter, type FileRouterProps } from "./fileRouter.js"
4
+ export { useFileRouter } from "./context.js"
5
+ export * from "./link.js"
6
+ export * from "./types.js"
@@ -0,0 +1,32 @@
1
+ import type { ElementProps } from "../types"
2
+ import { createElement } from "../element.js"
3
+ import { useCallback } from "../hooks/index.js"
4
+ import { useFileRouter } from "./context.js"
5
+
6
+ export interface LinkProps extends ElementProps<"a"> {
7
+ to: string
8
+ replace?: boolean
9
+ transition?: boolean
10
+ }
11
+
12
+ export const Link: Kiru.FC<LinkProps> = ({
13
+ to,
14
+ onclick,
15
+ replace,
16
+ transition,
17
+ ...props
18
+ }) => {
19
+ const { navigate } = useFileRouter()
20
+
21
+ const handleClick = useCallback(
22
+ (e: Kiru.MouseEvent<HTMLAnchorElement>) => {
23
+ onclick?.(e)
24
+ if (e.defaultPrevented) return
25
+ e.preventDefault()
26
+ navigate(to, { replace, transition })
27
+ },
28
+ [to, navigate, onclick, replace]
29
+ )
30
+
31
+ return createElement("a", { href: to, onclick: handleClick, ...props })
32
+ }
@@ -0,0 +1,14 @@
1
+ import type { PageConfig } from "./types"
2
+
3
+ export interface DefaultComponentModule {
4
+ default: Kiru.FC
5
+ }
6
+
7
+ export interface PageModule {
8
+ default: DefaultComponentModule
9
+ config?: PageConfig
10
+ }
11
+
12
+ export interface ViteImportMap {
13
+ [fp: string]: () => Promise<DefaultComponentModule>
14
+ }
@@ -0,0 +1,81 @@
1
+ import { AsyncTaskState } from "../types.utils"
2
+ import { FileRouterDataLoadError } from "./errors"
3
+
4
+ export interface FileRouterConfig {
5
+ /**
6
+ * The directory to load routes from
7
+ * @default "/pages"
8
+ */
9
+ dir?: string
10
+ /**
11
+ * The import map to use for loading pages
12
+ * @example
13
+ * ```tsx
14
+ * <FileRouter config={{ pages: import.meta.glob("/∗∗/index.tsx"), ... }} />
15
+ * ```
16
+ */
17
+ pages: Record<string, unknown>
18
+ /**
19
+ * The import map to use for loading layouts
20
+ * @example
21
+ * ```tsx
22
+ * <FileRouter config={{ pages: import.meta.glob("/∗∗/layout.tsx"), ... }} />
23
+ * ```
24
+ */
25
+ layouts: Record<string, unknown>
26
+
27
+ /**
28
+ * The base url to use as a prefix for route matching
29
+ * @default "/"
30
+ */
31
+ baseUrl?: string
32
+
33
+ /**
34
+ * Enable transitions for all routes and loading states
35
+ * @default false
36
+ */
37
+ transition?: boolean
38
+ }
39
+
40
+ export interface RouteParams {
41
+ [key: string]: string
42
+ }
43
+
44
+ export interface RouteQuery {
45
+ [key: string]: string | string[] | undefined
46
+ }
47
+
48
+ export interface RouterState {
49
+ path: string
50
+ params: RouteParams
51
+ query: RouteQuery
52
+ signal: AbortSignal
53
+ }
54
+
55
+ type PageDataLoaderContext = RouterState & {}
56
+
57
+ export interface PageDataLoaderConfig<T = unknown> {
58
+ load: (context: PageDataLoaderContext) => Promise<T>
59
+ transition?: boolean
60
+ }
61
+
62
+ export interface PageConfig {
63
+ loader?: PageDataLoaderConfig
64
+ // title?: string
65
+ // description?: string
66
+ // meta?: Record<string, string>
67
+ }
68
+
69
+ export type PageProps<T extends PageConfig> =
70
+ T["loader"] extends PageDataLoaderConfig
71
+ ? AsyncTaskState<
72
+ Awaited<ReturnType<T["loader"]["load"]>>,
73
+ FileRouterDataLoadError
74
+ >
75
+ : {}
76
+
77
+ export interface ErrorPageProps {
78
+ source?: {
79
+ path: string
80
+ }
81
+ }