Использование компилятора Closure с Webpack + Typescript через Tsickle

В AppMonet мы ежедневно обслуживаем сотни терабайт JavaScript на десятках миллионов устройств. Наш SDK развернут в сотнях лучших приложений; иногда в нескольких приложениях на одних и тех же телефонах. Крайне важно, чтобы мы минимизировали влияние нашего SDK - реклама должна потреблять как можно меньше ресурсов.

Раздутие Javascript поражает открытую сеть; средняя веб-страница содержит 350 КБ сжатого сжатым кодом Javascript (Эдди Османи потрясающе рассказал о том, почему размер файла JS настолько проблематичен на мобильных устройствах). Поскольку AppMonet предназначен только для мобильных устройств, мы особенно чувствительны к размеру наших файлов Javascript - помимо более низкой скорости сети, мы также работаем на устройствах с ограниченными ресурсами с медленным временем выполнения JavaScript (предварительная версия Android, iOS UIWebview).

Компилятор закрытия ADVANCED_OPTIMIZATIONS

К счастью, эта проблема не только у нас! Помимо terser (fka uglify-es) и babel-minify, у Google есть компилятор закрытия, который может выполнять множество действительно впечатляющих оптимизаций. Closure-compiler включает в себя устранение мертвого кода, некоторые оптимизации времени выполнения, подобные prepack, транспиляцию класса es5, сглаживание / полифиллы во время выполнения и минификацию / искажение.

Компилятор закрытия, который раньше требовал запуска jar-файла (обернутого модулем узла) - теперь он также доступен в виде собственной бинарной библиотеки или библиотеки на чистом javascript, что дает гораздо лучшее время компиляции.

Во многих случаях компилятор закрытия аналогичен по производительности uglifyJS / terser, особенно если вы можете изменять свойства в uglifyjs. Однако есть специальный режим ADVANCED_OPTIMIZATIONS, который на самом деле является святым Граалем оптимизации javascript. Из документов компиляции закрытия:

Уровень ADVANCED_OPTIMIZATIONS сжимает JavaScript намного больше, чем это возможно с другими инструментами.



К сожалению, использовать это не так просто - если вы прочитаете документацию по компилятору закрытия о ADVANCED_OPTIMIZATIONS, вы увидите, что вам не только нужно тщательно объявить все свои внешние (все, что нельзя изменить до неузнаваемости) , но также аннотируйте весь код комментариями, подобными jsdoc, для достижения максимальной производительности.

Эти комментарии представляют собой очень ориентированный на es3 способ выражения типов:

/**
 * A shape.
 * @interface
 */
function Shape() {};
Shape.prototype.draw = function() {};

/**
 * @constructor
 * @implements {Shape}
 */
function Square() {};
Square.prototype.draw = function() {
  ...
};

Но если мы используем машинописный текст, разве наш код уже не содержит всю эту информацию о типах и видимости? Если бы мы могли преобразовать машинописный текст в этот аннотированный javascript, мы бы смогли использовать ADVANCED_OPTIMIZATIONS без каких-либо изменений в нашем коде!

К счастью, команда angular разработала инструмент под названием tsickle, который делает именно это! Из tsickle github:

* inserts closure-compatible JSDoc annotations on functions/classes/etc
* converts ES6 modules into goog.module modules
* generates externs.js from TypeScript d.ts (and declare, see below)
* declares types for class member variables
* translates export * from ... into a form Closure accepts
* converts TypeScript enums into a form Closure accepts
* reprocesses all jsdoc to strip Closure-invalid tags

Потрясающие! К сожалению, окунь не совсем мейнстрим:

Мы уже используем tsickle в Google для минимизации наших приложений (в том числе использующих Angular), но у нас меньше опыта использования tsickle с различными сборками JavaScript, которые можно увидеть за пределами Google.

Как мы можем использовать tsickle для преобразования машинописного текста в наших существующих проектах на основе веб-пакетов (и / или) накопительных пакетов? В файле readme упоминается, что это прямая замена для tsc, но означает ли это, что мы можем использовать его напрямую с ts-loader? К счастью, Inseok Lee предоставил пример загрузчика веб-пакетов в качестве содержания github.

Этот загрузчик использует компилятор typescript для загрузки и обработки файла tsconfig.json, а затем запускает tsickle.emitWithTsickle для преобразования машинописного текста. В конфигурации нашего веб-пакета мы заменим ts-loader этим localtsickle-loader. Это создаст аннотированный источник javascript:

На самом деле вы не увидите этот аннотированный JS - загрузчик передает его остальной части процесса сборки веб-пакета (например, следующему загрузчику в цепочке).

Это означает, что мы можем передать аннотированный javascript компилятор закрытия на этапе оптимизации веб-пакета:

Обратите внимание, что мы используем ModuleConcatenationPlugin; это просто для того, чтобы явно включить встряхивание дерева: компилятор закрытия будет выполнять расширенное устранение мертвого кода, и если вы выводите из него модули ES5, webpack может уменьшить накладные расходы на связывание за счет встряхивания дерева.

Чтобы использовать эту настройку в производстве, мы хотели добавить несколько тестов в этот загрузчик и охватить некоторые крайние случаи при компиляции tsickle:

  • обработка нескольких типов модулей машинописного текста (вывод es5 оптимален для встряхивания дерева)
  • разрешить передачу параметров для указания tsconfig.json и местоположения файла externs.js
  • исправить некоторые проблемы с выводом tsickle (вызванные его использованием в загрузчике веб-пакетов)

Мы открыли исходный код нашей реализации tsickle-loader:



Используя google-closure-compiler и относительно новыйclosure-webpack-plugin, мы можем создать очень оптимизированный javascript. Учитывая этот произвольный пример:

closure-compiler выводит 1333 байта - по умолчанию терсер webpack производит 1466 байтов. Эти числа бесполезны, поскольку большая часть этих выходных данных - это API сборщика веб-пакетов. Однако вы можете видеть, что выходные данные компилятора закрытия могут изменять свойства класса таким образом, чтобы уменьшить размер пакета по мере роста сложности кода.

Вот улучшенный вывод компилятора замыкания для облегчения чтения:

Вы можете видеть, что компилятор tsickle + closure правильно сохранил структуру интерфейса PublicAPI.

Когда вы комбинируете это с машинописным шрифтом module: "es2015" и используете встряхивание дерева webpack (или портируете tsickle-loader для объединения), вы действительно можете уменьшить влияние использования служебных библиотек или стороннего кода в вашей библиотеке - широко повторяющийся импорт - это хорошо. искаженный, мертвый код исключен, а оставшийся код сильно оптимизирован.

Компилятор замыкания также будет включать все необходимые полифилы на основе вашего фактического использования кода и целевого языка вывода: методы массива es5 включаются индивидуально, поэтому вы не отправляете прокладку для findIndex, если вы когда-либо используете только find.

В некоторых тестах мы видим меньшие размеры пакетов по мере увеличения размера приложения:

Сокращение всего 10 КБ JavaScript на 100 миллионов сеансов SDK снизит пропускную способность на 1 ТБ. Что еще более важно, это поможет сократить использование ресурсов миллионами телефонов и улучшить взаимодействие с пользователем.