Продвинутые оптимизации
Продвинутые оптимизации google closure compiler включаются опцией --compilation_level ADVANCED_OPTIMIZATIONS.
Слово "продвинутые" (advanced) здесь, пожалуй, не совсем подходит. Кардинальное отличие этих оптимизаций от обычных (simple) - в том, что они небезопасны.
Чтобы ими пользоваться - надо уметь это делать.
Если в безопасном режиме переименовываются только переменные внутри функций, то в "продвинутом" - на более короткие имена заменяется все.
Если в безопасном режиме удаляется недостижимый код после return , то в продвинутом - вообще весь код, который не вызывается в явном виде.
Например, если запустить продвинутую оптимизацию на таком коде:
// my.js
function test(node) {
node.innerHTML = "newValue"
}
Строка запуска компилятора:
java -jar compiler.jar --compilation_level ADVANCED_OPTIMIZATIONS --js my.js
...То результат будет - пустой файл. Google Closure Compiler увидит, что функция test не используется и с чистой совестью вырежет ее.
А в следующем скрипте функция сохранится:
function test(n) {
alert("this is my test number "+n)
}
test(1)
test(2)
После сжатия:
function a(b) {
alert("this is my test number " + b)
}
a(1);
a(2);
Здесь в скрипте присутствует явный вызов функции, поэтому она сохранилась.
Конечно, есть способы, чтобы сохранить функции, вызов которых происходит вне скрипта, и мы их обязательно рассмотрим.
Режим "Advanced" не предусматривает сохранения глобальных переменных, наоборот - он переименовывает (и удаляет) все символы, кроме некоторых зарезервированных.
Иначе говоря, продвинутый режим (ADVANCED_OPTIMIZATIONS), в отличие от простого (SIMPLE_OPTIMIZATIONS - по умолчанию), вообще не заботится о доступности кода извне и сохранении ссылочной целостности относительно внешних скриптов.
Единственное, что он гарантирует - это внутреннюю ссылочную целостность, и то - при соблюдении ряда условий и практик программирования.
Собственно, за счет такого агрессивного подхода и достигается дополнительный эффект оптимизации и сжатия скриптов.
То есть, продвинутый режим - это не просто "улучшенный обычный", а принципиально другой, небезопасный и обфусцирующий подход к сжатию.
В этом коренное отличие Google Closure Compiler от компиляторов типа YUI Compressor и ShrinkSafe.
Для того, чтобы эффективно сжимать Google Closure Compiler в продвинутом режиме, нужно понимать, что и как он делает. Это мы сейчас обсудим.
Чтобы использовать сжатый скрипт, мы должны иметь возможность вызывать функции под теми именами, которые им дали.
То есть, перед нами стоит задача сохранения ссылочной целостности, которая заключается в том, чтобы обеспечить доступность нужных функций для обращений по исходному имени извне скрипта.
Существует два способа сохранения внешней ссылочной целостности для выборанных символов: экстерны и экспорты. Мы в подробностях рассмотрим оба, начнем с экстернов, т.к. они проще.
Экстерн - символ, который числится в специальном списке компилятора. Он должен быть определен вне скрипта, в файле экстернов.
Компилятор никогда не переименовывает экстерны. Например:
document.onkeyup = function(event) { alert(event.type) }
После продвинутого сжатия:
document.onkeyup = function(a) { alert(a.type) }
Как видите, переименованной оказалась только переменная event . Такое переименование заведомо безопасно, т.к. event - локальная переменная.
Почему компилятор не тронул остального? Попробуем другой вариант:
document.blabla = function(event) { alert(event.megaProperty) }
После компиляции:
document.a = function(a) { alert(a.b) }
Теперь компилятор переименовал и blabla и megaProperty .
Дело в том, что названия, использованные до этого, были во внутреннем списке экстернов компилятора. Этот список охватывает основные объекты браузеров и находится (под именем externs.zip ) в корне архива compiler.jar или в директории externs в svn.
Компилятор никогда не переименовывает символы из списка экстернов, кроме случая, когда так названа локальная переменная.
Еще один пример:
window.resetNode = function(node) {
var innerHTML = "test"
node.innerHTML = innerHTML
}
На выходе:
window.a = function(a) {
a.innerHTML = "test"
};
Как видите, внутренняя переменная innerHTML не просто переименована - она заинлайнена (заменена на значение). Так как эта переменная локальна, то любые действия внутри функции с ней безопасны.
А свойство innerHTML не тронуто, как и объект window - так как они в списке экстернов и не являются локальными переменными.
Это приводит к следующему побочному эффекту. Иногда свойства, которые следовало бы сжать, не сжимаются. Например:
window['User'] = function(name, type, age) {
this.name = name
this.type = type
this.age = age
}
После сжатия:
window.User = function(a, b, c) {
this.name = a;
this.type = b;
this.a = c
};
Как видно, свойство age сжалось, а name и type - нет. Это побочный эффект экстернов: name и type - в списке объектов браузера, и компилятор просто старается не наломать дров.
Поэтому отметим еще одно полезное правило оптимизации:
Названия своих свойств не должны совпадать с зарезервированными словами (экстернами). Тогда они будут хорошо сжиматься.
Для задания списка экстернов их достаточно перечислить в файле и указать этот файл флагом --externs <файл экстернов.js> .
При перечислении объектов в файле экстернов - объявляйте их и перечисляйте свойства. Все эти объявления никуда не идут, они используются только для создания списка, который обрабатывается компилятором.
Пример: myexterns.js
var dojo = {}
dojo._scopeMap;
Использование такого файла при сжатии (опция --externs myexterns.js ) приведет к тому, что все обращения к символам dojo и к dojo._scopeMap будут не сжаты, а оставлены "как есть".
Кстати, бывает удобно разрешить переименование для некоторых экстернов. Это делается через присваивание их промежуточной переменной-неэкстерну.
В основном, это работает с глобальными переменными и статическими методами.
Например, мы хотим сжать document в следующем фрагменте:
function doSomething() {
var elem = document.createElement("div")
document.appendChild(elem)
}
Для этого присвоим document промежуточной переменной doc :
var doc = document
function doSomething() {
var elem = doc.createElement("div")
doc.appendChild(elem)
}
Компилятор не имеет права сжимать document , а вот все обращения к doc будут сжаты, т.к имя переменной doc будет заменено на более короткое. В результате, в сжатом коде document появится лишь один раз, а все остальные обращения (через doc ) будут сжаты.
Экспорт - программный ход, основанный на следующем правиле поведения компилятора.
Компилятор заменяет обращения к свойствам через кавычки на точку, и при этом не трогает название свойства.
Например, window['User'] превратится в window.User , но не дальше.
Таким образом можно экспортировать нужные функции и объекты:
function SayWidget(elem) {
this.elem = elem
this.init()
}
window['SayWidget'] = SayWidget
На выходе:
function a(b) {
this.a = b;
this.b()
}
window.SayWidget = a;
Обратим внимание - сама функция SayWidget была переименована в a . Но затем - экспортирована как window.SayWidget , и таким образом доступна внешним скриптам.
Добавим пару методов в прототип:
function SayWidget(elem) {
this.elem = elem
this.init()
}
SayWidget.prototype = {
init: function() {
this.elem.style.display = 'none'
},
setSayHandler: function() {
this.elem.onclick = function() {
alert("hi")
}
}
}
window['SayWidget'] = SayWidget
SayWidget.prototype['setSayHandler'] = SayWidget.prototype.setSayHandler
После сжатия:
function a(b) {
this.a = b;
this.b()
}
a.prototype = {b:function() {
this.a.style.display = "none"
}, c:function() {
this.a.onclick = function() {
alert("hi")
}
}};
window.SayWidget = a;
a.prototype.setSayHandler = a.prototype.c;
Благодаря строке
SayWidget.prototype['setSayHandler'] = SayWidget.prototype.setSayHandler
метод setSayHandler экспортирован и доступен для внешнего вызова.
Сама строка экспорта выглядит довольно глупо. По виду - присваиваем свойство самому себе.
Но логика сжатия google closure compiler работает так, что такая конструкция является экспортом. Справа переименование свойства setSayHandler происходит, а слева - нет.
Кстати, google closure compiler больше любит другую организацию свойств объекта при ООП, но об этом - чуть позже.
Планируйте жизнь после сжатия.
Рассмотрим следующий код:
window['Animal'] = function() {
this.blabla = 1
this['blabla'] = 2
}
После сжатия:
window.Animal = function() {
this.a = 1;
this.blabla = 2
};
Как видите, первое обращение к свойству blabla сжалось, а второе (как и все аналогичные) - преобразовалось в синтаксис через точку.
В результате получили некорректное поведение кода.
Так что, используя продвинутый режим оптимизации, планируйте поведение кода после сжатия, особенно при обращении к свойствам через квадратные скобки: object[methodName] .
В библиотеке Google Closure Library для экспорта есть специальная функция goog.exportSymbol . Вызывается так:
goog.exportSymbol('my.SayWidget', SayWidget)
Эта функция по сути работает также, как и рассмотренная выше строка с присвоением свойства, но при необходимости создает нужные объекты.
Она аналогична коду:
window['my'] = window['my'] || {}
window['my']['SayWidget'] = SayWidget
То есть, если путь к объекту не существует - exportSymbol создаст нужные пустые объекты.
Функция goog.exportProperty экспортирует свойство объекта:
goog.exportProperty(SayWidget.prototype, 'setSayHandler', SayWidget.prototype.setSayHandler)
Строка выше - то же самое, что и:
SayWidget.prototype['setSayHandler'] = SayWidget.prototype.setSayHandler
Зачем нужны exportSymbol и exportProperty ?
Действительно, зачем, если все можно сделать простым присваиванием?
Основная цель этих функций - во взаимодействии с Google Closure Compiler. Они дают информацию компилятору об экспортах, которую он может использовать.
Например, есть недокументированная внутренняя опция externExportsPath , которая генерирует из всех экспортов файл экстернов. Таким образом можно распространять откомпилированный javascript-файл как внешнюю библиотеку, с файлом экстернов для удобного внешнего связывания.
Кроме того, экспорт через эти функциями удобен и нагляден.
Если вы используете продвинутый режим оптимизации, то можно подключить файл base.js из Google Closure Library просто чтобы работали эти функции.
Оптимизатор при продвинутом сжатии вырежет из base.js (почти) все лишнее, кроме функций экспорта, так что overhead будет минимальным.
Между экспортом и экстерном есть кое-что общее. И то и другое дает возможность доступа к объектам под исходным именем, до переименования.
Но, в остальном, это совершенно разные вещи.
Экстерн |
Экспорт |
Служит для тотального запрета на переименование всех обращений к свойству.
Задумано для сохранения обращений к стандартным объектам браузера, внешним библиотекам. |
Служит для открытия доступа к свойству извне под указанным именем.
Задумано для открытия внешнего интерфейса к сжатому скрипту. |
Работает со свойством, объявленным вне скрипта.
Вы не можете объявить новое свойство в скрипте и сделать его экстерном. |
Создает ссылку на свойство, объявленное в скрипте. |
Если window - экстерн, то все обращения к window в скрипте останутся как есть. |
Если user экспортируется, то создается только одна ссылка под полным именем, а все остальные обращения будут сокращены. |
Еще один важный принцип работы продвинутого режима заключается в инлайнинге констант и функций.
Инлайнинг - это замена всех вызовов функции на код этой функции и, аналогично, замена всех обращений к константе на ее значение.
Например:
function test(n) {
alert("this is my test number "+n)
}
test(1)
После сжатия в продвинутом режиме:
alert("this is my test number 1");
Функция test не является экстерном и не экспортируема, поэтому нет никаких требований к тому, чтобы ее сохранять.
Поэтому компилятор заменил вызов test(1) на тело функции. Кроме того, конкретно в этом примере, произошло объединение констант:"this is my test number"+1 .
Константы инлайнятся аналогично функциям.
Например:
var MULTIPLER = 10
function test(n) {
alert("this is my test number: "+n*MULTIPLER)
}
test(1)
После сжатия:
alert("this is my test number: 10");
Важно понимать, что инлайнинг происходит только тогда, когда он приводит к уменьшению размера кода.
Например:
function test(n) {
alert("this is my test number "+n)
}
test(1)
После сжатия:
function a(b) {
alert("this is my test number " + b)
}
a(1);
a(2);
Здесь компилятор посчитал потенциальную выгоду от замены функции на ее содержание и увидел, что инлайнинг увеличит размер кода. Поэтому функция просто переименована.
Если в обычном режиме сжатия стиль программирования - "как можно больше локальных переменных, как можно больше внутренних функций", то при использовании продвинутого режима все с точностью наоборот.
Функции надо как можно лучше отделять друг от друга. Поток выполнения должен быть как можно более понятный компилятору.
Например, для обычного режима сжатия типично такое объявление библиотеки:
(function(){
// локальная переменная для переименования компрессором
var document = window.document
// пространство имен и локальная перменная для него
var MyFramework = window.MyFramework = { }
// нужная функция
MyFramework.used = function() {
document.createElement('div')
}
// ненужная функция
MyFramework.unused = function() {
alert("unused")
}
})();
// использование
MyFramework.used()
Оно удобно, т.к. позволяет внутри внешней функции объявлять любые методы, временные переменные, делать загрузочные действия и т.п.
Такой стиль используется в большинстве javascript-библиотек на момент написания этой статьи.
Но теперь посмотрим на результат компиляции. В обычном режиме:
(function() {
var b = window.document, a = window.MyFramework = {};
a.used = function() {
b.createElement("div")
};
a.unused = function() {
alert("unused")
}
})();
MyFramework.used();
Это - примерно то, что мы ожидали от безопасного режима. Ненужный метод остался, локальные свойства переименованы.
А теперь продвинутый режим:
(function() {
var b = window.document, a = window.b = {};
a.a = function() {
b.createElement("div")
};
a.c = function() {
alert("unused")
}
})();
MyFramework.a();
Полученный код не то, что недосжат: оставлен заведомо ненужный метод, он просто-напросто перестал работать после сжатия. Google Closure Compiler не разобрался, какой символ куда идет, и некорректно сжал код.
Это похоже на недоработку создателей компилятора, но нам-то хочется не просто чтобы работало. Хочется удаление недостижимого кода и другие фичи.
Поэтому более эффективен будет простой стиль объявления, без внешней функции:
// переименовать document в doc
// для лучшего сжатия
var doc = window.document
// пространство имен
var MyFramework = { }
// нужная функция
MyFramework.used = function() {
doc.createElement('div')
}
// ненужная функция
MyFramework.unused = function() {
alert("unused")
}
// использование
MyFramework.used()
Компиляция этого кода в обычном режиме не даст особых сюрпризов. А вот продвинутый режим, напротив, очень удивит:
var a = window.document;
(function() {
a.createElement("div")
})();
Google Closure Compiler не только разобрался в структуре и удалил лишний метод - он заинлайнил функции, чтобы итоговый размер получился минимальным.
Как говорится, преимущества налицо.
Продвинутый режим оптимизации сжимает все свойства и методы, кроме находящихся в списке экстернов.
Это является принципиальным отличием и улучшением, по сравнению с другими упаковщиками.
Кроме того, отказ от сохранения внешней ссылочной целостности позволяет заинлайнить константы и функции, вырезать ненужный код и произвести ряд других полезных оптимизаций.
Вырезание невызываемого (и не экспортированного) кода особенно эффективно с большой библиотекой javascript, т.к. с высокой точностью, на уровне переменной/функции, выбрасывает неиспользуемые возможности.
Конечно, для того, чтобы это вырезание работало, библиотека должна быть написана в правильном стиле, с пониманием процесса оптимизации. Примером такой библиотеки является Google Closure Library.
В крупных проектах, оно отлично работает в сочетании системой зависимостей.
Для того, чтобы сделать символ доступным снаружи, используется экспорт.
Для того, чтобы не переименовывать обращение к внешнему символу, он помещается в список экстернов.
Вообще, судя по виду javascript'ов на сайтах, созданных Google, сам Google жмет свои скрипты именно продвинутым режимом оптимизации.
|
На все this, используемые для доступа к свойствам, компилятор выдаёт предупреждения:
JSC_USED_GLOBAL_THIS: dangerous use of the global this object at line ... character ...
Это страшно?
Выдаёт такое предупреждение на
в этом примере из статьи:
если ваш скрипт юзает какую-либо библиотеку, то не надо отдельно создавать файл екстернов
просто укажите файл библиотеки
как объявить екстерны внутри кода (не через внешний файл) ?
Спасибо за труд по написанию данного цикла статей! Крутая штука этот GCC - действительно, по сравнению с YUI Compressor`ом - как автомобиль в сравнении с великом!
Вот только есть небольшое нарекание...
Была задача объявить переменную в глобальном контексте так, что бы она стала доступной извне. когда пишу:
GCC её сжимает. Когда прописываю в экстерны
GCC выдаёт ошибку: "ERROR - Variable myVariable first declared in [путь к файлу экстернов]".
Сделал пока вместо декларации объявление свойства объекта window:
, но в итоге в сжатом варианте появляется лишнее "window.myVariable" вместо выглядящего более коротко "var myVariable", что слегка напрягает...
Так же напрягает похожая проблема с объектом event, который доступен в глобальном контексте в IE при наступлении любого события - его тоже приходится писать с приставкой "window." - только тогда GCC его понимает и не сжимает. В итоге потом в скомпрессированном скрипте приходится руками выискивать и вычищать эти приставочки "window.", поскольку regexp`ам такое деликатное дело доверять пока боюсь...
Компиля конечно хорош, но менят стиль кодирования к примеру в уже существующем большом проекте довольно сложно из-за такого подхода.
Почему бы не сделать какие нибудь директивы компиляции? Что-то типа такого
#Ifdef
#endif
Чтобы для определённых участков кода, не делать такое сжатие.
function my(elem){
return 0;
}
window['my']=my
На выходе:
window.my=function(){return 0};
Грамотно оптимизирует. Спасибо.
если ваш скрипт юзает какую-либо библиотеку, то не надо отдельно создавать файл екстернов. Так же напрягает похожая проблема с объектом event, который доступен в глобальном контексте в IE при наступлении любого события - его тоже приходится писать с приставкой "window." - только тогда GCC его понимает и не сжимает. В итоге потом в скомпрессированном скрипте приходится руками выискивать и вычищать эти приставочки "window."
slitherio
All the contents you mentioned in post is too good and can be very useful. I will keep it in mind, thanks for sharing the information keep updating, looking forward for more posts.Thanks https://www.jessicaclarktherapyservices.com/
I had a great time reading your essay and found it to be very useful. If you have any extra time, you are welcome to join me in playing retro bowl
This is a great inspiring article.I am pretty much pleased with your good work.You put really very helpful information...
Thank you for producing such a fascinating essay on this subject. This has sparked a lot of thought in me, and I'm looking forward to reading more. waffle game
Sex est une application de rencontre relativement récente, conçue pour les personnes ayant une expérience trans et celles qui souhaitent sortir avec elles. Les profils sont simples et faciles à utiliser
Rub! I've been looking for this code for a long time for my programming work, I'm glad I found it here. When I add to my file everything goes well. wordle - wordle nyt
This function essentially works the same way as the property assignment line discussed above, but creates the necessary objects if necessary. www.treeserviceofaustin.com/tree-trimming
Sex Bremerhaven ist der Ort, an dem man nach Sex sucht. Menschen suchen Sex an verschiedenen Orten. Manche gehen in Bars und Clubs, andere nehmen den ersten Kontakt bei anderen gesellschaftlichen Anlässen auf. Aber der beste Ort, um nach Fickfreunden zu suchen, ist online.
Your article was thought-provoking coreball and sparked a lot of new ideas for me.
Tienerslet vereist een minimum aan informatie - leeftijd, woonplaats, geslacht en e-mailadres, evenals uw seksuele voorkeur
Many people play it every day, and when I play pizaa tower with my friends, I want to follow suit.
Собственно, за счет такого агрессивного подхода и достигается дополнительный эффект оптимизации и сжатия скриптов.
White Settlement drywall company
Choices in word searches It's much simpler and quicker to make a crossword if the words don't share any letters. word puzzles
Nice paste. I learn something new and challenging every day on blogs I stumble across. It's always helpful to read articles by other authors and use some of the content on other sites.먹튀검증
If the Moto X3M game provides a great experience, please give us a 5-star rating so that the game can be shared with more gamers.
Благодаря вашему рассказу об оптимизации компилятора Google, его дополнительных параметрах и функциях я узнал много новых полезных знаний. geometry dash
I'm hooked on Retro Bowl Unblocked movie, the challenge of managing my team keeps me coming back.
What are the potential risks associated with using the ADVANCED_OPTIMIZATIONS option in the Google Closure Compiler? Since the Block Blast document mentions these optimizations are unsafe, what are the trade-offs in terms of potential bugs or unexpected behavior?
Mmm.. it is so appreciable to be here in your report or announcement whatever fnaf, I believe I should also handle strongly for my site wish I play some more healthy activities updated busy in your place. retro games
Community involvement fosters a sense of accountability, as decisions are made openly and with broad input. Visit Lucky Block Crypto Casino
Отправить комментарий
Приветствуются комментарии:Для остальных вопросов и обсуждений есть форум.