@alepha/react 0.8.1 → 0.9.1
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 +63 -50
- package/dist/index.browser.js.map +1 -1
- package/dist/index.cjs +273 -383
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +48 -183
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +48 -183
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +279 -390
- package/dist/index.js.map +1 -1
- package/package.json +8 -8
- package/src/components/ErrorViewer.tsx +4 -3
- 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 +34 -26
- package/src/providers/ReactBrowserProvider.ts +1 -1
- package/src/providers/ReactBrowserRenderer.ts +2 -2
- package/src/providers/ReactServerProvider.ts +15 -13
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
|
|
@@ -70,23 +74,11 @@ const ClientOnly = (props) => {
|
|
|
70
74
|
};
|
|
71
75
|
var ClientOnly_default = ClientOnly;
|
|
72
76
|
|
|
73
|
-
//#endregion
|
|
74
|
-
//#region src/contexts/RouterContext.ts
|
|
75
|
-
const RouterContext = (0, react.createContext)(void 0);
|
|
76
|
-
|
|
77
|
-
//#endregion
|
|
78
|
-
//#region src/hooks/useAlepha.ts
|
|
79
|
-
const useAlepha = () => {
|
|
80
|
-
const routerContext = (0, react.useContext)(RouterContext);
|
|
81
|
-
if (!routerContext) throw new Error("useAlepha must be used within a RouterProvider");
|
|
82
|
-
return routerContext.alepha;
|
|
83
|
-
};
|
|
84
|
-
|
|
85
77
|
//#endregion
|
|
86
78
|
//#region src/components/ErrorViewer.tsx
|
|
87
|
-
const ErrorViewer = ({ error }) => {
|
|
79
|
+
const ErrorViewer = ({ error, alepha }) => {
|
|
88
80
|
const [expanded, setExpanded] = (0, react.useState)(false);
|
|
89
|
-
const isProduction =
|
|
81
|
+
const isProduction = alepha.isProduction();
|
|
90
82
|
if (isProduction) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ErrorViewerProduction, {});
|
|
91
83
|
const stackLines = error.stack?.split("\n") ?? [];
|
|
92
84
|
const previewLines = stackLines.slice(0, 5);
|
|
@@ -230,6 +222,10 @@ const ErrorViewerProduction = () => {
|
|
|
230
222
|
});
|
|
231
223
|
};
|
|
232
224
|
|
|
225
|
+
//#endregion
|
|
226
|
+
//#region src/contexts/RouterContext.ts
|
|
227
|
+
const RouterContext = (0, react.createContext)(void 0);
|
|
228
|
+
|
|
233
229
|
//#endregion
|
|
234
230
|
//#region src/contexts/RouterLayerContext.ts
|
|
235
231
|
const RouterLayerContext = (0, react.createContext)(void 0);
|
|
@@ -364,7 +360,7 @@ var RedirectionError = class extends Error {
|
|
|
364
360
|
const envSchema$1 = __alepha_core.t.object({ REACT_STRICT_MODE: __alepha_core.t.boolean({ default: true }) });
|
|
365
361
|
var PageDescriptorProvider = class {
|
|
366
362
|
log = (0, __alepha_core.$logger)();
|
|
367
|
-
env = (0, __alepha_core.$
|
|
363
|
+
env = (0, __alepha_core.$env)(envSchema$1);
|
|
368
364
|
alepha = (0, __alepha_core.$inject)(__alepha_core.Alepha);
|
|
369
365
|
pages = [];
|
|
370
366
|
getPages() {
|
|
@@ -537,7 +533,10 @@ var PageDescriptorProvider = class {
|
|
|
537
533
|
return void 0;
|
|
538
534
|
}
|
|
539
535
|
renderError(error) {
|
|
540
|
-
return (0, react.createElement)(ErrorViewer_default, {
|
|
536
|
+
return (0, react.createElement)(ErrorViewer_default, {
|
|
537
|
+
error,
|
|
538
|
+
alepha: this.alepha
|
|
539
|
+
});
|
|
541
540
|
}
|
|
542
541
|
renderEmptyView() {
|
|
543
542
|
return (0, react.createElement)(NestedView_default, {});
|
|
@@ -570,18 +569,17 @@ var PageDescriptorProvider = class {
|
|
|
570
569
|
on: "configure",
|
|
571
570
|
handler: () => {
|
|
572
571
|
let hasNotFoundHandler = false;
|
|
573
|
-
const pages = this.alepha.
|
|
572
|
+
const pages = this.alepha.descriptors($page);
|
|
574
573
|
const hasParent = (it) => {
|
|
575
574
|
for (const page of pages) {
|
|
576
|
-
const children = page.
|
|
575
|
+
const children = page.options.children ? Array.isArray(page.options.children) ? page.options.children : page.options.children() : [];
|
|
577
576
|
if (children.includes(it)) return true;
|
|
578
577
|
}
|
|
579
578
|
};
|
|
580
|
-
for (const
|
|
581
|
-
|
|
582
|
-
if (
|
|
583
|
-
|
|
584
|
-
this.add(this.map(pages, value));
|
|
579
|
+
for (const page of pages) {
|
|
580
|
+
if (page.options.path === "/*") hasNotFoundHandler = true;
|
|
581
|
+
if (hasParent(page)) continue;
|
|
582
|
+
this.add(this.map(pages, page));
|
|
585
583
|
}
|
|
586
584
|
if (!hasNotFoundHandler && pages.length > 0) this.add({
|
|
587
585
|
path: "/*",
|
|
@@ -595,9 +593,10 @@ var PageDescriptorProvider = class {
|
|
|
595
593
|
}
|
|
596
594
|
});
|
|
597
595
|
map(pages, target) {
|
|
598
|
-
const children = target
|
|
596
|
+
const children = target.options.children ? Array.isArray(target.options.children) ? target.options.children : target.options.children() : [];
|
|
599
597
|
return {
|
|
600
|
-
...target
|
|
598
|
+
...target.options,
|
|
599
|
+
name: target.name,
|
|
601
600
|
parent: void 0,
|
|
602
601
|
children: children.map((it) => this.map(pages, it))
|
|
603
602
|
};
|
|
@@ -634,13 +633,217 @@ const isPageRoute = (it) => {
|
|
|
634
633
|
return it && typeof it === "object" && typeof it.path === "string" && typeof it.page === "object";
|
|
635
634
|
};
|
|
636
635
|
|
|
636
|
+
//#endregion
|
|
637
|
+
//#region src/providers/BrowserRouterProvider.ts
|
|
638
|
+
var BrowserRouterProvider = class extends __alepha_router.RouterProvider {
|
|
639
|
+
log = (0, __alepha_core.$logger)();
|
|
640
|
+
alepha = (0, __alepha_core.$inject)(__alepha_core.Alepha);
|
|
641
|
+
pageDescriptorProvider = (0, __alepha_core.$inject)(PageDescriptorProvider);
|
|
642
|
+
add(entry) {
|
|
643
|
+
this.pageDescriptorProvider.add(entry);
|
|
644
|
+
}
|
|
645
|
+
configure = (0, __alepha_core.$hook)({
|
|
646
|
+
on: "configure",
|
|
647
|
+
handler: async () => {
|
|
648
|
+
for (const page of this.pageDescriptorProvider.getPages()) if (page.component || page.lazy) this.push({
|
|
649
|
+
path: page.match,
|
|
650
|
+
page
|
|
651
|
+
});
|
|
652
|
+
}
|
|
653
|
+
});
|
|
654
|
+
async transition(url, options = {}) {
|
|
655
|
+
const { pathname, search } = url;
|
|
656
|
+
const state = {
|
|
657
|
+
pathname,
|
|
658
|
+
search,
|
|
659
|
+
layers: []
|
|
660
|
+
};
|
|
661
|
+
const context = {
|
|
662
|
+
url,
|
|
663
|
+
query: {},
|
|
664
|
+
params: {},
|
|
665
|
+
onError: () => null,
|
|
666
|
+
...options.context ?? {}
|
|
667
|
+
};
|
|
668
|
+
await this.alepha.emit("react:transition:begin", {
|
|
669
|
+
state,
|
|
670
|
+
context
|
|
671
|
+
});
|
|
672
|
+
try {
|
|
673
|
+
const previous = options.previous;
|
|
674
|
+
const { route, params } = this.match(pathname);
|
|
675
|
+
const query = {};
|
|
676
|
+
if (search) for (const [key, value] of new URLSearchParams(search).entries()) query[key] = String(value);
|
|
677
|
+
context.query = query;
|
|
678
|
+
context.params = params ?? {};
|
|
679
|
+
context.previous = previous;
|
|
680
|
+
if (isPageRoute(route)) {
|
|
681
|
+
const result = await this.pageDescriptorProvider.createLayers(route.page, context);
|
|
682
|
+
if (result.redirect) return {
|
|
683
|
+
redirect: result.redirect,
|
|
684
|
+
state,
|
|
685
|
+
context
|
|
686
|
+
};
|
|
687
|
+
state.layers = result.layers;
|
|
688
|
+
}
|
|
689
|
+
if (state.layers.length === 0) state.layers.push({
|
|
690
|
+
name: "not-found",
|
|
691
|
+
element: (0, react.createElement)(NotFoundPage),
|
|
692
|
+
index: 0,
|
|
693
|
+
path: "/"
|
|
694
|
+
});
|
|
695
|
+
await this.alepha.emit("react:transition:success", {
|
|
696
|
+
state,
|
|
697
|
+
context
|
|
698
|
+
});
|
|
699
|
+
} catch (e) {
|
|
700
|
+
this.log.error(e);
|
|
701
|
+
state.layers = [{
|
|
702
|
+
name: "error",
|
|
703
|
+
element: this.pageDescriptorProvider.renderError(e),
|
|
704
|
+
index: 0,
|
|
705
|
+
path: "/"
|
|
706
|
+
}];
|
|
707
|
+
await this.alepha.emit("react:transition:error", {
|
|
708
|
+
error: e,
|
|
709
|
+
state,
|
|
710
|
+
context
|
|
711
|
+
});
|
|
712
|
+
}
|
|
713
|
+
if (options.state) {
|
|
714
|
+
options.state.layers = state.layers;
|
|
715
|
+
options.state.pathname = state.pathname;
|
|
716
|
+
options.state.search = state.search;
|
|
717
|
+
}
|
|
718
|
+
await this.alepha.emit("react:transition:end", {
|
|
719
|
+
state: options.state,
|
|
720
|
+
context
|
|
721
|
+
});
|
|
722
|
+
return {
|
|
723
|
+
context,
|
|
724
|
+
state
|
|
725
|
+
};
|
|
726
|
+
}
|
|
727
|
+
root(state, context) {
|
|
728
|
+
return this.pageDescriptorProvider.root(state, context);
|
|
729
|
+
}
|
|
730
|
+
};
|
|
731
|
+
|
|
732
|
+
//#endregion
|
|
733
|
+
//#region src/providers/ReactBrowserProvider.ts
|
|
734
|
+
var ReactBrowserProvider = class {
|
|
735
|
+
log = (0, __alepha_core.$logger)();
|
|
736
|
+
client = (0, __alepha_core.$inject)(__alepha_server_links.LinkProvider);
|
|
737
|
+
alepha = (0, __alepha_core.$inject)(__alepha_core.Alepha);
|
|
738
|
+
router = (0, __alepha_core.$inject)(BrowserRouterProvider);
|
|
739
|
+
root;
|
|
740
|
+
transitioning;
|
|
741
|
+
state = {
|
|
742
|
+
layers: [],
|
|
743
|
+
pathname: "",
|
|
744
|
+
search: ""
|
|
745
|
+
};
|
|
746
|
+
get document() {
|
|
747
|
+
return window.document;
|
|
748
|
+
}
|
|
749
|
+
get history() {
|
|
750
|
+
return window.history;
|
|
751
|
+
}
|
|
752
|
+
get location() {
|
|
753
|
+
return window.location;
|
|
754
|
+
}
|
|
755
|
+
get url() {
|
|
756
|
+
let url = this.location.pathname + this.location.search;
|
|
757
|
+
return url;
|
|
758
|
+
}
|
|
759
|
+
pushState(url, replace) {
|
|
760
|
+
let path = url;
|
|
761
|
+
if (replace) this.history.replaceState({}, "", path);
|
|
762
|
+
else this.history.pushState({}, "", path);
|
|
763
|
+
}
|
|
764
|
+
async invalidate(props) {
|
|
765
|
+
const previous = [];
|
|
766
|
+
if (props) {
|
|
767
|
+
const [key] = Object.keys(props);
|
|
768
|
+
const value = props[key];
|
|
769
|
+
for (const layer of this.state.layers) {
|
|
770
|
+
if (layer.props?.[key]) {
|
|
771
|
+
previous.push({
|
|
772
|
+
...layer,
|
|
773
|
+
props: {
|
|
774
|
+
...layer.props,
|
|
775
|
+
[key]: value
|
|
776
|
+
}
|
|
777
|
+
});
|
|
778
|
+
break;
|
|
779
|
+
}
|
|
780
|
+
previous.push(layer);
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
await this.render({ previous });
|
|
784
|
+
}
|
|
785
|
+
async go(url, options = {}) {
|
|
786
|
+
const result = await this.render({ url });
|
|
787
|
+
if (result.context.url.pathname !== url) {
|
|
788
|
+
this.pushState(result.context.url.pathname);
|
|
789
|
+
return;
|
|
790
|
+
}
|
|
791
|
+
if (options.replace) {
|
|
792
|
+
this.pushState(url);
|
|
793
|
+
return;
|
|
794
|
+
}
|
|
795
|
+
this.pushState(url);
|
|
796
|
+
}
|
|
797
|
+
async render(options = {}) {
|
|
798
|
+
const previous = options.previous ?? this.state.layers;
|
|
799
|
+
const url = options.url ?? this.url;
|
|
800
|
+
this.transitioning = { to: url };
|
|
801
|
+
const result = await this.router.transition(new URL(`http://localhost${url}`), {
|
|
802
|
+
previous,
|
|
803
|
+
state: this.state
|
|
804
|
+
});
|
|
805
|
+
if (result.redirect) return await this.render({ url: result.redirect });
|
|
806
|
+
this.transitioning = void 0;
|
|
807
|
+
return result;
|
|
808
|
+
}
|
|
809
|
+
/**
|
|
810
|
+
* Get embedded layers from the server.
|
|
811
|
+
*/
|
|
812
|
+
getHydrationState() {
|
|
813
|
+
try {
|
|
814
|
+
if ("__ssr" in window && typeof window.__ssr === "object") return window.__ssr;
|
|
815
|
+
} catch (error) {
|
|
816
|
+
console.error(error);
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
ready = (0, __alepha_core.$hook)({
|
|
820
|
+
on: "ready",
|
|
821
|
+
handler: async () => {
|
|
822
|
+
const hydration = this.getHydrationState();
|
|
823
|
+
const previous = hydration?.layers ?? [];
|
|
824
|
+
if (hydration?.links) for (const link of hydration.links.links) this.client.pushLink(link);
|
|
825
|
+
const { context } = await this.render({ previous });
|
|
826
|
+
await this.alepha.emit("react:browser:render", {
|
|
827
|
+
state: this.state,
|
|
828
|
+
context,
|
|
829
|
+
hydration
|
|
830
|
+
});
|
|
831
|
+
window.addEventListener("popstate", () => {
|
|
832
|
+
if (this.state.pathname === this.url) return;
|
|
833
|
+
this.render();
|
|
834
|
+
});
|
|
835
|
+
}
|
|
836
|
+
});
|
|
837
|
+
};
|
|
838
|
+
|
|
637
839
|
//#endregion
|
|
638
840
|
//#region src/providers/ReactServerProvider.ts
|
|
639
841
|
const envSchema = __alepha_core.t.object({
|
|
640
842
|
REACT_SERVER_DIST: __alepha_core.t.string({ default: "public" }),
|
|
641
843
|
REACT_SERVER_PREFIX: __alepha_core.t.string({ default: "" }),
|
|
642
844
|
REACT_SSR_ENABLED: __alepha_core.t.optional(__alepha_core.t.boolean()),
|
|
643
|
-
REACT_ROOT_ID: __alepha_core.t.string({ default: "root" })
|
|
845
|
+
REACT_ROOT_ID: __alepha_core.t.string({ default: "root" }),
|
|
846
|
+
REACT_SERVER_TEMPLATE: __alepha_core.t.optional(__alepha_core.t.string({ size: "rich" }))
|
|
644
847
|
});
|
|
645
848
|
var ReactServerProvider = class {
|
|
646
849
|
log = (0, __alepha_core.$logger)();
|
|
@@ -649,18 +852,15 @@ var ReactServerProvider = class {
|
|
|
649
852
|
serverStaticProvider = (0, __alepha_core.$inject)(__alepha_server_static.ServerStaticProvider);
|
|
650
853
|
serverRouterProvider = (0, __alepha_core.$inject)(__alepha_server.ServerRouterProvider);
|
|
651
854
|
serverTimingProvider = (0, __alepha_core.$inject)(__alepha_server.ServerTimingProvider);
|
|
652
|
-
env = (0, __alepha_core.$
|
|
855
|
+
env = (0, __alepha_core.$env)(envSchema);
|
|
653
856
|
ROOT_DIV_REGEX = new RegExp(`<div([^>]*)\\s+id=["']${this.env.REACT_ROOT_ID}["']([^>]*)>(.*?)<\\/div>`, "is");
|
|
654
857
|
onConfigure = (0, __alepha_core.$hook)({
|
|
655
858
|
on: "configure",
|
|
656
859
|
handler: async () => {
|
|
657
|
-
const pages = this.alepha.
|
|
860
|
+
const pages = this.alepha.descriptors($page);
|
|
658
861
|
const ssrEnabled = pages.length > 0 && this.env.REACT_SSR_ENABLED !== false;
|
|
659
862
|
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
|
-
}
|
|
863
|
+
for (const page of pages) page.render = this.createRenderFunction(page.name);
|
|
664
864
|
if (this.alepha.isServerless() === "vite") {
|
|
665
865
|
await this.configureVite(ssrEnabled);
|
|
666
866
|
return;
|
|
@@ -680,7 +880,7 @@ var ReactServerProvider = class {
|
|
|
680
880
|
return;
|
|
681
881
|
}
|
|
682
882
|
this.log.info("SSR is disabled, use History API fallback");
|
|
683
|
-
|
|
883
|
+
this.serverRouterProvider.createRoute({
|
|
684
884
|
path: "*",
|
|
685
885
|
handler: async ({ url, reply }) => {
|
|
686
886
|
if (url.pathname.includes(".")) {
|
|
@@ -696,13 +896,13 @@ var ReactServerProvider = class {
|
|
|
696
896
|
}
|
|
697
897
|
});
|
|
698
898
|
get template() {
|
|
699
|
-
return this.alepha.
|
|
899
|
+
return this.alepha.env.REACT_SERVER_TEMPLATE ?? "<!DOCTYPE html><html lang='en'><head></head><body></body></html>";
|
|
700
900
|
}
|
|
701
901
|
async registerPages(templateLoader) {
|
|
702
902
|
for (const page of this.pageDescriptorProvider.getPages()) {
|
|
703
903
|
if (page.children?.length) continue;
|
|
704
904
|
this.log.debug(`+ ${page.match} -> ${page.name}`);
|
|
705
|
-
|
|
905
|
+
this.serverRouterProvider.createRoute({
|
|
706
906
|
...page,
|
|
707
907
|
schema: void 0,
|
|
708
908
|
method: "GET",
|
|
@@ -717,7 +917,7 @@ var ReactServerProvider = class {
|
|
|
717
917
|
return "";
|
|
718
918
|
}
|
|
719
919
|
async configureStaticServer(root) {
|
|
720
|
-
await this.serverStaticProvider.
|
|
920
|
+
await this.serverStaticProvider.createStaticServer({
|
|
721
921
|
root,
|
|
722
922
|
path: this.env.REACT_SERVER_PREFIX
|
|
723
923
|
});
|
|
@@ -771,7 +971,7 @@ var ReactServerProvider = class {
|
|
|
771
971
|
onError: () => null
|
|
772
972
|
};
|
|
773
973
|
if (this.alepha.has(__alepha_server_links.ServerLinksProvider)) {
|
|
774
|
-
const srv = this.alepha.
|
|
974
|
+
const srv = this.alepha.inject(__alepha_server_links.ServerLinksProvider);
|
|
775
975
|
const schema = __alepha_server.apiLinksResponseSchema;
|
|
776
976
|
context.links = this.alepha.parse(schema, await srv.getLinks({
|
|
777
977
|
user: serverRequest.user,
|
|
@@ -861,209 +1061,6 @@ var ReactServerProvider = class {
|
|
|
861
1061
|
}
|
|
862
1062
|
};
|
|
863
1063
|
|
|
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
1064
|
//#endregion
|
|
1068
1065
|
//#region src/hooks/RouterHookApi.ts
|
|
1069
1066
|
var RouterHookApi = class {
|
|
@@ -1157,9 +1154,9 @@ const useRouter = () => {
|
|
|
1157
1154
|
const layer = (0, react.useContext)(RouterLayerContext);
|
|
1158
1155
|
if (!ctx || !layer) throw new Error("useRouter must be used within a RouterProvider");
|
|
1159
1156
|
const pages = (0, react.useMemo)(() => {
|
|
1160
|
-
return ctx.alepha.
|
|
1157
|
+
return ctx.alepha.inject(PageDescriptorProvider).getPages();
|
|
1161
1158
|
}, []);
|
|
1162
|
-
return (0, react.useMemo)(() => new RouterHookApi(pages, ctx.context, ctx.state, layer, ctx.alepha.isBrowser() ? ctx.alepha.
|
|
1159
|
+
return (0, react.useMemo)(() => new RouterHookApi(pages, ctx.context, ctx.state, layer, ctx.alepha.isBrowser() ? ctx.alepha.inject(ReactBrowserProvider) : void 0), [layer]);
|
|
1163
1160
|
};
|
|
1164
1161
|
|
|
1165
1162
|
//#endregion
|
|
@@ -1167,11 +1164,11 @@ const useRouter = () => {
|
|
|
1167
1164
|
const Link = (props) => {
|
|
1168
1165
|
react.default.useContext(RouterContext);
|
|
1169
1166
|
const router = useRouter();
|
|
1170
|
-
const to = typeof props.to === "string" ? props.to : props.to
|
|
1167
|
+
const to = typeof props.to === "string" ? props.to : props.to.options.path;
|
|
1171
1168
|
if (!to) return null;
|
|
1172
|
-
const can = typeof props.to === "string" ? void 0 : props.to
|
|
1169
|
+
const can = typeof props.to === "string" ? void 0 : props.to.options.can;
|
|
1173
1170
|
if (can && !can()) return null;
|
|
1174
|
-
const name = typeof props.to === "string" ? void 0 : props.to
|
|
1171
|
+
const name = typeof props.to === "string" ? void 0 : props.to.options.name;
|
|
1175
1172
|
const anchorProps = {
|
|
1176
1173
|
...props,
|
|
1177
1174
|
to: void 0
|
|
@@ -1218,12 +1215,20 @@ const useActive = (path) => {
|
|
|
1218
1215
|
};
|
|
1219
1216
|
};
|
|
1220
1217
|
|
|
1218
|
+
//#endregion
|
|
1219
|
+
//#region src/hooks/useAlepha.ts
|
|
1220
|
+
const useAlepha = () => {
|
|
1221
|
+
const routerContext = (0, react.useContext)(RouterContext);
|
|
1222
|
+
if (!routerContext) throw new Error("useAlepha must be used within a RouterProvider");
|
|
1223
|
+
return routerContext.alepha;
|
|
1224
|
+
};
|
|
1225
|
+
|
|
1221
1226
|
//#endregion
|
|
1222
1227
|
//#region src/hooks/useInject.ts
|
|
1223
1228
|
const useInject = (clazz) => {
|
|
1224
1229
|
const ctx = (0, react.useContext)(RouterContext);
|
|
1225
1230
|
if (!ctx) throw new Error("useRouter must be used within a <RouterProvider>");
|
|
1226
|
-
return (0, react.useMemo)(() => ctx.alepha.
|
|
1231
|
+
return (0, react.useMemo)(() => ctx.alepha.inject(clazz), []);
|
|
1227
1232
|
};
|
|
1228
1233
|
|
|
1229
1234
|
//#endregion
|
|
@@ -1285,135 +1290,19 @@ const useRouterState = () => {
|
|
|
1285
1290
|
* It delivers seamless server-side rendering, automatic code splitting, and client-side navigation with full
|
|
1286
1291
|
* type safety and schema validation for route parameters and data.
|
|
1287
1292
|
*
|
|
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
1293
|
* @see {@link $page}
|
|
1410
1294
|
* @module alepha.react
|
|
1411
1295
|
*/
|
|
1412
|
-
|
|
1413
|
-
name
|
|
1414
|
-
$
|
|
1415
|
-
|
|
1416
|
-
|
|
1296
|
+
const AlephaReact = (0, __alepha_core.$module)({
|
|
1297
|
+
name: "alepha.react",
|
|
1298
|
+
descriptors: [$page],
|
|
1299
|
+
services: [
|
|
1300
|
+
ReactServerProvider,
|
|
1301
|
+
PageDescriptorProvider,
|
|
1302
|
+
ReactBrowserProvider
|
|
1303
|
+
],
|
|
1304
|
+
register: (alepha) => alepha.with(__alepha_server.AlephaServer).with(__alepha_server_cache.AlephaServerCache).with(__alepha_server_links.AlephaServerLinks).with(ReactServerProvider).with(PageDescriptorProvider)
|
|
1305
|
+
});
|
|
1417
1306
|
|
|
1418
1307
|
//#endregion
|
|
1419
1308
|
exports.$page = $page;
|
|
@@ -1423,6 +1312,7 @@ exports.ErrorBoundary = ErrorBoundary_default;
|
|
|
1423
1312
|
exports.Link = Link_default;
|
|
1424
1313
|
exports.NestedView = NestedView_default;
|
|
1425
1314
|
exports.NotFound = NotFoundPage;
|
|
1315
|
+
exports.PageDescriptor = PageDescriptor;
|
|
1426
1316
|
exports.PageDescriptorProvider = PageDescriptorProvider;
|
|
1427
1317
|
exports.ReactBrowserProvider = ReactBrowserProvider;
|
|
1428
1318
|
exports.ReactServerProvider = ReactServerProvider;
|