kiru 0.50.7 → 0.51.0-preview.0
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.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 +224 -206
- package/src/types.utils.ts +4 -0
- package/src/utils/vdom.ts +9 -0
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) {
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import { createElement } from "../../element.js"
|
|
2
|
+
import { renderToReadableStream } from "../../ssr/server.js"
|
|
3
|
+
import {
|
|
4
|
+
matchLayouts,
|
|
5
|
+
matchRoute,
|
|
6
|
+
match404Route,
|
|
7
|
+
parseQuery,
|
|
8
|
+
wrapWithLayouts,
|
|
9
|
+
} from "../utils/index.js"
|
|
10
|
+
import { RouterContext } from "../context.js"
|
|
11
|
+
import type { PageConfig, PageProps, RouterState } from "../types.js"
|
|
12
|
+
import type { Readable } from "node:stream"
|
|
13
|
+
import { FormattedViteImportMap, PageModule } from "../types.internal.js"
|
|
14
|
+
import { __DEV__ } from "../../env.js"
|
|
15
|
+
import { FileRouterDataLoadError } from "../errors.js"
|
|
16
|
+
|
|
17
|
+
export interface RenderContext {
|
|
18
|
+
pages: FormattedViteImportMap
|
|
19
|
+
layouts: FormattedViteImportMap
|
|
20
|
+
Document: Kiru.FC
|
|
21
|
+
registerModule: (moduleId: string) => void
|
|
22
|
+
registerPreloadedPageProps: (props: Record<string, unknown>) => void
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface RenderResult {
|
|
26
|
+
status: number
|
|
27
|
+
immediate: string
|
|
28
|
+
stream: Readable | null
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export async function render(
|
|
32
|
+
url: string,
|
|
33
|
+
ctx: RenderContext,
|
|
34
|
+
result?: RenderResult
|
|
35
|
+
): Promise<RenderResult> {
|
|
36
|
+
const u = new URL(url, "http://localhost")
|
|
37
|
+
const pathSegments = u.pathname.split("/").filter(Boolean)
|
|
38
|
+
let routeMatch = matchRoute(ctx.pages, pathSegments)
|
|
39
|
+
|
|
40
|
+
if (!routeMatch) {
|
|
41
|
+
// Try to find a 404 page in parent directories
|
|
42
|
+
const fourOhFourMatch = match404Route(ctx.pages, pathSegments)
|
|
43
|
+
if (fourOhFourMatch) {
|
|
44
|
+
routeMatch = fourOhFourMatch
|
|
45
|
+
} else {
|
|
46
|
+
// Fallback to root 404 or default fallback
|
|
47
|
+
if (url === "/404" && result) {
|
|
48
|
+
if (__DEV__) {
|
|
49
|
+
console.warn(
|
|
50
|
+
"[kiru/router]: No 404 route defined. Using fallback 404 page."
|
|
51
|
+
)
|
|
52
|
+
}
|
|
53
|
+
return {
|
|
54
|
+
status: 404,
|
|
55
|
+
immediate:
|
|
56
|
+
"<!doctype html><html><head><title>Not Found</title></head><body><h1>404</h1></body></html>",
|
|
57
|
+
stream: null,
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return render("/404", ctx, {
|
|
61
|
+
...(result ?? {}),
|
|
62
|
+
immediate: "",
|
|
63
|
+
stream: null,
|
|
64
|
+
status: 404,
|
|
65
|
+
})
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const { pageEntry, routeSegments, params } = routeMatch
|
|
70
|
+
const is404Route = routeMatch.routeSegments.includes("404")
|
|
71
|
+
const layoutEntries = matchLayouts(ctx.layouts, routeSegments)
|
|
72
|
+
|
|
73
|
+
if (__DEV__) {
|
|
74
|
+
;[pageEntry, ...layoutEntries].forEach((e) => {
|
|
75
|
+
ctx.registerModule(e.filePath!)
|
|
76
|
+
})
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const [page, ...layouts] = await Promise.all([
|
|
80
|
+
pageEntry.load() as unknown as Promise<PageModule>,
|
|
81
|
+
...layoutEntries.map((layoutEntry) => layoutEntry.load()),
|
|
82
|
+
])
|
|
83
|
+
|
|
84
|
+
const query = parseQuery(u.search)
|
|
85
|
+
|
|
86
|
+
let props = {} as PageProps<PageConfig>
|
|
87
|
+
const config = page.config ?? {}
|
|
88
|
+
const abortController = new AbortController()
|
|
89
|
+
|
|
90
|
+
if (config.loader) {
|
|
91
|
+
if (config.loader.mode !== "static" || __DEV__) {
|
|
92
|
+
props = { loading: true, data: null, error: null }
|
|
93
|
+
} else {
|
|
94
|
+
const routerState: RouterState = {
|
|
95
|
+
path: u.pathname,
|
|
96
|
+
params,
|
|
97
|
+
query,
|
|
98
|
+
signal: abortController.signal,
|
|
99
|
+
}
|
|
100
|
+
const timeout = setTimeout(() => {
|
|
101
|
+
abortController.abort(
|
|
102
|
+
"[kiru/router]: Page data loading timed out after 10 seconds"
|
|
103
|
+
)
|
|
104
|
+
}, 10000)
|
|
105
|
+
|
|
106
|
+
try {
|
|
107
|
+
const data = await config.loader.load(routerState)
|
|
108
|
+
props = {
|
|
109
|
+
data,
|
|
110
|
+
error: null,
|
|
111
|
+
loading: false,
|
|
112
|
+
}
|
|
113
|
+
} catch (error) {
|
|
114
|
+
props = {
|
|
115
|
+
error: new FileRouterDataLoadError(error),
|
|
116
|
+
loading: false,
|
|
117
|
+
data: null,
|
|
118
|
+
}
|
|
119
|
+
} finally {
|
|
120
|
+
clearTimeout(timeout)
|
|
121
|
+
ctx.registerPreloadedPageProps({ data: props.data, error: props.error })
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const children = wrapWithLayouts(
|
|
127
|
+
layouts
|
|
128
|
+
.map((layout) => layout.default)
|
|
129
|
+
.filter((l) => typeof l === "function"),
|
|
130
|
+
page.default,
|
|
131
|
+
props
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
const app = createElement(RouterContext.Provider, {
|
|
135
|
+
children: createElement(ctx.Document, { children }),
|
|
136
|
+
value: {
|
|
137
|
+
state: {
|
|
138
|
+
params,
|
|
139
|
+
query,
|
|
140
|
+
path: u.pathname,
|
|
141
|
+
signal: abortController.signal, // Server-side signal (not abortable)
|
|
142
|
+
} as RouterState,
|
|
143
|
+
},
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
let { immediate, stream } = renderToReadableStream(app)
|
|
147
|
+
const hasHeadOutlet = immediate.includes("<kiru-head-outlet>")
|
|
148
|
+
const hasHeadContent = immediate.includes("<kiru-head-content>")
|
|
149
|
+
|
|
150
|
+
if (hasHeadOutlet && hasHeadContent) {
|
|
151
|
+
let [preHeadContent = "", headContentInner = "", postHeadContent = ""] =
|
|
152
|
+
immediate.split(/<kiru-head-content>|<\/kiru-head-content>/)
|
|
153
|
+
|
|
154
|
+
preHeadContent = preHeadContent.replace(
|
|
155
|
+
"<kiru-head-outlet>",
|
|
156
|
+
headContentInner
|
|
157
|
+
)
|
|
158
|
+
immediate = `${preHeadContent}${postHeadContent}`
|
|
159
|
+
} else if (hasHeadContent) {
|
|
160
|
+
// remove head content element and everything within it
|
|
161
|
+
immediate = immediate.replace(
|
|
162
|
+
/<kiru-head-content>(.*?)<\/kiru-head-content>/,
|
|
163
|
+
""
|
|
164
|
+
)
|
|
165
|
+
} else if (hasHeadOutlet) {
|
|
166
|
+
// remove head outlet element and everything within it
|
|
167
|
+
immediate = immediate.replaceAll("<kiru-head-outlet>", "")
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// console.log("immediate", immediate)
|
|
171
|
+
|
|
172
|
+
return {
|
|
173
|
+
status: is404Route ? 404 : result?.status ?? 200,
|
|
174
|
+
immediate: "<!doctype html>" + immediate,
|
|
175
|
+
stream,
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export async function generateStaticPaths(pages: FormattedViteImportMap) {
|
|
180
|
+
const results: Record<string, string> = {}
|
|
181
|
+
const entries = Object.values(pages)
|
|
182
|
+
for (const entry of entries) {
|
|
183
|
+
// Build a clean URL path excluding group segments like (articles)
|
|
184
|
+
const urlSegments = entry.segments.filter(
|
|
185
|
+
(s) => !(s.startsWith("(") && s.endsWith(")"))
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
const basePath = "/" + urlSegments.join("/")
|
|
189
|
+
// if (basePath.endsWith("/404")) continue
|
|
190
|
+
|
|
191
|
+
const hasDynamic = urlSegments.some((s) => s.startsWith(":"))
|
|
192
|
+
if (!hasDynamic) {
|
|
193
|
+
results[basePath === "" ? "/" : basePath] = entry.filePath
|
|
194
|
+
continue
|
|
195
|
+
}
|
|
196
|
+
try {
|
|
197
|
+
const mod: PageModule = await entry.load()
|
|
198
|
+
const gen = mod?.config?.generateStaticParams
|
|
199
|
+
if (!gen) continue
|
|
200
|
+
const paramsList = await gen()
|
|
201
|
+
if (!Array.isArray(paramsList)) continue
|
|
202
|
+
|
|
203
|
+
for (const params of paramsList) {
|
|
204
|
+
let p = basePath
|
|
205
|
+
for (const key in params) {
|
|
206
|
+
const value = params[key]
|
|
207
|
+
p = p.replace(`:${key}*`, value).replace(`:${key}`, value)
|
|
208
|
+
}
|
|
209
|
+
results[p] = entry.filePath
|
|
210
|
+
}
|
|
211
|
+
} catch {}
|
|
212
|
+
}
|
|
213
|
+
return results
|
|
214
|
+
}
|
|
@@ -5,8 +5,12 @@ export interface DefaultComponentModule {
|
|
|
5
5
|
}
|
|
6
6
|
|
|
7
7
|
export interface PageModule {
|
|
8
|
-
default:
|
|
8
|
+
default: Kiru.FC
|
|
9
9
|
config?: PageConfig
|
|
10
|
+
__KIRU_STATIC_PROPS__?: Record<
|
|
11
|
+
string,
|
|
12
|
+
{ data: unknown; error: string | null }
|
|
13
|
+
>
|
|
10
14
|
}
|
|
11
15
|
|
|
12
16
|
export interface ViteImportMap {
|
|
@@ -18,7 +22,7 @@ export interface FormattedViteImportMap {
|
|
|
18
22
|
load: () => Promise<DefaultComponentModule>
|
|
19
23
|
specificity: number
|
|
20
24
|
segments: string[]
|
|
21
|
-
filePath
|
|
25
|
+
filePath: string
|
|
22
26
|
}
|
|
23
27
|
}
|
|
24
28
|
|
package/src/router/types.ts
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
import { AsyncTaskState } from "../types.utils"
|
|
2
2
|
import { FileRouterDataLoadError } from "./errors"
|
|
3
|
+
import {
|
|
4
|
+
DefaultComponentModule,
|
|
5
|
+
FormattedViteImportMap,
|
|
6
|
+
PageModule,
|
|
7
|
+
} from "./types.internal"
|
|
8
|
+
|
|
9
|
+
export interface FileRouterPreloadConfig {
|
|
10
|
+
pages: FormattedViteImportMap
|
|
11
|
+
layouts: FormattedViteImportMap
|
|
12
|
+
page: PageModule
|
|
13
|
+
pageProps: Record<string, unknown>
|
|
14
|
+
pageLayouts: DefaultComponentModule[]
|
|
15
|
+
params: RouteParams
|
|
16
|
+
query: RouteQuery
|
|
17
|
+
route: string
|
|
18
|
+
}
|
|
3
19
|
|
|
4
20
|
export interface FileRouterConfig {
|
|
5
21
|
/**
|
|
@@ -35,6 +51,12 @@ export interface FileRouterConfig {
|
|
|
35
51
|
* @default false
|
|
36
52
|
*/
|
|
37
53
|
transition?: boolean
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Used for generated entry point files
|
|
57
|
+
* @internal
|
|
58
|
+
*/
|
|
59
|
+
preloaded?: FileRouterPreloadConfig
|
|
38
60
|
}
|
|
39
61
|
|
|
40
62
|
export interface RouteParams {
|
|
@@ -69,37 +91,38 @@ export interface RouterState {
|
|
|
69
91
|
|
|
70
92
|
type PageDataLoaderContext = RouterState & {}
|
|
71
93
|
|
|
72
|
-
export
|
|
94
|
+
export type PageDataLoaderConfig<T = unknown> = {
|
|
73
95
|
/**
|
|
74
96
|
* The function to load the page data
|
|
75
97
|
*/
|
|
76
98
|
load: (context: PageDataLoaderContext) => Promise<T>
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* The mode to use for the page data loader
|
|
102
|
+
* @default "client"
|
|
103
|
+
* @description
|
|
104
|
+
* - **static**: The page data is loaded at build time and never updated
|
|
105
|
+
* - **client**: The page data is loaded upon navigation and updated on subsequent navigations
|
|
106
|
+
*/
|
|
107
|
+
mode?: "static" | "client"
|
|
77
108
|
/**
|
|
78
|
-
* Enable transitions when swapping between "load", "error" and "data" states
|
|
109
|
+
* Enable transitions when swapping between "load", "error" and "data" states (only when mode is "client")
|
|
79
110
|
*/
|
|
80
111
|
transition?: boolean
|
|
81
112
|
}
|
|
82
113
|
|
|
83
|
-
export interface PageConfig {
|
|
114
|
+
export interface PageConfig<T = unknown> {
|
|
84
115
|
/**
|
|
85
116
|
* The loader configuration for this page
|
|
86
117
|
*/
|
|
87
|
-
loader?: PageDataLoaderConfig
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
118
|
+
loader?: PageDataLoaderConfig<T>
|
|
119
|
+
/**
|
|
120
|
+
* Generate static params for this page. For each params
|
|
121
|
+
* returned, a page will be generated
|
|
122
|
+
*/
|
|
123
|
+
generateStaticParams?: () => RouteParams[] | Promise<RouteParams[]>
|
|
91
124
|
}
|
|
92
125
|
|
|
93
|
-
export type PageProps<T extends PageConfig
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
Awaited<ReturnType<T["loader"]["load"]>>,
|
|
97
|
-
FileRouterDataLoadError
|
|
98
|
-
>
|
|
99
|
-
: {}
|
|
100
|
-
|
|
101
|
-
export interface ErrorPageProps {
|
|
102
|
-
source?: {
|
|
103
|
-
path: string
|
|
104
|
-
}
|
|
105
|
-
}
|
|
126
|
+
export type PageProps<T extends PageConfig<any>> = T extends PageConfig<infer U>
|
|
127
|
+
? AsyncTaskState<U, FileRouterDataLoadError>
|
|
128
|
+
: {}
|