akanjs 2.1.1 → 2.1.2-rc.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/base/baseEnv.ts +2 -1
- package/client/csrTypes.ts +22 -0
- package/client/translator.ts +25 -11
- package/package.json +1 -1
- package/server/akanApp.ts +1 -1
- package/server/artifact/routeSeedIndexStore.ts +34 -0
- package/server/routeElementComposer.tsx +101 -1
- package/server/routeTreeBuilder.ts +82 -1
- package/server/rscClient.tsx +9 -1
- package/server/rscWorker.tsx +308 -66
- package/server/rscWorkerHost.ts +10 -5
- package/server/systemPages.tsx +165 -0
- package/server/webRouter.ts +116 -18
- package/service/predefinedAdaptor/database.adaptor.ts +2 -0
- package/service/predefinedAdaptor/solidSqlite.ts +1 -0
- package/service/predefinedAdaptor/sqlitePath.ts +4 -1
- package/service/predefinedAdaptor/storage.adaptor.ts +2 -2
- package/types/client/csrTypes.d.ts +21 -0
- package/types/client/translator.d.ts +1 -0
- package/types/server/artifact/routeSeedIndexStore.d.ts +1 -0
- package/types/server/routeElementComposer.d.ts +15 -1
- package/types/server/routeTreeBuilder.d.ts +6 -1
- package/types/server/rscWorkerHost.d.ts +1 -0
- package/types/server/systemPages.d.ts +27 -0
- package/types/service/predefinedAdaptor/sqlitePath.d.ts +2 -1
- package/types/ui/System/Client.d.ts +5 -9
- package/types/ui/System/Common.d.ts +2 -0
- package/types/ui/System/SSR.d.ts +2 -2
- package/ui/InfiniteScroll.tsx +0 -1
- package/ui/System/Client.tsx +78 -20
- package/ui/System/Common.tsx +11 -2
- package/ui/System/SSR.tsx +58 -11
- package/webkit/bootCsr.tsx +13 -2
|
@@ -28,6 +28,8 @@ export interface ProviderProps {
|
|
|
28
28
|
layoutStyle?: "mobile" | "web";
|
|
29
29
|
/** Enable reconnect helper. Defaults to local operation mode in CSR. */
|
|
30
30
|
reconnect?: boolean;
|
|
31
|
+
/** Active-locale dictionary injected by the server (SSR only) to seed the client Translator. */
|
|
32
|
+
dictionary?: Record<string, Record<string, unknown>>;
|
|
31
33
|
/** Root route component used by CSR page loading. */
|
|
32
34
|
of: (props: unknown) => ReactNode | null;
|
|
33
35
|
}
|
package/types/ui/System/SSR.d.ts
CHANGED
|
@@ -3,13 +3,13 @@ import { type ReactNode } from "react";
|
|
|
3
3
|
import { type ProviderProps } from "./Common.d.ts";
|
|
4
4
|
export declare const SSR: {
|
|
5
5
|
(): import("react/jsx-runtime").JSX.Element;
|
|
6
|
-
Provider: ({ className, appName, params, head, manifest, env, gaTrackingId, children, theme, prefix, fonts, layoutStyle, reconnect, of, }: SSRProviderProps) => import("react/jsx-runtime").JSX.Element;
|
|
6
|
+
Provider: ({ className, appName, params, head, manifest, env, gaTrackingId, children, theme, prefix, fonts, layoutStyle, reconnect, dictionary, of, }: SSRProviderProps) => import("react/jsx-runtime").JSX.Element;
|
|
7
7
|
Wrapper: ({ children, head, manifest, fonts, className, prefix, layoutStyle, }: SSRWrapperProps) => import("react/jsx-runtime").JSX.Element;
|
|
8
8
|
};
|
|
9
9
|
export type SSRProviderProps = ProviderProps & {
|
|
10
10
|
fonts?: ReactFont[];
|
|
11
11
|
};
|
|
12
|
-
declare const SSRProvider: ({ className, appName, params, head, manifest, env, gaTrackingId, children, theme, prefix, fonts, layoutStyle, reconnect, of, }: SSRProviderProps) => import("react/jsx-runtime").JSX.Element;
|
|
12
|
+
declare const SSRProvider: ({ className, appName, params, head, manifest, env, gaTrackingId, children, theme, prefix, fonts, layoutStyle, reconnect, dictionary, of, }: SSRProviderProps) => import("react/jsx-runtime").JSX.Element;
|
|
13
13
|
interface SSRWrapperProps {
|
|
14
14
|
className?: string;
|
|
15
15
|
appName: string;
|
package/ui/InfiniteScroll.tsx
CHANGED
package/ui/System/Client.tsx
CHANGED
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
router,
|
|
15
15
|
setCookie,
|
|
16
16
|
type TransitionStyle,
|
|
17
|
+
Translator,
|
|
17
18
|
} from "akanjs/client";
|
|
18
19
|
import { Logger } from "akanjs/common";
|
|
19
20
|
import type { AkanTheme } from "akanjs/fetch";
|
|
@@ -41,7 +42,7 @@ interface ClientWrapperProps {
|
|
|
41
42
|
children: ReactNode;
|
|
42
43
|
theme?: AkanTheme;
|
|
43
44
|
lang?: string;
|
|
44
|
-
dictionary?:
|
|
45
|
+
dictionary?: Record<string, Record<string, unknown>>;
|
|
45
46
|
signals?: SerializedSignal[];
|
|
46
47
|
reconnect?: boolean;
|
|
47
48
|
}
|
|
@@ -49,10 +50,12 @@ export const ClientWrapper = ({
|
|
|
49
50
|
children,
|
|
50
51
|
theme,
|
|
51
52
|
lang = "en",
|
|
52
|
-
dictionary
|
|
53
|
+
dictionary,
|
|
53
54
|
signals = [],
|
|
54
55
|
reconnect = true,
|
|
55
56
|
}: ClientWrapperProps) => {
|
|
57
|
+
|
|
58
|
+
if (dictionary) Translator.seed(lang, dictionary);
|
|
56
59
|
if (getEnv().renderMode === "ssr") {
|
|
57
60
|
}
|
|
58
61
|
useLayoutEffect(() => {
|
|
@@ -68,7 +71,8 @@ export const ClientWrapper = ({
|
|
|
68
71
|
};
|
|
69
72
|
Client.Wrapper = ClientWrapper;
|
|
70
73
|
|
|
71
|
-
interface ClientPathWrapperProps
|
|
74
|
+
interface ClientPathWrapperProps
|
|
75
|
+
extends Omit<HTMLAttributes<HTMLDivElement>, "style"> {
|
|
72
76
|
bind?: () => HTMLAttributes<HTMLDivElement>;
|
|
73
77
|
wrapperRef?: RefObject<HTMLDivElement | null> | null;
|
|
74
78
|
pageType?: "current" | "prev" | "cached";
|
|
@@ -89,12 +93,18 @@ export const ClientPathWrapper = ({
|
|
|
89
93
|
layoutStyle = "web",
|
|
90
94
|
...props
|
|
91
95
|
}: ClientPathWrapperProps) => {
|
|
92
|
-
const href =
|
|
93
|
-
|
|
96
|
+
const href =
|
|
97
|
+
location?.href ??
|
|
98
|
+
(typeof window !== "undefined" ? window.location.href : "");
|
|
99
|
+
const hash =
|
|
100
|
+
location?.hash ??
|
|
101
|
+
(typeof window !== "undefined" ? window.location.hash : "");
|
|
94
102
|
const pathname = location?.pathname ?? "/";
|
|
95
103
|
const params = location?.params ?? {};
|
|
96
104
|
const searchParams = location?.searchParams ?? {};
|
|
97
|
-
const search =
|
|
105
|
+
const search =
|
|
106
|
+
location?.search ??
|
|
107
|
+
(typeof window !== "undefined" ? window.location.search : "");
|
|
98
108
|
const lang = params.lang;
|
|
99
109
|
const firstPath = pathname.split("/")[2];
|
|
100
110
|
const pathRoute: PathRoute = location?.pathRoute ?? {
|
|
@@ -112,14 +122,24 @@ export const ClientPathWrapper = ({
|
|
|
112
122
|
<pathContext.Provider
|
|
113
123
|
value={{
|
|
114
124
|
pageType,
|
|
115
|
-
location: {
|
|
125
|
+
location: {
|
|
126
|
+
href,
|
|
127
|
+
hash,
|
|
128
|
+
pathname,
|
|
129
|
+
params,
|
|
130
|
+
searchParams,
|
|
131
|
+
search,
|
|
132
|
+
pathRoute,
|
|
133
|
+
},
|
|
116
134
|
prefix,
|
|
117
135
|
gestureEnabled,
|
|
118
136
|
setGestureEnabled,
|
|
119
137
|
}}
|
|
120
138
|
>
|
|
121
139
|
<animated.div
|
|
122
|
-
{...(bind && pathRoute.pageState.gesture && gestureEnabled
|
|
140
|
+
{...(bind && pathRoute.pageState.gesture && gestureEnabled
|
|
141
|
+
? bind()
|
|
142
|
+
: {})}
|
|
123
143
|
className={clsx("group/path", className)}
|
|
124
144
|
ref={wrapperRef}
|
|
125
145
|
{...props}
|
|
@@ -141,7 +161,13 @@ interface ClientBridgeProps {
|
|
|
141
161
|
gaTrackingId?: string;
|
|
142
162
|
}
|
|
143
163
|
|
|
144
|
-
export const ClientBridge = ({
|
|
164
|
+
export const ClientBridge = ({
|
|
165
|
+
env,
|
|
166
|
+
lang,
|
|
167
|
+
theme,
|
|
168
|
+
prefix,
|
|
169
|
+
gaTrackingId,
|
|
170
|
+
}: ClientBridgeProps) => {
|
|
145
171
|
const uiOperation = st.use.uiOperation();
|
|
146
172
|
const pathname = st.use.pathname();
|
|
147
173
|
const params = st.use.params();
|
|
@@ -208,18 +234,26 @@ function applyThemePolicy(theme: AkanTheme): void {
|
|
|
208
234
|
}
|
|
209
235
|
if (theme === "system") {
|
|
210
236
|
const dark = window.matchMedia("(prefers-color-scheme: dark)").matches;
|
|
211
|
-
document.documentElement.setAttribute(
|
|
237
|
+
document.documentElement.setAttribute(
|
|
238
|
+
"data-theme",
|
|
239
|
+
dark ? "dark" : "light",
|
|
240
|
+
);
|
|
212
241
|
return;
|
|
213
242
|
}
|
|
214
243
|
document.documentElement.setAttribute("data-theme", theme);
|
|
215
244
|
}
|
|
216
245
|
|
|
217
|
-
function buildSearchParams(
|
|
246
|
+
function buildSearchParams(
|
|
247
|
+
entries: Iterable<[string, string]>,
|
|
248
|
+
): Record<string, string | string[]> {
|
|
218
249
|
const params: Record<string, string | string[]> = {};
|
|
219
250
|
for (const [key, value] of entries) {
|
|
220
251
|
const current = params[key];
|
|
221
252
|
if (current === undefined) params[key] = value;
|
|
222
|
-
else
|
|
253
|
+
else
|
|
254
|
+
params[key] = Array.isArray(current)
|
|
255
|
+
? [...current, value]
|
|
256
|
+
: [current, value];
|
|
223
257
|
}
|
|
224
258
|
return params;
|
|
225
259
|
}
|
|
@@ -239,7 +273,10 @@ interface ClientSsrBridgeProps {
|
|
|
239
273
|
lang: string;
|
|
240
274
|
prefix?: string;
|
|
241
275
|
}
|
|
242
|
-
export const ClientSsrBridge = ({
|
|
276
|
+
export const ClientSsrBridge = ({
|
|
277
|
+
lang,
|
|
278
|
+
prefix = "",
|
|
279
|
+
}: ClientSsrBridgeProps) => {
|
|
243
280
|
useEffect(() => {
|
|
244
281
|
const visiblePrefix = getEnv().operationMode === "local" ? prefix : "";
|
|
245
282
|
const navigateRscWithFallback = (
|
|
@@ -253,13 +290,19 @@ export const ClientSsrBridge = ({ lang, prefix = "" }: ClientSsrBridgeProps) =>
|
|
|
253
290
|
return;
|
|
254
291
|
}
|
|
255
292
|
void navigation.catch((error) => {
|
|
256
|
-
Logger.warn(
|
|
293
|
+
Logger.warn(
|
|
294
|
+
`RSC navigation failed, falling back to document navigation: ${String(error)}`,
|
|
295
|
+
);
|
|
257
296
|
fallback();
|
|
258
297
|
});
|
|
259
298
|
};
|
|
260
299
|
const syncHref = (href: string) => {
|
|
261
300
|
const url = new URL(href, window.location.origin);
|
|
262
|
-
const { path } = getPathInfo(
|
|
301
|
+
const { path } = getPathInfo(
|
|
302
|
+
`${url.pathname}${url.search}${url.hash}`,
|
|
303
|
+
lang,
|
|
304
|
+
visiblePrefix,
|
|
305
|
+
);
|
|
263
306
|
const searchParams = buildSearchParams(url.searchParams.entries());
|
|
264
307
|
st.set({ pathname: url.pathname, path, searchParams });
|
|
265
308
|
};
|
|
@@ -271,11 +314,17 @@ export const ClientSsrBridge = ({ lang, prefix = "" }: ClientSsrBridgeProps) =>
|
|
|
271
314
|
router: {
|
|
272
315
|
push: (href, routeOptions) => {
|
|
273
316
|
syncHref(href);
|
|
274
|
-
navigateRscWithFallback(href, routeOptions, () =>
|
|
317
|
+
navigateRscWithFallback(href, routeOptions, () =>
|
|
318
|
+
window.location.assign(href),
|
|
319
|
+
);
|
|
275
320
|
},
|
|
276
321
|
replace: (href, routeOptions) => {
|
|
277
322
|
syncHref(href);
|
|
278
|
-
navigateRscWithFallback(
|
|
323
|
+
navigateRscWithFallback(
|
|
324
|
+
href,
|
|
325
|
+
{ ...routeOptions, replace: true },
|
|
326
|
+
() => window.location.replace(href),
|
|
327
|
+
);
|
|
279
328
|
},
|
|
280
329
|
back: () => {
|
|
281
330
|
window.history.back();
|
|
@@ -283,7 +332,10 @@ export const ClientSsrBridge = ({ lang, prefix = "" }: ClientSsrBridgeProps) =>
|
|
|
283
332
|
refresh: () => {
|
|
284
333
|
clearRscNavigationCache();
|
|
285
334
|
syncHref(window.location.href);
|
|
286
|
-
void navigateRsc(window.location.href, {
|
|
335
|
+
void navigateRsc(window.location.href, {
|
|
336
|
+
replace: true,
|
|
337
|
+
scrollToTop: false,
|
|
338
|
+
});
|
|
287
339
|
},
|
|
288
340
|
},
|
|
289
341
|
});
|
|
@@ -294,8 +346,14 @@ export const ClientSsrBridge = ({ lang, prefix = "" }: ClientSsrBridgeProps) =>
|
|
|
294
346
|
const visiblePrefix = getEnv().operationMode === "local" ? prefix : "";
|
|
295
347
|
const sync = () => {
|
|
296
348
|
const { pathname, search, hash } = window.location;
|
|
297
|
-
const { path } = getPathInfo(
|
|
298
|
-
|
|
349
|
+
const { path } = getPathInfo(
|
|
350
|
+
`${pathname}${search}${hash}`,
|
|
351
|
+
lang,
|
|
352
|
+
visiblePrefix,
|
|
353
|
+
);
|
|
354
|
+
const searchParams = buildSearchParams(
|
|
355
|
+
new URLSearchParams(search).entries(),
|
|
356
|
+
);
|
|
299
357
|
st.set({ pathname: window.location.pathname, path, searchParams });
|
|
300
358
|
};
|
|
301
359
|
sync();
|
package/ui/System/Common.tsx
CHANGED
|
@@ -30,6 +30,8 @@ export interface ProviderProps {
|
|
|
30
30
|
layoutStyle?: "mobile" | "web";
|
|
31
31
|
/** Enable reconnect helper. Defaults to local operation mode in CSR. */
|
|
32
32
|
reconnect?: boolean;
|
|
33
|
+
/** Active-locale dictionary injected by the server (SSR only) to seed the client Translator. */
|
|
34
|
+
dictionary?: Record<string, Record<string, unknown>>;
|
|
33
35
|
/** Root route component used by CSR page loading. */
|
|
34
36
|
of: (props: unknown) => ReactNode | null;
|
|
35
37
|
}
|
|
@@ -55,7 +57,10 @@ export function toManifestJson(value: unknown): unknown {
|
|
|
55
57
|
return Object.fromEntries(
|
|
56
58
|
Object.entries(value)
|
|
57
59
|
.filter(([, entryValue]) => entryValue !== undefined)
|
|
58
|
-
.map(([key, entryValue]) => [
|
|
60
|
+
.map(([key, entryValue]) => [
|
|
61
|
+
camelToSnake(key),
|
|
62
|
+
toManifestJson(entryValue),
|
|
63
|
+
]),
|
|
59
64
|
);
|
|
60
65
|
}
|
|
61
66
|
|
|
@@ -75,7 +80,11 @@ function encodeBase64Utf8(value: string): string {
|
|
|
75
80
|
|
|
76
81
|
const buffer = (
|
|
77
82
|
globalThis as typeof globalThis & {
|
|
78
|
-
Buffer?: {
|
|
83
|
+
Buffer?: {
|
|
84
|
+
from: (bytes: Uint8Array) => {
|
|
85
|
+
toString: (encoding: "base64") => string;
|
|
86
|
+
};
|
|
87
|
+
};
|
|
79
88
|
}
|
|
80
89
|
).Buffer;
|
|
81
90
|
if (buffer) return buffer.from(bytes).toString("base64");
|
package/ui/System/SSR.tsx
CHANGED
|
@@ -1,10 +1,21 @@
|
|
|
1
1
|
import { getEnv } from "akanjs/base";
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
clsx,
|
|
4
|
+
type ReactFont,
|
|
5
|
+
router,
|
|
6
|
+
type WebAppManifest,
|
|
7
|
+
} from "akanjs/client";
|
|
3
8
|
import { setRequestTheme } from "akanjs/fetch";
|
|
4
9
|
import { Children, Fragment, type ReactNode, Suspense } from "react";
|
|
5
10
|
import { FontCss } from "../fontCss";
|
|
6
11
|
import { Load } from "../Load";
|
|
7
|
-
import {
|
|
12
|
+
import {
|
|
13
|
+
ClientBridge,
|
|
14
|
+
ClientInner,
|
|
15
|
+
ClientPathWrapper,
|
|
16
|
+
ClientSsrBridge,
|
|
17
|
+
ClientWrapper,
|
|
18
|
+
} from "./Client";
|
|
8
19
|
import { ManifestLink, type ProviderProps } from "./Common";
|
|
9
20
|
|
|
10
21
|
export const SSR = () => {
|
|
@@ -29,6 +40,7 @@ const SSRProvider = ({
|
|
|
29
40
|
fonts,
|
|
30
41
|
layoutStyle = "web",
|
|
31
42
|
reconnect = getEnv().operationMode === "local",
|
|
43
|
+
dictionary,
|
|
32
44
|
of,
|
|
33
45
|
}: SSRProviderProps) => {
|
|
34
46
|
setRequestTheme(theme);
|
|
@@ -38,7 +50,8 @@ const SSRProvider = ({
|
|
|
38
50
|
of={of}
|
|
39
51
|
loader={async () => {
|
|
40
52
|
const { lang } = params;
|
|
41
|
-
if (!router.isInitialized)
|
|
53
|
+
if (!router.isInitialized)
|
|
54
|
+
router.init({ type: "ssr", side: "server", lang, prefix });
|
|
42
55
|
return { lang } as const;
|
|
43
56
|
}}
|
|
44
57
|
render={({ lang }) => (
|
|
@@ -52,13 +65,24 @@ const SSRProvider = ({
|
|
|
52
65
|
prefix={prefix}
|
|
53
66
|
layoutStyle={layoutStyle}
|
|
54
67
|
>
|
|
55
|
-
<ClientWrapper
|
|
68
|
+
<ClientWrapper
|
|
69
|
+
theme={theme}
|
|
70
|
+
lang={lang}
|
|
71
|
+
reconnect={reconnect}
|
|
72
|
+
dictionary={dictionary}
|
|
73
|
+
>
|
|
56
74
|
<Fragment key="children">{Children.toArray(children)}</Fragment>
|
|
57
75
|
<Suspense key="client-inner" fallback={null}>
|
|
58
76
|
<ClientInner />
|
|
59
77
|
</Suspense>
|
|
60
78
|
<Suspense key="client-bridge" fallback={null}>
|
|
61
|
-
<ClientBridge
|
|
79
|
+
<ClientBridge
|
|
80
|
+
key="bridge"
|
|
81
|
+
env={env}
|
|
82
|
+
theme={theme}
|
|
83
|
+
prefix={prefix}
|
|
84
|
+
gaTrackingId={gaTrackingId}
|
|
85
|
+
/>
|
|
62
86
|
<ClientSsrBridge key="ssr-bridge" lang={lang} prefix={prefix} />
|
|
63
87
|
</Suspense>
|
|
64
88
|
</ClientWrapper>
|
|
@@ -77,7 +101,14 @@ const ServerFontFace = ({ fonts }: { fonts: ReactFont[] }) => {
|
|
|
77
101
|
return (
|
|
78
102
|
<>
|
|
79
103
|
{preloads.map((preload) => (
|
|
80
|
-
<link
|
|
104
|
+
<link
|
|
105
|
+
key={preload.href}
|
|
106
|
+
rel="preload"
|
|
107
|
+
href={preload.href}
|
|
108
|
+
as="font"
|
|
109
|
+
type={preload.type}
|
|
110
|
+
crossOrigin=""
|
|
111
|
+
/>
|
|
81
112
|
))}
|
|
82
113
|
{css ? (
|
|
83
114
|
|
|
@@ -114,14 +145,23 @@ const SSRWrapper = ({
|
|
|
114
145
|
{head ? <Fragment key="head">{head}</Fragment> : null}
|
|
115
146
|
<div key="frame-root" id="frameRoot" className={className}>
|
|
116
147
|
<ClientPathWrapper layoutStyle={layoutStyle} prefix={prefix}>
|
|
117
|
-
<div
|
|
118
|
-
|
|
148
|
+
<div
|
|
149
|
+
key="top-safe-area"
|
|
150
|
+
id="topSafeArea"
|
|
151
|
+
className={clsx("fixed inset-x-0 top-0 bg-base-100")}
|
|
152
|
+
/>
|
|
153
|
+
<div
|
|
154
|
+
key="page-containers"
|
|
155
|
+
id="pageContainers"
|
|
156
|
+
className={clsx("isolate")}
|
|
157
|
+
>
|
|
119
158
|
<div id="pageContainer">
|
|
120
159
|
<div
|
|
121
160
|
id="pageContent"
|
|
122
161
|
className={clsx("relative isolate", {
|
|
123
162
|
"w-full": layoutStyle === "web",
|
|
124
|
-
"left-1/2 h-screen w-[600px] -translate-x-1/2":
|
|
163
|
+
"left-1/2 h-screen w-[600px] -translate-x-1/2":
|
|
164
|
+
layoutStyle === "mobile",
|
|
125
165
|
})}
|
|
126
166
|
>
|
|
127
167
|
{Children.toArray(children)}
|
|
@@ -136,7 +176,10 @@ const SSRWrapper = ({
|
|
|
136
176
|
"w-full": layoutStyle === "web",
|
|
137
177
|
})}
|
|
138
178
|
>
|
|
139
|
-
<div
|
|
179
|
+
<div
|
|
180
|
+
id="topInsetContent"
|
|
181
|
+
className={clsx("relative isolate size-full")}
|
|
182
|
+
/>
|
|
140
183
|
</div>
|
|
141
184
|
<div
|
|
142
185
|
key="top-left-action"
|
|
@@ -153,7 +196,11 @@ const SSRWrapper = ({
|
|
|
153
196
|
>
|
|
154
197
|
<div id="bottomInsetContent" className="isolate size-full" />
|
|
155
198
|
</div>
|
|
156
|
-
<div
|
|
199
|
+
<div
|
|
200
|
+
key="bottom-safe-area"
|
|
201
|
+
id="bottomSafeArea"
|
|
202
|
+
className="fixed inset-x-0 bg-base-100"
|
|
203
|
+
/>
|
|
157
204
|
</ClientPathWrapper>
|
|
158
205
|
</div>
|
|
159
206
|
</>
|
package/webkit/bootCsr.tsx
CHANGED
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
Device,
|
|
7
7
|
defaultPageState,
|
|
8
8
|
initAuth,
|
|
9
|
+
type LayoutModule,
|
|
9
10
|
type PageConfig,
|
|
10
11
|
type PageState,
|
|
11
12
|
type PathRoute,
|
|
@@ -151,7 +152,15 @@ export const bootCsr = async (context: Record<string, () => Promise<RouteModule>
|
|
|
151
152
|
if (!targetPath) continue;
|
|
152
153
|
const page = pages[filePath];
|
|
153
154
|
if (!page) continue;
|
|
154
|
-
const
|
|
155
|
+
const layoutPage = parsed.kind === "layout" ? (page as LayoutModule) : null;
|
|
156
|
+
const routeRender: RouteRender = {
|
|
157
|
+
render: page.default as never,
|
|
158
|
+
Loading: page.Loading as never,
|
|
159
|
+
NotFound: layoutPage?.NotFound,
|
|
160
|
+
Error: layoutPage?.Error,
|
|
161
|
+
resolveNotFound: layoutPage ? () => layoutPage.NotFound : undefined,
|
|
162
|
+
resolveError: layoutPage ? () => layoutPage.Error : undefined,
|
|
163
|
+
};
|
|
155
164
|
targetRouteMap.set(targetPath, {
|
|
156
165
|
|
|
157
166
|
...(targetRouteMap.get(targetPath) ?? { path: targetPath, children: new Map<string, Route>() }),
|
|
@@ -265,8 +274,10 @@ function validateRouteModuleExports(key: string, mod: RouteModule) {
|
|
|
265
274
|
"layoutStyle",
|
|
266
275
|
"gaTrackingId",
|
|
267
276
|
"Loading",
|
|
277
|
+
"NotFound",
|
|
278
|
+
"Error",
|
|
268
279
|
])
|
|
269
|
-
: new Set(["default", "head", "generateHead", "Loading"]);
|
|
280
|
+
: new Set(["default", "head", "generateHead", "Loading", "NotFound", "Error"]);
|
|
270
281
|
for (const exportName of Object.keys(mod)) {
|
|
271
282
|
if (!allowed.has(exportName)) {
|
|
272
283
|
throw new Error(`[route-convention] unsupported export "${exportName}" in ${key}`);
|