@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.
- package/{__preview-1643426469482.js → __preview-1643858579620.js} +1 -1
- package/dist/index.d.ts +7 -7
- package/dist/index.js +4 -52
- package/dist/index.js.map +1 -1
- package/dist/react-ssr/index.d.ts +5 -0
- package/dist/react-ssr/index.js +23 -0
- package/dist/react-ssr/index.js.map +1 -0
- package/dist/react-ssr/react-ssr.d.ts +21 -0
- package/dist/react-ssr/react-ssr.js +285 -0
- package/dist/react-ssr/react-ssr.js.map +1 -0
- package/dist/{render-lifecycle.d.ts → react-ssr/render-lifecycle.d.ts} +8 -4
- package/dist/{render-lifecycle.js → react-ssr/render-lifecycle.js} +0 -0
- package/dist/{render-lifecycle.js.map → react-ssr/render-lifecycle.js.map} +0 -0
- package/dist/{ssr → react-ssr}/request-browser.d.ts +0 -5
- package/dist/react-ssr/request-browser.js +3 -0
- package/dist/{ssr/request-server.js.map → react-ssr/request-browser.js.map} +0 -0
- package/dist/{ssr → react-ssr}/request-server.d.ts +0 -0
- package/dist/{ssr → react-ssr}/request-server.js +0 -0
- package/dist/{ssr/ssr-content.js.map → react-ssr/request-server.js.map} +0 -0
- package/dist/{ssr → react-ssr}/ssr-content.d.ts +0 -0
- package/dist/{ssr → react-ssr}/ssr-content.js +0 -0
- package/dist/react-ssr/ssr-content.js.map +1 -0
- package/dist/ssr-middleware/extract-browser-data.d.ts +6 -0
- package/dist/{ssr/request-browser.js → ssr-middleware/extract-browser-data.js} +4 -5
- package/dist/ssr-middleware/extract-browser-data.js.map +1 -0
- package/dist/ssr-middleware/index.d.ts +1 -0
- package/dist/ssr-middleware/index.js +23 -0
- package/dist/ssr-middleware/index.js.map +1 -0
- package/dist/{ssr/render-middleware.d.ts → ssr-middleware/ssr-middleware.d.ts} +0 -0
- package/dist/{ssr/render-middleware.js → ssr-middleware/ssr-middleware.js} +5 -5
- package/dist/ssr-middleware/ssr-middleware.js.map +1 -0
- package/dist/ui-server.js +4 -4
- package/dist/ui-server.js.map +1 -1
- package/dist/ui.ui.runtime.d.ts +8 -20
- package/dist/ui.ui.runtime.js +25 -215
- package/dist/ui.ui.runtime.js.map +1 -1
- package/package-tar/teambit-ui-0.0.640.tgz +0 -0
- package/package.json +18 -17
- package/react-ssr/index.ts +6 -0
- package/react-ssr/react-ssr.tsx +183 -0
- package/{render-lifecycle.tsx → react-ssr/render-lifecycle.tsx} +6 -4
- package/{ssr → react-ssr}/request-browser.ts +0 -31
- package/{ssr → react-ssr}/request-server.ts +0 -0
- package/{ssr → react-ssr}/ssr-content.ts +0 -0
- package/ssr-middleware/extract-browser-data.ts +31 -0
- package/ssr-middleware/index.ts +1 -0
- package/{ssr/render-middleware.ts → ssr-middleware/ssr-middleware.ts} +3 -3
- package/ui.ui.runtime.tsx +29 -179
- package/compose.tsx +0 -27
- package/dist/compose.d.ts +0 -14
- package/dist/compose.js +0 -37
- package/dist/compose.js.map +0 -1
- package/dist/ssr/render-middleware.js.map +0 -1
- package/dist/ssr/request-browser.js.map +0 -1
- 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.
|
|
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.
|
|
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.
|
|
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.
|
|
60
|
-
"@teambit/cli": "0.0.
|
|
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.
|
|
63
|
-
"@teambit/component": "0.0.
|
|
64
|
-
"@teambit/express": "0.0.
|
|
65
|
-
"@teambit/graphql": "0.0.
|
|
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.
|
|
68
|
-
"@teambit/cache": "0.0.
|
|
69
|
-
"@teambit/pubsub": "0.0.
|
|
70
|
-
"@teambit/react-router": "0.0.
|
|
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.
|
|
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.
|
|
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.
|
|
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 './
|
|
3
|
-
import { RequestServer } from './
|
|
4
|
-
import { ContextProps } from './ui.ui.runtime';
|
|
2
|
+
import { BrowserData } from './request-browser';
|
|
3
|
+
import { RequestServer } from './request-server';
|
|
5
4
|
|
|
6
|
-
export type
|
|
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 {
|
|
7
|
-
import {
|
|
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 =
|
|
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
|
|
20
|
-
import
|
|
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
|
|
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
|
|
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.
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
103
|
-
const
|
|
104
|
-
<
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
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.
|
|
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:
|
|
152
|
-
return this.
|
|
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<
|
|
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,
|
|
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.
|
|
121
|
+
uiUi.registerRenderHooks(GraphqlUi.renderPlugins);
|
|
272
122
|
|
|
273
123
|
return uiUi;
|
|
274
124
|
}
|