При сжатии javascript-кода минификатор делает две основные вещи.
Есть несколько несложных приемов программирования, которые могут увеличить сжимаемость JS-кода.
function flyToMoon(moon) {
var spaceShip = new SpaceShip()
spaceShip.fly(moon.getDistance())
}
</div>
После минификации станет:
function flyToMoon(A) {
var B = new SpaceShip()
B.fly(A.getDistance())
}
Заведомо локальные переменные moon
, spaceShip
могут быть безопасно заменены на более короткие A,B
. Минификатор использует патченный открытый интерпретатор javascript, написанный на java: Rhino, он разбирает код, анализирует и собирает обратно с заменой, поэтому все делается корректно.
С другой стороны, название функции в этом скрипте - глобальная переменная, оно не сжимается, чтобы сохранить возможность обращения к функции.
По тем же причинам не сжимается вызов SpaceShip
и методы fly
, getDistance()
.
Итак, сделаем какой-нибудь более реальный скрипт и напустим на него YUI Compressor.
function SpaceShip(fuel) {
this.fuel = fuel
this.inflight = false
this.showWarning = function(message) {
var warningBox = document.createElement('div')
with (warningBox) {
innerHTML = message
className = 'warningBox'
}
document.body.appendChild(warningBox)
}
this.showInfo = function(message) {
var messageBox = document.createElement('div')
messageBox.innerHTML = message
document.body.appendChild(messageBox)
}
this.fly = function(distance) {
if (distance<this.fuel) {
this.showWarning("Мало топлива!")
} else {
this.inflight = true
this.showInfo("Взлетаем")
}
}
}
function flyToMoon() {
var spaceShip = new SpaceShip()
spaceShip.fly(1000)
}
flyToMoon()
К сожалению, в YUI Compressor нельзя отключить убивание переводов строки, поэтому получилось такое:
function SpaceShip(fuel){this.fuel=fuel;
this.inflight=false;this.showWarning=function(message){
var warningBox=document.createElement("div");
with(warningBox){innerHTML=message;
className="warningBox"
}document.body.appendChild(warningBox)
};this.showInfo=function(message){
var messageBox=document.createElement("div");
messageBox.innerHTML=message;
document.body.appendChild(messageBox)
};this.fly=function(distance){
if(distance<this.fuel){this.showWarning("Мало топлива!")
}else{this.inflight=true;
this.showInfo("Взлетаем")
}}}function flyToMoon(){var A=new SpaceShip();
A.fly(1000)};flyToMoon()
Если посмотреть внимательно - видно, что локальные переменные вообще не сжались внутри SpaceShip
, а сжались только в функции flyToMoon
.
ShrinkSafe замечательно сжимает все, что только может.
function SpaceShip(_1){
this.fuel=_1;
this.inflight=false;
this.showWarning=function(_3){
var _4=document.createElement("div");
with(_4){
innerHTML=_3
className="warningBox";
}
document.body.appendChild(_4);
};
this.showInfo=function(_5){
var _6=document.createElement("div");
_6.innerHTML=_5;
document.body.appendChild(_6);
};
this.fly=function(_7){
if(_7<this.fuel){
this.showWarning("Мало топлива!");
}else{
this.inflight=true;
this.showInfo("Взлетаем");
}
};
};
function flyToMoon(){
var _8=new SpaceShip();
_8.fly(1000);
};
flyToMoon();
Все локальные переменные заменены на более короткие _варианты.
Почему YUI не сжал, а ShrinkSafe справился?
Дело в конструкции with() { ... }
в методе showWarning
. Эти два компрессора по-разному к ней относятся.
ShrinkSafe игнорирует нелокальные названия переменных внутри with
:
// Было
with(val) {
prop = val
}
// Стало
with(_1) {
prop = _1
}
К сожалению, в последней версии ShrinkSafe есть баг: если переменная prop объявлена локально, то она заменяется:
// Было
var prop
with(val) {
prop = val
}
// Стало
var _1
with(_2) {
_1 = _2
}
Например, если val = { prop : 5 }
, то сжатая таким образом конструкция with
сработает неверно из-за сжатия prop
до _1
.
Впрочем, никогда не слышал, чтобы кто-то на такой баг реально напоролся. Видимо, люди локальные переменные не объявляют одноименные со свойствами аргумента with
, чтобы код понятнее был.
Внутри оператора with(obj)
никогда нельзя точно сказать: будет ли переменная взята из obj
или из внешней области видимости.
Поэтому никакие переменные внутри with сжимать нельзя.
А раз так - получается, что локальные переменные с именами, упомянутыми внутри with
тоже сжимать нельзя.
YUI Compressor почему-то (почему? есть идеи?) пошел еще дальше: он не минифицирует вообще никаких локальных переменных даже в соседних функциях.
Может быть, это баг (версия 2.3.5), а может - фича, не знаю. Будут идеи - пишите в комментариях. Например, локальные переменные функции fly
вполне можно было минифицировать.
Вывод:
YUI категорически не любит with.
ShrinkSafe любит, но с багофичей.
Если заменить функцию showWarning
на вариант без with
, то YUI сожмет код без проблем:
// вариант без with
this.showWarning = function(message) {
var warningBox = document.createElement('div')
warningBox.innerHTML = message
warningBox.className = 'warningBox'
document.body.appendChild(warningBox)
}
Результат сжатия YUI без with:
function SpaceShip(A){this.fuel=A;
this.inflight=false;this.showWarning=function(B){var C=document.createElement("div");
C.innerHTML=B;C.className="warningBox";
document.body.appendChild(C)
};this.showInfo=function(B){var C=document.createElement("div");
C.innerHTML=B;document.body.appendChild(C)
};this.fly=function(B){if(B<this.fuel){this.showWarning("Мало топлива!")
}else{this.inflight=true;
this.showInfo("Взлетаем")
}}}function flyToMoon(){var A=new SpaceShip();
A.fly(1000)}
В примере не сжались вызовы к объекту document
.
Для того, чтобы сжатие сработало, надо заменить обращение к глобальной переменной document
вызовом локальной функции.
Например, вот так:
function SpaceShip(fuel) {
/* сокращенные локальные вызовы */
var doc = document
var createElement = function(str) {
return doc.createElement(str)
}
var appendChild = function(elem) {
doc.body.appendChild(elem)
}
this.fuel = fuel
this.inflight = false
this.showWarning = function(message) {
var warningBox = createElement('div')
warningBox.innerHTML = message
warningBox.className = 'warningBox'
appendChild(warningBox)
}
this.showInfo = function(message) {
var messageBox = createElement('div')
messageBox.innerHTML = message
appendChild(messageBox)
}
this.fly = function(distance) {
if (distance<this.fuel) {
this.showWarning("Мало топлива!")
} else {
this.inflight = true
this.showInfo("Взлетаем")
}
}
}
Обращение к document
осталось в одном месте, что тут же улучшает сжатие:
(Здесь и дальше для сжатия использован ShrinkSafe, т.к он оставляет переводы строки.
Результаты YUI - по сути, такие же)
function SpaceShip(_1){
var _2=document;
var _3=function(_4){
return _2.createElement(_4);
};
var _5=function(_6){
_2.body.appendChild(_6);
};
this.fuel=_1;
this.inflight=false;
this.showWarning=function(_7){
var _8=_3("div");
_8.innerHTML=_7;
_8.className="warningBox";
_5(_8);
};
this.showInfo=function(_9){
var _a=_3("div");
_a.innerHTML=_9;
_5(_a);
};
this.fly=function(_b){
if(_b<this.fuel){
this.showWarning("Мало топлива!");
}else{
this.inflight=true;
this.showInfo("Взлетаем");
}
};
Как правило, в интерфейсах достаточно много обращений к document
, и все они длинные, поэтому этот подход может уменьшить сразу код эдак на 10-20%.
Функции объявлены через var
, а не function
:
var createElement = function(str) { // (1)
return doc.createElement(str)
}
// а не
function createElement(str) { // (2)
return doc.createElement(str)
}
Это нужно для ShrinkSafe, который сжимает только определения (1). Для YUI - без разницы, как объявлять функцию, сожмет и так и так.
Существуют различные способы объявления объектов.
Один из них - фабрика объектов, когда для создания не используется оператор new
.
Общая схема фабрики объектов:
function object() {
var private = 1 // приватная переменная для будущего объекта
return { // создаем объект прямым объявлением в виде { ... }
increment: function(arg) { // открытое свойство объекта
arg += private // доступ к приватной переменной
return arg
}
}
}
// вызов не new object(), а просто
var obj = object()
Прелесть тут состоит в том, что приватные переменные являются локальными, и поэтому могут быть сжаты. Кроме того, убираются лишние this
.
function SpaceShip(fuel) {
var doc = document
var createElement = function(str) {
return doc.createElement(str)
}
var appendChild = function(elem) {
doc.body.appendChild(elem)
}
var inflight = false
var showWarning = function(message) {
var warningBox = createElement('div')
warningBox.innerHTML = message
warningBox.className = 'warningBox'
appendChild(warningBox)
}
var showInfo = function(message) {
var messageBox = createElement('div')
messageBox.innerHTML = message
appendChild(messageBox)
}
return {
fly: function(distance) {
if (distance<this.fuel) {
showWarning("Мало топлива!")
} else {
inflight = true
showInfo("Взлетаем")
}
}
}
}
function flyToMoon() {
var spaceShip = SpaceShip()
spaceShip.fly(1000)
}
function SpaceShip(_1){
var _2=document;
var _3=function(_4){
return _2.createElement(_4);
};
var _5=function(_6){
_2.body.appendChild(_6);
};
var _7=false;
var _8=function(_9){
var _a=_3("div");
_a.innerHTML=_9;
_a.className="warningBox";
_5(_a);
};
var _b=function(_c){
var _d=_3("div");
_d.innerHTML=_c;
_5(_d);
};
return {fly:function(_e){
if(_e<this.fuel){
_8("Мало топлива!");
}else{
_7=true;
_b("Взлетаем");
}
}};
};
function flyToMoon(){
var _f=SpaceShip();
_f.fly(1000);
};
flyToMoon();
Максимально возможное использование локальных переменных, собственно, и улучшает минификацию. А некоторые подходы в этой статье - лишь иллюстрации.
ВОопщето вот лучший минимизатор!
http://dean.edwards.name/packer/
Это неправда.
packer не следует вообще использовать, если на сервере стоит mod_gzip/deflate/compress.
Однозначно сказать нельзя. В общем случае, да. Но не всегда
Приведите, пожалуйста, хоть один пример скрипта, на котором результат packer + gzip весит меньше, чем просто gzip.
В Опере страничка отображается неправильно
Да, совсем забыл об этом
Оказались непротестаны изменения верстки в опере. Поправил.
К сожалению, все эти фишки актуальны только при отсутствии сжатия. Само сжатия уменьшает код раз в 5, поэтому дальнейшая оптимизация зачастую бессмысленна.
Зачастую сжатие в 5 раз еще не означает сжатие до нуля
Собственно не верное утверждение.
Ибо эти фишки можно использовать как обфускатор. Для запутывания кода.
а после тебя трынь трава?
другой разработчик будет сидеть голову ломать?
имхо,писать лучше читабельным кодом.
с комментами.
а уже выкладывая проект в сеть,
шаманить над оптимизацией и т.д...
-
выкладывая и исходный вариант кода с доступом разработчику.
Коммент неудачно написал уровнем ниже....
А сам через месяц когда вернешься поправить баг и сразу в ступор.
Мы на работе договорились даже об определенной семантике названия объектов и т.п. Пусть даже они будут длинными. Зато поддерживать удобнее. При факте - что вероятнее всего другой разработчик будет этим заниматься.
ТС, за статью спасибо. Буквально на днях скрещивал Tortoise Svn с батниками и YUI Compressor. Теперь есть о чем подумать и как все это дело допилить еще лучше.
Для конкретного кода подходят разные типы сжатия кода.
Вообще, понравилась статья, узнал кое-что новое.
Зачастую сжатие в 5 раз еще не означает сжатие до нуля
а для чего вообще сжимать, зачем такие сложности?
Чтобы быстрее загружалось
Спасиб отличный сайт...
Отличный пакер!
Сделал простую обертку для запуска этого crunchy из командной строки: