Compose Web здорового человека

839f3d7e05f022162cb79fc1abe1f51a

Чтобы сэкономить ваше время, если вы пришли за статьёй про Compose WASM/JS — в данной статье речь пойдёт про Compose HTML:) Добро пожаловать.

Когда-то очень давно, когда трава еще была зелёная (нет, не месяц назад) Kotlin Multiplatform имела основным постулатом «Общее — в общем, частное — в частном». По-сути это означало, что бизнес-логика (которая обязана быть независимой от платформы), бОльшая часть моделек (читай, data classов), интернет клиенты (привет Ktor, ты лучший) и другие общие части лежат в общем коде. При этом частные вещи — настройки клиентов, файловых хранилок, системозависимых вещей — должны быть в таргетах, то есть платформах (натив, jvm, js).

Какое-то время назад вышли сначала Jetpack Compose, а затем он, можно сказать, превратился в JetBrains Compose, поскольку никто не мешал вынести Compose Runtime как общую логику для всех платформ. В конце концов, декларативное программирование и в Африке декларативное программирование, и имея общую логику расчетов изменений в UI для всех платформ — грех было её не вынести в мультиплатформу. Далее вопрос оставался за реализацией на платформах для существующих библиотек или чего-то своего

Про работу Compose Runtime

По-сути Compose Runtime предоставляет некоторый API (кто бы мог подумать :)), который сам отслеживает изменения состояний переменных (State/MutableState) и производит вызовы перестановок/добавлений/удалений элементов. При этом непосредственно перестановки/добавления/удаления реализуются в рамках Applierов и других абстракций

Вечные ворчания старика

Какое-то время всё шло хорошо: для Android жил и процветал Jetpack Compose, для JS был Compose for Web, для десктопа делали мостики от Jetpack Compose.

Казалось бы, что могло пойти не так? Пилили бы себе под каждую платформу нативную (насколько возможно) реализацию, при желании — объединили бы в какой-то KMP библиотеке. Но кому-то пришло в голову, что попытаться сместить Flutter с его пьедестала «write once, run everywhere» (где-то мы это слышали) — хорошая идея. Мало того, что сам Flutter до сих пор не очень много где появляется и при попытках его использовать чувствуется его ненативность, так еще и по-сути своей он — полная противоположность основным принципам KMP. Хорошо разницу между кроссплатформой и KMP объяснил Александр Соколинский в соответствующем докладе.

И сейчас мы находимся здесь:

  • Для андроида всё хорошо

  • Compose Web теперь не Compose Web, а Compose HTML, а Compose Web — это Compose Canvas, точнее уже тоже не Compose Canvas, а Compose WASM/JS (ну то есть сейчас Compose Web — это Compose WASM)

  • Для iOS вроде как всё становится лучше и лучше, но я пока не увидел повальной миграции

  • Десктоп вроде как тоже всё лучше и лучше, но что-то я не видел там реализаций UI отличных от Material

Казалось бы, надо радоваться, получается же…, но нет. Для iOS пришлось пилить просто неописуемую кучу всего (как я помню, просто чтобы писать под iOS команде котлина пришлось запилить свой GC), на десктопе это просто выглядит ненативно, тормозит, требует Java (хотя с этим еще можно жить). WASM (он же Web) вообще не пойми в каком состоянии, то нужно было какие-то галочки тыкать, то собиралось как-то сложно, а когда эти проблемы решили — проблема ненативности никуда не делась. Более того — даже на андроиде до сих пор встречаются приколы с тем, что приложение тормозит и выглядит… странно. Leroy Merlin выпустил статью о том, как они переезжали в Lemana Pro, хотя казалось бы — о том, как перекрасить своё Android/iOS приложение УЖЕ есть миллионы статей, но они были про нативный UI и нативные способы замен тем и текстов.

Проблема с Compose Multiplatform усугубляется крайне скудными доками, ещё пилящимися фичами и т.д. Какой я вижу выход из ситуации? Запилить нативные таргеты. Про iOS ничего не могу сказать, но для андроида и веба они есть. Для десктопа сложнее, поскольку там какой-то жесткий зоопарк, но есть Swing для JVM, можно было бы потеребить труп JavaFX, он говорят иногда бывает полезен. Потом уже на базе нативных реализаций можно было бы пилить общую библиотеку, если очень хотелось бы. Такой подход:

На вопрос «Почему ты считаешь, что это возможно?» ответ очень простой: Джейк Вортон запилил Mosaic — терминальную реализацию на базе композ рантайма. А по поводу общего кода — посмотрите на реакт натив, который вполне справляется с похожей задачей.

Таким образом, сейчас имеем следующее: появился Compose for Web и превратился в Compose HTML. Что же это такое?

Compose HTML — платформенная библиотека (работает только в JS), включает в себя:

  • Реализацию необходимого API для связки с Compose Runtime + полезные функции

  • DSL для непосредственно элементов

  • DSL для создания своих стилей

  • DSL для создания анимаций

По-сути, это всё, что нужно для написания веб-приложений. Поскольку вы используете Kotlin/JS, вам будет доступен весь набор мультиплатформенных (не всегда) библиотек с наличием таргета Kotlin/JS. Список опенсурс репозиторивев можно посмотреть на гитхабе.

Если хочется добавить и настроить div, делаем вот так:

Div({ // тут настраиваем аттрибуты и подписки на элемент,
  // следит за изменениями в композиции
  onClick {
    // реагируем на клики
  }
  addEventListener("click", {
    // тоже реагируем на клики
  })
  style {
    // настройка аттрибута `style`
  }
  classes(/* список классов */)
  if (condition) {
    // какие-то опциональные параметры настройки
  }
}) {
  // Контент div'а
  Text("HelloWorld")
}

Далее вы можете захотеть настроить стили:

style {
  width(128.px)
  height(50.percent)
  paddingLeft(50.percent + 128.px)
  display(DisplayStyle.Grid)
  property("someCustomProperty", "someCustomValue")
}

Но я бы рекомендовал выделять стили в StyleSheet'ы:

object SomeStyleSheet : StyleSheet() {
  val someClassName by style {
    width(128.px)
    height(50.percent)
    paddingLeft(50.percent + 128.px)
  }
}

И применение затем будет выглядеть вот так:

Style(SomeStyleSheet) // подключение стайлшита
Div({
  classes(SomeStyleSheet.someClassName) // применение стайлшита
}) {
  Text("HelloWorld")
}

Ну и последнее, что осталось, это анимации. Анимации (точнее, keyframes) можно создавать только в StyleSheet:

object SomeStyleSheet : StyleSheet() {
  val keyframe by keyframes {
    from {
      width(128.px)
    }
    each(50.percent) {
      width(0.px)
    }
    to {
      width(50.percent)
    }
  }

  val someClassName by style {
    animation(keyframe) {
      duration(2.s)
      direction(AnimationDirection.Normal)
    }
  }
}

Подключение будет как в предыдущем сниппете.

Из минусов:

  • Они НЕидентичны style аттрибуту в тэге

  • Много приколов при построении зависимостей. Например, если вы хотите описать класс с ховером, вы делаете (self + ":hover") style { /* тут описывать стили */ }, но даже это срабатывает не всегда

  • Указать !important — целое приключение (добавлю костыль, если найду, как мы его делали)

  • Ошибки на уровне адаптеров к API, как CMP-6802. Правда, это проблема решаемая — поставьте по-больше больших пальцев и к таким задачам будет больше внимания :)

  • Сама JB сейчас увлечена созданием аналога флаттера в виде Compose Multiplatform, поэтому Compose HTML задвинут на задний план и поэтому о технологии не сильно известно. Это тоже решается появлением библиотек для/с поддержкой Compose HTML и повышением внимания к технологии

Заключение

Текущий статус у технологии очень простой: ему не уделяют достаточного внимания, но существующая база позволяет делать полноценные веб-приложения — от лендингов (что будет жирновато, имхо), до полноценных приложений. Лично я запустил уже несколько таких приложений и не сказать, что были серьёзные проблемы. По-сути, основная проблема в том, что JB задвинули технологию по-дальше и поэтому о ней мало кто знает. Но именно этот аспект мы с вами можем исправить, а дальше подтянутся и исправления других проблем :)

© Habrahabr.ru