Продуктовое описание winow-view
Задача
Приложению на OneScript нужен живой интерфейс: список, который обновляется без перезагрузки, форма, которая показывает ошибку рядом с полем, меню, которое раскрывается. Обычный ответ на это - собрать фронтенд на JS-фреймворке, а вместе с ним получить node_modules, шаг сборки, отдельный конвейер и второй язык в проекте.
htmx предлагает другой путь: сервер отдаёт HTML, а htmx подставляет его в нужный элемент страницы. Alpine.js берёт локальное состояние вроде раскрытого меню. От сервера нужно немного: понять, что запрос пришёл от htmx, сформировать фрагмент разметки и выставить пару заголовков. Ровно это и делает библиотека.
Четыре задачи:
- читать и выставлять заголовки htmx, не разбираясь каждый раз в их регистре и формате;
- формировать фрагменты и страницы из шаблонов, различая ответ на hx-запрос и на обычный переход;
- раздавать htmx и Alpine.js из состава пакета, чтобы в проекте не появлялся сборщик;
- защищать формы от подделки межсайтового запроса без хранилища на сервере.
Кому пригодится
| Ситуация | Что даёт библиотека |
|---|---|
| Список обновляется без перезагрузки | Фрагмент на hx-запрос, страница на обычный переход, один обработчик |
| Форма показывает ошибку рядом с полем | ПереопределитьЦель и ПереопределитьСпособЗамены |
| Клиент должен узнать о событии на сервере | УстановитьТриггер: htmx вызовет событие, клиент обновит счётчик |
| Нельзя ставить Node.js на сервер сборки | htmx и Alpine.js уже в пакете |
| Формы принимают POST от вошедшего посетителя | CsrfGuard без общего хранилища между процессами |
| Нужны и HTML-страница, и HTML-фрагмент из одного шаблона | Макет с плейсхолдером @Контент |
Отличия от альтернатив
SPA на JS-фреймворке. Даёт максимум возможностей и максимум инфраструктуры: сборка, зависимости, отдельный жизненный цикл, дублирование моделей на двух языках. Для внутреннего приложения с формами и списками это цена без выигрыша. Здесь состояние остаётся на сервере, а язык в проекте один.
Ручная работа с htmx. Заголовки читаются в каждом обработчике по-своему, регистр имён приходится угадывать, HX-Trigger с несколькими событиями собирается вручную и ломается на кавычках. Библиотека закрывает это и покрывает тестами.
Шаблонизатор напрямую. JinjOS сам не знает про макеты, про кэш скомпилированных шаблонов и про запрет выхода за каталог шаблонов. Плюс в экосистеме есть коллизия имени класса Шаблон между JinjOS и winow, и она проявляется только при определённом порядке подключения библиотек. Пакет решает её раз и навсегда.
htmx и Alpine.js с CDN. Просто, пока сервер имеет доступ в интернет и пока версия на CDN не изменилась. В составе пакета файлы зафиксированы, доступны в закрытом контуре, а объявленная версия проверяется тестом.
Своя проверка CSRF. Токен в сессии требует общего хранилища между рабочими процессами и переживает перезапуск только вместе с сессиями. Подписанный токен не требует ни того, ни другого.
Спецификация и совместимость
Библиотека следует справочнику заголовков htmx: реализованы восемь заголовков запроса и десять заголовков ответа. Поведение HX-Trigger соответствует тому, что ожидает htmx: единственное событие без данных передаётся простым именем, несколько событий или событие с данными - объектом JSON.
| Компонент | Версия |
|---|---|
| htmx | 2.0.10 |
| Alpine.js | 3.15.12 |
| Шаблонизатор | JinjOS |
| Веб-сервер | winow |
Обе лицензии поставляемых библиотек разрешают распространение в составе пакета; 0BSD не требует даже сохранения текста лицензии.
Зависимость от winow не жёсткая: функции принимают и возвращают Соответствие заголовков и строки, поэтому работают в тестах без поднятого сервера. Сам winow нужен только приложению.
Особенности
Синтаксис JinjOS строже, чем кажется. Внутри {% %} операторы завершаются точкой с запятой ({% КонецЦикла; %}), выражение в {{ }} не может содержать }, а блок {% %} - знак процента. Плейсхолдер макета не должен выглядеть как подстановка: {{{контент}}} шаблонизатор попытается вычислить.
Имя класса Шаблон в экосистеме занято дважды - в JinjOS и в winow - и достаётся библиотеке, подключённой первой. В приложении с #Использовать winow выражение Новый Шаблон(...) вернёт класс winow, рассчитанный на внедрение зависимостей контейнером autumn. Поэтому пакет не полагается на глобальное имя: файл шаблонизатора подключается напрямую и регистрируется под своим. Если JinjOS установлен нестандартно, путь к нему задаётся параметром ПутьКJinjOS.
Имя шаблона и имя библиотеки нередко приходят из запроса. Имена с .., двоеточием и ведущим слешем отвергаются, файлы статики ограничены закрытым списком: через них нельзя прочитать произвольный файл на диске.
Секрет CSRF задаётся извне. Один и тот же у всех рабочих процессов, разный в разных окружениях, в исходный код не попадает. Токен привязан к сессии, поэтому сессии нужны включёнными.
Ограничения
| Ограничение | Причина |
|---|---|
| Только заголовки htmx | Расширения htmx вроде hx-sse и hx-ws своих заголовков не добавляют, отдельной поддержки для них нет |
| Только шаблонизатор JinjOS | Другие шаблонизаторы подключаются мимо ViewRenderer |
| Кэш шаблонов живёт в памяти процесса | При нескольких рабочих процессах каждый компилирует шаблоны сам |
| Экранирование не разбирает разметку | Функции экранируют значение, а не проверяют готовый HTML: подставлять надёжнее по частям |
| Экранирования для JS и CSS нет | Значения, попадающие в скрипт или стиль, требуют других правил |
| Токен CSRF требует сессий | Привязка к сессии - основа схемы: без идентификатора токен не выдаётся |
| Токен нельзя отозвать досрочно | Состояния на сервере нет, поэтому досрочный отзыв невозможен: работает только срок жизни |
