I’ve been working on an application using Backbone.js recently. I really enjoy it, however I realised today that I now have over 100 javascript files in my project. This means that every time my page loads, the browser is making 100 separate HTTP requests. Very quickly, your site slows down.
The obvious solution is to combine all my scripts into one file and minify. I’ve done this several times before, but I needed to make sure that certain files loaded in a certain order, so I whipped up a shell script to ensure some of the files are pushed to the front of the line.
Having never really done that much shell scripting before, this wasn’t the easiest task for me. To help save others some time, I’ve attached my script and documented it pretty well. Let me know if you have any questions!
Above, you’ll see our (sample) folder structure. Under the models subfolder, we have tab.js, which needs to be loaded before subtab.js.
Below, you’ll see the gist of what I’m doing. If you have any sort of shell experience, I’m sure this isn’t a revelation, but I wish I had something like this when I started writing earlier today!
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/bash | |
# This method returns 1 if a string is contained in an array, and 0 if it is not. | |
containsElement () { | |
local e | |
# For each element in the second argument to this method | |
# The ${} indicates that this is an array, the @ indicates we want all | |
# elements, and the 2 denotes that this is the second argument to this | |
# method. | |
for e in "${@:2}" | |
do | |
# If the current element, $e, is equal to the first argument passed | |
# into this method, $1, return 1 | |
[[ "$e" == "$1" ]] && return 1 | |
done | |
# We'ver reached the end of the loop, return 0 | |
return 0 | |
} | |
# The directory your JavaScript files are in. | |
JS_DIR='js' | |
# A temporary file that will be deleted upon every run | |
JS_TEMP="$JS_DIR/_combined.js" | |
# The final output file | |
JS_COMBINED_FILE="$JS_DIR/combined.js" | |
# Get an array of all of the javascript files. | |
allFiles=(models/*.js) | |
# Create a new array of just the items that need to be moved to the front. | |
modelsToBeMovedForwards=(models/tab.js) | |
# Get the length of the allFiles array. There's a lot going on in this line. | |
# The $ indicates that we're 'getting' (rather than setting) | |
# {} indicates that this is an array | |
# The # says that we want the length | |
# [@] says we want all of the items in the array | |
length=${#allFiles[@]} | |
# Loop through the first array. Remove the items that need to be moved to the front. | |
for ((i=1; i<$length; i++)) | |
do | |
# Call containsElement, declared above | |
containsElement "${allModels[$i]}" ${modelsToBeMovedForwards} | |
# If the results of the method, $?, is 1, remove this item from the array. | |
if [[ "$?" == "1" ]]; then | |
unset allModels[$i] | |
i=$i-1 | |
fi | |
done | |
# Concatonate the arrays. | |
final=("${modelsToBeMovedForwards[@]}" "${allFiles[@]}") | |
# Loop through each item… | |
for F in ${final[@]}; do | |
CURR_FILE="$JS_DIR/$F" | |
# … and shove it in our temp file using `cat` | |
cat $CURR_FILE >> $JS_TEMP | |
done | |
# Clear the old combined file | |
> $JS_COMBINED_FILE | |
# Compress the temp file into the real file using the YUI Compressor | |
java -jar yuicompressor-2.4.8.jar $JS_TEMP -o $JS_COMBINED_FILE | |
# Remove the temp files | |
rm $JS_TEMP | |
# Optional: Add the file to git. | |
# git add $JS_COMBINED_FILE |
A few things to note:
- I highly recommend adding this code as a git hook, if you want your combined JS to be in source control
- The heavy lifting is done with the fabulous YUI Compressor, which requires Java to be installed (It also does CSS minification).
- Remember to chmod your compression script to allow you to execute or else it won’t work!