Using Vite.js with Django 3.x

Create a new Django app

I prefer to keep ma front-end code contained to a single Django app, which is convenient for most Django websites.

Create a new app called theme.

django-admin startapp theme

In your settings.py , make sure you add the newly created app to your INSTALLED_APPS

INSTALLED_APPS = [
    ...
    'django.contrib.staticfiles',
    'theme',
]
<PROJECT_ROOT>
├── db.sqlite3
├── manage.py
├── requirements.txt
├── theme
    ├── __init__.py
    ├── apps.py
    ├── views.py
    ├── models.py
    └── templates/
        └── theme
            └── index.html

Setup Vite

  1. Create a main.js and a main.css file that’ll serve as an entry point.
// main.js

import "./main.css"
console.log("hello world");
// main.css
body {
    background-color: tomato;
}
  1. Install dependencies. In our newly created app theme, we’ll setup a package.json. The only dependency we’ll be using is vite, as it’s already packing everything you need to compile js and css.
cd PROJECT_ROOT/theme
npm init -y  
npm -S install vite 
  1. Under PROJECT_ROOT/theme/ create a vite.config.js
// vite.config.js

const { resolve } = require('path');

export default {
    build: {
        manifest: true, // adds a manifest.json
        rollupOptions: {
            input: [
              resolve(__dirname, './main.js'),
            ]
        },
        outDir:  'static', // puts the manifest.json in PROJECT_ROOT/theme/static/
        assetsDir:  'theme', // puts asset files in in PROJECT_ROOT/theme/static/theme
    },
    plugins: [],
    server: {
        port: 3001, // make sure this doesn't conflict with other ports you're using
        open: false,
    }
};

In your package.json,

"scripts": {
    "theme:dev": "vite",
    "theme:build": "vite build"
}

Now you’re able to run your build.

  • For production, by simply running npm run theme:build. Vite build with Django
  • For development, Vite build with Django

Use the compiled assets

In PROJECT_ROOT/theme, create a new python module templatetags and add a vite.py file in it

cd PROJECT_ROOT/theme
mkdir templatetags/
touch templatetags/__init__.py
touch templatetags/vite.py

The final structure should look like the following:

<PROJECT_ROOT>
├── db.sqlite3
├── manage.py
├── requirements.txt
└── theme
    ├── __init__.py
    ├── apps.py
    ├── views.py
    ├── models.py
    ├── templates/
    └── templatetags/
        ├── __init__.py
        └── vite.py

Now invite templatetags/vite.py, add:

# PROJECT_ROOT/theme/templatetags/vite.py

from os import path
import re
import json
from django import template
from django.conf import settings
from django.utils.safestring import mark_safe
from django.templatetags.static import static

register = template.Library()

is_absolute_url = lambda url: re.match("^https?://", url)

DEV = settings.DEBUG
DEV_SERVER_ROOT = "http://localhost:3001"


def vite_manifest(entries_names):
    app_name = 'theme'
    manifest_filepath = path.join(app_name, 'static/manifest.json')


    if DEV:
        scripts = [
            f"{DEV_SERVER_ROOT}/@vite/client",
        ]

        for name in entries_names:
            scripts.append(f'{DEV_SERVER_ROOT}/{name}')

        styles = []
        return scripts, styles
    else:
        with open(manifest_filepath) as fp:
            manifest = json.load(fp)
        _processed = set()

        def _process_entries(names):
            scripts = []
            styles = []

            for name in names:
                if name in _processed:
                    continue

                chunk = manifest[name]

                import_scripts, import_styles = _process_entries(chunk.get('imports', []))
                scripts += import_scripts
                styles += import_styles

                scripts += [chunk['file']]
                styles += [css for css in chunk.get('css', [])]

                _processed.add(name)

            return scripts, styles

        return _process_entries(entries_names)


@register.simple_tag(name="vite_styles")
def vite_styles(*entries_names):
    """
    Populate an html template with styles generated by vite

    Usage::

        {% vite_styles 'main.js' 'other-entry.js' %}

    Examples::
        <head>
            ...
            {% vite_styles 'main.js' 'other-entry.js' %}
        </head>
    """
    _, styles = vite_manifest(entries_names)
    styles = map(lambda href: href if is_absolute_url(href) else static(href), styles)
    as_link_tag = lambda href: f'<link rel="stylesheet" href="{href}" />'
    return mark_safe("\n".join(map(as_link_tag, styles)))


@register.simple_tag(name="vite_scripts")
def vite_scripts(*entries_names):
    """
    Populate an html template with script tags generated by vite

    Usage::

        {% vite_scripts 'main.js' 'other-entry.js' %}

    Examples::
        <body>
            <!-- Your HTML -->
            {% vite_scripts 'main.js' 'other-entry.js' %}
        </body>
    """
    scripts, _ = vite_manifest(entries_names)
    scripts = map(lambda src: src if is_absolute_url(src) else static(src), scripts)
    as_script_tag = lambda src: f'<script type="module" src="{src}"></script>'
    return mark_safe("\n".join(map(as_script_tag, scripts)))

Then in your index.html:

<html>
<head>

   {% vite_styles 'main.js' 'other-entry.js' %}
</head>
<body>


   {% vite_scripts 'main.js' %}
</body>

</html>