kiru 0.52.4 → 0.54.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/appContext.d.ts.map +1 -1
- package/dist/appContext.js +14 -11
- package/dist/appContext.js.map +1 -1
- package/dist/components/derive.d.ts +20 -0
- package/dist/components/derive.d.ts.map +1 -0
- package/dist/components/derive.js +57 -0
- package/dist/components/derive.js.map +1 -0
- package/dist/components/errorBoundary.d.ts +1 -1
- package/dist/components/errorBoundary.d.ts.map +1 -1
- package/dist/components/index.d.ts +1 -1
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/index.js +1 -1
- package/dist/components/index.js.map +1 -1
- package/dist/components/memo.d.ts +6 -3
- package/dist/components/memo.d.ts.map +1 -1
- package/dist/components/memo.js.map +1 -1
- package/dist/constants.d.ts +3 -3
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +3 -3
- package/dist/constants.js.map +1 -1
- package/dist/dom.d.ts.map +1 -1
- package/dist/dom.js +69 -66
- package/dist/dom.js.map +1 -1
- package/dist/element.d.ts +2 -2
- package/dist/element.d.ts.map +1 -1
- package/dist/element.js +20 -32
- package/dist/element.js.map +1 -1
- package/dist/globalContext.d.ts +6 -1
- package/dist/globalContext.d.ts.map +1 -1
- package/dist/globalContext.js +14 -2
- package/dist/globalContext.js.map +1 -1
- package/dist/globals.d.ts +1 -1
- package/dist/globals.d.ts.map +1 -1
- package/dist/globals.js.map +1 -1
- package/dist/hooks/usePromise.d.ts +4 -9
- package/dist/hooks/usePromise.d.ts.map +1 -1
- package/dist/hooks/usePromise.js +25 -28
- package/dist/hooks/usePromise.js.map +1 -1
- package/dist/hooks/useRef.d.ts +2 -2
- package/dist/hooks/useRef.d.ts.map +1 -1
- package/dist/jsx.d.ts +1 -1
- package/dist/jsx.d.ts.map +1 -1
- package/dist/reconciler.d.ts +1 -1
- package/dist/reconciler.d.ts.map +1 -1
- package/dist/reconciler.js +57 -117
- package/dist/reconciler.js.map +1 -1
- package/dist/recursiveRender.d.ts +4 -3
- package/dist/recursiveRender.d.ts.map +1 -1
- package/dist/recursiveRender.js +20 -19
- package/dist/recursiveRender.js.map +1 -1
- package/dist/renderToString.js +2 -2
- package/dist/renderToString.js.map +1 -1
- package/dist/router/client/index.d.ts +4 -2
- package/dist/router/client/index.d.ts.map +1 -1
- package/dist/router/client/index.js +49 -11
- package/dist/router/client/index.js.map +1 -1
- package/dist/router/context.d.ts +2 -0
- package/dist/router/context.d.ts.map +1 -1
- package/dist/router/context.js +5 -1
- package/dist/router/context.js.map +1 -1
- package/dist/router/fileRouterController.d.ts +3 -1
- package/dist/router/fileRouterController.d.ts.map +1 -1
- package/dist/router/fileRouterController.js +80 -9
- 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/guard.d.ts +17 -0
- package/dist/router/guard.d.ts.map +1 -0
- package/dist/router/guard.js +45 -0
- package/dist/router/guard.js.map +1 -0
- package/dist/router/head.d.ts.map +1 -1
- package/dist/router/head.js +5 -7
- package/dist/router/head.js.map +1 -1
- package/dist/router/index.d.ts +3 -2
- package/dist/router/index.d.ts.map +1 -1
- package/dist/router/index.js +2 -1
- package/dist/router/index.js.map +1 -1
- package/dist/router/{server → ssg}/index.d.ts +4 -3
- package/dist/router/ssg/index.d.ts.map +1 -0
- package/dist/router/{server → ssg}/index.js +7 -4
- package/dist/router/ssg/index.js.map +1 -0
- package/dist/router/ssr/index.d.ts +20 -0
- package/dist/router/ssr/index.d.ts.map +1 -0
- package/dist/router/ssr/index.js +160 -0
- package/dist/router/ssr/index.js.map +1 -0
- package/dist/router/types.d.ts +37 -4
- package/dist/router/types.d.ts.map +1 -1
- package/dist/router/types.internal.d.ts +4 -0
- package/dist/router/types.internal.d.ts.map +1 -1
- package/dist/router/utils/index.d.ts +9 -4
- package/dist/router/utils/index.d.ts.map +1 -1
- package/dist/router/utils/index.js +38 -6
- package/dist/router/utils/index.js.map +1 -1
- package/dist/signals/base.d.ts +1 -3
- package/dist/signals/base.d.ts.map +1 -1
- package/dist/signals/base.js +3 -3
- package/dist/signals/base.js.map +1 -1
- package/dist/signals/computed.d.ts +4 -2
- package/dist/signals/computed.d.ts.map +1 -1
- package/dist/signals/computed.js +29 -6
- package/dist/signals/computed.js.map +1 -1
- package/dist/signals/for.d.ts +10 -0
- package/dist/signals/for.d.ts.map +1 -0
- package/dist/signals/for.js +7 -0
- package/dist/signals/for.js.map +1 -0
- package/dist/signals/index.d.ts +1 -1
- package/dist/signals/index.js +1 -1
- package/dist/signals/types.d.ts +1 -1
- package/dist/signals/types.d.ts.map +1 -1
- package/dist/ssr/client.d.ts +1 -1
- package/dist/ssr/client.d.ts.map +1 -1
- package/dist/ssr/server.d.ts +1 -2
- package/dist/ssr/server.d.ts.map +1 -1
- package/dist/ssr/server.js +26 -29
- package/dist/ssr/server.js.map +1 -1
- package/dist/types.d.ts +13 -11
- package/dist/types.d.ts.map +1 -1
- package/dist/types.dom.d.ts +33 -32
- package/dist/types.dom.d.ts.map +1 -1
- package/dist/types.utils.d.ts +5 -1
- package/dist/types.utils.d.ts.map +1 -1
- package/dist/utils/format.d.ts +4 -3
- package/dist/utils/format.d.ts.map +1 -1
- package/dist/utils/format.js +8 -4
- package/dist/utils/format.js.map +1 -1
- package/dist/utils/index.d.ts +0 -1
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +0 -1
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/promise.d.ts +16 -0
- package/dist/utils/promise.d.ts.map +1 -0
- package/dist/utils/promise.js +14 -0
- package/dist/utils/promise.js.map +1 -0
- package/dist/utils/runtime.d.ts +11 -2
- package/dist/utils/runtime.d.ts.map +1 -1
- package/dist/utils/runtime.js +26 -2
- package/dist/utils/runtime.js.map +1 -1
- package/dist/utils/vdom.d.ts +4 -4
- package/dist/utils/vdom.d.ts.map +1 -1
- package/dist/utils/vdom.js +18 -17
- package/dist/utils/vdom.js.map +1 -1
- package/dist/vNode.d.ts +4 -0
- package/dist/vNode.d.ts.map +1 -0
- package/dist/vNode.js +22 -0
- package/dist/vNode.js.map +1 -0
- package/package.json +8 -4
- package/src/appContext.ts +15 -13
- package/src/components/derive.ts +121 -0
- package/src/components/index.ts +1 -1
- package/src/components/memo.ts +3 -2
- package/src/constants.ts +4 -4
- package/src/dom.ts +71 -66
- package/src/element.ts +22 -35
- package/src/globalContext.ts +24 -3
- package/src/globals.ts +1 -1
- package/src/hooks/usePromise.ts +32 -41
- package/src/hooks/useRef.ts +2 -2
- package/src/reconciler.ts +87 -125
- package/src/recursiveRender.ts +25 -23
- package/src/renderToString.ts +3 -3
- package/src/router/client/index.ts +100 -14
- package/src/router/context.ts +7 -1
- package/src/router/fileRouterController.ts +137 -8
- package/src/router/globals.ts +4 -0
- package/src/router/guard.ts +72 -0
- package/src/router/head.ts +5 -7
- package/src/router/index.ts +12 -1
- package/src/router/{server → ssg}/index.ts +16 -9
- package/src/router/ssr/index.ts +247 -0
- package/src/router/types.internal.ts +5 -0
- package/src/router/types.ts +48 -4
- package/src/router/utils/index.ts +74 -8
- package/src/signals/base.ts +3 -3
- package/src/signals/computed.ts +43 -6
- package/src/signals/for.ts +25 -0
- package/src/signals/index.ts +1 -1
- package/src/signals/types.ts +5 -1
- package/src/ssr/client.ts +1 -1
- package/src/ssr/server.ts +31 -33
- package/src/types.dom.ts +40 -40
- package/src/types.ts +14 -11
- package/src/types.utils.ts +11 -1
- package/src/utils/format.ts +12 -4
- package/src/utils/index.ts +0 -2
- package/src/utils/promise.ts +26 -0
- package/src/utils/runtime.ts +30 -2
- package/src/utils/vdom.ts +20 -23
- package/src/vNode.ts +30 -0
- package/dist/components/suspense.d.ts +0 -24
- package/dist/components/suspense.d.ts.map +0 -1
- package/dist/components/suspense.js +0 -36
- package/dist/components/suspense.js.map +0 -1
- package/dist/router/server/index.d.ts.map +0 -1
- package/dist/router/server/index.js.map +0 -1
- package/dist/signals/jsx.d.ts +0 -17
- package/dist/signals/jsx.d.ts.map +0 -1
- package/dist/signals/jsx.js +0 -11
- package/dist/signals/jsx.js.map +0 -1
- package/src/components/suspense.ts +0 -72
- package/src/signals/jsx.ts +0 -46
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
import path from "path"
|
|
2
|
+
import { createElement, Fragment } from "../../element.js"
|
|
3
|
+
import { __DEV__ } from "../../env.js"
|
|
4
|
+
import { renderToString } from "../../renderToString.js"
|
|
5
|
+
import { renderToReadableStream } from "../../ssr/server.js"
|
|
6
|
+
import { toArray } from "../../utils/format.js"
|
|
7
|
+
import {
|
|
8
|
+
matchModules,
|
|
9
|
+
matchRoute,
|
|
10
|
+
match404Route,
|
|
11
|
+
parseQuery,
|
|
12
|
+
wrapWithLayouts,
|
|
13
|
+
runBeforeEachGuards,
|
|
14
|
+
runAfterEachGuards,
|
|
15
|
+
runBeforeEnterHooks,
|
|
16
|
+
} from "../utils/index.js"
|
|
17
|
+
import { RouterContext, RequestContext } from "../context.js"
|
|
18
|
+
import type { PageConfig, PageProps, RouterState } from "../types.js"
|
|
19
|
+
import type {
|
|
20
|
+
FormattedViteImportMap,
|
|
21
|
+
GuardModule,
|
|
22
|
+
PageModule,
|
|
23
|
+
} from "../types.internal.js"
|
|
24
|
+
|
|
25
|
+
export interface SSRRenderContext {
|
|
26
|
+
pages: FormattedViteImportMap<PageModule>
|
|
27
|
+
layouts: FormattedViteImportMap
|
|
28
|
+
guards: FormattedViteImportMap<GuardModule>
|
|
29
|
+
Document: Kiru.FC
|
|
30
|
+
userContext: Kiru.RequestContext
|
|
31
|
+
registerModule: (moduleId: string) => void
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface SSRHttpResponse {
|
|
35
|
+
html: string
|
|
36
|
+
statusCode: number
|
|
37
|
+
headers: Array<[string, string]>
|
|
38
|
+
stream: ReadableStream | null
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface SSRRenderResult {
|
|
42
|
+
httpResponse: SSRHttpResponse | null
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export async function render(
|
|
46
|
+
url: string,
|
|
47
|
+
ctx: SSRRenderContext
|
|
48
|
+
): Promise<SSRRenderResult> {
|
|
49
|
+
const extName = path.extname(url)
|
|
50
|
+
if (extName && extName.length > 0) {
|
|
51
|
+
return {
|
|
52
|
+
httpResponse: null,
|
|
53
|
+
}
|
|
54
|
+
} else if (url.startsWith("/@")) {
|
|
55
|
+
return {
|
|
56
|
+
httpResponse: null,
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
const u = new URL(url, "http://localhost")
|
|
60
|
+
const pathSegments = u.pathname.split("/").filter(Boolean)
|
|
61
|
+
let routeMatch = matchRoute(ctx.pages, pathSegments)
|
|
62
|
+
|
|
63
|
+
if (!routeMatch) {
|
|
64
|
+
// Try to find a 404 page in parent directories
|
|
65
|
+
const fourOhFourMatch = match404Route(ctx.pages, pathSegments)
|
|
66
|
+
if (fourOhFourMatch) {
|
|
67
|
+
routeMatch = fourOhFourMatch
|
|
68
|
+
} else {
|
|
69
|
+
// Fallback to root 404 or default fallback
|
|
70
|
+
if (url === "/404") {
|
|
71
|
+
if (__DEV__) {
|
|
72
|
+
console.warn(
|
|
73
|
+
"[kiru/router]: No 404 route defined. Using fallback 404 page."
|
|
74
|
+
)
|
|
75
|
+
}
|
|
76
|
+
return {
|
|
77
|
+
httpResponse: {
|
|
78
|
+
statusCode: 404,
|
|
79
|
+
headers: [["Content-Type", "text/html"]],
|
|
80
|
+
html: "<!doctype html><html><head><title>Not Found</title></head><body><h1>404</h1></body></html>",
|
|
81
|
+
stream: null,
|
|
82
|
+
},
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
// Recursively render the 404 page
|
|
86
|
+
const notFoundResponse = await render("/404", ctx)
|
|
87
|
+
return {
|
|
88
|
+
httpResponse: {
|
|
89
|
+
html: notFoundResponse.httpResponse?.html ?? "",
|
|
90
|
+
headers: notFoundResponse.httpResponse?.headers ?? [
|
|
91
|
+
["Content-Type", "text/html"],
|
|
92
|
+
],
|
|
93
|
+
...notFoundResponse,
|
|
94
|
+
statusCode: 404,
|
|
95
|
+
stream: null,
|
|
96
|
+
},
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
const { pageEntry, routeSegments, params } = routeMatch
|
|
101
|
+
const is404Route = routeMatch.routeSegments.includes("404")
|
|
102
|
+
|
|
103
|
+
const guardEntries = matchModules(ctx.guards, routeSegments)
|
|
104
|
+
const guardModules = await Promise.all(
|
|
105
|
+
guardEntries.map((entry) => entry.load() as unknown as Promise<GuardModule>)
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
const redirectPath = await runBeforeEachGuards(
|
|
109
|
+
guardModules,
|
|
110
|
+
{ ...ctx.userContext },
|
|
111
|
+
u.pathname
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
if (redirectPath !== null) {
|
|
115
|
+
return createRedirectResult(redirectPath)
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const layoutEntries = matchModules(ctx.layouts, routeSegments)
|
|
119
|
+
|
|
120
|
+
// Register all modules for CSS collection
|
|
121
|
+
;[pageEntry, ...layoutEntries].forEach((e) => {
|
|
122
|
+
ctx.registerModule(e.filePath)
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
const [page, ...layouts] = await Promise.all([
|
|
126
|
+
pageEntry.load(),
|
|
127
|
+
...layoutEntries.map((layoutEntry) => layoutEntry.load()),
|
|
128
|
+
])
|
|
129
|
+
|
|
130
|
+
const onBeforeEnter = page.config?.hooks?.onBeforeEnter
|
|
131
|
+
if (onBeforeEnter) {
|
|
132
|
+
const redirectPath = await runBeforeEnterHooks(
|
|
133
|
+
toArray(onBeforeEnter),
|
|
134
|
+
{ ...ctx.userContext },
|
|
135
|
+
u.pathname
|
|
136
|
+
)
|
|
137
|
+
if (redirectPath) {
|
|
138
|
+
return createRedirectResult(redirectPath)
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const query = parseQuery(u.search)
|
|
143
|
+
|
|
144
|
+
let props = {} as PageProps<PageConfig>
|
|
145
|
+
const config = page.config ?? {}
|
|
146
|
+
const abortController = new AbortController()
|
|
147
|
+
|
|
148
|
+
// PageConfig loaders don't run on the server
|
|
149
|
+
if (config.loader) {
|
|
150
|
+
props = {
|
|
151
|
+
data: null,
|
|
152
|
+
error: null,
|
|
153
|
+
loading: true,
|
|
154
|
+
}
|
|
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
|
+
)
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const children = wrapWithLayouts(
|
|
169
|
+
layouts
|
|
170
|
+
.map((layout) => layout.default)
|
|
171
|
+
.filter((l) => typeof l === "function"),
|
|
172
|
+
page.default,
|
|
173
|
+
props
|
|
174
|
+
)
|
|
175
|
+
|
|
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
|
+
const app = createElement(RouterContext.Provider, {
|
|
187
|
+
children: createElement(RequestContext.Provider, {
|
|
188
|
+
children: Fragment({ children }),
|
|
189
|
+
value: ctx.userContext,
|
|
190
|
+
}),
|
|
191
|
+
value: routerContextValue,
|
|
192
|
+
})
|
|
193
|
+
|
|
194
|
+
let { immediate: pageOutletContent, stream } = renderToReadableStream(app)
|
|
195
|
+
const hasHeadContent = pageOutletContent.includes("<kiru-head-content>")
|
|
196
|
+
const hasHeadOutlet = documentShell.includes("<kiru-head-outlet>")
|
|
197
|
+
|
|
198
|
+
if (hasHeadOutlet && hasHeadContent) {
|
|
199
|
+
let [preHeadContent = "", headContentInner = "", postHeadContent = ""] =
|
|
200
|
+
pageOutletContent.split(/<kiru-head-content>|<\/kiru-head-content>/)
|
|
201
|
+
|
|
202
|
+
documentShell = documentShell.replace(
|
|
203
|
+
"<kiru-head-outlet>",
|
|
204
|
+
headContentInner
|
|
205
|
+
)
|
|
206
|
+
pageOutletContent = `${preHeadContent}${postHeadContent}`
|
|
207
|
+
} else if (hasHeadContent) {
|
|
208
|
+
// remove head content element and everything within it
|
|
209
|
+
pageOutletContent = pageOutletContent.replace(
|
|
210
|
+
/<kiru-head-content>(.*?)<\/kiru-head-content>/,
|
|
211
|
+
""
|
|
212
|
+
)
|
|
213
|
+
} else if (hasHeadOutlet) {
|
|
214
|
+
// remove head outlet element and everything within it
|
|
215
|
+
documentShell = documentShell.replaceAll("<kiru-head-outlet>", "")
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
const [prePageOutlet, postPageOutlet] =
|
|
219
|
+
documentShell.split("<kiru-body-outlet>")
|
|
220
|
+
|
|
221
|
+
const html = `<!DOCTYPE html>${prePageOutlet}<body>${pageOutletContent}</body>${postPageOutlet}`
|
|
222
|
+
const statusCode = is404Route ? 404 : 200
|
|
223
|
+
|
|
224
|
+
queueMicrotask(() => {
|
|
225
|
+
runAfterEachGuards(guardModules, { ...ctx.userContext }, u.pathname)
|
|
226
|
+
})
|
|
227
|
+
|
|
228
|
+
return {
|
|
229
|
+
httpResponse: {
|
|
230
|
+
html,
|
|
231
|
+
statusCode,
|
|
232
|
+
headers: [["Content-Type", "text/html;charset=utf-8"]],
|
|
233
|
+
stream,
|
|
234
|
+
},
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
function createRedirectResult(to: string): SSRRenderResult {
|
|
239
|
+
return {
|
|
240
|
+
httpResponse: {
|
|
241
|
+
statusCode: 302,
|
|
242
|
+
headers: [["Location", to]],
|
|
243
|
+
html: "",
|
|
244
|
+
stream: null,
|
|
245
|
+
},
|
|
246
|
+
}
|
|
247
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { FileRouterContextType } from "./context"
|
|
2
2
|
import type { PageConfig } from "./types"
|
|
3
|
+
import type { NavGuardBuilder } from "./guard"
|
|
3
4
|
|
|
4
5
|
export interface CurrentPage {
|
|
5
6
|
component: Kiru.FC<any>
|
|
@@ -20,6 +21,10 @@ export interface PageModule {
|
|
|
20
21
|
>
|
|
21
22
|
}
|
|
22
23
|
|
|
24
|
+
export interface GuardModule {
|
|
25
|
+
guard: NavGuardBuilder
|
|
26
|
+
}
|
|
27
|
+
|
|
23
28
|
export interface ViteImportMap {
|
|
24
29
|
[fp: string]: () => Promise<DefaultComponentModule>
|
|
25
30
|
}
|
package/src/router/types.ts
CHANGED
|
@@ -3,12 +3,14 @@ import type { FileRouterDataLoadError } from "./errors"
|
|
|
3
3
|
import type {
|
|
4
4
|
DefaultComponentModule,
|
|
5
5
|
FormattedViteImportMap,
|
|
6
|
+
GuardModule,
|
|
6
7
|
PageModule,
|
|
7
8
|
} from "./types.internal"
|
|
8
9
|
|
|
9
10
|
export interface FileRouterPreloadConfig {
|
|
10
|
-
pages: FormattedViteImportMap
|
|
11
|
+
pages: FormattedViteImportMap<PageModule>
|
|
11
12
|
layouts: FormattedViteImportMap
|
|
13
|
+
guards?: FormattedViteImportMap<GuardModule>
|
|
12
14
|
page: PageModule
|
|
13
15
|
pageProps: Record<string, unknown>
|
|
14
16
|
pageLayouts: DefaultComponentModule[]
|
|
@@ -40,6 +42,14 @@ export interface FileRouterConfig {
|
|
|
40
42
|
* ```
|
|
41
43
|
*/
|
|
42
44
|
layouts: Record<string, unknown>
|
|
45
|
+
/**
|
|
46
|
+
* The import map to use for loading nav guards
|
|
47
|
+
* @example
|
|
48
|
+
* ```tsx
|
|
49
|
+
* <FileRouter config={{ guards: import.meta.glob("/∗∗/guard.{ts,js}"), ... }} />
|
|
50
|
+
* ```
|
|
51
|
+
*/
|
|
52
|
+
guards?: Record<string, unknown>
|
|
43
53
|
|
|
44
54
|
/**
|
|
45
55
|
* The base url to use as a prefix for route matching
|
|
@@ -98,18 +108,34 @@ export interface RouterState {
|
|
|
98
108
|
signal: AbortSignal
|
|
99
109
|
}
|
|
100
110
|
|
|
101
|
-
type PageDataLoaderContext = RouterState & {}
|
|
102
|
-
|
|
103
111
|
export interface PageDataLoaderCacheConfig {
|
|
104
112
|
type: "memory" | "localStorage" | "sessionStorage"
|
|
105
113
|
ttl: number
|
|
106
114
|
}
|
|
107
115
|
|
|
116
|
+
interface LoaderContext extends RouterState {
|
|
117
|
+
/**
|
|
118
|
+
* The request context - in SSR, this is the data from the server
|
|
119
|
+
* that's passed to the `renderPage` function
|
|
120
|
+
* @example
|
|
121
|
+
* ```ts
|
|
122
|
+
* // server.ts
|
|
123
|
+
* renderPage({ url, context: { test: 123 } })
|
|
124
|
+
*
|
|
125
|
+
* // page.tsx
|
|
126
|
+
* loader: {
|
|
127
|
+
* load: ({ context }) => context.test
|
|
128
|
+
* }
|
|
129
|
+
* ```
|
|
130
|
+
*/
|
|
131
|
+
context: Kiru.RequestContext
|
|
132
|
+
}
|
|
133
|
+
|
|
108
134
|
export type PageDataLoaderConfig<T = unknown> = {
|
|
109
135
|
/**
|
|
110
136
|
* The function to load the page data
|
|
111
137
|
*/
|
|
112
|
-
load: (context:
|
|
138
|
+
load: (context: LoaderContext) => Promise<T>
|
|
113
139
|
} & (
|
|
114
140
|
| {
|
|
115
141
|
/**
|
|
@@ -149,6 +175,22 @@ export type PageDataLoaderConfig<T = unknown> = {
|
|
|
149
175
|
}
|
|
150
176
|
)
|
|
151
177
|
|
|
178
|
+
export type NavigationHook<T> = (
|
|
179
|
+
context: Kiru.RequestContext,
|
|
180
|
+
to: string,
|
|
181
|
+
from: string
|
|
182
|
+
) => T
|
|
183
|
+
|
|
184
|
+
export type OnBeforeEnterHook = NavigationHook<
|
|
185
|
+
string | void | Promise<string | void>
|
|
186
|
+
>
|
|
187
|
+
export type OnBeforeLeaveHook = NavigationHook<false | void>
|
|
188
|
+
|
|
189
|
+
interface PageContextHooks {
|
|
190
|
+
onBeforeEnter?: OnBeforeEnterHook | OnBeforeEnterHook[]
|
|
191
|
+
onBeforeLeave?: OnBeforeLeaveHook | OnBeforeLeaveHook[]
|
|
192
|
+
}
|
|
193
|
+
|
|
152
194
|
export interface PageConfig<T = unknown> {
|
|
153
195
|
/**
|
|
154
196
|
* The loader configuration for this page
|
|
@@ -159,6 +201,8 @@ export interface PageConfig<T = unknown> {
|
|
|
159
201
|
* returned, a page will be generated
|
|
160
202
|
*/
|
|
161
203
|
generateStaticParams?: () => RouteParams[] | Promise<RouteParams[]>
|
|
204
|
+
|
|
205
|
+
hooks?: PageContextHooks
|
|
162
206
|
}
|
|
163
207
|
|
|
164
208
|
export type PageProps<T extends PageConfig<any>> = T extends PageConfig<infer U>
|
|
@@ -1,19 +1,27 @@
|
|
|
1
1
|
import { createElement } from "../../element.js"
|
|
2
2
|
import { __DEV__ } from "../../env.js"
|
|
3
|
+
import { resolveNavguard } from "../guard.js"
|
|
3
4
|
import type {
|
|
5
|
+
DefaultComponentModule,
|
|
4
6
|
FormattedViteImportMap,
|
|
7
|
+
GuardModule,
|
|
5
8
|
RouteMatch,
|
|
6
9
|
ViteImportMap,
|
|
7
10
|
} from "../types.internal"
|
|
11
|
+
import { OnBeforeEnterHook, OnBeforeLeaveHook } from "../types.js"
|
|
8
12
|
|
|
9
13
|
export {
|
|
10
14
|
formatViteImportMap,
|
|
11
15
|
matchRoute,
|
|
12
16
|
match404Route,
|
|
13
|
-
|
|
17
|
+
matchModules,
|
|
14
18
|
normalizePrefixPath,
|
|
15
19
|
parseQuery,
|
|
16
20
|
wrapWithLayouts,
|
|
21
|
+
runBeforeLeaveHooks,
|
|
22
|
+
runBeforeEnterHooks,
|
|
23
|
+
runBeforeEachGuards,
|
|
24
|
+
runAfterEachGuards,
|
|
17
25
|
}
|
|
18
26
|
|
|
19
27
|
function formatViteImportMap(
|
|
@@ -177,20 +185,20 @@ function match404Route(
|
|
|
177
185
|
return null
|
|
178
186
|
}
|
|
179
187
|
|
|
180
|
-
function
|
|
181
|
-
|
|
188
|
+
function matchModules<T = DefaultComponentModule>(
|
|
189
|
+
modules: FormattedViteImportMap<T>,
|
|
182
190
|
routeSegments: string[]
|
|
183
191
|
) {
|
|
184
192
|
return ["/", ...routeSegments].reduce((acc, _, i) => {
|
|
185
|
-
const
|
|
186
|
-
const
|
|
193
|
+
const modulePath = "/" + routeSegments.slice(0, i).join("/")
|
|
194
|
+
const module = modules[modulePath]
|
|
187
195
|
|
|
188
|
-
if (!
|
|
196
|
+
if (!module) {
|
|
189
197
|
return acc
|
|
190
198
|
}
|
|
191
199
|
|
|
192
|
-
return [...acc,
|
|
193
|
-
}, [] as FormattedViteImportMap[string][])
|
|
200
|
+
return [...acc, module]
|
|
201
|
+
}, [] as FormattedViteImportMap<T>[string][])
|
|
194
202
|
}
|
|
195
203
|
|
|
196
204
|
function normalizePrefixPath(path: string) {
|
|
@@ -239,3 +247,61 @@ function wrapWithLayouts(
|
|
|
239
247
|
createElement(page, props)
|
|
240
248
|
)
|
|
241
249
|
}
|
|
250
|
+
|
|
251
|
+
function runBeforeLeaveHooks(
|
|
252
|
+
hooks: OnBeforeLeaveHook[],
|
|
253
|
+
context: Kiru.RequestContext,
|
|
254
|
+
to: string,
|
|
255
|
+
from: string = to
|
|
256
|
+
): false | void {
|
|
257
|
+
for (const hook of hooks) {
|
|
258
|
+
const res = hook(context, to, from)
|
|
259
|
+
if (res === false) {
|
|
260
|
+
return false
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
async function runBeforeEnterHooks(
|
|
266
|
+
hooks: OnBeforeEnterHook[],
|
|
267
|
+
context: Kiru.RequestContext,
|
|
268
|
+
to: string,
|
|
269
|
+
from: string = to
|
|
270
|
+
) {
|
|
271
|
+
for (const hook of hooks) {
|
|
272
|
+
const result = await hook(context, to, from)
|
|
273
|
+
if (typeof result === "string") {
|
|
274
|
+
return result
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
return null
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
async function runBeforeEachGuards(
|
|
282
|
+
guardModules: GuardModule[],
|
|
283
|
+
context: Kiru.RequestContext,
|
|
284
|
+
to: string,
|
|
285
|
+
from: string = to
|
|
286
|
+
): Promise<string | null> {
|
|
287
|
+
const beforeHooks = guardModules
|
|
288
|
+
.map((guardModule) => resolveNavguard(guardModule)?.beforeEach)
|
|
289
|
+
.filter((x) => typeof x === "function")
|
|
290
|
+
|
|
291
|
+
return runBeforeEnterHooks(beforeHooks, context, to, from)
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
async function runAfterEachGuards(
|
|
295
|
+
guardModules: GuardModule[],
|
|
296
|
+
context: Kiru.RequestContext,
|
|
297
|
+
to: string,
|
|
298
|
+
from: string = to
|
|
299
|
+
): Promise<void> {
|
|
300
|
+
const afterHooks = guardModules
|
|
301
|
+
.map((guardModule) => resolveNavguard(guardModule)?.afterEach)
|
|
302
|
+
.filter((x) => typeof x === "function")
|
|
303
|
+
|
|
304
|
+
for (const hook of afterHooks) {
|
|
305
|
+
await hook(context, to, from)
|
|
306
|
+
}
|
|
307
|
+
}
|
package/src/signals/base.ts
CHANGED
|
@@ -125,16 +125,16 @@ export class Signal<T> {
|
|
|
125
125
|
return () => this.$subs!.delete(cb)
|
|
126
126
|
}
|
|
127
127
|
|
|
128
|
-
notify(
|
|
128
|
+
notify(filter?: (sub: SignalSubscriber) => boolean) {
|
|
129
129
|
if (__DEV__) {
|
|
130
130
|
return signalSubsMap.get(this.$id)?.forEach((sub) => {
|
|
131
|
-
if (
|
|
131
|
+
if (filter && !filter(sub)) return
|
|
132
132
|
const { $value, $prevValue } = latest(this)
|
|
133
133
|
return sub($value, $prevValue)
|
|
134
134
|
})
|
|
135
135
|
}
|
|
136
136
|
this.$subs!.forEach((sub) => {
|
|
137
|
-
if (
|
|
137
|
+
if (filter && !filter(sub)) return
|
|
138
138
|
return sub(this.$value, this.$prevValue)
|
|
139
139
|
})
|
|
140
140
|
}
|
package/src/signals/computed.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { __DEV__ } from "../env.js"
|
|
2
2
|
import { $HMR_ACCEPT } from "../constants.js"
|
|
3
|
-
import { useHook } from "../hooks/utils.js"
|
|
3
|
+
import { depsRequireChange, useHook } from "../hooks/utils.js"
|
|
4
4
|
import { latest } from "../utils/index.js"
|
|
5
5
|
import { effectQueue, signalSubsMap } from "./globals.js"
|
|
6
6
|
import { executeWithTracking } from "./effect.js"
|
|
@@ -69,6 +69,16 @@ export class ComputedSignal<T> extends Signal<T> {
|
|
|
69
69
|
Signal.dispose(signal)
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
+
static updateGetter<T>(signal: ComputedSignal<T>, getter: (prev?: T) => T) {
|
|
73
|
+
const $computed = latest(signal)
|
|
74
|
+
$computed.$getter = getter
|
|
75
|
+
$computed.$isDirty = true
|
|
76
|
+
|
|
77
|
+
ComputedSignal.run($computed)
|
|
78
|
+
if (Object.is($computed.$value, $computed.$prevValue)) return
|
|
79
|
+
$computed.notify()
|
|
80
|
+
}
|
|
81
|
+
|
|
72
82
|
private static stop<T>(computed: ComputedSignal<T>) {
|
|
73
83
|
const { $id, $unsubs } = latest(computed)
|
|
74
84
|
|
|
@@ -103,20 +113,35 @@ export class ComputedSignal<T> extends Signal<T> {
|
|
|
103
113
|
}
|
|
104
114
|
}
|
|
105
115
|
|
|
106
|
-
export
|
|
116
|
+
export function computed<T>(
|
|
107
117
|
getter: (prev?: T) => T,
|
|
108
118
|
displayName?: string
|
|
109
|
-
): ComputedSignal<T>
|
|
119
|
+
): ComputedSignal<T> {
|
|
110
120
|
return new ComputedSignal(getter, displayName)
|
|
111
121
|
}
|
|
112
122
|
|
|
113
|
-
export
|
|
123
|
+
export function useComputed<T>(
|
|
124
|
+
getter: (prev?: T) => T,
|
|
125
|
+
displayName?: string
|
|
126
|
+
): ComputedSignal<T>
|
|
127
|
+
|
|
128
|
+
export function useComputed<T>(
|
|
114
129
|
getter: (prev?: T) => T,
|
|
130
|
+
deps?: unknown[],
|
|
115
131
|
displayName?: string
|
|
116
|
-
)
|
|
132
|
+
): ComputedSignal<T>
|
|
133
|
+
|
|
134
|
+
export function useComputed<T>(
|
|
135
|
+
getter: (prev?: T) => T,
|
|
136
|
+
depsOrDisplayName?: string | unknown[],
|
|
137
|
+
displayName?: string
|
|
138
|
+
) {
|
|
117
139
|
return useHook(
|
|
118
140
|
"useComputedSignal",
|
|
119
|
-
{
|
|
141
|
+
{
|
|
142
|
+
signal: null! as ComputedSignal<T>,
|
|
143
|
+
deps: void 0 as unknown[] | undefined,
|
|
144
|
+
},
|
|
120
145
|
({ hook, isInit, isHMR }) => {
|
|
121
146
|
if (__DEV__) {
|
|
122
147
|
hook.dev = {
|
|
@@ -134,8 +159,20 @@ export const useComputed = <T>(
|
|
|
134
159
|
}
|
|
135
160
|
}
|
|
136
161
|
if (isInit) {
|
|
162
|
+
if (typeof depsOrDisplayName === "string") {
|
|
163
|
+
displayName = depsOrDisplayName
|
|
164
|
+
depsOrDisplayName = void 0
|
|
165
|
+
}
|
|
166
|
+
hook.deps = depsOrDisplayName
|
|
137
167
|
hook.cleanup = () => ComputedSignal.dispose(hook.signal)
|
|
138
168
|
hook.signal = computed(getter, displayName)
|
|
169
|
+
} else if (
|
|
170
|
+
hook.deps &&
|
|
171
|
+
typeof depsOrDisplayName !== "string" &&
|
|
172
|
+
depsRequireChange(hook.deps, depsOrDisplayName)
|
|
173
|
+
) {
|
|
174
|
+
hook.deps = depsOrDisplayName
|
|
175
|
+
ComputedSignal.updateGetter(hook.signal, getter)
|
|
139
176
|
}
|
|
140
177
|
|
|
141
178
|
return hook.signal
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { Signal } from "./base.js"
|
|
2
|
+
|
|
3
|
+
type InferArraySignalItemType<T extends Signal<any[]>> = T extends Signal<
|
|
4
|
+
infer V
|
|
5
|
+
>
|
|
6
|
+
? V extends Array<infer W>
|
|
7
|
+
? W
|
|
8
|
+
: never
|
|
9
|
+
: never
|
|
10
|
+
|
|
11
|
+
type ForProps<T extends Signal<any[]>, U = InferArraySignalItemType<T>> = {
|
|
12
|
+
each: T
|
|
13
|
+
fallback?: JSX.Element
|
|
14
|
+
children: (value: U, index: number, array: U[]) => JSX.Element
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function For<T extends Signal<any[]>>({
|
|
18
|
+
each,
|
|
19
|
+
fallback,
|
|
20
|
+
children,
|
|
21
|
+
}: ForProps<T>) {
|
|
22
|
+
const items = each.value
|
|
23
|
+
if (items.length === 0) return fallback
|
|
24
|
+
return items.map(children)
|
|
25
|
+
}
|
package/src/signals/index.ts
CHANGED
|
@@ -8,5 +8,5 @@ export { Signal, signal, useSignal } from "./base.js"
|
|
|
8
8
|
export { ComputedSignal, computed, useComputed } from "./computed.js"
|
|
9
9
|
export { WatchEffect, watch, useWatch } from "./watch.js"
|
|
10
10
|
export { unwrap, tick } from "./utils.js"
|
|
11
|
-
export * from "./
|
|
11
|
+
export * from "./for.js"
|
|
12
12
|
export * from "./types.js"
|
package/src/signals/types.ts
CHANGED
|
@@ -6,5 +6,9 @@ export type ReadonlySignal<T> = Signal<T> & {
|
|
|
6
6
|
export type SignalSubscriber<T = unknown> = (value: T, prevValue?: T) => void
|
|
7
7
|
|
|
8
8
|
export type SignalValues<T extends readonly Signal<unknown>[]> = {
|
|
9
|
-
[I in keyof T]: T[I] extends Signal<infer V>
|
|
9
|
+
[I in keyof T]: T[I] extends Signal<infer V>
|
|
10
|
+
? V extends Kiru.StatefulPromise<infer P>
|
|
11
|
+
? P
|
|
12
|
+
: V
|
|
13
|
+
: never
|
|
10
14
|
}
|
package/src/ssr/client.ts
CHANGED