- 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 default, Django devserver
will reload if we change .py
file of the project.
In this blog, let's add the live reloading
feature to the frontend project, if we edit SCSS
or JS
, the page will reload automatically.
By the end of this chapter, you should be able to:
- Learn to make
webpack-dev-server
work smoothly with Django - Understand what is Hot Module Replacement (HMR) and the workflow
Advantage
In the previous chapter, we are using Webpack watch mode
. (webpack can watch files and recompile whenever they change)
So some people might ask, why do we need webpack-dev-server
?
webpack-dev-server will launch a server process, which makes
live reloading
possible.
So the whole workflow can work like this:
- We edit
js
orscss
in the editor. - The
webpack-dev-server
detect the change, it recompile the files. And send message to the browser. (It is built on WebSocket and I will talk about in a bit) - Browser refresh the page automatically to let us see the change in realtime. (We do not have to manually refresh the page anymore)
Next, let's migrate from webpack watch mode
to server
mode.
Config
Update frontend/package.json
"start": "webpack serve --config webpack/webpack.config.dev.js"
We changed the start
command to webpack server
so it would launch webpack-dev-server
Update webpack/webpack.config.dev.js
const Path = require('path');
const Webpack = require('webpack');
const { merge } = require('webpack-merge');
const StylelintPlugin = require('stylelint-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const common = require('./webpack.common.js');
module.exports = merge(common, {
mode: 'development',
devtool: 'inline-cheap-source-map',
output: {
chunkFilename: 'js/[name].chunk.js',
publicPath: 'http://localhost:9091/',
},
devServer: {
inline: true,
hot: true,
port: 9091,
},
plugins: [
new Webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify('development'),
}),
new StylelintPlugin({
files: Path.join('src', '**/*.s?(a|c)ss'),
}),
new MiniCssExtractPlugin({filename: 'css/app.css',})
],
module: {
rules: [
{
test: /\.js$/,
include: Path.resolve(__dirname, '../src'),
enforce: 'pre',
loader: 'eslint-loader',
options: {
emitWarning: true,
},
},
{
test: /\.html$/i,
loader: 'html-loader',
},
{
test: /\.js$/,
include: Path.resolve(__dirname, '../src'),
loader: 'babel-loader',
},
{
test: /\.s?css$/i,
use: [MiniCssExtractPlugin.loader, 'css-loader?sourceMap=true', 'postcss-loader', 'sass-loader'],
},
],
},
});
Notes:
- We can add config of the
webpack-dev-server
in thedevServer
- Here we set the
port: 9091
to specify the port the server is listening - Please note we changed
publicPath
from/static/
tohttp://localhost:9091/
. Because now the bundle files are served bywebpack-dev-server
(frontend)$ npm run start
# run in another terminal
(frontend)$ cat webpack-stats.json
{
"status": "done",
"chunks": {
"app": [
"js/jquery.js",
"js/vendors.js",
"css/app.css",
"js/app.js"
]
},
"publicPath": "http://localhost:9091/",
"assets": {
"css/app.css": {
"name": "css/app.css",
"publicPath": "http://localhost:9091/css/app.css"
},
"js/app.js": {
"name": "js/app.js",
"publicPath": "http://localhost:9091/js/app.js"
},
"js/jquery.js": {
"name": "js/jquery.js",
"publicPath": "http://localhost:9091/js/jquery.js"
},
"js/vendors.js": {
"name": "js/vendors.js",
"publicPath": "http://localhost:9091/js/vendors.js"
},
"public/.gitkeep": {
"name": "public/.gitkeep",
"publicPath": "http://localhost:9091/public/.gitkeep"
},
"index.html": {
"name": "index.html",
"publicPath": "http://localhost:9091/index.html"
}
}
}
Notes:
- The bundle files are server on
http://localhost:9091
- We can test by visiting
http://localhost:9091/js/app.js
in the browser. - We can also check
http://localhost:9091/webpack-dev-server
to see what files are served bywebpack-dev-server
- If we check the
frontend/build
directory, we can see the directory is empty. I will talk about it in next section.
writeToDisk
By default, the webpack-dev-server
would serve files from memory, which means, we can not see the bundle files on the disk. (frontend/build
in this project)
However, we can change this behavior to make it work the same as webpack watch
mode.
Update webpack/webpack.config.dev.js
devServer: {
inline: true,
hot: true,
port: 9091,
writeToDisk: true,
},
- We set
devServer.writeToDisk
totrue
Now if we rerun the npm run start
, we can see the files in frontend/build
directory.
./build
├── css
│ └── app.css
├── index.html
├── js
│ ├── app.js
│ ├── jquery.js
│ └── vendors.js
└── public
Hot Module Replacement (HMR)
Workflow
The HMR feature is built on WebSocket
.
WebSocket is a computer communications protocol, providing full-duplex communication channels over a single TCP connection.
- With WebSocket, the browser and server would establish a long-lived connection. They can send or receive message between each other.
- If
webpack-dev-server
detect code change, it would recompile and send message to the browser. - Then browser would refresh the web page after receiving the message.
Config
Update webpack/webpack.config.dev.js
const Path = require('path');
const Webpack = require('webpack');
const { merge } = require('webpack-merge');
const StylelintPlugin = require('stylelint-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const common = require('./webpack.common.js');
module.exports = merge(common, {
target: 'web',
mode: 'development',
devtool: 'inline-cheap-source-map',
output: {
chunkFilename: 'js/[name].chunk.js',
publicPath: 'http://localhost:9091/',
},
devServer: {
inline: true,
hot: true,
port: 9091,
writeToDisk: true,
},
// ...
});
Notes:
- Here we add
target: 'web'
, we do this because of the bug of webpack-dev-server - The
inline: true,
is needed (a script will be inserted in your bundle to take care of live reloading) - The
hot: true,
indevServer
means the HMR feature is enabled withwebpack-dev-server
.
Next, let's check how HMR work with webpack-dev-server.
- Rerun
npm run start
- Open devtool in the browser.
- Click the
network
tab so you can check the web requests. - In
network
tab, click theWS
(WebSocket) - Visit
http://localhost:9091
in browser. (The html come fromsrc/index.html
and I will talk about it later)
We would see something like this
Notes:
- A websocket
ws://localhost:9091/sockjs-node/346/dtoksgqu/websocket
has been established. - In the
messages
, we can see the detailed messages from the webpack-dev-server.
Next, let's update 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 from michaelyin");
});
Notes:
- We changed the success message in
window.console.log
- We will see the web page refreshed automatically and
dom ready from michaelyin
show up on theconsole
of the browserdevtool
HMR with Django
Now we already have a good understanding of HMR and let's make it work with Django.
(env)$ ./manage.py runserver
If we try to change src/index.js
and see if it works, we get exception from the console
of the browser devtool.
Access to fetch at 'http://localhost:9091/app.5c361815e51c8082dd52.hot-update.json' from origin 'http://127.0.0.1:8000' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
Notes:
- Here we try to fetch some file on
localhost:9091
from origin127.0.0.1:8000
in browser. - The browser denied this because this is risky.
- You can check Cross-origin resource sharing to learn more.
Update webpack/webpack.config.dev.js
devServer: {
inline: true,
hot: true,
port: 9091,
writeToDisk: true,
headers: {
"Access-Control-Allow-Origin": "*",
}
},
Notes:
- We added
"Access-Control-Allow-Origin": "*"
to theheaders
soCORS error
would be resolved.
Now rerun npm run start
, and update src/styles/index.scss
$primary: red;
@import "~bootstrap/scss/functions";
@import "~bootstrap/scss/variables";
@import "~bootstrap/scss/mixins";
...
Notes:
- This time, we update the
index.scss
and see if it works - We add
$primary: red;
to the top. - We can see the button color changed automatically.
The console of the browser devtool also have output like this
[HMR] Updated modules:
[HMR] - ./src/styles/index.scss
[HMR] App is up to date.
[HMR] Reload all css
If we check the network
tab carefully, we will see the web page is not full reloaded.
Conclusion
You can also check Hot Module Replacement to learn more about HMR
.
- 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