I’m happy people are finding the article on javascript optimization useful. But I made a giant, horrible mistake. A mistake that befalls many tutorials.
I didn’t include actual, working examples for you to play with. You can talk all you want, but until you’ve got some code, it’s just theory and listless sighs. And without seeing the code walk (or run! Get it?), it’s hard to believe that it really works. So here’s some live, working examples to show these techniques in action:
- Online Example: Imported and Delayed Loading of Javascript. Notice how the delayed javascript file appears 5 seconds after the regular file and the
$import
ed one. - Download: Optimized_Javascript.zip
The examples are free and in the public domain. However, if you find it useful I’d appreciate you sharing it with friends or dropping me a note. I like knowing what explanation styles work so I can do more of it in the future.
And now, the guided tour of what you’ll see in the zip file.
Eliminate Tedium: Use Scripts
Automate, automate, automate! I’ve created a set of batch files (and .sh files for you Linux/UNIX gurus) to get you started:
makeall.bat
: Runs the commands belowmake_libraries.bat
: Combines *.js into “allfiles.lib.js”, and combines files prefixed with “example” into “example.lib.js”.pack_js.bat
: Compresses *.js and creates *.js.packedadd_cache_header.bat
: Inserts the PHP caching header into the .js.packed files, creating js.packed.phpcleanup.bat
: Removes generated files, leaving you with your original .js files.
These are templates – modify them to suit your own needs. If you find yourself typing a command again and again, throw it into a script.
Compressing Javascript
I’ve included custom_rhino.jar
which does the compression (more info).
There are a few javascript files for demo purposes. The first is example_compressed.js
, which has extremely long variable names in various scopes (local and global). Take a look at this sucker, it’s ripe to get crunched by Rhino:
/* these names are global, so will not get compressed (could be used elsewhere) */
var LongName = 1;
var OtherLongName = 2;
var ReallyReallyLongName = 3;
var AbsurdlyLongNameImNotQuiteSureWhyAnyoneWouldUseThisButItIsGoodForExamples = 3;
/* these names are local to foo(), and will get compressed. Isn't Rhino awesome? */
function foo(){
var LongName = 1;
var OtherLongName = 2;
var ReallyReallyLongName = 3;
var AbsurdlyLongNameImNotQuiteSureWhyAnyoneWouldUseThisButItIsGoodForExamples = 3;
var LongName = 1;
var OtherLongName = 2;
var ReallyReallyLongName = 3;
var AbsurdlyLongNameImNotQuiteSureWhyAnyoneWouldUseThisButItIsGoodForExamples = 3;
(repeated...)
return 0; // of course :)
}
log("Compressed Example Loaded!");
A typical “javascript compressor” will simply remove extra spaces and comments, which doesn’t help much. Rhino actually analyzes your code: when it sees global variables, it knows the name shouldn’t be changed since other scripts may reference them.
But local variables are another story. Since locals are only referenced inside of their function, they are ripe for squashing. This is your javascript on Rhino:
var LongName=1;
var OtherLongName=2;
var ReallyReallyLongName=3;
var AbsurdlyLongNameImNotQuiteSureWhyAnyoneWouldUseThisButItIsGoodForExamples = 3;
function foo(){
var _1=1;
var _2=2;
var _3=3;
var _4=3;
var _1=1;
var _2=2;
var _3=3;
var _4=3;
var _1=1;
var _2=2;
...
return 0;
}
log("Compressed Example Loaded!");
Any questions?
Rhino trampled the variable names and replaced them with the shortest identifiers it could find: _1, _2, etc. This saves a lot of space, and has the side-effect of partially obfuscating your code (if you are looking for that sort of thing).
Dynamic Import and Delayed Loading
Now here’s the fun stuff: example_imported.js
and example_delayed.js
don’t do anything special, except call a logging function that shows when they were loaded.
Check out sample.html
<html>
<head>
<!-- only put scripts here if you really need to -->
</head>
<body>
<!-- Scripts that need to run first -->
<script src="import.js"></script>
<script>
/* Simple logger -- looks for div with id "log".
We want this available from the get-go. */
function log(str){
var logger = document.getElementById("log");
if (logger){
logger.innerHTML += new Date().toString() + ": ";
logger.innerHTML += str;
logger.innerHTML += "<br/>";
}
}
</script>
<!-- content, images, tables, etc. -->
Put content here...
<div id="log">
</div>
<!-- include packed version -->
<script src="example_compressed.js.packed"></script>
<!-- dynamic import -->
<script>
$import('example_imported.js');
</script>
<!-- delayed loading -->
<script>
function loadDelayedScripts(){
$import('example_delayed.js');
}
var delay = 5; // wait and then load the file
setTimeout("loadDelayedScripts()", delay * 1000);
</script>
<!-- other heavy scripts, tracking code, etc. -->
</body>
</html>
Take a look at the result:
Notice how $import
acts immediately, and the delayed load happens 5 seconds later. All the scripts call the log
function, but it could be any callback, like registerLoadEvent()
or displayHiddenFeature()
. Leave that for your imagination.
Creating Library Files
It can also be helpful to combine smaller files into a larger one, especially if they don’t change often. This reduces the number of requests the browser makes and you don’t suffer the overhead for each item.
Downloading one 10k script is faster than ten 1k ones – browsers can only have a certain number of connections open at a time. Once you’ve got the connection going, you may as well cram a larger file down.
The UNIX “cat” (or Windows “type”) command is perfect for this. If you set a filter (example*.js) you can combine files with the same prefix into a library:
cat *.js > allfiles.lib.js
cat example*.js > example.lib.js
And since the library ends in .js, it will get packed along with the other .js files in our packing script.
Adding PHP Cache Headers
The last step is to add the cache headers to the files. There is a general “set_cache_header.php” file that is combined with the packed javascript (.js.packed) to create the js.packed.php files. Assuming your server is configured to serve PHP, this will set the caching headers for 3 days (change this to any number you like).
Always Keep learning
We’re never done learning — I’d love to see what other tricks you use to speed up your javascript or automate the “build” process.
Remember that there are all sorts of interesting callbacks you can do. The scripts, once loaded, can call functions to display previously hidden features in the page: as scripts are loaded, menu items/images/text could appear. Or, you can just have one master script that $imports
the others, so you don’t need to monkey with your HTML file if you add a new javascript file (some Javascript libraries behave this way). The possibilities go on: use some, all, or none of these techniques. Experiment and learn what works for you.
Happy hacking.