@alepha/react 0.9.3 → 0.9.5
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 +64 -6
- package/dist/index.browser.js +442 -328
- package/dist/index.browser.js.map +1 -1
- package/dist/index.cjs +644 -482
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +402 -339
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +412 -349
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +641 -484
- package/dist/index.js.map +1 -1
- package/package.json +16 -11
- package/src/components/Link.tsx +2 -5
- package/src/components/NestedView.tsx +164 -19
- package/src/components/NotFound.tsx +1 -1
- package/src/descriptors/$page.ts +100 -5
- package/src/errors/Redirection.ts +8 -5
- package/src/hooks/useActive.ts +25 -35
- package/src/hooks/useAlepha.ts +16 -2
- package/src/hooks/useClient.ts +7 -4
- package/src/hooks/useInject.ts +4 -1
- package/src/hooks/useQueryParams.ts +9 -6
- package/src/hooks/useRouter.ts +18 -31
- package/src/hooks/useRouterEvents.ts +30 -22
- package/src/hooks/useRouterState.ts +8 -20
- package/src/hooks/useSchema.ts +10 -15
- package/src/hooks/useStore.ts +0 -7
- package/src/index.browser.ts +14 -11
- package/src/index.shared.ts +2 -3
- package/src/index.ts +27 -31
- package/src/providers/ReactBrowserProvider.ts +151 -62
- package/src/providers/ReactBrowserRendererProvider.ts +22 -0
- package/src/providers/ReactBrowserRouterProvider.ts +137 -0
- package/src/providers/{PageDescriptorProvider.ts → ReactPageProvider.ts} +121 -104
- package/src/providers/ReactServerProvider.ts +90 -76
- package/src/{hooks/RouterHookApi.ts → services/ReactRouter.ts} +49 -62
- package/src/contexts/RouterContext.ts +0 -14
- package/src/providers/BrowserRouterProvider.ts +0 -155
- package/src/providers/ReactBrowserRenderer.ts +0 -93
package/dist/index.cjs
CHANGED
|
@@ -25,13 +25,15 @@ const __alepha_core = __toESM(require("@alepha/core"));
|
|
|
25
25
|
const __alepha_server = __toESM(require("@alepha/server"));
|
|
26
26
|
const __alepha_server_cache = __toESM(require("@alepha/server-cache"));
|
|
27
27
|
const __alepha_server_links = __toESM(require("@alepha/server-links"));
|
|
28
|
+
const __alepha_logger = __toESM(require("@alepha/logger"));
|
|
28
29
|
const react = __toESM(require("react"));
|
|
29
30
|
const react_jsx_runtime = __toESM(require("react/jsx-runtime"));
|
|
30
|
-
const __alepha_router = __toESM(require("@alepha/router"));
|
|
31
31
|
const node_fs = __toESM(require("node:fs"));
|
|
32
32
|
const node_path = __toESM(require("node:path"));
|
|
33
33
|
const __alepha_server_static = __toESM(require("@alepha/server-static"));
|
|
34
34
|
const react_dom_server = __toESM(require("react-dom/server"));
|
|
35
|
+
const __alepha_datetime = __toESM(require("@alepha/datetime"));
|
|
36
|
+
const __alepha_router = __toESM(require("@alepha/router"));
|
|
35
37
|
|
|
36
38
|
//#region src/descriptors/$page.ts
|
|
37
39
|
/**
|
|
@@ -55,7 +57,16 @@ var PageDescriptor = class extends __alepha_core.Descriptor {
|
|
|
55
57
|
* Only valid for server-side rendering, it will throw an error if called on the client-side.
|
|
56
58
|
*/
|
|
57
59
|
async render(options) {
|
|
58
|
-
throw new
|
|
60
|
+
throw new __alepha_core.AlephaError("render() method is not implemented in this environment");
|
|
61
|
+
}
|
|
62
|
+
async fetch(options) {
|
|
63
|
+
throw new __alepha_core.AlephaError("fetch() method is not implemented in this environment");
|
|
64
|
+
}
|
|
65
|
+
match(url) {
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
pathname(config) {
|
|
69
|
+
return this.options.path || "";
|
|
59
70
|
}
|
|
60
71
|
};
|
|
61
72
|
$page[__alepha_core.KIND] = PageDescriptor;
|
|
@@ -78,7 +89,6 @@ const ClientOnly = (props) => {
|
|
|
78
89
|
if (props.disabled) return props.children;
|
|
79
90
|
return mounted ? props.children : props.fallback;
|
|
80
91
|
};
|
|
81
|
-
var ClientOnly_default = ClientOnly;
|
|
82
92
|
|
|
83
93
|
//#endregion
|
|
84
94
|
//#region src/components/ErrorViewer.tsx
|
|
@@ -186,7 +196,6 @@ const ErrorViewer = ({ error, alepha }) => {
|
|
|
186
196
|
})] })]
|
|
187
197
|
});
|
|
188
198
|
};
|
|
189
|
-
var ErrorViewer_default = ErrorViewer;
|
|
190
199
|
const ErrorViewerProduction = () => {
|
|
191
200
|
const styles = {
|
|
192
201
|
container: {
|
|
@@ -228,45 +237,107 @@ const ErrorViewerProduction = () => {
|
|
|
228
237
|
});
|
|
229
238
|
};
|
|
230
239
|
|
|
231
|
-
//#endregion
|
|
232
|
-
//#region src/contexts/RouterContext.ts
|
|
233
|
-
const RouterContext = (0, react.createContext)(void 0);
|
|
234
|
-
|
|
235
240
|
//#endregion
|
|
236
241
|
//#region src/contexts/RouterLayerContext.ts
|
|
237
242
|
const RouterLayerContext = (0, react.createContext)(void 0);
|
|
238
243
|
|
|
244
|
+
//#endregion
|
|
245
|
+
//#region src/errors/Redirection.ts
|
|
246
|
+
/**
|
|
247
|
+
* Used for Redirection during the page loading.
|
|
248
|
+
*
|
|
249
|
+
* Depends on the context, it can be thrown or just returned.
|
|
250
|
+
*/
|
|
251
|
+
var Redirection = class extends Error {
|
|
252
|
+
redirect;
|
|
253
|
+
constructor(redirect) {
|
|
254
|
+
super("Redirection");
|
|
255
|
+
this.redirect = redirect;
|
|
256
|
+
}
|
|
257
|
+
};
|
|
258
|
+
|
|
239
259
|
//#endregion
|
|
240
260
|
//#region src/contexts/AlephaContext.ts
|
|
241
261
|
const AlephaContext = (0, react.createContext)(void 0);
|
|
242
262
|
|
|
243
263
|
//#endregion
|
|
244
264
|
//#region src/hooks/useAlepha.ts
|
|
265
|
+
/**
|
|
266
|
+
* Main Alepha hook.
|
|
267
|
+
*
|
|
268
|
+
* It provides access to the Alepha instance within a React component.
|
|
269
|
+
*
|
|
270
|
+
* With Alepha, you can access the core functionalities of the framework:
|
|
271
|
+
*
|
|
272
|
+
* - alepha.state() for state management
|
|
273
|
+
* - alepha.inject() for dependency injection
|
|
274
|
+
* - alepha.emit() for event handling
|
|
275
|
+
* etc...
|
|
276
|
+
*/
|
|
245
277
|
const useAlepha = () => {
|
|
246
278
|
const alepha = (0, react.useContext)(AlephaContext);
|
|
247
|
-
if (!alepha) throw new
|
|
279
|
+
if (!alepha) throw new __alepha_core.AlephaError("Hook 'useAlepha()' must be used within an AlephaContext.Provider");
|
|
248
280
|
return alepha;
|
|
249
281
|
};
|
|
250
282
|
|
|
251
283
|
//#endregion
|
|
252
284
|
//#region src/hooks/useRouterEvents.ts
|
|
285
|
+
/**
|
|
286
|
+
* Subscribe to various router events.
|
|
287
|
+
*/
|
|
253
288
|
const useRouterEvents = (opts = {}, deps = []) => {
|
|
254
289
|
const alepha = useAlepha();
|
|
255
290
|
(0, react.useEffect)(() => {
|
|
256
291
|
if (!alepha.isBrowser()) return;
|
|
292
|
+
const cb = (callback) => {
|
|
293
|
+
if (typeof callback === "function") return { callback };
|
|
294
|
+
return callback;
|
|
295
|
+
};
|
|
257
296
|
const subs = [];
|
|
258
297
|
const onBegin = opts.onBegin;
|
|
259
298
|
const onEnd = opts.onEnd;
|
|
260
299
|
const onError = opts.onError;
|
|
261
|
-
|
|
262
|
-
if (
|
|
263
|
-
if (
|
|
300
|
+
const onSuccess = opts.onSuccess;
|
|
301
|
+
if (onBegin) subs.push(alepha.on("react:transition:begin", cb(onBegin)));
|
|
302
|
+
if (onEnd) subs.push(alepha.on("react:transition:end", cb(onEnd)));
|
|
303
|
+
if (onError) subs.push(alepha.on("react:transition:error", cb(onError)));
|
|
304
|
+
if (onSuccess) subs.push(alepha.on("react:transition:success", cb(onSuccess)));
|
|
264
305
|
return () => {
|
|
265
306
|
for (const sub of subs) sub();
|
|
266
307
|
};
|
|
267
308
|
}, deps);
|
|
268
309
|
};
|
|
269
310
|
|
|
311
|
+
//#endregion
|
|
312
|
+
//#region src/hooks/useStore.ts
|
|
313
|
+
/**
|
|
314
|
+
* Hook to access and mutate the Alepha state.
|
|
315
|
+
*/
|
|
316
|
+
const useStore = (key, defaultValue) => {
|
|
317
|
+
const alepha = useAlepha();
|
|
318
|
+
(0, react.useMemo)(() => {
|
|
319
|
+
if (defaultValue != null && alepha.state(key) == null) alepha.state(key, defaultValue);
|
|
320
|
+
}, [defaultValue]);
|
|
321
|
+
const [state, setState] = (0, react.useState)(alepha.state(key));
|
|
322
|
+
(0, react.useEffect)(() => {
|
|
323
|
+
if (!alepha.isBrowser()) return;
|
|
324
|
+
return alepha.on("state:mutate", (ev) => {
|
|
325
|
+
if (ev.key === key) setState(ev.value);
|
|
326
|
+
});
|
|
327
|
+
}, []);
|
|
328
|
+
return [state, (value) => {
|
|
329
|
+
alepha.state(key, value);
|
|
330
|
+
}];
|
|
331
|
+
};
|
|
332
|
+
|
|
333
|
+
//#endregion
|
|
334
|
+
//#region src/hooks/useRouterState.ts
|
|
335
|
+
const useRouterState = () => {
|
|
336
|
+
const [state] = useStore("react.router.state");
|
|
337
|
+
if (!state) throw new __alepha_core.AlephaError("Missing react router state");
|
|
338
|
+
return state;
|
|
339
|
+
};
|
|
340
|
+
|
|
270
341
|
//#endregion
|
|
271
342
|
//#region src/components/ErrorBoundary.tsx
|
|
272
343
|
/**
|
|
@@ -296,7 +367,6 @@ var ErrorBoundary = class extends react.default.Component {
|
|
|
296
367
|
return this.props.children;
|
|
297
368
|
}
|
|
298
369
|
};
|
|
299
|
-
var ErrorBoundary_default = ErrorBoundary;
|
|
300
370
|
|
|
301
371
|
//#endregion
|
|
302
372
|
//#region src/components/NestedView.tsx
|
|
@@ -307,7 +377,7 @@ var ErrorBoundary_default = ErrorBoundary;
|
|
|
307
377
|
*
|
|
308
378
|
* @example
|
|
309
379
|
* ```tsx
|
|
310
|
-
* import { NestedView } from "
|
|
380
|
+
* import { NestedView } from "alepha/react";
|
|
311
381
|
*
|
|
312
382
|
* class App {
|
|
313
383
|
* parent = $page({
|
|
@@ -322,24 +392,108 @@ var ErrorBoundary_default = ErrorBoundary;
|
|
|
322
392
|
* ```
|
|
323
393
|
*/
|
|
324
394
|
const NestedView = (props) => {
|
|
325
|
-
const
|
|
326
|
-
const
|
|
327
|
-
const
|
|
328
|
-
const [
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
395
|
+
const index = (0, react.use)(RouterLayerContext)?.index ?? 0;
|
|
396
|
+
const state = useRouterState();
|
|
397
|
+
const [view, setView] = (0, react.useState)(state.layers[index]?.element);
|
|
398
|
+
const [animation, setAnimation] = (0, react.useState)("");
|
|
399
|
+
const animationExitDuration = (0, react.useRef)(0);
|
|
400
|
+
const animationExitNow = (0, react.useRef)(0);
|
|
401
|
+
useRouterEvents({
|
|
402
|
+
onBegin: async ({ previous, state: state$1 }) => {
|
|
403
|
+
const layer = previous.layers[index];
|
|
404
|
+
if (`${state$1.url.pathname}/`.startsWith(`${layer?.path}/`)) return;
|
|
405
|
+
const animationExit = parseAnimation(layer.route?.animation, state$1, "exit");
|
|
406
|
+
if (animationExit) {
|
|
407
|
+
const duration = animationExit.duration || 200;
|
|
408
|
+
animationExitNow.current = Date.now();
|
|
409
|
+
animationExitDuration.current = duration;
|
|
410
|
+
setAnimation(animationExit.animation);
|
|
411
|
+
} else {
|
|
412
|
+
animationExitNow.current = 0;
|
|
413
|
+
animationExitDuration.current = 0;
|
|
414
|
+
setAnimation("");
|
|
415
|
+
}
|
|
416
|
+
},
|
|
417
|
+
onEnd: async ({ state: state$1 }) => {
|
|
418
|
+
const layer = state$1.layers[index];
|
|
419
|
+
if (animationExitNow.current) {
|
|
420
|
+
const duration = animationExitDuration.current;
|
|
421
|
+
const diff = Date.now() - animationExitNow.current;
|
|
422
|
+
if (diff < duration) await new Promise((resolve) => setTimeout(resolve, duration - diff));
|
|
423
|
+
}
|
|
424
|
+
if (!layer?.cache) {
|
|
425
|
+
setView(layer?.element);
|
|
426
|
+
const animationEnter = parseAnimation(layer?.route?.animation, state$1, "enter");
|
|
427
|
+
if (animationEnter) setAnimation(animationEnter.animation);
|
|
428
|
+
else setAnimation("");
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
}, []);
|
|
432
|
+
let element = view ?? props.children ?? null;
|
|
433
|
+
if (animation) element = /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
434
|
+
style: {
|
|
435
|
+
display: "flex",
|
|
436
|
+
flex: 1,
|
|
437
|
+
height: "100%",
|
|
438
|
+
width: "100%",
|
|
439
|
+
position: "relative",
|
|
440
|
+
overflow: "hidden"
|
|
441
|
+
},
|
|
442
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
443
|
+
style: {
|
|
444
|
+
height: "100%",
|
|
445
|
+
width: "100%",
|
|
446
|
+
display: "flex",
|
|
447
|
+
animation
|
|
448
|
+
},
|
|
449
|
+
children: element
|
|
450
|
+
})
|
|
451
|
+
});
|
|
452
|
+
if (props.errorBoundary === false) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, { children: element });
|
|
453
|
+
if (props.errorBoundary) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ErrorBoundary, {
|
|
454
|
+
fallback: props.errorBoundary,
|
|
455
|
+
children: element
|
|
456
|
+
});
|
|
457
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ErrorBoundary, {
|
|
336
458
|
fallback: (error) => {
|
|
337
|
-
|
|
459
|
+
const result = state.onError(error, state);
|
|
460
|
+
if (result instanceof Redirection) return "Redirection inside ErrorBoundary is not allowed.";
|
|
461
|
+
return result;
|
|
338
462
|
},
|
|
339
463
|
children: element
|
|
340
464
|
});
|
|
341
465
|
};
|
|
342
|
-
var NestedView_default = NestedView;
|
|
466
|
+
var NestedView_default = (0, react.memo)(NestedView);
|
|
467
|
+
function parseAnimation(animationLike, state, type = "enter") {
|
|
468
|
+
if (!animationLike) return void 0;
|
|
469
|
+
const DEFAULT_DURATION = 300;
|
|
470
|
+
const animation = typeof animationLike === "function" ? animationLike(state) : animationLike;
|
|
471
|
+
if (typeof animation === "string") {
|
|
472
|
+
if (type === "exit") return;
|
|
473
|
+
return {
|
|
474
|
+
duration: DEFAULT_DURATION,
|
|
475
|
+
animation: `${DEFAULT_DURATION}ms ${animation}`
|
|
476
|
+
};
|
|
477
|
+
}
|
|
478
|
+
if (typeof animation === "object") {
|
|
479
|
+
const anim = animation[type];
|
|
480
|
+
const duration = typeof anim === "object" ? anim.duration ?? DEFAULT_DURATION : DEFAULT_DURATION;
|
|
481
|
+
const name = typeof anim === "object" ? anim.name : anim;
|
|
482
|
+
if (type === "exit") {
|
|
483
|
+
const timing$1 = typeof anim === "object" ? anim.timing ?? "" : "";
|
|
484
|
+
return {
|
|
485
|
+
duration,
|
|
486
|
+
animation: `${duration}ms ${timing$1} ${name}`
|
|
487
|
+
};
|
|
488
|
+
}
|
|
489
|
+
const timing = typeof anim === "object" ? anim.timing ?? "" : "";
|
|
490
|
+
return {
|
|
491
|
+
duration,
|
|
492
|
+
animation: `${duration}ms ${timing} ${name}`
|
|
493
|
+
};
|
|
494
|
+
}
|
|
495
|
+
return void 0;
|
|
496
|
+
}
|
|
343
497
|
|
|
344
498
|
//#endregion
|
|
345
499
|
//#region src/components/NotFound.tsx
|
|
@@ -361,27 +515,17 @@ function NotFoundPage(props) {
|
|
|
361
515
|
fontSize: "1rem",
|
|
362
516
|
marginBottom: "0.5rem"
|
|
363
517
|
},
|
|
364
|
-
children: "This page does not exist"
|
|
518
|
+
children: "404 - This page does not exist"
|
|
365
519
|
})
|
|
366
520
|
});
|
|
367
521
|
}
|
|
368
522
|
|
|
369
523
|
//#endregion
|
|
370
|
-
//#region src/
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
this.page = page;
|
|
376
|
-
}
|
|
377
|
-
};
|
|
378
|
-
|
|
379
|
-
//#endregion
|
|
380
|
-
//#region src/providers/PageDescriptorProvider.ts
|
|
381
|
-
const envSchema$1 = __alepha_core.t.object({ REACT_STRICT_MODE: __alepha_core.t.boolean({ default: true }) });
|
|
382
|
-
var PageDescriptorProvider = class {
|
|
383
|
-
log = (0, __alepha_core.$logger)();
|
|
384
|
-
env = (0, __alepha_core.$env)(envSchema$1);
|
|
524
|
+
//#region src/providers/ReactPageProvider.ts
|
|
525
|
+
const envSchema$2 = __alepha_core.t.object({ REACT_STRICT_MODE: __alepha_core.t.boolean({ default: true }) });
|
|
526
|
+
var ReactPageProvider = class {
|
|
527
|
+
log = (0, __alepha_logger.$logger)();
|
|
528
|
+
env = (0, __alepha_core.$env)(envSchema$2);
|
|
385
529
|
alepha = (0, __alepha_core.$inject)(__alepha_core.Alepha);
|
|
386
530
|
pages = [];
|
|
387
531
|
getPages() {
|
|
@@ -408,22 +552,29 @@ var PageDescriptorProvider = class {
|
|
|
408
552
|
return url.replace(/\/\/+/g, "/") || "/";
|
|
409
553
|
}
|
|
410
554
|
url(name, options = {}) {
|
|
411
|
-
return new URL(this.pathname(name, options), options.
|
|
555
|
+
return new URL(this.pathname(name, options), options.host ?? `http://localhost`);
|
|
412
556
|
}
|
|
413
|
-
root(state
|
|
414
|
-
const root = (0, react.createElement)(AlephaContext.Provider, { value: this.alepha }, (0, react.createElement)(
|
|
415
|
-
state,
|
|
416
|
-
context
|
|
417
|
-
} }, (0, react.createElement)(NestedView_default, {}, state.layers[0]?.element)));
|
|
557
|
+
root(state) {
|
|
558
|
+
const root = (0, react.createElement)(AlephaContext.Provider, { value: this.alepha }, (0, react.createElement)(NestedView_default, {}, state.layers[0]?.element));
|
|
418
559
|
if (this.env.REACT_STRICT_MODE) return (0, react.createElement)(react.StrictMode, {}, root);
|
|
419
560
|
return root;
|
|
420
561
|
}
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
562
|
+
convertStringObjectToObject = (schema, value) => {
|
|
563
|
+
if (__alepha_core.TypeGuard.IsObject(schema) && typeof value === "object") {
|
|
564
|
+
for (const key in schema.properties) if (__alepha_core.TypeGuard.IsObject(schema.properties[key]) && typeof value[key] === "string") try {
|
|
565
|
+
value[key] = this.alepha.parse(schema.properties[key], decodeURIComponent(value[key]));
|
|
566
|
+
} catch (e) {}
|
|
567
|
+
}
|
|
568
|
+
return value;
|
|
569
|
+
};
|
|
570
|
+
/**
|
|
571
|
+
* Create a new RouterState based on a given route and request.
|
|
572
|
+
* This method resolves the layers for the route, applying any query and params schemas defined in the route.
|
|
573
|
+
* It also handles errors and redirects.
|
|
574
|
+
*/
|
|
575
|
+
async createLayers(route, state, previous = []) {
|
|
424
576
|
let context = {};
|
|
425
577
|
const stack = [{ route }];
|
|
426
|
-
request.onError = (error) => this.renderError(error);
|
|
427
578
|
let parent = route.parent;
|
|
428
579
|
while (parent) {
|
|
429
580
|
stack.unshift({ route: parent });
|
|
@@ -435,19 +586,19 @@ var PageDescriptorProvider = class {
|
|
|
435
586
|
const route$1 = it.route;
|
|
436
587
|
const config = {};
|
|
437
588
|
try {
|
|
438
|
-
|
|
589
|
+
this.convertStringObjectToObject(route$1.schema?.query, state.query);
|
|
590
|
+
config.query = route$1.schema?.query ? this.alepha.parse(route$1.schema.query, state.query) : {};
|
|
439
591
|
} catch (e) {
|
|
440
592
|
it.error = e;
|
|
441
593
|
break;
|
|
442
594
|
}
|
|
443
595
|
try {
|
|
444
|
-
config.params = route$1.schema?.params ? this.alepha.parse(route$1.schema.params,
|
|
596
|
+
config.params = route$1.schema?.params ? this.alepha.parse(route$1.schema.params, state.params) : {};
|
|
445
597
|
} catch (e) {
|
|
446
598
|
it.error = e;
|
|
447
599
|
break;
|
|
448
600
|
}
|
|
449
601
|
it.config = { ...config };
|
|
450
|
-
const previous = request.previous;
|
|
451
602
|
if (previous?.[i] && !forceRefresh && previous[i].name === route$1.name) {
|
|
452
603
|
const url = (str) => str ? str.replace(/\/\/+/g, "/") : "/";
|
|
453
604
|
const prev = JSON.stringify({
|
|
@@ -473,7 +624,7 @@ var PageDescriptorProvider = class {
|
|
|
473
624
|
if (!route$1.resolve) continue;
|
|
474
625
|
try {
|
|
475
626
|
const props = await route$1.resolve?.({
|
|
476
|
-
...
|
|
627
|
+
...state,
|
|
477
628
|
...config,
|
|
478
629
|
...context
|
|
479
630
|
}) ?? {};
|
|
@@ -483,11 +634,8 @@ var PageDescriptorProvider = class {
|
|
|
483
634
|
...props
|
|
484
635
|
};
|
|
485
636
|
} catch (e) {
|
|
486
|
-
if (e instanceof Redirection) return
|
|
487
|
-
|
|
488
|
-
search
|
|
489
|
-
});
|
|
490
|
-
this.log.error(e);
|
|
637
|
+
if (e instanceof Redirection) return { redirect: e.redirect };
|
|
638
|
+
this.log.error("Page resolver has failed", e);
|
|
491
639
|
it.error = e;
|
|
492
640
|
break;
|
|
493
641
|
}
|
|
@@ -503,69 +651,58 @@ var PageDescriptorProvider = class {
|
|
|
503
651
|
const path = acc.replace(/\/+/, "/");
|
|
504
652
|
const localErrorHandler = this.getErrorHandler(it.route);
|
|
505
653
|
if (localErrorHandler) {
|
|
506
|
-
const onErrorParent =
|
|
507
|
-
|
|
654
|
+
const onErrorParent = state.onError;
|
|
655
|
+
state.onError = (error, context$1) => {
|
|
508
656
|
const result = localErrorHandler(error, context$1);
|
|
509
657
|
if (result === void 0) return onErrorParent(error, context$1);
|
|
510
658
|
return result;
|
|
511
659
|
};
|
|
512
660
|
}
|
|
513
|
-
if (it.error) try {
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
pathname,
|
|
518
|
-
search
|
|
661
|
+
if (!it.error) try {
|
|
662
|
+
const element = await this.createElement(it.route, {
|
|
663
|
+
...props,
|
|
664
|
+
...context
|
|
519
665
|
});
|
|
520
|
-
|
|
521
|
-
|
|
666
|
+
state.layers.push({
|
|
667
|
+
name: it.route.name,
|
|
668
|
+
props,
|
|
669
|
+
part: it.route.path,
|
|
670
|
+
config: it.config,
|
|
671
|
+
element: this.renderView(i + 1, path, element, it.route),
|
|
672
|
+
index: i + 1,
|
|
673
|
+
path,
|
|
674
|
+
route: it.route,
|
|
675
|
+
cache: it.cache
|
|
676
|
+
});
|
|
677
|
+
} catch (e) {
|
|
678
|
+
it.error = e;
|
|
679
|
+
}
|
|
680
|
+
if (it.error) try {
|
|
681
|
+
let element = await state.onError(it.error, state);
|
|
682
|
+
if (element === void 0) throw it.error;
|
|
683
|
+
if (element instanceof Redirection) return { redirect: element.redirect };
|
|
684
|
+
if (element === null) element = this.renderError(it.error);
|
|
685
|
+
state.layers.push({
|
|
522
686
|
props,
|
|
523
687
|
error: it.error,
|
|
524
688
|
name: it.route.name,
|
|
525
689
|
part: it.route.path,
|
|
526
690
|
config: it.config,
|
|
527
|
-
element: this.renderView(i + 1, path, element
|
|
691
|
+
element: this.renderView(i + 1, path, element, it.route),
|
|
528
692
|
index: i + 1,
|
|
529
693
|
path,
|
|
530
694
|
route: it.route
|
|
531
695
|
});
|
|
532
696
|
break;
|
|
533
697
|
} catch (e) {
|
|
534
|
-
if (e instanceof Redirection) return
|
|
535
|
-
pathname,
|
|
536
|
-
search
|
|
537
|
-
});
|
|
698
|
+
if (e instanceof Redirection) return { redirect: e.redirect };
|
|
538
699
|
throw e;
|
|
539
700
|
}
|
|
540
|
-
const element = await this.createElement(it.route, {
|
|
541
|
-
...props,
|
|
542
|
-
...context
|
|
543
|
-
});
|
|
544
|
-
layers.push({
|
|
545
|
-
name: it.route.name,
|
|
546
|
-
props,
|
|
547
|
-
part: it.route.path,
|
|
548
|
-
config: it.config,
|
|
549
|
-
element: this.renderView(i + 1, path, element, it.route),
|
|
550
|
-
index: i + 1,
|
|
551
|
-
path,
|
|
552
|
-
route: it.route,
|
|
553
|
-
cache: it.cache
|
|
554
|
-
});
|
|
555
701
|
}
|
|
556
|
-
return {
|
|
557
|
-
layers,
|
|
558
|
-
pathname,
|
|
559
|
-
search
|
|
560
|
-
};
|
|
702
|
+
return { state };
|
|
561
703
|
}
|
|
562
|
-
createRedirectionLayer(
|
|
563
|
-
return {
|
|
564
|
-
layers: [],
|
|
565
|
-
redirect: typeof href === "string" ? href : this.href(href),
|
|
566
|
-
pathname: context.pathname,
|
|
567
|
-
search: context.search
|
|
568
|
-
};
|
|
704
|
+
createRedirectionLayer(redirect) {
|
|
705
|
+
return { redirect };
|
|
569
706
|
}
|
|
570
707
|
getErrorHandler(route) {
|
|
571
708
|
if (route.errorHandler) return route.errorHandler;
|
|
@@ -576,6 +713,7 @@ var PageDescriptorProvider = class {
|
|
|
576
713
|
}
|
|
577
714
|
}
|
|
578
715
|
async createElement(page, props) {
|
|
716
|
+
if (page.lazy && page.component) this.log.warn(`Page ${page.name} has both lazy and component options, lazy will be used`);
|
|
579
717
|
if (page.lazy) {
|
|
580
718
|
const component = await page.lazy();
|
|
581
719
|
return (0, react.createElement)(component.default, props);
|
|
@@ -584,7 +722,7 @@ var PageDescriptorProvider = class {
|
|
|
584
722
|
return void 0;
|
|
585
723
|
}
|
|
586
724
|
renderError(error) {
|
|
587
|
-
return (0, react.createElement)(
|
|
725
|
+
return (0, react.createElement)(ErrorViewer, {
|
|
588
726
|
error,
|
|
589
727
|
alepha: this.alepha
|
|
590
728
|
});
|
|
@@ -610,7 +748,7 @@ var PageDescriptorProvider = class {
|
|
|
610
748
|
}
|
|
611
749
|
renderView(index, path, view, page) {
|
|
612
750
|
view ??= this.renderEmptyView();
|
|
613
|
-
const element = page.client ? (0, react.createElement)(
|
|
751
|
+
const element = page.client ? (0, react.createElement)(ClientOnly, typeof page.client === "object" ? page.client : {}, view) : view;
|
|
614
752
|
return (0, react.createElement)(RouterLayerContext.Provider, { value: {
|
|
615
753
|
index,
|
|
616
754
|
path
|
|
@@ -691,218 +829,9 @@ const isPageRoute = (it) => {
|
|
|
691
829
|
return it && typeof it === "object" && typeof it.path === "string" && typeof it.page === "object";
|
|
692
830
|
};
|
|
693
831
|
|
|
694
|
-
//#endregion
|
|
695
|
-
//#region src/providers/BrowserRouterProvider.ts
|
|
696
|
-
var BrowserRouterProvider = class extends __alepha_router.RouterProvider {
|
|
697
|
-
log = (0, __alepha_core.$logger)();
|
|
698
|
-
alepha = (0, __alepha_core.$inject)(__alepha_core.Alepha);
|
|
699
|
-
pageDescriptorProvider = (0, __alepha_core.$inject)(PageDescriptorProvider);
|
|
700
|
-
add(entry) {
|
|
701
|
-
this.pageDescriptorProvider.add(entry);
|
|
702
|
-
}
|
|
703
|
-
configure = (0, __alepha_core.$hook)({
|
|
704
|
-
on: "configure",
|
|
705
|
-
handler: async () => {
|
|
706
|
-
for (const page of this.pageDescriptorProvider.getPages()) if (page.component || page.lazy) this.push({
|
|
707
|
-
path: page.match,
|
|
708
|
-
page
|
|
709
|
-
});
|
|
710
|
-
}
|
|
711
|
-
});
|
|
712
|
-
async transition(url, options = {}) {
|
|
713
|
-
const { pathname, search } = url;
|
|
714
|
-
const state = {
|
|
715
|
-
pathname,
|
|
716
|
-
search,
|
|
717
|
-
layers: []
|
|
718
|
-
};
|
|
719
|
-
const context = {
|
|
720
|
-
url,
|
|
721
|
-
query: {},
|
|
722
|
-
params: {},
|
|
723
|
-
onError: () => null,
|
|
724
|
-
...options.context ?? {}
|
|
725
|
-
};
|
|
726
|
-
await this.alepha.emit("react:transition:begin", {
|
|
727
|
-
state,
|
|
728
|
-
context
|
|
729
|
-
});
|
|
730
|
-
try {
|
|
731
|
-
const previous = options.previous;
|
|
732
|
-
const { route, params } = this.match(pathname);
|
|
733
|
-
const query = {};
|
|
734
|
-
if (search) for (const [key, value] of new URLSearchParams(search).entries()) query[key] = String(value);
|
|
735
|
-
context.query = query;
|
|
736
|
-
context.params = params ?? {};
|
|
737
|
-
context.previous = previous;
|
|
738
|
-
if (isPageRoute(route)) {
|
|
739
|
-
const result = await this.pageDescriptorProvider.createLayers(route.page, context);
|
|
740
|
-
if (result.redirect) return {
|
|
741
|
-
redirect: result.redirect,
|
|
742
|
-
state,
|
|
743
|
-
context
|
|
744
|
-
};
|
|
745
|
-
state.layers = result.layers;
|
|
746
|
-
}
|
|
747
|
-
if (state.layers.length === 0) state.layers.push({
|
|
748
|
-
name: "not-found",
|
|
749
|
-
element: (0, react.createElement)(NotFoundPage),
|
|
750
|
-
index: 0,
|
|
751
|
-
path: "/"
|
|
752
|
-
});
|
|
753
|
-
await this.alepha.emit("react:transition:success", {
|
|
754
|
-
state,
|
|
755
|
-
context
|
|
756
|
-
});
|
|
757
|
-
} catch (e) {
|
|
758
|
-
this.log.error(e);
|
|
759
|
-
state.layers = [{
|
|
760
|
-
name: "error",
|
|
761
|
-
element: this.pageDescriptorProvider.renderError(e),
|
|
762
|
-
index: 0,
|
|
763
|
-
path: "/"
|
|
764
|
-
}];
|
|
765
|
-
await this.alepha.emit("react:transition:error", {
|
|
766
|
-
error: e,
|
|
767
|
-
state,
|
|
768
|
-
context
|
|
769
|
-
});
|
|
770
|
-
}
|
|
771
|
-
if (options.state) {
|
|
772
|
-
options.state.layers = state.layers;
|
|
773
|
-
options.state.pathname = state.pathname;
|
|
774
|
-
options.state.search = state.search;
|
|
775
|
-
}
|
|
776
|
-
if (options.previous) for (let i = 0; i < options.previous.length; i++) {
|
|
777
|
-
const layer = options.previous[i];
|
|
778
|
-
if (state.layers[i]?.name !== layer.name) this.pageDescriptorProvider.page(layer.name)?.onLeave?.();
|
|
779
|
-
}
|
|
780
|
-
await this.alepha.emit("react:transition:end", {
|
|
781
|
-
state: options.state,
|
|
782
|
-
context
|
|
783
|
-
});
|
|
784
|
-
return {
|
|
785
|
-
context,
|
|
786
|
-
state
|
|
787
|
-
};
|
|
788
|
-
}
|
|
789
|
-
root(state, context) {
|
|
790
|
-
return this.pageDescriptorProvider.root(state, context);
|
|
791
|
-
}
|
|
792
|
-
};
|
|
793
|
-
|
|
794
|
-
//#endregion
|
|
795
|
-
//#region src/providers/ReactBrowserProvider.ts
|
|
796
|
-
var ReactBrowserProvider = class {
|
|
797
|
-
log = (0, __alepha_core.$logger)();
|
|
798
|
-
client = (0, __alepha_core.$inject)(__alepha_server_links.LinkProvider);
|
|
799
|
-
alepha = (0, __alepha_core.$inject)(__alepha_core.Alepha);
|
|
800
|
-
router = (0, __alepha_core.$inject)(BrowserRouterProvider);
|
|
801
|
-
root;
|
|
802
|
-
transitioning;
|
|
803
|
-
state = {
|
|
804
|
-
layers: [],
|
|
805
|
-
pathname: "",
|
|
806
|
-
search: ""
|
|
807
|
-
};
|
|
808
|
-
get document() {
|
|
809
|
-
return window.document;
|
|
810
|
-
}
|
|
811
|
-
get history() {
|
|
812
|
-
return window.history;
|
|
813
|
-
}
|
|
814
|
-
get location() {
|
|
815
|
-
return window.location;
|
|
816
|
-
}
|
|
817
|
-
get url() {
|
|
818
|
-
let url = this.location.pathname + this.location.search;
|
|
819
|
-
return url;
|
|
820
|
-
}
|
|
821
|
-
pushState(url, replace) {
|
|
822
|
-
let path = url;
|
|
823
|
-
if (replace) this.history.replaceState({}, "", path);
|
|
824
|
-
else this.history.pushState({}, "", path);
|
|
825
|
-
}
|
|
826
|
-
async invalidate(props) {
|
|
827
|
-
const previous = [];
|
|
828
|
-
if (props) {
|
|
829
|
-
const [key] = Object.keys(props);
|
|
830
|
-
const value = props[key];
|
|
831
|
-
for (const layer of this.state.layers) {
|
|
832
|
-
if (layer.props?.[key]) {
|
|
833
|
-
previous.push({
|
|
834
|
-
...layer,
|
|
835
|
-
props: {
|
|
836
|
-
...layer.props,
|
|
837
|
-
[key]: value
|
|
838
|
-
}
|
|
839
|
-
});
|
|
840
|
-
break;
|
|
841
|
-
}
|
|
842
|
-
previous.push(layer);
|
|
843
|
-
}
|
|
844
|
-
}
|
|
845
|
-
await this.render({ previous });
|
|
846
|
-
}
|
|
847
|
-
async go(url, options = {}) {
|
|
848
|
-
const result = await this.render({ url });
|
|
849
|
-
if (result.context.url.pathname + result.context.url.search !== url) {
|
|
850
|
-
this.pushState(result.context.url.pathname + result.context.url.search);
|
|
851
|
-
return;
|
|
852
|
-
}
|
|
853
|
-
this.pushState(url, options.replace);
|
|
854
|
-
}
|
|
855
|
-
async render(options = {}) {
|
|
856
|
-
const previous = options.previous ?? this.state.layers;
|
|
857
|
-
const url = options.url ?? this.url;
|
|
858
|
-
this.transitioning = { to: url };
|
|
859
|
-
const result = await this.router.transition(new URL(`http://localhost${url}`), {
|
|
860
|
-
previous,
|
|
861
|
-
state: this.state
|
|
862
|
-
});
|
|
863
|
-
if (result.redirect) return await this.render({ url: result.redirect });
|
|
864
|
-
this.transitioning = void 0;
|
|
865
|
-
return result;
|
|
866
|
-
}
|
|
867
|
-
/**
|
|
868
|
-
* Get embedded layers from the server.
|
|
869
|
-
*/
|
|
870
|
-
getHydrationState() {
|
|
871
|
-
try {
|
|
872
|
-
if ("__ssr" in window && typeof window.__ssr === "object") return window.__ssr;
|
|
873
|
-
} catch (error) {
|
|
874
|
-
console.error(error);
|
|
875
|
-
}
|
|
876
|
-
}
|
|
877
|
-
ready = (0, __alepha_core.$hook)({
|
|
878
|
-
on: "ready",
|
|
879
|
-
handler: async () => {
|
|
880
|
-
const hydration = this.getHydrationState();
|
|
881
|
-
const previous = hydration?.layers ?? [];
|
|
882
|
-
if (hydration) {
|
|
883
|
-
for (const [key, value] of Object.entries(hydration)) if (key !== "layers" && key !== "links") this.alepha.state(key, value);
|
|
884
|
-
}
|
|
885
|
-
if (hydration?.links) for (const link of hydration.links.links) this.client.pushLink({
|
|
886
|
-
...link,
|
|
887
|
-
prefix: hydration.links.prefix
|
|
888
|
-
});
|
|
889
|
-
const { context } = await this.render({ previous });
|
|
890
|
-
await this.alepha.emit("react:browser:render", {
|
|
891
|
-
state: this.state,
|
|
892
|
-
context,
|
|
893
|
-
hydration
|
|
894
|
-
});
|
|
895
|
-
window.addEventListener("popstate", () => {
|
|
896
|
-
if (this.state.pathname === this.url) return;
|
|
897
|
-
this.render();
|
|
898
|
-
});
|
|
899
|
-
}
|
|
900
|
-
});
|
|
901
|
-
};
|
|
902
|
-
|
|
903
832
|
//#endregion
|
|
904
833
|
//#region src/providers/ReactServerProvider.ts
|
|
905
|
-
const envSchema = __alepha_core.t.object({
|
|
834
|
+
const envSchema$1 = __alepha_core.t.object({
|
|
906
835
|
REACT_SERVER_DIST: __alepha_core.t.string({ default: "public" }),
|
|
907
836
|
REACT_SERVER_PREFIX: __alepha_core.t.string({ default: "" }),
|
|
908
837
|
REACT_SSR_ENABLED: __alepha_core.t.optional(__alepha_core.t.boolean()),
|
|
@@ -910,13 +839,14 @@ const envSchema = __alepha_core.t.object({
|
|
|
910
839
|
REACT_SERVER_TEMPLATE: __alepha_core.t.optional(__alepha_core.t.string({ size: "rich" }))
|
|
911
840
|
});
|
|
912
841
|
var ReactServerProvider = class {
|
|
913
|
-
log = (0,
|
|
842
|
+
log = (0, __alepha_logger.$logger)();
|
|
914
843
|
alepha = (0, __alepha_core.$inject)(__alepha_core.Alepha);
|
|
915
|
-
|
|
844
|
+
pageApi = (0, __alepha_core.$inject)(ReactPageProvider);
|
|
845
|
+
serverProvider = (0, __alepha_core.$inject)(__alepha_server.ServerProvider);
|
|
916
846
|
serverStaticProvider = (0, __alepha_core.$inject)(__alepha_server_static.ServerStaticProvider);
|
|
917
847
|
serverRouterProvider = (0, __alepha_core.$inject)(__alepha_server.ServerRouterProvider);
|
|
918
848
|
serverTimingProvider = (0, __alepha_core.$inject)(__alepha_server.ServerTimingProvider);
|
|
919
|
-
env = (0, __alepha_core.$env)(envSchema);
|
|
849
|
+
env = (0, __alepha_core.$env)(envSchema$1);
|
|
920
850
|
ROOT_DIV_REGEX = new RegExp(`<div([^>]*)\\s+id=["']${this.env.REACT_ROOT_ID}["']([^>]*)>(.*?)<\\/div>`, "is");
|
|
921
851
|
onConfigure = (0, __alepha_core.$hook)({
|
|
922
852
|
on: "configure",
|
|
@@ -924,7 +854,23 @@ var ReactServerProvider = class {
|
|
|
924
854
|
const pages = this.alepha.descriptors($page);
|
|
925
855
|
const ssrEnabled = pages.length > 0 && this.env.REACT_SSR_ENABLED !== false;
|
|
926
856
|
this.alepha.state("react.server.ssr", ssrEnabled);
|
|
927
|
-
for (const page of pages)
|
|
857
|
+
for (const page of pages) {
|
|
858
|
+
page.render = this.createRenderFunction(page.name);
|
|
859
|
+
page.fetch = async (options) => {
|
|
860
|
+
const response = await fetch(`${this.serverProvider.hostname}/${page.pathname(options)}`);
|
|
861
|
+
const html = await response.text();
|
|
862
|
+
if (options?.html) return {
|
|
863
|
+
html,
|
|
864
|
+
response
|
|
865
|
+
};
|
|
866
|
+
const match = html.match(this.ROOT_DIV_REGEX);
|
|
867
|
+
if (match) return {
|
|
868
|
+
html: match[3],
|
|
869
|
+
response
|
|
870
|
+
};
|
|
871
|
+
throw new __alepha_core.AlephaError("Invalid HTML response");
|
|
872
|
+
};
|
|
873
|
+
}
|
|
928
874
|
if (this.alepha.isServerless() === "vite") {
|
|
929
875
|
await this.configureVite(ssrEnabled);
|
|
930
876
|
return;
|
|
@@ -963,7 +909,7 @@ var ReactServerProvider = class {
|
|
|
963
909
|
return this.alepha.env.REACT_SERVER_TEMPLATE ?? "<!DOCTYPE html><html lang='en'><head></head><body></body></html>";
|
|
964
910
|
}
|
|
965
911
|
async registerPages(templateLoader) {
|
|
966
|
-
for (const page of this.
|
|
912
|
+
for (const page of this.pageApi.getPages()) {
|
|
967
913
|
if (page.children?.length) continue;
|
|
968
914
|
this.log.debug(`+ ${page.match} -> ${page.name}`);
|
|
969
915
|
this.serverRouterProvider.createRoute({
|
|
@@ -997,25 +943,39 @@ var ReactServerProvider = class {
|
|
|
997
943
|
*/
|
|
998
944
|
createRenderFunction(name, withIndex = false) {
|
|
999
945
|
return async (options = {}) => {
|
|
1000
|
-
const page = this.
|
|
1001
|
-
const url = new URL(this.
|
|
1002
|
-
const
|
|
946
|
+
const page = this.pageApi.page(name);
|
|
947
|
+
const url = new URL(this.pageApi.url(name, options));
|
|
948
|
+
const entry = {
|
|
1003
949
|
url,
|
|
1004
950
|
params: options.params ?? {},
|
|
1005
951
|
query: options.query ?? {},
|
|
1006
|
-
|
|
1007
|
-
|
|
952
|
+
onError: () => null,
|
|
953
|
+
layers: [],
|
|
954
|
+
meta: {}
|
|
1008
955
|
};
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
956
|
+
const state = entry;
|
|
957
|
+
this.log.trace("Rendering", { url });
|
|
958
|
+
await this.alepha.emit("react:server:render:begin", { state });
|
|
959
|
+
const { redirect } = await this.pageApi.createLayers(page, state);
|
|
960
|
+
if (redirect) return {
|
|
961
|
+
state,
|
|
962
|
+
html: "",
|
|
963
|
+
redirect
|
|
964
|
+
};
|
|
965
|
+
if (!withIndex && !options.html) {
|
|
966
|
+
this.alepha.state("react.router.state", state);
|
|
967
|
+
return {
|
|
968
|
+
state,
|
|
969
|
+
html: (0, react_dom_server.renderToString)(this.pageApi.root(state))
|
|
970
|
+
};
|
|
971
|
+
}
|
|
972
|
+
const html = this.renderToHtml(this.template ?? "", state, options.hydration);
|
|
973
|
+
if (html instanceof Redirection) return {
|
|
974
|
+
state,
|
|
975
|
+
html: "",
|
|
976
|
+
redirect
|
|
1014
977
|
};
|
|
1015
|
-
const html = this.renderToHtml(this.template ?? "", state, context, options.hydration);
|
|
1016
|
-
if (html instanceof Redirection) throw new Error("Redirection is not supported in this context");
|
|
1017
978
|
const result = {
|
|
1018
|
-
context,
|
|
1019
979
|
state,
|
|
1020
980
|
html
|
|
1021
981
|
};
|
|
@@ -1023,89 +983,82 @@ var ReactServerProvider = class {
|
|
|
1023
983
|
return result;
|
|
1024
984
|
};
|
|
1025
985
|
}
|
|
1026
|
-
createHandler(
|
|
986
|
+
createHandler(route, templateLoader) {
|
|
1027
987
|
return async (serverRequest) => {
|
|
1028
988
|
const { url, reply, query, params } = serverRequest;
|
|
1029
989
|
const template = await templateLoader();
|
|
1030
990
|
if (!template) throw new Error("Template not found");
|
|
1031
|
-
this.log.trace("Rendering page", { name:
|
|
1032
|
-
const
|
|
991
|
+
this.log.trace("Rendering page", { name: route.name });
|
|
992
|
+
const entry = {
|
|
1033
993
|
url,
|
|
1034
994
|
params,
|
|
1035
995
|
query,
|
|
1036
|
-
|
|
1037
|
-
|
|
996
|
+
onError: () => null,
|
|
997
|
+
layers: []
|
|
1038
998
|
};
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
}));
|
|
1046
|
-
this.alepha.context.set("links", context.links);
|
|
1047
|
-
}
|
|
1048
|
-
let target = page;
|
|
999
|
+
const state = entry;
|
|
1000
|
+
if (this.alepha.has(__alepha_server_links.ServerLinksProvider)) this.alepha.state("api", await this.alepha.inject(__alepha_server_links.ServerLinksProvider).getUserApiLinks({
|
|
1001
|
+
user: serverRequest.user,
|
|
1002
|
+
authorization: serverRequest.headers.authorization
|
|
1003
|
+
}));
|
|
1004
|
+
let target = route;
|
|
1049
1005
|
while (target) {
|
|
1050
|
-
if (
|
|
1006
|
+
if (route.can && !route.can()) {
|
|
1051
1007
|
reply.status = 403;
|
|
1052
1008
|
reply.headers["content-type"] = "text/plain";
|
|
1053
1009
|
return "Forbidden";
|
|
1054
1010
|
}
|
|
1055
1011
|
target = target.parent;
|
|
1056
1012
|
}
|
|
1057
|
-
await this.alepha.emit("react:transition:begin", {
|
|
1058
|
-
request: serverRequest,
|
|
1059
|
-
context
|
|
1060
|
-
});
|
|
1061
1013
|
await this.alepha.emit("react:server:render:begin", {
|
|
1062
1014
|
request: serverRequest,
|
|
1063
|
-
|
|
1015
|
+
state
|
|
1064
1016
|
});
|
|
1065
1017
|
this.serverTimingProvider.beginTiming("createLayers");
|
|
1066
|
-
const
|
|
1018
|
+
const { redirect } = await this.pageApi.createLayers(route, state);
|
|
1067
1019
|
this.serverTimingProvider.endTiming("createLayers");
|
|
1068
|
-
if (
|
|
1020
|
+
if (redirect) return reply.redirect(redirect);
|
|
1069
1021
|
reply.headers["content-type"] = "text/html";
|
|
1070
1022
|
reply.headers["cache-control"] = "no-store, no-cache, must-revalidate, proxy-revalidate";
|
|
1071
1023
|
reply.headers.pragma = "no-cache";
|
|
1072
1024
|
reply.headers.expires = "0";
|
|
1073
|
-
|
|
1074
|
-
const html = this.renderToHtml(template, state, context);
|
|
1025
|
+
const html = this.renderToHtml(template, state);
|
|
1075
1026
|
if (html instanceof Redirection) {
|
|
1076
|
-
reply.redirect(typeof html.
|
|
1027
|
+
reply.redirect(typeof html.redirect === "string" ? html.redirect : this.pageApi.href(html.redirect));
|
|
1077
1028
|
return;
|
|
1078
1029
|
}
|
|
1079
1030
|
const event = {
|
|
1080
1031
|
request: serverRequest,
|
|
1081
|
-
context,
|
|
1082
1032
|
state,
|
|
1083
1033
|
html
|
|
1084
1034
|
};
|
|
1085
1035
|
await this.alepha.emit("react:server:render:end", event);
|
|
1086
|
-
|
|
1087
|
-
this.log.trace("Page rendered", { name:
|
|
1036
|
+
route.onServerResponse?.(serverRequest);
|
|
1037
|
+
this.log.trace("Page rendered", { name: route.name });
|
|
1088
1038
|
return event.html;
|
|
1089
1039
|
};
|
|
1090
1040
|
}
|
|
1091
|
-
renderToHtml(template, state,
|
|
1092
|
-
const element = this.
|
|
1041
|
+
renderToHtml(template, state, hydration = true) {
|
|
1042
|
+
const element = this.pageApi.root(state);
|
|
1043
|
+
this.alepha.state("react.router.state", state);
|
|
1093
1044
|
this.serverTimingProvider.beginTiming("renderToString");
|
|
1094
1045
|
let app = "";
|
|
1095
1046
|
try {
|
|
1096
1047
|
app = (0, react_dom_server.renderToString)(element);
|
|
1097
1048
|
} catch (error) {
|
|
1098
|
-
this.log.error("
|
|
1099
|
-
const element$1 =
|
|
1049
|
+
this.log.error("renderToString has failed, fallback to error handler", error);
|
|
1050
|
+
const element$1 = state.onError(error, state);
|
|
1100
1051
|
if (element$1 instanceof Redirection) return element$1;
|
|
1101
1052
|
app = (0, react_dom_server.renderToString)(element$1);
|
|
1053
|
+
this.log.debug("Error handled successfully with fallback");
|
|
1102
1054
|
}
|
|
1103
1055
|
this.serverTimingProvider.endTiming("renderToString");
|
|
1104
1056
|
const response = { html: template };
|
|
1105
1057
|
if (hydration) {
|
|
1106
|
-
const { request, context
|
|
1058
|
+
const { request, context,...store } = this.alepha.context.als?.getStore() ?? {};
|
|
1107
1059
|
const hydrationData = {
|
|
1108
|
-
...
|
|
1060
|
+
...store,
|
|
1061
|
+
"react.router.state": void 0,
|
|
1109
1062
|
layers: state.layers.map((it) => ({
|
|
1110
1063
|
...it,
|
|
1111
1064
|
error: it.error ? {
|
|
@@ -1120,7 +1073,7 @@ var ReactServerProvider = class {
|
|
|
1120
1073
|
route: void 0
|
|
1121
1074
|
}))
|
|
1122
1075
|
};
|
|
1123
|
-
const script = `<script>window.__ssr=${JSON.stringify(hydrationData)}
|
|
1076
|
+
const script = `<script>window.__ssr=${JSON.stringify(hydrationData)}<\/script>`;
|
|
1124
1077
|
this.fillTemplate(response, app, script);
|
|
1125
1078
|
}
|
|
1126
1079
|
return response.html;
|
|
@@ -1141,27 +1094,262 @@ var ReactServerProvider = class {
|
|
|
1141
1094
|
};
|
|
1142
1095
|
|
|
1143
1096
|
//#endregion
|
|
1144
|
-
//#region src/
|
|
1145
|
-
var
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
this.
|
|
1151
|
-
|
|
1152
|
-
|
|
1097
|
+
//#region src/providers/ReactBrowserRouterProvider.ts
|
|
1098
|
+
var ReactBrowserRouterProvider = class extends __alepha_router.RouterProvider {
|
|
1099
|
+
log = (0, __alepha_logger.$logger)();
|
|
1100
|
+
alepha = (0, __alepha_core.$inject)(__alepha_core.Alepha);
|
|
1101
|
+
pageApi = (0, __alepha_core.$inject)(ReactPageProvider);
|
|
1102
|
+
add(entry) {
|
|
1103
|
+
this.pageApi.add(entry);
|
|
1104
|
+
}
|
|
1105
|
+
configure = (0, __alepha_core.$hook)({
|
|
1106
|
+
on: "configure",
|
|
1107
|
+
handler: async () => {
|
|
1108
|
+
for (const page of this.pageApi.getPages()) if (page.component || page.lazy) this.push({
|
|
1109
|
+
path: page.match,
|
|
1110
|
+
page
|
|
1111
|
+
});
|
|
1112
|
+
}
|
|
1113
|
+
});
|
|
1114
|
+
async transition(url, previous = [], meta = {}) {
|
|
1115
|
+
const { pathname, search } = url;
|
|
1116
|
+
const entry = {
|
|
1117
|
+
url,
|
|
1118
|
+
query: {},
|
|
1119
|
+
params: {},
|
|
1120
|
+
layers: [],
|
|
1121
|
+
onError: () => null,
|
|
1122
|
+
meta
|
|
1123
|
+
};
|
|
1124
|
+
const state = entry;
|
|
1125
|
+
await this.alepha.emit("react:transition:begin", {
|
|
1126
|
+
previous: this.alepha.state("react.router.state"),
|
|
1127
|
+
state
|
|
1128
|
+
});
|
|
1129
|
+
try {
|
|
1130
|
+
const { route, params } = this.match(pathname);
|
|
1131
|
+
const query = {};
|
|
1132
|
+
if (search) for (const [key, value] of new URLSearchParams(search).entries()) query[key] = String(value);
|
|
1133
|
+
state.query = query;
|
|
1134
|
+
state.params = params ?? {};
|
|
1135
|
+
if (isPageRoute(route)) {
|
|
1136
|
+
const { redirect } = await this.pageApi.createLayers(route.page, state, previous);
|
|
1137
|
+
if (redirect) return redirect;
|
|
1138
|
+
}
|
|
1139
|
+
if (state.layers.length === 0) state.layers.push({
|
|
1140
|
+
name: "not-found",
|
|
1141
|
+
element: (0, react.createElement)(NotFoundPage),
|
|
1142
|
+
index: 0,
|
|
1143
|
+
path: "/"
|
|
1144
|
+
});
|
|
1145
|
+
await this.alepha.emit("react:transition:success", { state });
|
|
1146
|
+
} catch (e) {
|
|
1147
|
+
this.log.error("Transition has failed", e);
|
|
1148
|
+
state.layers = [{
|
|
1149
|
+
name: "error",
|
|
1150
|
+
element: this.pageApi.renderError(e),
|
|
1151
|
+
index: 0,
|
|
1152
|
+
path: "/"
|
|
1153
|
+
}];
|
|
1154
|
+
await this.alepha.emit("react:transition:error", {
|
|
1155
|
+
error: e,
|
|
1156
|
+
state
|
|
1157
|
+
});
|
|
1158
|
+
}
|
|
1159
|
+
if (previous) for (let i = 0; i < previous.length; i++) {
|
|
1160
|
+
const layer = previous[i];
|
|
1161
|
+
if (state.layers[i]?.name !== layer.name) this.pageApi.page(layer.name)?.onLeave?.();
|
|
1162
|
+
}
|
|
1163
|
+
this.alepha.state("react.router.state", state);
|
|
1164
|
+
await this.alepha.emit("react:transition:end", { state });
|
|
1165
|
+
}
|
|
1166
|
+
root(state) {
|
|
1167
|
+
return this.pageApi.root(state);
|
|
1168
|
+
}
|
|
1169
|
+
};
|
|
1170
|
+
|
|
1171
|
+
//#endregion
|
|
1172
|
+
//#region src/providers/ReactBrowserProvider.ts
|
|
1173
|
+
const envSchema = __alepha_core.t.object({ REACT_ROOT_ID: __alepha_core.t.string({ default: "root" }) });
|
|
1174
|
+
var ReactBrowserProvider = class {
|
|
1175
|
+
env = (0, __alepha_core.$env)(envSchema);
|
|
1176
|
+
log = (0, __alepha_logger.$logger)();
|
|
1177
|
+
client = (0, __alepha_core.$inject)(__alepha_server_links.LinkProvider);
|
|
1178
|
+
alepha = (0, __alepha_core.$inject)(__alepha_core.Alepha);
|
|
1179
|
+
router = (0, __alepha_core.$inject)(ReactBrowserRouterProvider);
|
|
1180
|
+
dateTimeProvider = (0, __alepha_core.$inject)(__alepha_datetime.DateTimeProvider);
|
|
1181
|
+
options = { scrollRestoration: "top" };
|
|
1182
|
+
getRootElement() {
|
|
1183
|
+
const root = this.document.getElementById(this.env.REACT_ROOT_ID);
|
|
1184
|
+
if (root) return root;
|
|
1185
|
+
const div = this.document.createElement("div");
|
|
1186
|
+
div.id = this.env.REACT_ROOT_ID;
|
|
1187
|
+
this.document.body.prepend(div);
|
|
1188
|
+
return div;
|
|
1189
|
+
}
|
|
1190
|
+
transitioning;
|
|
1191
|
+
get state() {
|
|
1192
|
+
return this.alepha.state("react.router.state");
|
|
1193
|
+
}
|
|
1194
|
+
/**
|
|
1195
|
+
* Accessor for Document DOM API.
|
|
1196
|
+
*/
|
|
1197
|
+
get document() {
|
|
1198
|
+
return window.document;
|
|
1199
|
+
}
|
|
1200
|
+
/**
|
|
1201
|
+
* Accessor for History DOM API.
|
|
1202
|
+
*/
|
|
1203
|
+
get history() {
|
|
1204
|
+
return window.history;
|
|
1205
|
+
}
|
|
1206
|
+
/**
|
|
1207
|
+
* Accessor for Location DOM API.
|
|
1208
|
+
*/
|
|
1209
|
+
get location() {
|
|
1210
|
+
return window.location;
|
|
1211
|
+
}
|
|
1212
|
+
get base() {
|
|
1213
|
+
const base = {}.env?.BASE_URL;
|
|
1214
|
+
if (!base || base === "/") return "";
|
|
1215
|
+
return base;
|
|
1216
|
+
}
|
|
1217
|
+
get url() {
|
|
1218
|
+
const url = this.location.pathname + this.location.search;
|
|
1219
|
+
if (this.base) return url.replace(this.base, "");
|
|
1220
|
+
return url;
|
|
1221
|
+
}
|
|
1222
|
+
pushState(path, replace) {
|
|
1223
|
+
const url = this.base + path;
|
|
1224
|
+
if (replace) this.history.replaceState({}, "", url);
|
|
1225
|
+
else this.history.pushState({}, "", url);
|
|
1226
|
+
}
|
|
1227
|
+
async invalidate(props) {
|
|
1228
|
+
const previous = [];
|
|
1229
|
+
this.log.trace("Invalidating layers");
|
|
1230
|
+
if (props) {
|
|
1231
|
+
const [key] = Object.keys(props);
|
|
1232
|
+
const value = props[key];
|
|
1233
|
+
for (const layer of this.state.layers) {
|
|
1234
|
+
if (layer.props?.[key]) {
|
|
1235
|
+
previous.push({
|
|
1236
|
+
...layer,
|
|
1237
|
+
props: {
|
|
1238
|
+
...layer.props,
|
|
1239
|
+
[key]: value
|
|
1240
|
+
}
|
|
1241
|
+
});
|
|
1242
|
+
break;
|
|
1243
|
+
}
|
|
1244
|
+
previous.push(layer);
|
|
1245
|
+
}
|
|
1246
|
+
}
|
|
1247
|
+
await this.render({ previous });
|
|
1248
|
+
}
|
|
1249
|
+
async go(url, options = {}) {
|
|
1250
|
+
this.log.trace(`Going to ${url}`, {
|
|
1251
|
+
url,
|
|
1252
|
+
options
|
|
1253
|
+
});
|
|
1254
|
+
await this.render({
|
|
1255
|
+
url,
|
|
1256
|
+
previous: options.force ? [] : this.state.layers,
|
|
1257
|
+
meta: options.meta
|
|
1258
|
+
});
|
|
1259
|
+
if (this.state.url.pathname + this.state.url.search !== url) {
|
|
1260
|
+
this.pushState(this.state.url.pathname + this.state.url.search);
|
|
1261
|
+
return;
|
|
1262
|
+
}
|
|
1263
|
+
this.pushState(url, options.replace);
|
|
1264
|
+
}
|
|
1265
|
+
async render(options = {}) {
|
|
1266
|
+
const previous = options.previous ?? this.state.layers;
|
|
1267
|
+
const url = options.url ?? this.url;
|
|
1268
|
+
const start = this.dateTimeProvider.now();
|
|
1269
|
+
this.transitioning = {
|
|
1270
|
+
to: url,
|
|
1271
|
+
from: this.state?.url.pathname
|
|
1272
|
+
};
|
|
1273
|
+
this.log.debug("Transitioning...", { to: url });
|
|
1274
|
+
const redirect = await this.router.transition(new URL(`http://localhost${url}`), previous, options.meta);
|
|
1275
|
+
if (redirect) {
|
|
1276
|
+
this.log.info("Redirecting to", { redirect });
|
|
1277
|
+
return await this.render({ url: redirect });
|
|
1278
|
+
}
|
|
1279
|
+
const ms = this.dateTimeProvider.now().diff(start);
|
|
1280
|
+
this.log.info(`Transition OK [${ms}ms]`, this.transitioning);
|
|
1281
|
+
this.transitioning = void 0;
|
|
1282
|
+
}
|
|
1283
|
+
/**
|
|
1284
|
+
* Get embedded layers from the server.
|
|
1285
|
+
*/
|
|
1286
|
+
getHydrationState() {
|
|
1287
|
+
try {
|
|
1288
|
+
if ("__ssr" in window && typeof window.__ssr === "object") return window.__ssr;
|
|
1289
|
+
} catch (error) {
|
|
1290
|
+
console.error(error);
|
|
1291
|
+
}
|
|
1292
|
+
}
|
|
1293
|
+
onTransitionEnd = (0, __alepha_core.$hook)({
|
|
1294
|
+
on: "react:transition:end",
|
|
1295
|
+
handler: () => {
|
|
1296
|
+
if (this.options.scrollRestoration === "top" && typeof window !== "undefined" && !this.alepha.isTest()) {
|
|
1297
|
+
this.log.trace("Restoring scroll position to top");
|
|
1298
|
+
window.scrollTo(0, 0);
|
|
1299
|
+
}
|
|
1300
|
+
}
|
|
1301
|
+
});
|
|
1302
|
+
ready = (0, __alepha_core.$hook)({
|
|
1303
|
+
on: "ready",
|
|
1304
|
+
handler: async () => {
|
|
1305
|
+
const hydration = this.getHydrationState();
|
|
1306
|
+
const previous = hydration?.layers ?? [];
|
|
1307
|
+
if (hydration) {
|
|
1308
|
+
for (const [key, value] of Object.entries(hydration)) if (key !== "layers") this.alepha.state(key, value);
|
|
1309
|
+
}
|
|
1310
|
+
await this.render({ previous });
|
|
1311
|
+
const element = this.router.root(this.state);
|
|
1312
|
+
await this.alepha.emit("react:browser:render", {
|
|
1313
|
+
element,
|
|
1314
|
+
root: this.getRootElement(),
|
|
1315
|
+
hydration,
|
|
1316
|
+
state: this.state
|
|
1317
|
+
});
|
|
1318
|
+
window.addEventListener("popstate", () => {
|
|
1319
|
+
if (this.base + this.state.url.pathname === this.location.pathname) return;
|
|
1320
|
+
this.log.debug("Popstate event triggered - rendering new state", { url: this.location.pathname + this.location.search });
|
|
1321
|
+
this.render();
|
|
1322
|
+
});
|
|
1323
|
+
}
|
|
1324
|
+
});
|
|
1325
|
+
};
|
|
1326
|
+
|
|
1327
|
+
//#endregion
|
|
1328
|
+
//#region src/services/ReactRouter.ts
|
|
1329
|
+
var ReactRouter = class {
|
|
1330
|
+
alepha = (0, __alepha_core.$inject)(__alepha_core.Alepha);
|
|
1331
|
+
pageApi = (0, __alepha_core.$inject)(ReactPageProvider);
|
|
1332
|
+
get state() {
|
|
1333
|
+
return this.alepha.state("react.router.state");
|
|
1334
|
+
}
|
|
1335
|
+
get pages() {
|
|
1336
|
+
return this.pageApi.getPages();
|
|
1337
|
+
}
|
|
1338
|
+
get browser() {
|
|
1339
|
+
if (this.alepha.isBrowser()) return this.alepha.inject(ReactBrowserProvider);
|
|
1340
|
+
return void 0;
|
|
1153
1341
|
}
|
|
1154
1342
|
path(name, config = {}) {
|
|
1155
1343
|
return this.pageApi.pathname(name, {
|
|
1156
1344
|
params: {
|
|
1157
|
-
...this.
|
|
1345
|
+
...this.state.params,
|
|
1158
1346
|
...config.params
|
|
1159
1347
|
},
|
|
1160
1348
|
query: config.query
|
|
1161
1349
|
});
|
|
1162
1350
|
}
|
|
1163
1351
|
getURL() {
|
|
1164
|
-
if (!this.browser) return this.
|
|
1352
|
+
if (!this.browser) return this.state.url;
|
|
1165
1353
|
return new URL(this.location.href);
|
|
1166
1354
|
}
|
|
1167
1355
|
get location() {
|
|
@@ -1172,11 +1360,11 @@ var RouterHookApi = class {
|
|
|
1172
1360
|
return this.state;
|
|
1173
1361
|
}
|
|
1174
1362
|
get pathname() {
|
|
1175
|
-
return this.state.pathname;
|
|
1363
|
+
return this.state.url.pathname;
|
|
1176
1364
|
}
|
|
1177
1365
|
get query() {
|
|
1178
1366
|
const query = {};
|
|
1179
|
-
for (const [key, value] of new URLSearchParams(this.state.search).entries()) query[key] = String(value);
|
|
1367
|
+
for (const [key, value] of new URLSearchParams(this.state.url.search).entries()) query[key] = String(value);
|
|
1180
1368
|
return query;
|
|
1181
1369
|
}
|
|
1182
1370
|
async back() {
|
|
@@ -1188,17 +1376,6 @@ var RouterHookApi = class {
|
|
|
1188
1376
|
async invalidate(props) {
|
|
1189
1377
|
await this.browser?.invalidate(props);
|
|
1190
1378
|
}
|
|
1191
|
-
/**
|
|
1192
|
-
* Create a valid href for the given pathname.
|
|
1193
|
-
*
|
|
1194
|
-
* @param pathname
|
|
1195
|
-
* @param layer
|
|
1196
|
-
*/
|
|
1197
|
-
createHref(pathname, layer = this.layer, options = {}) {
|
|
1198
|
-
if (typeof pathname === "object") pathname = pathname.options.path ?? "";
|
|
1199
|
-
if (options.params) for (const [key, value] of Object.entries(options.params)) pathname = pathname.replace(`:${key}`, String(value));
|
|
1200
|
-
return pathname.startsWith("/") ? pathname : `${layer.path}/${pathname}`.replace(/\/\/+/g, "/");
|
|
1201
|
-
}
|
|
1202
1379
|
async go(path, options) {
|
|
1203
1380
|
for (const page of this.pages) if (page.name === path) {
|
|
1204
1381
|
await this.browser?.go(this.path(path, options), options);
|
|
@@ -1213,7 +1390,7 @@ var RouterHookApi = class {
|
|
|
1213
1390
|
break;
|
|
1214
1391
|
}
|
|
1215
1392
|
return {
|
|
1216
|
-
href,
|
|
1393
|
+
href: this.base(href),
|
|
1217
1394
|
onClick: (ev) => {
|
|
1218
1395
|
ev.stopPropagation();
|
|
1219
1396
|
ev.preventDefault();
|
|
@@ -1221,6 +1398,11 @@ var RouterHookApi = class {
|
|
|
1221
1398
|
}
|
|
1222
1399
|
};
|
|
1223
1400
|
}
|
|
1401
|
+
base(path) {
|
|
1402
|
+
const base = {}.env?.BASE_URL;
|
|
1403
|
+
if (!base || base === "/") return path;
|
|
1404
|
+
return base + path;
|
|
1405
|
+
}
|
|
1224
1406
|
/**
|
|
1225
1407
|
* Set query params.
|
|
1226
1408
|
*
|
|
@@ -1236,107 +1418,99 @@ var RouterHookApi = class {
|
|
|
1236
1418
|
}
|
|
1237
1419
|
};
|
|
1238
1420
|
|
|
1421
|
+
//#endregion
|
|
1422
|
+
//#region src/hooks/useInject.ts
|
|
1423
|
+
/**
|
|
1424
|
+
* Hook to inject a service instance.
|
|
1425
|
+
* It's a wrapper of `useAlepha().inject(service)` with a memoization.
|
|
1426
|
+
*/
|
|
1427
|
+
const useInject = (service) => {
|
|
1428
|
+
const alepha = useAlepha();
|
|
1429
|
+
return (0, react.useMemo)(() => alepha.inject(service), []);
|
|
1430
|
+
};
|
|
1431
|
+
|
|
1239
1432
|
//#endregion
|
|
1240
1433
|
//#region src/hooks/useRouter.ts
|
|
1434
|
+
/**
|
|
1435
|
+
* Use this hook to access the React Router instance.
|
|
1436
|
+
*
|
|
1437
|
+
* You can add a type parameter to specify the type of your application.
|
|
1438
|
+
* This will allow you to use the router in a typesafe way.
|
|
1439
|
+
*
|
|
1440
|
+
* @example
|
|
1441
|
+
* class App {
|
|
1442
|
+
* home = $page();
|
|
1443
|
+
* }
|
|
1444
|
+
*
|
|
1445
|
+
* const router = useRouter<App>();
|
|
1446
|
+
* router.go("home"); // typesafe
|
|
1447
|
+
*/
|
|
1241
1448
|
const useRouter = () => {
|
|
1242
|
-
|
|
1243
|
-
const ctx = (0, react.useContext)(RouterContext);
|
|
1244
|
-
const layer = (0, react.useContext)(RouterLayerContext);
|
|
1245
|
-
if (!ctx || !layer) throw new Error("useRouter must be used within a RouterProvider");
|
|
1246
|
-
const pages = (0, react.useMemo)(() => {
|
|
1247
|
-
return alepha.inject(PageDescriptorProvider).getPages();
|
|
1248
|
-
}, []);
|
|
1249
|
-
return (0, react.useMemo)(() => new RouterHookApi(pages, ctx.context, ctx.state, layer, alepha.inject(PageDescriptorProvider), alepha.isBrowser() ? alepha.inject(ReactBrowserProvider) : void 0), [layer]);
|
|
1449
|
+
return useInject(ReactRouter);
|
|
1250
1450
|
};
|
|
1251
1451
|
|
|
1252
1452
|
//#endregion
|
|
1253
1453
|
//#region src/components/Link.tsx
|
|
1254
1454
|
const Link = (props) => {
|
|
1255
1455
|
const router = useRouter();
|
|
1256
|
-
const { to,...anchorProps } = props;
|
|
1257
1456
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
|
|
1258
|
-
...
|
|
1259
|
-
...
|
|
1457
|
+
...props,
|
|
1458
|
+
...router.anchor(props.href),
|
|
1260
1459
|
children: props.children
|
|
1261
1460
|
});
|
|
1262
1461
|
};
|
|
1263
|
-
var Link_default = Link;
|
|
1264
1462
|
|
|
1265
1463
|
//#endregion
|
|
1266
1464
|
//#region src/hooks/useActive.ts
|
|
1267
|
-
const useActive = (
|
|
1465
|
+
const useActive = (args) => {
|
|
1268
1466
|
const router = useRouter();
|
|
1269
|
-
const ctx = (0, react.useContext)(RouterContext);
|
|
1270
|
-
const layer = (0, react.useContext)(RouterLayerContext);
|
|
1271
|
-
if (!ctx || !layer) throw new Error("useRouter must be used within a RouterProvider");
|
|
1272
|
-
const [current, setCurrent] = (0, react.useState)(ctx.state.pathname);
|
|
1273
|
-
const href = (0, react.useMemo)(() => router.createHref(path ?? "", layer), [path, layer]);
|
|
1274
1467
|
const [isPending, setPending] = (0, react.useState)(false);
|
|
1275
|
-
const
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1468
|
+
const state = useRouterState();
|
|
1469
|
+
const current = state.url.pathname;
|
|
1470
|
+
const options = typeof args === "string" ? { href: args } : {
|
|
1471
|
+
...args,
|
|
1472
|
+
href: args.href
|
|
1473
|
+
};
|
|
1474
|
+
const href = options.href;
|
|
1475
|
+
let isActive = current === href || current === `${href}/` || `${current}/` === href;
|
|
1476
|
+
if (options.startWith && !isActive) isActive = current.startsWith(href);
|
|
1279
1477
|
return {
|
|
1280
1478
|
isPending,
|
|
1281
1479
|
isActive,
|
|
1282
1480
|
anchorProps: {
|
|
1283
|
-
href,
|
|
1284
|
-
onClick: (ev) => {
|
|
1481
|
+
href: router.base(href),
|
|
1482
|
+
onClick: async (ev) => {
|
|
1285
1483
|
ev?.stopPropagation();
|
|
1286
1484
|
ev?.preventDefault();
|
|
1287
1485
|
if (isActive) return;
|
|
1288
1486
|
if (isPending) return;
|
|
1289
1487
|
setPending(true);
|
|
1290
|
-
|
|
1488
|
+
try {
|
|
1489
|
+
await router.go(href);
|
|
1490
|
+
} finally {
|
|
1291
1491
|
setPending(false);
|
|
1292
|
-
}
|
|
1492
|
+
}
|
|
1293
1493
|
}
|
|
1294
1494
|
}
|
|
1295
1495
|
};
|
|
1296
1496
|
};
|
|
1297
1497
|
|
|
1298
1498
|
//#endregion
|
|
1299
|
-
//#region src/hooks/
|
|
1300
|
-
const useInject = (service) => {
|
|
1301
|
-
const alepha = useAlepha();
|
|
1302
|
-
return (0, react.useMemo)(() => alepha.inject(service), []);
|
|
1303
|
-
};
|
|
1304
|
-
|
|
1305
|
-
//#endregion
|
|
1306
|
-
//#region src/hooks/useStore.ts
|
|
1499
|
+
//#region src/hooks/useClient.ts
|
|
1307
1500
|
/**
|
|
1308
|
-
* Hook to
|
|
1501
|
+
* Hook to get a virtual client for the specified scope.
|
|
1502
|
+
*
|
|
1503
|
+
* It's the React-hook version of `$client()`, from `AlephaServerLinks` module.
|
|
1309
1504
|
*/
|
|
1310
|
-
const
|
|
1311
|
-
|
|
1312
|
-
(0, react.useMemo)(() => {
|
|
1313
|
-
if (defaultValue != null && alepha.state(key) == null) alepha.state(key, defaultValue);
|
|
1314
|
-
}, [defaultValue]);
|
|
1315
|
-
const [state, setState] = (0, react.useState)(alepha.state(key));
|
|
1316
|
-
(0, react.useEffect)(() => {
|
|
1317
|
-
if (!alepha.isBrowser()) return;
|
|
1318
|
-
return alepha.on("state:mutate", (ev) => {
|
|
1319
|
-
if (ev.key === key) setState(ev.value);
|
|
1320
|
-
});
|
|
1321
|
-
}, []);
|
|
1322
|
-
if (!alepha.isBrowser()) {
|
|
1323
|
-
const value = alepha.context.get(key);
|
|
1324
|
-
if (value !== null) return [value, (_) => {}];
|
|
1325
|
-
}
|
|
1326
|
-
return [state, (value) => {
|
|
1327
|
-
alepha.state(key, value);
|
|
1328
|
-
}];
|
|
1329
|
-
};
|
|
1330
|
-
|
|
1331
|
-
//#endregion
|
|
1332
|
-
//#region src/hooks/useClient.ts
|
|
1333
|
-
const useClient = (_scope) => {
|
|
1334
|
-
useStore("user");
|
|
1335
|
-
return useInject(__alepha_server_links.LinkProvider).client();
|
|
1505
|
+
const useClient = (scope) => {
|
|
1506
|
+
return useInject(__alepha_server_links.LinkProvider).client(scope);
|
|
1336
1507
|
};
|
|
1337
1508
|
|
|
1338
1509
|
//#endregion
|
|
1339
1510
|
//#region src/hooks/useQueryParams.ts
|
|
1511
|
+
/**
|
|
1512
|
+
* Not well tested. Use with caution.
|
|
1513
|
+
*/
|
|
1340
1514
|
const useQueryParams = (schema, options = {}) => {
|
|
1341
1515
|
const alepha = useAlepha();
|
|
1342
1516
|
const key = options.key ?? "q";
|
|
@@ -1367,29 +1541,17 @@ const decode = (alepha, schema, data) => {
|
|
|
1367
1541
|
}
|
|
1368
1542
|
};
|
|
1369
1543
|
|
|
1370
|
-
//#endregion
|
|
1371
|
-
//#region src/hooks/useRouterState.ts
|
|
1372
|
-
const useRouterState = () => {
|
|
1373
|
-
const router = (0, react.useContext)(RouterContext);
|
|
1374
|
-
const layer = (0, react.useContext)(RouterLayerContext);
|
|
1375
|
-
if (!router || !layer) throw new Error("useRouterState must be used within a RouterContext.Provider");
|
|
1376
|
-
const [state, setState] = (0, react.useState)(router.state);
|
|
1377
|
-
useRouterEvents({ onEnd: ({ state: state$1 }) => setState({ ...state$1 }) });
|
|
1378
|
-
return state;
|
|
1379
|
-
};
|
|
1380
|
-
|
|
1381
1544
|
//#endregion
|
|
1382
1545
|
//#region src/hooks/useSchema.ts
|
|
1383
1546
|
const useSchema = (action) => {
|
|
1384
1547
|
const name = action.name;
|
|
1385
1548
|
const alepha = useAlepha();
|
|
1386
1549
|
const httpClient = useInject(__alepha_server.HttpClient);
|
|
1387
|
-
const linkProvider = useInject(__alepha_server_links.LinkProvider);
|
|
1388
1550
|
const [schema, setSchema] = (0, react.useState)(ssrSchemaLoading(alepha, name));
|
|
1389
1551
|
(0, react.useEffect)(() => {
|
|
1390
1552
|
if (!schema.loading) return;
|
|
1391
1553
|
const opts = { cache: true };
|
|
1392
|
-
httpClient.fetch(`${
|
|
1554
|
+
httpClient.fetch(`${__alepha_server_links.LinkProvider.path.apiLinks}/${name}/schema`, {}, opts).then((it) => setSchema(it.data));
|
|
1393
1555
|
}, [name]);
|
|
1394
1556
|
return schema;
|
|
1395
1557
|
};
|
|
@@ -1398,10 +1560,10 @@ const useSchema = (action) => {
|
|
|
1398
1560
|
*/
|
|
1399
1561
|
const ssrSchemaLoading = (alepha, name) => {
|
|
1400
1562
|
if (!alepha.isBrowser()) {
|
|
1401
|
-
const
|
|
1402
|
-
const can =
|
|
1563
|
+
const linkProvider = alepha.inject(__alepha_server_links.LinkProvider);
|
|
1564
|
+
const can = linkProvider.getServerLinks().find((link) => link.name === name);
|
|
1403
1565
|
if (can) {
|
|
1404
|
-
const schema$1 =
|
|
1566
|
+
const schema$1 = linkProvider.links.find((it) => it.name === name)?.schema;
|
|
1405
1567
|
if (schema$1) {
|
|
1406
1568
|
can.schema = schema$1;
|
|
1407
1569
|
return schema$1;
|
|
@@ -1409,7 +1571,7 @@ const ssrSchemaLoading = (alepha, name) => {
|
|
|
1409
1571
|
}
|
|
1410
1572
|
return { loading: true };
|
|
1411
1573
|
}
|
|
1412
|
-
const schema = alepha.inject(__alepha_server_links.LinkProvider).links
|
|
1574
|
+
const schema = alepha.inject(__alepha_server_links.LinkProvider).links.find((it) => it.name === name)?.schema;
|
|
1413
1575
|
if (schema) return schema;
|
|
1414
1576
|
return { loading: true };
|
|
1415
1577
|
};
|
|
@@ -1431,28 +1593,28 @@ const AlephaReact = (0, __alepha_core.$module)({
|
|
|
1431
1593
|
descriptors: [$page],
|
|
1432
1594
|
services: [
|
|
1433
1595
|
ReactServerProvider,
|
|
1434
|
-
|
|
1435
|
-
|
|
1596
|
+
ReactPageProvider,
|
|
1597
|
+
ReactRouter
|
|
1436
1598
|
],
|
|
1437
|
-
register: (alepha) => alepha.with(__alepha_server.AlephaServer).with(__alepha_server_cache.AlephaServerCache).with(__alepha_server_links.AlephaServerLinks).with(ReactServerProvider).with(
|
|
1599
|
+
register: (alepha) => alepha.with(__alepha_server.AlephaServer).with(__alepha_server_cache.AlephaServerCache).with(__alepha_server_links.AlephaServerLinks).with(ReactServerProvider).with(ReactPageProvider).with(ReactRouter)
|
|
1438
1600
|
});
|
|
1439
1601
|
|
|
1440
1602
|
//#endregion
|
|
1441
1603
|
exports.$page = $page;
|
|
1442
1604
|
exports.AlephaContext = AlephaContext;
|
|
1443
1605
|
exports.AlephaReact = AlephaReact;
|
|
1444
|
-
exports.ClientOnly =
|
|
1445
|
-
exports.ErrorBoundary =
|
|
1446
|
-
exports.
|
|
1606
|
+
exports.ClientOnly = ClientOnly;
|
|
1607
|
+
exports.ErrorBoundary = ErrorBoundary;
|
|
1608
|
+
exports.ErrorViewer = ErrorViewer;
|
|
1609
|
+
exports.Link = Link;
|
|
1447
1610
|
exports.NestedView = NestedView_default;
|
|
1448
1611
|
exports.NotFound = NotFoundPage;
|
|
1449
1612
|
exports.PageDescriptor = PageDescriptor;
|
|
1450
|
-
exports.PageDescriptorProvider = PageDescriptorProvider;
|
|
1451
1613
|
exports.ReactBrowserProvider = ReactBrowserProvider;
|
|
1614
|
+
exports.ReactPageProvider = ReactPageProvider;
|
|
1615
|
+
exports.ReactRouter = ReactRouter;
|
|
1452
1616
|
exports.ReactServerProvider = ReactServerProvider;
|
|
1453
1617
|
exports.Redirection = Redirection;
|
|
1454
|
-
exports.RouterContext = RouterContext;
|
|
1455
|
-
exports.RouterHookApi = RouterHookApi;
|
|
1456
1618
|
exports.RouterLayerContext = RouterLayerContext;
|
|
1457
1619
|
exports.isPageRoute = isPageRoute;
|
|
1458
1620
|
exports.ssrSchemaLoading = ssrSchemaLoading;
|