At Lincoln Loop, we use Gulp as our task runner to do the following things:
- Compile Sass into CSS, including autoprefixer support
- Minify images with imagemin
- Refresh the CSS without reloading the browser on CSS changes
- Generate a
buildfolder 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 though, 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
The only tasks we need to run locally are:
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.
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
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.
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
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.27e20196a850.css and allow the
static template tag to serve the versioned file without any changes to your code.
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.
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.