Setup Webpack Project with Django

Table of Contents

  1. Introduction
  2. Setup Webpack Project with Django
  3. Load Webpack bundles in Django
  4. Linting in Webpack
  5. Load Webpack hash bundle in Django
  6. Code splitting with Webpack
  7. How to config HMR with Webpack and Django
  8. 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:

  1. Create a Webpack project and make it run in watch mode.
  2. Understand what is Webpack loader
  3. Learn how to use Webpack loaders to work with SCSS and modern Javascript

Django Project

Before we work on Webpack part, let's first create a Django project.

Create Project

$ mkdir django_webpack && cd django_webpack
$ python3 -m venv env
$ source env/bin/activate

You can also use other tool such as Poetry or Pipenv

Create requirements.txt

django
(env)$ pip install -r requirements.txt
(env)$ env/bin/django-admin.py startproject django_webpack_app .

We will see structure like this

.
├── django_webpack_app
├── env
├── manage.py
└── requirements.txt

Run DevServer

Let's run the Django application.

# migrate db.sqlite3
(env)$ ./manage.py migrate

# runserver
(env)$ ./manage.py runserver

System check identified no issues (0 silenced).
January 22, 2021 - 02:10:17
Django version 3.1.5, using settings 'django_webpack_app.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.
  1. Now if we visit http://127.0.0.1:8000/ we will see welcome page which has The install worked successfully! Congratulations!.
  2. We will see db.sqlite3 is created at the project directory, here we did not specify Django to use other db so sqlite is used by default.

Webpack project

Please make sure you have node and nvm installed. If not, you can get it:

  1. On nodejs homepage
  2. Using nvm I recommend this way.
$ node -v
v12.20.0
$ npm -v
6.14.8

Basic Concepts

webpack is an open-source JavaScript module bundler. It is made primarily for JavaScript, but it can transform front-end assets such as HTML, CSS, and images if the corresponding loaders are included

Now Webpack is the most popular bundle solution in frontend community, so we will learn how to config it and use it with Django project.

Setup

Considering webpack is a little complicated to newbie developers, we can use some awesome boilerplate project on the Github to help us quickly get started and then dive deeper.

Here I recommend wbkd/webpack-starter, which have all the features we need and is actively maintained.

Let's clone the project to the frontend directory.

$ git clone [email protected]:wbkd/webpack-starter.git frontend

.
├── db.sqlite3
├── django_webpack_app
├── frontend
│   ├── LICENSE
│   ├── README.md
│   ├── package-lock.json
│   ├── package.json
│   ├── postcss.config.js
│   ├── public
│   ├── src
│   │   ├── index.html
│   │   ├── scripts
│   │   │   └── index.js
│   │   └── styles
│   │       └── index.scss
│   └── webpack
│       ├── webpack.common.js
│       ├── webpack.config.dev.js
│       └── webpack.config.prod.js
├── manage.py
└── requirements.txt

Notes:

  1. frontend/webpack contains config file for webpack, the config files are split for development and production.
  2. frontend/src contains the source js, scss and index.html. I will talk about them in a bit.
  3. Please note that there are some dot files (.babelrc, .eslintrc, .stylelintrc.json) in the frontend directory, they are required to make the Webpack project work.

Now let's run the frontend project

$ cd frontend

# delete .git directory in frontend directory
(frontend)$ rm -rf ./.git

# install dependency in frontend/node_modules
(frontend)$ npm install

# run start command
(frontend)$ npm run start

Project is running at http://localhost:8080/

If we visit http://localhost:8080/ we will see the successful message.

Press Ctrl-C to terminate the npm run start

Start command

Now the frontend app can run without error but we still need to update some config to make it work with our Django project.

Update scripts.start in wagtail_project/frontend/package.json

{
  "scripts": {
    "start": "webpack --watch --config webpack/webpack.config.dev.js"
  },
}

Notes:

  1. We changed the start command to webpack --watch --config webpack/webpack.config.dev.js
  2. --watch means we run webpack in mode mode (webpack can watch files and recompile whenever they change)

Why we do that?

  1. The default start command is webpack serve --config webpack/webpack.config.dev.js, which would launch webpack-dev-server on local.
  2. webpack-dev-server doesn't write any output files after compiling. Instead, it keeps bundle files in memory and serves them as if they were real files mounted at the server's root path
  3. This makes webpack-dev-server not work with Django in easy way, so here we choose to use watch mode which is easy to understand.
  4. I will talk about how to use webpack-dev-server with Django in later chapter.
$ cd frontend

# run start command
(frontend)$ npm run start

[webpack-cli] watching files for updates...

Congratulations! Now the webpack can work in watch mode.

Press Ctrl-C to terminate the npm run start

We can see something like this in the frontend/build

./build
├── index.html
├── js
│   ├── app.js
│   └── vendors-node_modules_css-loader_dist_runtime_api_js-node_modules_css-loader_dist_runtime_cssW-926fd9.js
└── public

Next, let's check the webpack config to understand how it works.

Webpack Entry and Output

Let's check webpack/webpack.common.js

module.exports = {
  entry: {
    app: Path.resolve(__dirname, '../src/scripts/index.js'),
  },
  output: {
    path: Path.join(__dirname, '../build'),
    filename: 'js/[name].js',
  },
}

Notes:

  1. The entry define the entry of the frontend app.
  2. The output define the build directory and the bundle filename. We will learn more about this in later chapter.

Webpack loader For SCSS

What is Webpack loader?

Loaders are transformations that are applied to the source code of a module. They allow you to pre-process files as you import or “load” them.

Loaders can be chained. Each loader in the chain applies transformations to the processed resource. A chain is executed in reverse order.

Let's check module.rules in frontend/webpack/webpack.config.dev.js

{
  test: /\.s?css$/i,
  use: ['style-loader', 'css-loader?sourceMap=true', 'postcss-loader', 'sass-loader'],
},

Notes:

  1. The test is a JS regex and it would match scss and css filenames
  2. As we know, the loaders can be a chain which is executed in reverse order
  3. So the loaders would work like style-loader(css-loader?sourceMap=true(postcss-loader(sass-loader(file.scss))))

Next, let's update frontend/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',
  },
  devServer: {
    inline: true,
    hot: true,
  },
  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'],
      },
    ],
  },
});
  1. We import MiniCssExtractPlugin at the top
  2. new MiniCssExtractPlugin({filename: 'css/app.css',}) in the plugins tell Webpack to put all css code into one single file app.css
  3. In the rule for css/scss file, we removed the first style-loader, which inject css into the dom because we do not need it in this project.
  4. MiniCssExtractPlugin.loader would make sure all css code would be put into app.css.

So here is the workflow:

  1. sass-loader compile scss to css
  2. postcss-loader would parse css and add vendor prefixes to CSS rules.
  3. css-loader interprets @import and url() in css.
  4. MiniCssExtractPlugin.loader extract css code to app.css

As for the sourcemap,

  1. We changed the devtool to inline-cheap-source-map for better SCSS debug. (This is optional)

Now, let's rerun the npm run start, and we can see files like this

./build
├── css
│   └── app.css
├── index.html
├── js
│   └── app.js
└── public

Notes:

  1. css/app.css is created.
  2. If we check app.css, we can see code /*# sourceMappingURL=data:application/json; at the bottom of the file, which is soucemap.

Webpack loader for modern Javascript

Let's check frontend/webpack/webpack.config.dev.js again!

module: {
  rules: [
    {
      test: /\.js$/,
      include: Path.resolve(__dirname, '../src'),
      loader: 'babel-loader',
    },
  ],
},

Notes:

  1. babel-loader is to make Babel to work with Webpack.

Babel is a free and open-source JavaScript transcompiler that is mainly used to convert ECMAScript 2015+ (ES6+) code into a backwards compatible version of JavaScript that can be run by older JavaScript engines.

With babel-loader, we can write JS code in modern syntax and the code can still work on old browser.

The babel config is in frontend/.babelrc, you can check babeljs doc to learn more.

Conclusion

  1. Introduction
  2. Setup Webpack Project with Django
  3. Load Webpack bundles in Django
  4. Linting in Webpack
  5. Load Webpack hash bundle in Django
  6. Code splitting with Webpack
  7. How to config HMR with Webpack and Django
  8. 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

Launch Products Faster with Django

Unlock the power of Django combined with Hotwire through SaaS Hammer. Supercharge productivity, tap into Python's rich ecosystem, and focus on perfecting your product!

Michael Yin

Michael Yin

Michael is a Full Stack Developer who loves writing code, tutorials about Django, and modern frontend techs.

He has published some tech course on testdriven.io and ebooks on leanpub.

© 2024 SaaS Hammer