- Introduction
- Setup Webpack Project with Django
- Load Webpack bundles in Django
- Linting in Webpack
- Load Webpack hash bundle in Django
- Code splitting with Webpack
- How to config HMR with Webpack and Django
- How to use HtmlWebpackPlugin to load Webpack bundle in Django
If you want a quick start with Webpack and Django, please check python-webpack-boilerplate
Objective
By the end of this chapter, you should be able to:
- Import
Bootstrapto the frontend project usingnpm install. - Load
Webpackbundle files with Djangostatictemplate tag. - Learn the difference between
developmentmode andproductionmode in Webpack.
Import Bootstrap
Let's first import jquery and bootstrap to our Webpack project.
$ cd frontend
(frontend)$ npm install jquery
(frontend)$ npm install bootstrap
If we check frontend/package.json, we will see the packages exists in dependencies
"dependencies": {
"@babel/polyfill": "^7.12.1",
"bootstrap": "^4.6.0",
"core-js": "^3.8.1",
"jquery": "^3.5.1"
}
Update frontend/src/scripts/index.js
import "../styles/index.scss";
import $ from "jquery/dist/jquery.slim";
import "bootstrap/dist/js/bootstrap.bundle";
$(document).ready(function () {
window.console.log("dom ready");
});
Notes:
- We import the
index.scssfile at the top.index.scssis the SCSS index file. - And then we import
jqueryandbootstrapjs bundle file (They are installed bynpm installand exists atnode_modules) - Here we only use
jquery.slimbecause we only need jquery to do some DOM operation.
Update frontend/src/styles/index.scss
@import "~bootstrap/scss/bootstrap.scss";
Notes:
- The
tilde (~)means the relative path of thenode_modules, this features is added by sass-loader - Here we import Bootstrap SCSS files and webpack would help us build SCSS to CSS files.
Let's rerun npm run start
(frontend)$ npm run start
modules by path ./node_modules/ 741 KiB
./node_modules/jquery/dist/jquery.slim.js 229 KiB [built] [code generated]
./node_modules/bootstrap/dist/js/bootstrap.bundle.js 231 KiB [built] [code generated]
./node_modules/jquery/dist/jquery.js 281 KiB [built] [code generated]
webpack 5.10.0 compiled successfully in 2665 ms
[webpack-cli] watching files for updates...
If we do not see any errors, which means our code is working
build
├── css
│ └── app.css
├── index.html
├── js
│ ├── app.js
│ └── vendors-node_modules_bootstrap_dist_js_bootstrap_bundle_js-node_modules_jquery_dist_jquery_slim_js.js
└── public
Code splitting
In the above section, after we import jquery and bootstrap in the index.js
We saw a new js file was generated which has a very long filename. From the filename, we can guess the file contains jquery and bootstrap code.
So let's think about this problem (assume you are the core developer of the webpack) and there are two options for you:
- Compile all js files to one file (
app.js) - Compile
commonpart which does not change frequently tovendor-XXX.js, and the app logic part toapp.js
Which one is better?
Solution 2 is better because in most cases, changes made to the app would only happen in app.js, so user who already use your products does not need to download the vendorjs file again. (And this can make the application run faster)
This behavior in webpack is controlled by SplitChunksPlugin, we will learn more about this in later chapter.
In this chapter, we choose the simplest plan, we can put the vender all in one file which has fixed filename.
Update frontend/webpack/webpack.common.js
optimization: {
splitChunks: {
chunks: 'all',
name: 'vendors',
},
},
We set the splitChunks.name to vendors, which is a string.
Let's rerun npm run start again.
(frontend)$ npm run start
./build
├── css
│ └── app.css
├── index.html
├── js
│ ├── app.js
│ └── vendors.js
└── public
Notes:
- Now
app.jscontains code fromsrc/index.js - And
vendors.jscontains code fromjqueryandbootstrap
Django Config
Update wagtail_bootstrap_blog/settings/base.py
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [
str(BASE_DIR / 'django_webpack_app/templates'),
],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
STATIC_URL = '/static/'
# can be found at the bottom
STATICFILES_DIRS = [
str(BASE_DIR / 'frontend/build'),
]
- We added
str(BASE_DIR / 'django_webpack_app/templates')to theDIRSofTEMPLATES - We added
str(BASE_DIR / 'frontend/build')toSTATICFILES_DIRSso Django can find the compiled bundle files.
Update django_webpack_app/templates/index.html
{% load static %}
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<link href="{% static 'css/app.css' %}" rel="stylesheet">
</head>
<body>
<div class="jumbotron">
<div class="container">
<h1 class="display-3">Hello, world!</h1>
<p>This is a template for a simple marketing or informational website. It includes a large callout called a
jumbotron and three supporting pieces of content. Use it as a starting point to create something more unique.</p>
<p><a class="btn btn-primary btn-lg" href="#" role="button">Learn more »</a></p>
</div>
</div>
</body>
<script src="{% static 'js/vendors.js' %}"></script>
<script src="{% static 'js/app.js' %}"></script>
</html>
Notes:
- In
headwe imported css file using{% static 'css/app.css' %} - At the bottom, we import js file using
{% static 'js/vendors.js' %}and{% static 'js/app.js' %}
Update django_webpack_app/urls.py
from django.contrib import admin
from django.urls import path
from django.views.generic import TemplateView
urlpatterns = [
path('', TemplateView.as_view(template_name='index.html')),
path('admin/', admin.site.urls),
]
The Django would return index.html when we visit the root URL.
(env)$ python manage.py runserver
Notes:
- If we check on
http://127.0.0.1:8000/, everything would still work as expected. - We can also see message
dom readyin the console of thedevtool(code in frontend/src/scripts/index.js), which means the js run successfully.

SCSS
Let's take a step further
Update frontend/src/styles/index.scss
$primary: red;
// copied from "~bootstrap/scss/bootstrap.scss";
@import "~bootstrap/scss/functions";
@import "~bootstrap/scss/variables";
@import "~bootstrap/scss/mixins";
@import "~bootstrap/scss/root";
@import "~bootstrap/scss/reboot";
@import "~bootstrap/scss/type";
@import "~bootstrap/scss/images";
@import "~bootstrap/scss/code";
@import "~bootstrap/scss/grid";
@import "~bootstrap/scss/tables";
@import "~bootstrap/scss/forms";
@import "~bootstrap/scss/buttons";
@import "~bootstrap/scss/transitions";
@import "~bootstrap/scss/dropdown";
@import "~bootstrap/scss/button-group";
@import "~bootstrap/scss/input-group";
@import "~bootstrap/scss/custom-forms";
@import "~bootstrap/scss/nav";
@import "~bootstrap/scss/navbar";
@import "~bootstrap/scss/card";
@import "~bootstrap/scss/breadcrumb";
@import "~bootstrap/scss/pagination";
@import "~bootstrap/scss/badge";
@import "~bootstrap/scss/jumbotron";
@import "~bootstrap/scss/alert";
@import "~bootstrap/scss/progress";
@import "~bootstrap/scss/media";
@import "~bootstrap/scss/list-group";
@import "~bootstrap/scss/close";
@import "~bootstrap/scss/toasts";
@import "~bootstrap/scss/modal";
@import "~bootstrap/scss/tooltip";
@import "~bootstrap/scss/popover";
@import "~bootstrap/scss/carousel";
@import "~bootstrap/scss/spinners";
@import "~bootstrap/scss/utilities";
@import "~bootstrap/scss/print";
Notes:
- The top
$primary: redset primary color in Bootstrap to red. - The rest of the import statement come from
bootstrap.scss, we can comment out some code to decrease the final css file size. - You can check Theming bootstrap to learn more.
Now if we refresh and check on http://127.0.0.1:8000/ again, we will see the button color has changed. (Please remember to disable cache in browser when developing)

Production build
Please terminate the npm run start before reading content below.
Let's check build command in the frontend/package.json
For npm projects, 'npm run start' is for development and 'npm run build' is for production
"scripts": {
"build": "cross-env NODE_ENV=production webpack --config webpack/webpack.config.prod.js",
},
We can see it set env NODE_ENV=production and run webpack with webpack.config.prod.js command.
Let's check frontend/webpack/webpack.config.prod.js
const Webpack = require('webpack');
const { merge } = require('webpack-merge');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const common = require('./webpack.common.js');
module.exports = merge(common, {
mode: 'production',
devtool: 'source-map',
stats: 'errors-only',
bail: true,
output: {
filename: 'js/[name].[chunkhash:8].js',
chunkFilename: 'js/[name].[chunkhash:8].chunk.js',
},
plugins: [
new Webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify('production'),
}),
new MiniCssExtractPlugin({
filename: 'bundle.css',
}),
],
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: 'babel-loader',
},
{
test: /\.s?css/i,
use: [MiniCssExtractPlugin.loader, 'css-loader', 'postcss-loader', 'sass-loader'],
},
],
},
});
Notes:
- Here we saw the
mode: 'production', - In production mode, Webpack would focus on minified bundles, lighter weight source maps, and optimized assets to improve load time.
Update frontend/webpack/webpack.config.prod.js
const Webpack = require('webpack');
const { merge } = require('webpack-merge');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const common = require('./webpack.common.js');
module.exports = merge(common, {
mode: 'production',
devtool: 'source-map',
bail: true,
output: {
filename: 'js/[name].js',
chunkFilename: 'js/[name].[chunkhash:8].chunk.js',
},
plugins: [
new Webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify('production'),
}),
new MiniCssExtractPlugin({
filename: 'css/app.css',
}),
],
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: 'babel-loader',
},
{
test: /\.s?css/i,
use: [MiniCssExtractPlugin.loader, 'css-loader', 'postcss-loader', 'sass-loader'],
},
],
},
});
Notes:
- We removed
stats: 'errors-only'so we can get more useful info about the build process. - We change the
output.filenametojs/[name].js, because we do not need thechunkhashhere. (I will talk more about hash in later chapter) - We changed the
MiniCssExtractPlugin.filenametocss/app.css. Since dev and prod both contains this config, you can move them towebpack.common.json your own.
(frontend)$ npm run build
The files in frontend/build
build
├── css
│ ├── app.css
│ └── app.css.map
├── index.html
├── js
│ ├── app.js
│ ├── app.js.map
│ ├── vendors.js
│ ├── vendors.js.LICENSE.txt
│ └── vendors.js.map
└── public
Notes:
app.js,vendors.jsandapp.cssare also created inproductionmode.- The
sourcemapfiles are generated because ofdevtool: 'source-map' - When we deploy our project to the production server, we should use
npm run buildinstead ofnpm run start.
Conclusion
- Introduction
- Setup Webpack Project with Django
- Load Webpack bundles in Django
- Linting in Webpack
- Load Webpack hash bundle in Django
- Code splitting with Webpack
- How to config HMR with Webpack and Django
- How to use HtmlWebpackPlugin to load Webpack bundle in Django
If you want a quick start with Webpack and Django, please check python-webpack-boilerplate