Запропонувати правкуПокращити цю статтюДопрацюйте відповідь до «Як використовувати шаблонні движки в Express.js?». Ваші зміни проходять модерацію перед публікацією.Потрібне підтвердженняКонтентЩо ви змінюєте🇺🇸EN🇺🇦UAПереглядЗаголовок (UA)Коротка відповідь (UA)**Шаблонні движки (template engines)** в Express.js рендерять динамічний HTML на сервері, об'єднуючи файл шаблону з об'єктом даних. Встанови движок, налаштуй через `app.set('view engine', 'ejs')` і викликай `res.render('view', data)` у маршрутах. ```js app.set('view engine', 'ejs'); app.set('views', path.join(__dirname, 'views')); app.get('/', (req, res) => res.render('home', { title: 'Привіт' })); ``` **Ключове:** використовуй для серверного рендерингу HTML; для JSON API або React/Vue не потрібен.Показується над повною відповіддю для швидкого нагадування.Відповідь (UA)Зображення**Шаблонні движки (template engines)** в Express.js генерують HTML на сервері, об'єднуючи файл шаблону з об'єктом даних. Замість `res.send('<h1>' + name + '</h1>')` ти тримаєш розмітку в `.ejs` або `.pug` файлах і викликаєш `res.render('view', data)` з обробника маршруту. ## Теорія ### TL;DR - Шаблон = файл з плейсхолдерами; `res.render('view', data)` заповнює їх і повертає HTML браузеру - Аналогія: як гра Mad Libs, шаблон - це текст з пропусками, а дані - слова для них - EJS найпоширеніший стартовий варіант, бо це просто HTML з тегами `<% %>` - Використовуй шаблонний движок коли надсилаєш HTML; для JSON API або React/Vue він не потрібен - Express не включає жоден движок, тому спочатку `npm install ejs` (або pug, handlebars) ### Швидке налаштування ```bash npm install ejs ``` ```js // app.js const express = require('express'); const path = require('path'); const app = express(); app.set('view engine', 'ejs'); app.set('views', path.join(__dirname, 'views')); // абсолютний шлях уникає помилок пошуку app.get('/', (req, res) => { res.render('home', { title: 'Привіт', users: ['Alice', 'Bob'] }); }); app.listen(3000); ``` ```html <!-- views/home.ejs --> <h1><%= title %></h1> <ul> <% users.forEach(user => { %> <li><%= user %></li> <% }); %> </ul> <!-- Результат: <h1>Привіт</h1><ul><li>Alice</li><li>Bob</li></ul> --> ``` Express завантажує `.ejs` файл, компілює його у функцію Node.js, підставляє дані і надсилає готовий HTML рядок. Скомпільована функція залишається в пам'яті, тому повторні запити не читають диск. ### Популярні движки | Движок | Файл | Стиль | Для чого | |---|---|---|---| | **EJS** | `.ejs` | HTML з тегами `<% %>` | Швидкий старт, HTML-розробники | | **Pug** | `.pug` | Відступи, без закриваючих тегів | Лаконічна розмітка | | **Handlebars** | `.hbs` | Синтаксис `{{ }}` без логіки | Простий вивід даних | | **Nunjucks** | `.njk` | Синтаксис Jinja2 | Складні цикли та фільтри | EJS найпопулярніший серед початківців, бо виглядає як звичайний HTML. Pug скорочує шаблонний код. Handlebars тримає шаблони чистими, не дозволяючи писати логіку всередині них. Жоден не є об'єктивно кращим. Вибирай виходячи з того, що вже знає твоя команда. ### Теги EJS | Тег | Що робить | |---|---| | `<%= value %>` | Виводить значення з HTML-екрануванням | | `<%- value %>` | Виводить сирий HTML без екранування | | `<% code %>` | Виконує JavaScript, без виводу | | `<%- include('partial') %>` | Вставляє інший файл шаблону | Різниця між `<%= %>` і `<%- %>` важлива для безпеки. Дані від користувача завжди йдуть через `<%= %>`, бо він екранує `<`, `>` і `&`. Тег `<%- %>` рендерить сирий HTML, що відкриває XSS якщо туди потрапляють недовірені дані. ### Коли використовувати - Блог або дашборд з даними з бази: шаблонний движок підходить добре - API що повертає JSON: пропускай, використовуй `res.json()` - SPA з React або Vue на фронтенді: не потрібен, клієнт сам рендерить - Email або HTML звіт на вимогу: шаблонний движок також підходить - Швидкий прототип де треба одразу бачити HTML: EJS, нульова крива навчання ### Партшали (partials) і глобальні змінні EJS підтримує партшали через `<%- include('header') %>`. Це дозволяє повторно використовувати навігацію, футери і каркас сторінки без копіювання HTML між файлами. Для даних потрібних у кожному шаблоні, наприклад назва застосунку або поточний рік, використовуй `app.locals`: ```js app.locals.siteName = 'MyApp'; app.locals.year = new Date().getFullYear(); // Тепер <%= siteName %> працює в кожному шаблоні без явної передачі ``` Для даних конкретного запиту, наприклад авторизованого користувача, використовуй `res.locals` у middleware: ```js app.use((req, res, next) => { res.locals.currentUser = req.user || null; next(); }); ``` Більшість адмін-панелей що я бачив на Express використовують саме цей патерн: `app.locals` для статичних налаштувань, `res.locals` для контексту авторизації, і дані per-render тільки для того що змінюється між маршрутами. ### Як Express обробляє рендеринг `res.render('dashboard', data)` робить ось що: завантажує `views/dashboard.ejs` з диска при першому запиті, компілює шаблон у JavaScript функцію, кешує цю функцію в пам'яті, викликає її з об'єднаними locals (твої дані плюс `app.locals` плюс `res.locals`) і надсилає отриманий рядок. В `NODE_ENV=production` кеш завжди увімкнений. В режимі розробки Express перекомпільовує при кожному запиті, тому зміни видно без перезапуску. ### Типові помилки **Не встановлено пакет движка.** `app.set('view engine', 'ejs')` лише говорить Express що шукати. Якщо пропустити `npm install ejs`, отримаєш `Cannot find module 'ejs'` під час виконання. Express нічого не включає автоматично. **Відносний шлях для views.** ```js // Ламається коли процес запускається з іншої директорії app.set('views', './views'); // Завжди використовуй так app.set('views', path.join(__dirname, 'views')); ``` **Використання `<%- %>` для даних від користувача.** ```html <!-- XSS: рендерить <script>alert(1)</script> як справжній HTML --> <li><%- user.name %></li> <!-- Безпечно: виводить <script>alert(1)</script> як текст --> <li><%= user.name %></li> ``` **Забув передати дані.** `res.render('users')` без другого аргументу означає що шаблон не отримає змінних. Звернення до `<%= users.length %>` кине `Cannot read properties of undefined`. **Змішування `res.render()` і `res.json()` в одному маршруті.** В одному запиті викликається або один, або інший. Виклик обох призводить до `Cannot set headers after they are sent`. ### Де використовується - Ghost (блог-платформа) використовує серверні шаблони для сторінок постів - Handlebars лежить в основі тем Shopify для рендерингу даних товарів - Документація Mozilla використовує Nunjucks для складної логіки фільтрів, аналогічно Python Flask - Express + EJS типовий стек для адмін-дашбордів і проектів у буткемпах де важливий SEO та швидке перше завантаження ### Питання для поглиблення **Q:** Як працює кешування в продакшені? **A:** В `NODE_ENV=production` Express кешує скомпільовані функції шаблонів після першого рендеру. В режимі розробки перекомпільовує щоразу, тому зміни видно одразу. Налаштовувати це вручну не потрібно. **Q:** Яка різниця між `<% %>`, `<%= %>` і `<%- %>` в EJS? **A:** `<% %>` виконує JavaScript без виводу. `<%= %>` виводить значення з HTML-екрануванням. `<%- %>` виводить сирий HTML без екранування. Для будь-яких даних від користувача або бази завжди використовуй `<%= %>`. **Q:** Чим `res.render` відрізняється від `res.send`? **A:** `res.render` компілює файл шаблону з даними і надсилає результат. `res.send` надсилає безпосередньо той рядок або буфер що ти передаєш. Файли шаблонів не задіяні. **Q:** Чи можуть два маршрути рендерити один шаблон без витоку даних між запитами? **A:** Так. Express кешує скомпільовану функцію, але кожен виклик виконує її зі свіжими locals. Дані одного запиту не потрапляють в інший. Але те що потрапляє в `app.locals` є глобальним, тому туди мають йти лише дійсно статичні значення. **Q:** Pug чи EJS для продуктивності під великим навантаженням? **A:** Після першої компіляції обидва стають JavaScript функціями і показують приблизно однакову швидкість. Початковий парсинг Pug трохи повільніший, але ця різниця зникає після кешування. Не варто обирати движок виходячи з цього критерію. ## Приклади ### Базова EJS сторінка зі списком користувачів ```js // app.js const express = require('express'); const path = require('path'); const app = express(); app.set('view engine', 'ejs'); app.set('views', path.join(__dirname, 'views')); app.get('/users', (req, res) => { const users = [ { id: 1, name: 'Alice' }, { id: 2, name: 'Bob' }, ]; res.render('users', { title: 'Список користувачів', users }); }); app.listen(3000); ``` ```html <!-- views/users.ejs --> <h1><%= title %></h1> <ul> <% users.forEach(user => { %> <li><a href="/users/<%= user.id %>"><%= user.name %></a></li> <% }); %> </ul> <% if (users.length === 0) { %> <p>Користувачів не знайдено.</p> <% } %> ``` Дані з маршруту потрапляють у шаблон. `forEach` рендерить по одному `<li>` на кожного користувача. Перевірка порожнього масиву тримає сторінку корисною. ### Дашборд з партшалами та auth middleware ```js // app.js app.locals.siteName = 'MyApp'; // доступно в кожному шаблоні app.use((req, res, next) => { res.locals.currentUser = req.user || null; next(); }); app.get('/dashboard', async (req, res) => { const users = await db.query('SELECT * FROM users LIMIT 10'); res.render('dashboard', { recentUsers: users.rows, stats: { total: users.rows.length }, }); }); ``` ```html <!-- views/partials/header.ejs --> <header> <span><%= siteName %></span> <% if (currentUser) { %> <span>Привіт, <%= currentUser.name %></span> <% } %> </header> <!-- views/dashboard.ejs --> <%- include('partials/header') %> <p>Всього користувачів: <%= stats.total %></p> <table> <% recentUsers.forEach(u => { %> <tr> <td><%= u.name %></td> <td><%= u.email %></td> </tr> <% }); %> </table> ``` `res.locals.currentUser` встановлений у middleware доступний у партшалі без явної передачі. `app.locals.siteName` встановлений один раз при старті з'являється в кожному шаблоні. Саме так реальні Express застосунки уникають повторюваної передачі одних і тих самих даних по всіх маршрутах. ### Безпечний і небезпечний вивід ```js // Маршрут що отримує контент від користувача app.get('/comment', (req, res) => { const comment = { author: 'Bob', text: '<script>alert("XSS")</script> Гарна стаття!', }; res.render('comment', { comment }); }); ``` ```html <!-- views/comment.ejs --> <strong><%= comment.author %></strong> <!-- Безпечний вивід --> <p><%= comment.text %></p> <!-- Браузер показує: <script>alert("XSS")</script> Гарна стаття! --> <!-- Кутові дужки екрановані, скрипт не виконується --> <!-- Небезпечний вивід --> <p><%- comment.text %></p> <!-- Браузер виконає тег script --> ``` `<%= %>` - вибір за замовчуванням для кожного значення що приходить від користувача, форми або бази даних. `<%- %>` тільки для довіреного контенту, наприклад HTML що ти сам формуєш на сервері.Для рев’юераПримітка для модератора (необов’язково)Бачить лише модератор. Прискорює рев’ю.