Simplifying the Front-end Build Process
12 Mar 2014CSS and JavaScript seems like it should be pretty simple. We have our site stylesheet, our site support scripts, and some dependencies like Twitter Bootstrap, JQuery, etc. And that’s fine, until it gets to be a lot. Next thing we know we have 5 internal scripts, 10 dependencies, 3 internal stylesheets. Google PageSpeed is not happy with making 18 HTTP calls to load our front-end dependencies, which aren’t gzipped, minified, or uglified. If you’re like me, we tend to just write JQuery spaghetti code, too. No classes, no objects, just $('.thing').doSomething();
all strung together. Now we realize we have a mess on our hands. We work hard to organize our back-end code and keep it maintainable. Why not the same for front-end?
Measurable Improvements
To prevent this, let’s first get a tool to guide us in best practices to optimize our load times. The preferred tool for this is Google PageSpeed. You can get it as a Chrome Inspector Extension or just go to their PageSpeed Insights page. PageSpeed just tells you whether you’re doing the right things, and you can go down the list and make improvements one-by-one. Gzipping and far-future expiration dates can be done with the HTML5Boilerplate .htacess file. After that you’ll see that you need to minify css and javascript - removing spaces and stuff. Javascript usually goes a step further - “uglifying” code. In addition to removing white space, this renames variables and functions to single characters to shorten the code. And you’ll need to combine all your css and js into one file each. This is a lot to do manually. How can we do this without adding a ton of work?
PHP Solutions
There are various back-end (PHP) tools to accomplish this. This is where I started. Tools like Carabiner for CodeIgniter, Basset or Asset Pipeline for Laravel and Rails, and various other stuff. They’re pretty similar but I’ll explain how Basset works. In your views you do something like basset_assets()
which operates differently in development and production. In development it just links to the unminified individual css/js files with script
and link
tags. This way you can easily debug with comments, source file names, and code formatting. In production, you manually run a build script, which does all the front-end build and caching stuff (you can customize what it does and how), and puts the cache files somewhere like assets/css/cache/49t3psafisduifis.css
. Then in production, when you call basset_assets()
, it will only link to the single combined, processed cache file instead. This also is an effective cache buster to only cache between versions. It is, however, a bit complicated.
Refining the Process with Better Tools
In the last year or two, this has gotten simpler. First there are Source Maps. This is metadata that gets inserted into your minified code in development which tells you where that code is defined in the source. This allows you to serve the minified/combined version in dev, but be able to jump to the file/line of the source css/js in the Chrome Web Inspector. But wait, there’s more! It can also link SASS or LESS to CSS and CoffeeScript to JavaScript! So now the only thing you need to do differently is add this metadata in dev.
Next, there are better tools to watch and build files and build. First there were tools like guard which is a ruby gem that will watch directories or files and do stuff with them. There are community plugins that let you mix and match various tasks when it finds changes. Then there was Grunt which is a nodejs version of basically the same thing, but less buggy and simpler. More recently there is Gulp, which has simpler syntax than Grunt and a little more flexibility on watching and such. Gulp makes writing these behaviors, installing and using plugins a snap. Jeffrey Way made a great video explaining how to use Gulp. Here is what a Gulpfile looks like. Gulp also has a livereload plugin to automatically reload your js/css on save, a s3 plugin to upload to Amazon S3, a js uglify plugin, a jshint plugin, and hundreds of others.
Enhancing JavaScript and CSS with Pre-Processors
What other cool stuff can we do with css/js? Well there’s pre-processor languages. CSS has several ones such as Less and Sass. These augment CSS with additional structures to add functions, nested rules, and variables. CoffeeScript is similar for JavaScript, but looks very different from JavaScript. It has a class definition syntax, uses indention for structure, and various other conveniences. Plus it makes it easier to write JSHint-friendly code since the compiled code is usually good to go. These can be hooked in as part of the build process to compile, link with source maps, and do the rest.
A Different Way to Approach JavaScript Dependencies
When I want to use a jquery plugin or something, I usually just put the plugin’s script
tag above my site script
tag so it’s included first. Then do whatever method functionality the tool provides in my site js. This is pretty different from PHP. What if we didn’t just assume it was there but did a require()
instead? Enter RequireJS and more recently a superset of that: Browserify. This allows you to do require('path/to/script.js')
. With Browserify, you can also use a plugin to require CoffeeScript files. This will compile the CoffeeScript and then require the compiled JavaScript. Why is Browserify better? It includes various convenience modules from NodeJS such as url
, path
, stream
, events
, and http
. More info available in this article.
And Now for Something Completely Different
That takes care of building, but here’s a related front-end issue: front-end package dependencies. Let’s assume you don’t want to link to a JQuery CDN because it could go down and bring your site functionality with it. So we could just download the latest JQuery and add it to your codebase. But now updating becomes a little tedious, especially when you have 10 JQuery plugins, Twitter Bootstrap, various third-party CSS boilerplates, etc. Another issue is that when you add those libraries to your code, it makes code review a little tricky because you have third-party code cluttering up your commits. Will the real code please stand up?
Bower solves these issues. It works like Composer for js/css packages. What you do is just store the manifest for which front-end packages you need in bower.json
, then let bower install them into a folder ignored by Git. This is done manually on the command line via bower install
. You can also do bower update
to update packages within your defined constraints, such as version 1.2.X. You do the same in the build process before all your front-end processing. Now you can easily find most front-end packages from one place, easily install and update, and keep third-party code out of git. Bam!
Tieing It All Together
GulpJs and Bower take care of these front-end issues and do it well. Combine this with a build/deploy stack such as Jenkins, This PHP Template for Jenkins, Laravel Envoy, and Phing, and you’ve got a pretty lean, fast, and smart setup.