@teambit/ui 0.0.636 → 0.0.640

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.
Files changed (55) hide show
  1. package/{__preview-1643426469482.js → __preview-1643858579620.js} +1 -1
  2. package/dist/index.d.ts +7 -7
  3. package/dist/index.js +4 -52
  4. package/dist/index.js.map +1 -1
  5. package/dist/react-ssr/index.d.ts +5 -0
  6. package/dist/react-ssr/index.js +23 -0
  7. package/dist/react-ssr/index.js.map +1 -0
  8. package/dist/react-ssr/react-ssr.d.ts +21 -0
  9. package/dist/react-ssr/react-ssr.js +285 -0
  10. package/dist/react-ssr/react-ssr.js.map +1 -0
  11. package/dist/{render-lifecycle.d.ts → react-ssr/render-lifecycle.d.ts} +8 -4
  12. package/dist/{render-lifecycle.js → react-ssr/render-lifecycle.js} +0 -0
  13. package/dist/{render-lifecycle.js.map → react-ssr/render-lifecycle.js.map} +0 -0
  14. package/dist/{ssr → react-ssr}/request-browser.d.ts +0 -5
  15. package/dist/react-ssr/request-browser.js +3 -0
  16. package/dist/{ssr/request-server.js.map → react-ssr/request-browser.js.map} +0 -0
  17. package/dist/{ssr → react-ssr}/request-server.d.ts +0 -0
  18. package/dist/{ssr → react-ssr}/request-server.js +0 -0
  19. package/dist/{ssr/ssr-content.js.map → react-ssr/request-server.js.map} +0 -0
  20. package/dist/{ssr → react-ssr}/ssr-content.d.ts +0 -0
  21. package/dist/{ssr → react-ssr}/ssr-content.js +0 -0
  22. package/dist/react-ssr/ssr-content.js.map +1 -0
  23. package/dist/ssr-middleware/extract-browser-data.d.ts +6 -0
  24. package/dist/{ssr/request-browser.js → ssr-middleware/extract-browser-data.js} +4 -5
  25. package/dist/ssr-middleware/extract-browser-data.js.map +1 -0
  26. package/dist/ssr-middleware/index.d.ts +1 -0
  27. package/dist/ssr-middleware/index.js +23 -0
  28. package/dist/ssr-middleware/index.js.map +1 -0
  29. package/dist/{ssr/render-middleware.d.ts → ssr-middleware/ssr-middleware.d.ts} +0 -0
  30. package/dist/{ssr/render-middleware.js → ssr-middleware/ssr-middleware.js} +5 -5
  31. package/dist/ssr-middleware/ssr-middleware.js.map +1 -0
  32. package/dist/ui-server.js +4 -4
  33. package/dist/ui-server.js.map +1 -1
  34. package/dist/ui.ui.runtime.d.ts +8 -20
  35. package/dist/ui.ui.runtime.js +25 -215
  36. package/dist/ui.ui.runtime.js.map +1 -1
  37. package/package-tar/teambit-ui-0.0.640.tgz +0 -0
  38. package/package.json +18 -17
  39. package/react-ssr/index.ts +6 -0
  40. package/react-ssr/react-ssr.tsx +183 -0
  41. package/{render-lifecycle.tsx → react-ssr/render-lifecycle.tsx} +6 -4
  42. package/{ssr → react-ssr}/request-browser.ts +0 -31
  43. package/{ssr → react-ssr}/request-server.ts +0 -0
  44. package/{ssr → react-ssr}/ssr-content.ts +0 -0
  45. package/ssr-middleware/extract-browser-data.ts +31 -0
  46. package/ssr-middleware/index.ts +1 -0
  47. package/{ssr/render-middleware.ts → ssr-middleware/ssr-middleware.ts} +3 -3
  48. package/ui.ui.runtime.tsx +29 -179
  49. package/compose.tsx +0 -27
  50. package/dist/compose.d.ts +0 -14
  51. package/dist/compose.js +0 -37
  52. package/dist/compose.js.map +0 -1
  53. package/dist/ssr/render-middleware.js.map +0 -1
  54. package/dist/ssr/request-browser.js.map +0 -1
  55. package/package-tar/teambit-ui-0.0.636.tgz +0 -0
package/package.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@teambit/ui",
3
- "version": "0.0.636",
3
+ "version": "0.0.640",
4
4
  "homepage": "https://bit.dev/teambit/ui-foundation/ui",
5
5
  "main": "dist/index.js",
6
6
  "componentId": {
7
7
  "scope": "teambit.ui-foundation",
8
8
  "name": "ui",
9
- "version": "0.0.636"
9
+ "version": "0.0.640"
10
10
  },
11
11
  "dependencies": {
12
12
  "lodash": "4.17.21",
@@ -50,36 +50,37 @@
50
50
  "source-map-loader": "3.0.0",
51
51
  "@babel/runtime": "7.12.18",
52
52
  "core-js": "^3.0.0",
53
+ "@teambit/base-ui.utils.composer": "1.0.0",
53
54
  "@teambit/base-ui.loaders.loader-ribbon": "1.0.0",
54
55
  "@teambit/base-ui.theme.fonts.roboto": "1.0.0",
55
56
  "@teambit/base-ui.theme.theme-provider": "1.0.1",
56
- "@teambit/aspect-loader": "0.0.636",
57
+ "@teambit/aspect-loader": "0.0.640",
57
58
  "@teambit/toolbox.path.to-windows-compatible-path": "0.0.482",
58
59
  "@teambit/ui-foundation.ui.hooks.use-data-query": "0.0.486",
59
- "@teambit/logger": "0.0.524",
60
- "@teambit/cli": "0.0.434",
60
+ "@teambit/logger": "0.0.527",
61
+ "@teambit/cli": "0.0.437",
61
62
  "@teambit/ui-foundation.cli.ui-server-console": "0.0.489",
62
- "@teambit/bundler": "0.0.636",
63
- "@teambit/component": "0.0.636",
64
- "@teambit/express": "0.0.528",
65
- "@teambit/graphql": "0.0.636",
63
+ "@teambit/bundler": "0.0.640",
64
+ "@teambit/component": "0.0.640",
65
+ "@teambit/express": "0.0.531",
66
+ "@teambit/graphql": "0.0.640",
66
67
  "@teambit/toolbox.network.get-port": "0.0.112",
67
- "@teambit/aspect": "0.0.636",
68
- "@teambit/cache": "0.0.524",
69
- "@teambit/pubsub": "0.0.636",
70
- "@teambit/react-router": "0.0.636",
68
+ "@teambit/aspect": "0.0.640",
69
+ "@teambit/cache": "0.0.527",
70
+ "@teambit/pubsub": "0.0.640",
71
+ "@teambit/react-router": "0.0.640",
71
72
  "@teambit/ui-foundation.ui.rendering.html": "0.0.68",
72
73
  "@teambit/design.theme.icons-font": "1.0.8",
73
74
  "@teambit/design.ui.tooltip": "0.0.352",
74
75
  "@teambit/ui-foundation.ui.global-loader": "0.0.485",
75
76
  "@teambit/webpack.modules.generate-style-loaders": "0.0.102",
76
77
  "@teambit/webpack.modules.style-regexps": "0.0.131",
77
- "@teambit/webpack": "0.0.636"
78
+ "@teambit/webpack": "0.0.640"
78
79
  },
79
80
  "devDependencies": {
80
- "@types/react": "^17.0.8",
81
81
  "@types/lodash": "4.14.165",
82
82
  "@types/cross-spawn": "6.0.2",
83
+ "@types/react": "^17.0.8",
83
84
  "@types/webpack-dev-server": "4.0.3",
84
85
  "@types/react-router-dom": "5.1.7",
85
86
  "@types/express": "4.17.9",
@@ -96,7 +97,7 @@
96
97
  },
97
98
  "peerDependencies": {
98
99
  "@apollo/client": "^3.0.0",
99
- "@teambit/legacy": "1.0.210",
100
+ "@teambit/legacy": "1.0.213",
100
101
  "react-dom": "^16.8.0 || ^17.0.0",
101
102
  "react": "^16.8.0 || ^17.0.0"
102
103
  },
@@ -141,7 +142,7 @@
141
142
  "react": "-"
142
143
  },
143
144
  "peerDependencies": {
144
- "@teambit/legacy": "1.0.210",
145
+ "@teambit/legacy": "1.0.213",
145
146
  "react-dom": "^16.8.0 || ^17.0.0",
146
147
  "react": "^16.8.0 || ^17.0.0"
147
148
  }
@@ -0,0 +1,6 @@
1
+ export { ReactSSR } from './react-ssr';
2
+
3
+ export type { ContextProps, RenderPlugins } from './render-lifecycle';
4
+ export type { BrowserData, ParsedQuery } from './request-browser';
5
+ export type { RequestServer } from './request-server';
6
+ export type { SsrContent } from './ssr-content';
@@ -0,0 +1,183 @@
1
+ import React, { ReactNode } from 'react';
2
+ import { merge } from 'webpack-merge';
3
+ import compact from 'lodash.compact';
4
+ import ReactDOM from 'react-dom';
5
+ import ReactDOMServer from 'react-dom/server';
6
+
7
+ import { Html, MountPoint, mountPointId, ssrCleanup, Assets } from '@teambit/ui-foundation.ui.rendering.html';
8
+ import { Composer, Wrapper } from '@teambit/base-ui.utils.composer';
9
+
10
+ import type { RenderPlugins } from './render-lifecycle';
11
+ import type { SsrContent } from './ssr-content';
12
+ import type { RequestServer } from './request-server';
13
+ import type { BrowserData } from './request-browser';
14
+
15
+ type RenderPluginsWithId = [key: string, hooks: RenderPlugins];
16
+
17
+ export class ReactSSR {
18
+ constructor(
19
+ // create array once to keep consistent indexes
20
+ private lifecycleHooks: RenderPluginsWithId[]
21
+ ) {}
22
+
23
+ /** render and rehydrate client-side */
24
+ async renderBrowser(children: ReactNode) {
25
+ // (*) load state from the dom
26
+ const deserializedState = await this.deserialize();
27
+
28
+ // (1) init setup client plugins
29
+ let renderContexts = await this.triggerBrowserInit(deserializedState);
30
+
31
+ // (2) make react dom
32
+ const reactContexts = this.getReactContexts(renderContexts);
33
+ const app = <Composer components={reactContexts}>{children}</Composer>;
34
+
35
+ renderContexts = await this.triggerBeforeHydrateHook(renderContexts, app);
36
+
37
+ // (3) render / rehydrate
38
+ const mountPoint = document.getElementById(mountPointId);
39
+ // .render() already runs `.hydrate()` behind the scenes.
40
+ // in the future, we may want to replace it with .hydrate()
41
+ ReactDOM.render(app, mountPoint);
42
+
43
+ await this.triggerHydrateHook(renderContexts, mountPoint);
44
+
45
+ // (3.1) remove ssr only styles
46
+ ssrCleanup();
47
+ }
48
+
49
+ /** render dehydrated server-side */
50
+ async renderServer(children: ReactNode, { assets, browser, server }: SsrContent = {}): Promise<string> {
51
+ // (1) init
52
+ let renderContexts = await this.triggerServerInit(browser, server);
53
+
54
+ // (2) make React dom
55
+ const reactContexts = this.getReactContexts(renderContexts);
56
+ const app = (
57
+ <MountPoint>
58
+ <Composer components={reactContexts}>{children}</Composer>
59
+ </MountPoint>
60
+ );
61
+
62
+ renderContexts = await this.triggerBeforeRender(renderContexts, app);
63
+
64
+ // (3) render (to string)
65
+ const renderedApp = ReactDOMServer.renderToString(app);
66
+
67
+ // (*) serialize state
68
+ const realtimeAssets = await this.serialize(renderContexts, app);
69
+ // @ts-ignore // TODO upgrade 'webpack-merge'
70
+ const totalAssets = merge(assets, realtimeAssets) as Assets;
71
+
72
+ // (4) render html-template (to string)
73
+ const html = <Html assets={totalAssets} withDevTools fullHeight ssr />;
74
+ const renderedHtml = `<!DOCTYPE html>${ReactDOMServer.renderToStaticMarkup(html)}`;
75
+ const fullHtml = Html.fillContent(renderedHtml, renderedApp);
76
+
77
+ // (5) serve
78
+ return fullHtml;
79
+ }
80
+
81
+ private triggerBrowserInit(deserializedState: any[]) {
82
+ const { lifecycleHooks } = this;
83
+
84
+ const initPromises = lifecycleHooks.map(([, hooks], idx) => {
85
+ const state = deserializedState[idx];
86
+ return hooks.browserInit?.(state);
87
+ });
88
+ return Promise.all(initPromises);
89
+ }
90
+
91
+ private triggerServerInit(browser?: BrowserData, server?: RequestServer) {
92
+ const { lifecycleHooks } = this;
93
+ const promises = lifecycleHooks.map(([, hooks]) => hooks.serverInit?.({ browser, server }));
94
+ return Promise.all(promises);
95
+ }
96
+
97
+ private triggerBeforeHydrateHook(renderContexts: any[], app: JSX.Element) {
98
+ const { lifecycleHooks } = this;
99
+
100
+ const promises = lifecycleHooks.map(async ([, hooks], idx) => {
101
+ const ctx = renderContexts[idx];
102
+ const nextCtx = await hooks.onBeforeHydrate?.(ctx, app);
103
+ return nextCtx || ctx;
104
+ });
105
+
106
+ return Promise.all(promises);
107
+ }
108
+
109
+ private async triggerHydrateHook(renderContexts: any[], mountPoint: HTMLElement | null) {
110
+ const { lifecycleHooks } = this;
111
+
112
+ const promises = lifecycleHooks.map(([, hooks], idx) => {
113
+ const renderCtx = renderContexts[idx];
114
+ return hooks.onHydrate?.(renderCtx, mountPoint);
115
+ });
116
+
117
+ await Promise.all(promises);
118
+ }
119
+
120
+ private async triggerBeforeRender(renderContexts: any[], app: JSX.Element) {
121
+ const { lifecycleHooks } = this;
122
+
123
+ const promises = lifecycleHooks.map(async ([, hooks], idx) => {
124
+ const ctx = renderContexts[idx];
125
+ const nextCtx = await hooks.onBeforeRender?.(ctx, app);
126
+ return nextCtx || ctx;
127
+ });
128
+
129
+ await Promise.all(promises);
130
+
131
+ return renderContexts;
132
+ }
133
+
134
+ private getReactContexts(renderContexts: any[]): Wrapper[] {
135
+ const { lifecycleHooks } = this;
136
+
137
+ return compact(
138
+ lifecycleHooks.map(([, hooks], idx) => {
139
+ const renderCtx = renderContexts[idx];
140
+ const props = { renderCtx };
141
+ return hooks.reactContext ? [hooks.reactContext, props] : undefined;
142
+ })
143
+ );
144
+ }
145
+
146
+ private async deserialize() {
147
+ const { lifecycleHooks } = this;
148
+ const rawAssets = Html.popAssets();
149
+
150
+ const deserialized = await Promise.all(
151
+ lifecycleHooks.map(async ([key, hooks]) => {
152
+ try {
153
+ const raw = rawAssets.get(key);
154
+ return hooks.deserialize?.(raw);
155
+ } catch (e) {
156
+ // eslint-disable-next-line no-console
157
+ console.error(`failed deserializing server state for aspect ${key}`, e);
158
+ return undefined;
159
+ }
160
+ })
161
+ );
162
+
163
+ return deserialized;
164
+ }
165
+
166
+ private async serialize(renderContexts: any[], app: ReactNode): Promise<Assets> {
167
+ const { lifecycleHooks } = this;
168
+ const json = {};
169
+
170
+ const promises = lifecycleHooks.map(async ([key, hooks], idx) => {
171
+ const renderCtx = renderContexts[idx];
172
+ const result = await hooks.serialize?.(renderCtx, app);
173
+
174
+ if (!result) return;
175
+ if (result.json) json[key] = result.json;
176
+ });
177
+
178
+ await Promise.all(promises);
179
+
180
+ // more assets will be available in the future
181
+ return { json };
182
+ }
183
+ }
@@ -1,9 +1,11 @@
1
1
  import { ReactNode, ComponentType } from 'react';
2
- import { BrowserData } from './ssr/request-browser';
3
- import { RequestServer } from './ssr/request-server';
4
- import { ContextProps } from './ui.ui.runtime';
2
+ import { BrowserData } from './request-browser';
3
+ import { RequestServer } from './request-server';
5
4
 
6
- export type RenderLifecycle<RenderCtx = any, Serialized = any> = {
5
+ export type ContextProps<T = any> = { renderCtx?: T; children: ReactNode };
6
+
7
+ /** Plugins for each step of the SSR and regular rendering lifecycle */
8
+ export type RenderPlugins<RenderCtx = any, Serialized = any> = {
7
9
  /**
8
10
  * Initialize a context state for this specific rendering.
9
11
  * Context state will only be available to the current Aspect, in the other hooks, as well as a prop to the react context component.
@@ -1,5 +1,4 @@
1
1
  import type { IncomingHttpHeaders } from 'http';
2
- import type { Request } from 'express';
3
2
 
4
3
  export type ParsedQuery = { [key: string]: undefined | string | string[] | ParsedQuery | ParsedQuery[] };
5
4
 
@@ -54,33 +53,3 @@ export type BrowserData = {
54
53
  };
55
54
  cookie?: string;
56
55
  };
57
-
58
- /**
59
- * extract relevant information from Express request.
60
- */
61
- export function requestToObj(req: Request, port: number) {
62
- // apparently port is not readily available in request.
63
-
64
- const browser: BrowserData = {
65
- connection: {
66
- secure: req.secure,
67
- headers: req.headers,
68
- body: req.body,
69
- },
70
- // trying to match to browser.location
71
- location: {
72
- host: `${req.hostname}:${port}`,
73
- hostname: req.hostname,
74
- href: `${req.protocol}://${req.hostname}:${port}${req.url}`,
75
- origin: `${req.protocol}://${req.hostname}:${port}`,
76
- pathname: req.path,
77
- port,
78
- protocol: `${req.protocol}:`,
79
- query: req.query,
80
- url: req.url,
81
- },
82
- cookie: req.header('Cookie'),
83
- };
84
-
85
- return browser;
86
- }
File without changes
File without changes
@@ -0,0 +1,31 @@
1
+ import { Request } from 'express';
2
+ import { BrowserData } from '../react-ssr';
3
+
4
+ /**
5
+ * extract relevant information from Express request.
6
+ */
7
+
8
+ export function extractBrowserData(req: Request, port: number) {
9
+ const browser: BrowserData = {
10
+ connection: {
11
+ secure: req.secure,
12
+ headers: req.headers,
13
+ body: req.body,
14
+ },
15
+ // rebuild browser location from request, +port
16
+ location: {
17
+ host: `${req.hostname}:${port}`,
18
+ hostname: req.hostname,
19
+ href: `${req.protocol}://${req.hostname}:${port}${req.url}`,
20
+ origin: `${req.protocol}://${req.hostname}:${port}`,
21
+ pathname: req.path,
22
+ port,
23
+ protocol: `${req.protocol}:`,
24
+ query: req.query,
25
+ url: req.url,
26
+ },
27
+ cookie: req.header('Cookie'),
28
+ };
29
+
30
+ return browser;
31
+ }
@@ -0,0 +1 @@
1
+ export { createSsrMiddleware } from './ssr-middleware';
@@ -3,8 +3,8 @@ import { Request, Response, NextFunction } from 'express';
3
3
  import path from 'path';
4
4
  import * as fs from 'fs-extra';
5
5
  import type { Logger } from '@teambit/logger';
6
- import { requestToObj } from './request-browser';
7
- import { SsrContent } from './ssr-content';
6
+ import type { SsrContent } from '../react-ssr';
7
+ import { extractBrowserData } from './extract-browser-data';
8
8
 
9
9
  const denyList = /^\/favicon.ico$/;
10
10
 
@@ -30,7 +30,7 @@ export async function createSsrMiddleware({ root, port, title, logger }: ssrRend
30
30
  return async function serverRenderMiddleware(req: Request, res: Response, next: NextFunction) {
31
31
  const { query, url } = req;
32
32
 
33
- const browser = requestToObj(req, port);
33
+ const browser = extractBrowserData(req, port);
34
34
 
35
35
  if (denyList.test(url)) {
36
36
  logger.debug(`[ssr] skipping static denyList file ${url}`);
package/ui.ui.runtime.tsx CHANGED
@@ -3,27 +3,18 @@ import { GraphqlAspect } from '@teambit/graphql';
3
3
  import { Slot, SlotRegistry } from '@teambit/harmony';
4
4
  import type { ReactRouterUI } from '@teambit/react-router';
5
5
  import { ReactRouterAspect } from '@teambit/react-router';
6
- import { Html, MountPoint, mountPointId, ssrCleanup, Assets } from '@teambit/ui-foundation.ui.rendering.html';
7
6
 
8
- import { merge } from 'webpack-merge';
9
7
  import React, { ReactNode, ComponentType } from 'react';
10
- import ReactDOM from 'react-dom';
11
- import ReactDOMServer from 'react-dom/server';
12
- import compact from 'lodash.compact';
13
8
 
14
- import { Compose, Wrapper } from './compose';
15
9
  import { UIRootFactory } from './ui-root.ui';
16
10
  import { UIAspect, UIRuntime } from './ui.aspect';
17
11
  import { ClientContext } from './ui/client-context';
18
- import type { SsrContent } from './ssr/ssr-content';
19
- import type { RequestServer } from './ssr/request-server';
20
- import type { BrowserData } from './ssr/request-browser';
21
- import { RenderLifecycle } from './render-lifecycle';
22
-
23
- export type ContextProps<T = any> = { renderCtx?: T; children: ReactNode };
12
+ import type { SsrContent } from './react-ssr/ssr-content';
13
+ import { ContextProps, RenderPlugins } from './react-ssr/render-lifecycle';
14
+ import { ReactSSR } from './react-ssr/react-ssr';
24
15
 
25
16
  type HudSlot = SlotRegistry<ReactNode>;
26
- type renderLifecycleSlot = SlotRegistry<RenderLifecycle>;
17
+ type RenderPluginsSlot = SlotRegistry<RenderPlugins>;
27
18
  type UIRootRegistry = SlotRegistry<UIRootFactory>;
28
19
 
29
20
  /**
@@ -42,7 +33,7 @@ export class UiUI {
42
33
  /** slot for overlay ui elements */
43
34
  private hudSlot: HudSlot,
44
35
  /** hooks into the ssr render process */
45
- private lifecycleSlot: renderLifecycleSlot
36
+ private renderPluginsSlot: RenderPluginsSlot
46
37
  ) {}
47
38
 
48
39
  /** render and rehydrate client-side */
@@ -53,79 +44,37 @@ export class UiUI {
53
44
  const initialLocation = `${window.location.pathname}${window.location.search}${window.location.hash}`;
54
45
  const routes = this.router.renderRoutes(uiRoot.routes, { initialLocation });
55
46
  const hudItems = this.hudSlot.values();
56
- const lifecycleHooks = this.lifecycleSlot.toArray();
57
-
58
- // TODO - extract the logic from here down as reusable ssr machine
59
- const deserializedState = await this.deserialize(lifecycleHooks);
60
- let renderContexts = await this.triggerBrowserInit(lifecycleHooks, deserializedState);
61
- const reactContexts = this.getReactContexts(lifecycleHooks, renderContexts);
62
-
63
- const app = (
64
- <Compose components={reactContexts}>
65
- <ClientContext>
66
- {hudItems}
67
- {routes}
68
- </ClientContext>
69
- </Compose>
47
+ const lifecycleHooks = this.renderPluginsSlot.toArray();
48
+
49
+ const reactSsr = new ReactSSR(lifecycleHooks);
50
+ await reactSsr.renderBrowser(
51
+ <ClientContext>
52
+ {hudItems}
53
+ {routes}
54
+ </ClientContext>
70
55
  );
71
-
72
- renderContexts = await this.triggerBeforeHydrateHook(renderContexts, lifecycleHooks, app);
73
-
74
- const mountPoint = document.getElementById(mountPointId);
75
- // .render() already runs `.hydrate()` behind the scenes.
76
- // in the future, we may want to replace it with .hydrate()
77
- ReactDOM.render(app, mountPoint);
78
-
79
- await this.triggerHydrateHook(renderContexts, lifecycleHooks, mountPoint);
80
-
81
- // remove ssr only styles
82
- ssrCleanup();
83
56
  }
84
57
 
85
58
  /** render dehydrated server-side */
86
- async renderSsr(rootExtension: string, { assets, browser, server }: SsrContent = {}): Promise<string> {
59
+ async renderSsr(rootExtension: string, ssrContent: SsrContent): Promise<string> {
87
60
  const rootFactory = this.getRoot(rootExtension);
88
61
  if (!rootFactory) throw new Error(`root: ${rootExtension} was not found`);
89
62
 
90
63
  const uiRoot = rootFactory();
91
- const routes = this.router.renderRoutes(uiRoot.routes, { initialLocation: browser?.location.url });
64
+ const routes = this.router.renderRoutes(uiRoot.routes, { initialLocation: ssrContent?.browser?.location.url });
92
65
  const hudItems = this.hudSlot.values();
93
66
 
94
- // create array once to keep consistent indexes
95
- const lifecycleHooks = this.lifecycleSlot.toArray();
96
-
97
- // TODO - extract the logic from here down as reusable ssr machine
98
- // (1) init
99
- let renderContexts = await this.triggerServerInit(lifecycleHooks, browser, server);
100
- const reactContexts = this.getReactContexts(lifecycleHooks, renderContexts);
67
+ const lifecycleHooks = this.renderPluginsSlot.toArray();
101
68
 
102
- // (2) make (virtual) dom
103
- const app = (
104
- <MountPoint>
105
- <Compose components={reactContexts}>
106
- <ClientContext>
107
- {hudItems}
108
- {routes}
109
- </ClientContext>
110
- </Compose>
111
- </MountPoint>
69
+ const reactSsr = new ReactSSR(lifecycleHooks);
70
+ const fullHtml = await reactSsr.renderServer(
71
+ <ClientContext>
72
+ {hudItems}
73
+ {routes}
74
+ </ClientContext>,
75
+ ssrContent
112
76
  );
113
77
 
114
- // (3) render
115
- renderContexts = await this.onBeforeRender(renderContexts, lifecycleHooks, app);
116
-
117
- const renderedApp = ReactDOMServer.renderToString(app);
118
-
119
- // (3) render html-template
120
- const realtimeAssets = await this.serialize(lifecycleHooks, renderContexts, app);
121
- // @ts-ignore // TODO upgrade 'webpack-merge'
122
- const totalAssets = merge(assets, realtimeAssets) as Assets;
123
-
124
- const html = <Html assets={totalAssets} withDevTools fullHeight ssr />;
125
- const renderedHtml = `<!DOCTYPE html>${ReactDOMServer.renderToStaticMarkup(html)}`;
126
- const fullHtml = Html.fillContent(renderedHtml, renderedApp);
127
-
128
- // (4) serve
129
78
  return fullHtml;
130
79
  }
131
80
 
@@ -139,7 +88,7 @@ export class UiUI {
139
88
  * @deprecated replace with `.registerRenderHooks({ reactContext })`.
140
89
  */
141
90
  registerContext<T>(context: ComponentType<ContextProps<T>>) {
142
- this.lifecycleSlot.register({
91
+ this.renderPluginsSlot.register({
143
92
  reactContext: context,
144
93
  });
145
94
  }
@@ -148,114 +97,15 @@ export class UiUI {
148
97
  return this.uiRootSlot.register(uiRoot);
149
98
  }
150
99
 
151
- registerRenderHooks<T, Y>(hooks: RenderLifecycle<T, Y>) {
152
- return this.lifecycleSlot.register(hooks);
153
- }
154
-
155
- private triggerBrowserInit(lifecycleHooks: [string, RenderLifecycle<any, any>][], deserializedState: any[]) {
156
- return Promise.all(lifecycleHooks.map(([, hooks], idx) => hooks.browserInit?.(deserializedState[idx])));
157
- }
158
-
159
- private triggerServerInit(
160
- lifecycleHooks: [string, RenderLifecycle<any, any>][],
161
- browser?: BrowserData,
162
- server?: RequestServer
163
- ) {
164
- return Promise.all(lifecycleHooks.map(([, hooks]) => hooks.serverInit?.({ browser, server })));
165
- }
166
-
167
- private getReactContexts(lifecycleHooks: [string, RenderLifecycle<any>][], renderContexts: any[]): Wrapper[] {
168
- return compact(
169
- lifecycleHooks.map(([, hooks], idx) => {
170
- const renderCtx = renderContexts[idx];
171
- const props = { renderCtx };
172
- return hooks.reactContext ? [hooks.reactContext, props] : undefined;
173
- })
174
- );
175
- }
176
-
177
- private async onBeforeRender(
178
- renderContexts: any[],
179
- lifecycleHooks: [string, RenderLifecycle<any>][],
180
- app: JSX.Element
181
- ) {
182
- await Promise.all(
183
- lifecycleHooks.map(async ([, hooks], idx) => {
184
- const ctx = renderContexts[idx];
185
- const nextCtx = await hooks.onBeforeRender?.(ctx, app);
186
- return nextCtx || ctx;
187
- })
188
- );
189
- return renderContexts;
190
- }
191
-
192
- private triggerBeforeHydrateHook(
193
- renderContexts: any[],
194
- lifecycleHooks: [string, RenderLifecycle<any>][],
195
- app: JSX.Element
196
- ) {
197
- return Promise.all(
198
- lifecycleHooks.map(async ([, hooks], idx) => {
199
- const ctx = renderContexts[idx];
200
- const nextCtx = await hooks.onBeforeHydrate?.(ctx, app);
201
- return nextCtx || ctx;
202
- })
203
- );
204
- }
205
-
206
- private async triggerHydrateHook(
207
- renderContexts: any[],
208
- lifecycleHooks: [string, RenderLifecycle<any, any>][],
209
- mountPoint: HTMLElement | null
210
- ) {
211
- await Promise.all(lifecycleHooks.map(([, hooks], idx) => hooks.onHydrate?.(renderContexts[idx], mountPoint)));
212
- }
213
-
214
- private async serialize(
215
- lifecycleHooks: [string, RenderLifecycle][],
216
- renderContexts: any[],
217
- app: ReactNode
218
- ): Promise<Assets> {
219
- const json = {};
220
-
221
- await Promise.all(
222
- lifecycleHooks.map(async ([key, hooks], idx) => {
223
- const renderCtx = renderContexts[idx];
224
- const result = await hooks.serialize?.(renderCtx, app);
225
-
226
- if (!result) return;
227
- if (result.json) json[key] = result.json;
228
- })
229
- );
230
-
231
- // more assets will be available in the future
232
- return { json };
233
- }
234
-
235
- private async deserialize(lifecycleHooks: [string, RenderLifecycle][]) {
236
- const rawAssets = Html.popAssets();
237
-
238
- const deserialized = await Promise.all(
239
- lifecycleHooks.map(async ([key, hooks]) => {
240
- try {
241
- const raw = rawAssets.get(key);
242
- return hooks.deserialize?.(raw);
243
- } catch (e) {
244
- // eslint-disable-next-line no-console
245
- console.error(`failed deserializing server state for aspect ${key}`, e);
246
- return undefined;
247
- }
248
- })
249
- );
250
-
251
- return deserialized;
100
+ registerRenderHooks<T, Y>(hooks: RenderPlugins<T, Y>) {
101
+ return this.renderPluginsSlot.register(hooks);
252
102
  }
253
103
 
254
104
  private getRoot(rootExtension: string) {
255
105
  return this.uiRootSlot.get(rootExtension);
256
106
  }
257
107
 
258
- static slots = [Slot.withType<UIRootFactory>(), Slot.withType<ReactNode>(), Slot.withType<RenderLifecycle>()];
108
+ static slots = [Slot.withType<UIRootFactory>(), Slot.withType<ReactNode>(), Slot.withType<RenderPlugins>()];
259
109
 
260
110
  static dependencies = [GraphqlAspect, ReactRouterAspect];
261
111
 
@@ -264,11 +114,11 @@ export class UiUI {
264
114
  static async provider(
265
115
  [GraphqlUi, router]: [GraphqlUI, ReactRouterUI],
266
116
  config,
267
- [uiRootSlot, hudSlot, renderLifecycleSlot]: [UIRootRegistry, HudSlot, renderLifecycleSlot]
117
+ [uiRootSlot, hudSlot, renderLifecycleSlot]: [UIRootRegistry, HudSlot, RenderPluginsSlot]
268
118
  ) {
269
119
  const uiUi = new UiUI(router, uiRootSlot, hudSlot, renderLifecycleSlot);
270
120
 
271
- uiUi.registerRenderHooks(GraphqlUi.renderHooks);
121
+ uiUi.registerRenderHooks(GraphqlUi.renderPlugins);
272
122
 
273
123
  return uiUi;
274
124
  }