Files
bro.landing/scripts/ssr-render.js
T
2025-10-24 13:08:02 +03:00

141 lines
5.8 KiB
JavaScript

/* 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 { Window } = require('happy-dom');
const { createCanvas } = require('canvas');
// Читаем index.ejs как основу для SSR
const ejsTemplatePath = path.resolve(__dirname, '../src/index.ejs');
const ejsTemplate = fs.readFileSync(ejsTemplatePath, 'utf-8');
// Настройка полноценного DOM окружения через happy-dom на основе index.ejs
const window = new Window({
url: 'http://localhost',
width: 1024,
height: 768
});
const document = window.document;
document.write(ejsTemplate);
// Расширяем happy-dom canvas поддержкой
window.HTMLCanvasElement.prototype.getContext = function() {
return createCanvas(200, 200).getContext('2d');
};
global.window = window;
global.document = document;
global.navigator = window.navigator;
global.HTMLElement = window.HTMLElement;
global.SVGElement = window.SVGElement;
console.log('🚀 Запуск SSR с рендерингом React компонентов...');
try {
// Импортируем компоненты
const { UnderConstruction } = require('../src/pages/under-construction/underConstruction.tsx');
const { Terms } = require('../src/pages/terms/Terms.tsx');
const { ChakraProvider, extendTheme } = require('@chakra-ui/react');
console.log('✅ Компоненты загружены');
// Функция для рендера с Chakra UI + базовыми стилями
function renderWithStyles(Component) {
// Chakra theme с базовыми стилями
const theme = extendTheme({});
// Генерируем базовые CSS переменные и стили Chakra
const baseStyles = `
<style data-emotion="chakra-global">
:root,:host{--chakra-vh:100vh}@supports(height:100dvh){:root,:host{--chakra-vh:100dvh}}
:root{color-scheme:light;--chakra-colors-chakra-body-text:#1A202C;--chakra-colors-chakra-body-bg:#fff;
--chakra-colors-chakra-border-color:#E2E8F0;--chakra-colors-chakra-placeholder-color:#A0AEC0}
body{background:var(--chakra-colors-chakra-body-bg);color:var(--chakra-colors-chakra-body-text);
font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Helvetica,Arial,sans-serif;line-height:1.5}
.chakra-container{width:100%;max-width:1200px;margin-left:auto;margin-right:auto;padding-left:16px;padding-right:16px}
.chakra-stack>*:not(style)~*:not(style){margin-top:1.5rem;margin-inline-start:0}
.chakra-heading{font-weight:700;font-size:1.875rem;line-height:2.25rem;margin-bottom:0.5rem}
.chakra-text{margin:0;margin-bottom:0.5rem}
.chakra-link{color:#3182ce;text-decoration:none}
.chakra-link:hover{text-decoration:underline}
.chakra-divider{border-color:var(--chakra-colors-chakra-border-color);border-style:solid;
border-width:0;border-bottom-width:1px;margin-top:1rem;margin-bottom:1rem}
ul,ol{margin-left:1.5rem;margin-bottom:1rem}
li{margin-bottom:0.5rem}
hr{border-top-width:1px;border-color:#E2E8F0}
</style>`;
const html = renderToString(
React.createElement(
ChakraProvider,
{ theme, cssVarsRoot: 'body' },
React.createElement(Component)
)
);
return { html, styles: baseStyles };
}
// Рендерим компоненты с извлечением стилей
const { html: homeContent, styles: homeStyles } = renderWithStyles(UnderConstruction);
const { html: termsContent, styles: termsStyles } = renderWithStyles(Terms);
console.log('✅ Компоненты отрендерены с Chakra UI + Emotion стилями');
// Читаем 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 = '<div id="app"></div>';
if (indexHtml.includes(searchString)) {
indexHtml = indexHtml
.replace(searchString, `<div id="app">${homeContent}</div>`)
.replace('</head>', `${homeStyles}</head>`);
fs.writeFileSync(indexPath, indexHtml, 'utf-8');
console.log('✅ index.html обновлен с SSR контентом и стилями');
}
// 2. Страница terms
let termsHtml = indexHtml
.replace(homeContent, termsContent)
.replace(homeStyles, termsStyles)
.replace('<title>bro-js admin</title>', '<title>Пользовательское соглашение - BROJS.RU</title>')
.replace(
'</head>',
'<meta name="description" content="Пользовательское соглашение для платформы обучения фронтенд-разработке BROJS.RU. Условия использования, обработка персональных данных, права и обязанности сторон." /></head>'
);
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');
console.log('🎨 Критические стили Emotion встроены в HTML');
} catch (error) {
console.error('❌ Ошибка при SSR:', error.message);
console.error(error.stack);
process.exit(1);
}