- 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
Bootstrap
to the frontend project usingnpm install
. - Load
Webpack
bundle files with Djangostatic
template tag. - Learn the difference between
development
mode andproduction
mode 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.scss
file at the top.index.scss
is the SCSS index file. - And then we import
jquery
andbootstrap
js bundle file (They are installed bynpm install
and exists atnode_modules
) - Here we only use
jquery.slim
because 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
common
part 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 vendor
js 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.js
contains code fromsrc/index.js
- And
vendors.js
contains code fromjquery
andbootstrap
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 theDIRS
ofTEMPLATES
- We added
str(BASE_DIR / 'frontend/build')
toSTATICFILES_DIRS
so 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
head
we 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 ready
in 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: red
set 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.filename
tojs/[name].js
, because we do not need thechunkhash
here. (I will talk more about hash in later chapter) - We changed the
MiniCssExtractPlugin.filename
tocss/app.css
. Since dev and prod both contains this config, you can move them towebpack.common.js
on 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.js
andapp.css
are also created inproduction
mode.- The
sourcemap
files are generated because ofdevtool: 'source-map'
- When we deploy our project to the production server, we should use
npm run build
instead 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