How LiveChat Uses Grunt.js for Easy Product Deployment

Recently, we have moved our whole deployment script from raw bash scripts to Grunt.js. We needed a bulletproof solution that lets us focus on product development, not its maintenance. Within a few days of work, the results were really fascinating.

The problem: custom deployment scripts

In LiveChat, as in other web products, some common deployment patterns need to be followed:

  1. Turn many JavaScript files into a single .js file.
  2. Turn many CSS (or SASS/LESS) files into a single .css stylesheet.
  3. Compress your JS/CSS code to reduce file sizes.
  4. Append MD5 fingerprint to file names to take advantage of heavy browser caching.
  5. Create image sprites to limit the number of HTTP requests.
  6. Send the files to the server(s).

We used to use bash scripts for doing this donkey work. But when you see this over and over…

if [ `wc -c "$BASEDIR/app.js" | awk '{print $1}'` -lt 50 ]; then
    echo "File size lower than 50 chars. Aborting."
    exit
fi

if [ $(tail -c7 "$BASEDIR/app.js") != '/*OK*/' ]; then
    echo "File does not end with \"/*OK*/\" comment. Aborting."
    exit
fi

…and again and again…

function putParamIntoFile {
    sed "s/$2/$3/g" $1 > "$1.tmp"
    mv "$1.tmp" $1
}
putParamIntoFile "$SCRIPTS_PATH/index.html" "{{jsPath}}" "${CDN_TARGET_DIR}app.$DATE.js"

…I can go on…

echo "** Adding timestamp to file..."
echo "/* `date` */" > $BASEDIR/date.tmp
mv $BASEDIR/tracking.js $BASEDIR/tracking.date.js
cat $BASEDIR/date.tmp $BASEDIR/tracking.date.js > $BASEDIR/tracking.js
rm $BASEDIR/date.tmp $BASEDIR/tracking.date.js

…that’s when you’re starting to realize that there must be a more humane solution.

The solution: Grunt.js

Product deployment - Grunt.js logo

Grunt’s popularity speaks for itself. Not even a year has passed since its creation and Grunt can now perform more than 1200 (!) different tasks such as minification, code validation, files concatenation, sprites generation and more. 4 new plugins for Grunt.js are published every day. Now, that’s a good pace of growth.

LiveChat has two big projects that need to be deployed on a daily basis: a chat widget (product used by website visitors) and the web application (used by agents talking with these visitors).

1. The chat widget — deployment

A chat widget (the one you can see in the bottom-right corner of this page) is installed on our customers’ websites. It’s written in pure JavaScript. Grunt.js automates the following deployment tasks for us:

  • copy — copies some files from one place to another.
  • less — compiles LESS files to CSS.
  • cssmin — minifies CSS files.
  • concat — concatenates multiple source files into a single one.
  • replace — replaces inline patterns with variables. I believe most advanced projects involve some kind of regexp replacement, and it’s a great tool to do this.
  • glue — helps us build image sprites using a great Glue library.
  • uglify — minifies files before we deploy them to our customers.
  • shell — helps us run an SCP command which sends the final version of the chat widget code to our servers.
  • watch — re-compiles the chat widget source whenever we save a new version of the source file in a text editor. Immensely useful during local development.

Here’s the good part: we have a single tool for both building local versions and deploying the product to our customers. When we want to run a development version of the widget, including watch task for instant re-compilation, we just use:

grunt

Deploying the widget to our staging environment could not be simpler:

grunt deploy:staging

What if a chat widget version is ready to be deployed to all customers?

grunt deploy:production

But there’s one more thing. The learning curve for other team members became significantly shorter, compared to our previous solutions. If a web developer worked with Grunt before, he can start working on the project immediately by simply typing in the grunt command.

2. The web application — deployment

Our customers sign in to the web application to handle chats with their customers. The front–end of the application is written in CoffeeScript, HTML and CSS. We use the same Grunt plugins as in the chat widget project, however there are some more:

  • handlebars — a Grunt task for working with Handlebars templates.
  • snockets — lets us forget about including necessary files in the given order. Priceless for projects with 50+ source files.

We’ve also built a custom plugin for calculating MD5 hashes of deployed files. Using this solution, the browser will only download the files that have changed compared to the previous version. In a nutshell, it works like this:

# MD5 module for node.js: https://github.com/pvorb/node-md5
md5 = require 'MD5'

calculateMD5String = (path) ->
    '-' + md5 fs.readFileSync path

filepath = "app" + calculateMD5String("app.js") + ".js"
# filepath is now "app-f78b7292153b991a52b65c1115451118.js"

Again, deploying the application to all customers is as simple as:

grunt deploy:production

You won’t find a better way to work with your JavaScript projects.

Categorised in:

  • Witold

    I’ll recommend grunt-css-url-rewrite or other base64 css/rewrite.

  • Witold

    A and one more thing, better use grunt-recess, then less, cssmin and concat.