How to use HtmlWebpackPlugin to load Webpack bundle in 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

In the previous chapter, we have learned to use django-webpack-loader to help us load Webpack bundle files in Django like this {% render_bundle 'app' 'js' %}

In this blog, I will show you another way to do this without ANY Django 3-party package?

By the end of this chapter, you should be able to:

  1. Learn what is HtmlWebpackPlugin and how it works.
  2. Use HtmlWebpackPlugin and Django Template inheritance to load Webpack bundle

HtmlWebpackPlugin

Let's first check webpack/webpack.common.js

plugins: [
  new CleanWebpackPlugin(),
  new CopyWebpackPlugin({ patterns: [{ from: Path.resolve(__dirname, '../public'), to: 'public' }] }),
  new HtmlWebpackPlugin({
    template: Path.resolve(__dirname, '../src/index.html'),
  }),
  new BundleTracker({filename: './webpack-stats.json'}),
],

Notes:

  1. In the plugins, we see HtmlWebpackPlugin
  2. The HtmlWebpackPlugin will generate an HTML5 file for you, which includes all the webpack bundles in the body using script tags.
  3. If you have any CSS assets in Webpack's output, then these will be included with tags in the element of generated HTML.
(frontend)$ npm run start

Check src/index.html

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>webpack starterkit</title>
</head>
<body>
  <h1>webpack starter</h1>
  <p>✨ A lightweight foundation for your next webpack based frontend project.</p>
</body>
</html>

Notes:

  1. The above file has no links for js and css files

Let's check build/index.html

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>webpack starterkit</title>
<link href="http://localhost:9091/css/app.css" rel="stylesheet"></head>
<body>
  <h1>webpack starter</h1>
  <p>✨ A lightweight foundation for your next webpack based frontend project.</p>
<script src="http://localhost:9091/js/jquery.js"></script><script src="http://localhost:9091/js/vendors.js"></script><script src="http://localhost:9091/js/app.js"></script></body>
</html>

Notes:

  1. The script and link tags has been added after npm run build
  2. We do not need to config which files to be imported, the plugin and Webpack would do that for us.

Workflow

Here is the workflow:

  1. We use HtmlWebpackPlugin to help us generate a frontend_base.html, which contains css and js links from Webpack.
  2. In Django, we use Template inheritance to make all Django templates to inherit from the above frontend_base.html

So the relationship would seem like this:

frontend_base.html        build by Webpack

    base.html             Django base template

        index.html
        blog.html
        about.html

Custom insertion

Actually, HtmlWebpackPlugin already contains example to show people how to do this.

Let's check custom-insertion-position

It has index.ejs

<!DOCTYPE html>
<html>
  <head>
    <%= htmlWebpackPlugin.tags.headTags %>
    <title>Custom insertion example</title>
  </head>
  <body>
    All scripts are placed here:
    <%= htmlWebpackPlugin.tags.bodyTags %>
    <script>console.log("Executed after all other scripts")</script>
  </body>
</html>
  1. <%= htmlWebpackPlugin.tags.headTags %> would inject css links
  2. <%= htmlWebpackPlugin.tags.bodyTags %> would inject js links.
  3. ejs means Embedded JavaScript templating, and you can check https://ejs.co/ to learn more.

frontend_base.html

Create src/index.ejs

{% block html %}

  {% block css %}
    <%= htmlWebpackPlugin.tags.headTags %>
  {% endblock %}

  {% block js %}
    <%= htmlWebpackPlugin.tags.bodyTags %>
  {% endblock %}

{% endblock %}

Notes:

  1. In ejs, it would only process code wrapped by <%= %>
  2. So code like {% block js %} would be treated as text, and still exists in the final html.

Let's update webpack/webpack.common.js

plugins: [
  new CleanWebpackPlugin({dangerouslyAllowCleanPatternsOutsideProject: true}),
  new CopyWebpackPlugin({ patterns: [{ from: Path.resolve(__dirname, '../public'), to: 'public' }] }),
  new HtmlWebpackPlugin({
    template: Path.resolve(__dirname, '../src/index.ejs'),
    filename: Path.resolve(__dirname, '../../django_webpack_app/templates/frontend-base.html'),
    inject:false,
  }),
  new BundleTracker({filename: './webpack-stats.json'}),
],

Notes:

  1. We deleted the previous HtmlWebpackPlugin from the plugins
  2. We added a new HtmlWebpackPlugin, which compile from src/index.ejs and output to the Django templates directory.
  3. inject: false, should be added so HtmlWebpackPlugin would not inject automatically.
  4. In CleanWebpackPlugin, we pass {dangerouslyAllowCleanPatternsOutsideProject: true, dry: false} to solve Cannot delete files/folders outside the current working directory error.
$ npm run start

In django_webpack_app/templates, we can see files

./django_webpack_app/templates
├── frontend-base.html
└── index.html

Check django_webpack_app/templates/frontend-base.html

{% block html %}

  {% block css %}
    <link href="http://localhost:9091/css/app.css" rel="stylesheet">
  {% endblock %}

  {% block js %}
    <script src="http://localhost:9091/js/jquery.js"></script><script src="http://localhost:9091/js/vendors.js"></script><script src="http://localhost:9091/js/app.js"></script>
  {% endblock %}

{% endblock %}

Django Template inheritance

Create django_webpack_app/templates/base.html

{% extends 'frontend-base.html' %}

{% block html %}
<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  {% block css %}
    {{ block.super }}
    {# we can add more css here #}
  {% endblock %}

</head>
<body>
{% block content %}{% endblock %}

{% block js %}
  {{ block.super }}
  {# we can add more js here #}
{% endblock %}

</body>
</html>
{% endblock %}

Notes:

  1. At the top, we {% extends 'frontend-base.html' %}
  2. We put the html code at the {% block html %}, to override the html block in frontend-base.html
  3. In {% block css %}, we use {{ block.super }} to get content from the parent template frontend-base.html. So css links would be inserted here. We can even add more css links as we like.
  4. The {% block js %} works in the same way.

Update django_webpack_app/templates/index.html

{% extends 'base.html' %}

{% block content %}
<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>
{% endblock %}

Notes:

  1. At the top, we {% extends 'base.html' %}
  2. We fill the {% block content %}

Now if we check the page in Django server, the js and css should work without any issues.

Conclusion

  1. The django_webpack_app/templates/frontend-base.html should be generated by HtmlWebpackPlugin, please remember to add it to gitignore
  2. With this solution, we do not need django-webpack-loader or other 3-party Django packages.
  3. Some open source projects also use this strategy, for example, you can check django-vue-cli-webpack-demo
  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