Django & Webpack - without any plugins 
Tags: django, webpack
Published: 2020-04-20 05:31:25

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 leveraging both Webpack's and Django's strengths.

### Django-webpack-loader

[django-webpack-loader](https://github.com/owais/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](https://github.com/owais/webpack-bundle-tracker) (that was also buggy for me).
2. It provides a set of custom Django template helpers, instead of levering 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](https://docs.djangoproject.com/en/3.0/howto/static-files/) 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](https://docs.djangoproject.com/en/3.0/howto/static-files/) for details. Important settings are:

* `STATICFILES_DIRS`

* `STATIC_URL`

* `STATICFILES_STORAGE` - Use `ManifestStaticFilesStorage` to let Django hash filenames for cachability.

#### 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)

```js
// 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](https://github.com/mattn/goreman) to conveniently do this without having to open two terminals. Webpack writes files into `STATICFILES_DIRS` and Django serves the files.

To include an asset produced by Webpack you use the Django [`static`](https://docs.djangoproject.com/en/3.0/ref/templates/builtins/#std:templatetag-static) template tag:

```html
{% 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](https://docs.djangoproject.com/en/3.0/ref/contrib/staticfiles/#django-admin-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](https://github.com/pascalw/django-webpack-boilerplate) so you can see it in action. It's also deployed on Heroku [here](https://django-webpack.herokuapp.com/).

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