@alepha/react 0.9.2 → 0.9.4
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/README.md +46 -0
- package/dist/index.browser.js +378 -325
- package/dist/index.browser.js.map +1 -1
- package/dist/index.cjs +570 -458
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +305 -213
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +304 -212
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +567 -460
- package/dist/index.js.map +1 -1
- package/package.json +16 -13
- package/src/components/ErrorViewer.tsx +1 -1
- package/src/components/Link.tsx +4 -24
- package/src/components/NestedView.tsx +20 -9
- package/src/components/NotFound.tsx +5 -2
- package/src/descriptors/$page.ts +86 -12
- package/src/errors/Redirection.ts +13 -0
- package/src/hooks/useActive.ts +28 -30
- package/src/hooks/useAlepha.ts +16 -2
- package/src/hooks/useClient.ts +7 -2
- package/src/hooks/useInject.ts +4 -1
- package/src/hooks/useQueryParams.ts +9 -6
- package/src/hooks/useRouter.ts +18 -30
- package/src/hooks/useRouterEvents.ts +7 -4
- package/src/hooks/useRouterState.ts +8 -20
- package/src/hooks/useSchema.ts +10 -15
- package/src/hooks/useStore.ts +9 -8
- package/src/index.browser.ts +11 -11
- package/src/index.shared.ts +4 -5
- package/src/index.ts +21 -30
- package/src/providers/ReactBrowserProvider.ts +155 -65
- package/src/providers/ReactBrowserRouterProvider.ts +132 -0
- package/src/providers/{PageDescriptorProvider.ts → ReactPageProvider.ts} +164 -112
- package/src/providers/ReactServerProvider.ts +100 -68
- package/src/{hooks/RouterHookApi.ts → services/ReactRouter.ts} +75 -61
- package/src/contexts/RouterContext.ts +0 -14
- package/src/errors/RedirectionError.ts +0 -10
- package/src/providers/BrowserRouterProvider.ts +0 -146
- package/src/providers/ReactBrowserRenderer.ts +0 -93
|
@@ -4,13 +4,13 @@ import {
|
|
|
4
4
|
$env,
|
|
5
5
|
$hook,
|
|
6
6
|
$inject,
|
|
7
|
-
$logger,
|
|
8
7
|
Alepha,
|
|
8
|
+
AlephaError,
|
|
9
9
|
type Static,
|
|
10
10
|
t,
|
|
11
11
|
} from "@alepha/core";
|
|
12
|
+
import { $logger } from "@alepha/logger";
|
|
12
13
|
import {
|
|
13
|
-
apiLinksResponseSchema,
|
|
14
14
|
type ServerHandler,
|
|
15
15
|
ServerRouterProvider,
|
|
16
16
|
ServerTimingProvider,
|
|
@@ -22,14 +22,13 @@ import {
|
|
|
22
22
|
$page,
|
|
23
23
|
type PageDescriptorRenderOptions,
|
|
24
24
|
} from "../descriptors/$page.ts";
|
|
25
|
+
import { Redirection } from "../errors/Redirection.ts";
|
|
26
|
+
import type { ReactHydrationState } from "./ReactBrowserProvider.ts";
|
|
25
27
|
import {
|
|
26
|
-
PageDescriptorProvider,
|
|
27
|
-
type PageReactContext,
|
|
28
|
-
type PageRequest,
|
|
29
28
|
type PageRoute,
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
29
|
+
ReactPageProvider,
|
|
30
|
+
type ReactRouterState,
|
|
31
|
+
} from "./ReactPageProvider.ts";
|
|
33
32
|
|
|
34
33
|
const envSchema = t.object({
|
|
35
34
|
REACT_SERVER_DIST: t.string({ default: "public" }),
|
|
@@ -53,7 +52,7 @@ declare module "@alepha/core" {
|
|
|
53
52
|
export class ReactServerProvider {
|
|
54
53
|
protected readonly log = $logger();
|
|
55
54
|
protected readonly alepha = $inject(Alepha);
|
|
56
|
-
protected readonly
|
|
55
|
+
protected readonly pageApi = $inject(ReactPageProvider);
|
|
57
56
|
protected readonly serverStaticProvider = $inject(ServerStaticProvider);
|
|
58
57
|
protected readonly serverRouterProvider = $inject(ServerRouterProvider);
|
|
59
58
|
protected readonly serverTimingProvider = $inject(ServerTimingProvider);
|
|
@@ -135,7 +134,7 @@ export class ReactServerProvider {
|
|
|
135
134
|
}
|
|
136
135
|
|
|
137
136
|
protected async registerPages(templateLoader: TemplateLoader) {
|
|
138
|
-
for (const page of this.
|
|
137
|
+
for (const page of this.pageApi.getPages()) {
|
|
139
138
|
if (page.children?.length) {
|
|
140
139
|
continue;
|
|
141
140
|
}
|
|
@@ -196,43 +195,56 @@ export class ReactServerProvider {
|
|
|
196
195
|
*/
|
|
197
196
|
protected createRenderFunction(name: string, withIndex = false) {
|
|
198
197
|
return async (options: PageDescriptorRenderOptions = {}) => {
|
|
199
|
-
const page = this.
|
|
200
|
-
const url = new URL(this.
|
|
201
|
-
|
|
198
|
+
const page = this.pageApi.page(name);
|
|
199
|
+
const url = new URL(this.pageApi.url(name, options));
|
|
200
|
+
|
|
201
|
+
const entry: Partial<ReactRouterState> = {
|
|
202
202
|
url,
|
|
203
203
|
params: options.params ?? {},
|
|
204
204
|
query: options.query ?? {},
|
|
205
|
-
head: {},
|
|
206
205
|
onError: () => null,
|
|
206
|
+
layers: [],
|
|
207
207
|
};
|
|
208
208
|
|
|
209
|
+
const state = entry as ReactRouterState;
|
|
210
|
+
|
|
211
|
+
this.log.trace("Rendering", {
|
|
212
|
+
url,
|
|
213
|
+
});
|
|
214
|
+
|
|
209
215
|
await this.alepha.emit("react:server:render:begin", {
|
|
210
|
-
|
|
216
|
+
state,
|
|
211
217
|
});
|
|
212
218
|
|
|
213
|
-
const
|
|
219
|
+
const { redirect } = await this.pageApi.createLayers(
|
|
214
220
|
page,
|
|
215
|
-
|
|
221
|
+
state as ReactRouterState,
|
|
216
222
|
);
|
|
217
223
|
|
|
224
|
+
if (redirect) {
|
|
225
|
+
throw new AlephaError("Redirection is not supported in this context");
|
|
226
|
+
}
|
|
227
|
+
|
|
218
228
|
if (!withIndex && !options.html) {
|
|
229
|
+
this.alepha.state("react.router.state", state);
|
|
230
|
+
|
|
219
231
|
return {
|
|
220
|
-
|
|
221
|
-
html: renderToString(
|
|
222
|
-
this.pageDescriptorProvider.root(state, context),
|
|
223
|
-
),
|
|
232
|
+
state,
|
|
233
|
+
html: renderToString(this.pageApi.root(state)),
|
|
224
234
|
};
|
|
225
235
|
}
|
|
226
236
|
|
|
227
237
|
const html = this.renderToHtml(
|
|
228
238
|
this.template ?? "",
|
|
229
239
|
state,
|
|
230
|
-
context,
|
|
231
240
|
options.hydration,
|
|
232
241
|
);
|
|
233
242
|
|
|
243
|
+
if (html instanceof Redirection) {
|
|
244
|
+
throw new Error("Redirection is not supported in this context");
|
|
245
|
+
}
|
|
246
|
+
|
|
234
247
|
const result = {
|
|
235
|
-
context,
|
|
236
248
|
state,
|
|
237
249
|
html,
|
|
238
250
|
};
|
|
@@ -244,7 +256,7 @@ export class ReactServerProvider {
|
|
|
244
256
|
}
|
|
245
257
|
|
|
246
258
|
protected createHandler(
|
|
247
|
-
|
|
259
|
+
route: PageRoute,
|
|
248
260
|
templateLoader: TemplateLoader,
|
|
249
261
|
): ServerHandler {
|
|
250
262
|
return async (serverRequest) => {
|
|
@@ -254,33 +266,33 @@ export class ReactServerProvider {
|
|
|
254
266
|
throw new Error("Template not found");
|
|
255
267
|
}
|
|
256
268
|
|
|
257
|
-
|
|
269
|
+
this.log.trace("Rendering page", {
|
|
270
|
+
name: route.name,
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
const entry: Partial<ReactRouterState> = {
|
|
258
274
|
url,
|
|
259
275
|
params,
|
|
260
276
|
query,
|
|
261
|
-
// plugins
|
|
262
|
-
head: {},
|
|
263
277
|
onError: () => null,
|
|
278
|
+
layers: [],
|
|
264
279
|
};
|
|
265
280
|
|
|
266
|
-
|
|
267
|
-
const srv = this.alepha.inject(ServerLinksProvider);
|
|
268
|
-
const schema = apiLinksResponseSchema as any;
|
|
281
|
+
const state = entry as ReactRouterState;
|
|
269
282
|
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
283
|
+
if (this.alepha.has(ServerLinksProvider)) {
|
|
284
|
+
this.alepha.state(
|
|
285
|
+
"api",
|
|
286
|
+
await this.alepha.inject(ServerLinksProvider).getUserApiLinks({
|
|
287
|
+
user: (serverRequest as any).user, // TODO: fix type
|
|
274
288
|
authorization: serverRequest.headers.authorization,
|
|
275
289
|
}),
|
|
276
|
-
)
|
|
277
|
-
|
|
278
|
-
this.alepha.context.set("links", context.links);
|
|
290
|
+
);
|
|
279
291
|
}
|
|
280
292
|
|
|
281
|
-
let target: PageRoute | undefined =
|
|
293
|
+
let target: PageRoute | undefined = route; // TODO: move to PageDescriptorProvider
|
|
282
294
|
while (target) {
|
|
283
|
-
if (
|
|
295
|
+
if (route.can && !route.can()) {
|
|
284
296
|
// if the page is not accessible, return 403
|
|
285
297
|
reply.status = 403;
|
|
286
298
|
reply.headers["content-type"] = "text/plain";
|
|
@@ -302,20 +314,17 @@ export class ReactServerProvider {
|
|
|
302
314
|
|
|
303
315
|
await this.alepha.emit("react:server:render:begin", {
|
|
304
316
|
request: serverRequest,
|
|
305
|
-
|
|
317
|
+
state,
|
|
306
318
|
});
|
|
307
319
|
|
|
308
320
|
this.serverTimingProvider.beginTiming("createLayers");
|
|
309
321
|
|
|
310
|
-
const
|
|
311
|
-
page,
|
|
312
|
-
context,
|
|
313
|
-
);
|
|
322
|
+
const { redirect } = await this.pageApi.createLayers(route, state);
|
|
314
323
|
|
|
315
324
|
this.serverTimingProvider.endTiming("createLayers");
|
|
316
325
|
|
|
317
|
-
if (
|
|
318
|
-
return reply.redirect(
|
|
326
|
+
if (redirect) {
|
|
327
|
+
return reply.redirect(redirect);
|
|
319
328
|
}
|
|
320
329
|
|
|
321
330
|
reply.headers["content-type"] = "text/html";
|
|
@@ -327,44 +336,62 @@ export class ReactServerProvider {
|
|
|
327
336
|
reply.headers.pragma = "no-cache";
|
|
328
337
|
reply.headers.expires = "0";
|
|
329
338
|
|
|
330
|
-
|
|
331
|
-
if (
|
|
332
|
-
|
|
339
|
+
const html = this.renderToHtml(template, state);
|
|
340
|
+
if (html instanceof Redirection) {
|
|
341
|
+
reply.redirect(
|
|
342
|
+
typeof html.redirect === "string"
|
|
343
|
+
? html.redirect
|
|
344
|
+
: this.pageApi.href(html.redirect),
|
|
345
|
+
);
|
|
346
|
+
return;
|
|
333
347
|
}
|
|
334
348
|
|
|
335
|
-
const
|
|
336
|
-
|
|
337
|
-
await this.alepha.emit("react:server:render:end", {
|
|
349
|
+
const event = {
|
|
338
350
|
request: serverRequest,
|
|
339
|
-
context,
|
|
340
351
|
state,
|
|
341
352
|
html,
|
|
342
|
-
}
|
|
353
|
+
};
|
|
354
|
+
|
|
355
|
+
await this.alepha.emit("react:server:render:end", event);
|
|
343
356
|
|
|
344
|
-
|
|
357
|
+
route.onServerResponse?.(serverRequest);
|
|
358
|
+
|
|
359
|
+
this.log.trace("Page rendered", {
|
|
360
|
+
name: route.name,
|
|
361
|
+
});
|
|
345
362
|
|
|
346
|
-
return html;
|
|
363
|
+
return event.html;
|
|
347
364
|
};
|
|
348
365
|
}
|
|
349
366
|
|
|
350
367
|
public renderToHtml(
|
|
351
368
|
template: string,
|
|
352
|
-
state:
|
|
353
|
-
context: PageReactContext,
|
|
369
|
+
state: ReactRouterState,
|
|
354
370
|
hydration = true,
|
|
355
|
-
) {
|
|
356
|
-
const element = this.
|
|
371
|
+
): string | Redirection {
|
|
372
|
+
const element = this.pageApi.root(state);
|
|
357
373
|
|
|
358
|
-
|
|
374
|
+
// attach react router state to the http request context
|
|
375
|
+
this.alepha.state("react.router.state", state);
|
|
359
376
|
|
|
377
|
+
this.serverTimingProvider.beginTiming("renderToString");
|
|
360
378
|
let app = "";
|
|
361
379
|
try {
|
|
362
380
|
app = renderToString(element);
|
|
363
381
|
} catch (error) {
|
|
364
|
-
this.log.error(
|
|
365
|
-
|
|
366
|
-
|
|
382
|
+
this.log.error(
|
|
383
|
+
"renderToString has failed, fallback to error handler",
|
|
384
|
+
error,
|
|
385
|
+
);
|
|
386
|
+
const element = state.onError(error as Error, state);
|
|
387
|
+
if (element instanceof Redirection) {
|
|
388
|
+
// if the error is a redirection, return the redirection URL
|
|
389
|
+
return element;
|
|
390
|
+
}
|
|
367
391
|
|
|
392
|
+
app = renderToString(element);
|
|
393
|
+
this.log.debug("Error handled successfully with fallback");
|
|
394
|
+
}
|
|
368
395
|
this.serverTimingProvider.endTiming("renderToString");
|
|
369
396
|
|
|
370
397
|
const response = {
|
|
@@ -372,8 +399,13 @@ export class ReactServerProvider {
|
|
|
372
399
|
};
|
|
373
400
|
|
|
374
401
|
if (hydration) {
|
|
402
|
+
const { request, context, ...store } =
|
|
403
|
+
this.alepha.context.als?.getStore() ?? {}; /// TODO: als must be protected, find a way to iterate on alepha.state
|
|
404
|
+
|
|
375
405
|
const hydrationData: ReactHydrationState = {
|
|
376
|
-
|
|
406
|
+
...store,
|
|
407
|
+
// map react.router.state to the hydration state
|
|
408
|
+
"react.router.state": undefined,
|
|
377
409
|
layers: state.layers.map((it) => ({
|
|
378
410
|
...it,
|
|
379
411
|
error: it.error
|
|
@@ -381,7 +413,7 @@ export class ReactServerProvider {
|
|
|
381
413
|
...it.error,
|
|
382
414
|
name: it.error.name,
|
|
383
415
|
message: it.error.message,
|
|
384
|
-
stack: it.error.stack
|
|
416
|
+
stack: !this.alepha.isProduction() ? it.error.stack : undefined,
|
|
385
417
|
}
|
|
386
418
|
: undefined,
|
|
387
419
|
index: undefined,
|
|
@@ -418,7 +450,7 @@ export class ReactServerProvider {
|
|
|
418
450
|
const bodyOpenTag = /<body([^>]*)>/i;
|
|
419
451
|
if (bodyOpenTag.test(response.html)) {
|
|
420
452
|
response.html = response.html.replace(bodyOpenTag, (match) => {
|
|
421
|
-
return `${match}
|
|
453
|
+
return `${match}<div id="${this.env.REACT_ROOT_ID}">${app}</div>`;
|
|
422
454
|
});
|
|
423
455
|
}
|
|
424
456
|
}
|
|
@@ -427,7 +459,7 @@ export class ReactServerProvider {
|
|
|
427
459
|
if (bodyCloseTagRegex.test(response.html)) {
|
|
428
460
|
response.html = response.html.replace(
|
|
429
461
|
bodyCloseTagRegex,
|
|
430
|
-
`${script}
|
|
462
|
+
`${script}</body>`,
|
|
431
463
|
);
|
|
432
464
|
}
|
|
433
465
|
}
|
|
@@ -1,30 +1,56 @@
|
|
|
1
|
+
import { $inject, Alepha } from "@alepha/core";
|
|
1
2
|
import type { PageDescriptor } from "../descriptors/$page.ts";
|
|
2
|
-
import
|
|
3
|
-
AnchorProps,
|
|
4
|
-
PageReactContext,
|
|
5
|
-
PageRoute,
|
|
6
|
-
RouterState,
|
|
7
|
-
} from "../providers/PageDescriptorProvider.ts";
|
|
8
|
-
import type {
|
|
3
|
+
import {
|
|
9
4
|
ReactBrowserProvider,
|
|
10
|
-
RouterGoOptions,
|
|
5
|
+
type RouterGoOptions,
|
|
11
6
|
} from "../providers/ReactBrowserProvider.ts";
|
|
7
|
+
import {
|
|
8
|
+
type AnchorProps,
|
|
9
|
+
ReactPageProvider,
|
|
10
|
+
type ReactRouterState,
|
|
11
|
+
} from "../providers/ReactPageProvider.ts";
|
|
12
|
+
|
|
13
|
+
export class ReactRouter<T extends object> {
|
|
14
|
+
protected readonly alepha = $inject(Alepha);
|
|
15
|
+
protected readonly pageApi = $inject(ReactPageProvider);
|
|
16
|
+
|
|
17
|
+
public get state(): ReactRouterState {
|
|
18
|
+
return this.alepha.state("react.router.state")!;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
public get pages() {
|
|
22
|
+
return this.pageApi.getPages();
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
public get browser(): ReactBrowserProvider | undefined {
|
|
26
|
+
if (this.alepha.isBrowser()) {
|
|
27
|
+
return this.alepha.inject(ReactBrowserProvider);
|
|
28
|
+
}
|
|
29
|
+
// server-side
|
|
30
|
+
return undefined;
|
|
31
|
+
}
|
|
12
32
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
33
|
+
public path(
|
|
34
|
+
name: keyof VirtualRouter<T>,
|
|
35
|
+
config: {
|
|
36
|
+
params?: Record<string, string>;
|
|
37
|
+
query?: Record<string, string>;
|
|
38
|
+
} = {},
|
|
39
|
+
): string {
|
|
40
|
+
return this.pageApi.pathname(name as string, {
|
|
41
|
+
params: {
|
|
42
|
+
...this.state.params,
|
|
43
|
+
...config.params,
|
|
44
|
+
},
|
|
45
|
+
query: config.query,
|
|
46
|
+
});
|
|
47
|
+
}
|
|
23
48
|
|
|
24
49
|
public getURL(): URL {
|
|
25
50
|
if (!this.browser) {
|
|
26
|
-
return this.
|
|
51
|
+
return this.state.url;
|
|
27
52
|
}
|
|
53
|
+
|
|
28
54
|
return new URL(this.location.href);
|
|
29
55
|
}
|
|
30
56
|
|
|
@@ -36,19 +62,19 @@ export class RouterHookApi {
|
|
|
36
62
|
return this.browser.location;
|
|
37
63
|
}
|
|
38
64
|
|
|
39
|
-
public get current():
|
|
65
|
+
public get current(): ReactRouterState {
|
|
40
66
|
return this.state;
|
|
41
67
|
}
|
|
42
68
|
|
|
43
69
|
public get pathname(): string {
|
|
44
|
-
return this.state.pathname;
|
|
70
|
+
return this.state.url.pathname;
|
|
45
71
|
}
|
|
46
72
|
|
|
47
73
|
public get query(): Record<string, string> {
|
|
48
74
|
const query: Record<string, string> = {};
|
|
49
75
|
|
|
50
76
|
for (const [key, value] of new URLSearchParams(
|
|
51
|
-
this.state.search,
|
|
77
|
+
this.state.url.search,
|
|
52
78
|
).entries()) {
|
|
53
79
|
query[key] = String(value);
|
|
54
80
|
}
|
|
@@ -68,79 +94,69 @@ export class RouterHookApi {
|
|
|
68
94
|
await this.browser?.invalidate(props);
|
|
69
95
|
}
|
|
70
96
|
|
|
71
|
-
/**
|
|
72
|
-
* Create a valid href for the given pathname.
|
|
73
|
-
*
|
|
74
|
-
* @param pathname
|
|
75
|
-
* @param layer
|
|
76
|
-
*/
|
|
77
|
-
public createHref(
|
|
78
|
-
pathname: HrefLike,
|
|
79
|
-
layer: { path: string } = this.layer,
|
|
80
|
-
options: { params?: Record<string, any> } = {},
|
|
81
|
-
) {
|
|
82
|
-
if (typeof pathname === "object") {
|
|
83
|
-
pathname = pathname.options.path ?? "";
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
if (options.params) {
|
|
87
|
-
for (const [key, value] of Object.entries(options.params)) {
|
|
88
|
-
pathname = pathname.replace(`:${key}`, String(value));
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
return pathname.startsWith("/")
|
|
93
|
-
? pathname
|
|
94
|
-
: `${layer.path}/${pathname}`.replace(/\/\/+/g, "/");
|
|
95
|
-
}
|
|
96
|
-
|
|
97
97
|
public async go(path: string, options?: RouterGoOptions): Promise<void>;
|
|
98
|
-
public async go
|
|
98
|
+
public async go(
|
|
99
99
|
path: keyof VirtualRouter<T>,
|
|
100
100
|
options?: RouterGoOptions,
|
|
101
101
|
): Promise<void>;
|
|
102
|
-
public async go(
|
|
102
|
+
public async go(
|
|
103
|
+
path: string | keyof VirtualRouter<T>,
|
|
104
|
+
options?: RouterGoOptions,
|
|
105
|
+
): Promise<void> {
|
|
103
106
|
for (const page of this.pages) {
|
|
104
107
|
if (page.name === path) {
|
|
105
|
-
|
|
106
|
-
|
|
108
|
+
await this.browser?.go(
|
|
109
|
+
this.path(path as keyof VirtualRouter<T>, options),
|
|
110
|
+
options,
|
|
111
|
+
);
|
|
112
|
+
return;
|
|
107
113
|
}
|
|
108
114
|
}
|
|
109
115
|
|
|
110
|
-
await this.browser?.go(
|
|
116
|
+
await this.browser?.go(path as string, options);
|
|
111
117
|
}
|
|
112
118
|
|
|
113
119
|
public anchor(
|
|
114
120
|
path: string,
|
|
115
121
|
options?: { params?: Record<string, any> },
|
|
116
122
|
): AnchorProps;
|
|
117
|
-
public anchor
|
|
123
|
+
public anchor(
|
|
118
124
|
path: keyof VirtualRouter<T>,
|
|
119
125
|
options?: { params?: Record<string, any> },
|
|
120
126
|
): AnchorProps;
|
|
121
127
|
public anchor(
|
|
122
|
-
path: string
|
|
128
|
+
path: string | keyof VirtualRouter<T>,
|
|
123
129
|
options: { params?: Record<string, any> } = {},
|
|
124
130
|
): AnchorProps {
|
|
131
|
+
let href = path as string;
|
|
132
|
+
|
|
125
133
|
for (const page of this.pages) {
|
|
126
134
|
if (page.name === path) {
|
|
127
|
-
|
|
135
|
+
href = this.path(path as keyof VirtualRouter<T>, options);
|
|
128
136
|
break;
|
|
129
137
|
}
|
|
130
138
|
}
|
|
131
139
|
|
|
132
|
-
const href = this.createHref(path, this.layer, options);
|
|
133
140
|
return {
|
|
134
|
-
href,
|
|
141
|
+
href: this.base(href),
|
|
135
142
|
onClick: (ev: any) => {
|
|
136
143
|
ev.stopPropagation();
|
|
137
144
|
ev.preventDefault();
|
|
138
145
|
|
|
139
|
-
this.go(
|
|
146
|
+
this.go(href, options).catch(console.error);
|
|
140
147
|
},
|
|
141
148
|
};
|
|
142
149
|
}
|
|
143
150
|
|
|
151
|
+
public base(path: string): string {
|
|
152
|
+
const base = import.meta.env?.BASE_URL;
|
|
153
|
+
if (!base || base === "/") {
|
|
154
|
+
return path;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return base + path;
|
|
158
|
+
}
|
|
159
|
+
|
|
144
160
|
/**
|
|
145
161
|
* Set query params.
|
|
146
162
|
*
|
|
@@ -170,8 +186,6 @@ export class RouterHookApi {
|
|
|
170
186
|
}
|
|
171
187
|
}
|
|
172
188
|
|
|
173
|
-
export type HrefLike = string | { options: { path?: string; name?: string } };
|
|
174
|
-
|
|
175
189
|
export type VirtualRouter<T> = {
|
|
176
190
|
[K in keyof T as T[K] extends PageDescriptor ? K : never]: T[K];
|
|
177
191
|
};
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import { createContext } from "react";
|
|
2
|
-
import type {
|
|
3
|
-
PageReactContext,
|
|
4
|
-
RouterState,
|
|
5
|
-
} from "../providers/PageDescriptorProvider.ts";
|
|
6
|
-
|
|
7
|
-
export interface RouterContextValue {
|
|
8
|
-
state: RouterState;
|
|
9
|
-
context: PageReactContext;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export const RouterContext = createContext<RouterContextValue | undefined>(
|
|
13
|
-
undefined,
|
|
14
|
-
);
|