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

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

Пока мы делаем это, наш пользователь смотрит на пустой белый экран, гадая, вернется ли когда-нибудь страница, которую он хочет увидеть. И когда это наконец произойдет, что мы отправим в первую очередь?

<!doctype html>

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

Оказывается, есть довольно много вещей, которые мы могли бы отправить сразу, даже до того, как база данных ответила. Мы могли бы отправить заголовок страницы и навигацию, метатеги, теги ссылок, теги скриптов, логотипы. Браузер мог получать внешние ресурсы, такие как изображения и JavaScript, в то же время, когда работала наша база данных.

Мы могли бы поработать, чтобы быстро что-нибудь представить нашим пользователям, чтобы они знали, что страница, которую они запросили, загружается. И даже если общее время загрузки страницы будет точно таким же, тот факт, что контент начал появляться раньше, улучшит восприятие производительности для этого пользователя.

У нас есть возможность передавать такие данные в потоковом режиме в течение длительного времени (я смотрю на вас, промывка), но это никогда не было особенно простым делом. А по мере того, как Интернет повзрослел, и разработчики начали использовать фреймворки и библиотеки шаблонов, становилось все труднее.

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

Talisman: шаблоны стриминга для Node.js

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

Talisman предназначен для отправки пользователю как можно большего количества контента и как можно быстрее. Это достигается за счет возврата потока Readable, который разработчик может передать напрямую в объект http.ServerResponse в Node или Express.

Когда у Talisman есть новый контент, готовый к работе, он может протолкнуть его по потоку, который сразу же доставит его пользователю. Если он встречает Promise, он будет ждать его разрешения, прежде чем отправлять разрешенное значение в выходной поток. Если он встречает другой поток Readable, он перенаправляет его содержимое в выходной поток.

Когда заканчивается контент, поток закрывается.

Язык шаблона

Шаблоны Talisman имеют синтаксис, внешне похожий на Mustache, с переменными заполнителями, вставленными в ваш HTML-код и заключенными в двойные фигурные скобки.

<!doctype html>
<title>{{pageTitle}}</title>

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

Затем он будет искать в своей таблице переменных, доступна ли переменная с именем pageTitle. Если значение этой переменной является строкой или другим скаляром, оно немедленно отправляется в выходной поток ... но если значение является Promise, тогда Talisman будет ждать этого обещания, которое он разрешит первым.

Talisman также может обрабатывать значения переменных, которые являются функциями, потоками или функцией, которая возвращает строку (или поток), или обещанием, которое разрешается в функцию, которая возвращает поток, или обещанием, которое разрешается в функцию, которая возвращает Обещание, которое разрешается в строку (или поток), и так далее, и так далее ...

Блок и итерация

Talisman также позволяет определить Блок, который представляет собой раздел связанного содержимого. Блоки могут использоваться как if, удаляя его по условию, или как цикл, определяя раздел содержимого, который должен повторяться, управляемый каким-либо итератором.

<ul>
    {#ingredients}
    <li>{{name}} ({{price}})</li>
    {/ingredients}
</ul>

Talisman будет повторять раздел, содержащий ‹li› столько раз, сколько будет диктовать назначенный итератор. В настоящее время Talisman поддерживает два типа итераторов: простые массивы и потоки в объектном режиме. Эта последняя функция позволяет нам выводить строки по мере их получения из базы данных. Нам не нужно ждать, пока будет доступен весь набор записей!

Преимущества

Talisman, кажется, улучшает время загрузки страниц, а также улучшает воспринимаемую производительность приложения для пользователя.

В простом примере мы перенесли домашнюю страницу одного проекта с PHP на Node + Express. Используемая база данных - CouchDB.

В версии PHP нашего приложения время до первого байта страницы составляло ~ 900 мс. Перенос на Node + Express снизил время до ~ 200 мс. Добавление Talisman еще больше снизило TTFB - до ~ 15 мс!

В том же примере браузер начал загружать JavaScript и изображения для этой страницы через ~ 900 мс в PHP, через ~ 200 мс в Node + Express и всего через ~ 30 мс в Node + Express + Talisman.

В целом время DOMContentLoaded было сравнимо между двумя тестами Node, но window.onload сработал на ~ 150 мс раньше, потому что мы начали загружать ресурсы раньше.

Недостатки

Как и все остальное в программном обеспечении, Talisman имеет некоторые компромиссы.

Поскольку контент уже был отправлен клиенту, включая код состояния HTTP 200, мы не можем изменить его на 404 или 500, если наша база данных не возвращает строк или что-то пойдет не так.

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

Где взять?

$ npm install talismanjs

Вы также можете ознакомиться с исходным кодом и документацией по API на Github. Он должен работать на Node 4.x и выше.

Комментарии, отчеты об ошибках и запросы на вытягивание приветствуются.