Django and Webpack - Setup and Basic Usage

In this series of posts, we are going to discuss how to set Webpack up in the context of a Django application, and how to use Webpack's core features to bundle together JavaScript files and packages, as well as work with CSS, SCSS, images, TypeScript and more.

In later posts, we'll see how to use Webpack for complex use cases, as well as to bundle together external Node packages along with your source code.

Webpack is a very useful, but complex beast. It's often a source of frustration for developers, and in this set of posts and videos, we'll attempt to tame the beast.

Webpack can transform JavaScript files, and by default is a JavaScript tool. However, it can also be used to transform stylesheets (including Sass stylesheets), images and other files. We'll learn how to do this throughout the series. Most notably, it's also used to bundle together multiple modular JavaScript and CSS files into a single package (or a small set of packags) for deployment, which has many advantages.

With Webpack and Django, we're going to create a toolchain that allows us to take multiple assets, and bundle them together into a single file (and later, multiple bundles) that can be output into our Django static directories. After the bundle is created, it is accessible to our Django static files app, and can be served in templates. We'll see how to achieve a simple workflow in this post, and will build on it in later posts.

The associated video for this post can be found below.


Objectives

In this post, we will:

  • Set up Webpack within a Django project
  • Understand the anatomy of a basic Webpack configuration file, and the entry and output concepts
  • Create custom JavaScript files
  • Bundle all our static assets together into a single bundle file, using Webpack
  • Use Webpack in watch-mode, to allow us to make changes and reload our bundles during development

Setting up Django

Let's start by setting up a Django project. In the terminal, execute these commands to create a project called webpack_video, and within that, an app called core.

django-admin startproject webpack_video
cd webpack_video
python manage.py startapp core

Once we have the Django project, we will set up templates and static files within the project. In your settings.py file, add the following settings:

# add core app to INSTALLED_APPS
INSTALLED_APPS = [
   ...
   'core',
]

STATIC_URL = '/static/'
STATIC_ROOT = BASE_DIR / 'static'

With the settings in place, let's test this out. Create two directories within the core app:

  • static - to hold static files belonging to the core app
  • templates - to hold HTML templates for the core app

Now, we'll create a simple HTML template. Within the template directory, create an index.html file with the following code:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Django Webpack template</title>
    
  </head>
  <body>
	
    <p id="message">Hello</p>
    
  </body>
</html>

This is a super-simple template, containing a paragraph tag with an ID of "message". Let's create a URL and a view that can serve this template.

In the project urls.py file, add the following code:

from django.contrib import admin
from django.urls import include, path

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', include('core.urls'))
]

Now, in the core app directory, add a urls.py file with the following code.

from django.urls import path
from core import views

urlpatterns = [
    path('', views.index, name='index')
]

Next, we need to add that view. Within the views.py file in the core app, add the following view function.

from django.shortcuts import render

# Create your views here.
def index(request):
    return render(request, 'index.html', {})

We're now going to test the static file serving within this template.

In the static directory, add a JavaScript file called test.js and add the following code.

window.addEventListener('load', () => {
    document.getElementById('message').textContent = 'FROM JAVASCRIPT!';
}); 

When the window's load event is fired, this function will target the paragraph tag with the given ID of "message", and change its text. We can test that this works by including the script in our index.html file - amend the file as below.

{% load static %}

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Django Webpack template</title>

    <script src="{% static 'test.js' %}"></script>
    
  </head>
  <body>
    
    <p id="message">Hello</p>
    
  </body>
</html>

Note the new lines that have been added. On line 1, we load the Django static template-tag library. Then, on line 11, we use the static template-tag to reference the index.js file, and load that into this template as a <script> tag.

Later, we'll use the <script> tag to reference our Webpack bundles, hence why we've elaborated this setup. For now, let's move on to installing Webpack!

Setting up Webpack

We are now ready to work with npm and Webpack. You must ensure you have the latest version of Node.js installed on your system - you can get Node.js from here.

Installing Node.js will also give you access to the npm command-line tool - this is the Node Package Manager, which allows you to install dependencies in your environment.

To verify the installation, open up a new terminal and run the command: npm --version.

What we're going to do is initialise a Node.js project. Run the following command, from the same directory as where your manage.py script is.

npm init

When running this command, you can fill in the options such as the "author" field with your own name. But the defaults will suffice, too. After running the command, this should generate a package.json file within your project. This file allows you to manage your JavaScript/Node.js dependencies, in a similar manner to Python's requirements.txt or PHP's composer.json - it's a similar idea.

Let's now use npm to install Webpack. Execute the following command.

npm install -D webpack webpack-cli

Execute this command, and you will install these two packages into your environment. As well as Webpack itself, we're also installing the Webpack CLI, which provides us with a command-line tool that allows us to launch Webpack in watch mode, and to generate our bundles.

Notice a few things here:

  • We use the -D flag, which denotes that these are development dependencies.
  • In the package.json file, these two packages are now present in the devDependencies object.

These are development dependencies because they are used to generate bundle files that'll be served when the app is deployed. However, the app itself does not require Webpack to operate - Webpack is used only to generate the bundles that are eventually used.

You should notice that these packages (and their dependencies) are now available within a node_modules folder in your project. This should be added to a .gitignore file if you are committing to a source control repository.

Webpack Concept: Entry-Point

What we'll do now is add an index.js JavaScript file to our project. This is going to be the entry-point for our assets - it is the root file that Webpack will look at when creating its bundle.

Let's create an assets folder within our project root. This should live alongside the Django apps in our project, and in the same directory as the webpack configuration file. This is my preferred way to store front-end assets - directly within an assets folder.

Within the assets folder, we'll create sub-directories for different resources. Create the following directories:

  • scripts - this will be where we define our JavaScript (and later, TypeScript) files
  • styles - this is where our CSS and SASS files will live

We may create more later, for files, images, and fonts. For now, let's keep it to the main assets used in a web project!

Now, an important Webpack concept is the entry-point. This is the first file that Webpack will look at, and from there it will figure out which dependencies it needs to load for the given file (for example, by looking at the source code and import statements). Under the hood, Webpack builds a dependency graph that allows it to figure out exactly which code should be included in the final bundle.

Our entry-point is going to be the aforementioned index.js file. We'll create that at assets/scripts/index.js - add that now!

In this new file, paste the same code that was in our test.js file from the previous section:

window.addEventListener('load', () => {
    document.getElementById('message').textContent = 'FROM JAVASCRIPT!';
}); 

You can now remove the test.js file, if you wish.

So we've set up our entry-point. We now need to tell Webpack about this!

So, we're now going to generate a configuration file that will define our Webpack config. In the same folder as the package.json file, add another file: webpack.config.js. In there, let's add some basic configuration.

const path = require('path');

module.exports = {
    entry: './assets/scripts/index.js'
}

This is a minimal configuration - we start by importing the path module from Node.js. This is very similar to the os.path module in Python, and allows us to work with the filesystem and paths to files/directories. We'll use this module a bit later.

We then create a JavaScript object defining what the module should export - this object contains settings that are used by Webpack.

On line 4, we define our entry configuration. This points to the index.js file in our assets directory, and with this line, we're telling Webpack where the entry-point is when it comes to bundling our code.

Webpack will look at this file and start here when creating our final bundle.

Webpack Concept: Outputs

You should understand by now that the purpose of Webpack is to take our front-end source code (in our assets folder), and package this code together into bundles. So, if we have 100 JavaScript files, using Webpack allows us to bundle these together into a smaller number of files. This is much better for our apps - rather than serving 100 scripts, we can bundle together and serve one (or two, three, etc).

We know that the input setting tells Webpack where it should start looking at our assets. But the question is, where should the bundled files be stored? This is the purpose of the output Webpack configuration option.

Let's amend the Webpack configuration file.

const path = require('path');

module.exports = {
    entry: './assets/scripts/index.js',
    output: {
        'path': path.resolve(__dirname, 'core', 'static'),
        'filename': 'bundle.js'
    }
}

The new code is on lines 5-7, where we define an output setting. The purpose of this is to tell Webpack where to emit the final bundle file, and what to name the final file.

We define the output as an object with two settings. Firstly, the path setting refers to the directory where the bundle will be emitted. We use the path.resolve() method to point to our core application's static directory.

Secondly, we define the filename, which refers to the name of our outputted file. We simply call this bundle.js.

This is actually all we need to run a basic Webpack build. Webpack will look at the entry point, and will bundle together all the code in that file (and code used from imported modules) into a single deployable unit - the bundle.js file. This file will be dropped into our Django static folder, where it can then be referenced with the static template-tag and served to your users.

Next up, we'll see how to use Webpack to actually create the bundle and drop it into our output location.

Running Webpack to Generate Bundle

To run Webpack, we're going to create an npm script. This will allow us to easily involve the webpack-cli commands, which we installed earlier and are available within the node_modules folder.

Add the following block to your package.json file.

  "scripts": {
    "dev": "webpack --watch --mode development"
  }

The dev script will run the webpack command, which is an alias for the webpack build command. This is the command that will build your bundle file!

We use the --watch flag during development to watch for changes. This will allow Webpack to re-build the bundle when you add changes to your assets. We also specify the development mode, which "enables useful names for modules and chunks".

By default, this script will look for a file called webpack.config.js, which we already have. However, with a --config flag, you could also specify a different filename.

To run this, we can use the npm run dev command. Let's run that now, and if all goes well, you should see a message like this:

The asset bundle.js was emitted, and should now be available in your output location, which is the core/static directory.

If your bundle exists, you can now reference it within your templates!

Serving the Bundle

OK, we've generated a bundle.js file - now let's serve it in the template. Add the following code to your index.html template.

{% load static %}

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Django Webpack template</title>

    <script src="{% static 'bundle.js' %}"></script>
    
  </head>
  <body>
    
    <p id="message">Hello</p>
    
  </body>
</html>

The only change from our previous incarnation of this file is the filename referenced by the static template-tag on line 11 - we now refer to bundle.js, the file emitted by our Webpack build process.

Let's test this out. In a separate terminal, start the Django development server - and keep the webpack script running in another terminal, to watch for any changes in our assets.

If all goes well, you should see the message coming from our JavaScript file - "FROM JAVASCRIPT!" - after loading in the bundle.

And with that, you have a Webpack based build process! It's not doing anything useful yet, but we'll start building this out soon.

Remember, we're running Webpack in watch mode - so any changes to our assets should re-build our bundle automatically. Try it out - in the index.js file, change the message:

window.addEventListener('load', () => {
    document.getElementById('message').textContent = 'REBUNDLED BY WEBPACK!';
});

On the terminal running Webpack in watch mode, you should see that the assets are re-compiled. If you reload the page, this should be reflected! (you may need to clear the browser cache when re-loading, however - we'll learn how to better deal with this later).

Bundling multiple JavaScript files

There's not much point in bundling a single JavaScript file with a single event-listener. Let's see how this workflow can be useful and learn how to bundle multiple JS files into a single deployable unit.

Within the assets/scripts directory, create two more files - helper1.js and helper2.js.

In helper1.js, add the following code:

console.log("Hello");

And in helper2.js:

console.log("World");

So we have two new files, but at the moment, Webpack won't use them when building our bundle. Remember, Webpack looks at the entry file - index.js - which contains no reference to these two files.

So, let's add that reference! In your index.js file, add the following code:

import './helper1';
import './helper2';

window.addEventListener('load', () => {
    document.getElementById('message').textContent = 'REBUNDLED BY WEBPACK!';
}); 

We import those two new JavaScript modules at the top. This brings them into the entry point, and Webpack then knows about them, and will include their source code in the final bundle!

If you save the files, refresh the browser (remember to clear the browser cache), and load the console, you should see the messages in the console.

So to summarize, we now have 3 JavaScript files within our assets folder. In our entry-point, we import the other two JS files, and then our pipeline with Webpack bundles these three files into a single output file - the bundle.js file. This is dropped into our static directory and can then be served in our application.

This is much preferable to using three separate <script> tags, which would require three network requests for each client to fetch each script from the server. Instead, we bundle them together, and serve as a single output.

Summary

In this post, we've covered the basics of Webpack, learning the following:

  • How to install and set up Webpack within a Django application (and this should largely be similar for ANY backend system)
  • The entry and output concepts
  • How to bundle multiple JavaScript files into a single output unit that can be served by Django
  • How to add NPM scripts to the package.json file in order to run Webpack builds (in watch mode)

Next up, we're going to dive into more advanced concepts. You can find this post here, where we'll learn:

  • How to install and bundle external libraries - HTMX and Alpine.js - in our Webpack pipeline
  • What Webpack Loaders are
  • How to include CSS stylesheets in our final bundles

If you enjoyed this post, please subscribe to our YouTube channel and follow us on Twitter to keep up with our new content!

Please also consider buying us a coffee, to encourage us to create more posts and videos!

;