1. Фон проекта
Плагин Maro moti был разработан как инструмент производительности, который автоматически создает экранные компоненты под названием Beat в среде IntelliJ.
Этот плагин был разработан с целью автоматизации структуры экранов и размещения полей, которые необходимо создавать повторно, что повысит производительность разработчиков.
После выполнения пользователем CreateBeatAction, через диалоговое окно определяется структура экрана, размещаются необходимые поля в каждом разделе, а затем выполняется окончательная генерация кода.
В первоначальной версии этот процесс обрабатывался с помощью двух диалогов на базе Java Swing.
В первом диалоге определялась структура раздела экрана, а во втором диалоге происходила фактическая раскладка полей и их сопоставление.
Сначала структура казалась простой, поэтому больших проблем не возникало, но по мере увеличения частоты использования начали появляться различные проблемы с UX.
В частности, пользователям приходилось постоянно переключаться между двумя диалогами, чтобы запомнить состояние, и часто им приходилось возвращаться на предыдущий шаг, чтобы снова проверить предыдущие настройки.
Кроме того, пользовательский интерфейс на основе Swing с каркасом значительно отличался от фактического экрана, что затрудняло пользователям интуитивное предсказание конечного результата.
С ростом проекта увеличились требования к функционалу перетаскивания, сложной структуре макета и возможности предварительного просмотра в реальном времени.
Однако в структуре на основе Swing для реализации таких функций требовалось много кода событий и логики управления состоянием, и затраты на обслуживание начали быстро расти.
В конечном итоге мы пришли к выводу, что необходимо не только простое улучшение пользовательского интерфейса, но и переработка самой архитектуры, и для этого было решено реализовать переход к веб-структуре с использованием JCEF (Java Chromium Embedded Framework), предоставляемого IntelliJ.
2. Проблемы существующей структуры
Самая большая проблема существующей структуры заключалась в том, что поток пользователей был разбит на несколько этапов.
[Существующий экран]

Пользователь сначала должен был установить структуру секции, а затем перейти к следующему диалогу, чтобы разместить поля.
В этом процессе необходимо было помнить предыдущие настройки, и если нужно было что-то исправить, нужно было вернуться к предыдущему диалогу.
Особенно в случаях с сложной экранной структурой пользователю приходилось многократно сравнивать секции и поля, и было сложно понять всю экранную структуру одновременно.
Это повысило утомляемость пользователей и сделало сам процесс настройки неудобным.
Вторая проблема заключалась в ограничениях пользовательского интерфейса на основе 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 — это пользовательский опыт.
[Новый экран]
Ранее поток, который был разделен на два этапа, теперь объединен в один диалог, и пользователи могут одновременно видеть структуру полного экрана и компоновку полей.
Теперь пользователи могут выполнять все последующие действия на одном экране.
-
Создание структуры раздела
-
Расположение полей
-
Изменение порядка
-
Перетаскивание
-
Просмотр в реальном времени
-
Проверка макета
Благодаря функции реального времени, пользователи могли мгновенно видеть результаты редактируемого экрана, и стало намного проще предсказывать конечный результат.
Также были значительные улучшения с точки зрения UI/UX.
Переходя на структуру на основе React, мы смогли использовать различные фронтенд-библиотеки и паттерны управления состоянием, а также реализация анимаций и интеракций стала гораздо более естественной.
Также произошли положительные изменения с точки зрения разработки организации.
Ранее все изменения в UI приходилось обрабатывать в коде плагина IntelliJ, но теперь стало возможным независимо разделять фронтенд и бэкенд работы.
Фронтенд-разработчик мог сосредоточиться на работе с приложением React, а разработчик плагинов - на бизнес-логике на основе Java.
Благодаря этому два разработчика смогли работать параллельно, что значительно увеличило скорость разработки и эффективность обслуживания.
В этом проекте мы смогли подтвердить возможность активного использования веб-технологий в разработке плагинов для IntelliJ.
Я заметил, что если инструмент требует особенно сложного взаимодействия и богатого пользовательского опыта, то структурированный на основе JCEF — это очень эффективный выбор.
DEVKC