This year I submitted a piece to the JS1K competition: “You Are Here“. JS1K is a competition for writing JavaScript demos that are no larger than 1024 bytes. This blog post describes some of the tricks I learned along the way:

  • Writing small code
  • Compressing data
  • Using Closure Compiler efficiently to minify the code
  • Using JSCrush to compress the code

Writing Small Code

There are the usual tips on how to write short code which I won’t list here. For inspiration, check out

Compressing Data

Algorithms that produce cool graphics without having to rely on any data are a good choice for the JS1K competition. However, if you do include a chunk of data – like I did – then the data should be a compressed as possible. Strings are often chosen for this because they yield more information per character than numbers. For example, this is how I originally encoded the map (reduced to Australia to keep it short):

var australia = [-22,112, -36,116, -32,136, -46,144, -28, 152, -10, 144, -18,140, -12, 132, -22, 112];

// now for each index
latitude = australia[index];
longitude = australia[index + 1];

I already compromised precision to one degree. But the integer array for the whole world was very long. Encoding the world as a string saved a lot of space:

var australia = "Bi;j=o6q?sHqDpGnBi";

latitude = australia.charCodeAt(index) * 2 - 90;
longitude = australia.charCodeAt(index+1) * 4 - 180;

Using strings, you can encode numbers from 0 to 255 in each character (plus escapes where necessary), and there is no need for commas between values. If you have enough data to encode, then the overhead of the decoder can be tiny. The decoder code from the example will be compressed quite well with JSCrush.

Using Closure Compiler To Minify JS1K Demos

Closure Compiler is excellent at minifying JavaScript. I can’t think of any excuse not to use it. You will have to massage the input and output scripts a little to make it work perfectly for the JS1K competition, though.

These tips are for running Closure Compiler in “advanced” mode. In order to use the compiler to its full potential you may also want to read the Closure Compiler documentation.

Writing JavaScript For Closure Compiler

  • A lot of the tricks of the trade (ternary operators, etc) for writing small code is automated in Closure Compiler. It’s probably still a good idea to use them.
  • Use meaningful variable, function, and function parameter names when you write the script. Closure Compiler will minimize them for you.
  • Declare all variables and function with var if you want their names to be minimized.
  • Use the variables declared in the shim in your script normally. Closure Compiler will detect them and preserve their names.
  • You may have to prevent inlining (see below).

For example:

var currentRadius = 100;

var circumference = function (radius)
{
    return 2 * Math.PI * radius;
};

theCircumference = circumference(currentRadius);

In this case, currentRadius, circumference, and radius will be renamed a, b, and c. theCircumference was not declared with var und will not be renamed.

Actually, Closure Compiler will often go further than that and will use inlining to compress the above script to

theCircumference=200*Math.PI;

Sometimes, Closure Compiler’s aggressive inlining will result in larger code, so you might have to prevent it from inlining some functions or variables. You can force Closure to preserve variables by assigning to them, and we can preserve function by assigning them to a global variable. If you add the following lines to the bottom of the previous unminified source, then currentRadius will be renamed but not inlined. The same goes for circumference.

currentRadius = 1;
dummy = circumference;

A pitfall to look out for is that

var width=height=500;

is equivalent to

height=500;
var width=height;

This means that in this case, “height” would not be renamed.

Massaging Closure Compiler Output

You will most likely have to modify the minified script by hand:

  • You can probably remove each instance of “var”.
  • You may have had to break chained variable declaration and assignment to get good results (see the pitfall just above this section). Now is the time to restore these.
  • If you had to prevent Closure Compiler from inlining variables and functions, then you should remove the dummy code from the end of the script.

Using JSCrush To Compress the Code

JSCrush was developed as part of a JS1K 2010 entry. It really is a beautiful idea and implementation. I suggest studying JSCrush output at least once to understand how it works.

At first I wasn’t sure if using JSCrush was a good idea. And I wonder if the JS1K organizer wasn’t sure either as the competition after JSCrush came up forbid the use of eval functions. But in the end it’s a brilliant idea and the current rules don’t forbid it.

In order for JSCrush to work well, consider that you get the best compression if text is repeated:

  • Let Closure Compiler inline your constants. Normally, it would be good to assign a=12345 and then to only use a in your script. With JSCrush, you may save a byte if you use 12345 throughout the script.
  • Make all function signatures the same. After Closure Compiler, all functions should use the same list of parameter names already, so this step should be easy. If you have a=function(b,c) and d=function(b,c,e), then change a into a=function(b,c,e).
  • If your script includes a data section, consider that the data will only be well compressed if there is repetition in it.

In the end these are just some tools of the trade. If you look at the amazing JS1K entries over the years, it’s obvious that creativity and skill are the major factors in writing amazing demos.

Advertisements