@alepha/react 0.8.1 → 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 +0 -121
- package/dist/index.browser.js +44 -34
- package/dist/index.browser.js.map +1 -1
- package/dist/index.cjs +252 -366
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +46 -182
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +47 -183
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +258 -373
- package/dist/index.js.map +1 -1
- package/package.json +8 -8
- package/src/components/Link.tsx +3 -5
- package/src/descriptors/$page.ts +30 -49
- package/src/hooks/useInject.ts +1 -1
- package/src/hooks/useRouter.ts +2 -2
- package/src/index.browser.ts +13 -8
- package/src/index.ts +12 -130
- package/src/providers/PageDescriptorProvider.ts +33 -25
- package/src/providers/ReactBrowserRenderer.ts +2 -2
- package/src/providers/ReactServerProvider.ts +9 -11
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
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
41
|
+
return (0, __alepha_core.createDescriptor)(PageDescriptor, options);
|
|
42
|
+
};
|
|
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
|
+
}
|
|
50
54
|
};
|
|
51
|
-
$page[__alepha_core.KIND] =
|
|
55
|
+
$page[__alepha_core.KIND] = PageDescriptor;
|
|
52
56
|
|
|
53
57
|
//#endregion
|
|
54
58
|
//#region src/components/ClientOnly.tsx
|
|
@@ -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() {
|
|
@@ -570,18 +574,17 @@ var PageDescriptorProvider = class {
|
|
|
570
574
|
on: "configure",
|
|
571
575
|
handler: () => {
|
|
572
576
|
let hasNotFoundHandler = false;
|
|
573
|
-
const pages = this.alepha.
|
|
577
|
+
const pages = this.alepha.descriptors($page);
|
|
574
578
|
const hasParent = (it) => {
|
|
575
579
|
for (const page of pages) {
|
|
576
|
-
const children = page.
|
|
580
|
+
const children = page.options.children ? Array.isArray(page.options.children) ? page.options.children : page.options.children() : [];
|
|
577
581
|
if (children.includes(it)) return true;
|
|
578
582
|
}
|
|
579
583
|
};
|
|
580
|
-
for (const
|
|
581
|
-
|
|
582
|
-
if (
|
|
583
|
-
|
|
584
|
-
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));
|
|
585
588
|
}
|
|
586
589
|
if (!hasNotFoundHandler && pages.length > 0) this.add({
|
|
587
590
|
path: "/*",
|
|
@@ -595,9 +598,10 @@ var PageDescriptorProvider = class {
|
|
|
595
598
|
}
|
|
596
599
|
});
|
|
597
600
|
map(pages, target) {
|
|
598
|
-
const children = target
|
|
601
|
+
const children = target.options.children ? Array.isArray(target.options.children) ? target.options.children : target.options.children() : [];
|
|
599
602
|
return {
|
|
600
|
-
...target
|
|
603
|
+
...target.options,
|
|
604
|
+
name: target.name,
|
|
601
605
|
parent: void 0,
|
|
602
606
|
children: children.map((it) => this.map(pages, it))
|
|
603
607
|
};
|
|
@@ -634,6 +638,209 @@ const isPageRoute = (it) => {
|
|
|
634
638
|
return it && typeof it === "object" && typeof it.path === "string" && typeof it.page === "object";
|
|
635
639
|
};
|
|
636
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
|
+
|
|
637
844
|
//#endregion
|
|
638
845
|
//#region src/providers/ReactServerProvider.ts
|
|
639
846
|
const envSchema = __alepha_core.t.object({
|
|
@@ -649,18 +856,15 @@ var ReactServerProvider = class {
|
|
|
649
856
|
serverStaticProvider = (0, __alepha_core.$inject)(__alepha_server_static.ServerStaticProvider);
|
|
650
857
|
serverRouterProvider = (0, __alepha_core.$inject)(__alepha_server.ServerRouterProvider);
|
|
651
858
|
serverTimingProvider = (0, __alepha_core.$inject)(__alepha_server.ServerTimingProvider);
|
|
652
|
-
env = (0, __alepha_core.$
|
|
859
|
+
env = (0, __alepha_core.$env)(envSchema);
|
|
653
860
|
ROOT_DIV_REGEX = new RegExp(`<div([^>]*)\\s+id=["']${this.env.REACT_ROOT_ID}["']([^>]*)>(.*?)<\\/div>`, "is");
|
|
654
861
|
onConfigure = (0, __alepha_core.$hook)({
|
|
655
862
|
on: "configure",
|
|
656
863
|
handler: async () => {
|
|
657
|
-
const pages = this.alepha.
|
|
864
|
+
const pages = this.alepha.descriptors($page);
|
|
658
865
|
const ssrEnabled = pages.length > 0 && this.env.REACT_SSR_ENABLED !== false;
|
|
659
866
|
this.alepha.state("react.server.ssr", ssrEnabled);
|
|
660
|
-
for (const
|
|
661
|
-
const name = value[__alepha_core.OPTIONS].name ?? key;
|
|
662
|
-
instance[key].render = this.createRenderFunction(name);
|
|
663
|
-
}
|
|
867
|
+
for (const page of pages) page.render = this.createRenderFunction(page.name);
|
|
664
868
|
if (this.alepha.isServerless() === "vite") {
|
|
665
869
|
await this.configureVite(ssrEnabled);
|
|
666
870
|
return;
|
|
@@ -680,7 +884,7 @@ var ReactServerProvider = class {
|
|
|
680
884
|
return;
|
|
681
885
|
}
|
|
682
886
|
this.log.info("SSR is disabled, use History API fallback");
|
|
683
|
-
|
|
887
|
+
this.serverRouterProvider.createRoute({
|
|
684
888
|
path: "*",
|
|
685
889
|
handler: async ({ url, reply }) => {
|
|
686
890
|
if (url.pathname.includes(".")) {
|
|
@@ -702,7 +906,7 @@ var ReactServerProvider = class {
|
|
|
702
906
|
for (const page of this.pageDescriptorProvider.getPages()) {
|
|
703
907
|
if (page.children?.length) continue;
|
|
704
908
|
this.log.debug(`+ ${page.match} -> ${page.name}`);
|
|
705
|
-
|
|
909
|
+
this.serverRouterProvider.createRoute({
|
|
706
910
|
...page,
|
|
707
911
|
schema: void 0,
|
|
708
912
|
method: "GET",
|
|
@@ -717,7 +921,7 @@ var ReactServerProvider = class {
|
|
|
717
921
|
return "";
|
|
718
922
|
}
|
|
719
923
|
async configureStaticServer(root) {
|
|
720
|
-
await this.serverStaticProvider.
|
|
924
|
+
await this.serverStaticProvider.createStaticServer({
|
|
721
925
|
root,
|
|
722
926
|
path: this.env.REACT_SERVER_PREFIX
|
|
723
927
|
});
|
|
@@ -771,7 +975,7 @@ var ReactServerProvider = class {
|
|
|
771
975
|
onError: () => null
|
|
772
976
|
};
|
|
773
977
|
if (this.alepha.has(__alepha_server_links.ServerLinksProvider)) {
|
|
774
|
-
const srv = this.alepha.
|
|
978
|
+
const srv = this.alepha.inject(__alepha_server_links.ServerLinksProvider);
|
|
775
979
|
const schema = __alepha_server.apiLinksResponseSchema;
|
|
776
980
|
context.links = this.alepha.parse(schema, await srv.getLinks({
|
|
777
981
|
user: serverRequest.user,
|
|
@@ -861,209 +1065,6 @@ var ReactServerProvider = class {
|
|
|
861
1065
|
}
|
|
862
1066
|
};
|
|
863
1067
|
|
|
864
|
-
//#endregion
|
|
865
|
-
//#region src/providers/BrowserRouterProvider.ts
|
|
866
|
-
var BrowserRouterProvider = class extends __alepha_router.RouterProvider {
|
|
867
|
-
log = (0, __alepha_core.$logger)();
|
|
868
|
-
alepha = (0, __alepha_core.$inject)(__alepha_core.Alepha);
|
|
869
|
-
pageDescriptorProvider = (0, __alepha_core.$inject)(PageDescriptorProvider);
|
|
870
|
-
add(entry) {
|
|
871
|
-
this.pageDescriptorProvider.add(entry);
|
|
872
|
-
}
|
|
873
|
-
configure = (0, __alepha_core.$hook)({
|
|
874
|
-
on: "configure",
|
|
875
|
-
handler: async () => {
|
|
876
|
-
for (const page of this.pageDescriptorProvider.getPages()) if (page.component || page.lazy) this.push({
|
|
877
|
-
path: page.match,
|
|
878
|
-
page
|
|
879
|
-
});
|
|
880
|
-
}
|
|
881
|
-
});
|
|
882
|
-
async transition(url, options = {}) {
|
|
883
|
-
const { pathname, search } = url;
|
|
884
|
-
const state = {
|
|
885
|
-
pathname,
|
|
886
|
-
search,
|
|
887
|
-
layers: []
|
|
888
|
-
};
|
|
889
|
-
const context = {
|
|
890
|
-
url,
|
|
891
|
-
query: {},
|
|
892
|
-
params: {},
|
|
893
|
-
onError: () => null,
|
|
894
|
-
...options.context ?? {}
|
|
895
|
-
};
|
|
896
|
-
await this.alepha.emit("react:transition:begin", {
|
|
897
|
-
state,
|
|
898
|
-
context
|
|
899
|
-
});
|
|
900
|
-
try {
|
|
901
|
-
const previous = options.previous;
|
|
902
|
-
const { route, params } = this.match(pathname);
|
|
903
|
-
const query = {};
|
|
904
|
-
if (search) for (const [key, value] of new URLSearchParams(search).entries()) query[key] = String(value);
|
|
905
|
-
context.query = query;
|
|
906
|
-
context.params = params ?? {};
|
|
907
|
-
context.previous = previous;
|
|
908
|
-
if (isPageRoute(route)) {
|
|
909
|
-
const result = await this.pageDescriptorProvider.createLayers(route.page, context);
|
|
910
|
-
if (result.redirect) return {
|
|
911
|
-
redirect: result.redirect,
|
|
912
|
-
state,
|
|
913
|
-
context
|
|
914
|
-
};
|
|
915
|
-
state.layers = result.layers;
|
|
916
|
-
}
|
|
917
|
-
if (state.layers.length === 0) state.layers.push({
|
|
918
|
-
name: "not-found",
|
|
919
|
-
element: (0, react.createElement)(NotFoundPage),
|
|
920
|
-
index: 0,
|
|
921
|
-
path: "/"
|
|
922
|
-
});
|
|
923
|
-
await this.alepha.emit("react:transition:success", {
|
|
924
|
-
state,
|
|
925
|
-
context
|
|
926
|
-
});
|
|
927
|
-
} catch (e) {
|
|
928
|
-
this.log.error(e);
|
|
929
|
-
state.layers = [{
|
|
930
|
-
name: "error",
|
|
931
|
-
element: this.pageDescriptorProvider.renderError(e),
|
|
932
|
-
index: 0,
|
|
933
|
-
path: "/"
|
|
934
|
-
}];
|
|
935
|
-
await this.alepha.emit("react:transition:error", {
|
|
936
|
-
error: e,
|
|
937
|
-
state,
|
|
938
|
-
context
|
|
939
|
-
});
|
|
940
|
-
}
|
|
941
|
-
if (options.state) {
|
|
942
|
-
options.state.layers = state.layers;
|
|
943
|
-
options.state.pathname = state.pathname;
|
|
944
|
-
options.state.search = state.search;
|
|
945
|
-
}
|
|
946
|
-
await this.alepha.emit("react:transition:end", {
|
|
947
|
-
state: options.state,
|
|
948
|
-
context
|
|
949
|
-
});
|
|
950
|
-
return {
|
|
951
|
-
context,
|
|
952
|
-
state
|
|
953
|
-
};
|
|
954
|
-
}
|
|
955
|
-
root(state, context) {
|
|
956
|
-
return this.pageDescriptorProvider.root(state, context);
|
|
957
|
-
}
|
|
958
|
-
};
|
|
959
|
-
|
|
960
|
-
//#endregion
|
|
961
|
-
//#region src/providers/ReactBrowserProvider.ts
|
|
962
|
-
var ReactBrowserProvider = class {
|
|
963
|
-
log = (0, __alepha_core.$logger)();
|
|
964
|
-
client = (0, __alepha_core.$inject)(__alepha_server_links.LinkProvider);
|
|
965
|
-
alepha = (0, __alepha_core.$inject)(__alepha_core.Alepha);
|
|
966
|
-
router = (0, __alepha_core.$inject)(BrowserRouterProvider);
|
|
967
|
-
root;
|
|
968
|
-
transitioning;
|
|
969
|
-
state = {
|
|
970
|
-
layers: [],
|
|
971
|
-
pathname: "",
|
|
972
|
-
search: ""
|
|
973
|
-
};
|
|
974
|
-
get document() {
|
|
975
|
-
return window.document;
|
|
976
|
-
}
|
|
977
|
-
get history() {
|
|
978
|
-
return window.history;
|
|
979
|
-
}
|
|
980
|
-
get location() {
|
|
981
|
-
return window.location;
|
|
982
|
-
}
|
|
983
|
-
get url() {
|
|
984
|
-
let url = this.location.pathname + this.location.search;
|
|
985
|
-
return url;
|
|
986
|
-
}
|
|
987
|
-
pushState(url, replace) {
|
|
988
|
-
let path = url;
|
|
989
|
-
if (replace) this.history.replaceState({}, "", path);
|
|
990
|
-
else this.history.pushState({}, "", path);
|
|
991
|
-
}
|
|
992
|
-
async invalidate(props) {
|
|
993
|
-
const previous = [];
|
|
994
|
-
if (props) {
|
|
995
|
-
const [key] = Object.keys(props);
|
|
996
|
-
const value = props[key];
|
|
997
|
-
for (const layer of this.state.layers) {
|
|
998
|
-
if (layer.props?.[key]) {
|
|
999
|
-
previous.push({
|
|
1000
|
-
...layer,
|
|
1001
|
-
props: {
|
|
1002
|
-
...layer.props,
|
|
1003
|
-
[key]: value
|
|
1004
|
-
}
|
|
1005
|
-
});
|
|
1006
|
-
break;
|
|
1007
|
-
}
|
|
1008
|
-
previous.push(layer);
|
|
1009
|
-
}
|
|
1010
|
-
}
|
|
1011
|
-
await this.render({ previous });
|
|
1012
|
-
}
|
|
1013
|
-
async go(url, options = {}) {
|
|
1014
|
-
const result = await this.render({ url });
|
|
1015
|
-
if (result.context.url.pathname !== url) {
|
|
1016
|
-
this.pushState(result.context.url.pathname);
|
|
1017
|
-
return;
|
|
1018
|
-
}
|
|
1019
|
-
if (options.replace) {
|
|
1020
|
-
this.pushState(url);
|
|
1021
|
-
return;
|
|
1022
|
-
}
|
|
1023
|
-
this.pushState(url);
|
|
1024
|
-
}
|
|
1025
|
-
async render(options = {}) {
|
|
1026
|
-
const previous = options.previous ?? this.state.layers;
|
|
1027
|
-
const url = options.url ?? this.url;
|
|
1028
|
-
this.transitioning = { to: url };
|
|
1029
|
-
const result = await this.router.transition(new URL(`http://localhost${url}`), {
|
|
1030
|
-
previous,
|
|
1031
|
-
state: this.state
|
|
1032
|
-
});
|
|
1033
|
-
if (result.redirect) return await this.render({ url: result.redirect });
|
|
1034
|
-
this.transitioning = void 0;
|
|
1035
|
-
return result;
|
|
1036
|
-
}
|
|
1037
|
-
/**
|
|
1038
|
-
* Get embedded layers from the server.
|
|
1039
|
-
*/
|
|
1040
|
-
getHydrationState() {
|
|
1041
|
-
try {
|
|
1042
|
-
if ("__ssr" in window && typeof window.__ssr === "object") return window.__ssr;
|
|
1043
|
-
} catch (error) {
|
|
1044
|
-
console.error(error);
|
|
1045
|
-
}
|
|
1046
|
-
}
|
|
1047
|
-
ready = (0, __alepha_core.$hook)({
|
|
1048
|
-
on: "ready",
|
|
1049
|
-
handler: async () => {
|
|
1050
|
-
const hydration = this.getHydrationState();
|
|
1051
|
-
const previous = hydration?.layers ?? [];
|
|
1052
|
-
if (hydration?.links) for (const link of hydration.links.links) this.client.pushLink(link);
|
|
1053
|
-
const { context } = await this.render({ previous });
|
|
1054
|
-
await this.alepha.emit("react:browser:render", {
|
|
1055
|
-
state: this.state,
|
|
1056
|
-
context,
|
|
1057
|
-
hydration
|
|
1058
|
-
});
|
|
1059
|
-
window.addEventListener("popstate", () => {
|
|
1060
|
-
if (this.state.pathname === location.pathname) return;
|
|
1061
|
-
this.render();
|
|
1062
|
-
});
|
|
1063
|
-
}
|
|
1064
|
-
});
|
|
1065
|
-
};
|
|
1066
|
-
|
|
1067
1068
|
//#endregion
|
|
1068
1069
|
//#region src/hooks/RouterHookApi.ts
|
|
1069
1070
|
var RouterHookApi = class {
|
|
@@ -1157,9 +1158,9 @@ const useRouter = () => {
|
|
|
1157
1158
|
const layer = (0, react.useContext)(RouterLayerContext);
|
|
1158
1159
|
if (!ctx || !layer) throw new Error("useRouter must be used within a RouterProvider");
|
|
1159
1160
|
const pages = (0, react.useMemo)(() => {
|
|
1160
|
-
return ctx.alepha.
|
|
1161
|
+
return ctx.alepha.inject(PageDescriptorProvider).getPages();
|
|
1161
1162
|
}, []);
|
|
1162
|
-
return (0, react.useMemo)(() => new RouterHookApi(pages, ctx.context, 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]);
|
|
1163
1164
|
};
|
|
1164
1165
|
|
|
1165
1166
|
//#endregion
|
|
@@ -1167,11 +1168,11 @@ const useRouter = () => {
|
|
|
1167
1168
|
const Link = (props) => {
|
|
1168
1169
|
react.default.useContext(RouterContext);
|
|
1169
1170
|
const router = useRouter();
|
|
1170
|
-
const to = typeof props.to === "string" ? props.to : props.to
|
|
1171
|
+
const to = typeof props.to === "string" ? props.to : props.to.options.path;
|
|
1171
1172
|
if (!to) return null;
|
|
1172
|
-
const can = typeof props.to === "string" ? void 0 : props.to
|
|
1173
|
+
const can = typeof props.to === "string" ? void 0 : props.to.options.can;
|
|
1173
1174
|
if (can && !can()) return null;
|
|
1174
|
-
const name = typeof props.to === "string" ? void 0 : props.to
|
|
1175
|
+
const name = typeof props.to === "string" ? void 0 : props.to.options.name;
|
|
1175
1176
|
const anchorProps = {
|
|
1176
1177
|
...props,
|
|
1177
1178
|
to: void 0
|
|
@@ -1223,7 +1224,7 @@ const useActive = (path) => {
|
|
|
1223
1224
|
const useInject = (clazz) => {
|
|
1224
1225
|
const ctx = (0, react.useContext)(RouterContext);
|
|
1225
1226
|
if (!ctx) throw new Error("useRouter must be used within a <RouterProvider>");
|
|
1226
|
-
return (0, react.useMemo)(() => ctx.alepha.
|
|
1227
|
+
return (0, react.useMemo)(() => ctx.alepha.inject(clazz), []);
|
|
1227
1228
|
};
|
|
1228
1229
|
|
|
1229
1230
|
//#endregion
|
|
@@ -1285,135 +1286,19 @@ const useRouterState = () => {
|
|
|
1285
1286
|
* It delivers seamless server-side rendering, automatic code splitting, and client-side navigation with full
|
|
1286
1287
|
* type safety and schema validation for route parameters and data.
|
|
1287
1288
|
*
|
|
1288
|
-
* **Key Features:**
|
|
1289
|
-
* - Declarative page definition with `$page` descriptor
|
|
1290
|
-
* - Server-side rendering (SSR) with automatic hydration
|
|
1291
|
-
* - Type-safe routing with parameter validation
|
|
1292
|
-
* - Schema-based data resolution and validation
|
|
1293
|
-
* - SEO-friendly meta tag management
|
|
1294
|
-
* - Automatic code splitting and lazy loading
|
|
1295
|
-
* - Client-side navigation with browser history
|
|
1296
|
-
*
|
|
1297
|
-
* **Basic Usage:**
|
|
1298
|
-
* ```ts
|
|
1299
|
-
* import { Alepha, run, t } from "alepha";
|
|
1300
|
-
* import { AlephaReact, $page } from "alepha/react";
|
|
1301
|
-
*
|
|
1302
|
-
* class AppRoutes {
|
|
1303
|
-
* // Home page
|
|
1304
|
-
* home = $page({
|
|
1305
|
-
* path: "/",
|
|
1306
|
-
* component: () => (
|
|
1307
|
-
* <div>
|
|
1308
|
-
* <h1>Welcome to Alepha</h1>
|
|
1309
|
-
* <p>Build amazing React applications!</p>
|
|
1310
|
-
* </div>
|
|
1311
|
-
* ),
|
|
1312
|
-
* });
|
|
1313
|
-
*
|
|
1314
|
-
* // About page with meta tags
|
|
1315
|
-
* about = $page({
|
|
1316
|
-
* path: "/about",
|
|
1317
|
-
* head: {
|
|
1318
|
-
* title: "About Us",
|
|
1319
|
-
* description: "Learn more about our mission",
|
|
1320
|
-
* },
|
|
1321
|
-
* component: () => (
|
|
1322
|
-
* <div>
|
|
1323
|
-
* <h1>About Us</h1>
|
|
1324
|
-
* <p>Learn more about our mission.</p>
|
|
1325
|
-
* </div>
|
|
1326
|
-
* ),
|
|
1327
|
-
* });
|
|
1328
|
-
* }
|
|
1329
|
-
*
|
|
1330
|
-
* const alepha = Alepha.create()
|
|
1331
|
-
* .with(AlephaReact)
|
|
1332
|
-
* .with(AppRoutes);
|
|
1333
|
-
*
|
|
1334
|
-
* run(alepha);
|
|
1335
|
-
* ```
|
|
1336
|
-
*
|
|
1337
|
-
* **Dynamic Routes with Parameters:**
|
|
1338
|
-
* ```tsx
|
|
1339
|
-
* class UserRoutes {
|
|
1340
|
-
* userProfile = $page({
|
|
1341
|
-
* path: "/users/:id",
|
|
1342
|
-
* schema: {
|
|
1343
|
-
* params: t.object({
|
|
1344
|
-
* id: t.string(),
|
|
1345
|
-
* }),
|
|
1346
|
-
* },
|
|
1347
|
-
* resolve: async ({ params }) => {
|
|
1348
|
-
* // Fetch user data server-side
|
|
1349
|
-
* const user = await getUserById(params.id);
|
|
1350
|
-
* return { user };
|
|
1351
|
-
* },
|
|
1352
|
-
* head: ({ user }) => ({
|
|
1353
|
-
* title: `${user.name} - Profile`,
|
|
1354
|
-
* description: `View ${user.name}'s profile`,
|
|
1355
|
-
* }),
|
|
1356
|
-
* component: ({ user }) => (
|
|
1357
|
-
* <div>
|
|
1358
|
-
* <h1>{user.name}</h1>
|
|
1359
|
-
* <p>Email: {user.email}</p>
|
|
1360
|
-
* </div>
|
|
1361
|
-
* ),
|
|
1362
|
-
* });
|
|
1363
|
-
*
|
|
1364
|
-
* userSettings = $page({
|
|
1365
|
-
* path: "/users/:id/settings",
|
|
1366
|
-
* schema: {
|
|
1367
|
-
* params: t.object({
|
|
1368
|
-
* id: t.string(),
|
|
1369
|
-
* }),
|
|
1370
|
-
* },
|
|
1371
|
-
* component: ({ params }) => (
|
|
1372
|
-
* <UserSettings userId={params.id} />
|
|
1373
|
-
* ),
|
|
1374
|
-
* });
|
|
1375
|
-
* }
|
|
1376
|
-
* ```
|
|
1377
|
-
*
|
|
1378
|
-
* **Static Generation:**
|
|
1379
|
-
* ```tsx
|
|
1380
|
-
* class BlogRoutes {
|
|
1381
|
-
* blogPost = $page({
|
|
1382
|
-
* path: "/blog/:slug",
|
|
1383
|
-
* schema: {
|
|
1384
|
-
* params: t.object({
|
|
1385
|
-
* slug: t.string(),
|
|
1386
|
-
* }),
|
|
1387
|
-
* },
|
|
1388
|
-
* static: {
|
|
1389
|
-
* entries: [
|
|
1390
|
-
* { params: { slug: "getting-started" } },
|
|
1391
|
-
* { params: { slug: "advanced-features" } },
|
|
1392
|
-
* { params: { slug: "deployment" } },
|
|
1393
|
-
* ],
|
|
1394
|
-
* },
|
|
1395
|
-
* resolve: ({ params }) => {
|
|
1396
|
-
* const post = getBlogPost(params.slug);
|
|
1397
|
-
* return { post };
|
|
1398
|
-
* },
|
|
1399
|
-
* component: ({ post }) => (
|
|
1400
|
-
* <article>
|
|
1401
|
-
* <h1>{post.title}</h1>
|
|
1402
|
-
* <div>{post.content}</div>
|
|
1403
|
-
* </article>
|
|
1404
|
-
* ),
|
|
1405
|
-
* });
|
|
1406
|
-
* }
|
|
1407
|
-
* ```
|
|
1408
|
-
*
|
|
1409
1289
|
* @see {@link $page}
|
|
1410
1290
|
* @module alepha.react
|
|
1411
1291
|
*/
|
|
1412
|
-
|
|
1413
|
-
name
|
|
1414
|
-
$
|
|
1415
|
-
|
|
1416
|
-
|
|
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
|
+
});
|
|
1417
1302
|
|
|
1418
1303
|
//#endregion
|
|
1419
1304
|
exports.$page = $page;
|
|
@@ -1423,6 +1308,7 @@ exports.ErrorBoundary = ErrorBoundary_default;
|
|
|
1423
1308
|
exports.Link = Link_default;
|
|
1424
1309
|
exports.NestedView = NestedView_default;
|
|
1425
1310
|
exports.NotFound = NotFoundPage;
|
|
1311
|
+
exports.PageDescriptor = PageDescriptor;
|
|
1426
1312
|
exports.PageDescriptorProvider = PageDescriptorProvider;
|
|
1427
1313
|
exports.ReactBrowserProvider = ReactBrowserProvider;
|
|
1428
1314
|
exports.ReactServerProvider = ReactServerProvider;
|