Part 1 — Integrating Django and Vue.js with a webpack hot reload development server

In this article I will hopefully cover all the necessary steps and hurdles to setting up a Django 2.* and Vue.js development environment that is both consistent with production and easy to deploy. The specifics of how to deploy, final configuration touches and (automation) will be discussed in a future article.

To start, we need to make use of one Django package and one webpack plugin — specifically,django-webpack-loaderand webpack-bundle-tracker . In effect, we need to configure django-webpack-loader to read a manifest file and allow webpack-bundle-tracker to take care of updating it. So let’s get started!

N.B. This guide assumes knowledge of node, npm, the terminal, Python and webpack (as well as tracking dependencies with both pip for the Python libraries and npm for Vue and webpack related dependencies).

Development Environment & Project Setup

If you haven’t already I would recommend starting by setting up your Python environment. This ensures stability between your site files from development to production, and allows us to make use of a container approach to deployment (e.g., Docker). Luckily, I have already written a guide to creating your development environment (shameless plug):

N.B. Project specific variables in the following setup guide will be highlighted like_this . Such variables are interchangeable and project specific.

Once you have setup and environment, we need to create a base Vue.js application using vue-cli,

$ npm install -g vue-cli
$ vue init webpack my_project

replacing my_project with whatever you wish to call your project. After running through the initial setup it’s time to setup our Django 2.* project:

$ cd my_project
$ django-admin startproject my_project .

The trailing dot tells Django to install the project in the current directory.

Now we need to install both of the previously mentioned libraries:

$ npm install webpack-bundle-tracker --save-dev
$ pip install django-webpack-loader
$ pip freeze > requirements.txt

What we have done here is told our node dependency tracker (package.json) to save webpack-bundle-tracker as a development dependency. We’ve then installed django-wepback-loader using the pip Python package manager. The third step freezes our current Python environment dependencies to a text file to be used later (using $ pip -r install requirements.txt).

Once you have installed both, navigate to the settings.py file inside your django project, and add to your installed applications:

# ./my_project/settings.py
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'webpack_loader',
]

And we need to configure our BundleTracker:

// build/webpack.base.conf.js
const BundleTracker = require('webpack-bundle-tracker')
module.exports = {
// ...
plugins: [
new BundleTracker({filename: './webpack-stats.json'}),
],
// ...
}

Integrating Django static paths with the webpack bundler

The next step is to setup our configurations such that we have one asset directory in our root project directory, whereby we have one sub-directory for css/, js/, img/and not forgetting the Django generated admin/ asset directory. The final directory structure will look something like this:

- public/
- js/
- app.b234g2ea5c34bc8cd28a.js
- app.b234g2ea5c34bc8cd28a.js.map
- css/
- ...
- img/
- ...
- admin/
- ...

In order for this to happen we have to configure both worlds properly. Let’s take a look at the final configuration of both Django and webpack, and then we will discuss them.

To achieve this we need to configure our Django project settings.py file by adding the following:

# ./my_project/settings.py
STATICFILES_DIRS = (
os.path.join(BASE_DIR, 'dist'),
os.path.join(BASE_DIR, 'static'),
)
STATIC_ROOT = os.path.join(BASE_DIR, 'public')
STATIC_URL = '/static/'

WEBPACK_LOADER = {
'DEFAULT': {
'BUNDLE_DIR_NAME': '',
'STATS_FILE': os.path.join(BASE_DIR, 'webpack-stats.json'),
}
}

And we need to change the following asset path configurations for both our developemnt (dev) and our production (build) configurations in the config/index.js file:

// ./config/index.js
module.exports = {
dev: {
assetsSubDirectory: 'static',
assetsPublicPath: 'http://localhost:8080/',
proxyTable: {},
// ...
}
build: {
assetsRoot: path.resolve(__dirname, '../dist/'),
assetsSubDirectory: '',
assetsPublicPath: '/static/',

// ...
},
}

So what have we added and why have we added it?

  1. build.assetsRoot: path.resolve(__dirname, '../dist/')

This is the output directory of our npm run build task, that takes care of compiling and building all of our webpack-controlled assets. We will run this task as part of the deployment process. It will take care of analysing all our webpack entry points, traversing and compiling them. The output will be stored in the ./dist directory at the root of our project (which should be .gitignored).

2. STATICFILES_DIRS and STATIC_ROOT

When deploying to production we will also run the Django script collectstatic that takes care of finding all the static files our project makes use of, and putting them in the correct folder. With STATICFILES_DIRS we are telling it were to look for these files. With STATIC_ROOT we specify where to place them. I sometimes wrap this in a if DEBUG: conditional and we know we’re moving to production when DEBUG is false in settings.py.

In STATIC_DIRS we have specified 2 directories: one is the ./static (Django’s default), and the above defined ./dist where webpack will place its compiled assets.

3. build.assetsSubDirectory: '' and build.assetsPublicPath: '/static/'

Now we tell webpack that (in production) any asset reference should point to /static (ie at the root of the domain). For example, if our code reads,

<img src="images/logo.png">

…the final compiled version will be:

<img src="/static/images/logo.png">

4. STATIC_URL = '/static/'

The same thing but for Django. Every time one of our templates refers to a static file it will render it under the /static path in our project directory.

5. dev.assetsPublicPath: 'http://localhost:8080/'

This is one of the key points in which we achieve using only one url in our development environment. We want the webpack devevelopment server to serve our webpack assets. While we point our browser to Django’s localhost:8000, webpack will compile our code to point at localhost:8080 .

6. WEBPACK_LOADER

In WEBPACK_LOADER['DEFAULT']['STATS_FILE'] we point our django-webpack-loader to pick up the json manifest file generated by webpack’s BundleTracker (see above in file build/webpack.base.conf.js). We set to blank WEBPACK_LOADER['DEFAULT']['BUNDLE_DIR_NAME'] because we don’t want to nest our webpack assets into any subdirectory.


Referencing static files in our Vue components and within our Django (Jinja2) templates

We want to have a place to put images, fonts, or generic statics assets that can be used from both worlds: our Django templates and our Vue components. In other words, I want to have a ./static directory at the root of the project where I could put (for example) my logo and be able to write this code:

<!-- static reference in a Django template -->
<img src="{% static 'logo.png' %}">

<!-- static reference in a Vue component -->
<img src="static/logo.png">

The Django example already works thanks to the config above described (see STATICFILES_DIRS), but for Vue/webpack to work you’d need to add an alias to the webpack base configuration to resolve our static directory:

// build/webpack.base.conf.js
resolve: {
extensions: ['.js', '.vue', '.json'],
alias: {
'vue$': 'vue/dist/vue.esm.js',
'@': resolve('src'),
'__STATIC__': resolve('static'),
}
},

By adding this we can now write our Vue code like this (note the added tilda ~):

<!-- static reference in a Vue component -->
<img src="~__STATIC__/logo.png">

Configuring Hot Module Replacement (“hot reload”)

While in development we are only accessing Django’s server, and proxying our asset requests to the webpack server (see above dev.assetsPublicPath). Because of this our hot reload will not work. We need to configure webpack’s hot middleware to point to the right path, and we need to add a new header to the webpack dev server so it allows for CORS requests:

// build/webpack/dev.conf.js
devServer: {
// ...
watchOptions: {
poll: config.dev.poll,
},
headers: {
"Access-Control-Allow-Origin": "\*",
}

},

For all intents and purposes, we are done. However, let’s configure a base template for all other templates to utilise, regardless of where there are in our project.


Summary

Setting up our Django development project to work well with webpack required a fair amount of configuration. Our Django project will act as our main development server which will in turn proxy asset request to webpack. In doing so we have simplified the development workflow so we can spend more time programming and less time switching between browsers or fine tuning the config for new cases that may appear.

The next stage in this tutorial will be looking how to build templates and begin to use Vue components within our Django project. Stay tuned for Part II of this particular tutorial set.