Yesod и Haste: мёд и дёготь
date = fromGregorian 2015 feb 05
category = "Веб"
tags = ["Yesod", "Haste"]
Приветствую!
Сегодняшняя короткая заметка посвящена моему знакомству с Haste и попытке связать его с Yesod. К сожалению, кроме мёда я нашёл и дёготь.
Подготовка
Haste - это Haskell-диалект, заточенный специально под веб-разработку. В него заложены довольно-таки интересные идеи, и одна из них - попытка отказа от нативного JavaScript. Haste позволяет писать настоящий Haskell-код, компилирующийся в .js
-файлы. И это не могло не привлечь моё внимание: я захотел связать этот инструмент с моим Yesod-проектом с целью замены .julius
-шаблонов Haskell-кодом.
Установка Haste тривиальна, ведь, в отличие от того же PureScript, Haste не требует никаких JS-зависимостей:
$ cabal install haste-compiler
$ haste-boot
Минут через десять установка завершится. Теперь мне доступна команда hastec
, это и есть наш компилятор. Его использование в общем и целом таково:
$ haste Form.hs -o Form.js
Form.hs
- исходный Haste-код, Form.js
- сгенерированный JS-код. Всё предельно просто.
И вот какой была моя задумка: беру Yesod-виджет (состоящий, как вы помните, из трёх шаблонных файлов) и заменяю .julius
-шаблон Haste-файлом. Например, для формы регистрации нового пользователя было вот так:
templates/
home/
register/
form/
form.hamlet
form.lucius
form.julius
а стало вот так:
templates/
home/
register/
form/
form.hamlet
form.lucius
Form.hs
Файл назван с большой буквы по привычке, чтобы было единство с остальными .hs
-файлами. Затем я выполняю вот такую команду:
$ hastec templates/home/register/form/Form.hs -o static/js/Form.js
В результате в каталоге static/js/
появится файл Form.js
. После этого мне останется лишь подключить его к проекту:
-- Foundation.hs
...
<- widgetToPageContent $ do
pageContent ...
-- Локальные стили и скрипты.
$ StaticR js_default_js
addScript $ StaticR css_default_css
addStylesheet $ StaticR js_Form_js
addScript ...
Кстати, после добавления нового статического скрипта не забываем выполнить команду touch Settings/StaticFiles.hs
, иначе проект не соберётся.
Помимо прочего, компилятор hastec
позволяет оптимизировать генерируемый JS-код. Выполняем:
$ hastec --help
и читаем, там всё понятно изложено. К сожалению, получить production-оптимизацию “из коробки” не получится:
$ hastec --opt-all templates/home/register/form/Form.hs -o static/js/Form.js
No Java runtime present, requesting install.
hastec: user error (Couldn't execute Google Closure compiler: Command 'java' failed with error code 1)
Google Closure compiler требует наличия JDK.
Вот, собственно, и всё. Теперь заглянем внутрь.
Мёд
Вот содержимое файла Form.hs
:
import Haste
import Data.Maybe
= do
main <- elemById "createProfileButton"
createProfileButton "disabled" True setClass (fromJust createProfileButton)
Красиво и понятно: мы получаем доступ к кнопке #createProfileButton
и делаем её неактивной с помощью Bootstrap-класса .disabled
.
Как видите, это настоящий Haskell-код, и это не может не радовать. В модуле Haste.DOM
присутствует множество стандартных обвязок для нативных JS-функций, лично я сориентировался весьма быстро.
Какое же главное преимущество мы получаем, используя Haste? Мы получаем compile-time проверку! Больше никаких проблем с опечатками в именах JS-функций, никаких ошибок с несимметричными круглыми/фигурными скобками, никакой путаницы с количеством аргументов у функций. Теперь подобные ошибки будут отлавливаться на стадии компиляции, а про нагромождение JS-скобок вообще можно забыть.
Казалось бы, ура! Правда? Эх, я бы очень хотел сказать “да”, но, помимо мёда, есть и дёготь.
Дёготь
Да, и к сожалению, его довольно много.
Во-первых, Haste-код не является частью Yesod-проекта. Да, мы запихнули Haste-файлы в наши виджеты, но в действительности они не стали частью виджетов. Ведь мы вынуждены компилировать Haste-файлы компилятором hastec
, который не имеет ни малейшего представления о Yesod. Следовательно, мы должны организовать некий отдельный шаг сборки проекта, например скрипт, который будет пробегать по всем Haste-файлам и компилировать их, складывая результаты в каталог static/js/
.
Во-вторых, поскольку Haste-код не является частью Yesod-проекта, мы не можем использовать в нём наши любимые type-safe URL-ы. И это очень обидно. Более того, мы при всём желании не смогли бы интегрировать type-safe URL-ы в Haste-код, ведь hastec
не поддерживает Template Haskell.
И в-третьих, избавиться от ручного написания JS-кода нам вряд ли удастся, и это хуже всего.
В реальном проекте мы едва ли будем писать весь JS-код с нуля. Скорее всего, мы захотим использовать какие-то готовые библиотеки, типа JQuery и тому подобных. К сожалению, перевести такой библиотечный код в Haste-код невозможно, ведь разработчики Haste думали только о чистом JavaScript. Разумеется, мы можем интегрировать чистый JS-код в Haste-код с использованием FFI-механизма. Но это сводит к нулю главный плюс Haste - избавление от необходимости писать JS-код вручную.
Более того, даже если мы напишем FFI-JS обёртку, наподобие такой:
getText :: JQuery -> IO String
= ffi "(function (DOMElement) { return DOMElement.text(); })" getText
мы потеряем и второй плюс Haste, а именно проверку JS-кода на стадии компиляции. Как видите, JS-функция записана в виде обычной строки, и это не имеет никаких преимуществ перед тем, как если бы мы вернулись к сырым .js
-файлам. В частности, взгляните на этот FFI-пример. Мало того, что мы пишем-таки JS-код, так ещё и создаём дополнительный .js
-файл.
Выводы
Да, я считаю прекрасной саму идею замены JavaScript на Haskell. И если вы не собираетесь использовать в вашем Yesod-проекте много библиотечного JS-кода, то Haste весьма уместен. В самом деле, весь нативный JS-код можно заменить на Haste-код, а немногочисленные библиотечные функции обернуть в FFI. Конечно, у вас в любом случае не будет type-safe URL, но делать нечего.
Однако, если вы собираетесь использовать много библиотечного JS-кода, Haste, на мой взгляд, только усложнит проект. Зачем оборачивать в FFI кучу функций из какого-нибудь JQuery Validation? Нет, игра явно не стоит свеч. Впрочем, решать вам.