Здесь мы обсудим разные приёмы, которые используются, чтобы улучшить сжатие кода.
Больше локальных переменных
Например, код jQuery обёрнут в функцию, запускаемую «на месте».
(function(window, undefined) {
// ...
var jQuery = ...
window.jQuery = jQuery; // сделать переменную глобальной
})(window);
Переменные window
и undefined
стали локальными. Это позволит сжимателю заменить их на более короткие.
ООП без прототипов
Приватные переменные будут сжаты и заинлайнены.
Например, этот код хорошо сожмётся:
function User(firstName, lastName) {
var fullName = firstName + ' ' + lastName;
this.sayHi = function() {
showMessage(fullName);
}
function showMessage(msg) {
alert( '**' + msg + '**' );
}
}
…А этот – плохо:
function User(firstName, lastName) {
this._firstName = firstName;
this._lastName = lastName;
}
User.prototype.sayHi = function() {
this._showMessage(this._fullName);
}
User.prototype._showMessage = function(msg) {
alert( '**' + msg + '**' );
}
Сжимаются только локальные переменные, свойства объектов не сжимаются, поэтому эффект от сжатия для второго кода будет совсем небольшим.
При этом, конечно, нужно иметь в виду общий стиль ООП проекта, достоинства и недостатки такого подхода.
Сжатие под платформу, define
Можно делать разные сборки в зависимости от платформы (мобильная/десктоп) и браузера.
Ведь не секрет, что ряд функций могут быть реализованы по разному, в зависимости от того, поддерживает ли среда выполнения нужную возможность.
Способ 1: локальная переменная
Проще всего это сделать локальной переменной в модуле:
(function($) {
/** @const */
var platform = 'IE';
// .....
if (platform == 'IE') {
alert( 'IE' );
} else {
alert( 'NON-IE' );
}
})(jQuery);
Нужное значение директивы можно вставить при подготовке JavaScript к сжатию.
Сжиматель заинлайнит её и оптимизирует соответствующие IE.
Способ 2: define
UglifyJS и GCC позволяют задать значение глобальной переменной из командной строки.
В GCC эта возможность доступна лишь в «продвинутом режиме» работы оптимизатора, который мы рассмотрим далее (он редко используется).
Удобнее в этом плане устроен UglifyJS. В нём можно указать флаг -d SYMBOL[=VALUE]
, который заменит все переменные SYMBOL
на указанное значение VALUE
. Если VALUE
не указано, то оно считается равным true
.
Флаг не работает, если переменная определена явно.
Например, рассмотрим код:
// my.js
if (isIE) {
alert( "Привет от IE" );
} else {
alert( "Не IE :)" );
}
Сжатие вызовом uglifyjs -d isIE my.js
даст:
alert( "Привет от IE" );
…Ну а чтобы код работал в обычном окружении, нужно определить в нём значение переменной по умолчанию. Это обычно делается в каком-то другом файле (на весь проект), так как если объявить var isIE
в этом, то флаг -d isIE
не сработает.
Но можно и «хакнуть» сжиматель, объявив переменную так:
// объявит переменную при отсутствии сжатия
// при сжатии не повредит
window.isIE = window.isIE || getBrowserVersion();
Убираем вызовы console.*
Минификатор имеет в своём распоряжении дерево кода и может удалить ненужные вызовы.
Для UglifyJS это делают опции компилятора:
drop_debugger
– убирает вызовыdebugger
.drop_console
– убирает вызовыconsole.*
.
Можно написать и дополнительную функцию преобразования, которая убирает другие вызовы, например для log.*
:
var uglify = require('uglify-js');
var pro = uglify.uglify;
function ast_squeeze_console(ast) {
var w = pro.ast_walker(),
walk = w.walk,
scope;
return w.with_walkers({
"stat": function(stmt) {
if (stmt[0] === "call" && stmt[1][0] == "dot" && stmt[1][1] instanceof Array && stmt[1][1][0] == 'name' && stmt[1][1][1] == "log") {
return ["block"];
}
return ["stat", walk(stmt)];
},
"call": function(expr, args) {
if (expr[0] == "dot" && expr[1] instanceof Array && expr[1][0] == 'name' && expr[1][1] == "console") {
return ["atom", "0"];
}
}
}, function() {
return walk(ast);
});
};
Эту функцию следует вызвать на результате parse
, и она пройдётся по дереву и удалит все вызовы log.*
.