@alepha/react 0.8.0 → 0.9.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/README.md +32 -1
- package/dist/index.browser.js +88 -45
- package/dist/index.browser.js.map +1 -1
- package/dist/index.cjs +277 -247
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +70 -72
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +71 -73
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +282 -249
- package/dist/index.js.map +1 -1
- package/package.json +9 -8
- package/src/components/Link.tsx +3 -5
- package/src/components/NestedView.tsx +3 -1
- package/src/descriptors/$page.ts +39 -58
- package/src/hooks/RouterHookApi.ts +17 -0
- package/src/hooks/useInject.ts +1 -1
- package/src/hooks/useRouter.ts +3 -2
- package/src/index.browser.ts +13 -8
- package/src/index.shared.ts +1 -0
- package/src/index.ts +16 -12
- package/src/providers/PageDescriptorProvider.ts +49 -36
- package/src/providers/ReactBrowserProvider.ts +37 -4
- package/src/providers/ReactBrowserRenderer.ts +22 -2
- package/src/providers/ReactServerProvider.ts +13 -15
package/dist/index.cjs
CHANGED
|
@@ -27,28 +27,32 @@ const __alepha_server_cache = __toESM(require("@alepha/server-cache"));
|
|
|
27
27
|
const __alepha_server_links = __toESM(require("@alepha/server-links"));
|
|
28
28
|
const react = __toESM(require("react"));
|
|
29
29
|
const react_jsx_runtime = __toESM(require("react/jsx-runtime"));
|
|
30
|
+
const __alepha_router = __toESM(require("@alepha/router"));
|
|
30
31
|
const node_fs = __toESM(require("node:fs"));
|
|
31
32
|
const node_path = __toESM(require("node:path"));
|
|
32
33
|
const __alepha_server_static = __toESM(require("@alepha/server-static"));
|
|
33
34
|
const react_dom_server = __toESM(require("react-dom/server"));
|
|
34
|
-
const __alepha_router = __toESM(require("@alepha/router"));
|
|
35
35
|
|
|
36
36
|
//#region src/descriptors/$page.ts
|
|
37
|
-
const KEY = "PAGE";
|
|
38
37
|
/**
|
|
39
38
|
* Main descriptor for defining a React route in the application.
|
|
40
39
|
*/
|
|
41
40
|
const $page = (options) => {
|
|
42
|
-
(0, __alepha_core.
|
|
43
|
-
return {
|
|
44
|
-
[__alepha_core.KIND]: KEY,
|
|
45
|
-
[__alepha_core.OPTIONS]: options,
|
|
46
|
-
render: () => {
|
|
47
|
-
throw new __alepha_core.NotImplementedError(KEY);
|
|
48
|
-
}
|
|
49
|
-
};
|
|
41
|
+
return (0, __alepha_core.createDescriptor)(PageDescriptor, options);
|
|
50
42
|
};
|
|
51
|
-
|
|
43
|
+
var PageDescriptor = class extends __alepha_core.Descriptor {
|
|
44
|
+
get name() {
|
|
45
|
+
return this.options.name ?? this.config.propertyKey;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* For testing or build purposes, this will render the page (with or without the HTML layout) and return the HTML and context.
|
|
49
|
+
* Only valid for server-side rendering, it will throw an error if called on the client-side.
|
|
50
|
+
*/
|
|
51
|
+
async render(options) {
|
|
52
|
+
throw new __alepha_core.NotImplementedError("");
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
$page[__alepha_core.KIND] = PageDescriptor;
|
|
52
56
|
|
|
53
57
|
//#endregion
|
|
54
58
|
//#region src/components/ClientOnly.tsx
|
|
@@ -314,7 +318,7 @@ const NestedView = (props) => {
|
|
|
314
318
|
const index = layer?.index ?? 0;
|
|
315
319
|
const [view, setView] = (0, react.useState)(app?.state.layers[index]?.element);
|
|
316
320
|
useRouterEvents({ onEnd: ({ state }) => {
|
|
317
|
-
setView(state.layers[index]?.element);
|
|
321
|
+
if (!state.layers[index]?.cache) setView(state.layers[index]?.element);
|
|
318
322
|
} }, [app]);
|
|
319
323
|
if (!app) throw new Error("NestedView must be used within a RouterContext.");
|
|
320
324
|
const element = view ?? props.children ?? null;
|
|
@@ -364,7 +368,7 @@ var RedirectionError = class extends Error {
|
|
|
364
368
|
const envSchema$1 = __alepha_core.t.object({ REACT_STRICT_MODE: __alepha_core.t.boolean({ default: true }) });
|
|
365
369
|
var PageDescriptorProvider = class {
|
|
366
370
|
log = (0, __alepha_core.$logger)();
|
|
367
|
-
env = (0, __alepha_core.$
|
|
371
|
+
env = (0, __alepha_core.$env)(envSchema$1);
|
|
368
372
|
alepha = (0, __alepha_core.$inject)(__alepha_core.Alepha);
|
|
369
373
|
pages = [];
|
|
370
374
|
getPages() {
|
|
@@ -412,19 +416,18 @@ var PageDescriptorProvider = class {
|
|
|
412
416
|
const route$1 = it.route;
|
|
413
417
|
const config = {};
|
|
414
418
|
try {
|
|
415
|
-
config.query = route$1.schema?.query ? this.alepha.parse(route$1.schema.query, request.query) :
|
|
419
|
+
config.query = route$1.schema?.query ? this.alepha.parse(route$1.schema.query, request.query) : {};
|
|
416
420
|
} catch (e) {
|
|
417
421
|
it.error = e;
|
|
418
422
|
break;
|
|
419
423
|
}
|
|
420
424
|
try {
|
|
421
|
-
config.params = route$1.schema?.params ? this.alepha.parse(route$1.schema.params, request.params) :
|
|
425
|
+
config.params = route$1.schema?.params ? this.alepha.parse(route$1.schema.params, request.params) : {};
|
|
422
426
|
} catch (e) {
|
|
423
427
|
it.error = e;
|
|
424
428
|
break;
|
|
425
429
|
}
|
|
426
430
|
it.config = { ...config };
|
|
427
|
-
if (!route$1.resolve) continue;
|
|
428
431
|
const previous = request.previous;
|
|
429
432
|
if (previous?.[i] && !forceRefresh && previous[i].name === route$1.name) {
|
|
430
433
|
const url = (str) => str ? str.replace(/\/\/+/g, "/") : "/";
|
|
@@ -439,6 +442,7 @@ var PageDescriptorProvider = class {
|
|
|
439
442
|
if (prev === curr) {
|
|
440
443
|
it.props = previous[i].props;
|
|
441
444
|
it.error = previous[i].error;
|
|
445
|
+
it.cache = true;
|
|
442
446
|
context = {
|
|
443
447
|
...context,
|
|
444
448
|
...it.props
|
|
@@ -447,6 +451,7 @@ var PageDescriptorProvider = class {
|
|
|
447
451
|
}
|
|
448
452
|
forceRefresh = true;
|
|
449
453
|
}
|
|
454
|
+
if (!route$1.resolve) continue;
|
|
450
455
|
try {
|
|
451
456
|
const props = await route$1.resolve?.({
|
|
452
457
|
...request,
|
|
@@ -493,7 +498,7 @@ var PageDescriptorProvider = class {
|
|
|
493
498
|
element: this.renderView(i + 1, path, element$1, it.route),
|
|
494
499
|
index: i + 1,
|
|
495
500
|
path,
|
|
496
|
-
route
|
|
501
|
+
route: it.route
|
|
497
502
|
});
|
|
498
503
|
break;
|
|
499
504
|
}
|
|
@@ -509,7 +514,8 @@ var PageDescriptorProvider = class {
|
|
|
509
514
|
element: this.renderView(i + 1, path, element, it.route),
|
|
510
515
|
index: i + 1,
|
|
511
516
|
path,
|
|
512
|
-
route
|
|
517
|
+
route: it.route,
|
|
518
|
+
cache: it.cache
|
|
513
519
|
});
|
|
514
520
|
}
|
|
515
521
|
return {
|
|
@@ -568,18 +574,17 @@ var PageDescriptorProvider = class {
|
|
|
568
574
|
on: "configure",
|
|
569
575
|
handler: () => {
|
|
570
576
|
let hasNotFoundHandler = false;
|
|
571
|
-
const pages = this.alepha.
|
|
577
|
+
const pages = this.alepha.descriptors($page);
|
|
572
578
|
const hasParent = (it) => {
|
|
573
579
|
for (const page of pages) {
|
|
574
|
-
const children = page.
|
|
580
|
+
const children = page.options.children ? Array.isArray(page.options.children) ? page.options.children : page.options.children() : [];
|
|
575
581
|
if (children.includes(it)) return true;
|
|
576
582
|
}
|
|
577
583
|
};
|
|
578
|
-
for (const
|
|
579
|
-
|
|
580
|
-
if (hasParent(
|
|
581
|
-
|
|
582
|
-
this.add(this.map(pages, value));
|
|
584
|
+
for (const page of pages) {
|
|
585
|
+
if (page.options.path === "/*") hasNotFoundHandler = true;
|
|
586
|
+
if (hasParent(page)) continue;
|
|
587
|
+
this.add(this.map(pages, page));
|
|
583
588
|
}
|
|
584
589
|
if (!hasNotFoundHandler && pages.length > 0) this.add({
|
|
585
590
|
path: "/*",
|
|
@@ -593,9 +598,10 @@ var PageDescriptorProvider = class {
|
|
|
593
598
|
}
|
|
594
599
|
});
|
|
595
600
|
map(pages, target) {
|
|
596
|
-
const children = target
|
|
601
|
+
const children = target.options.children ? Array.isArray(target.options.children) ? target.options.children : target.options.children() : [];
|
|
597
602
|
return {
|
|
598
|
-
...target
|
|
603
|
+
...target.options,
|
|
604
|
+
name: target.name,
|
|
599
605
|
parent: void 0,
|
|
600
606
|
children: children.map((it) => this.map(pages, it))
|
|
601
607
|
};
|
|
@@ -632,6 +638,209 @@ const isPageRoute = (it) => {
|
|
|
632
638
|
return it && typeof it === "object" && typeof it.path === "string" && typeof it.page === "object";
|
|
633
639
|
};
|
|
634
640
|
|
|
641
|
+
//#endregion
|
|
642
|
+
//#region src/providers/BrowserRouterProvider.ts
|
|
643
|
+
var BrowserRouterProvider = class extends __alepha_router.RouterProvider {
|
|
644
|
+
log = (0, __alepha_core.$logger)();
|
|
645
|
+
alepha = (0, __alepha_core.$inject)(__alepha_core.Alepha);
|
|
646
|
+
pageDescriptorProvider = (0, __alepha_core.$inject)(PageDescriptorProvider);
|
|
647
|
+
add(entry) {
|
|
648
|
+
this.pageDescriptorProvider.add(entry);
|
|
649
|
+
}
|
|
650
|
+
configure = (0, __alepha_core.$hook)({
|
|
651
|
+
on: "configure",
|
|
652
|
+
handler: async () => {
|
|
653
|
+
for (const page of this.pageDescriptorProvider.getPages()) if (page.component || page.lazy) this.push({
|
|
654
|
+
path: page.match,
|
|
655
|
+
page
|
|
656
|
+
});
|
|
657
|
+
}
|
|
658
|
+
});
|
|
659
|
+
async transition(url, options = {}) {
|
|
660
|
+
const { pathname, search } = url;
|
|
661
|
+
const state = {
|
|
662
|
+
pathname,
|
|
663
|
+
search,
|
|
664
|
+
layers: []
|
|
665
|
+
};
|
|
666
|
+
const context = {
|
|
667
|
+
url,
|
|
668
|
+
query: {},
|
|
669
|
+
params: {},
|
|
670
|
+
onError: () => null,
|
|
671
|
+
...options.context ?? {}
|
|
672
|
+
};
|
|
673
|
+
await this.alepha.emit("react:transition:begin", {
|
|
674
|
+
state,
|
|
675
|
+
context
|
|
676
|
+
});
|
|
677
|
+
try {
|
|
678
|
+
const previous = options.previous;
|
|
679
|
+
const { route, params } = this.match(pathname);
|
|
680
|
+
const query = {};
|
|
681
|
+
if (search) for (const [key, value] of new URLSearchParams(search).entries()) query[key] = String(value);
|
|
682
|
+
context.query = query;
|
|
683
|
+
context.params = params ?? {};
|
|
684
|
+
context.previous = previous;
|
|
685
|
+
if (isPageRoute(route)) {
|
|
686
|
+
const result = await this.pageDescriptorProvider.createLayers(route.page, context);
|
|
687
|
+
if (result.redirect) return {
|
|
688
|
+
redirect: result.redirect,
|
|
689
|
+
state,
|
|
690
|
+
context
|
|
691
|
+
};
|
|
692
|
+
state.layers = result.layers;
|
|
693
|
+
}
|
|
694
|
+
if (state.layers.length === 0) state.layers.push({
|
|
695
|
+
name: "not-found",
|
|
696
|
+
element: (0, react.createElement)(NotFoundPage),
|
|
697
|
+
index: 0,
|
|
698
|
+
path: "/"
|
|
699
|
+
});
|
|
700
|
+
await this.alepha.emit("react:transition:success", {
|
|
701
|
+
state,
|
|
702
|
+
context
|
|
703
|
+
});
|
|
704
|
+
} catch (e) {
|
|
705
|
+
this.log.error(e);
|
|
706
|
+
state.layers = [{
|
|
707
|
+
name: "error",
|
|
708
|
+
element: this.pageDescriptorProvider.renderError(e),
|
|
709
|
+
index: 0,
|
|
710
|
+
path: "/"
|
|
711
|
+
}];
|
|
712
|
+
await this.alepha.emit("react:transition:error", {
|
|
713
|
+
error: e,
|
|
714
|
+
state,
|
|
715
|
+
context
|
|
716
|
+
});
|
|
717
|
+
}
|
|
718
|
+
if (options.state) {
|
|
719
|
+
options.state.layers = state.layers;
|
|
720
|
+
options.state.pathname = state.pathname;
|
|
721
|
+
options.state.search = state.search;
|
|
722
|
+
}
|
|
723
|
+
await this.alepha.emit("react:transition:end", {
|
|
724
|
+
state: options.state,
|
|
725
|
+
context
|
|
726
|
+
});
|
|
727
|
+
return {
|
|
728
|
+
context,
|
|
729
|
+
state
|
|
730
|
+
};
|
|
731
|
+
}
|
|
732
|
+
root(state, context) {
|
|
733
|
+
return this.pageDescriptorProvider.root(state, context);
|
|
734
|
+
}
|
|
735
|
+
};
|
|
736
|
+
|
|
737
|
+
//#endregion
|
|
738
|
+
//#region src/providers/ReactBrowserProvider.ts
|
|
739
|
+
var ReactBrowserProvider = class {
|
|
740
|
+
log = (0, __alepha_core.$logger)();
|
|
741
|
+
client = (0, __alepha_core.$inject)(__alepha_server_links.LinkProvider);
|
|
742
|
+
alepha = (0, __alepha_core.$inject)(__alepha_core.Alepha);
|
|
743
|
+
router = (0, __alepha_core.$inject)(BrowserRouterProvider);
|
|
744
|
+
root;
|
|
745
|
+
transitioning;
|
|
746
|
+
state = {
|
|
747
|
+
layers: [],
|
|
748
|
+
pathname: "",
|
|
749
|
+
search: ""
|
|
750
|
+
};
|
|
751
|
+
get document() {
|
|
752
|
+
return window.document;
|
|
753
|
+
}
|
|
754
|
+
get history() {
|
|
755
|
+
return window.history;
|
|
756
|
+
}
|
|
757
|
+
get location() {
|
|
758
|
+
return window.location;
|
|
759
|
+
}
|
|
760
|
+
get url() {
|
|
761
|
+
let url = this.location.pathname + this.location.search;
|
|
762
|
+
return url;
|
|
763
|
+
}
|
|
764
|
+
pushState(url, replace) {
|
|
765
|
+
let path = url;
|
|
766
|
+
if (replace) this.history.replaceState({}, "", path);
|
|
767
|
+
else this.history.pushState({}, "", path);
|
|
768
|
+
}
|
|
769
|
+
async invalidate(props) {
|
|
770
|
+
const previous = [];
|
|
771
|
+
if (props) {
|
|
772
|
+
const [key] = Object.keys(props);
|
|
773
|
+
const value = props[key];
|
|
774
|
+
for (const layer of this.state.layers) {
|
|
775
|
+
if (layer.props?.[key]) {
|
|
776
|
+
previous.push({
|
|
777
|
+
...layer,
|
|
778
|
+
props: {
|
|
779
|
+
...layer.props,
|
|
780
|
+
[key]: value
|
|
781
|
+
}
|
|
782
|
+
});
|
|
783
|
+
break;
|
|
784
|
+
}
|
|
785
|
+
previous.push(layer);
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
await this.render({ previous });
|
|
789
|
+
}
|
|
790
|
+
async go(url, options = {}) {
|
|
791
|
+
const result = await this.render({ url });
|
|
792
|
+
if (result.context.url.pathname !== url) {
|
|
793
|
+
this.pushState(result.context.url.pathname);
|
|
794
|
+
return;
|
|
795
|
+
}
|
|
796
|
+
if (options.replace) {
|
|
797
|
+
this.pushState(url);
|
|
798
|
+
return;
|
|
799
|
+
}
|
|
800
|
+
this.pushState(url);
|
|
801
|
+
}
|
|
802
|
+
async render(options = {}) {
|
|
803
|
+
const previous = options.previous ?? this.state.layers;
|
|
804
|
+
const url = options.url ?? this.url;
|
|
805
|
+
this.transitioning = { to: url };
|
|
806
|
+
const result = await this.router.transition(new URL(`http://localhost${url}`), {
|
|
807
|
+
previous,
|
|
808
|
+
state: this.state
|
|
809
|
+
});
|
|
810
|
+
if (result.redirect) return await this.render({ url: result.redirect });
|
|
811
|
+
this.transitioning = void 0;
|
|
812
|
+
return result;
|
|
813
|
+
}
|
|
814
|
+
/**
|
|
815
|
+
* Get embedded layers from the server.
|
|
816
|
+
*/
|
|
817
|
+
getHydrationState() {
|
|
818
|
+
try {
|
|
819
|
+
if ("__ssr" in window && typeof window.__ssr === "object") return window.__ssr;
|
|
820
|
+
} catch (error) {
|
|
821
|
+
console.error(error);
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
ready = (0, __alepha_core.$hook)({
|
|
825
|
+
on: "ready",
|
|
826
|
+
handler: async () => {
|
|
827
|
+
const hydration = this.getHydrationState();
|
|
828
|
+
const previous = hydration?.layers ?? [];
|
|
829
|
+
if (hydration?.links) for (const link of hydration.links.links) this.client.pushLink(link);
|
|
830
|
+
const { context } = await this.render({ previous });
|
|
831
|
+
await this.alepha.emit("react:browser:render", {
|
|
832
|
+
state: this.state,
|
|
833
|
+
context,
|
|
834
|
+
hydration
|
|
835
|
+
});
|
|
836
|
+
window.addEventListener("popstate", () => {
|
|
837
|
+
if (this.state.pathname === location.pathname) return;
|
|
838
|
+
this.render();
|
|
839
|
+
});
|
|
840
|
+
}
|
|
841
|
+
});
|
|
842
|
+
};
|
|
843
|
+
|
|
635
844
|
//#endregion
|
|
636
845
|
//#region src/providers/ReactServerProvider.ts
|
|
637
846
|
const envSchema = __alepha_core.t.object({
|
|
@@ -647,18 +856,15 @@ var ReactServerProvider = class {
|
|
|
647
856
|
serverStaticProvider = (0, __alepha_core.$inject)(__alepha_server_static.ServerStaticProvider);
|
|
648
857
|
serverRouterProvider = (0, __alepha_core.$inject)(__alepha_server.ServerRouterProvider);
|
|
649
858
|
serverTimingProvider = (0, __alepha_core.$inject)(__alepha_server.ServerTimingProvider);
|
|
650
|
-
env = (0, __alepha_core.$
|
|
859
|
+
env = (0, __alepha_core.$env)(envSchema);
|
|
651
860
|
ROOT_DIV_REGEX = new RegExp(`<div([^>]*)\\s+id=["']${this.env.REACT_ROOT_ID}["']([^>]*)>(.*?)<\\/div>`, "is");
|
|
652
861
|
onConfigure = (0, __alepha_core.$hook)({
|
|
653
862
|
on: "configure",
|
|
654
863
|
handler: async () => {
|
|
655
|
-
const pages = this.alepha.
|
|
864
|
+
const pages = this.alepha.descriptors($page);
|
|
656
865
|
const ssrEnabled = pages.length > 0 && this.env.REACT_SSR_ENABLED !== false;
|
|
657
|
-
this.alepha.state("
|
|
658
|
-
for (const
|
|
659
|
-
const name = value[__alepha_core.OPTIONS].name ?? key;
|
|
660
|
-
instance[key].render = this.createRenderFunction(name);
|
|
661
|
-
}
|
|
866
|
+
this.alepha.state("react.server.ssr", ssrEnabled);
|
|
867
|
+
for (const page of pages) page.render = this.createRenderFunction(page.name);
|
|
662
868
|
if (this.alepha.isServerless() === "vite") {
|
|
663
869
|
await this.configureVite(ssrEnabled);
|
|
664
870
|
return;
|
|
@@ -678,7 +884,7 @@ var ReactServerProvider = class {
|
|
|
678
884
|
return;
|
|
679
885
|
}
|
|
680
886
|
this.log.info("SSR is disabled, use History API fallback");
|
|
681
|
-
|
|
887
|
+
this.serverRouterProvider.createRoute({
|
|
682
888
|
path: "*",
|
|
683
889
|
handler: async ({ url, reply }) => {
|
|
684
890
|
if (url.pathname.includes(".")) {
|
|
@@ -694,13 +900,13 @@ var ReactServerProvider = class {
|
|
|
694
900
|
}
|
|
695
901
|
});
|
|
696
902
|
get template() {
|
|
697
|
-
return this.alepha.state("
|
|
903
|
+
return this.alepha.state("react.server.template") ?? "<!DOCTYPE html><html lang='en'><head></head><body></body></html>";
|
|
698
904
|
}
|
|
699
905
|
async registerPages(templateLoader) {
|
|
700
906
|
for (const page of this.pageDescriptorProvider.getPages()) {
|
|
701
907
|
if (page.children?.length) continue;
|
|
702
908
|
this.log.debug(`+ ${page.match} -> ${page.name}`);
|
|
703
|
-
|
|
909
|
+
this.serverRouterProvider.createRoute({
|
|
704
910
|
...page,
|
|
705
911
|
schema: void 0,
|
|
706
912
|
method: "GET",
|
|
@@ -715,7 +921,7 @@ var ReactServerProvider = class {
|
|
|
715
921
|
return "";
|
|
716
922
|
}
|
|
717
923
|
async configureStaticServer(root) {
|
|
718
|
-
await this.serverStaticProvider.
|
|
924
|
+
await this.serverStaticProvider.createStaticServer({
|
|
719
925
|
root,
|
|
720
926
|
path: this.env.REACT_SERVER_PREFIX
|
|
721
927
|
});
|
|
@@ -769,7 +975,7 @@ var ReactServerProvider = class {
|
|
|
769
975
|
onError: () => null
|
|
770
976
|
};
|
|
771
977
|
if (this.alepha.has(__alepha_server_links.ServerLinksProvider)) {
|
|
772
|
-
const srv = this.alepha.
|
|
978
|
+
const srv = this.alepha.inject(__alepha_server_links.ServerLinksProvider);
|
|
773
979
|
const schema = __alepha_server.apiLinksResponseSchema;
|
|
774
980
|
context.links = this.alepha.parse(schema, await srv.getLinks({
|
|
775
981
|
user: serverRequest.user,
|
|
@@ -859,208 +1065,24 @@ var ReactServerProvider = class {
|
|
|
859
1065
|
}
|
|
860
1066
|
};
|
|
861
1067
|
|
|
862
|
-
//#endregion
|
|
863
|
-
//#region src/providers/BrowserRouterProvider.ts
|
|
864
|
-
var BrowserRouterProvider = class extends __alepha_router.RouterProvider {
|
|
865
|
-
log = (0, __alepha_core.$logger)();
|
|
866
|
-
alepha = (0, __alepha_core.$inject)(__alepha_core.Alepha);
|
|
867
|
-
pageDescriptorProvider = (0, __alepha_core.$inject)(PageDescriptorProvider);
|
|
868
|
-
add(entry) {
|
|
869
|
-
this.pageDescriptorProvider.add(entry);
|
|
870
|
-
}
|
|
871
|
-
configure = (0, __alepha_core.$hook)({
|
|
872
|
-
on: "configure",
|
|
873
|
-
handler: async () => {
|
|
874
|
-
for (const page of this.pageDescriptorProvider.getPages()) if (page.component || page.lazy) this.push({
|
|
875
|
-
path: page.match,
|
|
876
|
-
page
|
|
877
|
-
});
|
|
878
|
-
}
|
|
879
|
-
});
|
|
880
|
-
async transition(url, options = {}) {
|
|
881
|
-
const { pathname, search } = url;
|
|
882
|
-
const state = {
|
|
883
|
-
pathname,
|
|
884
|
-
search,
|
|
885
|
-
layers: []
|
|
886
|
-
};
|
|
887
|
-
const context = {
|
|
888
|
-
url,
|
|
889
|
-
query: {},
|
|
890
|
-
params: {},
|
|
891
|
-
onError: () => null,
|
|
892
|
-
...options.context ?? {}
|
|
893
|
-
};
|
|
894
|
-
await this.alepha.emit("react:transition:begin", {
|
|
895
|
-
state,
|
|
896
|
-
context
|
|
897
|
-
});
|
|
898
|
-
try {
|
|
899
|
-
const previous = options.previous;
|
|
900
|
-
const { route, params } = this.match(pathname);
|
|
901
|
-
const query = {};
|
|
902
|
-
if (search) for (const [key, value] of new URLSearchParams(search).entries()) query[key] = String(value);
|
|
903
|
-
context.query = query;
|
|
904
|
-
context.params = params ?? {};
|
|
905
|
-
context.previous = previous;
|
|
906
|
-
if (isPageRoute(route)) {
|
|
907
|
-
const result = await this.pageDescriptorProvider.createLayers(route.page, context);
|
|
908
|
-
if (result.redirect) return {
|
|
909
|
-
redirect: result.redirect,
|
|
910
|
-
state,
|
|
911
|
-
context
|
|
912
|
-
};
|
|
913
|
-
state.layers = result.layers;
|
|
914
|
-
}
|
|
915
|
-
if (state.layers.length === 0) state.layers.push({
|
|
916
|
-
name: "not-found",
|
|
917
|
-
element: (0, react.createElement)(NotFoundPage),
|
|
918
|
-
index: 0,
|
|
919
|
-
path: "/"
|
|
920
|
-
});
|
|
921
|
-
await this.alepha.emit("react:transition:success", {
|
|
922
|
-
state,
|
|
923
|
-
context
|
|
924
|
-
});
|
|
925
|
-
} catch (e) {
|
|
926
|
-
this.log.error(e);
|
|
927
|
-
state.layers = [{
|
|
928
|
-
name: "error",
|
|
929
|
-
element: this.pageDescriptorProvider.renderError(e),
|
|
930
|
-
index: 0,
|
|
931
|
-
path: "/"
|
|
932
|
-
}];
|
|
933
|
-
await this.alepha.emit("react:transition:error", {
|
|
934
|
-
error: e,
|
|
935
|
-
state,
|
|
936
|
-
context
|
|
937
|
-
});
|
|
938
|
-
}
|
|
939
|
-
if (options.state) {
|
|
940
|
-
options.state.layers = state.layers;
|
|
941
|
-
options.state.pathname = state.pathname;
|
|
942
|
-
options.state.search = state.search;
|
|
943
|
-
}
|
|
944
|
-
await this.alepha.emit("react:transition:end", {
|
|
945
|
-
state: options.state,
|
|
946
|
-
context
|
|
947
|
-
});
|
|
948
|
-
return {
|
|
949
|
-
context,
|
|
950
|
-
state
|
|
951
|
-
};
|
|
952
|
-
}
|
|
953
|
-
root(state, context) {
|
|
954
|
-
return this.pageDescriptorProvider.root(state, context);
|
|
955
|
-
}
|
|
956
|
-
};
|
|
957
|
-
|
|
958
|
-
//#endregion
|
|
959
|
-
//#region src/providers/ReactBrowserProvider.ts
|
|
960
|
-
var ReactBrowserProvider = class {
|
|
961
|
-
log = (0, __alepha_core.$logger)();
|
|
962
|
-
client = (0, __alepha_core.$inject)(__alepha_server_links.LinkProvider);
|
|
963
|
-
alepha = (0, __alepha_core.$inject)(__alepha_core.Alepha);
|
|
964
|
-
router = (0, __alepha_core.$inject)(BrowserRouterProvider);
|
|
965
|
-
root;
|
|
966
|
-
transitioning;
|
|
967
|
-
state = {
|
|
968
|
-
layers: [],
|
|
969
|
-
pathname: "",
|
|
970
|
-
search: ""
|
|
971
|
-
};
|
|
972
|
-
get document() {
|
|
973
|
-
return window.document;
|
|
974
|
-
}
|
|
975
|
-
get history() {
|
|
976
|
-
return window.history;
|
|
977
|
-
}
|
|
978
|
-
get url() {
|
|
979
|
-
return window.location.pathname + window.location.search;
|
|
980
|
-
}
|
|
981
|
-
async invalidate(props) {
|
|
982
|
-
const previous = [];
|
|
983
|
-
if (props) {
|
|
984
|
-
const [key] = Object.keys(props);
|
|
985
|
-
const value = props[key];
|
|
986
|
-
for (const layer of this.state.layers) {
|
|
987
|
-
if (layer.props?.[key]) {
|
|
988
|
-
previous.push({
|
|
989
|
-
...layer,
|
|
990
|
-
props: {
|
|
991
|
-
...layer.props,
|
|
992
|
-
[key]: value
|
|
993
|
-
}
|
|
994
|
-
});
|
|
995
|
-
break;
|
|
996
|
-
}
|
|
997
|
-
previous.push(layer);
|
|
998
|
-
}
|
|
999
|
-
}
|
|
1000
|
-
await this.render({ previous });
|
|
1001
|
-
}
|
|
1002
|
-
async go(url, options = {}) {
|
|
1003
|
-
const result = await this.render({ url });
|
|
1004
|
-
if (result.context.url.pathname !== url) {
|
|
1005
|
-
this.history.replaceState({}, "", result.context.url.pathname);
|
|
1006
|
-
return;
|
|
1007
|
-
}
|
|
1008
|
-
if (options.replace) {
|
|
1009
|
-
this.history.replaceState({}, "", url);
|
|
1010
|
-
return;
|
|
1011
|
-
}
|
|
1012
|
-
this.history.pushState({}, "", url);
|
|
1013
|
-
}
|
|
1014
|
-
async render(options = {}) {
|
|
1015
|
-
const previous = options.previous ?? this.state.layers;
|
|
1016
|
-
const url = options.url ?? this.url;
|
|
1017
|
-
this.transitioning = { to: url };
|
|
1018
|
-
const result = await this.router.transition(new URL(`http://localhost${url}`), {
|
|
1019
|
-
previous,
|
|
1020
|
-
state: this.state
|
|
1021
|
-
});
|
|
1022
|
-
if (result.redirect) return await this.render({ url: result.redirect });
|
|
1023
|
-
this.transitioning = void 0;
|
|
1024
|
-
return result;
|
|
1025
|
-
}
|
|
1026
|
-
/**
|
|
1027
|
-
* Get embedded layers from the server.
|
|
1028
|
-
*/
|
|
1029
|
-
getHydrationState() {
|
|
1030
|
-
try {
|
|
1031
|
-
if ("__ssr" in window && typeof window.__ssr === "object") return window.__ssr;
|
|
1032
|
-
} catch (error) {
|
|
1033
|
-
console.error(error);
|
|
1034
|
-
}
|
|
1035
|
-
}
|
|
1036
|
-
ready = (0, __alepha_core.$hook)({
|
|
1037
|
-
on: "ready",
|
|
1038
|
-
handler: async () => {
|
|
1039
|
-
const hydration = this.getHydrationState();
|
|
1040
|
-
const previous = hydration?.layers ?? [];
|
|
1041
|
-
if (hydration?.links) for (const link of hydration.links.links) this.client.pushLink(link);
|
|
1042
|
-
const { context } = await this.render({ previous });
|
|
1043
|
-
await this.alepha.emit("react:browser:render", {
|
|
1044
|
-
state: this.state,
|
|
1045
|
-
context,
|
|
1046
|
-
hydration
|
|
1047
|
-
});
|
|
1048
|
-
window.addEventListener("popstate", () => {
|
|
1049
|
-
this.render();
|
|
1050
|
-
});
|
|
1051
|
-
}
|
|
1052
|
-
});
|
|
1053
|
-
};
|
|
1054
|
-
|
|
1055
1068
|
//#endregion
|
|
1056
1069
|
//#region src/hooks/RouterHookApi.ts
|
|
1057
1070
|
var RouterHookApi = class {
|
|
1058
|
-
constructor(pages, state, layer, browser) {
|
|
1071
|
+
constructor(pages, context, state, layer, browser) {
|
|
1059
1072
|
this.pages = pages;
|
|
1073
|
+
this.context = context;
|
|
1060
1074
|
this.state = state;
|
|
1061
1075
|
this.layer = layer;
|
|
1062
1076
|
this.browser = browser;
|
|
1063
1077
|
}
|
|
1078
|
+
getURL() {
|
|
1079
|
+
if (!this.browser) return this.context.url;
|
|
1080
|
+
return new URL(this.location.href);
|
|
1081
|
+
}
|
|
1082
|
+
get location() {
|
|
1083
|
+
if (!this.browser) throw new Error("Browser is required");
|
|
1084
|
+
return this.browser.location;
|
|
1085
|
+
}
|
|
1064
1086
|
get current() {
|
|
1065
1087
|
return this.state;
|
|
1066
1088
|
}
|
|
@@ -1136,9 +1158,9 @@ const useRouter = () => {
|
|
|
1136
1158
|
const layer = (0, react.useContext)(RouterLayerContext);
|
|
1137
1159
|
if (!ctx || !layer) throw new Error("useRouter must be used within a RouterProvider");
|
|
1138
1160
|
const pages = (0, react.useMemo)(() => {
|
|
1139
|
-
return ctx.alepha.
|
|
1161
|
+
return ctx.alepha.inject(PageDescriptorProvider).getPages();
|
|
1140
1162
|
}, []);
|
|
1141
|
-
return (0, react.useMemo)(() => new RouterHookApi(pages, ctx.state, layer, ctx.alepha.isBrowser() ? ctx.alepha.
|
|
1163
|
+
return (0, react.useMemo)(() => new RouterHookApi(pages, ctx.context, ctx.state, layer, ctx.alepha.isBrowser() ? ctx.alepha.inject(ReactBrowserProvider) : void 0), [layer]);
|
|
1142
1164
|
};
|
|
1143
1165
|
|
|
1144
1166
|
//#endregion
|
|
@@ -1146,11 +1168,11 @@ const useRouter = () => {
|
|
|
1146
1168
|
const Link = (props) => {
|
|
1147
1169
|
react.default.useContext(RouterContext);
|
|
1148
1170
|
const router = useRouter();
|
|
1149
|
-
const to = typeof props.to === "string" ? props.to : props.to
|
|
1171
|
+
const to = typeof props.to === "string" ? props.to : props.to.options.path;
|
|
1150
1172
|
if (!to) return null;
|
|
1151
|
-
const can = typeof props.to === "string" ? void 0 : props.to
|
|
1173
|
+
const can = typeof props.to === "string" ? void 0 : props.to.options.can;
|
|
1152
1174
|
if (can && !can()) return null;
|
|
1153
|
-
const name = typeof props.to === "string" ? void 0 : props.to
|
|
1175
|
+
const name = typeof props.to === "string" ? void 0 : props.to.options.name;
|
|
1154
1176
|
const anchorProps = {
|
|
1155
1177
|
...props,
|
|
1156
1178
|
to: void 0
|
|
@@ -1202,7 +1224,7 @@ const useActive = (path) => {
|
|
|
1202
1224
|
const useInject = (clazz) => {
|
|
1203
1225
|
const ctx = (0, react.useContext)(RouterContext);
|
|
1204
1226
|
if (!ctx) throw new Error("useRouter must be used within a <RouterProvider>");
|
|
1205
|
-
return (0, react.useMemo)(() => ctx.alepha.
|
|
1227
|
+
return (0, react.useMemo)(() => ctx.alepha.inject(clazz), []);
|
|
1206
1228
|
};
|
|
1207
1229
|
|
|
1208
1230
|
//#endregion
|
|
@@ -1258,19 +1280,25 @@ const useRouterState = () => {
|
|
|
1258
1280
|
//#endregion
|
|
1259
1281
|
//#region src/index.ts
|
|
1260
1282
|
/**
|
|
1261
|
-
*
|
|
1283
|
+
* Provides full-stack React development with declarative routing, server-side rendering, and client-side hydration.
|
|
1262
1284
|
*
|
|
1263
|
-
*
|
|
1264
|
-
*
|
|
1285
|
+
* The React module enables building modern React applications using the `$page` descriptor on class properties.
|
|
1286
|
+
* It delivers seamless server-side rendering, automatic code splitting, and client-side navigation with full
|
|
1287
|
+
* type safety and schema validation for route parameters and data.
|
|
1265
1288
|
*
|
|
1266
1289
|
* @see {@link $page}
|
|
1267
1290
|
* @module alepha.react
|
|
1268
1291
|
*/
|
|
1269
|
-
|
|
1270
|
-
name
|
|
1271
|
-
$
|
|
1272
|
-
|
|
1273
|
-
|
|
1292
|
+
const AlephaReact = (0, __alepha_core.$module)({
|
|
1293
|
+
name: "alepha.react",
|
|
1294
|
+
descriptors: [$page],
|
|
1295
|
+
services: [
|
|
1296
|
+
ReactServerProvider,
|
|
1297
|
+
PageDescriptorProvider,
|
|
1298
|
+
ReactBrowserProvider
|
|
1299
|
+
],
|
|
1300
|
+
register: (alepha) => alepha.with(__alepha_server.AlephaServer).with(__alepha_server_cache.AlephaServerCache).with(__alepha_server_links.AlephaServerLinks).with(ReactServerProvider).with(PageDescriptorProvider)
|
|
1301
|
+
});
|
|
1274
1302
|
|
|
1275
1303
|
//#endregion
|
|
1276
1304
|
exports.$page = $page;
|
|
@@ -1279,6 +1307,8 @@ exports.ClientOnly = ClientOnly_default;
|
|
|
1279
1307
|
exports.ErrorBoundary = ErrorBoundary_default;
|
|
1280
1308
|
exports.Link = Link_default;
|
|
1281
1309
|
exports.NestedView = NestedView_default;
|
|
1310
|
+
exports.NotFound = NotFoundPage;
|
|
1311
|
+
exports.PageDescriptor = PageDescriptor;
|
|
1282
1312
|
exports.PageDescriptorProvider = PageDescriptorProvider;
|
|
1283
1313
|
exports.ReactBrowserProvider = ReactBrowserProvider;
|
|
1284
1314
|
exports.ReactServerProvider = ReactServerProvider;
|