kiru 0.50.8 → 0.51.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/constants.d.ts.map +1 -1
- package/dist/constants.js +1 -0
- package/dist/constants.js.map +1 -1
- package/dist/reconciler.d.ts.map +1 -1
- package/dist/reconciler.js +4 -12
- package/dist/reconciler.js.map +1 -1
- package/dist/router/client/index.d.ts +10 -0
- package/dist/router/client/index.d.ts.map +1 -0
- package/dist/router/client/index.js +73 -0
- package/dist/router/client/index.js.map +1 -0
- package/dist/router/dev/index.d.ts +2 -0
- package/dist/router/dev/index.d.ts.map +1 -0
- package/dist/router/dev/index.js +46 -0
- package/dist/router/dev/index.js.map +1 -0
- package/dist/router/fileRouter.d.ts +1 -1
- package/dist/router/fileRouter.d.ts.map +1 -1
- package/dist/router/fileRouter.js +10 -2
- package/dist/router/fileRouter.js.map +1 -1
- package/dist/router/fileRouterController.d.ts +4 -4
- package/dist/router/fileRouterController.d.ts.map +1 -1
- package/dist/router/fileRouterController.js +118 -47
- 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/head.d.ts +38 -0
- package/dist/router/head.d.ts.map +1 -0
- package/dist/router/head.js +63 -0
- package/dist/router/head.js.map +1 -0
- package/dist/router/index.d.ts +5 -0
- package/dist/router/index.d.ts.map +1 -1
- package/dist/router/index.js +5 -0
- package/dist/router/index.js.map +1 -1
- package/dist/router/pageConfig.d.ts +1 -1
- package/dist/router/pageConfig.d.ts.map +1 -1
- package/dist/router/pageConfig.js +1 -1
- package/dist/router/pageConfig.js.map +1 -1
- package/dist/router/server/index.d.ts +17 -0
- package/dist/router/server/index.d.ts.map +1 -0
- package/dist/router/server/index.js +160 -0
- package/dist/router/server/index.js.map +1 -0
- package/dist/router/types.d.ts +35 -11
- package/dist/router/types.d.ts.map +1 -1
- package/dist/router/types.internal.d.ts +6 -2
- package/dist/router/types.internal.d.ts.map +1 -1
- package/dist/router/utils/index.d.ts +4 -4
- package/dist/router/utils/index.d.ts.map +1 -1
- package/dist/router/utils/index.js +18 -5
- package/dist/router/utils/index.js.map +1 -1
- package/dist/types.dom.d.ts +2 -2
- package/dist/types.dom.d.ts.map +1 -1
- package/dist/types.utils.d.ts +3 -0
- package/dist/types.utils.d.ts.map +1 -1
- package/dist/utils/vdom.d.ts +2 -1
- package/dist/utils/vdom.d.ts.map +1 -1
- package/dist/utils/vdom.js +6 -1
- package/dist/utils/vdom.js.map +1 -1
- package/package.json +17 -1
- package/src/constants.ts +1 -0
- package/src/reconciler.ts +9 -19
- package/src/router/client/index.ts +97 -0
- package/src/router/dev/index.ts +63 -0
- package/src/router/fileRouter.ts +12 -3
- package/src/router/fileRouterController.ts +185 -73
- package/src/router/globals.ts +4 -0
- package/src/router/head.ts +66 -0
- package/src/router/index.ts +7 -0
- package/src/router/pageConfig.ts +2 -2
- package/src/router/server/index.ts +214 -0
- package/src/router/types.internal.ts +6 -2
- package/src/router/types.ts +43 -20
- package/src/router/utils/index.ts +25 -7
- package/src/types.dom.ts +2 -5
- package/src/types.utils.ts +4 -0
- package/src/utils/vdom.ts +9 -0
|
@@ -3,11 +3,11 @@ import { flushSync } from "../scheduler.js"
|
|
|
3
3
|
import { __DEV__ } from "../env.js"
|
|
4
4
|
import { type FileRouterContextType } from "./context.js"
|
|
5
5
|
import { FileRouterDataLoadError } from "./errors.js"
|
|
6
|
-
import { fileRouterInstance } from "./globals.js"
|
|
6
|
+
import { fileRouterInstance, fileRouterRoute } from "./globals.js"
|
|
7
7
|
import type {
|
|
8
|
-
ErrorPageProps,
|
|
9
8
|
FileRouterConfig,
|
|
10
9
|
PageConfig,
|
|
10
|
+
PageDataLoaderConfig,
|
|
11
11
|
PageProps,
|
|
12
12
|
RouteQuery,
|
|
13
13
|
RouterState,
|
|
@@ -21,23 +21,28 @@ import {
|
|
|
21
21
|
formatViteImportMap,
|
|
22
22
|
matchLayouts,
|
|
23
23
|
matchRoute,
|
|
24
|
+
match404Route,
|
|
24
25
|
normalizePrefixPath,
|
|
25
26
|
parseQuery,
|
|
26
27
|
wrapWithLayouts,
|
|
27
28
|
} from "./utils/index.js"
|
|
28
29
|
|
|
30
|
+
interface PageConfigWithLoader<T = unknown> extends PageConfig {
|
|
31
|
+
loader: PageDataLoaderConfig<T>
|
|
32
|
+
}
|
|
33
|
+
|
|
29
34
|
export class FileRouterController {
|
|
30
35
|
public contextValue: FileRouterContextType
|
|
31
36
|
private enableTransitions: boolean
|
|
32
37
|
private pages: FormattedViteImportMap
|
|
33
38
|
private layouts: FormattedViteImportMap
|
|
34
39
|
private abortController: AbortController
|
|
35
|
-
private
|
|
40
|
+
private currentPage: Signal<{
|
|
36
41
|
component: Kiru.FC<any>
|
|
37
42
|
config?: PageConfig
|
|
38
43
|
route: string
|
|
39
44
|
} | null>
|
|
40
|
-
private currentPageProps: Signal<
|
|
45
|
+
private currentPageProps: Signal<Record<string, unknown>>
|
|
41
46
|
private currentLayouts: Signal<Kiru.FC[]>
|
|
42
47
|
private state: RouterState
|
|
43
48
|
private cleanups: (() => void)[] = []
|
|
@@ -46,14 +51,13 @@ export class FileRouterController {
|
|
|
46
51
|
{ route: string; config: PageConfig }
|
|
47
52
|
>
|
|
48
53
|
private pageRouteToConfig?: Map<string, PageConfig>
|
|
49
|
-
private currentRoute: string | null
|
|
50
54
|
|
|
51
|
-
constructor(
|
|
52
|
-
|
|
55
|
+
constructor() {
|
|
56
|
+
this.enableTransitions = false
|
|
53
57
|
this.pages = {}
|
|
54
58
|
this.layouts = {}
|
|
55
59
|
this.abortController = new AbortController()
|
|
56
|
-
this.
|
|
60
|
+
this.currentPage = new Signal(null)
|
|
57
61
|
this.currentPageProps = new Signal({})
|
|
58
62
|
this.currentLayouts = new Signal([])
|
|
59
63
|
this.state = {
|
|
@@ -77,46 +81,107 @@ export class FileRouterController {
|
|
|
77
81
|
this.filePathToPageRoute = new Map()
|
|
78
82
|
this.pageRouteToConfig = new Map()
|
|
79
83
|
}
|
|
80
|
-
this.currentRoute = null
|
|
81
84
|
|
|
82
|
-
const
|
|
85
|
+
const handlePopState = () => this.loadRoute()
|
|
86
|
+
window.addEventListener("popstate", handlePopState)
|
|
87
|
+
this.cleanups.push(() =>
|
|
88
|
+
window.removeEventListener("popstate", handlePopState)
|
|
89
|
+
)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
public init(config: FileRouterConfig) {
|
|
93
|
+
const {
|
|
94
|
+
pages,
|
|
95
|
+
layouts,
|
|
96
|
+
dir = "/pages",
|
|
97
|
+
baseUrl = "/",
|
|
98
|
+
transition,
|
|
99
|
+
preloaded,
|
|
100
|
+
} = config
|
|
83
101
|
this.enableTransitions = !!transition
|
|
84
102
|
const [normalizedDir, normalizedBaseUrl] = [
|
|
85
103
|
normalizePrefixPath(dir),
|
|
86
104
|
normalizePrefixPath(baseUrl),
|
|
87
105
|
]
|
|
88
|
-
this.pages = formatViteImportMap(
|
|
89
|
-
pages as ViteImportMap,
|
|
90
|
-
normalizedDir,
|
|
91
|
-
normalizedBaseUrl
|
|
92
|
-
)
|
|
93
|
-
if (__DEV__) {
|
|
94
|
-
validateRoutes(this.pages)
|
|
95
|
-
}
|
|
96
|
-
this.layouts = formatViteImportMap(
|
|
97
|
-
layouts as ViteImportMap,
|
|
98
|
-
normalizedDir,
|
|
99
|
-
normalizedBaseUrl
|
|
100
|
-
)
|
|
101
106
|
|
|
102
|
-
|
|
107
|
+
if (preloaded) {
|
|
108
|
+
const {
|
|
109
|
+
pages,
|
|
110
|
+
layouts,
|
|
111
|
+
page,
|
|
112
|
+
pageProps,
|
|
113
|
+
pageLayouts,
|
|
114
|
+
route,
|
|
115
|
+
params,
|
|
116
|
+
query,
|
|
117
|
+
} = preloaded
|
|
118
|
+
this.state = {
|
|
119
|
+
params,
|
|
120
|
+
query,
|
|
121
|
+
path: route,
|
|
122
|
+
signal: this.abortController.signal,
|
|
123
|
+
}
|
|
124
|
+
this.currentPage.value = {
|
|
125
|
+
component: page.default,
|
|
126
|
+
config: page.config,
|
|
127
|
+
route,
|
|
128
|
+
}
|
|
129
|
+
this.currentPageProps.value = pageProps
|
|
130
|
+
this.currentLayouts.value = pageLayouts.map((l) => l.default)
|
|
131
|
+
this.pages = pages
|
|
132
|
+
this.layouts = layouts
|
|
133
|
+
if (__DEV__) {
|
|
134
|
+
if (page.config) {
|
|
135
|
+
this.onPageConfigDefined(route, page.config)
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
if (__DEV__) {
|
|
139
|
+
validateRoutes(this.pages)
|
|
140
|
+
}
|
|
141
|
+
const loader = page.config?.loader
|
|
142
|
+
if (__DEV__) {
|
|
143
|
+
if (loader) {
|
|
144
|
+
this.loadRouteData(
|
|
145
|
+
page.config as PageConfigWithLoader,
|
|
146
|
+
pageProps,
|
|
147
|
+
this.state
|
|
148
|
+
)
|
|
149
|
+
}
|
|
150
|
+
} else if (loader && loader.mode !== "static") {
|
|
151
|
+
this.loadRouteData(
|
|
152
|
+
page.config as PageConfigWithLoader,
|
|
153
|
+
pageProps,
|
|
154
|
+
this.state
|
|
155
|
+
)
|
|
156
|
+
}
|
|
157
|
+
} else {
|
|
158
|
+
this.pages = formatViteImportMap(
|
|
159
|
+
pages as ViteImportMap,
|
|
160
|
+
normalizedDir,
|
|
161
|
+
normalizedBaseUrl
|
|
162
|
+
)
|
|
103
163
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
164
|
+
this.layouts = formatViteImportMap(
|
|
165
|
+
layouts as ViteImportMap,
|
|
166
|
+
normalizedDir,
|
|
167
|
+
normalizedBaseUrl
|
|
168
|
+
)
|
|
169
|
+
if (__DEV__) {
|
|
170
|
+
validateRoutes(this.pages)
|
|
171
|
+
}
|
|
172
|
+
this.loadRoute()
|
|
173
|
+
}
|
|
109
174
|
}
|
|
110
175
|
|
|
111
|
-
public onPageConfigDefined<T extends PageConfig
|
|
176
|
+
public onPageConfigDefined<T extends PageConfig<any>>(fp: string, config: T) {
|
|
112
177
|
const existing = this.filePathToPageRoute?.get(fp)
|
|
113
178
|
if (existing === undefined) {
|
|
114
|
-
const route =
|
|
179
|
+
const route = fileRouterRoute.current
|
|
115
180
|
if (!route) return
|
|
116
181
|
this.filePathToPageRoute?.set(fp, { route, config })
|
|
117
182
|
return
|
|
118
183
|
}
|
|
119
|
-
const curPage = this.
|
|
184
|
+
const curPage = this.currentPage.value
|
|
120
185
|
if (curPage?.route === existing.route && config.loader) {
|
|
121
186
|
const p = this.currentPageProps.value
|
|
122
187
|
let transition = this.enableTransitions
|
|
@@ -133,14 +198,19 @@ export class FileRouterController {
|
|
|
133
198
|
this.currentPageProps.value = props
|
|
134
199
|
})
|
|
135
200
|
|
|
136
|
-
this.loadRouteData(
|
|
201
|
+
this.loadRouteData(
|
|
202
|
+
config as PageConfigWithLoader,
|
|
203
|
+
props,
|
|
204
|
+
this.state,
|
|
205
|
+
transition
|
|
206
|
+
)
|
|
137
207
|
}
|
|
138
208
|
|
|
139
209
|
this.pageRouteToConfig?.set(existing.route, config)
|
|
140
210
|
}
|
|
141
211
|
|
|
142
212
|
public getChildren() {
|
|
143
|
-
const page = this.
|
|
213
|
+
const page = this.currentPage.value
|
|
144
214
|
if (!page) return null
|
|
145
215
|
|
|
146
216
|
const props = this.currentPageProps.value,
|
|
@@ -150,24 +220,34 @@ export class FileRouterController {
|
|
|
150
220
|
}
|
|
151
221
|
|
|
152
222
|
public dispose() {
|
|
223
|
+
this.abortController?.abort()
|
|
153
224
|
this.cleanups.forEach((cleanup) => cleanup())
|
|
225
|
+
this.cleanups.length = 0
|
|
226
|
+
if (__DEV__) {
|
|
227
|
+
this.filePathToPageRoute?.clear()
|
|
228
|
+
this.pageRouteToConfig?.clear()
|
|
229
|
+
}
|
|
230
|
+
fileRouterRoute.current = null
|
|
231
|
+
fileRouterInstance.current = null
|
|
154
232
|
}
|
|
155
233
|
|
|
156
234
|
private async loadRoute(
|
|
157
235
|
path: string = window.location.pathname,
|
|
158
|
-
props:
|
|
159
|
-
enableTransition = this.enableTransitions
|
|
236
|
+
props: Record<string, unknown> = {},
|
|
237
|
+
enableTransition = this.enableTransitions,
|
|
238
|
+
isStatic404 = false
|
|
160
239
|
): Promise<void> {
|
|
161
240
|
this.abortController?.abort()
|
|
162
241
|
const signal = (this.abortController = new AbortController()).signal
|
|
163
242
|
|
|
164
243
|
try {
|
|
165
244
|
const pathSegments = path.split("/").filter(Boolean)
|
|
166
|
-
|
|
245
|
+
let routeMatch = matchRoute(this.pages, pathSegments)
|
|
167
246
|
|
|
168
|
-
if (!routeMatch) {
|
|
169
|
-
|
|
170
|
-
|
|
247
|
+
if (!routeMatch || isStatic404) {
|
|
248
|
+
// Try to find a 404 page in parent directories
|
|
249
|
+
const _404Match = match404Route(this.pages, pathSegments)
|
|
250
|
+
if (!_404Match) {
|
|
171
251
|
if (__DEV__) {
|
|
172
252
|
console.error(
|
|
173
253
|
`[kiru/router]: No 404 route defined (path: ${path}).
|
|
@@ -176,16 +256,12 @@ See https://kirujs.dev/docs/api/file-router#404 for more information.`
|
|
|
176
256
|
}
|
|
177
257
|
return
|
|
178
258
|
}
|
|
179
|
-
|
|
180
|
-
source: { path },
|
|
181
|
-
} satisfies ErrorPageProps
|
|
182
|
-
|
|
183
|
-
return this.navigate("/404", { replace: true, props: errorProps })
|
|
259
|
+
routeMatch = _404Match
|
|
184
260
|
}
|
|
185
261
|
|
|
186
262
|
const { route, pageEntry, params, routeSegments } = routeMatch
|
|
187
263
|
|
|
188
|
-
|
|
264
|
+
fileRouterRoute.current = route
|
|
189
265
|
const pagePromise = pageEntry.load()
|
|
190
266
|
|
|
191
267
|
const layoutPromises = matchLayouts(this.layouts, routeSegments).map(
|
|
@@ -193,12 +269,12 @@ See https://kirujs.dev/docs/api/file-router#404 for more information.`
|
|
|
193
269
|
)
|
|
194
270
|
|
|
195
271
|
const [page, ...layouts] = await Promise.all([
|
|
196
|
-
pagePromise
|
|
272
|
+
pagePromise as Promise<PageModule>,
|
|
197
273
|
...layoutPromises,
|
|
198
274
|
])
|
|
199
275
|
|
|
200
276
|
const query = parseQuery(window.location.search)
|
|
201
|
-
|
|
277
|
+
fileRouterRoute.current = null
|
|
202
278
|
if (signal.aborted) return
|
|
203
279
|
|
|
204
280
|
if (typeof page.default !== "function") {
|
|
@@ -214,21 +290,49 @@ See https://kirujs.dev/docs/api/file-router#404 for more information.`
|
|
|
214
290
|
signal,
|
|
215
291
|
}
|
|
216
292
|
|
|
217
|
-
let config =
|
|
293
|
+
let config = page.config ?? ({} as PageConfig)
|
|
218
294
|
if (__DEV__) {
|
|
219
295
|
if (this.pageRouteToConfig?.has(route)) {
|
|
220
|
-
config = this.pageRouteToConfig.get(route)
|
|
296
|
+
config = this.pageRouteToConfig.get(route)!
|
|
221
297
|
}
|
|
222
298
|
}
|
|
223
299
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
300
|
+
const { loader } = config
|
|
301
|
+
|
|
302
|
+
if (loader) {
|
|
303
|
+
if (loader.mode !== "static" || __DEV__) {
|
|
304
|
+
props = {
|
|
305
|
+
...props,
|
|
306
|
+
loading: true,
|
|
307
|
+
data: null,
|
|
308
|
+
error: null,
|
|
309
|
+
} satisfies PageProps<PageConfig<unknown>>
|
|
310
|
+
|
|
311
|
+
this.loadRouteData(
|
|
312
|
+
config as PageConfigWithLoader,
|
|
313
|
+
props,
|
|
314
|
+
routerState,
|
|
315
|
+
enableTransition
|
|
316
|
+
)
|
|
317
|
+
} else {
|
|
318
|
+
const staticProps = page.__KIRU_STATIC_PROPS__?.[path]
|
|
319
|
+
if (!staticProps) {
|
|
320
|
+
return this.loadRoute(path, props, enableTransition, true)
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
const { data, error } = staticProps
|
|
324
|
+
props = {
|
|
325
|
+
...props,
|
|
326
|
+
data: data,
|
|
327
|
+
error: error ? new FileRouterDataLoadError(error) : null,
|
|
328
|
+
loading: false,
|
|
329
|
+
} as PageProps<PageConfig<unknown>>
|
|
330
|
+
}
|
|
227
331
|
}
|
|
228
332
|
|
|
229
|
-
this.state = routerState
|
|
230
333
|
handleStateTransition(signal, enableTransition, () => {
|
|
231
|
-
this.
|
|
334
|
+
this.state = routerState
|
|
335
|
+
this.currentPage.value = {
|
|
232
336
|
component: page.default,
|
|
233
337
|
config,
|
|
234
338
|
route: "/" + routeSegments.join("/"),
|
|
@@ -240,26 +344,34 @@ See https://kirujs.dev/docs/api/file-router#404 for more information.`
|
|
|
240
344
|
})
|
|
241
345
|
} catch (error) {
|
|
242
346
|
console.error("[kiru/router]: Failed to load route component:", error)
|
|
243
|
-
this.
|
|
347
|
+
this.currentPage.value = null
|
|
244
348
|
}
|
|
245
349
|
}
|
|
246
350
|
|
|
247
351
|
private async loadRouteData(
|
|
248
|
-
|
|
249
|
-
props:
|
|
352
|
+
config: PageConfigWithLoader,
|
|
353
|
+
props: Record<string, unknown>,
|
|
250
354
|
routerState: RouterState,
|
|
251
355
|
enableTransition = this.enableTransitions
|
|
252
356
|
) {
|
|
357
|
+
const { loader } = config
|
|
253
358
|
loader
|
|
254
359
|
.load(routerState)
|
|
255
360
|
.then(
|
|
256
|
-
(data) =>
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
361
|
+
(data) =>
|
|
362
|
+
({
|
|
363
|
+
data,
|
|
364
|
+
error: null,
|
|
365
|
+
loading: false,
|
|
366
|
+
} satisfies PageProps<PageConfig<unknown>>),
|
|
367
|
+
(error) =>
|
|
368
|
+
({
|
|
369
|
+
data: null,
|
|
370
|
+
error: new FileRouterDataLoadError(error),
|
|
371
|
+
loading: false,
|
|
372
|
+
} satisfies PageProps<PageConfig<unknown>>)
|
|
261
373
|
)
|
|
262
|
-
.then((
|
|
374
|
+
.then((state) => {
|
|
263
375
|
if (routerState.signal.aborted) return
|
|
264
376
|
|
|
265
377
|
let transition = enableTransition
|
|
@@ -270,10 +382,8 @@ See https://kirujs.dev/docs/api/file-router#404 for more information.`
|
|
|
270
382
|
handleStateTransition(routerState.signal, transition, () => {
|
|
271
383
|
this.currentPageProps.value = {
|
|
272
384
|
...props,
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
error,
|
|
276
|
-
}
|
|
385
|
+
...state,
|
|
386
|
+
} satisfies PageProps<PageConfig<unknown>>
|
|
277
387
|
})
|
|
278
388
|
})
|
|
279
389
|
}
|
|
@@ -298,13 +408,13 @@ See https://kirujs.dev/docs/api/file-router#404 for more information.`
|
|
|
298
408
|
throw new Error(`No route defined (path: ${path}).`)
|
|
299
409
|
}
|
|
300
410
|
const { pageEntry, route } = routeMatch
|
|
301
|
-
|
|
411
|
+
fileRouterRoute.current = route
|
|
302
412
|
const pagePromise = pageEntry.load()
|
|
303
413
|
const layoutPromises = matchLayouts(this.layouts, route.split("/")).map(
|
|
304
414
|
(layoutEntry) => layoutEntry.load()
|
|
305
415
|
)
|
|
306
416
|
await Promise.all([pagePromise, ...layoutPromises])
|
|
307
|
-
|
|
417
|
+
fileRouterRoute.current = null
|
|
308
418
|
} catch (error) {
|
|
309
419
|
console.error("[kiru/router]: Failed to prefetch route:", error)
|
|
310
420
|
}
|
|
@@ -370,9 +480,11 @@ function validateRoutes(pageMap: FormattedViteImportMap) {
|
|
|
370
480
|
|
|
371
481
|
if (routeConflicts.length > 0) {
|
|
372
482
|
let warning = "[kiru/router]: Route conflicts detected:\n"
|
|
373
|
-
warning += routeConflicts
|
|
374
|
-
|
|
375
|
-
|
|
483
|
+
warning += routeConflicts
|
|
484
|
+
.map(([route1, route2]) => {
|
|
485
|
+
return ` - "${route1.filePath}" conflicts with "${route2.filePath}"\n`
|
|
486
|
+
})
|
|
487
|
+
.join("")
|
|
376
488
|
warning += "Routes are ordered by specificity (higher specificity wins)"
|
|
377
489
|
console.warn(warning)
|
|
378
490
|
}
|
package/src/router/globals.ts
CHANGED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { createElement } from "../index.js"
|
|
2
|
+
import { Signal } from "../signals/base.js"
|
|
3
|
+
import { isValidTextChild, isVNode } from "../utils/index.js"
|
|
4
|
+
|
|
5
|
+
export { Content, Outlet }
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Used with SSG. Renders content to the document head via a corresponding `<Head.Outlet>` component placed in your `document.tsx`.
|
|
9
|
+
* @example
|
|
10
|
+
* // src/pages/index.tsx
|
|
11
|
+
* export default function Index() {
|
|
12
|
+
* return (
|
|
13
|
+
* <div>
|
|
14
|
+
* <Head.Content>
|
|
15
|
+
* <title>My App - Home</title>
|
|
16
|
+
* </Head.Content>
|
|
17
|
+
* <h1>Home</h1>
|
|
18
|
+
* </div>
|
|
19
|
+
* )
|
|
20
|
+
}
|
|
21
|
+
*/
|
|
22
|
+
function Content({ children }: { children: JSX.Children }) {
|
|
23
|
+
if ("window" in globalThis) {
|
|
24
|
+
;(Array.isArray(children) ? children : [children])
|
|
25
|
+
.filter(isVNode)
|
|
26
|
+
.forEach(({ type, props }) => {
|
|
27
|
+
switch (type) {
|
|
28
|
+
case "title":
|
|
29
|
+
const title = (
|
|
30
|
+
Array.isArray(props.children) ? props.children : [props.children]
|
|
31
|
+
)
|
|
32
|
+
.map((c) => (Signal.isSignal(c) ? c.value : c))
|
|
33
|
+
.filter(isValidTextChild)
|
|
34
|
+
.join("")
|
|
35
|
+
return (document.title = title)
|
|
36
|
+
case "meta":
|
|
37
|
+
return document
|
|
38
|
+
.querySelector(`meta[name="${props.name}"]`)
|
|
39
|
+
?.setAttribute("content", String(props.content))
|
|
40
|
+
}
|
|
41
|
+
})
|
|
42
|
+
return null
|
|
43
|
+
}
|
|
44
|
+
return createElement("kiru-head-content", { children })
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Used with SSG. Renders content to the document head from a `<Head>` component in the currently rendered page.
|
|
49
|
+
* @example
|
|
50
|
+
* // document.tsx
|
|
51
|
+
* export default function Document() {
|
|
52
|
+
* return (
|
|
53
|
+
* <html lang="en">
|
|
54
|
+
* <head>
|
|
55
|
+
* <meta charset="utf-8" />
|
|
56
|
+
* <meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
57
|
+
* <Head.Outlet />
|
|
58
|
+
* </head>
|
|
59
|
+
* <body>{children}</body>
|
|
60
|
+
* </html>
|
|
61
|
+
* )
|
|
62
|
+
}
|
|
63
|
+
*/
|
|
64
|
+
function Outlet() {
|
|
65
|
+
return createElement("kiru-head-outlet")
|
|
66
|
+
}
|
package/src/router/index.ts
CHANGED
|
@@ -4,3 +4,10 @@ export { FileRouter, type FileRouterProps } from "./fileRouter.js"
|
|
|
4
4
|
export * from "./link.js"
|
|
5
5
|
export * from "./pageConfig.js"
|
|
6
6
|
export type * from "./types.js"
|
|
7
|
+
|
|
8
|
+
import { Content, Outlet } from "./head.js"
|
|
9
|
+
|
|
10
|
+
export const Head = {
|
|
11
|
+
Content,
|
|
12
|
+
Outlet,
|
|
13
|
+
}
|
package/src/router/pageConfig.ts
CHANGED
|
@@ -2,8 +2,8 @@ import { __DEV__ } from "../env.js"
|
|
|
2
2
|
import { fileRouterInstance } from "./globals.js"
|
|
3
3
|
import type { PageConfig } from "./types"
|
|
4
4
|
|
|
5
|
-
export function definePageConfig<T
|
|
6
|
-
if (__DEV__) {
|
|
5
|
+
export function definePageConfig<T>(config: PageConfig<T>): PageConfig<T> {
|
|
6
|
+
if (__DEV__ && "window" in globalThis) {
|
|
7
7
|
const filePath = window.__kiru?.HMRContext?.getCurrentFilePath()
|
|
8
8
|
const fileRouter = fileRouterInstance.current
|
|
9
9
|
if (filePath && fileRouter) {
|