@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.js
CHANGED
|
@@ -1,31 +1,35 @@
|
|
|
1
|
-
import { $hook, $inject, $logger,
|
|
1
|
+
import { $env, $hook, $inject, $logger, $module, Alepha, Descriptor, KIND, NotImplementedError, createDescriptor, t } from "@alepha/core";
|
|
2
2
|
import { AlephaServer, ServerRouterProvider, ServerTimingProvider, apiLinksResponseSchema } from "@alepha/server";
|
|
3
3
|
import { AlephaServerCache } from "@alepha/server-cache";
|
|
4
4
|
import { AlephaServerLinks, LinkProvider, ServerLinksProvider } from "@alepha/server-links";
|
|
5
5
|
import React, { StrictMode, createContext, createElement, useContext, useEffect, useMemo, useState } from "react";
|
|
6
6
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
7
|
+
import { RouterProvider } from "@alepha/router";
|
|
7
8
|
import { existsSync } from "node:fs";
|
|
8
9
|
import { join } from "node:path";
|
|
9
10
|
import { ServerStaticProvider } from "@alepha/server-static";
|
|
10
11
|
import { renderToString } from "react-dom/server";
|
|
11
|
-
import { RouterProvider } from "@alepha/router";
|
|
12
12
|
|
|
13
13
|
//#region src/descriptors/$page.ts
|
|
14
|
-
const KEY = "PAGE";
|
|
15
14
|
/**
|
|
16
15
|
* Main descriptor for defining a React route in the application.
|
|
17
16
|
*/
|
|
18
17
|
const $page = (options) => {
|
|
19
|
-
|
|
20
|
-
return {
|
|
21
|
-
[KIND]: KEY,
|
|
22
|
-
[OPTIONS]: options,
|
|
23
|
-
render: () => {
|
|
24
|
-
throw new NotImplementedError(KEY);
|
|
25
|
-
}
|
|
26
|
-
};
|
|
18
|
+
return createDescriptor(PageDescriptor, options);
|
|
27
19
|
};
|
|
28
|
-
|
|
20
|
+
var PageDescriptor = class extends Descriptor {
|
|
21
|
+
get name() {
|
|
22
|
+
return this.options.name ?? this.config.propertyKey;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* For testing or build purposes, this will render the page (with or without the HTML layout) and return the HTML and context.
|
|
26
|
+
* Only valid for server-side rendering, it will throw an error if called on the client-side.
|
|
27
|
+
*/
|
|
28
|
+
async render(options) {
|
|
29
|
+
throw new NotImplementedError("");
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
$page[KIND] = PageDescriptor;
|
|
29
33
|
|
|
30
34
|
//#endregion
|
|
31
35
|
//#region src/components/ClientOnly.tsx
|
|
@@ -341,7 +345,7 @@ var RedirectionError = class extends Error {
|
|
|
341
345
|
const envSchema$1 = t.object({ REACT_STRICT_MODE: t.boolean({ default: true }) });
|
|
342
346
|
var PageDescriptorProvider = class {
|
|
343
347
|
log = $logger();
|
|
344
|
-
env = $
|
|
348
|
+
env = $env(envSchema$1);
|
|
345
349
|
alepha = $inject(Alepha);
|
|
346
350
|
pages = [];
|
|
347
351
|
getPages() {
|
|
@@ -547,18 +551,17 @@ var PageDescriptorProvider = class {
|
|
|
547
551
|
on: "configure",
|
|
548
552
|
handler: () => {
|
|
549
553
|
let hasNotFoundHandler = false;
|
|
550
|
-
const pages = this.alepha.
|
|
554
|
+
const pages = this.alepha.descriptors($page);
|
|
551
555
|
const hasParent = (it) => {
|
|
552
556
|
for (const page of pages) {
|
|
553
|
-
const children = page.
|
|
557
|
+
const children = page.options.children ? Array.isArray(page.options.children) ? page.options.children : page.options.children() : [];
|
|
554
558
|
if (children.includes(it)) return true;
|
|
555
559
|
}
|
|
556
560
|
};
|
|
557
|
-
for (const
|
|
558
|
-
|
|
559
|
-
if (
|
|
560
|
-
|
|
561
|
-
this.add(this.map(pages, value));
|
|
561
|
+
for (const page of pages) {
|
|
562
|
+
if (page.options.path === "/*") hasNotFoundHandler = true;
|
|
563
|
+
if (hasParent(page)) continue;
|
|
564
|
+
this.add(this.map(pages, page));
|
|
562
565
|
}
|
|
563
566
|
if (!hasNotFoundHandler && pages.length > 0) this.add({
|
|
564
567
|
path: "/*",
|
|
@@ -572,9 +575,10 @@ var PageDescriptorProvider = class {
|
|
|
572
575
|
}
|
|
573
576
|
});
|
|
574
577
|
map(pages, target) {
|
|
575
|
-
const children = target
|
|
578
|
+
const children = target.options.children ? Array.isArray(target.options.children) ? target.options.children : target.options.children() : [];
|
|
576
579
|
return {
|
|
577
|
-
...target
|
|
580
|
+
...target.options,
|
|
581
|
+
name: target.name,
|
|
578
582
|
parent: void 0,
|
|
579
583
|
children: children.map((it) => this.map(pages, it))
|
|
580
584
|
};
|
|
@@ -611,6 +615,214 @@ const isPageRoute = (it) => {
|
|
|
611
615
|
return it && typeof it === "object" && typeof it.path === "string" && typeof it.page === "object";
|
|
612
616
|
};
|
|
613
617
|
|
|
618
|
+
//#endregion
|
|
619
|
+
//#region src/providers/BrowserRouterProvider.ts
|
|
620
|
+
var BrowserRouterProvider = class extends RouterProvider {
|
|
621
|
+
log = $logger();
|
|
622
|
+
alepha = $inject(Alepha);
|
|
623
|
+
pageDescriptorProvider = $inject(PageDescriptorProvider);
|
|
624
|
+
add(entry) {
|
|
625
|
+
this.pageDescriptorProvider.add(entry);
|
|
626
|
+
}
|
|
627
|
+
configure = $hook({
|
|
628
|
+
on: "configure",
|
|
629
|
+
handler: async () => {
|
|
630
|
+
for (const page of this.pageDescriptorProvider.getPages()) if (page.component || page.lazy) this.push({
|
|
631
|
+
path: page.match,
|
|
632
|
+
page
|
|
633
|
+
});
|
|
634
|
+
}
|
|
635
|
+
});
|
|
636
|
+
async transition(url, options = {}) {
|
|
637
|
+
const { pathname, search } = url;
|
|
638
|
+
const state = {
|
|
639
|
+
pathname,
|
|
640
|
+
search,
|
|
641
|
+
layers: []
|
|
642
|
+
};
|
|
643
|
+
const context = {
|
|
644
|
+
url,
|
|
645
|
+
query: {},
|
|
646
|
+
params: {},
|
|
647
|
+
onError: () => null,
|
|
648
|
+
...options.context ?? {}
|
|
649
|
+
};
|
|
650
|
+
await this.alepha.emit("react:transition:begin", {
|
|
651
|
+
state,
|
|
652
|
+
context
|
|
653
|
+
});
|
|
654
|
+
try {
|
|
655
|
+
const previous = options.previous;
|
|
656
|
+
const { route, params } = this.match(pathname);
|
|
657
|
+
const query = {};
|
|
658
|
+
if (search) for (const [key, value] of new URLSearchParams(search).entries()) query[key] = String(value);
|
|
659
|
+
context.query = query;
|
|
660
|
+
context.params = params ?? {};
|
|
661
|
+
context.previous = previous;
|
|
662
|
+
if (isPageRoute(route)) {
|
|
663
|
+
const result = await this.pageDescriptorProvider.createLayers(route.page, context);
|
|
664
|
+
if (result.redirect) return {
|
|
665
|
+
redirect: result.redirect,
|
|
666
|
+
state,
|
|
667
|
+
context
|
|
668
|
+
};
|
|
669
|
+
state.layers = result.layers;
|
|
670
|
+
}
|
|
671
|
+
if (state.layers.length === 0) state.layers.push({
|
|
672
|
+
name: "not-found",
|
|
673
|
+
element: createElement(NotFoundPage),
|
|
674
|
+
index: 0,
|
|
675
|
+
path: "/"
|
|
676
|
+
});
|
|
677
|
+
await this.alepha.emit("react:transition:success", {
|
|
678
|
+
state,
|
|
679
|
+
context
|
|
680
|
+
});
|
|
681
|
+
} catch (e) {
|
|
682
|
+
this.log.error(e);
|
|
683
|
+
state.layers = [{
|
|
684
|
+
name: "error",
|
|
685
|
+
element: this.pageDescriptorProvider.renderError(e),
|
|
686
|
+
index: 0,
|
|
687
|
+
path: "/"
|
|
688
|
+
}];
|
|
689
|
+
await this.alepha.emit("react:transition:error", {
|
|
690
|
+
error: e,
|
|
691
|
+
state,
|
|
692
|
+
context
|
|
693
|
+
});
|
|
694
|
+
}
|
|
695
|
+
if (options.state) {
|
|
696
|
+
options.state.layers = state.layers;
|
|
697
|
+
options.state.pathname = state.pathname;
|
|
698
|
+
options.state.search = state.search;
|
|
699
|
+
}
|
|
700
|
+
await this.alepha.emit("react:transition:end", {
|
|
701
|
+
state: options.state,
|
|
702
|
+
context
|
|
703
|
+
});
|
|
704
|
+
return {
|
|
705
|
+
context,
|
|
706
|
+
state
|
|
707
|
+
};
|
|
708
|
+
}
|
|
709
|
+
root(state, context) {
|
|
710
|
+
return this.pageDescriptorProvider.root(state, context);
|
|
711
|
+
}
|
|
712
|
+
};
|
|
713
|
+
|
|
714
|
+
//#endregion
|
|
715
|
+
//#region src/providers/ReactBrowserProvider.ts
|
|
716
|
+
var ReactBrowserProvider = class {
|
|
717
|
+
log = $logger();
|
|
718
|
+
client = $inject(LinkProvider);
|
|
719
|
+
alepha = $inject(Alepha);
|
|
720
|
+
router = $inject(BrowserRouterProvider);
|
|
721
|
+
root;
|
|
722
|
+
transitioning;
|
|
723
|
+
state = {
|
|
724
|
+
layers: [],
|
|
725
|
+
pathname: "",
|
|
726
|
+
search: ""
|
|
727
|
+
};
|
|
728
|
+
get document() {
|
|
729
|
+
return window.document;
|
|
730
|
+
}
|
|
731
|
+
get history() {
|
|
732
|
+
return window.history;
|
|
733
|
+
}
|
|
734
|
+
get location() {
|
|
735
|
+
return window.location;
|
|
736
|
+
}
|
|
737
|
+
get url() {
|
|
738
|
+
let url = this.location.pathname + this.location.search;
|
|
739
|
+
if (import.meta?.env?.BASE_URL) {
|
|
740
|
+
url = url.replace(import.meta.env?.BASE_URL, "");
|
|
741
|
+
if (!url.startsWith("/")) url = `/${url}`;
|
|
742
|
+
}
|
|
743
|
+
return url;
|
|
744
|
+
}
|
|
745
|
+
pushState(url, replace) {
|
|
746
|
+
let path = url;
|
|
747
|
+
if (import.meta?.env?.BASE_URL) path = (import.meta.env?.BASE_URL + path).replaceAll("//", "/");
|
|
748
|
+
if (replace) this.history.replaceState({}, "", path);
|
|
749
|
+
else this.history.pushState({}, "", path);
|
|
750
|
+
}
|
|
751
|
+
async invalidate(props) {
|
|
752
|
+
const previous = [];
|
|
753
|
+
if (props) {
|
|
754
|
+
const [key] = Object.keys(props);
|
|
755
|
+
const value = props[key];
|
|
756
|
+
for (const layer of this.state.layers) {
|
|
757
|
+
if (layer.props?.[key]) {
|
|
758
|
+
previous.push({
|
|
759
|
+
...layer,
|
|
760
|
+
props: {
|
|
761
|
+
...layer.props,
|
|
762
|
+
[key]: value
|
|
763
|
+
}
|
|
764
|
+
});
|
|
765
|
+
break;
|
|
766
|
+
}
|
|
767
|
+
previous.push(layer);
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
await this.render({ previous });
|
|
771
|
+
}
|
|
772
|
+
async go(url, options = {}) {
|
|
773
|
+
const result = await this.render({ url });
|
|
774
|
+
if (result.context.url.pathname !== url) {
|
|
775
|
+
this.pushState(result.context.url.pathname);
|
|
776
|
+
return;
|
|
777
|
+
}
|
|
778
|
+
if (options.replace) {
|
|
779
|
+
this.pushState(url);
|
|
780
|
+
return;
|
|
781
|
+
}
|
|
782
|
+
this.pushState(url);
|
|
783
|
+
}
|
|
784
|
+
async render(options = {}) {
|
|
785
|
+
const previous = options.previous ?? this.state.layers;
|
|
786
|
+
const url = options.url ?? this.url;
|
|
787
|
+
this.transitioning = { to: url };
|
|
788
|
+
const result = await this.router.transition(new URL(`http://localhost${url}`), {
|
|
789
|
+
previous,
|
|
790
|
+
state: this.state
|
|
791
|
+
});
|
|
792
|
+
if (result.redirect) return await this.render({ url: result.redirect });
|
|
793
|
+
this.transitioning = void 0;
|
|
794
|
+
return result;
|
|
795
|
+
}
|
|
796
|
+
/**
|
|
797
|
+
* Get embedded layers from the server.
|
|
798
|
+
*/
|
|
799
|
+
getHydrationState() {
|
|
800
|
+
try {
|
|
801
|
+
if ("__ssr" in window && typeof window.__ssr === "object") return window.__ssr;
|
|
802
|
+
} catch (error) {
|
|
803
|
+
console.error(error);
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
ready = $hook({
|
|
807
|
+
on: "ready",
|
|
808
|
+
handler: async () => {
|
|
809
|
+
const hydration = this.getHydrationState();
|
|
810
|
+
const previous = hydration?.layers ?? [];
|
|
811
|
+
if (hydration?.links) for (const link of hydration.links.links) this.client.pushLink(link);
|
|
812
|
+
const { context } = await this.render({ previous });
|
|
813
|
+
await this.alepha.emit("react:browser:render", {
|
|
814
|
+
state: this.state,
|
|
815
|
+
context,
|
|
816
|
+
hydration
|
|
817
|
+
});
|
|
818
|
+
window.addEventListener("popstate", () => {
|
|
819
|
+
if (this.state.pathname === location.pathname) return;
|
|
820
|
+
this.render();
|
|
821
|
+
});
|
|
822
|
+
}
|
|
823
|
+
});
|
|
824
|
+
};
|
|
825
|
+
|
|
614
826
|
//#endregion
|
|
615
827
|
//#region src/providers/ReactServerProvider.ts
|
|
616
828
|
const envSchema = t.object({
|
|
@@ -626,18 +838,15 @@ var ReactServerProvider = class {
|
|
|
626
838
|
serverStaticProvider = $inject(ServerStaticProvider);
|
|
627
839
|
serverRouterProvider = $inject(ServerRouterProvider);
|
|
628
840
|
serverTimingProvider = $inject(ServerTimingProvider);
|
|
629
|
-
env = $
|
|
841
|
+
env = $env(envSchema);
|
|
630
842
|
ROOT_DIV_REGEX = new RegExp(`<div([^>]*)\\s+id=["']${this.env.REACT_ROOT_ID}["']([^>]*)>(.*?)<\\/div>`, "is");
|
|
631
843
|
onConfigure = $hook({
|
|
632
844
|
on: "configure",
|
|
633
845
|
handler: async () => {
|
|
634
|
-
const pages = this.alepha.
|
|
846
|
+
const pages = this.alepha.descriptors($page);
|
|
635
847
|
const ssrEnabled = pages.length > 0 && this.env.REACT_SSR_ENABLED !== false;
|
|
636
848
|
this.alepha.state("react.server.ssr", ssrEnabled);
|
|
637
|
-
for (const
|
|
638
|
-
const name = value[OPTIONS].name ?? key;
|
|
639
|
-
instance[key].render = this.createRenderFunction(name);
|
|
640
|
-
}
|
|
849
|
+
for (const page of pages) page.render = this.createRenderFunction(page.name);
|
|
641
850
|
if (this.alepha.isServerless() === "vite") {
|
|
642
851
|
await this.configureVite(ssrEnabled);
|
|
643
852
|
return;
|
|
@@ -657,7 +866,7 @@ var ReactServerProvider = class {
|
|
|
657
866
|
return;
|
|
658
867
|
}
|
|
659
868
|
this.log.info("SSR is disabled, use History API fallback");
|
|
660
|
-
|
|
869
|
+
this.serverRouterProvider.createRoute({
|
|
661
870
|
path: "*",
|
|
662
871
|
handler: async ({ url, reply }) => {
|
|
663
872
|
if (url.pathname.includes(".")) {
|
|
@@ -679,7 +888,7 @@ var ReactServerProvider = class {
|
|
|
679
888
|
for (const page of this.pageDescriptorProvider.getPages()) {
|
|
680
889
|
if (page.children?.length) continue;
|
|
681
890
|
this.log.debug(`+ ${page.match} -> ${page.name}`);
|
|
682
|
-
|
|
891
|
+
this.serverRouterProvider.createRoute({
|
|
683
892
|
...page,
|
|
684
893
|
schema: void 0,
|
|
685
894
|
method: "GET",
|
|
@@ -694,7 +903,7 @@ var ReactServerProvider = class {
|
|
|
694
903
|
return "";
|
|
695
904
|
}
|
|
696
905
|
async configureStaticServer(root) {
|
|
697
|
-
await this.serverStaticProvider.
|
|
906
|
+
await this.serverStaticProvider.createStaticServer({
|
|
698
907
|
root,
|
|
699
908
|
path: this.env.REACT_SERVER_PREFIX
|
|
700
909
|
});
|
|
@@ -748,7 +957,7 @@ var ReactServerProvider = class {
|
|
|
748
957
|
onError: () => null
|
|
749
958
|
};
|
|
750
959
|
if (this.alepha.has(ServerLinksProvider)) {
|
|
751
|
-
const srv = this.alepha.
|
|
960
|
+
const srv = this.alepha.inject(ServerLinksProvider);
|
|
752
961
|
const schema = apiLinksResponseSchema;
|
|
753
962
|
context.links = this.alepha.parse(schema, await srv.getLinks({
|
|
754
963
|
user: serverRequest.user,
|
|
@@ -838,214 +1047,6 @@ var ReactServerProvider = class {
|
|
|
838
1047
|
}
|
|
839
1048
|
};
|
|
840
1049
|
|
|
841
|
-
//#endregion
|
|
842
|
-
//#region src/providers/BrowserRouterProvider.ts
|
|
843
|
-
var BrowserRouterProvider = class extends RouterProvider {
|
|
844
|
-
log = $logger();
|
|
845
|
-
alepha = $inject(Alepha);
|
|
846
|
-
pageDescriptorProvider = $inject(PageDescriptorProvider);
|
|
847
|
-
add(entry) {
|
|
848
|
-
this.pageDescriptorProvider.add(entry);
|
|
849
|
-
}
|
|
850
|
-
configure = $hook({
|
|
851
|
-
on: "configure",
|
|
852
|
-
handler: async () => {
|
|
853
|
-
for (const page of this.pageDescriptorProvider.getPages()) if (page.component || page.lazy) this.push({
|
|
854
|
-
path: page.match,
|
|
855
|
-
page
|
|
856
|
-
});
|
|
857
|
-
}
|
|
858
|
-
});
|
|
859
|
-
async transition(url, options = {}) {
|
|
860
|
-
const { pathname, search } = url;
|
|
861
|
-
const state = {
|
|
862
|
-
pathname,
|
|
863
|
-
search,
|
|
864
|
-
layers: []
|
|
865
|
-
};
|
|
866
|
-
const context = {
|
|
867
|
-
url,
|
|
868
|
-
query: {},
|
|
869
|
-
params: {},
|
|
870
|
-
onError: () => null,
|
|
871
|
-
...options.context ?? {}
|
|
872
|
-
};
|
|
873
|
-
await this.alepha.emit("react:transition:begin", {
|
|
874
|
-
state,
|
|
875
|
-
context
|
|
876
|
-
});
|
|
877
|
-
try {
|
|
878
|
-
const previous = options.previous;
|
|
879
|
-
const { route, params } = this.match(pathname);
|
|
880
|
-
const query = {};
|
|
881
|
-
if (search) for (const [key, value] of new URLSearchParams(search).entries()) query[key] = String(value);
|
|
882
|
-
context.query = query;
|
|
883
|
-
context.params = params ?? {};
|
|
884
|
-
context.previous = previous;
|
|
885
|
-
if (isPageRoute(route)) {
|
|
886
|
-
const result = await this.pageDescriptorProvider.createLayers(route.page, context);
|
|
887
|
-
if (result.redirect) return {
|
|
888
|
-
redirect: result.redirect,
|
|
889
|
-
state,
|
|
890
|
-
context
|
|
891
|
-
};
|
|
892
|
-
state.layers = result.layers;
|
|
893
|
-
}
|
|
894
|
-
if (state.layers.length === 0) state.layers.push({
|
|
895
|
-
name: "not-found",
|
|
896
|
-
element: createElement(NotFoundPage),
|
|
897
|
-
index: 0,
|
|
898
|
-
path: "/"
|
|
899
|
-
});
|
|
900
|
-
await this.alepha.emit("react:transition:success", {
|
|
901
|
-
state,
|
|
902
|
-
context
|
|
903
|
-
});
|
|
904
|
-
} catch (e) {
|
|
905
|
-
this.log.error(e);
|
|
906
|
-
state.layers = [{
|
|
907
|
-
name: "error",
|
|
908
|
-
element: this.pageDescriptorProvider.renderError(e),
|
|
909
|
-
index: 0,
|
|
910
|
-
path: "/"
|
|
911
|
-
}];
|
|
912
|
-
await this.alepha.emit("react:transition:error", {
|
|
913
|
-
error: e,
|
|
914
|
-
state,
|
|
915
|
-
context
|
|
916
|
-
});
|
|
917
|
-
}
|
|
918
|
-
if (options.state) {
|
|
919
|
-
options.state.layers = state.layers;
|
|
920
|
-
options.state.pathname = state.pathname;
|
|
921
|
-
options.state.search = state.search;
|
|
922
|
-
}
|
|
923
|
-
await this.alepha.emit("react:transition:end", {
|
|
924
|
-
state: options.state,
|
|
925
|
-
context
|
|
926
|
-
});
|
|
927
|
-
return {
|
|
928
|
-
context,
|
|
929
|
-
state
|
|
930
|
-
};
|
|
931
|
-
}
|
|
932
|
-
root(state, context) {
|
|
933
|
-
return this.pageDescriptorProvider.root(state, context);
|
|
934
|
-
}
|
|
935
|
-
};
|
|
936
|
-
|
|
937
|
-
//#endregion
|
|
938
|
-
//#region src/providers/ReactBrowserProvider.ts
|
|
939
|
-
var ReactBrowserProvider = class {
|
|
940
|
-
log = $logger();
|
|
941
|
-
client = $inject(LinkProvider);
|
|
942
|
-
alepha = $inject(Alepha);
|
|
943
|
-
router = $inject(BrowserRouterProvider);
|
|
944
|
-
root;
|
|
945
|
-
transitioning;
|
|
946
|
-
state = {
|
|
947
|
-
layers: [],
|
|
948
|
-
pathname: "",
|
|
949
|
-
search: ""
|
|
950
|
-
};
|
|
951
|
-
get document() {
|
|
952
|
-
return window.document;
|
|
953
|
-
}
|
|
954
|
-
get history() {
|
|
955
|
-
return window.history;
|
|
956
|
-
}
|
|
957
|
-
get location() {
|
|
958
|
-
return window.location;
|
|
959
|
-
}
|
|
960
|
-
get url() {
|
|
961
|
-
let url = this.location.pathname + this.location.search;
|
|
962
|
-
if (import.meta?.env?.BASE_URL) {
|
|
963
|
-
url = url.replace(import.meta.env?.BASE_URL, "");
|
|
964
|
-
if (!url.startsWith("/")) url = `/${url}`;
|
|
965
|
-
}
|
|
966
|
-
return url;
|
|
967
|
-
}
|
|
968
|
-
pushState(url, replace) {
|
|
969
|
-
let path = url;
|
|
970
|
-
if (import.meta?.env?.BASE_URL) path = (import.meta.env?.BASE_URL + path).replaceAll("//", "/");
|
|
971
|
-
if (replace) this.history.replaceState({}, "", path);
|
|
972
|
-
else this.history.pushState({}, "", path);
|
|
973
|
-
}
|
|
974
|
-
async invalidate(props) {
|
|
975
|
-
const previous = [];
|
|
976
|
-
if (props) {
|
|
977
|
-
const [key] = Object.keys(props);
|
|
978
|
-
const value = props[key];
|
|
979
|
-
for (const layer of this.state.layers) {
|
|
980
|
-
if (layer.props?.[key]) {
|
|
981
|
-
previous.push({
|
|
982
|
-
...layer,
|
|
983
|
-
props: {
|
|
984
|
-
...layer.props,
|
|
985
|
-
[key]: value
|
|
986
|
-
}
|
|
987
|
-
});
|
|
988
|
-
break;
|
|
989
|
-
}
|
|
990
|
-
previous.push(layer);
|
|
991
|
-
}
|
|
992
|
-
}
|
|
993
|
-
await this.render({ previous });
|
|
994
|
-
}
|
|
995
|
-
async go(url, options = {}) {
|
|
996
|
-
const result = await this.render({ url });
|
|
997
|
-
if (result.context.url.pathname !== url) {
|
|
998
|
-
this.pushState(result.context.url.pathname);
|
|
999
|
-
return;
|
|
1000
|
-
}
|
|
1001
|
-
if (options.replace) {
|
|
1002
|
-
this.pushState(url);
|
|
1003
|
-
return;
|
|
1004
|
-
}
|
|
1005
|
-
this.pushState(url);
|
|
1006
|
-
}
|
|
1007
|
-
async render(options = {}) {
|
|
1008
|
-
const previous = options.previous ?? this.state.layers;
|
|
1009
|
-
const url = options.url ?? this.url;
|
|
1010
|
-
this.transitioning = { to: url };
|
|
1011
|
-
const result = await this.router.transition(new URL(`http://localhost${url}`), {
|
|
1012
|
-
previous,
|
|
1013
|
-
state: this.state
|
|
1014
|
-
});
|
|
1015
|
-
if (result.redirect) return await this.render({ url: result.redirect });
|
|
1016
|
-
this.transitioning = void 0;
|
|
1017
|
-
return result;
|
|
1018
|
-
}
|
|
1019
|
-
/**
|
|
1020
|
-
* Get embedded layers from the server.
|
|
1021
|
-
*/
|
|
1022
|
-
getHydrationState() {
|
|
1023
|
-
try {
|
|
1024
|
-
if ("__ssr" in window && typeof window.__ssr === "object") return window.__ssr;
|
|
1025
|
-
} catch (error) {
|
|
1026
|
-
console.error(error);
|
|
1027
|
-
}
|
|
1028
|
-
}
|
|
1029
|
-
ready = $hook({
|
|
1030
|
-
on: "ready",
|
|
1031
|
-
handler: async () => {
|
|
1032
|
-
const hydration = this.getHydrationState();
|
|
1033
|
-
const previous = hydration?.layers ?? [];
|
|
1034
|
-
if (hydration?.links) for (const link of hydration.links.links) this.client.pushLink(link);
|
|
1035
|
-
const { context } = await this.render({ previous });
|
|
1036
|
-
await this.alepha.emit("react:browser:render", {
|
|
1037
|
-
state: this.state,
|
|
1038
|
-
context,
|
|
1039
|
-
hydration
|
|
1040
|
-
});
|
|
1041
|
-
window.addEventListener("popstate", () => {
|
|
1042
|
-
if (this.state.pathname === location.pathname) return;
|
|
1043
|
-
this.render();
|
|
1044
|
-
});
|
|
1045
|
-
}
|
|
1046
|
-
});
|
|
1047
|
-
};
|
|
1048
|
-
|
|
1049
1050
|
//#endregion
|
|
1050
1051
|
//#region src/hooks/RouterHookApi.ts
|
|
1051
1052
|
var RouterHookApi = class {
|
|
@@ -1139,9 +1140,9 @@ const useRouter = () => {
|
|
|
1139
1140
|
const layer = useContext(RouterLayerContext);
|
|
1140
1141
|
if (!ctx || !layer) throw new Error("useRouter must be used within a RouterProvider");
|
|
1141
1142
|
const pages = useMemo(() => {
|
|
1142
|
-
return ctx.alepha.
|
|
1143
|
+
return ctx.alepha.inject(PageDescriptorProvider).getPages();
|
|
1143
1144
|
}, []);
|
|
1144
|
-
return useMemo(() => new RouterHookApi(pages, ctx.context, ctx.state, layer, ctx.alepha.isBrowser() ? ctx.alepha.
|
|
1145
|
+
return useMemo(() => new RouterHookApi(pages, ctx.context, ctx.state, layer, ctx.alepha.isBrowser() ? ctx.alepha.inject(ReactBrowserProvider) : void 0), [layer]);
|
|
1145
1146
|
};
|
|
1146
1147
|
|
|
1147
1148
|
//#endregion
|
|
@@ -1149,11 +1150,11 @@ const useRouter = () => {
|
|
|
1149
1150
|
const Link = (props) => {
|
|
1150
1151
|
React.useContext(RouterContext);
|
|
1151
1152
|
const router = useRouter();
|
|
1152
|
-
const to = typeof props.to === "string" ? props.to : props.to
|
|
1153
|
+
const to = typeof props.to === "string" ? props.to : props.to.options.path;
|
|
1153
1154
|
if (!to) return null;
|
|
1154
|
-
const can = typeof props.to === "string" ? void 0 : props.to
|
|
1155
|
+
const can = typeof props.to === "string" ? void 0 : props.to.options.can;
|
|
1155
1156
|
if (can && !can()) return null;
|
|
1156
|
-
const name = typeof props.to === "string" ? void 0 : props.to
|
|
1157
|
+
const name = typeof props.to === "string" ? void 0 : props.to.options.name;
|
|
1157
1158
|
const anchorProps = {
|
|
1158
1159
|
...props,
|
|
1159
1160
|
to: void 0
|
|
@@ -1205,7 +1206,7 @@ const useActive = (path) => {
|
|
|
1205
1206
|
const useInject = (clazz) => {
|
|
1206
1207
|
const ctx = useContext(RouterContext);
|
|
1207
1208
|
if (!ctx) throw new Error("useRouter must be used within a <RouterProvider>");
|
|
1208
|
-
return useMemo(() => ctx.alepha.
|
|
1209
|
+
return useMemo(() => ctx.alepha.inject(clazz), []);
|
|
1209
1210
|
};
|
|
1210
1211
|
|
|
1211
1212
|
//#endregion
|
|
@@ -1267,136 +1268,20 @@ const useRouterState = () => {
|
|
|
1267
1268
|
* It delivers seamless server-side rendering, automatic code splitting, and client-side navigation with full
|
|
1268
1269
|
* type safety and schema validation for route parameters and data.
|
|
1269
1270
|
*
|
|
1270
|
-
* **Key Features:**
|
|
1271
|
-
* - Declarative page definition with `$page` descriptor
|
|
1272
|
-
* - Server-side rendering (SSR) with automatic hydration
|
|
1273
|
-
* - Type-safe routing with parameter validation
|
|
1274
|
-
* - Schema-based data resolution and validation
|
|
1275
|
-
* - SEO-friendly meta tag management
|
|
1276
|
-
* - Automatic code splitting and lazy loading
|
|
1277
|
-
* - Client-side navigation with browser history
|
|
1278
|
-
*
|
|
1279
|
-
* **Basic Usage:**
|
|
1280
|
-
* ```ts
|
|
1281
|
-
* import { Alepha, run, t } from "alepha";
|
|
1282
|
-
* import { AlephaReact, $page } from "alepha/react";
|
|
1283
|
-
*
|
|
1284
|
-
* class AppRoutes {
|
|
1285
|
-
* // Home page
|
|
1286
|
-
* home = $page({
|
|
1287
|
-
* path: "/",
|
|
1288
|
-
* component: () => (
|
|
1289
|
-
* <div>
|
|
1290
|
-
* <h1>Welcome to Alepha</h1>
|
|
1291
|
-
* <p>Build amazing React applications!</p>
|
|
1292
|
-
* </div>
|
|
1293
|
-
* ),
|
|
1294
|
-
* });
|
|
1295
|
-
*
|
|
1296
|
-
* // About page with meta tags
|
|
1297
|
-
* about = $page({
|
|
1298
|
-
* path: "/about",
|
|
1299
|
-
* head: {
|
|
1300
|
-
* title: "About Us",
|
|
1301
|
-
* description: "Learn more about our mission",
|
|
1302
|
-
* },
|
|
1303
|
-
* component: () => (
|
|
1304
|
-
* <div>
|
|
1305
|
-
* <h1>About Us</h1>
|
|
1306
|
-
* <p>Learn more about our mission.</p>
|
|
1307
|
-
* </div>
|
|
1308
|
-
* ),
|
|
1309
|
-
* });
|
|
1310
|
-
* }
|
|
1311
|
-
*
|
|
1312
|
-
* const alepha = Alepha.create()
|
|
1313
|
-
* .with(AlephaReact)
|
|
1314
|
-
* .with(AppRoutes);
|
|
1315
|
-
*
|
|
1316
|
-
* run(alepha);
|
|
1317
|
-
* ```
|
|
1318
|
-
*
|
|
1319
|
-
* **Dynamic Routes with Parameters:**
|
|
1320
|
-
* ```tsx
|
|
1321
|
-
* class UserRoutes {
|
|
1322
|
-
* userProfile = $page({
|
|
1323
|
-
* path: "/users/:id",
|
|
1324
|
-
* schema: {
|
|
1325
|
-
* params: t.object({
|
|
1326
|
-
* id: t.string(),
|
|
1327
|
-
* }),
|
|
1328
|
-
* },
|
|
1329
|
-
* resolve: async ({ params }) => {
|
|
1330
|
-
* // Fetch user data server-side
|
|
1331
|
-
* const user = await getUserById(params.id);
|
|
1332
|
-
* return { user };
|
|
1333
|
-
* },
|
|
1334
|
-
* head: ({ user }) => ({
|
|
1335
|
-
* title: `${user.name} - Profile`,
|
|
1336
|
-
* description: `View ${user.name}'s profile`,
|
|
1337
|
-
* }),
|
|
1338
|
-
* component: ({ user }) => (
|
|
1339
|
-
* <div>
|
|
1340
|
-
* <h1>{user.name}</h1>
|
|
1341
|
-
* <p>Email: {user.email}</p>
|
|
1342
|
-
* </div>
|
|
1343
|
-
* ),
|
|
1344
|
-
* });
|
|
1345
|
-
*
|
|
1346
|
-
* userSettings = $page({
|
|
1347
|
-
* path: "/users/:id/settings",
|
|
1348
|
-
* schema: {
|
|
1349
|
-
* params: t.object({
|
|
1350
|
-
* id: t.string(),
|
|
1351
|
-
* }),
|
|
1352
|
-
* },
|
|
1353
|
-
* component: ({ params }) => (
|
|
1354
|
-
* <UserSettings userId={params.id} />
|
|
1355
|
-
* ),
|
|
1356
|
-
* });
|
|
1357
|
-
* }
|
|
1358
|
-
* ```
|
|
1359
|
-
*
|
|
1360
|
-
* **Static Generation:**
|
|
1361
|
-
* ```tsx
|
|
1362
|
-
* class BlogRoutes {
|
|
1363
|
-
* blogPost = $page({
|
|
1364
|
-
* path: "/blog/:slug",
|
|
1365
|
-
* schema: {
|
|
1366
|
-
* params: t.object({
|
|
1367
|
-
* slug: t.string(),
|
|
1368
|
-
* }),
|
|
1369
|
-
* },
|
|
1370
|
-
* static: {
|
|
1371
|
-
* entries: [
|
|
1372
|
-
* { params: { slug: "getting-started" } },
|
|
1373
|
-
* { params: { slug: "advanced-features" } },
|
|
1374
|
-
* { params: { slug: "deployment" } },
|
|
1375
|
-
* ],
|
|
1376
|
-
* },
|
|
1377
|
-
* resolve: ({ params }) => {
|
|
1378
|
-
* const post = getBlogPost(params.slug);
|
|
1379
|
-
* return { post };
|
|
1380
|
-
* },
|
|
1381
|
-
* component: ({ post }) => (
|
|
1382
|
-
* <article>
|
|
1383
|
-
* <h1>{post.title}</h1>
|
|
1384
|
-
* <div>{post.content}</div>
|
|
1385
|
-
* </article>
|
|
1386
|
-
* ),
|
|
1387
|
-
* });
|
|
1388
|
-
* }
|
|
1389
|
-
* ```
|
|
1390
|
-
*
|
|
1391
1271
|
* @see {@link $page}
|
|
1392
1272
|
* @module alepha.react
|
|
1393
1273
|
*/
|
|
1394
|
-
|
|
1395
|
-
name
|
|
1396
|
-
$
|
|
1397
|
-
|
|
1398
|
-
|
|
1274
|
+
const AlephaReact = $module({
|
|
1275
|
+
name: "alepha.react",
|
|
1276
|
+
descriptors: [$page],
|
|
1277
|
+
services: [
|
|
1278
|
+
ReactServerProvider,
|
|
1279
|
+
PageDescriptorProvider,
|
|
1280
|
+
ReactBrowserProvider
|
|
1281
|
+
],
|
|
1282
|
+
register: (alepha) => alepha.with(AlephaServer).with(AlephaServerCache).with(AlephaServerLinks).with(ReactServerProvider).with(PageDescriptorProvider)
|
|
1283
|
+
});
|
|
1399
1284
|
|
|
1400
1285
|
//#endregion
|
|
1401
|
-
export { $page, AlephaReact, ClientOnly_default as ClientOnly, ErrorBoundary_default as ErrorBoundary, Link_default as Link, NestedView_default as NestedView, NotFoundPage as NotFound, PageDescriptorProvider, ReactBrowserProvider, ReactServerProvider, RedirectionError, RouterContext, RouterHookApi, RouterLayerContext, isPageRoute, useActive, useAlepha, useClient, useInject, useQueryParams, useRouter, useRouterEvents, useRouterState };
|
|
1286
|
+
export { $page, AlephaReact, ClientOnly_default as ClientOnly, ErrorBoundary_default as ErrorBoundary, Link_default as Link, NestedView_default as NestedView, NotFoundPage as NotFound, PageDescriptor, PageDescriptorProvider, ReactBrowserProvider, ReactServerProvider, RedirectionError, RouterContext, RouterHookApi, RouterLayerContext, isPageRoute, useActive, useAlepha, useClient, useInject, useQueryParams, useRouter, useRouterEvents, useRouterState };
|
|
1402
1287
|
//# sourceMappingURL=index.js.map
|