Использование Fastify и Preact для быстрого прототипирования веб-приложений +20


Автор материала, перевод которого мы сегодня публикуем, хочет поделиться рассказом о том, какими технологиями он пользуется для быстрой разработки прототипов веб-приложений. В число этих технологий входят библиотеки Fastify и Preact. Он, кроме того, пользуется библиотекой htm. Она легко интегрируется с Preact и используется для описания элементов DOM с использованием понятных конструкций, напоминающих JSX. При этом для работы с ней не нужен транспилятор вроде Babel. Продемонстрировав инструментарий разработки прототипов и методику работы с ним, автор материала покажет как упаковывать такие приложения в контейнеры Docker. Это позволяет легко демонстрировать приложения всем, кому они интересны.



Начало


Я вышел на использование вышеупомянутого набора технологий несколько недель назад, когда мне нужно было создать очень простой прототип веб-приложения, предназначенный для того, чтобы проверить, совместно с коллегами, некоторые предположения.

Мой эксперимент оказался крайне удачным. Я смог создать прототип очень быстро, коллеги получили возможность удобно экспериментировать с ним, они смогли оперативно высказать свои впечатления о нём. При этом испытать проект они могли даже в том случае, если на их компьютерах не были установлены Node.js и NPM.

Всё это привело меня к мысли о том, что мне стоит написать материал о моём подходе к быстрому прототипированию веб-приложений. Вполне возможно, что этот подход пригодится кому-нибудь ещё. Для тех, кто уже знаком с Fastify и Preact, я сразу же обрисую самое важное, то, что позволит им тут же использовать мои идеи на практике.

Основные идеи


Если вы уже знакомы с Fastify и Preact и хотите узнать о том, как организовать разработку проектов на основе этих технологий, то вы находитесь буквально в паре шагов от желаемого. А именно, речь идёт о выполнении следующих команд:

git clone https://github.com/lmammino/fastify-preact-htm-boilerplate.git my-new-project
cd my-new-project
rm -rf .git
npm install

Конечно, вы можете поменять название проекта, my-new-project, на название вашего проекта.

После установки всего необходимого можно приступать к работе над проектом. А именно, речь идёт о следующем:

  • В папке src/ui собраны файлы клиентской части приложения (тут используются Preact и htm).
  • В папке src/server собраны файлы, относящиеся к серверной части приложения (тут используется Fastify).

Отредактировав соответствующие файлы, вы можете запустить проект:

npm start

После этого испытать его можно, перейдя в браузере по адресу localhost:3000.

И ещё кое-что. Если вам моя разработка понравилась — буду безмерно благодарен за звёздочку на GitHub.

Теперь давайте рассмотрим используемые здесь технологии и особенности работы с ними.

Fastify


Fastify — это быстрый и экономичный веб-фреймворк для Node.js. Этот проект изначально создали два программиста. Теперь же команда тех, кто над ним трудится, насчитывает 10 человек, более 130 человек помогают в разработке проекта, он собрал почти 10000 звёзд на GitHub.

На Fastify оказали влияние Node.js-фреймворки, вроде Express и Hapi, которые существуют уже довольно давно. Он изначально был нацелен на производительность, на удобство работы программистов и на расширение его возможностей с помощью плагинов. Это, кстати, одна из моих любимых особенностей Fastify.

Если вы не знакомы с фреймворком Fastify или хотите лучше его узнать, могу порекомендовать его официальную документацию.

Надо отметить, что я имею отношение к Fastify. Я являюсь членом основной команды разработчиков и занимаюсь, в основном, поддержкой сайта проекта и работой над его документацией.

Preact


Preact — это библиотека для разработки пользовательских интерфейсов для веб-проектов, которая была создана одним человеком как компактная и быстрая замена React. Этот проект оказался довольно успешным, им теперь занимается целая команда разработчиков, на GitHub он набрал более 20000 звёзд.

Одной из причин, по которой мне нравится Preact, является то, что у этой библиотеки есть расширяемый слой описания визуальных компонентов приложения. В обычных условиях этой библиотекой можно пользоваться с применением JSX в комбинации с Babel для транспиляции кода, но если вам не хочется устанавливать Babel и настраивать процесс сборки приложения, вы можете использовать Preact, например, совместно с библиотекой htm, которая использует шаблонные литералы и не требует транспиляции при запуске проектов, в которых она применяется, в современных браузерах.

Мы в этом материале будем пользоваться именно библиотекой htm и скоро рассмотрим несколько примеров.

Обзор проекта


Здесь мы рассмотрим весь процесс создания проекта. Нашей целью будет разработка простого веб-приложения, которое выводит сведения о времени на сервере в момент его запуска. Вот, для того, чтобы было понятнее, к чему мы будем стремиться.


Приложение в браузере

Это — одностраничное приложение (Single Page Application, SPA), в котором Preact и htm используются для формирования его клиентской части, а Fastify применяется для создания API, предназначенного для получения серверного времени.

Внимательный читатель мог заметить, что у страницы, показанной на предыдущем рисунке, есть симпатичный значок favicon. Он там, правда, очень маленький, поэтому я собираюсь облегчить задачу тем, кто, ломая глаза, пытается его разглядеть. Вот его увеличенная версия.


Favicon

Настройка серверной части приложения


Начнём работу с создания новой папки:

mkdir server-time
cd server-time

Теперь инициализируем NPM-проект и установим Fastify:

npm init -y
npm i --save fastify@next fastify-static@next fastify-cli

Обратите внимание на то, что я, при описании некоторых пакетов-зависимостей, воспользовался конструкцией @next. Сделано это для того, чтобы в проекте использовалась бы библиотека Fastify 2, которая в настоящий момент находится в состоянии релиз-кандидата, но очень скоро станет основной стабильной версией.

Обратите внимание на то, что создать новый проект, основанный на Fastify, можно и с помощью инструмента командной строки fastify-cli:

npx fastify-cli generate server-time

Во время написания этого материала данная команда создаёт проект, рассчитанный на использование Fastify 1.x, но очень скоро, после релиза Fastify 2, это средство обновится.

Проанализируем установленные пакеты:

  • fastify — это основной компонент фреймворка.
  • fastify-static — это дополнительный плагин, который позволяет удобно обслуживать статические файлы сервером Fastify.
  • fastify-cli — это средство командной строки, которое позволяет создавать проекты, основанные на Fastify.

В настоящий момент мы готовы к тому, чтобы создать API, основанное на Fastify. Поэтому давайте поместим серверный код в файл src/server/server.js:

const path = require('path')

module.exports = async function(fastify, opts) {
  // обслуживает статические ресурсы из папки `src/ui`
  fastify.register(require('fastify-static'), {
    root: path.join(__dirname, '..', 'ui'),
  })

  // Сюда добавляют конечные точки API
  fastify.get('/api/time', async (request, reply) => {
    return { time: new Date().toISOString() }
  })
}

Полагаю, что вышеприведённый код хорошо объясняет сам себя, но тут есть некоторые интересные детали, о которых стоит рассказать. Это будет особенно полезно для тех, у кого нет опыта работы с Fastify.

Первое, на что можно обратить внимание в этом коде, заключается в том, что тут используется ключевое слово async. Fastify поддерживает и разработку в стиле async/await, и более традиционный подход, основанный на коллбэках. Что именно выбрать — зависит от предпочтений конкретного разработчика.

Ещё одна интересная деталь заключается в том, что мы определяем здесь сервер как экспортируемый модуль. Этот модуль (на жаргоне Fastify это называется «плагином») представляет собой функцию, которая принимает в качестве аргументов экземпляр Fastify (fastify) и набор опций (opts). Внутри объявления модуля мы можем использовать экземпляр fastify для регистрации плагинов. Именно это и происходит с плагином fastify-static. Так же мы тут можем описывать конечные точки HTTP с использованием специальных методов, наподобие fastify.get и fastify.post.

Применяемый здесь модульный подход, хотя и выглядит немного непривычно, имеет свои преимущества. Для начала надо отметить то, что он позволяет объединять несколько серверов. Представьте себе, что вы создали сервер, предназначенный для обслуживания блога, и ещё один — для форума. Их можно легко встроить в существующее приложение, прикрепив их к путям наподобие /blog и /forum.

Более того, этот подход позволяет абстрагировать приложения и субприложения от привязок к серверу (речь идёт, например, о привязке сокетов), передавая решение этой задачи либо корневому приложению, либо fastify-cli.

Запустим сервер с использованием инструмента командной строки fastify:

node_modules/.bin/fastify start --log-level info src/server/server.js

Для того чтобы упростить себе жизнь, мы можем добавить эту команду в раздел scripts нашего файла package.json:

{
  "scripts": {
    "start": "fastify start --log-level info src/server/server.js"
  }
}

Прежде чем действительно запускать сервер, нам нужно позаботиться о том, чтобы существовала папка, в которой будут расположены статические ресурсы. В противном случае fastify-static выдаст ошибку. Создадим эту папку:

mkdir src/ui

Теперь мы можем запустить приложение командой npm start и перейти с помощью браузера по адресу localhost:3000/api/time.

Если всё работает правильно, в браузере можно будет увидеть примерно следующее:

{ "time": "2019-02-17T19:32:03.354Z" }

В этот момент вы можете оценить ещё одну приятную возможность Fastify. Она заключается в том, что JSON-сериализация, в том случае, если некий маршрут возвращает объект, применяется автоматически.

Теперь работа над серверным API завершена. Займёмся фронтендом.

Настройка фронтенда


Весь код нашего проекта, имеющий отношение к фронтенду, будет находиться в папке src/ui. Он будет состоять из 5 файлов:

  • app.js — код Preact-приложения.
  • bootstrap.min.css — CSS-код для стилизации приложения (он взят прямо из фреймворка Bootstrap).
  • favicon.ico — favicon-файл. Если вы разрабатываете серьёзное приложение, без хорошего favicon-файла вам не обойтись.
  • index.html — главный HTML-файл нашего одностраничного приложения.
  • preacthtm.js — код библиотек Preact и htm.

Для начала поместим в папку файлы, представляющие собой стили, библиотеки и значок favicon:

curl "https://unpkg.com/htm@2.0.0/preact/standalone.js" > src/ui/preacthtm.js
curl "https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" > src/ui/bootstrap.min.css
curl "https://github.com/lmammino/fastify-preact-htm-boilerplate/blob/master/src/ui/favicon.ico?raw=true" > src/ui/favicon.ico

Теперь создадим файл src/ui/index.html:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta
      name="viewport"
      content="width=device-width, initial-scale=1, shrink-to-fit=no"
    />

    <!-- Bootstrap CSS -->
    <link rel="stylesheet" href="/bootstrap.min.css" />

    <title>My awesome server time</title>
  </head>
  <body>
    <div id="app"></div>

    <!-- JavaScript -->
    <script src="/preacthtm.js"></script>
    <script src="/app.js"></script>
  </body>
</html>

Перед нами вполне обычная HTML-страница, с помощью которой мы загружаем все ресурсы (CSS и JS) и создаём пустой элемент <div> с идентификатором app, в который мы выведем, во время выполнения проекта, наше приложение.

Теперь взглянем на код приложения, который должен находиться в файле src/ui/app.js:

/* глобальный htmPreact */
const { html, Component, render } = htmPreact

class App extends Component {
  componentDidMount() {
    this.setState({ loading: true, time: null })
    fetch('/api/time')
      .then(response => response.json())
      .then(data => this.setState({ loading: false, time: data.time }))
  }
  render(props, state) {
    return html`
      <div class="container mt-5">
        <div class="row justify-content-center">
          <div class="col">
            <h1>Hello from your new App</h1>
            <div>
              ${state.loading &&
                html`
                  <p>Loading time from server...</p>
                `} ${state.time &&
                html`
                  <p>Time from server: <i><font color="#999999">${state.time}</font></i>
</p>
                `}
            </div>
            <hr />
            <div>
              Have fun changing the code from this boilerplate:
              <ul>
                <li>UI code available at <code>/src/ui</code></li>
                <li>Server-side code available at <code>/src/server</code></li>
              </ul>
            </div>
          </div>
        </div>
      </div>
    `
  }
}

render(
  html`
    <${App} />
  `,
  document.getElementById('app')
)

В этом приложении есть лишь один компонент с состоянием, называемый App. Состояние этого компонента включает в себя 2 переменные:

  • loading — логическая переменная, используемая для указания на то, выполняется ли в некий момент времени запрос к серверному API для получения сведений о серверном времени.
  • time — строка, которая содержит последние полученные с сервера сведения о времени.

Если вы знакомы с React, то вы без труда поймёте вышеприведённый код.
С использованием Preact и htm мы можем создавать компоненты, объявляя классы, которые расширяют встроенный класс Component.

В этом классе мы можем описать поведение компонента, используя методы жизненного цикла, наподобие componentDidMount(), а также пользоваться методом, который ведёт себя как обычный метод render() из React.

В нашем случае, как только компонент будет прикреплён к странице (метод componentDidMount()), мы устанавливаем свойство loading состояния и выполняем запрос к API с использованием fetch.
После завершения запроса мы устанавливаем значение свойства состояния time и сбрасываем свойство loading в значение false.

Метод render() вызывается автоматически при каждом изменении состояния компонента или при передаче ему новых свойств. В данном методе мы описываем DOM компонента с использованием htm.

Библиотека htm позволяет описывать узлы DOM, используя тегированные шаблонные литералы со специальным тегом — html. В пределах нашего шаблонного литерала могут присутствовать динамические выражения, наподобие тех, что мы используем для проверки состояния и для принятия решения о том, что вывести на экран в том случае, если приложение выполняет загрузку данных с сервера, и в том случае, если данные уже загружены.

Ещё стоит отметить то, что нам нужно создать экземпляр приложения и вывести его на HTML-страницу. Делается это с помощью функции render() глобального объекта htmPreact.

Теперь работа над фронтенд-приложением завершена. Можете перезапустить сервер, перейти по адресу localhost:3000 и поэкспериментировать с тем, что мы только что создали. Например, можете разработать на базе этого приложения что-то своё. А когда то, что вы построите, покажется вам достаточно интересным для того, чтобы показать это кому-то ещё, вам, вероятно, полезно будет упаковать своё приложение в контейнер Docker.

Контейнеризация приложения


Я полагаю, что лучший способ демонстрировать другим свои новые небольшие проекты заключается в использовании для этой цели возможностей Docker.

Благодаря Docker любой, кто попытается запустить у себя ваше приложение, будет избавлен от размышлений о том, установлена ли у него подходящая версия Node.js и NPM, ему не нужно будет, скачав исходники приложения, заботиться о том, чтобы, введя правильную последовательность команд, установить его зависимости и запустить сервер.

Для того чтобы упаковать приложение в контейнер Docker, нам нужно создать очень простой файл Dockerfile в корневой папке нашего проекта:

FROM node:11-alpine

WORKDIR /app
COPY . /app
RUN npm install --production

EXPOSE 3000

CMD ["npm", "start"]

Здесь мы описываем следующие действия:

  • Образ создаётся на основе образа Node.js 11, построенного на базе Alpine Linux.
  • Всё из текущей папки копируется в папку /app контейнера.
  • После этого мы выполняем команду npm install для загрузки и установки зависимостей. Использование флага --production приводит к тому, что установлены будут только зависимости, необходимые для развёртывания проекта в продакшне. Это ускоряет создание образа в том случае, если в проекте используется много зависимостей разработки.
  • Мы указываем на то, что у контейнера должен быть открыт пор 3000, на котором, по умолчанию, будет работать сервер.
  • В итоге мы описываем команду, npm start, которая будет выполнена во время запуска контейнера. Она запускает приложение.

Для того чтобы собрать образ для контейнера, выполним следующую команду:

docker build -t server-time .

Через несколько секунд образ должен быть готов и у вас должна быть возможность запустить контейнер:

docker run -it -p 3000:3000 server-time

Параметр -p позволяет настроить связь порта контейнера 3000 с локальным портом 3000. Это позволит обращаться к контейнеризированному приложению по адресу localhost:3000.
Теперь вы готовы к тому, чтобы поделиться своим приложением с другими людьми. Для того чтобы запустить его в среде Docker, достаточно, при условии, что на компьютере установлен Docker, выполнить в его папке две вышеприведённых команды.

Итоги


В этом материале мы рассказали о том, как создать среду для быстрой разработки веб-приложений с использованием Fastify и Preact. Кроме того, мы поговорили о том, как поделиться приложением с другими людьми, используя Docker.

Как было сказано выше, предлагаемые инструменты предназначены для быстрого прототипирования, поэтому сейчас вы, возможно, задаётесь вопросом о том, чего здесь не хватает для разработки реальных приложений. Вероятнее всего, вы, говоря о «реальных приложениях», имеете в виду следующие возможности:

  • Компиляция ресурсов интерфейсной части приложения: создание оптимизированных файлов (бандлов), возможно, средствами Webpack, Babel, или с привлечением других средств.
  • Маршрутизация в интерфейсной части приложения.
  • Серверный рендеринг.
  • Средства постоянного хранения данных.

Все эти возможности разработки реальных приложений ещё не добавлены в рассмотренный здесь набор технологий, поэтому я пока воспринимаю его как средство для разработки прототипов. Уверен, что если вам то, что вы увидели, понравилось, и вы рассматриваете всё это как основу будущих приложений, решающих реальные задачи, вы без особых сложностей сможете найти то, что вам нужно, и создавать на базе Fastify и Preact приложения, готовые к выпуску в продакшн.

Уважаемые читатели! Как вы создаёте прототипы веб-приложений?




К сожалению, не доступен сервер mySQL