GWT: улучшаем производительность

 

Авторы: Ренат Ахмеров, Алексей Харламов

С момента своего появления в августе 2006 года Google Web Toolkit (GWT) снискал значительную популярность в среде веб-разработчиков. Эта технология позволяет создавать интерактивные и функциональные приложения при помощи удобных и технологичных инструментов (Eclipse, Maven, SVN). Более того, GWT нацелен на создание кроссбраузерных приложений при помощи изоляции платформо-зависимых элементов.

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

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

Другая проблема – обновление рабочей области браузера. Большинство браузеров не обновляет HTML-основу при выполнении JavaScript. Когда приложение обращается к GWT-компонентам – браузер зависает. Запросы пользователя при этом перестают выполняться. Таким образом медленный рендеринг компонентов становится настоящей проблемой. К счастью, существует простое и широко известное решение.

Любое длительное задание должно быть разбито на фрагменты меньшего размера при помощи DeferredCommand. Таким образом достигается подобие параллельного выполнения.

Processing with DeferredCommand

До тех пор, пока время выполнения одного DeferredCommand не превышает 0,1-0,2 сек., задержки и скорость ответа системы комфортны для пользователя. Но это решение не всемогуще. Оно смягчает проблему, но рендеринг сложных данных всё ещё может ощутимо “тормозить”. Проблема заключается во внутренней компонентной модели фреймворка.

В составе GWT много виджетов, работающих как контейнеры. Часто контейнеры используются, чтобы форматировать другие компоненты атрибутами. Выяснено, что для присоединения к документу своих дочерних объектов контейнеры используют DOM.appendChild(). Таким образом поступают, к примеру,FlexTable и VerticalPanel. Также стандартные компоненты активно используют вызовы DOM.createElement(). Эти операции достаточно быстры при небольшом числе компонентов, но при возрастании их числа наблюдается значительное замедление.

Наши тесты показали, что DOM.setInnerHtml() работает намного быстрее по сравнению со связкой DOM.createElement()/DOM.appendChild(). Это вполне объяснимо: innerHtml использует оптимизированный нативный код сборки DOM внутри браузера. Мы решили переделать компонент Tree под использование innerHtml. Обновленное дерево стало быстрее в сотни раз! По видимому, динамические операции DOM внутри браузеров весьма несовершенны. Для решения этой проблемы мы нашли стандартный путь реализации компонентов интерфейса, критичных к быстродействию, и назвали его FastContainer.

Структура FastContainer

Идея изначально проста: использовать setInnerHhtml для создания фрагментов DOM и присоединять компоненты GWT к соответствующим элементам. Но представлять объекты DOM в строковом виде не очень-то удобно, да и setInnerHtml более эффективен на больших фрагментах HTML-кода. Поэтому имеет смысл вставлять группу элементов/компонентов за раз.

Рабочий элемент

Рис. 2 Рабочий элемент

На рисунке выше приведен пример такой группы. Компонент может быть вирутально поделён на HTML-структуру и набор элементов управления (чекбокс и текстовые метки).

Под структурой здесь мы понимаем фрейм с серой заливкой и скругленными углами. Она также ответственна за выравнивание элементов. Чтобы реализовать всё это стандартными средствами – потребуется немало потрудиться. Вначале нужно создать экземпляр структуры – например, с помощьюFlexTable или другой панели. Затем полностью сконфигурировать объекты и расположить их по строкам/ячейкам (включая ячейки с закругленными углами), задать стили ячеек, вставить вкругления углов и элементы управления. Всё это должно выполняться динамически одно за другим. Каждый этап вызывает один или несколько методов createElement()/appendChild(). Но то же самое можно сделать в текстовом представлении одним вызовомDOM.setInnerHtml(). А HTML-структура может быть быстро создана в любом редакторе.

В рамках такого подхода FastContainer был создан для обеспечения комфортной работы с компонентами, представленными как HTML-фрагменты. Другими словами, он позволяет создать Widgetиз фрагмента HTML-текста и предоставляет интерфейс для взаимодействия с виджетами внутри. Исходная идея принадлежит Sergey Dryganets , который предложил способ абстрагирования и создал первую реализацию, названную LightWidget. Позже мы усовершенствовали её и создали FastContainer.

Основная идея – изоляция HTML-источника внутри Pattern. В него войдёт всё, ответственное за построение HTML-части. Класс FastContainer является предпочтительным компонентом, создающим себя из исходного контекста. Также он предоставляет интерфейс для операций с дочерними объектами.

На этапе рендеринга каждый экземпляр FastContainer использует Pattern для генерации HTML-фрагмента, который затем передается в браузер. В процессе генерации экземпляр ConcretePattern наполняет Context данными о связывании элементов. Таким образом, контейнер может быстро найти нужные элементы.

Fast Container Structure

 

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

Т.к. внутри документа может быть несколько экземпляров одного и того же элемента, для генерации уникальных номеров мест компонентов HTML нужно использовать IdManager. Но этот процесс спрятан внутри элемента, клиенту выводятся только имена (строковые или иные). Используя эти имена, клиент может:

·вставить текст или HTML-код в нужное местоr

·вставить виджет в назначенную для него позицию

·создать дочерний элемент Widget, используя внутренний элемент

За и против

В использовании такого подхода есть 2 преимущества. Во-первых, есть возможность реализовывать часть компонентов в виде HTML-кода. Это проще и понятнее, чем использовать объектную модель GWT. Во-вторых, благодаря использованию DOM.setInnerHtml (и присоединению сразу нескольких структур за раз вместо их пошагового “сшивания”) значительно возрастает скорость рендеринга.

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

Также несколько неудобным представляется управление HTML-кодом внутри Java-источников. Существуют методы ослабления этого неудобства при помощи внешних HTML-шаблонов, но все они снижают скорость рендеринга и загрузки.

На данный момент мы видим только один компромисс – поддержка создания HTML-структур внутри компилятора GWT по типу “предподготовленных” ресурсов. Это позволит:

·разрабатывать HTML-шаблоны в отдельных файлах, но хранить их внутри основного кода JavaScript-приложения

·избегать ручного труда при генерации структур и работе с id

·увеличить производительность

Мы будем рады обсудить способы усовершенствования компилятора GWT с сообществом.

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