A quick howto guide on setting up modern front-end development tools to improve your day to day work flow.

You'll learn how to:

Setup gulp to orchestrate everything

Install gulp plugins to combine, minify and otherwise transform your files

Use bower a package manager for "web things". It helps you install, track and manage CSS frameworks, JS libraries and other tools

Add LiveReload so your browser window(s) auto-refresh when you make changes to anything

Integrate all of this with a Django project with django-compressor and django-bower

I've always struggled working on frontend tasks. I know CSS well enough, but it can be a pain to get it just right. I'm more comfortable with Javascript. However, I tend to end up with spaghetti code all over the place and a dozen randomly included files and things quickly become insane.

So I've spent some time over the last several weeks trying to up my frontend development game. As it turns out, many of the pain points I experienced are now solved really well.

What is gulp?

Gulp is a node module that runs and executes various tasks based on rules you've setup. It has its own plugin ecosystem for doing common tasks. To get started today we're going to be using:

gulp-watch to spy on various files and directory trees an execute the tasks we define when files changes

gulp-sass to compile our SASS files into the CSS we will serve to the browser

gulp-minify-css to minify the CSS we build from SASS

gulp-rename to handle renaming files as we process them

gulp-gzip to store gzip compressed versions of files

gulp-livereload to make all our browser windows update as we make changes

What does bower do for us?

Bower is a package manager for web things. With a couple of easy commands you can install things like jQuery, Zurb Foundation or Bootstrap into your project. It also helps track and install the dependencies. It's CPAN, PyPI, gems or npm for "the web" rather than a single language.

Initial Setup

We need to install a few things before we can continue.

First, let's install gulp and bower system wide as once you start using these tools you're going to want to use them in all your web projects:

npm install --global gulp bower

Now move into your project directory, this is so npm and bower will install everything else to your project and not system wide. After you're there, install the gulp plugins we will be using:

npm install gulp-watch gulp-sass gulp-minify-css gulp-rename gulp-gzip gulp-livereload

Setup Zurb Foundation

Before we get too far into setting up Gulp we need something for it to do. So let's install Zurb Foundation 5 and set things up to customize it with SASS:

bower install foundation

Now let's create a directory named scss and create two files inside of it. One named _main_settings.scss is going to be where we override any of Foundation's default settings. For example purposes we just want to change something here, so let's go ahead and make Foundation's grid rows be 100% wide. To do that put this inside the file:

$ row-width : 100 %;

And we also need a "main" SASS file to tie everything together so inside scss/main.scss place the following SASS code:

@ charset 'UTF-8' ; @import "main_settings" ; @import "../bower_components/foundation/scss/foundation/components/grid" ; @import "../bower_components/foundation/scss/foundation/components/accordion" ; @import "../bower_components/foundation/scss/foundation/components/alert-boxes" ; @import "../bower_components/foundation/scss/foundation/components/block-grid" ; @import "../bower_components/foundation/scss/foundation/components/breadcrumbs" ; @import "../bower_components/foundation/scss/foundation/components/button-groups" ; @import "../bower_components/foundation/scss/foundation/components/buttons" ; @import "../bower_components/foundation/scss/foundation/components/clearing" ; @import "../bower_components/foundation/scss/foundation/components/dropdown" ; @import "../bower_components/foundation/scss/foundation/components/dropdown-buttons" ; @import "../bower_components/foundation/scss/foundation/components/flex-video" ; @import "../bower_components/foundation/scss/foundation/components/forms" ; @import "../bower_components/foundation/scss/foundation/components/icon-bar" ; @import "../bower_components/foundation/scss/foundation/components/inline-lists" ; @import "../bower_components/foundation/scss/foundation/components/joyride" ; @import "../bower_components/foundation/scss/foundation/components/keystrokes" ; @import "../bower_components/foundation/scss/foundation/components/labels" ; @import "../bower_components/foundation/scss/foundation/components/magellan" ; @import "../bower_components/foundation/scss/foundation/components/orbit" ; @import "../bower_components/foundation/scss/foundation/components/pagination" ; @import "../bower_components/foundation/scss/foundation/components/panels" ; @import "../bower_components/foundation/scss/foundation/components/pricing-tables" ; @import "../bower_components/foundation/scss/foundation/components/progress-bars" ; @import "../bower_components/foundation/scss/foundation/components/range-slider" ; @import "../bower_components/foundation/scss/foundation/components/reveal" ; @import "../bower_components/foundation/scss/foundation/components/side-nav" ; @import "../bower_components/foundation/scss/foundation/components/split-buttons" ; @import "../bower_components/foundation/scss/foundation/components/sub-nav" ; @import "../bower_components/foundation/scss/foundation/components/switches" ; @import "../bower_components/foundation/scss/foundation/components/tables" ; @import "../bower_components/foundation/scss/foundation/components/tabs" ; @import "../bower_components/foundation/scss/foundation/components/thumbs" ; @import "../bower_components/foundation/scss/foundation/components/tooltips" ; @import "../bower_components/foundation/scss/foundation/components/top-bar" ; @import "../bower_components/foundation/scss/foundation/components/type" ; @import "../bower_components/foundation/scss/foundation/components/offcanvas" ; @import "../bower_components/foundation/scss/foundation/components/visibility" ;

This just instructs SASS to use our settings files and load all of the various components of Foundation. In a real project you won't likely be using all of these features so you can comment them out and drastically reduce the size of the CSS you send to the browser.

Setup our gulpfile.js

You control Gulp by writing out a gulpfile.js. Put this in the root of your project, I'll explain what is going on below:

var gulp = require('gulp'); var sass = require('gulp-sass'); var watch = require('gulp-watch'); var minifycss = require('gulp-minify-css'); var rename = require('gulp-rename'); var gzip = require('gulp-gzip'); var livereload = require('gulp-livereload'); var gzip_options = { threshold: '1kb', gzipOptions: { level: 9 } }; /* Compile Our Sass */ gulp.task('sass', function() { return gulp.src('scss/*.scss') .pipe(sass()) .pipe(gulp.dest('static/stylesheets')) .pipe(rename({suffix: '.min'})) .pipe(minifycss()) .pipe(gulp.dest('static/stylesheets')) .pipe(gzip(gzip_options)) .pipe(gulp.dest('static/stylesheets')) .pipe(livereload()); }); /* Watch Files For Changes */ gulp.task('watch', function() { livereload.listen(); gulp.watch('scss/*.scss', ['sass']); /* Trigger a live reload on any Django template changes */ gulp.watch('**/templates/*').on('change', livereload.changed); }); gulp.task('default', ['sass', 'watch']);

The first few lines simple include all of the various plugins we will be using. We then set some common configuration information for the gzip plugin. Here we're saying we don't want to bother compressing anything smaller than 1kb and to use gzip's maximum compression. Gulp is incredibly fast, most everything happens in milliseconds so there isn't any reason to skimp here.

We then define two tasks, sass and watch . The sass command is used by watch command, so let's look at that first.

The watch looks for any file that matches scss/*.scss it will run that file through the sass command. We also we setup a livereload server which can be used with the LiveReload desktop app or in my case the Chrome extension from their browser extensions page.

Finally we have another rule that looks for any files that look like Django templates and trigger a live reload. Why is that awesome? Well you can open up multiple browsers, say one desktop and one mobile sized, and as you make edits to your Django templates and/or CSS your browser windows will automatically refresh. Saving you from having to lift your hands off the keyboard, move your mouse all the way over to the windows and refreshing each manually. This feature alone saves me time and likely a bit of carpal tunnel.

Our sass command is a bit more complicated. Gulp processes files as pipes, so it doesn't have to write any intermediary files to disk which is part of how it achieves such great speed. If you read the source of that task from top down here is what is going on:

Look for any files matching 'scss/*.scss'

Compile them with libsass

Write the results to this point to 'static/stylesheets'. In our case, with our original being main.scss it will write 'static/stylesheets/main.css'

Now rename the stream to append '.min' to the end of the filename and before the extension

Minify the CSS at this point

Write the results again into 'static/stylesheets' this time creating 'static/stylesheets/main.min.css'

Compress the stream using the gzip options we setup up top

Yet again, write the results to 'static/stylesheets' this time creating 'static/stylesheets/main.min.css.gz'

Trigger a live reload

Our final task is the 'default' task. With Gulp you can call tasks individually on the command line, but anything you define in your 'default' task will be run continuously in the foreground after just running gulp without any arguments.

Django Integration

Up to this point everything we've configured is backend agnostic. You could use everything we've setup with a totally static site, Rails, or whatever your preferred backend stack is. Here at RevSys we primarily use Django, so being able to integrate this with Django is pretty important otherwise it wouldn't be useful to us.

Luckily Django is totally agnostic about your frontend setup. What we have configured already would work just fine with a Django site. We're building our CSS and JS as combined files and all one would need to do is include them using a standard Django {% static %} template tag. However, I'm a big performance nerd and one of my favorite features of django-compressor is it's default configuration that creates hashed file names based on underlying CSS and JS it is compressing.

When you're using django-compressor you control what is combined and compressed using template tags like this:

{% load compress %} <head> {% compress css %} <link rel= "stylesheet" href= "{% static " stylesheets/main.css" %}" /> <link rel= "stylesheet" href= "{% static " stylesheets/some-other.css" %}" /> {% endcompress %} </head>

With this configuration compressor will combine, minify, etc., based on the rules you have setup in your Django settings, into a single file like:

<link rel="stylesheet" href="/static/CACHE/css/ed3523606236.css" type="text/css" />

Note that because gulp is combining and minifing our CSS for us, all we really want django-compressor to do for us is create this hashed file name. So to setup django-compressor all we need to do is 'pip install django-compressor', add 'compressor' to our INSTALLED_APPS and add 'compressor.finders.CompressorFinder' to the STATICFILES_FINDERS setting. By default compressor doesn't compress when DEBUG=True, so I also suggest adding COMPRESS_ENABLED=True to your local dev settings to help ensure your local environment mimics production as much as possible.

We are however also using bower, so to make Django's static files system be able to find the bower components we need to 'pip install django-bower', add 'djangobower' to INSTALLED_APPS and add 'djangobower.finders.BowerFinder' to STATICFILES_FINDERS. So ultimately we end up with this in settings:

INSTALLED_APPS = [ ... your other django apps ... 'django.contrib.staticfiles', 'djangobower', 'compressor', ] COMPRESS_ENABLED = True STATICFILES_FINDERS = ( 'django.contrib.staticfiles.finders.FileSystemFinder', 'django.contrib.staticfiles.finders.AppDirectoriesFinder', 'djangobower.finders.BowerFinder', 'compressor.finders.CompressorFinder', )

We can then easily setup far future Expires headers for anything living in /static/CACHE/ to a date way into the future without any worries we might serve up old CSS to our users. A typical nginx configuration, setting the expire header for 90 days into the future, looks like this:

location /static/CACHE { alias /home/site/static/CACHE; expires 90d; }

Why not just use django-compressor for all of this?

One of the draw backs of django-compressor is when you have several transformations going on it can get a bit slow. All of the SASS compilation, concatination, minification and whatever else you have configured it can take a half second to a second or two for pages to be rendered after a CSS or JS change when working locally with Django's runserver.

This doesn't sound like much or anything to be concerned about, but having worked with this new setup for a few days the perceived daily performance difference is pretty impressive. Add in the immediately useful LiveReload features that are easy to achieve with gulp and I can attest this setup helps you stay in a useful flow state more easily.

Switching over to this setup might take a half day of time at most, but I would wager you will gain that back in spades in less than 2 months of using it. Happy hacking!