From 08ffd5a7408360615d7060e081404f89d5070580 Mon Sep 17 00:00:00 2001 From: Primakov Alexandr Alexandrovich Date: Fri, 24 Oct 2025 11:34:04 +0300 Subject: [PATCH] Refactor SSR implementation and update documentation. Replaced Static Site Generation (SSG) with Server-Side Rendering (SSR) using `react-dom/server`. Updated scripts for SSR rendering and removed deprecated `prerender-multi.js`. Enhanced `terms.html` generation from React components. Updated dependencies for Babel and added support for canvas in SSR. --- cloud.md | 144 +++++---- package-lock.json | 619 +++++++++++++++++++++++++++++++++++-- package.json | 8 +- scripts/prerender-multi.js | 169 ---------- scripts/ssr-prerender.js | 79 ----- scripts/ssr-render.js | 93 ++++++ 6 files changed, 765 insertions(+), 347 deletions(-) delete mode 100644 scripts/prerender-multi.js delete mode 100644 scripts/ssr-prerender.js create mode 100644 scripts/ssr-render.js diff --git a/cloud.md b/cloud.md index 0ec304b..144100e 100644 --- a/cloud.md +++ b/cloud.md @@ -19,11 +19,12 @@ ### Особенности: ✅ **React 18** с TypeScript -✅ **Static Site Generation** - HTML вкомпилирован для SEO +✅ **Server-Side Rendering (SSR)** - полноценный рендеринг React компонентов для SEO ✅ **Chakra UI** - современный UI framework ✅ **i18next** - мультиязычность (ru/en) ✅ **React Router** - клиентский роутинг -✅ **Hydration** - гидратация статического контента +✅ **Hydration** - гидратация SSR контента +✅ **jsdom + canvas** - полноценная DOM эмуляция для SSR ### Страницы: @@ -46,8 +47,7 @@ bro.landing/ │ ├── index.tsx # Entry point + hydration │ └── index.ejs # HTML шаблон ├── scripts/ -│ ├── prerender-multi.js # ⭐ Основной SSG скрипт -│ └── ssr-prerender.js # Альтернативный SSR +│ └── ssr-render.js # ⭐ SSR скрипт (react-dom/server) ├── dist/ # Сборка (генерируется) │ ├── index.html # Главная (SSG) │ ├── terms.html # Соглашение (SSG) @@ -58,23 +58,25 @@ bro.landing/ --- -## Static Site Generation (SSG) +## Server-Side Rendering (SSR) ### Как работает? -1. **Webpack** собирает React → `dist/index.js` -2. **Скрипт `prerender-multi.js`** автоматически: - - Читает `dist/index.html` (пустой шаблон) - - Вставляет статический контент в `
` - - Генерирует `dist/terms.html` из `terma.md` -3. **React hydration** при загрузке оживляет статический HTML +1. **Webpack** собирает React → `dist/index.js` + `dist/index.html` (шаблон) +2. **Скрипт `ssr-render.js`** автоматически: + - Настраивает окружение Node.js (jsdom + canvas) + - Импортирует React компоненты из `src/pages/` + - Рендерит компоненты через `react-dom/server` + - Инжектит HTML в `dist/index.html` и `dist/terms.html` +3. **React hydration** при загрузке оживляет SSR HTML -### Преимущества SSG: +### Преимущества SSR: - 🚀 **Быстрая загрузка** - контент виден до загрузки JS -- 🔍 **SEO** - поисковики индексируют полный HTML +- 🔍 **SEO** - поисковики индексируют полный HTML с Chakra UI - ♿ **Доступность** - работает без JavaScript - 📊 **Метрики** - улучшенные FCP и LCP +- ✅ **Никакого хардкода** - рендер реальных компонентов ### Hydration в index.tsx: @@ -82,49 +84,37 @@ bro.landing/ const hasPrerenderedContent = MOUNT_NODE.hasChildNodes(); if (hasPrerenderedContent) { - hydrateRoot(MOUNT_NODE, ); // Оживляем статику + hydrateRoot(MOUNT_NODE, ); // Оживляем SSR HTML } else { createRoot(MOUNT_NODE).render(); // Обычный рендер } ``` -### Скрипт prerender-multi.js: +### Скрипт ssr-render.js: **Что делает**: -1. Читает `terma.md` (исходник соглашения) -2. Парсит markdown → HTML со стилями -3. Создает `dist/index.html` с контентом главной -4. Создает `dist/terms.html` с полным соглашением (~13KB) +1. Настраивает Babel для транспиляции TSX → JS +2. Настраивает jsdom для DOM эмуляции +3. Настраивает canvas для Lottie анимаций +4. Импортирует компоненты `UnderConstruction` и `Terms` +5. Рендерит через `renderToString(React.createElement(Component))` +6. Создает `dist/index.html` и `dist/terms.html` с полным HTML **Автоматический запуск**: - `npm run build:prod` - после webpack сборки -- `npm run build:prod:ssr` - альтернативный SSR --- ## Страница пользовательского соглашения -### Источник: `terma.md` +### Источник: `Terms.tsx` -⚠️ **Важно**: `terma.md` - единственный источник правды для соглашения! +⚠️ **Важно**: `src/pages/terms/Terms.tsx` - единственный источник правды для соглашения! Чтобы обновить соглашение: -1. Отредактируйте `terma.md` +1. Отредактируйте `src/pages/terms/Terms.tsx` 2. Запустите `npm run build:prod` -3. `dist/terms.html` обновится автоматически - -### Структура terma.md: - -```markdown -Пользовательское соглашение для BROJS.RU -Последнее обновление: 25 мая 2025 г. - -1. Термины -... - -10. Контакты -Для обращений: primakov.pro@yandex.ru -``` +3. `dist/terms.html` обновится автоматически через SSR ### React компонент: Terms.tsx @@ -132,21 +122,46 @@ if (hasPrerenderedContent) { - **Дизайн**: Chakra UI, официальный стиль документа - **SEO**: Helmet с meta-тегами - **Роут**: `/terms` в dashboard.tsx +- **SSR**: Рендерится через `renderToString()` в `ssr-render.js` + +### Структура компонента: + +```tsx +export const Terms = () => { + return ( + <> + + Пользовательское соглашение - BROJS.RU + + + + + + + Пользовательское соглашение + {/* ... полный контент ... */} + + + + + ); +}; +``` ### SEO-версия: terms.html -Генерируется автоматически из `terma.md`: +Генерируется автоматически через SSR из компонента `Terms.tsx`: ```html Пользовательское соглашение - BROJS.RU
-
-

Пользовательское соглашение для BROJS.RU

-

1. Термины

-
    -
  • Платформа — сайт https://brojs.ru ...
  • +
    +
    +

    Пользовательское соглашение

    +

    для BROJS.RU

    +

    Последнее обновление: 25 мая 2025 г.

    ...
@@ -174,8 +189,7 @@ if (hasPrerenderedContent) { { "start": "brojs server --port=8099 --with-open-browser", "build": "npm run clean && brojs build --dev", - "build:prod": "npm run clean && brojs build && node scripts/prerender-multi.js", - "build:prod:ssr": "npm run clean && brojs build && node scripts/ssr-prerender.js", + "build:prod": "npm run clean && brojs build && node scripts/ssr-render.js", "clean": "rimraf dist", "eslint": "npx eslint src", "prettier": "prettier --write .", @@ -204,35 +218,30 @@ npm run build # → Результат: dist/index.html (пустой шаблон) ``` -#### 🚀 Production сборка (с SSG): +#### 🚀 Production сборка (с SSR): ```bash npm run build:prod # → Webpack в production режиме # → Минификация -# → Автоматический SSG (prerender-multi.js) +# → Автоматический SSR (ssr-render.js) +# → Рендер реальных React компонентов через react-dom/server # → Результат: -# - dist/index.html (со статическим контентом) -# - dist/terms.html (полное соглашение для SEO) +# - dist/index.html (UnderConstruction компонент) +# - dist/terms.html (Terms компонент с Chakra UI) ``` Вывод: ``` ✅ Сборка успешно завершена! -🚀 Начинаем мульти-страничный пре-рендеринг... -✅ index.html обновлен -📝 Генерируем полный HTML из terma.md... -✅ terms.html создан с полным контентом -🎉 Пре-рендеринг завершен успешно! -``` - -#### 🔄 Production сборка (альтернативный SSR): - -```bash -npm run build:prod:ssr -# → Использует ssr-prerender.js -# → SSR с jsdom окружением -# → Результат аналогичен build:prod +🚀 Запуск SSR с рендерингом React компонентов... +✅ Компоненты загружены +✅ Компоненты отрендерены +✅ index.html обновлен с SSR контентом +✅ terms.html создан с SSR контентом +🎉 SSR завершен успешно! +📄 Созданы: index.html, terms.html +💡 Весь контент отрендерен через React SSR ``` ### Другие команды: @@ -267,12 +276,15 @@ npm run test # Запустить тесты - **@brojs/cli 1.9.5** - сборщик (обертка над webpack) - **Webpack 5** - бандлер -- **Babel** - транспиляция -- **jsdom** - SSR окружение +- **@babel/register** - транспиляция TSX в Node.js +- **@babel/preset-react** - JSX поддержка +- **@babel/preset-typescript** - TypeScript поддержка +- **jsdom 25.x** - DOM эмуляция для SSR +- **canvas 3.x** - Canvas API для Lottie в SSR ### Дополнительно: -- **Lottie React** - анимации +- **Lottie React** - анимации (работает в SSR!) - **Day.js** - работа с датами - **ESLint** - линтинг - **Prettier** - форматирование diff --git a/package-lock.json b/package-lock.json index f1166a7..e7fd893 100644 --- a/package-lock.json +++ b/package-lock.json @@ -33,9 +33,14 @@ "redux": "^5.0.1" }, "devDependencies": { + "@babel/core": "^7.28.5", + "@babel/preset-env": "^7.28.5", + "@babel/preset-react": "^7.28.5", + "@babel/register": "^7.28.3", "@eslint/js": "^9.9.0", "@types/jest": "^29.5.12", "babel-jest": "^29.7.0", + "canvas": "^3.2.0", "eslint": "^8.57.0", "eslint-plugin-react": "^7.35.0", "eslint-plugin-react-hooks": "^4.6.2", @@ -48,19 +53,6 @@ "typescript-eslint": "^8.1.0" } }, - "node_modules/@ampproject/remapping": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", - "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", - "license": "Apache-2.0", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" - }, - "engines": { - "node": ">=6.0.0" - } - }, "node_modules/@asamuzakjp/css-color": { "version": "4.0.5", "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-4.0.5.tgz", @@ -161,21 +153,21 @@ } }, "node_modules/@babel/core": { - "version": "7.25.2", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.25.2.tgz", - "integrity": "sha512-BBt3opiCOxUr9euZ5/ro/Xv8/V7yJ5bjYMqG/C1YAo8MIKAnumZalCN+msbci3Pigy4lIQfPUpfMM27HMGaYEA==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.5.tgz", + "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==", "license": "MIT", "dependencies": { - "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.24.7", - "@babel/generator": "^7.25.0", - "@babel/helper-compilation-targets": "^7.25.2", - "@babel/helper-module-transforms": "^7.25.2", - "@babel/helpers": "^7.25.0", - "@babel/parser": "^7.25.0", - "@babel/template": "^7.25.0", - "@babel/traverse": "^7.25.2", - "@babel/types": "^7.25.2", + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.5", + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-module-transforms": "^7.28.3", + "@babel/helpers": "^7.28.4", + "@babel/parser": "^7.28.5", + "@babel/template": "^7.27.2", + "@babel/traverse": "^7.28.5", + "@babel/types": "^7.28.5", + "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -450,13 +442,13 @@ } }, "node_modules/@babel/helpers": { - "version": "7.25.6", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.25.6.tgz", - "integrity": "sha512-Xg0tn4HcfTijTwfDwYlvVCl43V6h4KyVVX2aEm4qdO/PC6L2YvzLHFdmxhoeSA3eslcE6+ZVXHgWwopXYLNq4Q==", + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.4.tgz", + "integrity": "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==", "license": "MIT", "dependencies": { - "@babel/template": "^7.25.0", - "@babel/types": "^7.25.6" + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.4" }, "engines": { "node": ">=6.9.0" @@ -1861,6 +1853,165 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/register": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/register/-/register-7.28.3.tgz", + "integrity": "sha512-CieDOtd8u208eI49bYl4z1J22ySFw87IGwE+IswFEExH7e3rLgKb0WNQeumnacQ1+VoDJLYI5QFA3AJZuyZQfA==", + "dev": true, + "license": "MIT", + "dependencies": { + "clone-deep": "^4.0.1", + "find-cache-dir": "^2.0.0", + "make-dir": "^2.1.0", + "pirates": "^4.0.6", + "source-map-support": "^0.5.16" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/register/node_modules/find-cache-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.1.0.tgz", + "integrity": "sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "commondir": "^1.0.1", + "make-dir": "^2.0.0", + "pkg-dir": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@babel/register/node_modules/find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@babel/register/node_modules/locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@babel/register/node_modules/make-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", + "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pify": "^4.0.1", + "semver": "^5.6.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@babel/register/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@babel/register/node_modules/p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@babel/register/node_modules/path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/register/node_modules/pkg-dir": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", + "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", + "dev": true, + "license": "MIT", + "dependencies": { + "find-up": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@babel/register/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/@babel/register/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@babel/register/node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, "node_modules/@babel/runtime": { "version": "7.28.4", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz", @@ -4267,6 +4418,16 @@ "@jridgewell/trace-mapping": "^0.3.24" } }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, "node_modules/@jridgewell/resolve-uri": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", @@ -5906,6 +6067,27 @@ "bare-path": "^3.0.0" } }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, "node_modules/baseline-browser-mapping": { "version": "2.8.20", "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.20.tgz", @@ -5947,6 +6129,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, "node_modules/body-parser": { "version": "1.20.3", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", @@ -6071,6 +6265,31 @@ "node-int64": "^0.4.0" } }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, "node_modules/buffer-crc32": { "version": "0.2.13", "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", @@ -6165,6 +6384,21 @@ ], "license": "CC-BY-4.0" }, + "node_modules/canvas": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/canvas/-/canvas-3.2.0.tgz", + "integrity": "sha512-jk0GxrLtUEmW/TmFsk2WghvgHe8B0pxGilqCL21y8lHkPUGa6FTsnCNtHPOzT8O3y+N+m3espawV80bbBlgfTA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "node-addon-api": "^7.0.0", + "prebuild-install": "^7.1.3" + }, + "engines": { + "node": "^18.12.0 || >= 20.9.0" + } + }, "node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -6227,6 +6461,13 @@ "node": ">= 6" } }, + "node_modules/chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", + "dev": true, + "license": "ISC" + }, "node_modules/chrome-trace-event": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz", @@ -6415,6 +6656,13 @@ "integrity": "sha512-QE33hToZseCH3jS0qN96O/bSh3kaw/h+Tq7ngyY9eWDUnTlTNUyqfqvCXioLe5Na5jFsL78ra/wuBU4iuEgd4w==", "license": "ISC" }, + "node_modules/commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", + "dev": true, + "license": "MIT" + }, "node_modules/compute-scroll-into-view": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/compute-scroll-into-view/-/compute-scroll-into-view-3.0.3.tgz", @@ -6911,6 +7159,22 @@ "dev": true, "license": "MIT" }, + "node_modules/decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/dedent": { "version": "1.5.3", "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.5.3.tgz", @@ -6926,6 +7190,16 @@ } } }, + "node_modules/deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4.0.0" + } + }, "node_modules/deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", @@ -7072,6 +7346,16 @@ "npm": "1.2.8000 || >= 1.4.16" } }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, "node_modules/detect-newline": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", @@ -8024,6 +8308,16 @@ "node": ">= 0.8.0" } }, + "node_modules/expand-template": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", + "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==", + "dev": true, + "license": "(MIT OR WTFPL)", + "engines": { + "node": ">=6" + } + }, "node_modules/expect": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz", @@ -8743,6 +9037,13 @@ "node": ">= 0.6" } }, + "node_modules/fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", + "dev": true, + "license": "MIT" + }, "node_modules/fs-extra": { "version": "9.1.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", @@ -8925,6 +9226,13 @@ "node": ">= 14" } }, + "node_modules/github-from-package": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", + "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==", + "dev": true, + "license": "MIT" + }, "node_modules/glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", @@ -9533,6 +9841,27 @@ "postcss": "^8.1.0" } }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, "node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", @@ -11485,6 +11814,19 @@ "node": ">=6" } }, + "node_modules/mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -11522,6 +11864,13 @@ "dev": true, "license": "MIT" }, + "node_modules/mkdirp-classic": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", + "dev": true, + "license": "MIT" + }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -11546,6 +11895,13 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, + "node_modules/napi-build-utils": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-2.0.0.tgz", + "integrity": "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==", + "dev": true, + "license": "MIT" + }, "node_modules/natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", @@ -11589,6 +11945,39 @@ "tslib": "^2.0.3" } }, + "node_modules/node-abi": { + "version": "3.78.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.78.0.tgz", + "integrity": "sha512-E2wEyrgX/CqvicaQYU3Ze1PFGjc4QYPGsjUrlYkqAE0WjHEZwgOsGMPMzkMse4LjJbDmaEuDX3CM036j5K2DSQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/node-abi/node_modules/semver": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/node-addon-api": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", + "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", + "dev": true, + "license": "MIT" + }, "node_modules/node-fetch": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", @@ -12386,6 +12775,63 @@ "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", "license": "MIT" }, + "node_modules/prebuild-install": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz", + "integrity": "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==", + "dev": true, + "license": "MIT", + "dependencies": { + "detect-libc": "^2.0.0", + "expand-template": "^2.0.3", + "github-from-package": "0.0.0", + "minimist": "^1.2.3", + "mkdirp-classic": "^0.5.3", + "napi-build-utils": "^2.0.0", + "node-abi": "^3.3.0", + "pump": "^3.0.0", + "rc": "^1.2.7", + "simple-get": "^4.0.0", + "tar-fs": "^2.0.0", + "tunnel-agent": "^0.6.0" + }, + "bin": { + "prebuild-install": "bin.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/prebuild-install/node_modules/tar-fs": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.4.tgz", + "integrity": "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.1.4" + } + }, + "node_modules/prebuild-install/node_modules/tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -12742,6 +13188,32 @@ "node": ">= 0.8" } }, + "node_modules/rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "dev": true, + "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", + "dependencies": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "bin": { + "rc": "cli.js" + } + }, + "node_modules/rc/node_modules/strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/react": { "version": "18.3.1", "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", @@ -13115,6 +13587,21 @@ } } }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/readdirp": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", @@ -13743,6 +14230,53 @@ "dev": true, "license": "ISC" }, + "node_modules/simple-concat": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", + "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/simple-get": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz", + "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "decompress-response": "^6.0.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" + } + }, "node_modules/sisteransi": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", @@ -13889,6 +14423,16 @@ "text-decoder": "^1.1.0" } }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, "node_modules/string-length": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", @@ -14561,6 +15105,19 @@ "integrity": "sha512-+bGy9iDAqg3WSfc2ZrprToSPJhZjqy7vUv9wupQzsiv+BVPVx1T2a6G4T0290SpQj+56Toaw9BiLO5j5Bd7QzA==", "license": "MIT" }, + "node_modules/tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": "*" + } + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", diff --git a/package.json b/package.json index 069bd7e..8a030fd 100644 --- a/package.json +++ b/package.json @@ -13,8 +13,7 @@ "test": "jest --coverage", "start": "brojs server --port=8099 --with-open-browser", "build": "npm run clean && brojs build --dev", - "build:prod": "npm run clean && brojs build && node scripts/prerender-multi.js", - "build:prod:ssr": "npm run clean && brojs build && node scripts/ssr-prerender.js" + "build:prod": "npm run clean && brojs build && node scripts/ssr-render.js" }, "keywords": [], "author": "", @@ -44,9 +43,14 @@ "redux": "^5.0.1" }, "devDependencies": { + "@babel/core": "^7.28.5", + "@babel/preset-env": "^7.28.5", + "@babel/preset-react": "^7.28.5", + "@babel/register": "^7.28.3", "@eslint/js": "^9.9.0", "@types/jest": "^29.5.12", "babel-jest": "^29.7.0", + "canvas": "^3.2.0", "eslint": "^8.57.0", "eslint-plugin-react": "^7.35.0", "eslint-plugin-react-hooks": "^4.6.2", diff --git a/scripts/prerender-multi.js b/scripts/prerender-multi.js deleted file mode 100644 index 76a6b03..0000000 --- a/scripts/prerender-multi.js +++ /dev/null @@ -1,169 +0,0 @@ -/* eslint-disable @typescript-eslint/no-require-imports */ -/* eslint-disable no-undef */ -const fs = require('fs'); -const path = require('path'); - -// Контент для главной страницы -const homeContent = ` -
-
-
Loading...
-
-

Сайт в разработке

-
- `.trim(); - -// Функция для генерации полного HTML из terma.md -const generateTermsContent = () => { - const termaPath = path.resolve(__dirname, '../terma.md'); - const termaText = fs.readFileSync(termaPath, 'utf-8'); - - // Парсим markdown в HTML с сохранением структуры - let html = '
'; - html += '
'; - - const lines = termaText.split('\n'); - let inList = false; - - for (let i = 0; i < lines.length; i++) { - const line = lines[i].trim(); - - if (!line) { - if (inList) { - html += ''; - inList = false; - } - continue; - } - - // Заголовок H1 - if (i === 0) { - html += '
'; - html += `

${line}

`; - continue; - } - - // Дата обновления - if (line.includes('Последнее обновление')) { - html += `

${line}

`; - html += '

'; - continue; - } - - // Основные разделы (начинаются с цифры и точки без подразделов) - if (/^\d+\.\s+[А-Яа-я]/.test(line)) { - if (inList) { - html += ''; - inList = false; - } - const text = line.replace(/^\d+\.\s+/, ''); - html += `

${line}

`; - continue; - } - - // Подразделы (например, 2.1., 3.2.) - if (/^\d+\.\d+\.\s+/.test(line)) { - if (inList) { - html += ''; - inList = false; - } - html += `

${line}

`; - continue; - } - - // Списки (начинаются с заглавной буквы или содержат "—") - if (line.includes('—') || (i > 0 && lines[i-1].includes('через:')) || (i > 0 && lines[i-1].includes('обязуется:')) || (i > 0 && lines[i-1].includes('собирает:')) || (i > 0 && lines[i-1].includes('ответственности за:'))) { - if (!inList) { - html += '
    '; - inList = true; - } - - // Обработка ссылок - let processedLine = line - .replace(/https?:\/\/[^\s,)]+/g, (url) => { - const cleanUrl = url.replace(/\s*,?\s*$/, ''); - return `${cleanUrl}`; - }) - .replace(/([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,})/g, - '$1'); - - html += `
  • ${processedLine}
  • `; - continue; - } - - // Обычные параграфы - if (inList && !line.includes('—')) { - html += '
'; - inList = false; - } - - // Обработка жирного текста и ссылок - let processedLine = line - .replace(/\*\*([^*]+)\*\*/g, '$1') - .replace(/https?:\/\/[^\s,)]+/g, (url) => { - const cleanUrl = url.replace(/\s*,?\s*$/, ''); - return `${cleanUrl}`; - }) - .replace(/([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,})/g, - '$1'); - - html += `

${processedLine}

`; - } - - if (inList) { - html += ''; - } - - // Footer - html += '
'; - html += '

© 2025 BROJS.RU. Все права защищены.

'; - html += '
'; - - return html; -}; - -const prerender = () => { - try { - console.log('🚀 Начинаем мульти-страничный пре-рендеринг...'); - - const distPath = path.resolve(__dirname, '../dist'); - const indexPath = path.join(distPath, 'index.html'); - - // Читаем основной HTML - let indexHtml = fs.readFileSync(indexPath, 'utf-8'); - - // 1. Обрабатываем главную страницу - const searchString = '
'; - if (indexHtml.includes(searchString)) { - indexHtml = indexHtml.replace(searchString, `
${homeContent}
`); - fs.writeFileSync(indexPath, indexHtml, 'utf-8'); - console.log('✅ index.html обновлен'); - } - - // 2. Генерируем полный контент для terms.html из terma.md - console.log('📝 Генерируем полный HTML из terma.md...'); - const termsContent = generateTermsContent(); - - // 3. Создаем terms.html на основе index.html - let termsHtml = indexHtml - .replace(homeContent, termsContent) - .replace('bro-js admin', 'Пользовательское соглашение - BROJS.RU') - .replace( - '', - '' - ); - - const termsPath = path.join(distPath, 'terms.html'); - fs.writeFileSync(termsPath, termsHtml, 'utf-8'); - console.log('✅ terms.html создан с полным контентом'); - - console.log('🎉 Пре-рендеринг завершен успешно!'); - console.log('📄 Созданы файлы: index.html, terms.html'); - console.log('💡 terms.html содержит полный текст соглашения для SEO'); - } catch (error) { - console.error('❌ Ошибка при пре-рендеринге:', error); - process.exit(1); - } -}; - -prerender(); diff --git a/scripts/ssr-prerender.js b/scripts/ssr-prerender.js deleted file mode 100644 index da39a88..0000000 --- a/scripts/ssr-prerender.js +++ /dev/null @@ -1,79 +0,0 @@ -/* eslint-disable @typescript-eslint/no-require-imports */ -/* eslint-disable no-undef */ -/* eslint-disable @typescript-eslint/no-unused-vars */ -const fs = require('fs'); -const path = require('path'); -const { JSDOM } = require('jsdom'); - -// Настройка окружения для SSR -const setupDOM = () => { - const dom = new JSDOM('', { - url: 'http://localhost', - pretendToBeVisual: true, - resources: 'usable' - }); - - global.window = dom.window; - global.document = dom.window.document; - global.navigator = dom.window.navigator; - global.HTMLElement = dom.window.HTMLElement; - global.HTMLDivElement = dom.window.HTMLDivElement; - global.requestAnimationFrame = (callback) => setTimeout(callback, 0); - global.cancelAnimationFrame = clearTimeout; -}; - -const cleanupRender = () => { - delete global.window; - delete global.document; - delete global.navigator; - delete global.HTMLElement; - delete global.HTMLDivElement; - delete global.requestAnimationFrame; - delete global.cancelAnimationFrame; -}; - -const prerender = async () => { - try { - console.log('🚀 Начинаем SSR пре-рендеринг...'); - - setupDOM(); - - // Читаем HTML шаблон - const indexPath = path.resolve(__dirname, '../dist/index.html'); - let html = fs.readFileSync(indexPath, 'utf-8'); - - // Рендерим статический контент страницы "в разработке" - const prerenderContent = ` -
-
-
⚙️
-
-

Сайт в разработке

-

Страница загружается...

-
- `.trim(); - - // Вставляем пре-рендеренный контент в div#app - const searchString = '
'; - if (html.includes(searchString)) { - html = html.replace(searchString, `
${prerenderContent}
`); - - // Сохраняем результат - fs.writeFileSync(indexPath, html, 'utf-8'); - console.log('✅ SSR пре-рендеринг завершен успешно!'); - console.log('📄 HTML обновлен с серверным контентом'); - } else { - console.log('⚠️ Не найден
'); - console.log('Возможно, HTML уже содержит пре-рендеренный контент'); - } - - cleanupRender(); - } catch (error) { - console.error('❌ Ошибка при SSR пре-рендеринге:', error); - cleanupRender(); - process.exit(1); - } -}; - -prerender(); - diff --git a/scripts/ssr-render.js b/scripts/ssr-render.js new file mode 100644 index 0000000..267bd5a --- /dev/null +++ b/scripts/ssr-render.js @@ -0,0 +1,93 @@ +/* eslint-disable @typescript-eslint/no-require-imports */ +/* eslint-disable no-undef */ + +// Настройка Babel для транспиляции TSX/JSX в Node.js +require('@babel/register')({ + extensions: ['.ts', '.tsx', '.js', '.jsx'], + presets: [ + '@babel/preset-env', + '@babel/preset-react', + '@babel/preset-typescript' + ], + ignore: [/node_modules/], + cache: false +}); + +const fs = require('fs'); +const path = require('path'); +const React = require('react'); +const { renderToString } = require('react-dom/server'); +const { JSDOM } = require('jsdom'); +const { createCanvas } = require('canvas'); + +// Настройка полноценного DOM окружения через jsdom +const dom = new JSDOM('', { + url: 'http://localhost', + pretendToBeVisual: true, + resources: 'usable' +}); + +const canvas = createCanvas(200, 200); + +// Расширяем jsdom canvas поддержкой +dom.window.HTMLCanvasElement.prototype.getContext = function() { + return createCanvas(200, 200).getContext('2d'); +}; + +global.window = dom.window; +global.document = dom.window.document; +global.navigator = dom.window.navigator; +global.HTMLElement = dom.window.HTMLElement; +global.SVGElement = dom.window.SVGElement; + +console.log('🚀 Запуск SSR с рендерингом React компонентов...'); + +try { + // Импортируем компоненты + const { UnderConstruction } = require('../src/pages/under-construction/underConstruction.tsx'); + const { Terms } = require('../src/pages/terms/Terms.tsx'); + + console.log('✅ Компоненты загружены'); + + // Рендерим компоненты в HTML + const homeContent = renderToString(React.createElement(UnderConstruction)); + const termsContent = renderToString(React.createElement(Terms)); + + console.log('✅ Компоненты отрендерены'); + + // Читаем dist/index.html + const distPath = path.resolve(__dirname, '../dist'); + const indexPath = path.join(distPath, 'index.html'); + let indexHtml = fs.readFileSync(indexPath, 'utf-8'); + + // 1. Главная страница + const searchString = '
'; + if (indexHtml.includes(searchString)) { + indexHtml = indexHtml.replace(searchString, `
${homeContent}
`); + fs.writeFileSync(indexPath, indexHtml, 'utf-8'); + console.log('✅ index.html обновлен с SSR контентом'); + } + + // 2. Страница terms + let termsHtml = indexHtml + .replace(homeContent, termsContent) + .replace('bro-js admin', 'Пользовательское соглашение - BROJS.RU') + .replace( + '', + '' + ); + + const termsPath = path.join(distPath, 'terms.html'); + fs.writeFileSync(termsPath, termsHtml, 'utf-8'); + console.log('✅ terms.html создан с SSR контентом'); + + console.log('🎉 SSR завершен успешно!'); + console.log('📄 Созданы: index.html, terms.html'); + console.log('💡 Весь контент отрендерен через React SSR'); + +} catch (error) { + console.error('❌ Ошибка при SSR:', error.message); + console.error(error.stack); + process.exit(1); +} +