Skip to content

Instantly share code, notes, and snippets.

@lucianoratamero
Last active April 29, 2024 07:08
Show Gist options
  • Star 54 You must be signed in to star a gist
  • Fork 17 You must be signed in to fork a gist
  • Save lucianoratamero/7fc9737d24229ea9219f0987272896a2 to your computer and use it in GitHub Desktop.
Save lucianoratamero/7fc9737d24229ea9219f0987272896a2 to your computer and use it in GitHub Desktop.
Using Vite with Django, the simple way

Using Vite with Django, the simple way

Warning

I'm surely not maintaining this as well as I could. There are also other possible, better integrated solutions, like django-vite, so keep in mind this was supposed to be more of a note to myself than anything :]

This gist has most of the things I've used to develop the frontend using vite inside a monolithic django app.

Here's a boilerplate that uses this approach: https://github.com/labcodes/django-react-boilerplate

A couple of things to note:

  • it runs in SPA mode by default. If you want SSR, you may want to look into django_vite;
  • static files unrelated to your app, like images or fonts, should be served by django, instead of imported directly inside your frontend app;
  • you'll need to enable manifest in your vite configs for production;
  • I've only tested this with a React app, but I'll share my notes when testing with a Svelte app later.
    • I've already tested with svelte, and the changes are really small! For the most part, you'll only need to remove react-specific stuff (like react-refresh), use a svelte + vite app as base, and change base.html to import main.js instead of jsx. And I've created a sample template for it too!

Besides that, just read the comments carefully and everything should work out. Whenever I'm free, I'll make a video about this and add the URL here :]

I've published a video on how to do it, step-by-step: https://www.youtube.com/watch?v=FCyYIVfDkhY

{% load render_vite_bundle %}
<!DOCTYPE html>
<html lang="en">
<!--
For this base.html to work in dev and in production,
you'll need to set a couple of keys inside your settings.py.
Another file in this gist shows which ones you'll really need.
-->
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite App</title>
{% if debug %}
<script type="module" src="http://localhost:3000/@vite/client"></script>
<!-- If you're using vite with React, this next script will be needed for HMR -->
<script type="module">
import RefreshRuntime from 'http://localhost:3000/@react-refresh'
if (RefreshRuntime) {
RefreshRuntime.injectIntoGlobalHook(window)
window.$RefreshReg$ = () => { }
window.$RefreshSig$ = () => (type) => type
window.__vite_plugin_react_preamble_installed__ = true
}
</script>
{% endif %}
</head>
<body>
<div id="root"></div>
{% if debug %}
<!-- This url will be different for each type of app. Point it to your main js file. -->
<script type="module" src="http://localhost:3000/main.jsx"></script>
{% else %}
{% render_vite_bundle %}
{% endif %}
</body>
</html>
# This template tag is needed for production
# Add it to one of your django apps (/appdir/templatetags/render_vite_bundle.py, for example)
import os
import json
from django import template
from django.conf import settings
from django.utils.safestring import mark_safe
register = template.Library()
@register.simple_tag
def render_vite_bundle():
"""
Template tag to render a vite bundle.
Supposed to only be used in production.
For development, see other files.
"""
try:
fd = open(f"{settings.VITE_APP_DIR}/dist/manifest.json", "r")
manifest = json.load(fd)
except:
raise Exception(
f"Vite manifest file not found or invalid. Maybe your {settings.VITE_APP_DIR}/dist/manifest.json file is empty?"
)
imports_files = "".join(
[
f'<script type="module" src="/static/{manifest[file]["file"]}"></script>'
for file in manifest["index.html"]["imports"]
]
)
return mark_safe(
f"""<script type="module" src="/static/{manifest['index.html']['file']}"></script>
<link rel="stylesheet" type="text/css" href="/static/{manifest['index.html']['css'][0]}" />
{imports_files}"""
)
# These are the settings you should have for everything to work properly.
# Add these to your main settings.py file, or modify it accordingly.
# Needed for production. Avoid using '*'.
ALLOWED_HOSTS = ['your-production-domain.com']
# Needed for 'debug' to be available inside templates.
# https://docs.djangoproject.com/en/3.2/ref/templates/api/#django-template-context-processors-debug
INTERNAL_IPS = ['127.0.0.1']
# Vite App Dir: point it to the folder your vite app is in.
VITE_APP_DIR = BASE_DIR / "src"
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/3.1/howto/static-files/
# You may change these, but it's important that the dist folder is includedself.
# If it's not, collectstatic won't copy your bundle to production.
STATIC_URL = "/static/"
STATICFILES_DIRS = [
VITE_APP_DIR / "dist",
]
STATIC_ROOT = BASE_DIR / "staticfiles"
import { defineConfig } from "vite";
import reactRefresh from "@vitejs/plugin-react-refresh";
// https://vitejs.dev/config/
export default defineConfig({
build: { manifest: true },
base: process.env.mode === "production" ? "/static/" : "/",
root: "./src",
plugins: [reactRefresh()],
});
@luzfcb
Copy link

luzfcb commented May 13, 2021

Opa, dei uma refatorada rapida...
Agora o código é compativel com Windows e dá umas mensagens de erro mais uteis.
(talvez seja melhor criar umas classes exceptions mais especificas)

# render_vite_bundle.py
import json
from pathlib import Path

from django import template
from django.conf import settings
from django.utils.safestring import mark_safe

register = template.Library()


def load_json_from_dist(json_filename='manifest.json'):
    manifest_file_path = Path(str(settings.VITE_APP_DIR), 'dist', json_filename)
    if not manifest_file_path.exists():
        raise Exception(
            f"Vite manifest file not found on path: {str(manifest_file_path)}"
        )
    else:
        with open(manifest_file_path, 'r') as manifest_file:
            try:
                manifest = json.load(manifest_file)
            except Exception:
                raise Exception(
                    f"Vite manifest file invalid. Maybe your {str(manifest_file_path)} file is empty?"
                )
            else:
                return manifest


@register.simple_tag
def render_vite_bundle():
    """
    Template tag to render a vite bundle.
    Supposed to only be used in production.
    For development, see other files.
    """

    manifest = load_json_from_dist()

    imports_files = "".join(
        [
            f'<script type="module" src="/static/{manifest[file]["file"]}"></script>'
            for file in manifest["index.html"]["imports"]
        ]
    )

    return mark_safe(
        f"""<script type="module" src="/static/{manifest['index.html']['file']}"></script>
        <link rel="stylesheet" type="text/css" href="/static/{manifest['index.html']['css'][0]}" />
        {imports_files}"""
    )

@bvpav
Copy link

bvpav commented Feb 19, 2022

In vite.config.js, I'm pretty sure that the check process.env === "production" should be replaced with process.env.NODE_ENV === "production" as process.env is an object and it will never equal a string.

@myntoralex
Copy link

Is there a different code snippet for Hot Reloading with vite-vue?

@incognos
Copy link

Thanks for the gist. A couple of suggestions.

in settings.py use the `os.path.join function to build your paths for compatibility with the different os's:

VITE_APP_DIR = os.path.join(BASE_DIR, "src")

STATIC_URL = "/static/"
STATICFILES_DIRS = [
    os.path.join(VITE_APP_DIR, "dist"),
]
STATIC_ROOT = os.path.join(BASE_DIR, "staticfiles")

and in the template tag:

def load_json_from_dist(json_filename="manifest.json"):
    manifest_file_path = Path(str(settings.VITE_APP_DIR), "dist", json_filename)
    if not manifest_file_path.exists():
        raise OSError(
            f"Vite manifest file not found on path: {str(manifest_file_path)}"
        )
    with open(manifest_file_path, "r") as manifest_file:
        try:
            manifest = json.load(manifest_file)
        except json.JSONDecodeError as e:
            raise json.JSONDecodeError(
                f"Vite manifest file invalid. Maybe your {str(manifest_file_path)} file is empty?"
            ) from e
        else:
            return manifest

and of course what @bvpav suggested - which is necessary

@alxroots
Copy link

alxroots commented Apr 1, 2022

Really good Gist!! I had to do some changes to make it work though.

  1. My first change was in base: process.env ... I did what @bvpav had suggested
{
...
  base: process.env.NODE_ENV === "production" ? "/static/" : "/",
...
}
  1. And for my second change I had to import a plugin to generate the imports key on manifest.json I guess the new version of vite is not placing the imports key (vendors) in the dist (manifest.json), so my last version of vite.config.js was:
import { defineConfig } from 'vite'
import { splitVendorChunkPlugin } from 'vite'
import react from '@vitejs/plugin-react'
import tsconfigPaths from 'vite-tsconfig-paths'

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [react(), splitVendorChunkPlugin(), tsconfigPaths()],
  build: { manifest: true },
  base: process.env.NODE_ENV === "production" ? "/static/" : "/",
  root: ".",
})

@seplveda
Copy link

wow @lucianoratamero, great work. I have a project, and I've been searching since 4 weeks now for the purest and cleanest way to integrate django and react (typescript) and you did it very well. this was a life saver.

I put all together in this repo: https://github.com/seplveda/prometheus for helps other developers. I would like your magic PR there!

@lucianoratamero
Copy link
Author

In vite.config.js, I'm pretty sure that the check process.env === "production" should be replaced with process.env.NODE_ENV === "production" as process.env is an object and it will never equal a string.

it was supposed to be process.env.mode, total mistake on my part. thanks for pointing it out @bvpav , I just fixed it!

@lucianoratamero
Copy link
Author

Is there a different code snippet for Hot Reloading with vite-vue?

I don't have used vite-vue, but I could help out if you gave me a sample project :]

@lucianoratamero
Copy link
Author

about the other comments: lately I've been lacking time (and peace of mind) to test out the other suggestions, like the ones to refactor the template tag.
it would be awesome if someone could create a simple sample project with the refactored template tag + tests, so I could be sure everything is working and merge the changes here.

@seplveda , about your comment, I can't work on that right now, but there is a working boilerplate for you to base prometheus on: https://github.com/labcodes/django-react-boilerplate
I'll add it on the readme as well, for future reference

@quroom
Copy link

quroom commented Jun 12, 2022

[plugin:vite-plugin-svelte] failed to transform tagged svelte request for id src/lib/Counter.svelte?svelte&type=style&lang.css
src/lib/Counter.svelte
    at TransformContext.transform (file:///C:/Users/Administrator/django-svelte-vite-template/node_modules/@sveltejs/vite-plugin-svelte/dist/index.js:975:15)
    at Object.transform (C:\Users\Administrator\django-svelte-vite-template\node_modules\vite\dist\node\chunks\dep-cc49d7be.js:50336:53)
    at transformRequest (C:\Users\Administrator\django-svelte-vite-template\node_modules\vite\dist\node\chunks\dep-cc49d7be.js:65952:51)
    at async viteTransformMiddleware (C:\Users\Administrator\django-svelte-vite-template\node_modules\vite\dist\node\chunks\dep-cc49d7be.js:66090:32
Click outside or fix the code to dismiss.
You can also disable this overlay with hmr: { overlay: false } in vite.config.js.

Thanks for great working. Sadly in my env, I got this error when I tested djang-svelte-vite-template
I have no idea. Do you have any idea?

----Self answer--------
I Fixed it with this comment
sveltejs/vite-plugin-svelte#113 (comment)

----after integration----
There is a little problem with tinro. I don't know why route not working.
Even if I click route1 ~ 3 text , there is no change.

@NobleSalt
Copy link

Please check out my vite-react-django solution on this here. Feel free to contact me for any challenges. Thanks
email: izuchukwue2@gmail.com

@nlappas
Copy link

nlappas commented Oct 3, 2022

Thank you so much for this!

Everything seems to work except hot reloading. When I hit directly the vite server at localhost:3000 it works, but when I visit the django served url at localhost:8000, then I need to refresh the page to see the code changes.

Any insights on what could be the culprit?

@NobleSalt
Copy link

NobleSalt commented Oct 3, 2022

Thank you so much for this!

Everything seems to work except hot reloading. When I hit directly the vite server at localhost:3000 it works, but when I visit the django served url at localhost:8000, then I need to refresh the page to see the code changes.

Any insights on what could be the culprit?

I created the template to behave that way.
if you have any contributions that can help out, you can simply create a pull request, and I will confirm your solution. Then approve it. Thanks for checking it out tho

@pedrovgp
Copy link

pedrovgp commented Feb 8, 2023

Hi, thanks a lot for this gist! I am using Django+vue3+vite, so I'd like to leave here the changes I made for it to work, for reference.

Context: I added vueapp directory in the root of my django project, with vite.config.js inside it. I use django to serve the index.html (Vue's entrypoint), so that I can use django to protect it with login required.

I created a django view to serve the vueapp.html template:

{% load render_vite_bundle %}
<!DOCTYPE html>
<html lang="en">


  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <link
      href="https://fonts.googleapis.com/css?family=Source+Sans+Pro:400,700"
      rel="stylesheet"
    />
    <link
      href="https://fonts.googleapis.com/css?family=Source+Code+Pro:400"
      rel="stylesheet"
    />
    <link
      href="https://fonts.googleapis.com/icon?family=Material+Icons"
      rel="stylesheet"
    />
    <link rel="icon" href="/static/vueapp/favicon.ico" />

    <title>Vite App</title>

    {% if debug %}
    <script type="module" src="http://localhost:3000/@vite/client"></script>

    {% endif %}
  </head>

  <body>
    <div id="app"></div>
    {% if debug %}
    <!-- This url will be different for each type of app. Point it to your main js file. -->
    <script type="module" src="http://localhost:3000/src/main.ts"></script>
    {% else %} {% render_vite_bundle %} {% endif %}
  </body>
</html>

I changed the render_vide_bundle.py a little to add all scripts and css's.

# This template tag is needed for production
# Add it to one of your django apps (/appdir/templatetags/render_vite_bundle.py, for example)

import json
from pathlib import Path

from django import template
from django.conf import settings
from django.utils.safestring import mark_safe

register = template.Library()


def load_json_from_dist(json_filename="manifest.json"):
    manifest_file_path = Path(str(settings.SITE_ROOT), "static", "vueapp", json_filename)
    if not manifest_file_path.exists():
        raise Exception(f"Vite manifest file not found on path: {str(manifest_file_path)}")
    else:
        with open(manifest_file_path, "r") as manifest_file:
            try:
                manifest = json.load(manifest_file)
            except Exception:
                raise Exception(f"Vite manifest file invalid. Maybe your {str(manifest_file_path)} file is empty?")
            else:
                return manifest


@register.simple_tag
def render_vite_bundle():
    """
    Template tag to render a vite bundle.
    Supposed to only be used in production.
    For development, see other files.
    """

    manifest = load_json_from_dist()
    files = manifest.keys()

    imports_files = "".join(
        [
            f'<script type="module" src="{settings.VITE_APP_STATIC_DIR}/{manifest[file]["file"]}"></script>'
            for file in files
            if manifest[file].get("file", "")
        ]
        + [
            f"""<link rel="stylesheet" type="text/css" href="{settings.VITE_APP_STATIC_DIR}/{css}" />"""
            for file in files
            for css in manifest[file].get("css", [])
        ]
    )

    return mark_safe(
        f"""<script type="module" src="{settings.VITE_APP_STATIC_DIR}/{manifest['index.html']['file']}"></script>
        <link rel="stylesheet" type="text/css" href="{settings.VITE_APP_STATIC_DIR}/{manifest['index.html']['css'][0]}" />
        {imports_files}"""
    )

My django settings.py

# Vite App Dir: point it to the folder your vite app is in.
VITE_APP_DIR = os.path.join(SITE_ROOT, "vueapp")
VITE_APP_STATIC_DIR = "/static/vueapp"

# Additional locations of static files
STATICFILES_DIRS = [
    os.path.join(VITE_APP_DIR, "static"),
]

@lucianoratamero
Copy link
Author

thanks for the notes @pedrovgp ! I'm glad you could make it work, and I hope to use your changes soon.

also, just as a note, I'm very, very busy with moving and work, so I added a couple of warnings on the base gist. :]
make sure to take a look at django-vite too, seems to be a better integrated solution. I didn't really try it, but I will when I have the time so I can leave more notes here.

@pedrovgp
Copy link

Yeap, django-vite seems to do better what I did above :P. Thanks!

@mahfy3w
Copy link

mahfy3w commented Jul 29, 2023

Thanks for the great work! Sadly I had to encounter this error in the python server:

[HMR][Svelte] Unrecoverable HMR error in <App>: next update will trigger a full reload [proxy.js:15:11](http://localhost:5173/@fs/home/Mahfy3w/Documents/projects/DjangoSvelte/DjangoSvelte/node_modules/svelte-hmr/runtime/proxy.js?v=45928d43)
    logError http://localhost:5173/@fs/home/Mahfy3w/Documents/projects/DjangoSvelte/DjangoSvelte/node_modules/svelte-hmr/runtime/proxy.js?v=45928d43:15
    Proxy<App> http://localhost:5173/@fs/home/Mahfy3w/Documents/projects/DjangoSvelte/DjangoSvelte/node_modules/svelte-hmr/runtime/proxy.js?v=45928d43:380
    <anonymous> http://localhost:5173/main.js:4
Uncaught Error: 'target' is a required option
    SvelteComponentDev dev.js:329
    App App.svelte:186
    createProxiedComponent svelte-hooks.js:341
    ProxyComponent proxy.js:242
    Proxy<App> proxy.js:349
    <anonymous> main.js:4
[dev.js:329:9](http://localhost:5173/@fs/home/Mahfy3w/Documents/projects/DjangoSvelte/DjangoSvelte/node_modules/svelte/src/runtime/internal/dev.js)
    SvelteComponentDev dev.js:329
    App App.svelte:186
    createProxiedComponent svelte-hooks.js:341
    ProxyComponent proxy.js:242
    Proxy<App> proxy.js:349
    <anonymous> main.js:4

I was doing this with svelte, and I did all the instructions including removing the react specific code.
it doesn't show anything on the page. Can someone tell me what I did wrong?

@matheuszwilk
Copy link

Olá senhor Luciano, eu vi seu video hoje depois de 2 anos de gravação, gostaria de saber se sua solução para o usar o Vite + Django ainda esta funcional ou se tem uma forma mais atualizada, aguardo sua resposta e obrigado pelo tutorial..

@lucianoratamero
Copy link
Author

oi @matheuszwilk ! eu pessoalmente tenho usado mais o django-vite ultimamente, mas já vi projeto novo (inclusive projeto em que eu não estive involvido) usando uma variação desse código que tá aqui e no vídeo.

ou seja, essa abordagem tá bem desatualizada, mas parece ainda funcionar para grande parte dos casos, com algumas alterações a depender do projeto. te sugiro, no entanto, dar uma olhada no django-vite, por ele dar melhor flexibilidade, ou simplesmente usar um framework full stack que dê suporte nativo a ferramentas de front, como o svelte kit, nextjs ou até mesmo o laravel.

@Newton52owusu
Copy link

how will i work with already built templates. How should i connect it to be viewed by the backend

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment