@alepha/react 0.7.6 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import { $hook, $inject, $logger, Alepha, KIND, NotImplementedError, OPTIONS, __bind, __descriptor, t } from "@alepha/core";
2
- import { AlephaServer, HttpClient, ServerLinksProvider, ServerRouterProvider, ServerTimingProvider, apiLinksResponseSchema } from "@alepha/server";
2
+ import { AlephaServer, ServerRouterProvider, ServerTimingProvider, apiLinksResponseSchema } from "@alepha/server";
3
3
  import { AlephaServerCache } from "@alepha/server-cache";
4
+ import { AlephaServerLinks, LinkProvider, ServerLinksProvider } from "@alepha/server-links";
4
5
  import React, { StrictMode, createContext, createElement, useContext, useEffect, useMemo, useState } from "react";
5
6
  import { jsx, jsxs } from "react/jsx-runtime";
6
7
  import { existsSync } from "node:fs";
@@ -16,11 +17,6 @@ const KEY = "PAGE";
16
17
  */
17
18
  const $page = (options) => {
18
19
  __descriptor(KEY);
19
- if (options.children) for (const child of options.children) child[OPTIONS].parent = { [OPTIONS]: options };
20
- if (options.parent) {
21
- options.parent[OPTIONS].children ??= [];
22
- options.parent[OPTIONS].children.push({ [OPTIONS]: options });
23
- }
24
20
  return {
25
21
  [KIND]: KEY,
26
22
  [OPTIONS]: options,
@@ -457,10 +453,6 @@ var PageDescriptorProvider = class {
457
453
  const props = it.props ?? {};
458
454
  const params = { ...it.config?.params };
459
455
  for (const key of Object.keys(params)) params[key] = String(params[key]);
460
- if (it.route.head && !it.error) this.fillHead(it.route, request, {
461
- ...props,
462
- ...context
463
- });
464
456
  acc += "/";
465
457
  acc += it.route.path ? this.compile(it.route.path, params) : "";
466
458
  const path = acc.replace(/\/+/, "/");
@@ -477,7 +469,8 @@ var PageDescriptorProvider = class {
477
469
  config: it.config,
478
470
  element: this.renderView(i + 1, path, element$1, it.route),
479
471
  index: i + 1,
480
- path
472
+ path,
473
+ route
481
474
  });
482
475
  break;
483
476
  }
@@ -492,7 +485,8 @@ var PageDescriptorProvider = class {
492
485
  config: it.config,
493
486
  element: this.renderView(i + 1, path, element, it.route),
494
487
  index: i + 1,
495
- path
488
+ path,
489
+ route
496
490
  });
497
491
  }
498
492
  return {
@@ -517,26 +511,6 @@ var PageDescriptorProvider = class {
517
511
  if (page.component) return createElement(page.component, props);
518
512
  return void 0;
519
513
  }
520
- fillHead(page, ctx, props) {
521
- if (!page.head) return;
522
- ctx.head ??= {};
523
- const head = typeof page.head === "function" ? page.head(props, ctx.head) : page.head;
524
- if (head.title) {
525
- ctx.head ??= {};
526
- if (ctx.head.titleSeparator) ctx.head.title = `${head.title}${ctx.head.titleSeparator}${ctx.head.title}`;
527
- else ctx.head.title = head.title;
528
- ctx.head.titleSeparator = head.titleSeparator;
529
- }
530
- if (head.htmlAttributes) ctx.head.htmlAttributes = {
531
- ...ctx.head.htmlAttributes,
532
- ...head.htmlAttributes
533
- };
534
- if (head.bodyAttributes) ctx.head.bodyAttributes = {
535
- ...ctx.head.bodyAttributes,
536
- ...head.bodyAttributes
537
- };
538
- if (head.meta) ctx.head.meta = [...ctx.head.meta ?? [], ...head.meta ?? []];
539
- }
540
514
  renderError(error) {
541
515
  return createElement(ErrorViewer_default, { error });
542
516
  }
@@ -568,13 +542,19 @@ var PageDescriptorProvider = class {
568
542
  } }, element);
569
543
  }
570
544
  configure = $hook({
571
- name: "configure",
545
+ on: "configure",
572
546
  handler: () => {
573
547
  let hasNotFoundHandler = false;
574
548
  const pages = this.alepha.getDescriptorValues($page);
549
+ const hasParent = (it) => {
550
+ for (const page of pages) {
551
+ const children = page.value[OPTIONS].children ? Array.isArray(page.value[OPTIONS].children) ? page.value[OPTIONS].children : page.value[OPTIONS].children() : [];
552
+ if (children.includes(it)) return true;
553
+ }
554
+ };
575
555
  for (const { value, key } of pages) value[OPTIONS].name ??= key;
576
556
  for (const { value } of pages) {
577
- if (value[OPTIONS].parent) continue;
557
+ if (hasParent(value)) continue;
578
558
  if (value[OPTIONS].path === "/*") hasNotFoundHandler = true;
579
559
  this.add(this.map(pages, value));
580
560
  }
@@ -590,7 +570,7 @@ var PageDescriptorProvider = class {
590
570
  }
591
571
  });
592
572
  map(pages, target) {
593
- const children = target[OPTIONS].children ?? [];
573
+ const children = target[OPTIONS].children ? Array.isArray(target[OPTIONS].children) ? target[OPTIONS].children : target[OPTIONS].children() : [];
594
574
  return {
595
575
  ...target[OPTIONS],
596
576
  parent: void 0,
@@ -629,46 +609,6 @@ const isPageRoute = (it) => {
629
609
  return it && typeof it === "object" && typeof it.path === "string" && typeof it.page === "object";
630
610
  };
631
611
 
632
- //#endregion
633
- //#region src/providers/ServerHeadProvider.ts
634
- var ServerHeadProvider = class {
635
- renderHead(template, head) {
636
- let result = template;
637
- const htmlAttributes = head.htmlAttributes;
638
- if (htmlAttributes) result = result.replace(/<html([^>]*)>/i, (_, existingAttrs) => `<html${this.mergeAttributes(existingAttrs, htmlAttributes)}>`);
639
- const bodyAttributes = head.bodyAttributes;
640
- if (bodyAttributes) result = result.replace(/<body([^>]*)>/i, (_, existingAttrs) => `<body${this.mergeAttributes(existingAttrs, bodyAttributes)}>`);
641
- let headContent = "";
642
- const title = head.title;
643
- if (title) if (template.includes("<title>")) result = result.replace(/<title>(.*?)<\/title>/i, () => `<title>${this.escapeHtml(title)}</title>`);
644
- else headContent += `<title>${this.escapeHtml(title)}</title>\n`;
645
- if (head.meta) for (const meta of head.meta) headContent += `<meta name="${this.escapeHtml(meta.name)}" content="${this.escapeHtml(meta.content)}">\n`;
646
- result = result.replace(/<head([^>]*)>(.*?)<\/head>/is, (_, existingAttrs, existingHead) => `<head${existingAttrs}>${existingHead}${headContent}</head>`);
647
- return result.trim();
648
- }
649
- mergeAttributes(existing, attrs) {
650
- const existingAttrs = this.parseAttributes(existing);
651
- const merged = {
652
- ...existingAttrs,
653
- ...attrs
654
- };
655
- return Object.entries(merged).map(([k, v]) => ` ${k}="${this.escapeHtml(v)}"`).join("");
656
- }
657
- parseAttributes(attrStr) {
658
- const attrs = {};
659
- const attrRegex = /([^\s=]+)(?:="([^"]*)")?/g;
660
- let match = attrRegex.exec(attrStr);
661
- while (match) {
662
- attrs[match[1]] = match[2] ?? "";
663
- match = attrRegex.exec(attrStr);
664
- }
665
- return attrs;
666
- }
667
- escapeHtml(str) {
668
- return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#039;");
669
- }
670
- };
671
-
672
612
  //#endregion
673
613
  //#region src/providers/ReactServerProvider.ts
674
614
  const envSchema = t.object({
@@ -683,20 +623,18 @@ var ReactServerProvider = class {
683
623
  pageDescriptorProvider = $inject(PageDescriptorProvider);
684
624
  serverStaticProvider = $inject(ServerStaticProvider);
685
625
  serverRouterProvider = $inject(ServerRouterProvider);
686
- headProvider = $inject(ServerHeadProvider);
687
626
  serverTimingProvider = $inject(ServerTimingProvider);
688
627
  env = $inject(envSchema);
689
628
  ROOT_DIV_REGEX = new RegExp(`<div([^>]*)\\s+id=["']${this.env.REACT_ROOT_ID}["']([^>]*)>(.*?)<\\/div>`, "is");
690
629
  onConfigure = $hook({
691
- name: "configure",
630
+ on: "configure",
692
631
  handler: async () => {
693
632
  const pages = this.alepha.getDescriptorValues($page);
694
633
  const ssrEnabled = pages.length > 0 && this.env.REACT_SSR_ENABLED !== false;
695
634
  this.alepha.state("ReactServerProvider.ssr", ssrEnabled);
696
635
  for (const { key, instance, value } of pages) {
697
636
  const name = value[OPTIONS].name ?? key;
698
- instance[key].prerender = this.createRenderFunction(name, true);
699
- if (this.alepha.isTest()) instance[key].render = this.createRenderFunction(name);
637
+ instance[key].render = this.createRenderFunction(name);
700
638
  }
701
639
  if (this.alepha.isServerless() === "vite") {
702
640
  await this.configureVite(ssrEnabled);
@@ -733,7 +671,7 @@ var ReactServerProvider = class {
733
671
  }
734
672
  });
735
673
  get template() {
736
- return this.alepha.state("ReactServerProvider.template");
674
+ return this.alepha.state("ReactServerProvider.template") ?? "<!DOCTYPE html><html lang='en'><head></head><body></body></html>";
737
675
  }
738
676
  async registerPages(templateLoader) {
739
677
  for (const page of this.pageDescriptorProvider.getPages()) {
@@ -779,15 +717,20 @@ var ReactServerProvider = class {
779
717
  head: {},
780
718
  onError: () => null
781
719
  };
720
+ await this.alepha.emit("react:server:render:begin", { context });
782
721
  const state = await this.pageDescriptorProvider.createLayers(page, context);
783
- if (!withIndex) return {
722
+ if (!withIndex && !options.html) return {
784
723
  context,
785
724
  html: renderToString(this.pageDescriptorProvider.root(state, context))
786
725
  };
787
- return {
726
+ const html = this.renderToHtml(this.template ?? "", state, context, options.hydration);
727
+ const result = {
788
728
  context,
789
- html: this.renderToHtml(this.template ?? "", state, context)
729
+ state,
730
+ html
790
731
  };
732
+ await this.alepha.emit("react:server:render:end", result);
733
+ return result;
791
734
  };
792
735
  }
793
736
  createHandler(page, templateLoader) {
@@ -820,10 +763,10 @@ var ReactServerProvider = class {
820
763
  }
821
764
  target = target.parent;
822
765
  }
823
- await this.alepha.emit("react:server:render", {
766
+ await this.alepha.emit("react:server:render:begin", {
824
767
  request: serverRequest,
825
- pageRequest: context
826
- }, { log: false });
768
+ context
769
+ });
827
770
  this.serverTimingProvider.beginTiming("createLayers");
828
771
  const state = await this.pageDescriptorProvider.createLayers(page, context);
829
772
  this.serverTimingProvider.endTiming("createLayers");
@@ -834,11 +777,17 @@ var ReactServerProvider = class {
834
777
  reply.headers.expires = "0";
835
778
  if (page.cache && serverRequest.user) delete context.links;
836
779
  const html = this.renderToHtml(template, state, context);
780
+ await this.alepha.emit("react:server:render:end", {
781
+ request: serverRequest,
782
+ context,
783
+ state,
784
+ html
785
+ });
837
786
  page.afterHandler?.(serverRequest);
838
787
  return html;
839
788
  };
840
789
  }
841
- renderToHtml(template, state, context) {
790
+ renderToHtml(template, state, context, hydration = true) {
842
791
  const element = this.pageDescriptorProvider.root(state, context);
843
792
  this.serverTimingProvider.beginTiming("renderToString");
844
793
  let app = "";
@@ -849,25 +798,27 @@ var ReactServerProvider = class {
849
798
  app = renderToString(context.onError(error));
850
799
  }
851
800
  this.serverTimingProvider.endTiming("renderToString");
852
- const hydrationData = {
853
- links: context.links,
854
- layers: state.layers.map((it) => ({
855
- ...it,
856
- error: it.error ? {
857
- ...it.error,
858
- name: it.error.name,
859
- message: it.error.message,
860
- stack: it.error.stack
861
- } : void 0,
862
- index: void 0,
863
- path: void 0,
864
- element: void 0
865
- }))
866
- };
867
- const script = `<script>window.__ssr=${JSON.stringify(hydrationData)}</script>`;
868
801
  const response = { html: template };
869
- this.fillTemplate(response, app, script);
870
- if (context.head) response.html = this.headProvider.renderHead(response.html, context.head);
802
+ if (hydration) {
803
+ const hydrationData = {
804
+ links: context.links,
805
+ layers: state.layers.map((it) => ({
806
+ ...it,
807
+ error: it.error ? {
808
+ ...it.error,
809
+ name: it.error.name,
810
+ message: it.error.message,
811
+ stack: it.error.stack
812
+ } : void 0,
813
+ index: void 0,
814
+ path: void 0,
815
+ element: void 0,
816
+ route: void 0
817
+ }))
818
+ };
819
+ const script = `<script>window.__ssr=${JSON.stringify(hydrationData)}</script>`;
820
+ this.fillTemplate(response, app, script);
821
+ }
871
822
  return response.html;
872
823
  }
873
824
  fillTemplate(response, app, script) {
@@ -885,28 +836,6 @@ var ReactServerProvider = class {
885
836
  }
886
837
  };
887
838
 
888
- //#endregion
889
- //#region src/providers/BrowserHeadProvider.ts
890
- var BrowserHeadProvider = class {
891
- renderHead(document, head) {
892
- if (head.title) document.title = head.title;
893
- if (head.bodyAttributes) for (const [key, value] of Object.entries(head.bodyAttributes)) if (value) document.body.setAttribute(key, value);
894
- else document.body.removeAttribute(key);
895
- if (head.htmlAttributes) for (const [key, value] of Object.entries(head.htmlAttributes)) if (value) document.documentElement.setAttribute(key, value);
896
- else document.documentElement.removeAttribute(key);
897
- if (head.meta) for (const [key, value] of Object.entries(head.meta)) {
898
- const meta = document.querySelector(`meta[name="${key}"]`);
899
- if (meta) meta.setAttribute("content", value.content);
900
- else {
901
- const newMeta = document.createElement("meta");
902
- newMeta.setAttribute("name", key);
903
- newMeta.setAttribute("content", value.content);
904
- document.head.appendChild(newMeta);
905
- }
906
- }
907
- }
908
- };
909
-
910
839
  //#endregion
911
840
  //#region src/providers/BrowserRouterProvider.ts
912
841
  var BrowserRouterProvider = class extends RouterProvider {
@@ -917,7 +846,7 @@ var BrowserRouterProvider = class extends RouterProvider {
917
846
  this.pageDescriptorProvider.add(entry);
918
847
  }
919
848
  configure = $hook({
920
- name: "configure",
849
+ on: "configure",
921
850
  handler: async () => {
922
851
  for (const page of this.pageDescriptorProvider.getPages()) if (page.component || page.lazy) this.push({
923
852
  path: page.match,
@@ -936,7 +865,6 @@ var BrowserRouterProvider = class extends RouterProvider {
936
865
  url,
937
866
  query: {},
938
867
  params: {},
939
- head: {},
940
868
  onError: () => null,
941
869
  ...options.context ?? {}
942
870
  };
@@ -967,7 +895,10 @@ var BrowserRouterProvider = class extends RouterProvider {
967
895
  index: 0,
968
896
  path: "/"
969
897
  });
970
- await this.alepha.emit("react:transition:success", { state });
898
+ await this.alepha.emit("react:transition:success", {
899
+ state,
900
+ context
901
+ });
971
902
  } catch (e) {
972
903
  this.log.error(e);
973
904
  state.layers = [{
@@ -1005,10 +936,9 @@ var BrowserRouterProvider = class extends RouterProvider {
1005
936
  //#region src/providers/ReactBrowserProvider.ts
1006
937
  var ReactBrowserProvider = class {
1007
938
  log = $logger();
1008
- client = $inject(HttpClient);
939
+ client = $inject(LinkProvider);
1009
940
  alepha = $inject(Alepha);
1010
941
  router = $inject(BrowserRouterProvider);
1011
- headProvider = $inject(BrowserHeadProvider);
1012
942
  root;
1013
943
  transitioning;
1014
944
  state = {
@@ -1081,13 +1011,12 @@ var ReactBrowserProvider = class {
1081
1011
  }
1082
1012
  }
1083
1013
  ready = $hook({
1084
- name: "ready",
1014
+ on: "ready",
1085
1015
  handler: async () => {
1086
1016
  const hydration = this.getHydrationState();
1087
1017
  const previous = hydration?.layers ?? [];
1088
1018
  if (hydration?.links) for (const link of hydration.links.links) this.client.pushLink(link);
1089
1019
  const { context } = await this.render({ previous });
1090
- if (context.head) this.headProvider.renderHead(this.document, context.head);
1091
1020
  await this.alepha.emit("react:browser:render", {
1092
1021
  state: this.state,
1093
1022
  context,
@@ -1098,12 +1027,6 @@ var ReactBrowserProvider = class {
1098
1027
  });
1099
1028
  }
1100
1029
  });
1101
- onTransitionEnd = $hook({
1102
- name: "react:transition:end",
1103
- handler: async ({ context }) => {
1104
- this.headProvider.renderHead(this.document, context.head);
1105
- }
1106
- });
1107
1030
  };
1108
1031
 
1109
1032
  //#endregion
@@ -1262,7 +1185,7 @@ const useInject = (clazz) => {
1262
1185
  //#endregion
1263
1186
  //#region src/hooks/useClient.ts
1264
1187
  const useClient = (_scope) => {
1265
- return useInject(HttpClient).of();
1188
+ return useInject(LinkProvider).client();
1266
1189
  };
1267
1190
 
1268
1191
  //#endregion
@@ -1322,7 +1245,7 @@ const useRouterState = () => {
1322
1245
  */
1323
1246
  var AlephaReact = class {
1324
1247
  name = "alepha.react";
1325
- $services = (alepha) => alepha.with(AlephaServer).with(AlephaServerCache).with(ReactServerProvider).with(PageDescriptorProvider);
1248
+ $services = (alepha) => alepha.with(AlephaServer).with(AlephaServerCache).with(AlephaServerLinks).with(ReactServerProvider).with(PageDescriptorProvider);
1326
1249
  };
1327
1250
  __bind($page, AlephaReact);
1328
1251