В январе прошла интересная конференция GWT.create, на ней было сделано несколько важных анонсов и представлено видение разработчиков GWT относительно будущего фреймворка. Я постараюсь рассказать вам о самых важных новостях из мира GWT разработки.
Основные компании участники:
- Vaadin
- Sencha
- Arcbees
По ощущениям, вся конференция затевалась, чтобы заявить миру — GWT жив и будет жить. Считаю, что у них совсем неплохо получилось.
Ну а теперь про сами доклады.
Самый главный доклад
Keynote от компании Google.
Первая часть доклада посвящена обзору вышедшей в 2014 году версии 2.7:
- Инкрементальная компиляция для SuperDevMode
- Мгновенная пересборка при отсутствии изменений
- В 5-8 раз ускорены критичные по скорости операции в Map (put/get/contains/iterator)
- Улучшены методы equals/startsWith/endsWith/trim класса String
- Исключения теперь используют ленивое разворачивание стека
- Ускорены операции cast/instanceOf
- Добавлен новый тип стилей GSS — Google Style Sheets
- Новая аннотация @JsInterop, которая позволяет использовать JavaScript объекты так, как если бы они могли быть приведены к некоторому Java интерфейсу. Такой подход теперь рекомендуется вместо использования JSNI.
Во второй части Ray Cromwel рассказывает о реализации приложения Inbox — клиента для Gmail. Основное достижение разработчиков — общий код (до 60%) для 4х платформ: телефоны (Android/iOS), планшеты, веб-приложения. При этом код для веб-приложения компилируется при помощи GWT, код для Android компилируется как обычное приложение для Dalvik, код для iOS компилируется при помощи нового транслятора J2ObjC, который транслирует Java код в Objective-C. Выглядит всё это круто и довольно безумно.
Следом идёт анонс GWT 2.8, нам обещают:
- Полную! поддержку Java 8
- GSS стили по умолчанию вместо CssResource
- Ускорение реализаций коллекций
- Улучшение отладки в SuperDevMode (просмотр коллекций, читабельные идентификаторы)
Ну и в заключительной части упоминается GWT 3.0:
- Релиз будет несовместим с веткой 2.x
- Будет полностью удалён устаревший DevMode
- Добавиться поддержка DeltaJS и ServiceWorker
- Новая версия API для работы с DOM — Elemental 2.0
- Поддержка ES6
Тут отдельно стоит поговорить о проекте ServiceWorker. По сути — это отдельный WebWorker, который будет исполнять все сетевые вызовы (или вызовы сервисов). Он позволяет инкапсулировать все сетевые вызовы и обеспечить работу JS приложений в отсутствии сети при помощи синхронизации и перехвата сетевых вызовов. К сожалению, сейчас он поддерживает (и видимо так и будет) только HTTPS на стороне сервера. Подробности тут.
Elemental 2.0 — это очень хорошо. Нам обещают автоматически сгенерированный API на основе спецификаций HTML5. Вам больше не придётся писать JSNI методы, если не нашлось Java API, поскольку API будет пополняться и обновляться очень быстро.
DeltaJS активно используется в Inbox и нереально доставляет. Доставляет версии приложения на устройство, при этом они хранятся в datastore, а новые версии запрашиваются с сервера на старте. DeltaJS строит VCDIFF патч между версией клиента и последней версией и пересылает по сети только его. Клиент применяет патч (пока не понятно как это быстро), регистрирует новую версию в хранилище и запускает её.
Внезапно, был представлен фреймворк Singular. Заявлено, что это такой классный Angular для Java, без процедуры dirty-checking для объектов, со сборкой шаблонов на этапе компиляции, быстрый и пригодный для мобильных устройств. Про Singular на конференции был отдельный доклад.
Доклад про внутренности компилятора.
В докладе подробно описываются шаги компиляции и применяемые оптимизации. Все оптимизации выполняются в цикле до тех пор, пока проход цикла вносит изменения в AST дерево.
Выполняемые оптимизаций:
- Pruner — Удаляет недостижимые методы, поля и классы
- Finalizer — Помечает как final поля, методы и классы, которые не изменяются после объявления. Классы помечаются final, если у них нет наследников, методы — если нет переопределений в наследниках.
- MakeCallStatic — заменяет мономорфные вызовы методов (точно известно, какая версия метода должна быть вызвана во время исполнения) на статический вызов, если
- TypeTightener — вычисляет наиболее точный тип выражений, значений, параметров:
Object o = new Foo() превращается в Foo o = new Foo() - MethodCallTightener — заменяет вызовы методов на более специфичные, например:
List x = new ArrayList(); x.(List)add(...); будет заменён на ArrayList x = new ArrayList(); x.(ArrayList)add(...); - DeadCodeEllimination — удаляет блоки кода, если точно известно значение условия, вычисляет выражения, в которых задействованы только константы
if (false) { /*Код будет удалён*/ }
x = 2 + 3 будет заменено на x = 5 - MethodInliner — встраивает тело метода на место его вызова, для коротких методов
- EnumOrdinalizer — заменяет использование перечислений на их Ordinal значения, там где это возможно (там где нет вызовов name() и toString() )
- SameParameterValueOptimizer — если все вызовы метода передают в параметр одно и тоже значение, то будет создана версия метода без этого параметра
foo(a, 5); будет заменён на foo(a); // в метод добавится int b = 5;
Второй этап работы компилятора — нормализация, она подготовливает AST дерево к трансляции в JavaScript.
Этапы нормализации:
- Devirtualizer — заменяет вызовы к строкам, примитивам и массивам на статические методы библиотеки GWT. Прототипы JavaScript объектов не изменяются.
- CatchBlockNormalizer — заменяет множественные выражения catch для одного try блока, поскольку JavaScript поддерживает только один блок catch для блока try.
try{} catch(T e1) { /*block 1*/ } catch(S e2) { /*block 2*/ } будет заменено на
123456try {}catch(Object o) {if (o instanceof T) { /*block 1*/ }if (o instanceof S) { /*block 2*/ }throw o;}
Такое превращение не совсем легально с точки зрения Java, но лучше решения не нашлось. - LongCastNormalizer — вставляет явное приведение к типу Long, там где это требуется.
- LongEmulationNormalizer — вставляет вызовы методов для арифметики с типом Long, вся она эмулируется библиотекой GWT LongLib, поскольку JavaScript не имеет встроенной поддержки Long.
- ImplementCastsAndTypeChecks — заменяет приведения типов и проверки instanceOf на соответствующие вызовы библиотеки GWT.
- ArrayNormalizer — заменяет операции с массивами на соответствующие вызовы времени исполнения. Для соблюдения контрактов Java добавляются соответствующие проверки, выбрасывающие исключения.
- EqualityNormalizer — заменяет сравнения через == либо на вызовы JavaScript, либо на вызов библиотеки GWT.
- ReplaceGetClassOverrides — заменяет вызовы getClass() на обращение к магическому полю this.___clazz
- ResolveRuntimeTypeReferences — заменяет ссылки на типы, используемые в instanceOf на представление, соответствующее времени исполнения.
Третий этап компиляции — генерация AST дерева JavaScript.
Этапы подготовки JavaScript AST:
- FixNameClashesVisitor — выполняет переименование переменных, поскольку в JavaScript семантика блочной видимости отличается от Java.
- CreateNamesAndScopeVisitor — вычисляет области видимости JavaScript и назначает имена Java методам, полям и классам.
- RecordCrossClassCalls — вычисляет, какие статические методы могут предполагать, что инициализация класса уже завершена. Это нужно для верной расстановки статических блоков инициализации.
- GenerateJavaSciptVisitor — генерация узов AST дерева.
В конечном итоге код класса будет выглядеть как-то так:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
function clinit() { clinit = nullMethod; // код инициализации класса } function FooConstructor1() { ... } function FooConstructor2() { ... } // создать прототип и назначить в переменную _ JCHSU.defineClass(typeId, superTypeId, castableTypeMap, FooConstructor1, FooConstructor2, ...); _.method1 = function() { ... }; _.method2 = function() { ... }; _.field1 = defaultValue; _.field2 = defaultValue; var Foo_classLit = createForClass("com.foo", "Foo", superClassLiteral, typeId); |
После трансляции будет выполнен ещё один этап оптимизации для получившегося JavaScipt кода:
- JsSymbolResolver — определяет область видимость и имя для каждого идентификатора.
- JsStaticEval — вычисляет выражения над константами.
- JsNormalizer — исправляет некорректные с точки зрения JavaScript выражения. Например (a, b)++ будет заменено на (a, b++) .
- JsInliner — встраивает короткие JavaScript методы в место их вызова.
- JsUnusedFunctionRemover — удаляет неиспользуемые функции.
- JsDuplicatedFunctionRemover — заменяет функции с одинаковым строковым представлением кода на одну.
- DuplicateExecuteOnceRemover — удаляет лишние инициализации класса.
- JsNamer — изменяет (обфусцирует) идентификаторы.
- JsStackEmulator — встраивает в код дополнительную информацию для лучшего представления стека вызовов.
В докладе описано намного больше того, что я могу осветить в этой статье. Всем советую посмотреть сам доклад.
Как они там инкрементальную компиляцию таки сделали.
Инкрементальная компиляция это такой больной вопрос, решение которого мы ждали больше 4х лет. И вот тут говорят, что наконец-то родили.
Начнём с того, что GWT выполняет глобальные оптимизации и, казалось, инкрементальная компиляция совсем невозможна.
Некоторые детали по реализации инкрементальной компиляции:
- Во время полной компиляции создаются дополнительные индексы и карты зависимостей.
- Во время перекомпиляции в соответствии с индексами и зависимостями обновляется сгенерированный JavaScript код.
- Почти все фазы компиляции, включая оптимизацию и нормализацию могут исполняться инкрементально, за исключением генерации кода, SourceMaps и построения индексов.
- Инкрементальная компиляция работает только с SuperDevMode, компиляция проекта остаётся полной при запуске из ant/maven/gradle.
В докладе освещены некоторые планы и идеи:
- Избавится от «взрыва» интернационализации: N языков x M браузеров.
- Замена SuperDevMode на сервер непрерывной компиляции (по типу FSC или Zinc).
- Более читабельный вариант JavaScript в режиме без обфускации.
- Кэширование приложения и фоновые обновления кода.
В целом конечно серебряной пули не получилось, остаётся надеяться что инкрементальная компиляция в SuperDevMode будет пригодна к использованию.
Заключение
В целом конференция вышла очень насыщенной по темам и докладчикам, GWT живее всех живых и рвётся ввысь!