Skip to content

Commit 8e25bab

Browse files
committed
Client: Improve SSR
1 parent 1f01a6b commit 8e25bab

File tree

5 files changed

+60
-44
lines changed

5 files changed

+60
-44
lines changed

client/src/components/ComponentB.tsx

+1-2
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ import { Helmet } from "react-helmet";
99
import { Header, Icon, Container, Menu } from "semantic-ui-react";
1010
import { BaseComponent } from "./BaseComponent";
1111
import { StructuredData } from "./StructuredData";
12-
import * as SPAs from "../../config/spa.config";
1312
import { getTitle, getCanonical } from "../utils/misc";
1413

1514
const cssNav = css({
@@ -64,7 +63,7 @@ const Navigation: React.FC = _props => {
6463
<Menu.Item>
6564
<Menu.Header>Go back to</Menu.Header>
6665
<Menu.Menu>
67-
<Menu.Item href={`/${SPAs.getNames()[0]}`}>
66+
<Menu.Item href="/">
6867
First SPA
6968
<Icon
7069
name="object group outline"

client/src/components/Navigation.tsx

+42-4
Original file line numberDiff line numberDiff line change
@@ -9,35 +9,73 @@ import { NavLink } from "react-router-dom";
99
import { Menu, Icon } from "semantic-ui-react";
1010
import * as SPAs from "../../config/spa.config";
1111
import styles from "../css/navigation.module.css";
12+
import { isServer } from "../utils/postprocess/misc";
1213

1314
const cssStyle: Record<string, string> = {
1415
menu: styles["menu"],
1516
};
1617

18+
const pathArray = ["/", "/a", "/lighthouse", "/namelookup"];
19+
20+
const getIndex = () => {
21+
if (isServer()) {
22+
return -1;
23+
}
24+
const path = window.location.pathname;
25+
return pathArray.findIndex((item) => item === path);
26+
}
27+
1728
export const Navigation: React.FC = _props => {
29+
React.useEffect(() => {
30+
const idx = getIndex();
31+
const menuItem = document.querySelector(`div.menu>a:nth-child(${idx + 1})`);
32+
menuItem?.classList.add("active");
33+
if (idx > 0) {
34+
const menuItem = document.querySelector("div.menu>a:nth-child(1)");
35+
menuItem?.classList.remove("active");
36+
}
37+
}, []); // run once upon initial rendering
38+
1839
return (
1940
<nav className={cssStyle.menu}>
2041
<Menu
2142
vertical
2243
size="large"
2344
className="nav_menu"
45+
defaultActiveIndex={0}
2446
>
2547
<Menu.Item>
2648
<Menu.Header>First SPA</Menu.Header>
2749
<Menu.Menu>
28-
<Menu.Item header as={NavLink} exact to="/">
50+
<Menu.Item
51+
header
52+
as={NavLink}
53+
exact to={pathArray[0]}
54+
>
2955
Overview
3056
<Icon name="file alternate outline" size="large" />
3157
</Menu.Item>
32-
<Menu.Item header as={NavLink} to="/a">
58+
<Menu.Item
59+
header
60+
as={NavLink}
61+
to={pathArray[1]}
62+
>
3363
ComponentA
3464
<Icon name="cube" size="large" />
3565
</Menu.Item>
36-
<Menu.Item header as={NavLink} to="/lighthouse">
66+
<Menu.Item
67+
header
68+
as={NavLink}
69+
to={pathArray[2]}
70+
>
3771
Lighthouse
3872
<Icon name="tachometer alternate" size="large" />
3973
</Menu.Item>
40-
<Menu.Item header as={NavLink} to="/namelookup">
74+
<Menu.Item
75+
header
76+
as={NavLink}
77+
to={pathArray[3]}
78+
>
4179
NameLookup
4280
<Icon name="search" size="large" />
4381
</Menu.Item>

client/src/entrypoints/first.tsx

-4
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313
*/
1414
import * as React from "react";
1515
import * as ReactDOM from "react-dom";
16-
import { Helmet } from "react-helmet";
1716
import { Router, Route, Switch } from "react-router-dom";
1817
import { ComponentA } from "../components/ComponentA";
1918
import { Lighthouse } from "../components/Lighthouse";
@@ -43,9 +42,6 @@ const First: React.FC = _props => {
4342
<>
4443
<Router history={getHistory()}>
4544
<ErrorBoundary>
46-
<Helmet>
47-
<meta charSet="utf-8" />
48-
</Helmet>
4945
<div className="welcome">
5046
<h2>Welcome to {SPAs.appTitle}</h2>
5147
</div>

client/src/entrypoints/second.tsx

-4
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313
*/
1414
import * as React from "react";
1515
import * as ReactDOM from "react-dom";
16-
import { Helmet } from "react-helmet";
1716
import { ComponentB } from "../components/ComponentB";
1817
import { ErrorBoundary } from "../components/ErrorBoundary";
1918
// import { renderToString } from "react-dom/server";
@@ -26,9 +25,6 @@ const Second: React.FC = _props => {
2625
return (
2726
<>
2827
<ErrorBoundary>
29-
<Helmet>
30-
<meta charSet="utf-8" />
31-
</Helmet>
3228
<div className="welcome">
3329
<h2>Welcome to {SPAs.appTitle}</h2>
3430
</div>

client/src/utils/postprocess/postProcessSSR.ts

+17-30
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import * as fs from "fs";
22
import * as path from "path";
33
import { promisify } from "util";
44
import { JSDOM } from "jsdom";
5-
import { CallbackWrapper } from "./misc"
65
import * as SPAs from "../../../config/spa.config";
76

87
export async function postProcess(workDir: string, filePattern: string): Promise<void> {
@@ -23,39 +22,27 @@ export async function postProcess(workDir: string, filePattern: string): Promise
2322

2423
async function postProcessBody(workDir: string, htmlFile: string, ssrFile: string): Promise<void> {
2524
const readFile = promisify(fs.readFile);
25+
const writeFile = promisify(fs.writeFile);
26+
2627
const htmlFilePath = path.join(workDir, htmlFile);
2728
const ssrFilePath = path.join(workDir, ssrFile);
2829

29-
const dataHtml = await readFile(htmlFilePath);
30+
const jsdom = await JSDOM.fromFile(htmlFilePath);
31+
32+
if (!jsdom) {
33+
throw "JSDOM creation failure";
34+
}
35+
3036
const dataSsr = (await readFile(ssrFilePath)).toString();
31-
const reReact = /^\s*<div\s+id="app-root">/;
32-
const ar: string[] = dataHtml.toString().replace(/\r\n?/g, "\n").split("\n");
33-
34-
const out = ar.map(str => {
35-
if (reReact.test(str)) {
36-
str += "\n";
37-
str += dataSsr;
38-
}
39-
str += "\n";
40-
return str;
41-
});
42-
43-
const stream = fs.createWriteStream(htmlFilePath);
44-
stream.on("error", err => {
45-
console.error(`Failed to write to file ${htmlFilePath}, error: ${err}`)
46-
});
47-
out.forEach(str => { stream.write(str); });
48-
49-
stream.end();
50-
51-
let cbWrapper: CallbackWrapper|undefined;
52-
const waitForBufferFlush = new Promise<void>((resolve) => { cbWrapper = new CallbackWrapper(resolve); });
53-
54-
stream.on("close", function() {
55-
cbWrapper?.invoke();
56-
});
57-
58-
await waitForBufferFlush;
37+
const fragment = JSDOM.fragment(dataSsr);
38+
const appRoot = jsdom.window.document.querySelector("div[id='app-root']");
39+
40+
if (!appRoot) {
41+
throw "JSDOM - 'app-root' not found";
42+
}
43+
44+
appRoot.appendChild(fragment);
45+
await writeFile(htmlFilePath, jsdom.serialize());
5946
}
6047

6148
async function postProcessHeader(workDir: string, htmlFile: string): Promise<void> {

0 commit comments

Comments
 (0)