Skip to content

Commit 4371f08

Browse files
authored
Merge pull request #1 from Yandex-Practicum/ssr-final-with-all-commits
Ssr final with all commits
2 parents 263ea17 + ea20370 commit 4371f08

28 files changed

+1252
-196
lines changed

.env.sample

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,5 @@ POSTGRES_USER=postgres
44
POSTGRES_PASSWORD=postgres
55
POSTGRES_DB=postgres
66
POSTGRES_PORT=5432
7+
EXTERNAL_SERVER_URL=http://localhost:3001
8+
INTERNAL_SERVER_URL=http://server:3001

Dockerfile.client

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
ARG NODE_VERSION=16
1+
ARG NODE_VERSION=18
22
ARG CLIENT_PORT=3001
33

44
FROM node:$NODE_VERSION-buster as base
@@ -15,12 +15,13 @@ COPY . .
1515
RUN yarn lerna bootstrap
1616
RUN rm -rf /app/packages/client/dist/ && yarn build --scope=client
1717

18-
19-
FROM nginx:latest as production
18+
FROM node:$NODE_VERSION-buster-slim as production
2019
WORKDIR /app
2120

22-
COPY --from=builder /app/packages/client/dist/ /app/
23-
COPY --from=builder /app/packages/client/nginx.conf /etc/nginx/nginx.conf
21+
COPY --from=builder /app/packages/client/dist/ /app/dist/
22+
COPY --from=builder /app/packages/client/server/ /app/server/
23+
COPY --from=builder /app/packages/client/package.json /app/package.json
24+
RUN yarn install --production=true
2425

2526
EXPOSE $CLIENT_PORT
26-
CMD [ "nginx", "-g", "daemon off;" ]
27+
CMD [ "node", "/app/server/index.js" ]

Dockerfile.server

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
ARG NODE_VERSION=16
1+
ARG NODE_VERSION=18
22
ARG SERVER_PORT=3001
33

44
FROM node:$NODE_VERSION-buster as base
@@ -24,4 +24,4 @@ COPY --from=builder /app/packages/server/package.json /app/package.json
2424
RUN yarn install --production=true
2525

2626
EXPOSE $SERVER_PORT
27-
CMD [ "node", "/app/index.js" ]
27+
CMD [ "node", "/app/index.js" ]

docker-compose.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ services:
3030
SERVER_PORT: ${SERVER_PORT}
3131

3232
postgres:
33-
image: postgres:14
33+
image: postgres:14
3434
ports:
3535
- "${POSTGRES_PORT}:${POSTGRES_PORT}"
3636
environment:

packages/client/index.html

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
<!DOCTYPE html>
22
<html lang="en">
33
<head>
4-
<meta charset="UTF-8" />
54
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
65
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
7-
<title>{TODO: Set title :) }</title>
6+
<!--ssr-styles-->
7+
<!--ssr-helmet-->
88
</head>
99
<body>
10-
<div id="root"></div>
10+
<!--ssr-initial-state-->
11+
<div id="root"><!--ssr-outlet--></div>
1112
<script type="module" src="/src/main.tsx"></script>
1213
</body>
1314
</html>

packages/client/package.json

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,31 @@
11
{
22
"name": "client",
33
"version": "0.0.0",
4-
"type": "module",
54
"scripts": {
6-
"dev": "vite",
7-
"build": "tsc && vite build",
8-
"preview": "vite preview",
5+
"dev": "tsc --project tsconfig.server.json && cross-env NODE_ENV=development EXTERNAL_SERVER_URL='http://localhost:3001' INTERNAL_SERVER_URL='http://localhost:3001' node server/index.js",
6+
"build": "tsc --project tsconfig.server.json && vite build && vite build --ssr src/entry-server.tsx --outDir dist/server",
7+
"preview": "node server/index.js",
98
"lint": "eslint .",
109
"format": "prettier --write .",
1110
"test": "jest ./"
1211
},
1312
"dependencies": {
13+
"@reduxjs/toolkit": "^1.9.5",
14+
"@types/react-helmet": "^6.1.7",
15+
"cookie-parser": "^1.4.6",
1416
"dotenv": "^16.0.2",
1517
"eslint-config-prettier": "^8.5.0",
18+
"express": "^4.18.2",
1619
"prettier": "^2.7.1",
1720
"react": "^18.2.0",
18-
"react-dom": "^18.2.0"
21+
"react-dom": "^18.2.0",
22+
"react-helmet": "^6.1.0",
23+
"react-redux": "^8.1.2",
24+
"react-router-dom": "^6.16.0",
25+
"redux": "^4.2.1",
26+
"serialize-javascript": "^6.0.1",
27+
"styled-components": "^6.1.0",
28+
"vite": "^4.4.9"
1929
},
2030
"devDependencies": {
2131
"@testing-library/react": "^13.3.0",
@@ -31,8 +41,7 @@
3141
"lefthook": "^1.3.9",
3242
"prettier": "^2.7.1",
3343
"ts-jest": "^28.0.8",
34-
"typescript": "^4.8.2",
35-
"vite": "^3.0.7"
44+
"typescript": "^4.8.2"
3645
},
3746
"license": "MIT"
3847
}

packages/client/server/index.ts

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
import dotenv from 'dotenv'
2+
dotenv.config()
3+
4+
import { HelmetData } from 'react-helmet'
5+
import express, { Request as ExpressRequest } from 'express'
6+
import path from 'path'
7+
8+
import fs from 'fs/promises'
9+
import { createServer as createViteServer, ViteDevServer } from 'vite'
10+
import serialize from 'serialize-javascript'
11+
import cookieParser from 'cookie-parser'
12+
13+
const port = process.env.PORT || 80
14+
const clientPath = path.join(__dirname, '..')
15+
const isDev = process.env.NODE_ENV === 'development'
16+
17+
async function createServer() {
18+
const app = express()
19+
20+
app.use(cookieParser())
21+
let vite: ViteDevServer | undefined
22+
if (isDev) {
23+
vite = await createViteServer({
24+
server: { middlewareMode: true },
25+
root: clientPath,
26+
appType: 'custom',
27+
})
28+
29+
app.use(vite.middlewares)
30+
} else {
31+
app.use(
32+
express.static(path.join(clientPath, 'dist/client'), { index: false })
33+
)
34+
}
35+
36+
app.get('*', async (req, res, next) => {
37+
const url = req.originalUrl
38+
39+
try {
40+
// Получаем файл client/index.html который мы правили ранее
41+
// Создаём переменные
42+
let render: (
43+
req: ExpressRequest
44+
) => Promise<{ html: string; initialState: unknown; helmet: HelmetData; styleTags: string }>
45+
let template: string
46+
if (vite) {
47+
template = await fs.readFile(
48+
path.resolve(clientPath, 'index.html'),
49+
'utf-8'
50+
)
51+
52+
// Применяем встроенные HTML-преобразования vite и плагинов
53+
template = await vite.transformIndexHtml(url, template)
54+
55+
// Загружаем модуль клиента, который писали выше,
56+
// он будет рендерить HTML-код
57+
render = (
58+
await vite.ssrLoadModule(
59+
path.join(clientPath, 'src/entry-server.tsx')
60+
)
61+
).render
62+
} else {
63+
template = await fs.readFile(
64+
path.join(clientPath, 'dist/client/index.html'),
65+
'utf-8'
66+
)
67+
68+
// Получаем путь до сбилдженого модуля клиента, чтобы не тащить средства сборки клиента на сервер
69+
const pathToServer = path.join(
70+
clientPath,
71+
'dist/server/entry-server.js'
72+
)
73+
74+
// Импортируем этот модуль и вызываем с инишл стейтом
75+
render = (await import(pathToServer)).render
76+
}
77+
78+
// Получаем HTML-строку из JSX
79+
const { html: appHtml, initialState, helmet, styleTags } = await render(req)
80+
81+
// Заменяем комментарий на сгенерированную HTML-строку
82+
const html = template
83+
.replace('<!--ssr-styles-->', styleTags)
84+
.replace(`<!--ssr-helmet-->`, `${helmet.meta.toString()} ${helmet.title.toString()} ${helmet.link.toString()}`)
85+
.replace(`<!--ssr-outlet-->`, appHtml)
86+
.replace(
87+
`<!--ssr-initial-state-->`,
88+
`<script>window.APP_INITIAL_STATE = ${serialize(initialState, {
89+
isJSON: true,
90+
})}</script>`
91+
)
92+
93+
// Завершаем запрос и отдаём HTML-страницу
94+
res.status(200).set({ 'Content-Type': 'text/html' }).end(html)
95+
} catch (e) {
96+
vite.ssrFixStacktrace(e as Error)
97+
next(e)
98+
}
99+
})
100+
101+
app.listen(port, () => {
102+
console.log(`Client is listening on port: ${port}`)
103+
})
104+
}
105+
106+
createServer()

packages/client/src/App.tsx

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,22 @@
1-
import { useEffect } from 'react'
2-
import './App.css'
1+
import { useSelector } from './store'
32

4-
function App() {
5-
useEffect(() => {
6-
const fetchServerData = async () => {
7-
const url = `http://localhost:${__SERVER_PORT__}`
8-
const response = await fetch(url)
9-
const data = await response.json()
10-
console.log(data)
11-
}
3+
import { fetchUserThunk, selectUser } from './slices/userSlice'
124

13-
fetchServerData()
14-
}, [])
15-
return <div className="App">Вот тут будет жить ваше приложение :)</div>
5+
const App = () => {
6+
const user = useSelector(selectUser)
7+
8+
return (
9+
<div>
10+
{user ? (
11+
<div>
12+
<p>{user.name}</p>
13+
<p>{user.secondName}</p>
14+
</div>
15+
) : (
16+
<p>Пользователь не найден!</p>
17+
)}
18+
</div>
19+
)
1620
}
1721

1822
export default App

packages/client/src/client.d.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1-
declare const __SERVER_PORT__: number
1+
declare const __EXTERNAL_SERVER_URL__: string
2+
declare const __INTERNAL_SERVER_URL__: string
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { Link } from 'react-router-dom'
2+
3+
export const Header = () => {
4+
return (
5+
<nav>
6+
<ul>
7+
<li>
8+
<Link to="/">Главная</Link>
9+
</li>
10+
<li>
11+
<Link to="/friends">Страница со списком друзей</Link>
12+
</li>
13+
<li>
14+
<Link to="/404">404</Link>
15+
</li>
16+
</ul>
17+
</nav>
18+
)
19+
}

0 commit comments

Comments
 (0)