Front-end tools like Grunt and Gulp are becoming very widespread, and there’s good reason for it. The front-end code is no longer static, CSS pre-processors are the norm, and JavaScript modules such as Require, Browserify or even coffeescript are also becoming more common as well. On top of that, when you’re deploying to production, you want to minify everything but when you’re in development mode, you want sourcemaps for JS and CSS among other things.

At Lincoln Loop, we use Gulp as our task runner to do the following things:

  • Compile Sass into CSS, including autoprefixer support
  • Compile JavaScript with Browserify
  • Add sourcemaps for our compiled JavaScript and CSS
  • Minify images with imagemin
  • Reload the browser automatically on JavaScript (and/or template) changes
  • Refresh the CSS without reloading the browser on CSS changes
  • Trigger system notifications when Sass or JavaScript has errors
  • Minify JavaScript and CSS for production deploy

and finally:

  • Generate a build folder where all the generated files are stored

We won’t go into details about our Gulp setup on this post, there’s plenty of information out there (we wrote about Grunt before).
If you’re curious about the code thou, here’s a gist of a gulpfile-in-one that includes most of what's described in this post and you can also look into a more up-to-date yeoman generator we’re working on that will bootstrap the whole setup easily.

Integration with Django

Development

The only tasks we need to run locally are:

gulp optimize

This is a one-off task that handles image minification. This task can be very CPU intensive, so we only run this locally to overwrite existing images with optimized versions.

gulp (watch)

In development we run gulp, and our default task is watch. The watch task watches for source file changes, compiles/builds them, and drops them in the build directory.

On the Django side, we’ll add our resulting build directory path to STATICFILES_DIRS so Django is able to serve them when running manage.py runserver during development or manage.py collectstatic during deployment.

The watch task is also responsible for refreshing your browser when CSS, JS or templates change, so you don't have to manually reload. We’re using a tool called BrowserSync that integrates with Django by configuring it to use runserver's port (typically localhost:8000) as a proxy. When we run gulp watch in the terminal, a BrowserSync server will kick-off on localhost:3000 from which we'll access our Django site with the automatic refresh of static files enabled.

Production

When the site is deployed to production, there is a different workflow in place to optimize the static files for data transfer.

Generate and minify assets

gulp build --production

We use this command to create production-ready assets. It performs the same tasks as gulp watch, but won't generate sourcemaps and instead minifies the CSS and JavaScript.

Version files

manage.py collectstatic

Once gulp has generated the assets, we want to “version” them by adding a unique hash to each filename. This will allow us to use far-future expiration headers to let clients safely cache the files and speed up future page loads. Django has this covered via the ManifestStaticFilesStorage. It will convert screen.css into screen.27e20196a850.css and allow the static template tag to serve the versioned file without any changes to your code.

Gzip files

Most clients can accept gzipped files to further speed up the data travelling over the wire. We typically offload this task to our web server, Nginx, which can handle it with a couple extra lines in its configuration. Alternatively, many CDN providers (see below) are capable of gzipping content as well.

CDN (optional)

Rather than using collectstatic to push your static files to a remote location, we simply have our CDN of choice pull them from the server by configuring it as the “origin”. This speeds up the deployment and in general simplifies the overall build process. The only change required is set your STATIC_URL to point to the CDN instead of your server.

Note: You can use a WSGI middleware like whitenoise to serve (and even gzip) the files directly from your Python server (gunicorn, uWSGI, etc.). This can be particularly helpful on a PaaS like Heroku or where installing Nginx is problematic.


We’ve tested a lot of different setups and finally feel like this one hits the sweet spot -- for now ;) Front-end developers are already using tools like gulp or grunt and we’re able to leverage them to handle much of the work we needed third-party libraries like django-compressor or django-pipeline to perform. Now the separation between front-end and back-end is clearer and we’ve actually reduced the complexity of our Django apps in the process.