Привет! Сегодня Яндекс выкладывает в опенсорс DivKit — фреймворк для отрисовки интерфейсов из ответа сервера. Серверная вёрстка поможет ускорить разработку: наладить отправку апдейтов от сервера разным версиям приложения, создать прототип или просто написать интерфейс один раз для нескольких платформ.


Фреймворк включает в себя несколько библиотек: клиентскую часть по отрисовке интерфейсов для Android, iOS и веба, а также DSL для формирования ответа сервера на Kotlin, TypeScript и Python. Исходный код опубликован на Гитхабе под лицензией Apache 2.0.

Сейчас DivKit используется в приложении Яндекс, Алисе, Едадиле, Маркете, ТВ и других приложениях. В этом посте я постараюсь вспомнить историю фреймворка, затем мы напишем с его помощью небольшой просмотрщик ленты Хабра, а в конце я покажу ещё несколько простых примеров интеграции.

Небольшой экскурс в историю


Мы в команде приложения Яндекс очень давно задавались вопросом, как быстрее докатывать изменения до пользователей. Главная страница приложения состоит из карточек, каждая из которых решает пользовательскую задачу. Например, показывает прогноз погоды, пробки на маршруте или перекрытия дорог в городе. Как предупредить человека, если закрылась станция метро, внезапно пошёл град — или случилось ещё что-то, что требует изменений не только в данных, но и в UI?

Наше первое решение — добавить в ленту карточку на основе WebView для показа кастомизированных сообщений. Но, потратив где-то полгода на разбор странных крешей WebView, мы решили искать другой путь.

Сначала посмотрели на ленту карточек и увидели в них много повторяющихся частей. Везде есть шапка с тремя точками и футер одного стиля. В простом варианте внутри содержится текст или текст плюс картинка.

Так родилась первая версия Div'ов, состоящая из высокоуровневых смысловых блоков. С сервера приходил минимум информации:

{
    "type": "title",
    "title": "Hello World!",
    "title_style": "title_s"
}

На клиенте мы зашили знание о том, какие параметры (размер, начертание, межстрочный и межбуквенный интервал) надо подставить для стиля title_s.

Всё было хорошо примерно год. Запустить новую карточку, поэкспериментировать с её видом и наполнением удавалось за неделю. Поэтому появлялось всё больше карточек, а новые сервисы быстро интегрировались в основную ленту приложения. Довольно просто было добиваться pixel perfect при сравнении с макетами: если все элементы переносились из макетов корректно, они точно соответствовали тому, что в итоге появлялось в продакшене.

Но концепции дизайна, как и разработки, не стоят на месте. Сначала стало не хватать зашитых стилей текста, потом — отступов горизонтального размера. Мы могли потихоньку вносить правки в стиль текущих блоков, но решили сделать всё с нуля и по-другому.

Div 2.0


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

Базовыми элементами у нас стали текст, картинка, линейный контейнер для расположения элементов и сетка.

Для примера — список параметров, которые сейчас можно настроить для текстового элемента
font_size: размер шрифта.

font_family: семейство шрифтов.

line_height: межстрочный интервал (интерлиньяж) диапазона текста. Отсчёт ведётся от базовой линии шрифта.

max_lines: максимальное количество строк, которые не будут обрезаны при выходе за ограничения.
min_hidden_lines: минимальное число обрезанных строк при выходе за ограничения.

auto_ellipsize: ввтоматическая обрезка текста под размер контейнера.

letter_spacing: интервал между символами.

font_weight: начертание.

text_alignment_horizontal: горизонтальное выравнивание текста.

text_alignment_vertical: вертикальное выравнивание текста.

text_color: цвет текста.

focused_text_color: цвет текста при фокусировке на элементе.

text_gradient: градиентный цвет текста.

text: сам текст.

underline: подчёркивание.

strike: зачёркивание.

ranges: диапазон символов, в котором можно установить дополнительные параметры стиля. Определяется обязательными полями start и end.

images: изображения, встроенные в текст.

ellipsis: маркер обрезки текста. Отображается, когда размер текста превышает ограничение по количеству строк.

selectable: выделение и копирование текста.

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

В это время мы почти не дорабатывали DivKit: нам хватало его возможностей. Пришёл «золотой век» технологии — все карточки можно было катить на версии давностью до года, и они одинаково выглядели и работали везде.

Примерно через год нам стало тесно и в текущих фичах тоже, захотелось уметь менять с сервера не только карточки в ленте. Постепенно с натива на дивы переехала главная шапка, у нас внутри она смешно называется Бендер. Профиль, bottom sheet и другие блоки тоже стали «дивными».

Но новые поверхности требовали новых фич. Разработка стартовала вновь, и мы продолжаем развиваться по сей день.

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


Едадил


Каждый продуктовый сценарий в мобильном приложении Едадил выделен в отдельное мини-веб-приложение. У такого подхода есть свои преимущества, но те экраны, где важно сохранить нативную скорость загрузки и плавность взаимодействия, реализованы без использования веб-технологий. Один из примеров — главный экран, который из-за этого не удавалось развивать так же быстро, как остальные разделы.

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

Так зародился сервис Mosaic. Он позволял генерировать UI для нативных экранов, и его основой (инструментом для Backend Driven UI) стал DivKit. Для удобства админов главной страницы и верстальщиков блоков также создали WYSIWYG-редактор. Mosaic уже второй год живёт в продакшене, продолжает развиваться, обрастать продуктовой и технической функциональностью.



Маркет


Команда Маркета рассматривала несколько вариантов:

  • Написать движок для кросплатформенной разработки — но это было бы слишком дорого и нецелесообразно.
  • Flutter/React Native. Дорого в обучении и интеграции в текущий проект, непроизводительно, плюс ограничена возможность конфигурирования.
  • KMM — не решал проблему с вёрсткой, не позволял динамически менять бизнес-логику.

Самой подходящей по критериям технологией оказался DivKit. В качестве proof of concept решили накидать самую сложную карточку заказа в онлайн-просмотрщике и посмотреть, сколько это займёт времени с нуля, без сторонней помощи, если пользоваться лишь документацией. В итоге человек, который раньше не сталкивался с DivKit, подготовил макет чуть больше чем за час.

В плане интеграции всё прошло гладко. Достаточно было:

  • Подключить библиотеку, опираясь на заранее предоставленный Sample App и документацию.
  • Написать обвязки для уже имеющихся функций, чтобы затем вызывать их напрямую из DivKit.
  • И начать присылать серверную вёрстку и на iOS, и на Android.



Яндекс ТВ


У Яндекс ТВ особая ситуация — прошивки для телевизоров обновляются гораздо реже, чем происходят обычные апдейты приложений, и цена ошибки слишком высока. Server Driven UI позволяет проверять гипотезы с желаемой частотой, выкатывать новые фичи, а прошивки выпускать редко. Сейчас один из блоков на главной странице ТВ создан с помощью DivKit.



К делу!


Давайте напишем небольшое приложение под Android и iOS. Это будет очень простой просмотрщик ленты Хабра.

Для DivKit сначала необходимо создать конфигурацию, инициализировать библиотеку. В Android это можно сделать так:

fun createDivConfiguration(): DivConfiguration {
    return DivConfiguration.Builder(DefaultDivImageLoader(imageManager))
        .actionHandler(DemoDivActionHandler())
        .supportHyphenation(true)
        .typefaceProvider(YandexSansTypefaceProvider(this))
        .visualErrorsEnabled(true)
        .build()
}

Обязательно нужно реализовать загрузчик картинок. Я возьму стандартную реализацию, которая поставляется вместе с фреймворком. Вы можете использовать привычные вам библиотеки: Glide, Picasso и так далее.

.supportHyphenation(true) — включаю перенос по слогам. Это нужно прописывать отдельно, потому что поддержка переноса по слогам может вызывать просадки в производительности. Поэтому включаем явно.

.typefaceProvider(YandexSansTypefaceProvider(this)) — подключаемый шрифт. Yandex Sans идёт отдельным пакетом, не в основном модуле.

.visualErrorsEnabled(true) — включаю отображение ошибок прямо в моей вьюхе. Тем самым можно упростить дебаг: если у меня будут ошибки в div-вёрстке, появится каунтер с ними в левом верхнем углу. Это ускорит разработку. В продакшен-версии отображение ошибок, конечно, следует отключить.

Код для iOS будет выглядеть так:

components = DivKitComponents(
  updateCardAction: nil,
  urlOpener: urlOpener
)

DivKitComponents — «фасад» для работы с DivKit.

updateCardAction — вызывается, когда DivKit хочет обновить вьюху.
urlOpener — внешний обработчик URL-адресов.

Тут можно подробнее остановиться на обработке ошибок в дивах. При невалидной вёрстке неправильные блоки просто выкидываются, убираются из отрисовки. Какие случаи считаются невалидными? Например, если нет поля "text", обязательного для текстового дива, то он не сможет построиться. В контейнере обязательно должен быть хотя бы один дочерний элемент в "items". Эти и другие правила описаны в нашей json-schema.

Распарсим JSON. Пока возьмём зашитые данные — DivKit'у не важно, откуда приходит вёрстка.

Android:

val divJson = assetReader.read(DIVJSON_PATH)
val templateJson = divJson.optJSONObject("templates")
val cardJson = divJson.getJSONObject("card")

iOS:

private func loadCard() throws -> DeserializationResult<DivData>? {
  let url = Bundle.main.url(forResource: "div_json", withExtension: "json")!
  let data = try Data(contentsOf: url)
  let divJson = try DivJson(JSONData: data)
  return divJson.cards.first.flatMap {
    DivData.resolve(
      card: $0,
      templates: divJson.templates
    )
  }
}

DivJson — структура, в которую десериализуется ответ нашего сервера.

DivData.resolve() — создаёт модель карточки по данным из ответа.

Что такое divJson.optJSONObject("templates")? Вёрстка делится на данные и шаблоны, необходимые для упрощения понимания и уменьшения объёма. У шаблона обязательно должен быть тип дива, или он должен наследоваться от такого шаблона.

Если потребуется подставлять данные в какие-то поля, то их необходимо пометить через $ в названии поля. Например: "$text": "card_title", где "card_title" — название поля шаблона.

Вот как будут выглядеть шаблоны для карточки Хабра
"templates": {
    "habr_card": {
        "type": "container",
        "background": [
            {
                "type": "solid",
                "color": "#FFF"
            }
        ],
        "paddings": {
            "left": 16,
            "top": 16,
            "right": 16,
            "bottom": 16
        },
        "margins": {
            "bottom": 8
        },
        "items": [
            {
                "type": "container",
                "orientation": "horizontal",
                "items": [
                    {
                        "type": "user_avatar",
                        "$image_url": "avatar"
                    },
                    {
                        "type": "user_name",
                        "$text": "username"
                    },
                    {
                        "type": "time",
                        "$text": "time"
                    }
                ],
                "margins": {
                    "bottom": 8
                }
            },
            {
                "type": "title",
                "$text": "title"
            },
            {
                "type": "container",
                "orientation": "horizontal",
                "items": [
                    {
                        "type": "footer_icon",
                        "image_url": "https://yastatic.net/s3/home/yandex-app/div_demo/diamond.png"
                    },
                    {
                        "type": "footer_text",
                        "text_color": "#7AA600",
                        "$text": "votes"
                    },
                    {
                        "type": "footer_space"
                    },
                    {
                        "type": "footer_icon",
                        "image_url": "https://yastatic.net/s3/home/yandex-app/div_demo/eye.png"
                    },
                    {
                        "type": "footer_text",
                        "$text": "views"
                    },
                    {
                        "type": "footer_space"
                    },
                    {
                        "type": "footer_icon",
                        "image_url": "https://yastatic.net/s3/home/yandex-app/div_demo/flag.png"
                    },
                    {
                        "type": "footer_text",
                        "$text": "favorites"
                    },
                    {
                        "type": "footer_space"
                    },
                    {
                        "type": "footer_icon",
                        "image_url": "https://yastatic.net/s3/home/yandex-app/div_demo/bubble.png"
                    },
                    {
                        "type": "footer_text",
                        "$text": "comments"
                    }
                ]
            }
        ],
        "orientation": "vertical"
    },
    "user_name": {
        "type": "text",
        "font_size": 13,
        "text_color": "#414b50",
        "font_weight": "medium",
        "width": {
            "type": "wrap_content"
        },
        "alignment_vertical": "center",
        "margins": {
            "right": 5
        }
    },
    "user_avatar": {
        "type": "image",
        "width": {
            "type": "fixed",
            "value": 24
        },
        "height": {
            "type": "fixed",
            "value": 24
        },
        "border": {
            "corner_radius": 3
        },
        "margins": {
            "right": 5
        }
    },
    "time": {
        "type": "text",
        "font_size": 13,
        "text_color": "#777",
        "width": {
            "type": "wrap_content"
        },
        "alignment_vertical": "center",
        "margins": {
            "right": 5
        }
    },
    "title": {
        "type": "text",
        "font_size": 20,
        "text_color": "#333",
        "line_height": 23,
        "font_weight": "medium",
        "margins": {
            "bottom": 20
        }
    },
    "footer_icon": {
        "type": "image",
        "scale_type": "fit",
        "width": {
            "type": "fixed",
            "value": 24
        },
        "height": {
            "type": "fixed",
            "value": 24
        },
        "border": {
            "corner_radius": 3
        },
        "paddings": {
            "top": 4,
            "bottom": 4,
            "left": 4,
            "right": 4
        }
    },
    "footer_text": {
        "type": "text",
        "font_size": 13,
        "text_color": "#BDCDD6",
        "font_weight": "medium",
        "width": {
            "type": "wrap_content"
        },
        "margins": {
            "left": 4
        },
        "alignment_vertical": "center"
    },
    "footer_space": {
        "type": "separator",
        "width": {
            "type": "match_parent",
            "weight": 1
        },
        "delimiter_style": {
            "color": "#0000"
        }
    }
}

Подставляем данные в шаблон:

"card": {
    "log_id": "div2_sample_card",
    "states": [
        {
            "state_id": 0,
            "div": {
                "type": "container",
                "background": [
                    {
                        "type": "solid",
                        "color": "#F0F0F0"
                    }
                ],
                "items": [
                    {
                        "type": "habr_card",
                        "avatar": "https://habrastorage.org/r/w32/getpro/habr/avatars/133/48f/f00/13348ff00887755177016af1d4ea450e.png",
                        "username": "Barseadr",
                        "time": "сегодня в 11:48",
                        "title": "Встречи формата 1-on-1: не противостояние, а слаженное взаимодействие",
                        "votes": "+4",
                        "comments": "10",
                        "views": "200",
                        "favorites": "2"
                    }
                ]
            }
        }
    ]
}

Здесь видно, что данные занимают очень малый объём. Шаблоны помогают значительно сократить вёрстку, выделив все повторяющиеся фрагменты. Вот как будет выглядеть сама карточка:



Добавляем фабрику по созданию вьюшек. Android:

internal class DivViewFactory(
    private val context: Div2Context,
    private val templatesJson: JSONObject? = null
) {

    private val environment = DivParsingEnvironment(ParsingErrorLogger.ASSERT).apply {
        if (templatesJson != null) parseTemplates(templatesJson)
    }

    fun createView(cardJson: JSONObject): Div2View {
        val divData = DivData(environment, cardJson)
        return Div2View(context).apply {
            setData(divData, DivDataTag(divData.logId))
        }
    }
}

DivParsingEnvironment — библиотека распаршенных шаблонов, они будут переиспользоваться при создании карточек. Методом createView мы создаём view из шаблонов и данных. В конструкторе фабрики упоминается DivContext — он аналогичен контексту в Android и используется для тех же целей. В нём можно расшарить общие между вьюхами зависимости.

Как создаётся контекст:

val divContext = Div2Context(baseContext = this, configuration = createDivConfiguration())

Добавляем данные во view: setData(divData, DivDataTag(divData.logId). DivData — это DTO-модель карточки, которая строится из шаблонов и данных.

iOS:

func setCard(_ card: DivData) throws {
  let context = components.makeContext(
    cardId: DivCardID(rawValue: card.logId),
    cachedImageHolders: []
  )
  let block = try card.makeBlock(context: context)
  let view = block.reuse(
    state?.view,
    observer: nil,
    overscrollDelegate: nil,
    renderingDelegate: nil,
    superview: self
  )
  state = State(block: block, view: view)
}

components.makeContext() — создаём контекст. Для этого нужно передать уникальный в рамках контекста идентификатор карточки и хранилище картинок, чтобы они не моргали при переиспользовании.

block.reuse() — создаст новую вьюху или переиспользует существующую.

Код приложения можно посмотреть на Гитхабе, там оно немного доработано: сделана не одна карточка, а лента на DivKit, и есть пример интеграции для iOS.

Кстати, поддержка переменных и функций позволяет писать с помощью DivKit и более сложные блоки интерфейса. Например, можно сделать виджет для настройки цвета:



Что дальше?


Всё больше команд в Яндексе переводят свои приложения на DivKit, и мы дорабатываем технологию под их требования. В ближайших планах:

  • Новый layout с «констрейтами»
  • Flutter
  • Вспомогательные компоненты для работы с DivKit

DivKit как технология Server Driven UI удобна тем, что не требует многого на старте. Вы можете перевести на дивы один экран или даже его часть — карточку, кнопку или другой элемент. В самом начале сервер не обязателен: DivKit легко использовать как облегчающий работу фреймворк, все данные зашить на клиенте, а когда появится механизм ответа сервера — начать получать апдейты.

Для экспериментов есть песочница. К ней подключён веб-движок DivKit, также можно скачать из Google Play и подключить к песочнице демоприложение (в App Store оно появится в сентябре). Данные будут обновляться вживую: песочница соединяется с демоприложением по веб-сокетам. Ответы на многие вопросы есть на сайте, если чего-то не нашли — спрашивайте в комментариях или в телеграм-чате.

Комментарии (37)


  1. serega404
    26.08.2022 09:44
    -17

    ♥️


  1. chesno4eck
    26.08.2022 11:01
    +1

    Код для клиентов (iOS, Android, Web) лучше бы по отдельным репозиториям разложили


  1. Pingvo
    26.08.2022 11:02

    Планируете расширять кол-во ЯП, на которые будет приходить ответ? 


    1. Tayrinn Автор
      26.08.2022 11:52
      +4

      Да, в планах добавить поддержку клиента на Flutter!


  1. littlefuntik
    26.08.2022 11:02
    +13

    Серьезно? Простите, что не читал статью полностью... Это из-за того, что меня приследует дежавю в том, что рендерить html и вешать скриптовый язык для оживления тоже-самой по идее...


    1. powerman
      26.08.2022 18:26
      +2

      А ещё раньше для управления UI с бэка был Tcl/Tk.


  1. SabMakc
    26.08.2022 11:08
    +21

    Наше первое решение — добавить в ленту карточку на основе WebView для показа кастомизированных сообщений. Но, потратив где-то полгода на разбор странных крешей WebView, мы решили искать другой путь.

    Спустя 5 лет:

    Наше первое решение — добавить в ленту карточку на основе DivKit для показа кастомизированных сообщений. Но, потратив где-то полгода на разбор странных крешей DivKit, мы решили искать другой путь.

    Не думаю, что переизобретение HTML/CSS/JS что-то принципиально решает. Ну кроме "Это сделано не нами"...

    Пока, на старте - да, наверное, удобно. Тем более что сами и разработали решение. А что будет потом? Внешние библиотеки, вопросы безопасности, вопросы доверия к серверу, появление своей песочницы, тормоза, глюки и т.д... И получаем еще один WebView со всеми его заморочками...


    1. Tayrinn Автор
      26.08.2022 11:51
      +3

      Технологии всегда сменяют одни другие. Сейчас DivKit лучше, чем HTML подходит для наших задач, что будет дальше - посмотрим.


    1. your_rubicon
      26.08.2022 12:21
      +8

      Как минимум

      еще один WebView


      - легче дебажить так как присутствует типизация в отличии от html + js
      - нативный код упрощает работу с api системы
      - это быстрее работает

      Так что технология точно имеет право на жизнь по моему мнению.


      1. SabMakc
        26.08.2022 13:36
        +6

        Право на жизнь имеет, не спорю. Но это достаточно нишевое решение. А если дальше развивать - то получим все тот же веб со, всеми его нюансами/заморочками.

        Легче дебажить? Не уверен - для HTML/CSS/JS инструментов больше и они будут более развитыми. Про типизацию - аналогично, TypeScript появился достаточно давно (хотя он далек от идеального решения - в первую очередь своей переусложненной экосистемой).

        Нативный код - чем отличается про проброса нативных методов в тот же WebView? Не говоря уже про "безопасность" вызова нативного кода с удаленного сервера.

        Быстрее работает - да, самый главный аргумент. Но опять же - а если верстать как в начале 2000х было принято? С минимумом CSS/JS и прочих "плюшек"? Тоже будет медленно? А если какой упрощенный WebView сделать, заточенный под скорость, упростив поддерживаемые стандарты?


        1. avdosev
          26.08.2022 14:28

          А если какой упрощенный WebView сделать, заточенный под скорость, упростив поддерживаемые стандарты?

          То получите свой собственный веб-вью, который сложнее дебажить чем обычный, писать на нем не каждый захочет и поддерживать и развивать его не каждый сможет


          И возвращаемся к тому от чего хотели уйти...


          А что будет потом? Внешние библиотеки, вопросы безопасности, вопросы доверия к серверу, появление своей песочницы, тормоза, глюки и т.д… И получаем еще один WebView со всеми его заморочками...


        1. F0iL
          26.08.2022 16:05
          +1

          А если какой упрощенный WebView сделать, заточенный под скорость, упростив поддерживаемые стандарты?

          Google примерно так и сделали для своего приложения YouTube на смарт-телевизорах, оно по сути дела запускается в специальном браузере (которым изначально был очень-очень сильно порезанный Blink, вплоть до того что там тег <p> не поддерживался, и с прикрученным кастомным js-движком) под названием Cobalt.

          А на десктопах есть ещё такая штука как Sciter.


    1. Eloev
      26.08.2022 12:34
      +1

      Концептуально да, но вопросы же как всегда в практической плоскости. Если нужен server driven ui - Яндекс в таком подходе не одинок. Альфа-Банк рассказывает на мобиусе о своих "много-шагах", что по сути тоже самое, что сабж.

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


  1. your_rubicon
    26.08.2022 11:41
    +5

    Очень похожую идею реализует jasonette. Рассматривали ли вы эту технологию и чем она вам не подошла?


  1. Tanner
    26.08.2022 12:54
    +7

    Как там дела с БЭМ?


    1. Druj
      26.08.2022 14:37

      А что с ним не так?


    1. yadobr
      26.08.2022 19:39
      +1

      Если не затруднит, ответьте пожалуйста более развернуто.
      Чем вам не нравится БЭМ?


      1. romanzhivo
        26.08.2022 22:19
        +1

        Прошу прощения, что вклиниваюсь, но тоже хотел бы ответить: мне БЭМ не нравится псевдоформальностью, избыточностью и многословностью. Как рекомендация к стилю написания разметки - норм. Не более.


  1. riky
    26.08.2022 13:04

    а потом заметили что для отрисовки списков элементов нужно одни и теже стили навешивать на каждый элемент, json получается громоздким, долго передавать по сети. и придумали вешать стили на классы...
    или gzip сжатия достаточно?


    1. avdosev
      26.08.2022 13:18
      +4

      Не, для этого придумали шаблоны (aka templates), в песочнице можно увидеть пример https://divkit.tech/ru/playground


      1. Tayrinn Автор
        26.08.2022 14:15

        Именно так, спасибо :)


      1. nin-jin
        27.08.2022 01:57

        Всё это уже было в симпсонах:

        $myTutorialCard $yaContainer
        	items /$yaDiv
        		<= Head $yaText
        			text <= title \
        			style *
        				font *
        					size 21
        					weight \bold
        				margin *
        					bottom 16
        		<= Body $yaText
        			text <= body \
        			style *
        				font * size 16
        				margin * bottom 16
        		<= Links $yaContainer
        			items <= links /$myLink
        	style *
        		margin * bottom 6
        		orientation \vertical
        		paddings *
        			top 10
        			bottom 0
        			left 30
        			right 3
        
        $myLink $yaText
        	action * url <= link \
        	style *
        		font * size 14
        		margin * bottom 2
        		text * color \#0000ff
        		underline \single
        
        $mySampleCard $yaCard
        	states *
        		default <= Default $yaContainer
        			items /$yaDiv
        				<= Logo $yaImage
        					image_url \https://yastatic.net/s3/home/divkit/logo.png
        					style * margin *
        						top 10
        						right 60
        						bottom 10
        						left 6
        				<= Main $myTutorialCard,
        					title \DivKit
        					body \What is DivKit and why did I get here?\n\nDivKit is a new Yandex open source framework that helps speed up mobile development.\n\niOS, Android, Web — update the interface of any applications directly from the server, without publishing updates.\n\nFor 5 years we have been using Devkit in the Yandex search app, Alice, Edadeal, Market, and now we are sharing it with you.\n\nThe source code is published on GitHub under the Apache 2.0 license.",
        					links /$myLink
        						<= Landing $myLink
        							text \More about DivKit
        							link \https://divkit.tech/
        						<= Docs $myLink
        							text \Documentation
        							link \https://divkit.tech/doc/
        							log \docs
        						<= TgNews $myLink
        							text \News channel
        							link \https://t.me/divkit_news
        						<= TgEnChat $myLink
        							text \EN Community chat
        							link \https://t.me/divkit_community_en
        						<= TgRuChat $myLink
        							text \RU Community chat
        							link \https://t.me/divkit_community_ru
        

        Пример трансляции этого в TS.

        При этом строчек в 2 раза меньше, меньше визуального шума, синтаксическая гарантия уникальности имён, статическая типизация.


  1. riky
    26.08.2022 13:07

    а как вы навешиваете события на эту верстку?


    1. Tayrinn Автор
      26.08.2022 14:15

      Можно вешать события на тап-лонгтап или видимость элемента.


  1. Suvitruf
    26.08.2022 14:37

    1. А как мне это, например, в Android Studio посмотреть и подправить?
    2. Я так понял, это очередной WebView под капотом, а не мапинг json'а на нативные контролы?


    1. Tayrinn Автор
      26.08.2022 14:53
      +5

      1. Подправить вёрстку? Мы скоро выпустим библиотеку для составления JSON на kotlin

      2. Под капотом самые настоящие нативные контролы


      1. Suvitruf
        26.08.2022 14:58

        Подправить вёрстку? Мы скоро выпустим библиотеку для составления JSON на kotlin
        Да, подправить в Студии, чтобы оно потом сериализовалось в JSON.
        Под капотом самые настоящие нативные контролы
        А вот это хорошая новость)


        1. Tayrinn Автор
          26.08.2022 18:09
          +3

          Есть мысли такое для Kotlin DSL сделать)


          1. gev
            26.08.2022 23:05

            сразу подумал о DSL для Haskell =)


  1. kpmy
    26.08.2022 16:17
    +2

    Это был в своё время такой Wicket для веб-приложений на Java, что-то похожее по концепции.


    1. izemskov
      26.08.2022 19:22
      +1

      Туда же JSF (RichFaces/PrimeFaces), ZK Framework, Vaadin. Таких вещей множество с удовольствием ими пользуюсь. Про Wicket не слышал, спасибо, посмотрю


      1. mbait
        27.08.2022 04:10

        Vaadin это надстройка над GWT. От последней технологии почему-то многие плюются, хотя никто не может дать чёткого ответа, что же не так с ней.


    1. yroman
      27.08.2022 02:33

      Он и сейчас есть, вполне себе развивается.


  1. Format-X22
    27.08.2022 03:44
    +5

    Когда-то был такой фреймворк, назывался ExtJS. Он и сейчас условно есть, но условно. Там тоже программировали по сути джейсонами, только задача была рендерить во всем зоопарке браузеров, включая мобильные, включая режим когда юи мимикрировал под нативный, в том числе если это сайт и под разные ОС. В том числе со сборщиком в приложение, а также в десктопное, до появления электрона и прочего. Ну И прикольно было иметь флексы ещё на IE6 ещё до того как их изобрели в CSS. И всё как раз такими конфигами. Проблемы копирования свойств и прочего не было из-за возможности конфигов итемов, наследуемых свойств и прочего, ну а если очень хотелось, то можно было и стили дописать, с автогенерацией неймспейсов для защиты от коллизий и уменьшения бойлерплейта. К последним версиям шли бонусом MVC, MVVM, модели, домены эвентов, самопильные промисы, коннекторы к апи, полифилы на любой чих и просто миллиард утилит и возможностей - от высокоуровневых компонентов-панелей, до возможности играться напрямую в теги, с оптимизациями привязки ссылок в памяти на элементы и ещё целый сундук фич на любого эстета. Ну и главное - уже рабочий набор компонентов, от кнопок до мультифункциональных гридов с экспортами, графиками прям в ячейках и сводными данными. И ты просто мог сесть и делать юи, не долбаться, не писать свой очередной юи-кит, не смотреть и плакать на тот минимум что дают всякие материал юи, даже на бутстрап и подобное - там был смешной минимум, а тут у тебя звездолёт и ты его архитектор со световым мечом - где надо просто взял и сделал юи, где захотелось поиграть в джедая - держи низкоуровневые функции и вперёд властвовать над всеми.

    Классный был фреймворк, был его ярым фанатом. А потом он умер. Потому что нельзя продвигать нормально то что стоит 5к баксов в год за право писать, а свободную версию прятали и ограничивали, в какой-то момент чтобы скачать надо было через гугл искать ссылку и потом ещё по почте получать персональную ссылку. В итоге развитие замедлилось и сейчас оно по факту мертво, полностью растеряв былое величие и отстав от современности в плане инструментов и версий самого языка, эдак года с 2014. А ведь этот дедушка умел больше чем некоторое сейчас, значительно больше, безапелляционно больше, титан своего времени.


    1. yroman
      27.08.2022 15:58

      Использовал его вплоть до версии 3.4, фреймворк действительно был отличный. И тогда он стоил намного меньше 5к. Хотя, в целом, довольно тяжеловат.


  1. Gizmich
    27.08.2022 10:43
    +3

    Прошелся по документации, периодически вылетает 404 Page not found. Будут ли какие-то плагины для Figma, Wordpress и прочего ? Без готового шаблонизатора, и большой библиотеки компонентов кажется игра не стоит свеч...


  1. Hemml
    27.08.2022 17:09
    +1

    Sorry за саморекламу, но у меня есть концепция по развитию этой идеи :)

    Суть в том, чтобы отказаться от JSON вообще, а передавать на устройство прямо код, который оно будет исполнять (в моем случае -- это JS, передаваемый в браузер через вебсокет). Код может не просто отрисовывать элементы, но и определять новые функции, так что можно просто передать клиенту функцию для отрисовки сложного элемента (включая всю внутреннюю логику его работы и всякие анимации/переходы), а потом просто вызывать ее. Или пусть он сам ее вызывает по событию. У меня это реализовано на Common Lisp (извините :), который компилируется в JS, но можно и для других языков это сделать.