Вступление
В этой статье мы рассмотрим, как использовать шаблонизатор Handlebars с Node.js и Express. Мы расскажем, что такое механизмы шаблонов и как можно использовать Handlebars для создания веб-приложений с рендерингом на стороне сервера (SSR).
Мы также обсудим, как настроить Handlebars с фреймворком Express.js и как использовать встроенные помощники для создания динамических страниц. Наконец, мы рассмотрим, как при необходимости разработать собственный помощник.
Что такое шаблонизатор?
Еще в 90-х годах, когда Интернет был представлен миру, он в основном использовался для научных целей, таких как публикация исследовательских работ и как канал связи между университетами и учеными. Большинство веб-страниц тогда были статичными. Статическая веб-страница одинакова для каждого пользователя и не меняется для каждого пользователя. Если что-то и нужно изменить на странице, это нужно сделать вручную.
В современном мире все гораздо более интерактивно и адаптировано под каждого пользователя. Сегодня почти у всех есть доступ к Интернету. Большинство веб-приложений сегодня динамические. Например, на Facebook вы и я увидим очень разные новостные ленты при входе в систему. Для каждого человека страница будет следовать одному и тому же шаблону (то есть последовательным сообщениям с именами пользователей над ними), но контент будет отличаться.
Это работа механизма шаблонов - определяется шаблон для новостной ленты, а затем на основе текущего пользователя и запроса к базе данных шаблон заполняется полученным контентом.
Мы можем использовать механизмы шаблонов как в серверной части, так и во внешнем интерфейсе. Если мы используем механизм шаблонов в бэкэнде для генерации HTML, мы называем это рендерингом на стороне сервера (SSR).
Рули
Handlebars популярен как для внутреннего, так и для внешнего шаблонов. Например, популярный интерфейсный фреймворк Ember использует Handlebars в качестве механизма создания шаблонов.
Handlebars - это расширение языка шаблонов Mustache , которое в основном ориентировано на простоту и минимальное создание шаблонов.
Использование Handlebars с Node.js
Для начала создайте пустую папку, откройте командную строку внутри этой
папки и затем запустите npm init -y
чтобы создать пустой проект
Node.js с настройками по умолчанию.
Перед началом нам нужно установить необходимые библиотеки Node.js. Вы можете установить модули Express и Express-Handlebars , запустив:
$ npm install --save express express-handlebars
Примечание . При использовании Handlebars на стороне сервера вы,
скорее всего, будете использовать вспомогательный модуль, такой как
express-handlebars
который интегрирует Handlebars с вашей
веб-платформой. В этой статье мы сосредоточимся в основном на синтаксисе
шаблонов, поэтому мы используем express-handlebars
, но в случае, если
вы сами обрабатываете компиляцию и рендеринг шаблона, вы захотите
проверить справку по API
компиляции
также.
Затем давайте воссоздадим структуру каталогов Handlebars по умолчанию.
views
содержит все шаблоны Handlebars:
.
├── app.js
└── views
├── home.hbs
└── layouts
└── main.hbs
В layouts
папки внутри views
папки будет содержать макеты или
обертку шаблона. Эти макеты будут содержать структуру HTML, таблицы
стилей и сценарии, которые используются в шаблонах.
main.hbs
- это основной макет. home.hbs
- это пример шаблона
Handlebars, который мы собираемся использовать.
По мере продолжения мы будем добавлять больше шаблонов и папок.
В нашем примере мы будем использовать один скрипт, чтобы упростить
задачу. Импортируем необходимые библиотеки в наш файл app.js
const express = require('express');
const exphbs = require('express-handlebars');
Затем давайте создадим приложение Express:
const app = express();
Теперь мы можем настроить express-handlebars
качестве нашего механизма
просмотра:
app.engine('hbs', exphbs({
defaultLayout: 'main',
extname: '.hbs'
}));
app.set('view engine', 'hbs');
По умолчанию расширение для шаблонов Handlebars - .handlebars
. Но в
настройках мы изменили его на .hbs
помощью extname
потому что он
короче.
Давайте включим скрипты и стили Bootstrap в
макет main.hbs
<html lang="en">
<head>
<!-- <meta> tags> -->
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css">
<title>Book Face</title>
</head>
<body>
<div class="container">
{{{body}}}
</div>
<script src="https://code.jquery.com/jquery-3.4.1.slim.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/ [email protected] /dist/umd/popper.min.js"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/js/bootstrap.min.js"></script>
</body>
</html>
А теперь давайте изменим наш home.hbs
включив в него сообщение:
<h1>Hello World from Handlebars</h1>
Чтобы получить доступ к этой странице, нам нужно настроить обработчик запросов. Установим его на корневой путь:
app.get('/', (req, res) => {
res.render('home');
});
Наконец, нам просто нужно начать прослушивать порт на предмет запросов:
app.listen(3000, () => {
console.log('The web server has started on port 3000');
});
Мы можем запустить приложение с помощью node app.js
в консоли, хотя мы
также можем использовать такой инструмент, как
nodemon . С помощью nodemon нам не нужно
перезапускать сервер каждый раз, когда мы вносим изменения - когда мы
меняем код, nodemon обновляет сервер.
Установим:
$ npm i -g nodemon
И запуск приложения с помощью nodemon выполняется с помощью:
$ nodemon app.js
Зайдем в наше приложение через браузер:
{.ezlazyload}
Когда все готово, давайте рассмотрим некоторые особенности Handlebars.
Особенности языка руля
Чтобы продемонстрировать некоторые функции Handlebars, мы создадим ленту в социальных сетях. Фид будет извлекать данные из простого массива, имитируя базу данных.
В ленте будут публикации с изображениями и комментариями. Если к изображению нет комментариев - появится сообщение «Прокомментируйте этот пост первым».
Давайте обновим наш home.hbs
чтобы начать:
<nav class="navbar navbar-dark bg-dark">
<a class="navbar-brand" href="#">Book Face</a>
</nav>
<div class="posts">
<div class="row justify-content-center">
<div class="col-lg-7" style="margin-top: 50px;">
<div class="card">
<img src="https://picsum.photos/500/500"
class="card-img-top" alt="...">
<div class="card-body">
<h5 class="card-title">Posted by Janith Kasun</h5>
<ul class="list-group">
<li class="list-group-item">This is supposed to be a comment</li>
<li class="list-group-item">This is supposed to be a comment</li>
</ul>
</div>
</div>
</div>
</div>
</div>
Как вы можете видеть в этом шаблоне Handlebars, мы добавили navbar
и
card
с некоторыми жестко заданными значениями-заполнителями.
Наша страница теперь выглядит так:
{.ezlazyload}
Передача параметров в шаблоны
Теперь давайте удалим эти жестко запрограммированные значения с самой страницы и передадим их из сценария на страницу. Позже они будут заменены значениями комментариев в массиве:
app.get('/', function (req, res) {
res.render('home', {
post: {
author: 'Janith Kasun',
image: 'https://picsum.photos/500/500',
comments: []
}
});
});
post
содержит такие поля, как author
, image
и comments
. Мы
можем ссылаться на post
в нашем шаблоне Handlebars {{post}}
:
<div class="posts">
<div class="row justify-content-center">
<div class="col-lg-7" style="margin-top: 50px;">
<div class="card">
<img src="{{post.image}}"
class="card-img-top" alt="...">
<div class="card-body">
<h5 class="card-title">Posted by {{post.author}}</h5>
<ul class="list-group">
<li class="list-group-item">This is suppose to be a comment</li>
<li class="list-group-item">This is suppose to be a comment</li>
</ul>
</div>
</div>
</div>
</div>
</div>
Ссылаясь на эти значения с помощью обработчика, который отображает страницу, они вставляются на стороне сервера, и пользователю предоставляется, казалось бы, статический HTML-код с уже существующими значениями.
Условия использования
Поскольку у нас есть условная логика, то есть показывать комментарии, если они есть, и сообщение, если их нет, давайте посмотрим, как мы можем использовать условные выражения в шаблонах Handlebars:
<div class="posts">
<div class="row justify-content-center">
<div class="col-lg-7" style="margin-top: 50px;">
<div class="card">
<img src="{{post.image}}" class="card-img-top" alt="...">
<div class="card-body">
<h5 class="card-title">Posted by {{post.author}}</h5>
{{#if post.comments}}
<ul class="list-group">
<!-- Display comment logic -->
</ul>
{{else}}
<ul class="list-group">
<li class="list-group-item">Be first to comment on this post!</li>
</ul>
{{/if}}
</div>
</div>
</div>
</div>
</div>
Теперь вы должны видеть только раздел «Прокомментируйте этот пост первым», отображаемый на вашей странице, поскольку массив комментариев пуст:
{.ezlazyload}
#if
- это встроенный помощник в Handlebars. Если оператор if
возвращает true
, блок внутри #if
будет отрисован. Если false
,
undefined
, null
, ""
, 0
или []
, блок не будет отображаться.
Наш массив пуст ( []
), поэтому блок не отображается.
#if
принимает только одно условие, и вы не можете использовать
синтаксис сравнения JavaScript ( ===
). Если вам нужно использовать
несколько условий или дополнительный синтаксис, вы можете создать
переменную в коде и передать ее в шаблон. Кроме того, вы можете
определить своего собственного помощника, что мы и сделаем в последнем
разделе.
Использование петель
Поскольку сообщение может содержать несколько комментариев, нам понадобится цикл, чтобы просмотреть их все и отобразить. Давайте сначала заполним наш массив некоторыми комментариями:
app.get('/', function (req, res) {
res.render('home', {
post: {
author: 'Janith Kasun',
image: 'https://picsum.photos/500/500',
comments: [
'This is the first comment',
'This is the second comment',
'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum nec fermentum ligula. Sed vitae erat lectus.'
]
}
});
});
А теперь в нашем шаблоне мы будем использовать #each
чтобы просмотреть
их все:
<div class="posts">
<div class="row justify-content-center">
<div class="col-lg-7" style="margin-top: 50px;">
<div class="card">
<img src="{{post.image}}" class="card-img-top" alt="...">
<div class="card-body">
<h5 class="card-title">Posted by {{post.author}}</h5>
{{#if post.comments}}
<ul class="list-group">
{{#each post.comments}}
<li class="list-group-item">{{this}}</li>
{{/each}}
</ul>
{{else}}
<ul class="list-group">
<li class="list-group-item">Be first to comment on this post</li>
</ul>
{{/if}}
</div>
</div>
</div>
</div>
</div>
Внутри цикла #each
вы можете использовать this
для ссылки на
элемент, находящийся в текущей итерации. В нашем случае это относится к
строке, которая затем отображается:
{.ezlazyload}
Если у вас есть массив объектов, вы также можете получить доступ к
любому атрибуту этого объекта. Например, если есть массив людей, вы
можете просто использовать this.name
для доступа к полю name
Теперь давайте изменим параметры нашего шаблона, чтобы он содержал несколько сообщений:
app.get('/', function (req, res) {
res.render('home', {
posts: [
{
author: 'Janith Kasun',
image: 'https://picsum.photos/500/500',
comments: [
'This is the first comment',
'This is the second comment',
'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum nec fermentum ligula. Sed vitae erat lectus.'
]
},
{
author: 'John Doe',
image: 'https://picsum.photos/500/500?2',
comments: [
]
}
]
});
});
Теперь мы также можем использовать #each
для перебора сообщений:
<div class="posts">
<div class="row justify-content-center">
{{#each posts}}
<div class="col-lg-7" style="margin-top: 50px;">
<div class="card">
<img src="{{this.image}}" class="card-img-top" alt="...">
<div class="card-body">
<h5 class="card-title">Posted by {{this.author}}</h5>
{{#if this.comments}}
<ul class="list-group">
{{#each this.comments}}
<li class="list-group-item">{{this}}</li>
{{/each}}
</ul>
{{else}}
<ul class="list-group">
<li class="list-group-item">Be first to comment on this post</li>
</ul>
{{/if}}
</div>
</div>
</div>
{{/each}}
</div>
</div>
Использование частичного
Практически все веб-страницы содержат разные разделы. На базовом уровне это разделы Header , Body и Footer. Поскольку верхний и нижний колонтитулы обычно используются на многих страницах, их наличие на всех веб-страницах скоро станет чрезвычайно раздражающим и просто излишним.
К счастью, мы можем использовать Handlebars для разделения этих разделов в шаблонах и просто включать эти шаблоны как «частичные» в сами страницы.
В нашем случае, поскольку у нас нет header.hbs
posts.hbs
файлы
header.hbs и posts.hbs в каталоге partials
.
├── app.js
└── views
├── home.hbs
├── layouts
| └── main.hbs
└── paritials
└── header.hbs
└── posts.hbs
Затем мы переместим код заголовка в файл header.hbs
<nav class="navbar navbar-dark bg-dark">
<a class="navbar-brand" href="#">Book Face</a>
</nav>
И код подачи в файл posts.hbs
<div class="posts">
<div class="row justify-content-center">
{{#each posts}}
<div class="col-lg-7" style="margin-top: 50px;">
<div class="card">
<img src="{{this.image}}" class="card-img-top" alt="...">
<div class="card-body">
<h5 class="card-title">Posted by {{this.author}}</h5>
{{#if this.comments}}
<ul class="list-group">
{{#each this.comments}}
<li class="list-group-item">{{this}}</li>
{{/each}}
</ul>
{{else}}
<ul class="list-group">
<li class="list-group-item">Be first to comment on this post</li>
</ul>
{{/if}}
</div>
</div>
</div>
{{/each}}
</div>
</div>
А теперь мы можем включить их в файл home.hbs
{{>header}}
{{>posts posts=posts}}
Пользователь не заметит разницы, но home.hbs
намного чище. Это
становится очень полезным, когда у вас сложные веб-страницы.
Здесь мы просто включили header.hbs
файл и приняли posts
параметра в
posts
области posts.hbs
файла.
Это то, что он передает posts
из нашего обработчика в параметр posts
posts.hbs
страницы posts.hbs.
Создание специального помощника
Как вы можете видеть на странице, у нас есть один комментарий, занимающий две строки. Давайте создадим собственный помощник, чтобы резюмировать этот текст.
Для этого в конфигурации Handlebars мы можем определить наши вспомогательные функции. В нашем случае мы обрежем комментарии до 64 символов:
app.engine('hbs', exphbs({
defaultLayout: 'main',
extname: '.hbs',
helpers: {
getShortComment(comment) {
if (comment.length < 64) {
return comment;
}
return comment.substring(0, 61) + '...';
}
}
}));
Теперь давайте воспользуемся этим помощником в нашем posts.hbs
для
обобщения комментариев:
<div class="posts">
<div class="row justify-content-center">
{{#each posts}}
<div class="col-lg-7" style="margin-top: 50px;">
<div class="card">
<img src="{{this.image}}" class="card-img-top" alt="...">
<div class="card-body">
<h5 class="card-title">Posted by {{this.author}}</h5>
{{#if this.comments}}
<ul class="list-group">
{{#each this.comments}}
<li class="list-group-item">{{getShortComment this}}</li>
{{/each}}
</ul>
{{else}}
<ul class="list-group">
<li class="list-group-item">Be first to comment on this post</li>
</ul>
{{/if}}
</div>
</div>
</div>
{{/each}}
</div>
</div>
Конечно же, комментарии на нашей странице теперь обрезаны:
{.ezlazyload}
Заключение
В этой статье мы рассмотрели основы Handlebars - движка шаблонов для Node.js и интерфейсного JavaScript. Используя Handlebars, мы можем создавать динамические веб-страницы, которые отображаются на стороне сервера или клиента. Используя условные выражения, циклы, частичные и настраиваемые вспомогательные функции Handlebars, наши веб-страницы становятся чем-то большим, чем просто статический HTML.
Код также, как обычно, доступен на GitHub. Вы также можете найти дополнительную информацию о Handlebars на их официальной веб-странице .