Django and Webpack – Implementing modern Javascript Tools

I am working on a Django project and want to enhance the front-end using npm and webpack. My web development experience has been primarily with Django, and I’m new to npm and webpack. I’ve run into a few issues during this integration process and need some guidance.

Here are some of the libraries I am including in my project:

  • Django v3.2
  • python 3.7.13
  • Node Libraries I am using
    • Bootstrap v5.3.3
    • Bootstrap-icons v1.11.3
    • jQuery v3.7.1
    • jQuery-ui v1.13.3
    • jQuery-validation v1.20.0
    • metro4-dist v4.5.0
    • webpack v5.91.0
    • webpack-cli v5.1.4
    • webpack-dev-server v5.0.4
    • [etc]

Tutorials/Guides I’ve Used

Sharing guides I used to help others in my scenario along their way.

  • Modern JavaScript for Django Developers : This helped me understand why npm and webpack were necessary.
  • Definitive Guide to Django and Webpack : Great guide for detailed configuration.
  • Learn Webpack – Full Tutorial for Beginners : Video that walks you through setup
  • Django with Webpack : Another video explaining how to configure webpack with a Django Project.

Coming from “Modern Javascript for Django” put it best for me on my situation.

First you learn Django. You go through the tutorial, learn about
models and views, and get your very first “hello world” application up
and running. You’re on a roll!

Then, inevitably, you want to do something that requires a little
front-end interaction. Maybe a modal dialog, some real-time form
validation, or a mobile menu. It doesn’t matter.

“No problem!” you say. “I’ll use JavaScript for this!”

Django makes it easy to drop JavaScript directly into your HTML
templates, so you do some Googling and copy/paste some code from Stack
Overflow, maybe import jQuery and a plugin or two, and you’re up and
running.

And it works great. Still on a roll!

Then, over time, there are more modals and more menus and more pages.
You start doing more complex things like managing state, dynamically
rendering components, and sharing code across templates.

It starts to feel unwieldy, but you keep pressing on. A document
selector here, an event callback there, maybe a few more libraries
imported on various pages as you need them. The front-end code slowly
expands through your Django templates and static files until it feels
like JavaScript has crept its tentacles throughout the entire
codebase.

At some point, your front-end code may start to feel like a spaghetti
monster—a tangled mess of code and dependencies spread across your
JavaScript and Django template files.

There are some great tutorials I have been able to find, but none seem to cater exactly where I need help. I think most of my issues boil down learning the concepts while following guides that have changed in implementation (i.e. webpack4 > webpack5)

Issues

  1. openBrowser() does not work as intended

    The function I wrote to launch a browser on the webpack-dev-server does not work as expected. Any suggestions for achieving this?

  2. Configuring webpack-dev-server with Django for HMR

    Integrating webpack-dev-server with Django’s server for Hot Module Replacement has been challenging. I tried a few different methods, but I kept getting a blank webpage with the error “Cannot Get /” based on guides I followed.

    Currently, what does work is the proxy option on the devServer config and writeToDisk option for devMiddleware. I question if there is a better/optimal approach? Can I make it where Django’s server can read the bundled files via memory?

  3. Bundled Files Not Recognized with Certain URLs

    When viewing the browser’s dev tools, I can see the page source for my bundeled files when my URL is:

    • http://localhost:8080/
    • http://localhost:8080/help
    • http://localhost:8080/sysetms/
    • http://localhost:8080/sites/

    But my bundled files are not recognized if I try to navigate to a page like:

    • http://localhost:8080/report/site/[some_name]/
    • http://localhost:8080/report/system/[some_name]/

    How can I troubleshoot and resolve this issue?

  4. webpack-dev-server launches http://localhost:80801/

    Why is port 8081 being used instead of 8080? Is this because Django’s server is using 8080 too? I can manually go to port 8080 and my application is available, but I don’t understand why this is happening.

Configuration

Notes

  • I utilize some of Django’s template tags to help with organization of sub templates (i.e. {% include subtemplate.html %} or {$% block content %} {% endblock content %} so not all HTML templates need the bundled files on page load – that is why I use the inject: false option for HtmlWebpackPlugin.
  • I have several templates that Django has to be able to recognize, so I use the HtmlWebpackPlugin to create all those HTML templates in the static/ folder of root directory.
  • When running the webpack-dev-server, I want to default using Google Chrome browser. If that is not available on the host machine, then use the host’s default browser.
  • I use a lot of SVG sprites from Bootstrap-icons throughout my templates, one icon from metro4-dist, and I have a few .pdf documents that I want webpack to create in the dist/ folder under their respective directories. I read its better to make SVG’s inline vs creating there own file, correct?
  • I know by default, webpack places the output files in dist/ and Django looks for its source files in static/, so the configuration has to change for one of them. I made Django static url read from the “/dist/”

package.json

{
    "name": "[removed for privacy]",
    "version": "1.0.0",
    "private": true,
    "description": "[removed for privacy]",
    "exports": "./index.js",
    "scripts": {
        "start": "webpack-dev-server --config webpack/webpack.config.dev.js",
        "build": "webpack --config webpack/webpack.config.prod.js"
    },
    "keywords": [],
    "author": "",
    "license": "ISC",
    "devDependencies": {
        "clean-webpack-plugin": "^4.0.0",
        "css-loader": "^7.1.1",
        "css-minimizer-webpack-plugin": "^6.0.0",
        "html-loader": "^5.0.0",
        "html-webpack-plugin": "^5.6.0",
        "mini-css-extract-plugin": "^2.9.0",
        "open": "^10.1.0",
        "prettier": "^3.2.5",
        "prettier-plugin-organize-attributes": "^1.0.0",
        "sass": "^1.77.0",
        "sass-loader": "^14.2.1",
        "style-loader": "^4.0.0",
        "terser-webpack-plugin": "^5.3.10",
        "webpack": "^5.91.0",
        "webpack-cli": "^5.1.4",
        "webpack-dev-server": "^5.0.4",
        "webpack-merge": "^5.10.0"
    },
    "dependencies": {
        "@floating-ui/dom": "^1.6.4",
        "bootstrap": "^5.3.3",
        "bootstrap-icons": "^1.11.3",
        "jquery": "^3.7.1",
        "jquery-ui": "^1.13.3",
        "jquery-validation": "^1.20.0",
        "metro4-dist": "^4.5.0"
    },
    "type": "module",
    "engines": {
        "node": ">=16"
    }
}

webpack.config.common.js

import HtmlWebpackPlugin from "html-webpack-plugin"
import path from "path"
import {fileURLToPath} from "url"

const __dirname = path.dirname(fileURLToPath(import.meta.url))

export default {
    entry: {
        vendor: path.resolve(__dirname, "../src/vendor.js"),
        main: path.resolve(__dirname, "../src/index.js"),
    },
    plugins: [
        new HtmlWebpackPlugin({
            template: path.resolve(__dirname, "../src/templates/base.html"),
            filename: path.resolve(__dirname, "../templates/base.html"),
            minify: false,
            favicon: path.resolve(__dirname, "../static/images/favicon.ico"),
        }),
        new HtmlWebpackPlugin({
            template: path.resolve(
                __dirname,
                "../src/templates/circuit_form.html",
            ),
            filename: path.resolve(__dirname, "../templates/circuit_form.html"),
            minify: false,
            inject: false,
        }),
        new HtmlWebpackPlugin({
            template: path.resolve(
                __dirname,
                "../src/templates/circuit_report.html",
            ),
            filename: path.resolve(
                __dirname,
                "../templates/circuit_report.html",
            ),
            minify: false,
            inject: false,
        }),
        new HtmlWebpackPlugin({
            template: path.resolve(
                __dirname,
                "../src/templates/csd-series_form.html",
            ),
            filename: path.resolve(
                __dirname,
                "../templates/csd-series_form.html",
            ),
            minify: false,
            inject: false,
        }),
        new HtmlWebpackPlugin({
            template: path.resolve(
                __dirname,
                "../src/templates/csd-series_report.html",
            ),
            filename: path.resolve(
                __dirname,
                "../templates/csd-series_report.html",
            ),
            minify: false,
            inject: false,
        }),
        new HtmlWebpackPlugin({
            template: path.resolve(__dirname, "../src/templates/footer.html"),
            filename: path.resolve(__dirname, "../templates/footer.html"),
            minify: false,
            inject: false,
        }),
        new HtmlWebpackPlugin({
            template: path.resolve(__dirname, "../src/templates/help.html"),
            filename: path.resolve(__dirname, "../templates/help.html"),
            minify: false,
            inject: false,
        }),
        new HtmlWebpackPlugin({
            template: path.resolve(__dirname, "../src/templates/home.html"),
            filename: path.resolve(__dirname, "../templates/home.html"),
            minify: false,
            inject: false,
        }),
        new HtmlWebpackPlugin({
            template: path.resolve(__dirname, "../src/templates/navbar.html"),
            filename: path.resolve(__dirname, "../templates/navbar.html"),
            minify: false,
            inject: false,
        }),
        new HtmlWebpackPlugin({
            template: path.resolve(__dirname, "../src/templates/results.html"),
            filename: path.resolve(__dirname, "../templates/results.html"),
            minify: false,
            inject: false,
        }),
        new HtmlWebpackPlugin({
            template: path.resolve(
                __dirname,
                "../src/templates/site_form.html",
            ),
            filename: path.resolve(__dirname, "../templates/site_form.html"),
            minify: false,
            inject: false,
        }),
        new HtmlWebpackPlugin({
            template: path.resolve(
                __dirname,
                "../src/templates/site_report.html",
            ),
            filename: path.resolve(__dirname, "../templates/site_report.html"),
            minify: false,
            inject: false,
        }),
        new HtmlWebpackPlugin({
            template: path.resolve(
                __dirname,
                "../src/templates/system_form.html",
            ),
            filename: path.resolve(__dirname, "../templates/system_form.html"),
            minify: false,
            inject: false,
        }),
        new HtmlWebpackPlugin({
            template: path.resolve(
                __dirname,
                "../src/templates/system_report.html",
            ),
            filename: path.resolve(
                __dirname,
                "../templates/system_report.html",
            ),
            minify: false,
            inject: false,
        }),
    ],
}

webpack.config.dev.js

import MiniCssExtractPlugin from "mini-css-extract-plugin"
import open from "open"
import path from "path"
import {fileURLToPath} from "url"
import {merge} from "webpack-merge"
import common from "./webpack.config.common.js"

const __dirname = path.dirname(fileURLToPath(import.meta.url))
const url = "http://localhost:8080"

function getBrowserName() {
    if (process.platform === "darwin") {
        return "Google Chrome"
    } else if (process.platform === "linux") {
        return "google-chrome"
    } else {
        return "chrome"
    }
}

async function openBrowser(url) {
    const browserName = getBrowserName()
    try {
        await open(url, {
            app: {
                name: browserName,
            },
        })
        console.log(`Opened devServer with URL: ${url}`)
    } catch (error) {
        await open(url)
        console.error(
            "Failed to open devServer with Google Chrome browser. Opening in default browser instead.",
        )
    }
}

const config = merge(common, {
    mode: "development",
    output: {
        filename: "[name].bundle.js",
        path: path.resolve(__dirname, "../dist"),
        clean: true,
    },
    devtool: "source-map",
    devServer: {
        hot: true,
        proxy: [
            {
                context: ["**"],
                target: url,
            },
        ],
        open: openBrowser(url),
        devMiddleware: {
            writeToDisk: true,
        },
    },
    plugins: [new MiniCssExtractPlugin({filename: "[name].css"})],
    module: {
        rules: [
            {
                test: /.scss$/i,
                use: [
                    MiniCssExtractPlugin.loader, // 3. Extract css into its own file
                    "css-loader", // 2. Turns css into commonjs
                    "sass-loader", // 1. Turns sass into css
                ],
            },
            {
                test: /.html$/i,
                use: ["html-loader"],
            },
            {
                test: /.svg$/i,
                type: "asset/inline",
                generator: {
                    filename: "images/[name][ext]",
                },
            },
            {
                test: /.(png|jpe?g|gif)$/i,
                type: "asset/resource",
                generator: {
                    filename: "images/[name][ext]",
                },
            },
            {
                test: /.(pdf|txt)$/i,
                type: "asset/resource",
                generator: {
                    filename: "documents/[name][ext]",
                },
            },
        ],
    },
})

export default config

webpack.config.prod.js

import CssMinimizerWebpackPlugin from "css-minimizer-webpack-plugin"
import MiniCssExtractPlugin from "mini-css-extract-plugin"
import path from "path"
import TerserPlugin from "terser-webpack-plugin"
import {fileURLToPath} from "url"
import {merge} from "webpack-merge"
import common from "./webpack.config.common.js"

const __dirname = path.dirname(fileURLToPath(import.meta.url))

const config = merge(common, {
    mode: "production",
    output: {
        filename: "[name].[contenthash].bundle.js",
        path: path.resolve(__dirname, "../dist"),
        clean: true,
    },
    optimization: {
        minimizer: [new CssMinimizerWebpackPlugin(), new TerserPlugin()],
    },
    plugins: [new MiniCssExtractPlugin({filename: "[name].[contenthash].css"})],
    module: {
        rules: [
            {
                test: /.scss$/i,
                use: [
                    MiniCssExtractPlugin.loader, // 3. Extract css into its own file
                    "css-loader", // 2. Turns css into commonjs
                    "sass-loader", // 1. Turns sass into css
                ],
            },
            {
                test: /.html$/i,
                use: ["html-loader"],
            },
            {
                test: /.svg$/i,
                type: "asset/inline",
                generator: {
                    filename: "images/[name].[hash][ext]",
                },
            },
            {
                test: /.(png|jpe?g|gif)$/i,
                type: "asset/resource",
                generator: {
                    filename: "images/[name].[hash][ext]",
                },
            },
            {
                test: /.(pdf|txt)$/i,
                type: "asset/resource",
                generator: {
                    filename: "documents/[name].[hash][ext]",
                },
            },
        ],
    },
})

export default config

Django settings.py

[..some code..]

# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/3.2/howto/static-files/

# These settings are for Django serving the static files (Custom files)
STATIC_URL = "/dist/"
STATICFILES_DIRS = [
    os.path.join(BASE_DIR, "static"),
    os.path.join(BASE_DIR, "dist"),
]

# This setting is for Apache serving the static files (Admin Page)
STATIC_ROOT = os.path.join(BASE_DIR, "static/admin")

Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa Dịch vụ tổ chức sự kiện 5 sao Thông tin về chúng tôi Dịch vụ sinh nhật bé trai Dịch vụ sinh nhật bé gái Sự kiện trọn gói Các tiết mục giải trí Dịch vụ bổ trợ Tiệc cưới sang trọng Dịch vụ khai trương Tư vấn tổ chức sự kiện Hình ảnh sự kiện Cập nhật tin tức Liên hệ ngay Thuê chú hề chuyên nghiệp Tiệc tất niên cho công ty Trang trí tiệc cuối năm Tiệc tất niên độc đáo Sinh nhật bé Hải Đăng Sinh nhật đáng yêu bé Khánh Vân Sinh nhật sang trọng Bích Ngân Tiệc sinh nhật bé Thanh Trang Dịch vụ ông già Noel Xiếc thú vui nhộn Biểu diễn xiếc quay đĩa Dịch vụ tổ chức tiệc uy tín Khám phá dịch vụ của chúng tôi Tiệc sinh nhật cho bé trai Trang trí tiệc cho bé gái Gói sự kiện chuyên nghiệp Chương trình giải trí hấp dẫn Dịch vụ hỗ trợ sự kiện Trang trí tiệc cưới đẹp Khởi đầu thành công với khai trương Chuyên gia tư vấn sự kiện Xem ảnh các sự kiện đẹp Tin mới về sự kiện Kết nối với đội ngũ chuyên gia Chú hề vui nhộn cho tiệc sinh nhật Ý tưởng tiệc cuối năm Tất niên độc đáo Trang trí tiệc hiện đại Tổ chức sinh nhật cho Hải Đăng Sinh nhật độc quyền Khánh Vân Phong cách tiệc Bích Ngân Trang trí tiệc bé Thanh Trang Thuê dịch vụ ông già Noel chuyên nghiệp Xem xiếc khỉ đặc sắc Xiếc quay đĩa thú vị
Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa
Thiết kế website Thiết kế website Thiết kế website Cách kháng tài khoản quảng cáo Mua bán Fanpage Facebook Dịch vụ SEO Tổ chức sinh nhật