@alepha/react 0.7.3 → 0.7.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/dist/{useRouterState-C2uo0jXu.cjs → ReactBrowserProvider-CXDElhnK.cjs} +2 -239
- package/dist/{useRouterState-D5__ZcUV.js → ReactBrowserProvider-ufHSOTmv.js} +3 -232
- package/dist/index.browser.cjs +14 -30
- package/dist/index.browser.js +10 -12
- package/dist/index.cjs +258 -32
- package/dist/index.d.ts +20 -58
- package/dist/index.js +239 -12
- package/package.json +6 -6
- package/src/index.browser.ts +11 -8
- package/src/index.ts +26 -18
- package/src/providers/ReactBrowserRenderer.ts +1 -0
- package/src/providers/ReactServerProvider.ts +1 -1
package/dist/index.js
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import { t, $logger, $inject, Alepha, $hook, OPTIONS, __bind } from '@alepha/core';
|
|
2
|
-
import { ServerRouterProvider, ServerTimingProvider, ServerLinksProvider, apiLinksResponseSchema,
|
|
3
|
-
import {
|
|
4
|
-
import { P as PageDescriptorProvider, $ as $page } from './
|
|
5
|
-
export { C as ClientOnly, E as ErrorBoundary,
|
|
2
|
+
import { ServerRouterProvider, ServerTimingProvider, ServerLinksProvider, apiLinksResponseSchema, HttpClient, AlephaServer } from '@alepha/server';
|
|
3
|
+
import { AlephaServerCache } from '@alepha/server-cache';
|
|
4
|
+
import { P as PageDescriptorProvider, $ as $page, R as RouterContext, a as RouterLayerContext, b as ReactBrowserProvider, u as useRouterEvents } from './ReactBrowserProvider-ufHSOTmv.js';
|
|
5
|
+
export { C as ClientOnly, E as ErrorBoundary, N as NestedView, c as RedirectionError, i as isPageRoute, d as useAlepha } from './ReactBrowserProvider-ufHSOTmv.js';
|
|
6
6
|
import { existsSync } from 'node:fs';
|
|
7
7
|
import { join } from 'node:path';
|
|
8
8
|
import { ServerStaticProvider } from '@alepha/server-static';
|
|
9
9
|
import { renderToString } from 'react-dom/server';
|
|
10
|
-
import 'react/jsx-runtime';
|
|
11
|
-
import 'react';
|
|
10
|
+
import { jsx } from 'react/jsx-runtime';
|
|
11
|
+
import React, { useContext, useMemo, useState, useEffect } from 'react';
|
|
12
12
|
import '@alepha/router';
|
|
13
13
|
|
|
14
14
|
class ServerHeadProvider {
|
|
@@ -353,12 +353,239 @@ class ReactServerProvider {
|
|
|
353
353
|
}
|
|
354
354
|
}
|
|
355
355
|
|
|
356
|
-
class
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
this.
|
|
356
|
+
class RouterHookApi {
|
|
357
|
+
constructor(pages, state, layer, browser) {
|
|
358
|
+
this.pages = pages;
|
|
359
|
+
this.state = state;
|
|
360
|
+
this.layer = layer;
|
|
361
|
+
this.browser = browser;
|
|
362
|
+
}
|
|
363
|
+
get current() {
|
|
364
|
+
return this.state;
|
|
365
|
+
}
|
|
366
|
+
get pathname() {
|
|
367
|
+
return this.state.pathname;
|
|
368
|
+
}
|
|
369
|
+
get query() {
|
|
370
|
+
const query = {};
|
|
371
|
+
for (const [key, value] of new URLSearchParams(
|
|
372
|
+
this.state.search
|
|
373
|
+
).entries()) {
|
|
374
|
+
query[key] = String(value);
|
|
375
|
+
}
|
|
376
|
+
return query;
|
|
377
|
+
}
|
|
378
|
+
async back() {
|
|
379
|
+
this.browser?.history.back();
|
|
380
|
+
}
|
|
381
|
+
async forward() {
|
|
382
|
+
this.browser?.history.forward();
|
|
383
|
+
}
|
|
384
|
+
async invalidate(props) {
|
|
385
|
+
await this.browser?.invalidate(props);
|
|
386
|
+
}
|
|
387
|
+
/**
|
|
388
|
+
* Create a valid href for the given pathname.
|
|
389
|
+
*
|
|
390
|
+
* @param pathname
|
|
391
|
+
* @param layer
|
|
392
|
+
*/
|
|
393
|
+
createHref(pathname, layer = this.layer, options = {}) {
|
|
394
|
+
if (typeof pathname === "object") {
|
|
395
|
+
pathname = pathname.options.path ?? "";
|
|
396
|
+
}
|
|
397
|
+
if (options.params) {
|
|
398
|
+
for (const [key, value] of Object.entries(options.params)) {
|
|
399
|
+
pathname = pathname.replace(`:${key}`, String(value));
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
return pathname.startsWith("/") ? pathname : `${layer.path}/${pathname}`.replace(/\/\/+/g, "/");
|
|
403
|
+
}
|
|
404
|
+
async go(path, options) {
|
|
405
|
+
for (const page of this.pages) {
|
|
406
|
+
if (page.name === path) {
|
|
407
|
+
path = page.path ?? "";
|
|
408
|
+
break;
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
await this.browser?.go(this.createHref(path, this.layer, options), options);
|
|
412
|
+
}
|
|
413
|
+
anchor(path, options = {}) {
|
|
414
|
+
for (const page of this.pages) {
|
|
415
|
+
if (page.name === path) {
|
|
416
|
+
path = page.path ?? "";
|
|
417
|
+
break;
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
const href = this.createHref(path, this.layer, options);
|
|
421
|
+
return {
|
|
422
|
+
href,
|
|
423
|
+
onClick: (ev) => {
|
|
424
|
+
ev.stopPropagation();
|
|
425
|
+
ev.preventDefault();
|
|
426
|
+
this.go(path, options).catch(console.error);
|
|
427
|
+
}
|
|
428
|
+
};
|
|
429
|
+
}
|
|
430
|
+
/**
|
|
431
|
+
* Set query params.
|
|
432
|
+
*
|
|
433
|
+
* @param record
|
|
434
|
+
* @param options
|
|
435
|
+
*/
|
|
436
|
+
setQueryParams(record, options = {}) {
|
|
437
|
+
const func = typeof record === "function" ? record : () => record;
|
|
438
|
+
const search = new URLSearchParams(func(this.query)).toString();
|
|
439
|
+
const state = search ? `${this.pathname}?${search}` : this.pathname;
|
|
440
|
+
if (options.push) {
|
|
441
|
+
window.history.pushState({}, "", state);
|
|
442
|
+
} else {
|
|
443
|
+
window.history.replaceState({}, "", state);
|
|
444
|
+
}
|
|
360
445
|
}
|
|
361
446
|
}
|
|
362
|
-
__bind($page, ReactModule);
|
|
363
447
|
|
|
364
|
-
|
|
448
|
+
const useRouter = () => {
|
|
449
|
+
const ctx = useContext(RouterContext);
|
|
450
|
+
const layer = useContext(RouterLayerContext);
|
|
451
|
+
if (!ctx || !layer) {
|
|
452
|
+
throw new Error("useRouter must be used within a RouterProvider");
|
|
453
|
+
}
|
|
454
|
+
const pages = useMemo(() => {
|
|
455
|
+
return ctx.alepha.get(PageDescriptorProvider).getPages();
|
|
456
|
+
}, []);
|
|
457
|
+
return useMemo(
|
|
458
|
+
() => new RouterHookApi(
|
|
459
|
+
pages,
|
|
460
|
+
ctx.state,
|
|
461
|
+
layer,
|
|
462
|
+
ctx.alepha.isBrowser() ? ctx.alepha.get(ReactBrowserProvider) : void 0
|
|
463
|
+
),
|
|
464
|
+
[layer]
|
|
465
|
+
);
|
|
466
|
+
};
|
|
467
|
+
|
|
468
|
+
const Link = (props) => {
|
|
469
|
+
React.useContext(RouterContext);
|
|
470
|
+
const router = useRouter();
|
|
471
|
+
const to = typeof props.to === "string" ? props.to : props.to[OPTIONS].path;
|
|
472
|
+
if (!to) {
|
|
473
|
+
return null;
|
|
474
|
+
}
|
|
475
|
+
const can = typeof props.to === "string" ? void 0 : props.to[OPTIONS].can;
|
|
476
|
+
if (can && !can()) {
|
|
477
|
+
return null;
|
|
478
|
+
}
|
|
479
|
+
const name = typeof props.to === "string" ? void 0 : props.to[OPTIONS].name;
|
|
480
|
+
const anchorProps = {
|
|
481
|
+
...props,
|
|
482
|
+
to: void 0
|
|
483
|
+
};
|
|
484
|
+
return /* @__PURE__ */ jsx("a", { ...router.anchor(to), ...anchorProps, children: props.children ?? name });
|
|
485
|
+
};
|
|
486
|
+
|
|
487
|
+
const useActive = (path) => {
|
|
488
|
+
const router = useRouter();
|
|
489
|
+
const ctx = useContext(RouterContext);
|
|
490
|
+
const layer = useContext(RouterLayerContext);
|
|
491
|
+
if (!ctx || !layer) {
|
|
492
|
+
throw new Error("useRouter must be used within a RouterProvider");
|
|
493
|
+
}
|
|
494
|
+
let name;
|
|
495
|
+
if (typeof path === "object" && path.options.name) {
|
|
496
|
+
name = path.options.name;
|
|
497
|
+
}
|
|
498
|
+
const [current, setCurrent] = useState(ctx.state.pathname);
|
|
499
|
+
const href = useMemo(() => router.createHref(path, layer), [path, layer]);
|
|
500
|
+
const [isPending, setPending] = useState(false);
|
|
501
|
+
const isActive = current === href;
|
|
502
|
+
useRouterEvents({
|
|
503
|
+
onEnd: ({ state }) => setCurrent(state.pathname)
|
|
504
|
+
});
|
|
505
|
+
return {
|
|
506
|
+
name,
|
|
507
|
+
isPending,
|
|
508
|
+
isActive,
|
|
509
|
+
anchorProps: {
|
|
510
|
+
href,
|
|
511
|
+
onClick: (ev) => {
|
|
512
|
+
ev.stopPropagation();
|
|
513
|
+
ev.preventDefault();
|
|
514
|
+
if (isActive) return;
|
|
515
|
+
if (isPending) return;
|
|
516
|
+
setPending(true);
|
|
517
|
+
router.go(href).then(() => {
|
|
518
|
+
setPending(false);
|
|
519
|
+
});
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
};
|
|
523
|
+
};
|
|
524
|
+
|
|
525
|
+
const useInject = (clazz) => {
|
|
526
|
+
const ctx = useContext(RouterContext);
|
|
527
|
+
if (!ctx) {
|
|
528
|
+
throw new Error("useRouter must be used within a <RouterProvider>");
|
|
529
|
+
}
|
|
530
|
+
return useMemo(() => ctx.alepha.get(clazz), []);
|
|
531
|
+
};
|
|
532
|
+
|
|
533
|
+
const useClient = (_scope) => {
|
|
534
|
+
return useInject(HttpClient).of();
|
|
535
|
+
};
|
|
536
|
+
|
|
537
|
+
const useQueryParams = (schema, options = {}) => {
|
|
538
|
+
const ctx = useContext(RouterContext);
|
|
539
|
+
if (!ctx) {
|
|
540
|
+
throw new Error("useQueryParams must be used within a RouterProvider");
|
|
541
|
+
}
|
|
542
|
+
const key = options.key ?? "q";
|
|
543
|
+
const router = useRouter();
|
|
544
|
+
const querystring = router.query[key];
|
|
545
|
+
const [queryParams, setQueryParams] = useState(
|
|
546
|
+
decode(ctx.alepha, schema, router.query[key])
|
|
547
|
+
);
|
|
548
|
+
useEffect(() => {
|
|
549
|
+
setQueryParams(decode(ctx.alepha, schema, querystring));
|
|
550
|
+
}, [querystring]);
|
|
551
|
+
return [
|
|
552
|
+
queryParams,
|
|
553
|
+
(queryParams2) => {
|
|
554
|
+
setQueryParams(queryParams2);
|
|
555
|
+
router.setQueryParams((data) => {
|
|
556
|
+
return { ...data, [key]: encode(ctx.alepha, schema, queryParams2) };
|
|
557
|
+
});
|
|
558
|
+
}
|
|
559
|
+
];
|
|
560
|
+
};
|
|
561
|
+
const encode = (alepha, schema, data) => {
|
|
562
|
+
return btoa(JSON.stringify(alepha.parse(schema, data)));
|
|
563
|
+
};
|
|
564
|
+
const decode = (alepha, schema, data) => {
|
|
565
|
+
try {
|
|
566
|
+
return alepha.parse(schema, JSON.parse(atob(decodeURIComponent(data))));
|
|
567
|
+
} catch (_error) {
|
|
568
|
+
return {};
|
|
569
|
+
}
|
|
570
|
+
};
|
|
571
|
+
|
|
572
|
+
const useRouterState = () => {
|
|
573
|
+
const ctx = useContext(RouterContext);
|
|
574
|
+
const layer = useContext(RouterLayerContext);
|
|
575
|
+
if (!ctx || !layer) {
|
|
576
|
+
throw new Error("useRouter must be used within a RouterProvider");
|
|
577
|
+
}
|
|
578
|
+
const [state, setState] = useState(ctx.state);
|
|
579
|
+
useRouterEvents({
|
|
580
|
+
onEnd: ({ state: state2 }) => setState({ ...state2 })
|
|
581
|
+
});
|
|
582
|
+
return state;
|
|
583
|
+
};
|
|
584
|
+
|
|
585
|
+
class AlephaReact {
|
|
586
|
+
name = "alepha.react";
|
|
587
|
+
$services = (alepha) => alepha.with(AlephaServer).with(AlephaServerCache).with(ReactServerProvider).with(PageDescriptorProvider);
|
|
588
|
+
}
|
|
589
|
+
__bind($page, AlephaReact);
|
|
590
|
+
|
|
591
|
+
export { $page, AlephaReact, Link, PageDescriptorProvider, ReactBrowserProvider, ReactServerProvider, RouterContext, RouterHookApi, RouterLayerContext, useActive, useClient, useInject, useQueryParams, useRouter, useRouterEvents, useRouterState };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@alepha/react",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.4",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -13,11 +13,11 @@
|
|
|
13
13
|
"src"
|
|
14
14
|
],
|
|
15
15
|
"dependencies": {
|
|
16
|
-
"@alepha/core": "0.7.
|
|
17
|
-
"@alepha/router": "0.7.
|
|
18
|
-
"@alepha/server": "0.7.
|
|
19
|
-
"@alepha/server-cache": "0.7.
|
|
20
|
-
"@alepha/server-static": "0.7.
|
|
16
|
+
"@alepha/core": "0.7.4",
|
|
17
|
+
"@alepha/router": "0.7.4",
|
|
18
|
+
"@alepha/server": "0.7.4",
|
|
19
|
+
"@alepha/server-cache": "0.7.4",
|
|
20
|
+
"@alepha/server-static": "0.7.4",
|
|
21
21
|
"react-dom": "^19.1.0"
|
|
22
22
|
},
|
|
23
23
|
"devDependencies": {
|
package/src/index.browser.ts
CHANGED
|
@@ -1,23 +1,26 @@
|
|
|
1
|
-
import { __bind,
|
|
1
|
+
import { __bind, type Alepha, type Module } from "@alepha/core";
|
|
2
2
|
import { $page } from "./descriptors/$page.ts";
|
|
3
3
|
import { BrowserRouterProvider } from "./providers/BrowserRouterProvider.ts";
|
|
4
4
|
import { PageDescriptorProvider } from "./providers/PageDescriptorProvider.ts";
|
|
5
5
|
import { ReactBrowserProvider } from "./providers/ReactBrowserProvider.ts";
|
|
6
6
|
import { ReactBrowserRenderer } from "./providers/ReactBrowserRenderer.ts";
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
// ---------------------------------------------------------------------------------------------------------------------
|
|
9
|
+
|
|
10
|
+
export * from "./providers/BrowserRouterProvider.ts";
|
|
11
|
+
export * from "./providers/PageDescriptorProvider.ts";
|
|
9
12
|
export * from "./providers/ReactBrowserProvider.ts";
|
|
10
13
|
|
|
11
|
-
|
|
12
|
-
protected readonly alepha = $inject(Alepha);
|
|
14
|
+
// ---------------------------------------------------------------------------------------------------------------------
|
|
13
15
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
+
export class AlephaReact implements Module {
|
|
17
|
+
public readonly name = "alepha.react";
|
|
18
|
+
public readonly $services = (alepha: Alepha) =>
|
|
19
|
+
alepha
|
|
16
20
|
.with(PageDescriptorProvider)
|
|
17
21
|
.with(ReactBrowserProvider)
|
|
18
22
|
.with(BrowserRouterProvider)
|
|
19
23
|
.with(ReactBrowserRenderer);
|
|
20
|
-
}
|
|
21
24
|
}
|
|
22
25
|
|
|
23
|
-
__bind($page,
|
|
26
|
+
__bind($page, AlephaReact);
|
package/src/index.ts
CHANGED
|
@@ -1,10 +1,6 @@
|
|
|
1
|
-
import { __bind,
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
ServerModule,
|
|
5
|
-
type ServerRequest,
|
|
6
|
-
} from "@alepha/server";
|
|
7
|
-
import { ServerCacheModule } from "@alepha/server-cache";
|
|
1
|
+
import { __bind, type Alepha, type Module } from "@alepha/core";
|
|
2
|
+
import { AlephaServer, type ServerRequest } from "@alepha/server";
|
|
3
|
+
import { AlephaServerCache } from "@alepha/server-cache";
|
|
8
4
|
import { $page } from "./descriptors/$page.ts";
|
|
9
5
|
import {
|
|
10
6
|
PageDescriptorProvider,
|
|
@@ -15,6 +11,8 @@ import {
|
|
|
15
11
|
import type { ReactHydrationState } from "./providers/ReactBrowserProvider.ts";
|
|
16
12
|
import { ReactServerProvider } from "./providers/ReactServerProvider.ts";
|
|
17
13
|
|
|
14
|
+
// ---------------------------------------------------------------------------------------------------------------------
|
|
15
|
+
|
|
18
16
|
export { default as NestedView } from "./components/NestedView.tsx";
|
|
19
17
|
export * from "./errors/RedirectionError.ts";
|
|
20
18
|
export * from "./index.shared.ts";
|
|
@@ -22,6 +20,8 @@ export * from "./providers/PageDescriptorProvider.ts";
|
|
|
22
20
|
export * from "./providers/ReactBrowserProvider.ts";
|
|
23
21
|
export * from "./providers/ReactServerProvider.ts";
|
|
24
22
|
|
|
23
|
+
// ---------------------------------------------------------------------------------------------------------------------
|
|
24
|
+
|
|
25
25
|
declare module "@alepha/core" {
|
|
26
26
|
interface Hooks {
|
|
27
27
|
"react:browser:render": {
|
|
@@ -52,17 +52,25 @@ declare module "@alepha/core" {
|
|
|
52
52
|
}
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
-
|
|
56
|
-
protected readonly alepha = $inject(Alepha);
|
|
55
|
+
// ---------------------------------------------------------------------------------------------------------------------
|
|
57
56
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
57
|
+
/**
|
|
58
|
+
* Alepha React Module
|
|
59
|
+
*
|
|
60
|
+
* Alepha React Module contains a router for client-side navigation and server-side rendering.
|
|
61
|
+
* Routes can be defined using the `$page` descriptor.
|
|
62
|
+
*
|
|
63
|
+
* @see {@link $page}
|
|
64
|
+
* @module alepha.react
|
|
65
|
+
*/
|
|
66
|
+
export class AlephaReact implements Module {
|
|
67
|
+
public readonly name = "alepha.react";
|
|
68
|
+
public readonly $services = (alepha: Alepha) =>
|
|
69
|
+
alepha
|
|
70
|
+
.with(AlephaServer)
|
|
71
|
+
.with(AlephaServerCache)
|
|
72
|
+
.with(ReactServerProvider)
|
|
73
|
+
.with(PageDescriptorProvider);
|
|
66
74
|
}
|
|
67
75
|
|
|
68
|
-
__bind($page,
|
|
76
|
+
__bind($page, AlephaReact);
|
|
@@ -17,6 +17,7 @@ declare module "@alepha/core" {
|
|
|
17
17
|
interface Env extends Partial<Static<typeof envSchema>> {}
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
+
// TODO: move to ReactBrowserProvider when it will be removed from server-side imports
|
|
20
21
|
export class ReactBrowserRenderer {
|
|
21
22
|
protected readonly browserProvider = $inject(ReactBrowserProvider);
|
|
22
23
|
protected readonly browserRouterProvider = $inject(BrowserRouterProvider);
|
|
@@ -29,7 +29,7 @@ import {
|
|
|
29
29
|
import type { ReactHydrationState } from "./ReactBrowserProvider.ts";
|
|
30
30
|
import { ServerHeadProvider } from "./ServerHeadProvider.ts";
|
|
31
31
|
|
|
32
|
-
|
|
32
|
+
const envSchema = t.object({
|
|
33
33
|
REACT_SERVER_DIST: t.string({ default: "public" }),
|
|
34
34
|
REACT_SERVER_PREFIX: t.string({ default: "" }),
|
|
35
35
|
REACT_SSR_ENABLED: t.optional(t.boolean()),
|