Hello World,
This topic is about how you can pre-code the code so as to improve its minimization. I recently minimized the library before the release.
Helios Kernel (about which he
wrote the day before yesterday ). The library source code weighs 28112 bytes, it has generous comments, and therefore it is
compressed from the floor with a
YUI compressor to 7083 bytes. Not that it seemed to me that 7 kilobytes is too fat. But just by looking at the minimized code with my own eyes, I could see a bunch of places where I could save more:

')
Let's see what can be done with the code to turn 7083 bytes into
4009 3937.
But before you begin, two reservations:
- We will not use all sorts of dirty tricks (like var a = this or var f = false ) that theoretically lead to a slowdown. It is assumed that the speed is still more important than the file size.
- At each step, I ran the code through a set of tests. It often happens that after a change, everything stops working. If you do not test the code during the manual optimization process (or if you don’t have any tests at all), then the code that will end up will most likely not work.

Minimizer selection
In general, this article is not about the comparison of minimizers, but in the process I noticed that the
YUI compressor has a
bug feature: it does not remove the curly brackets from code blocks consisting of one line. Moreover, it adds braces, even if they were not in the original (in the first picture it is marked with a
WTF tag). I took it as rudeness and, without hesitation, switched to using the online minimizer
http://jscompress.com/ . However, the rest of the reasoning applies to any minimizer of your choice.
Big Anonymous Function
To begin with, let's wrap all the code in a large anonymous function that will be immediately called (if this was not done initially). Then we can use the local scope of this function. How this will save bytes will be shown below. The most compact way to wrap code in an anonymous function is as follows:
It was | has become |
---|
| !function(){ |
"Private" objects
Surely the code has a large number of auxiliary objects that are not included in the public API. Since there is no native way in Javascript to indicate that an object is private, usually some kind of convention is used. Most often such objects are named starting with an underscore: "
_ ". Usually the minimizer replaces the names of local variables with single-letter ones, but leaves the names of “private” objects unchanged, because it does not make bold assumptions about how we designate “private” objects. But it doesn’t matter to us how these objects will be called in the minimized code, so you can rename them manually:
It was | has become |
---|
myObject._somethingPrivate = { | myObject.a = { |
MyObj = function() { this.somePublicProperty = ...; this._somePrivateProperty = ...; this._anotherPrivateProperty = ...; } | MyObj = function() { this.somePublicProperty = ...; this.a = ...; this.b = ...; } |
MyObj.prototype._privateMethod = function() { | MyObj.prototype.c = function() { |
Here you need to be careful. First, do not forget to replace the names of private functions and variables not only in declarations, but also where they are used. Secondly, you need to keep track of the logic of the code, and avoid intersections of names. For example, if a type has a function
a already declared in the prototype, you cannot call the private property of this object by the same name. This is an obvious thing, but it is easy to miss it if you do not pay special attention to it.
In addition, private objects are often declared not only in all constructors / initializers. Javascript allows you to add objects on the fly. In theory, all private identifiers in the code can be carefully replaced with single-letter ones:
It was | has become |
---|
MyObj.prototype.getSomething = function() { if ( typeof this._prop == "undefined" ) { this._prop = 0; } return this._prop; } | MyObj.prototype.getSomething = function() { if ( typeof this.x == "undefined" ) { this.x = 0; } return this.x; } |
"Public" objects
“Public” objects are those that are part of the API, and we need them to be called exactly as they were originally called. But if a “public” object is used too often inside the code (well, let's say, at least once), and its name is too long (well, let's say, more than two bytes), then it makes sense to give it an alias:
It was | has become |
---|
myObject = { ... } | var a = myObject = { ... } |
In this example, after such a change, the variable
a will be declared as local, and the variable
myObject as global (assuming that the identifier
myObject is used for the first time.
Now you can run through the code, find all the objects that are not only declared, but also used, and make it an alias:
It was | has become |
---|
MyObj = function() { this.somePublicProperty = ...; this.a = ...; this.b = ...; } | var b = MyObj = function() { this.somePublicProperty = ...; this.a = ...; this.b = ...; } |
MyObj.prototype.someMethod = function() { | b.prototype.d = b.prototype.someMethod = function() { |
someStorage.someMethod = function() { | var c = someStorage.someMethod = function() { |
And again, the main thing is not to get confused in scopes and not to name variables from the same scope with the same name. In the examples above, an object of type
MyObj already has a private property
b and a private method
c , and the new local variables
b and
c fall into the scope of the Big Anonymous Function, into which we wrapped all the code at the very beginning (we wrapped it, didn't we? ?)
In addition, we can make aliases to some public properties, but only to those that contain complex objects:
It was | has become |
---|
AnotherObj = function() { this.someProperty = [ 0, 0, 0 ]; | AnotherObj = function() { this.a = this.someProperty = [ 0, 0, 0 ]; this.b = this.secondProperty = { a: 1 }; this.thirdProperty = 0; this.fourthProperty = true; this.fifthProperty = "hello"; } |
If we make aliases for simple objects, it will copy the contents, and the alias will point to another object.
Putting var 's
Now we use the fact that you can declare variables separated by commas, using the word
var once. In the simplest case, it looks like this:
It was | has become |
---|
someFunction = function() { var a = 0; var b = something(); | someFunction = function() { var a = 0, b = something(); |
anotherFunction = function() { var c; | anotherFunction = function() { var c, d = something(), i = 0 |
In general, you need to pull out all the declarations to the beginning of the function and write them using one
var . I will write about optimization of the
for () loop below. And still it is necessary to collect all local announcements inside our Large Hadron Function and also to shove them under one
var at the beginning. These are the aliases we created in the previous section. All code should transform like this:
It was | has become |
---|
!function(){ | !function(){ var b = MyObj = function() { this.somePublicProperty = ...; this.a = ...; this.b = ...; }, c = b.prototype.someMethod = function() { |
Note that in this example, the variables
b ,
c and the like remain declared as local to the Big Function. Thus, we will save as many
vars as there were in the function (well, except for one).
And you need to make sure that the code logic does not change. We after all change the order of lines, therefore theoretically it can happen so that some object will be used before it is initialized, this cannot be allowed.
Prototypes
For each declared type and its constructor, it is possible to save a
lot on the word
protoype - it is too long. To do this, we describe the entire prototype for future objects of this type in the form of a single hash:
It was | has become |
---|
MyObj = function() { | MyObj = function() { |
As you can see, for this you need to remember to replace the "
= " with "
: " and separate the method declarations with commas. This method does not work for the case when you need to add some prototype for a type constructor declared somewhere else (because we completely redefine the prototype with such a record).
Cycle and condition optimization
Almost all cycles and many conditions can be optimized:
It was | has become |
---|
a--; if ( a == 0 ) { | if ( --a == 0 ) { |
if ( --a == 0 ) { | if ( !--a ) { |
for ( var i = 0; i < a; i++ ) { b( c[ i ] ); } | for ( var i = 0; i < a; ) { b( c[ i++ ] ); } |
But here, too, you need to be careful not to violate the logic of the code.
Commonly used values
It happens that there are values ​​that are used more than once. They can also be put into variables:
It was | has become |
---|
| var z = "undefined"; |
if ( typeof a != "function" ) { a = function(){} } | var f = "function", g = function(){} |
el = document.createElement( "script" ); el.type = "text/javascript"; | var x = "script"; el = document.createElement( x ); el.type = "text/java" + x; |
Throw away all unnecessary
It often happens that the code contains extra information “for clarity”, which can be eliminated. But here, as elsewhere, you need to carefully monitor what we delete:
It was | has become |
---|
if ( a.length > 0 ) { b = a.pop() } | if ( a.length ) { b = a.pop() } |
var someEnum = { foo : 0, bar : 1, buz : 2 } | var someEnum = { foo : 0, bar : 1, buz : 2 } |
Bonus: remove var 's
This is an interesting trick that is useful in cases where one local variable is declared inside a function (or if the variable is declared without initialization). Here we save on one
var 'e, but we have to duplicate the name of a variable:
It was | has become |
---|
doSomething = function( param1, param2 ) { var i = 0; | doSomething = function( param1, param2, i ) { i = 0; |
doSomething = function( param1, param2 ) { var a, b, c; | doSomething = function( param1, param2, a, b, c ) { |
Here we use parameters instead of local variables, but they behave the same way. This trick is not suitable in cases where the function takes an unknown number of parameters. Most often, it allows to get rid of almost all
var 's in the code.
What happened in the end
After processing the code in the ways described, I fed the script to the
jscompress.com service. A little thought, he gave me just such a porridge for 4009 bytes. Enjoy your meal!

By the way, I will distribute the advantages in karma to those who find and describe in the comments what else can be cut in this mess :-)
Update
nano_freelancer suggested some good ideas:
In addition, most
nulls can also be replaced by 0 (but not all).
Code size reduced to 3937 bytes :-)
Offtopic: the source and minimized codes with which I worked are available for download on the project’s home page:
http://home.gna.org/helios/kernel/