Из Swing в JCEF

Из Swing в JCEF

1. Фон проекта

Плагин Maro moti был разработан как инструмент производительности, который автоматически создает экранные компоненты под названием Beat в среде IntelliJ.

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

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

В первоначальной версии этот процесс обрабатывался с помощью двух диалогов на базе Java Swing.

В первом диалоге определялась структура раздела экрана, а во втором диалоге происходила фактическая раскладка полей и их сопоставление.

Сначала структура казалась простой, поэтому больших проблем не возникало, но по мере увеличения частоты использования начали появляться различные проблемы с UX.

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

Кроме того, пользовательский интерфейс на основе Swing с каркасом значительно отличался от фактического экрана, что затрудняло пользователям интуитивное предсказание конечного результата.

С ростом проекта увеличились требования к функционалу перетаскивания, сложной структуре макета и возможности предварительного просмотра в реальном времени.

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

В конечном итоге мы пришли к выводу, что необходимо не только простое улучшение пользовательского интерфейса, но и переработка самой архитектуры, и для этого было решено реализовать переход к веб-структуре с использованием JCEF (Java Chromium Embedded Framework), предоставляемого IntelliJ.

2. Проблемы существующей структуры

Самая большая проблема существующей структуры заключалась в том, что поток пользователей был разбит на несколько этапов.

[Существующий экран]

image1.pngimage2.png

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

В этом процессе необходимо было помнить предыдущие настройки, и если нужно было что-то исправить, нужно было вернуться к предыдущему диалогу.

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

Это повысило утомляемость пользователей и сделало сам процесс настройки неудобным.

Вторая проблема заключалась в ограничениях пользовательского интерфейса на основе Swing.

Существующий интерфейс пользователя был просто организован на основе BoxLayout, и имел значительные визуальные отличия от реального пользовательского интерфейса сервиса.

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

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

Третьей проблемой была масштабируемость.

С увеличением объема проекта требования к функциональности продолжали расти.

  • Перетаскивание полей

  • Вложенная структура макета

  • Рендеринг в реальном времени

  • Изменение порядка полей

  • Адаптивный предварительный просмотр

  • Поддержка различных компонентов

Однако в среде Swing для реализации таких функций требовалось много кода обработки событий и управления состоянием.

Особенно по мере усложнения синхронизации состояния между компонентами сложность сопровождения резко возросла.

В процессе модификации интерфейса также возникли проблемы.

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

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

3. Переход на структуру на основе JCEF

В новой структуре активно использовался JCEF (Java Chromium Embedded Framework), предоставленный IntelliJ.

JCEF — это функция, которая позволяет встраивать браузер Chromium в IntelliJ, обеспечивая возможность запуска веб-приложений даже внутри плагинов.

С помощью этого мы преобразовали существующий диалог на основе Swing в веб-приложение на основе React.

В новой структуре браузер открывается через JBCefBrowser, и в нем запускается отдельно разработанное приложение React под названием vui-editor.

Главное преимущество этой структуры заключалось в том, что способ разработки UI можно было полностью перенести на веб-фронтенд.

В старом Swing различные интерактивные элементы приходилось реализовывать вручную, а теперь это можно сделать гораздо эффективнее, используя React и фронтенд-библиотеки.

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

Особенно структура рендеринга на основе состояния сделала управление состоянием UI намного более интуитивным.

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

В новой структуре мы стремились достичь следующих целей.

  • Предоставление единого диалогового интерфейса

  • Поддержка предварительного просмотра в реальном времени

  • Обеспечение богатого взаимодействия

  • Увеличение производительности фронтенд-разработки

  • Обеспечение возможности расширения функционала в будущем

  • Создание системы поддержки на основе веб-технологий

Такая структурная трансформация была близка не просто к изменению UI, а к переработке самой архитектуры UI плагина IntelliJ.

4. Связь между Java и JavaScript

Проблема, которую нужно было решить в первую очередь при переходе на структуру на основе JCEF, заключалась в обмене данными между Java и JavaScript.

Внутри плагина уже существовали данные, такие как ComponentElement, DataModel, список полей и др., которые управляются в виде объектов Java.

Эти данные нужно было передать в начальное состояние React-приложения, и, наоборот, нужна была структура для передачи результатов редактирования обратно на сторону Java.

Начальная передача данных была реализована с использованием evaluateJavaScript().

После завершения загрузки диалога была вызвана функция window.initEditor(), чтобы передать данные в формате JSON в приложение React.

В приложении React мы разработали его так, чтобы он основывался на переданных данных для настройки начального состояния и рендеринга экрана.

Напротив, запросы на сохранение обрабатывались с использованием window.cefQuery().

Когда пользователь нажимает кнопку сохранения, React-приложение передает текущее состояние в формате JSON, и обработчик JBCefJSQuery на стороне Java получает его для обновления внутреннего состояния.

В процессе реализации также возникли различные проблемы с таймингом.

Одной из основных проблем было то, что если initEditor() вызывался до полной загрузки браузера, функция JavaScript еще не была готова, и инициализация терпела неудачу.

Для решения этой проблемы мы изменили процесс так, чтобы начальные данные вводились только после завершения загрузки DOM.

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

В процессе были добавлены логика обработки исключений и проверки состояния браузера для повышения надежности.

5. Решение проблем с упаковкой и CORS

Одной из ожидаемо сложных частей на основе структуры JCEF была среда развертывания.

React приложение создается в виде статических ресурсов после сборки и в конечном итоге распределяется внутри JAR плагина IntelliJ.

Проблема заключалась в том, что при загрузке React приложения на основе протокола file:// в JCEF некоторые функции не работают должным образом из-за политики CORS браузера.

В частности, возникли проблемы с блокировкой при вызове API fetch или процессе загрузки динамических ресурсов.

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

Для решения этой проблемы я извлек внутренние ресурсы JAR во временный каталог, а затем изменил структуру, чтобы запустить простой локальный HTTP-сервер для обслуживания ресурсов на основе localhost.

Для реализации HTTP-сервера использовался com.sun.net.httpserver.HttpServer.

С точки зрения браузера, это стало восприниматься как обычная среда HTTP-запросов, что в результате позволило решить проблему CORS.

Кроме того, в среде разработки настроено прямое подключение к серверу разработки Vite (localhost:5173) для упрощения разработки.

Таким образом, удалось полностью использовать среду Hot Reload, и производительность разработки фронтенда значительно возросла.

В частности, скорость повторной разработки значительно увеличилась, так как можно было сразу увидеть изменения при модификации UI, не пересобирая плагин IntelliJ.

В результате удалось стабильно настроить как среду разработки, так и среду эксплуатации, и теперь опыт разработки веб-интерфейсов может быть естественно использован и внутри плагина IntelliJ.

6. Результаты и эффекты улучшения

Самая значительная улучшенная часть после перехода на структуру на основе JCEF — это пользовательский опыт.

[Новый экран]

image3.png

Ранее поток, который был разделен на два этапа, теперь объединен в один диалог, и пользователи могут одновременно видеть структуру полного экрана и компоновку полей.

Теперь пользователи могут выполнять все последующие действия на одном экране.

  • Создание структуры раздела

  • Расположение полей

  • Изменение порядка

  • Перетаскивание

  • Просмотр в реальном времени

  • Проверка макета

Благодаря функции реального времени, пользователи могли мгновенно видеть результаты редактируемого экрана, и стало намного проще предсказывать конечный результат.

Также были значительные улучшения с точки зрения UI/UX.

Переходя на структуру на основе React, мы смогли использовать различные фронтенд-библиотеки и паттерны управления состоянием, а также реализация анимаций и интеракций стала гораздо более естественной.

Также произошли положительные изменения с точки зрения разработки организации.

Ранее все изменения в UI приходилось обрабатывать в коде плагина IntelliJ, но теперь стало возможным независимо разделять фронтенд и бэкенд работы.

Фронтенд-разработчик мог сосредоточиться на работе с приложением React, а разработчик плагинов - на бизнес-логике на основе Java.

Благодаря этому два разработчика смогли работать параллельно, что значительно увеличило скорость разработки и эффективность обслуживания.

В этом проекте мы смогли подтвердить возможность активного использования веб-технологий в разработке плагинов для IntelliJ.

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

DEVKC

Site footer