Обновить
4
0

Пользователь

Отправить сообщение

В продолжение темы серверного HTML рендеринга на Kotlin.

Если вы не читали статью, кратко напомню о чём речь: я написал небольшой можно сказать фреймворк для генерации HTML на стороне сервера на Kotlin. И поддержку строготипизированных routes для удобного создания ендпоинтов, ссылок на них и форм. Теперь Spring Views можно создавать на Kotlin и по крайней мере в моих проектах (а один из них очень большой - сотни отдельных страниц и десятки виджетов) это дало мне огромное удобство, уверенность, безопасность, рефакторо-пригодность, простое версионирование и переиспользование кода, например, теперь я точно не сделаю опечатку в URI или имени параметра и не смогу передать неверный тип данных на endpoint.

Одна из проблем которые до недавнего времени была в связи с использованием данного фреймворка - это CSS-стили. Приходилось либо инлайнить стили непосредственно для HTML элемента, либо мейнтейнить огромный (или несколько поменьше) CSS файл без возможности внятно следить за старыми более неиспользуемыми классами и селекторами. Кто создавал большие CSS файлы знает о чём я говорю - со временем количество классов накапливается и вычистить их не задев чего-то всё ещё нужного очень сложно. Вот так приходилось работать с CSS стилями раньше:

DIV("css-class-name") {...}
// или
DIV {
  style("color: red;")
  +"Hello World"
}

В принципе жить можно, но хотелось чего-нибудь:

  • что-то на подобии Styled-Components из ReactJS, но так же на Kotlin

  • по возможности поддержку JVM hot-reload в режиме отладки

  • как следствие кода на Kotlin - иметь возможность всегда отслеживать зависимости и удалять более неиспользующиеся CSS-правила.

Долго думал как это получше сделать, и пока что первая версия получила вот такой синтаксис:

@Component // Spring @Component
object MyCssClass : CssClass({
  style = "color: black;"
  hover = """
    color: red;
    text-decoration: underline;
  """
})

...

A(MyCssClass) {
  href(SomeUsefulRoute(param = 1))
  +"Click me"
}

То есть объявляем object который будет нести информацию о CSS свойствах элемента. И далее просто используем его в тех же местах где раньше можно было указать css-class-name. CssClass поддерживает массу "псевдоклассов" типа hover, active, firstOfType, before, after и так далее, так же можно добавлять media брейкпоинты и всякие другие штуки. Вот более насыщенный пример:

@Component
object Container : CssClass({
    style = "padding: 25px;"
    hover = "background: #eee;"

    add(">a", "color: green;")
    add(">a:hover", "text-decoration: underline;")

    media("max-width: 991px") {
        style = "padding: 10px;"
    }
})

Вы наверняка заметили двойные скобочки ({...}) - в конструктор я передаю функцию-инициализатор стилей. Это нужно для того, чтобы в дебаг режиме JVM можно было на лету менять свойства CSS класса без перезапуска приложения: код который генерирует сам css-файл может быть запущен в режиме dev-mode, когда на каждый запрос файла будут выполнены все функции-инициализаторы ещё раз и файл будет собран заново.

Если по какой-то причине вам не нравится object - можно просто объявить class и использовать его:

@Component
class Container : CssClass({...})
...
DIV(Container::class) { ... }

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

Смотрите исходники тут. Фидбэк приветствуется, всем хорошего дня.

Теги:
Всего голосов 4: ↑4 и ↓0+6
Комментарии4

Довелось мне в последнее время поглядеть на "современные" проекты на Kotlin, и я признаюсь был поражен повсеместным использованием корутин в коде. Используют их просто везде: для обработки входящих соединений, для запросов в базы данных, для вызова внешних API, для операций с файлами на сетевых файловых системах. Знаете, в языке есть слова-паразиты — так вот кажется мне что в Kotlin корутины — это паттерн-паразит. Кажется корутины — это как магические заклинания, которые волшебным образом запустят код "где-то там, в локальном облаке".

Каждый раз когда я сталкиваюсь с туториалами про корутины — самым первым примером идёт конечно sleep на 10 тысяч "потоков". Притом как вы понимаете, вызывать нужно специально подготовленный для этого sleep, а не настоящий Thread.sleep — потому что с ним никакой магии не получится. Это создаёт обманчивое впечатление будто бы любая блокирующая операция автоматически волшебным образом превращается в асинхронную, за бесплатно увеличивая пропускную способность приложения. Хотелось бы напомнить любителям корутин что это не так.

Никто за вас не перепишет обычное чтение из сокета/файлового дескриптора на асинхронные ввод-вывод с проверкой наличия байт для чтения во входящем потоке через Selector::select — чудес не бывает. И как бы вы не увеличивали kotlinx.coroutines.io.parallelism рано или поздно при безудержном использовании корутин пул потоков забьётся чем-нибудь медленным и ваше приложение просто встанет раком.

Для примера попросите чатжпт написать вам простейший echo socket server на корутинах. Ну и клиента, который попробует работать с этим сервером в 100 обычных потоков (не так уж и много для нормального продакшн бэкенда). Надеюсь для вас не будет сюрпризом то, что сервер будет обрабатывать только 64 первых счастливых клиентов, а к остальным доберётся только когда эти первые клиенты закроют соединения.

Теги:
Рейтинг0
Комментарии2

Информация

В рейтинге
Не участвует
Зарегистрирован
Активность