Using Webpack with Django: no plugins required!

This post explores setting up Webpack in a Django project with minimal friction. The defacto solution for this, django-webpack-loader, is too heavy handed in my opinion. This post aims to provide a guide to setup Webpack in Django, without any plugins and using both Webpack's and Django's strengths.

Django-webpack-loader

django-webpack-loader seems to be the defacto way of setting up Webpack in Django projects. I tried using it but didn't like that:

  1. It requires a non-standard manifest plugin (that was also buggy for me).
  2. It provides a set of custom Django template helpers, instead of using the excellent Django built-in static functionality.
  3. It required a bunch of configuration in Django, to make the custom helpers work.

It feels like a very heavy handed solution requiring both a Webpack plugin and a Django plugin.

Disclaimer: While I'm criticizing django-webpack-loader here this is not meant to knock on the hard work that the maintainer(s) have undoubtedly put into this.

Vanilla Django & Webpack

Making Django and Webpack play nice together turns out to be pretty straightforward, without needing any Webpack or Django plugins.

Django has built-in support for handling static assets. Django can serve static assets in development and compress files and hash filenames during production deployment. Webpack can do this too (and much more), but it's much more convenient to let Webpack only worry about producing your assets and Django about handling your assets.

In practise this works as follows:

1. Use Django's default Static support

Refer to the Django docs for details. Important settings are:

2. Configure Webpack to write files to STATICFILES_DIRS

You can use any Webpack configuration you like. Only a couple of settings are important:

  1. Have Webpack write to a directory in STATICFILES_DIRS
  2. Configure output.publicPath to match Django's STATIC_URL
  3. Don't let Webpack hash filenames (except for chunks, see below)
  4. Make sure webpack-dev-server writes files to disk (so Django can serve them in development)
// Relevant parts of webpack.config.js
output: {
path: path.resolve(__dirname, "myapp/static"), // Should be in STATICFILES_DIRS
publicPath: "/static/", // Should match Django STATIC_URL
filename: "[name].js", // No filename hashing, Django takes care of this
chunkFilename: "[id]-[chunkhash].js", // DO have Webpack hash chunk filename, see below
},
devServer: {
writeToDisk: true, // Write files to disk in dev mode, so Django can serve the assets
},

Now, during development you run both webpack-dev-server and the Django server. You can use a Procfile and a tool like Goreman to conveniently do this without having to open two terminals. Webpack writes files into STATICFILES_DIRS and Django serves the files. Best thing is: you still get all the Webpack goodies like hot reloading and dynamic imports!

To include an asset produced by Webpack you use the Django static template tag:

{% load static %}
<link rel="stylesheet" type="text/css" href="{% static 'app.css' %}">
<script type="text/javascript" src="{% static 'app.js' %}"></script>

For production deployments you first have Webpack build your assets and then use manage.py collectstatic as usual. Presto! Just plain Django and Webpack, nice and simple.

Webpack chunks (dynamic imports)

As I mentioned above it's important to do let Webpack hash chunk filenames. By default Webpack uses numeric chunk ids to refer to chunks at runtime. When Django's collectstatic runs, it hashes the filenames and then looks for unhashed references in other files to replace them.

For example if you have a stylesheet that refers to a file foo.jpg and Django renames foo.jpg to foo.695e1b313f34.jpg, it will replace the foo.jpg reference in the stylesheet with foo.695e1b313f34.jpg.

Webpack at runtime refers to chunks by their numeric chunk id by default and as such collectstatic will not correctly recognize the chunk filenames. By letting Webpack hash the chunk filenames we get properly hashed chunk filenames, so these files can be cached properly by the browser.

Example project

I've created a demo project for this setup on GitHub here so you can see it in action. It's also deployed on fly.io here.

If this was useful to you I'd love to hear from you!