Vite Integration with Django BlockNote¶
This guide explains how Django BlockNote integrates with Vite for modern asset management, including filename hashing for optimal caching performance.
Overview¶
Django BlockNote uses Vite to build and bundle JavaScript and CSS assets. The integration provides:
Content-based filename hashing for cache busting
Development/production optimization with different build strategies
Automatic asset discovery through manifest files
Seamless Django template integration
How It Works¶
The integration consists of three main components:
Vite Build Process - Compiles and hashes assets, generates manifest
Widget Asset Resolution - Reads manifest to find current asset paths
Template Asset Loading - Dynamically loads correct asset URLs
graph LR
A[Vite Build] --> B[manifest.json]
B --> C[Widget.get_vite_assets()]
C --> D[Template Context]
D --> E[Dynamic URLs]
E --> F[Browser Cache]
Vite Configuration¶
Build Configuration¶
The Vite configuration handles multiple entry points and generates production-optimized bundles:
// vite.config.js
import { defineConfig } from 'vite'
const isProduction = process.env.NODE_ENV === 'production'
export default defineConfig({
build: {
// Output to Django static directory
outDir: '../django_blocknote/static/django_blocknote',
emptyOutDir: true,
// Multiple entry points for different components
rollupOptions: {
input: {
blocknote: './src/editor.js',
widget: './src/widget.js'
},
output: {
// Content hashing in production for cache busting
entryFileNames: (chunkInfo) => {
const name = chunkInfo.name
if (isProduction) {
return `js/${name}.[hash].min.js`
}
return `js/${name}.js`
},
// CSS with hashing
assetFileNames: (assetInfo) => {
if (assetInfo.name?.endsWith('.css')) {
const name = assetInfo.name.replace('.css', '')
if (isProduction) {
return `css/${name}.[hash].min.css`
}
return `css/${name}.css`
}
return isProduction ? 'assets/[name].[hash].[ext]' : 'assets/[name].[ext]'
},
format: 'umd',
name: 'DjangoBlockNote'
}
},
// Generate manifest for Django integration
manifest: true,
// Production optimizations
minify: isProduction ? 'terser' : false,
terserOptions: isProduction ? {
compress: {
dead_code: true,
passes: 2
},
mangle: {
keep_quoted: "strict" // Safe and predictable
},
format: {
comments: false // Clean output
},
ecma: 2022,
module: true
} : undefined,
// Single CSS bundle
cssCodeSplit: false,
// No source maps in production
sourcemap: isProduction ? false : 'inline'
},
// Modern ES target
esbuild: {
drop: isProduction ? ['console', 'debugger'] : [],
target: 'es2022'
}
})
Build Output Structure¶
The build process creates the following structure:
django_blocknote/static/django_blocknote/
├── manifest.json # Asset mapping file
├── js/
│ ├── blocknote.a1b2c3d4.min.js # Hashed main bundle
│ └── widget.e5f6g7h8.min.js # Hashed widget bundle
├── css/
│ └── blocknote.i9j0k1l2.min.css # Hashed styles
└── assets/ # Other static assets
└── [fonts, images, etc.]
Manifest File Format¶
The manifest.json file maps logical names to actual build outputs:
{
"blocknote": {
"file": "js/blocknote.a1b2c3d4.min.js",
"src": "src/editor.js"
},
"widget": {
"file": "js/widget.e5f6g7h8.min.js",
"src": "src/widget.js"
},
"blocknote.css": {
"file": "css/blocknote.i9j0k1l2.min.css"
}
}
Widget Integration¶
Asset Resolution Method¶
The BlockNoteWidget class includes a method to resolve current asset paths:
# django_blocknote/widgets.py
import json
from pathlib import Path
from django.conf import settings
class BlockNoteWidget(forms.Textarea):
def get_vite_assets(self):
"""Get the current asset paths from Vite manifest"""
try:
# Try to find manifest in static files
if hasattr(settings, 'STATIC_ROOT') and settings.STATIC_ROOT:
manifest_path = Path(settings.STATIC_ROOT) / 'django_blocknote' / 'manifest.json'
else:
# Fallback for development
from django.apps import apps
app_config = apps.get_app_config('django_blocknote')
manifest_path = Path(app_config.path) / 'static' / 'django_blocknote' / 'manifest.json'
if manifest_path.exists():
with open(manifest_path, 'r') as f:
manifest = json.load(f)
# Extract asset paths from manifest
assets = {}
for key, data in manifest.items():
if isinstance(data, dict) and 'file' in data:
assets[key] = data['file']
else:
assets[key] = data
return {
'js': {
'blocknote': assets.get('blocknote', 'js/blocknote.js'),
'widget': assets.get('widget', 'js/widget.js')
},
'css': {
'blocknote': assets.get('blocknote.css', 'css/blocknote.css')
}
}
except (FileNotFoundError, json.JSONDecodeError, KeyError) as e:
if getattr(settings, 'DEBUG', False):
print(f"Warning: Could not load Vite manifest: {e}")
# Fallback to non-hashed filenames for development
return {
'js': {
'blocknote': 'js/blocknote.js',
'widget': 'js/widget.js'
},
'css': {
'blocknote': 'css/blocknote.css'
}
}
Context Integration¶
The widget passes asset information to the template context:
def get_context(self, name, value, attrs):
context = super().get_context(name, value, attrs)
# Get current asset paths
assets = self.get_vite_assets()
# Add asset paths to context
context["widget"]["assets"] = assets
# Debug output in development
if getattr(settings, 'DEBUG', False):
print(f"BlockNote Widget Assets: {assets}")
return context
Template Integration¶
Dynamic Asset Loading¶
The widget template uses the asset paths from the context to load resources:
<!-- django_blocknote/templates/django_blocknote/widgets/blocknote.html -->
{% load static %}
{# Load CSS with dynamic asset paths #}
<link rel="stylesheet" href="{% static 'django_blocknote/'|add:widget.assets.css.blocknote %}">
{# Load JavaScript with dynamic asset paths #}
<script src="{% static 'django_blocknote/'|add:widget.assets.js.blocknote %}"></script>
<!-- Widget HTML structure -->
<div class="django-blocknote-wrapper">
<textarea name="{{ widget.name }}"
id="{{ widget.editor_id }}"
style="display: none">{{ widget.value|default:'' }}</textarea>
<div id="{{ widget.editor_id }}_editor"
class="django-blocknote-container">
<!-- Editor initialization happens here -->
</div>
</div>
URL Resolution Process¶
The template URL resolution follows this pattern:
Template Access:
widget.assets.css.blocknoteValue Resolution:
"css/blocknote.i9j0k1l2.min.css"String Concatenation:
'django_blocknote/' + 'css/blocknote.i9j0k1l2.min.css'Static URL Generation:
{% static 'django_blocknote/css/blocknote.i9j0k1l2.min.css' %}Final URL:
"/static/django_blocknote/css/blocknote.i9j0k1l2.min.css"
Cache Strategy¶
How Content Hashing Works¶
Problem: Traditional asset caching creates conflicts between performance and freshness:
<!-- Without hashing - cache conflicts -->
<script src="/static/blocknote.js"></script>
<!-- Browser caches for 1 year, but updates are invisible -->
Solution: Content-based filenames eliminate cache conflicts:
<!-- With hashing - perfect caching -->
<script src="/static/blocknote.a1b2c3d4.min.js"></script>
<!-- Each version gets unique URL, forcing fresh downloads when needed -->
Cache Behavior¶
Development Environment:
Simple filenames:
blocknote.js,blocknote.cssShort cache times for rapid iteration
Inline source maps for debugging
Production Environment:
Hashed filenames:
blocknote.a1b2c3d4.min.jsLong cache times (1 year) for performance
Automatic cache invalidation on updates
Cache Lifecycle¶
First Deploy: Browser downloads
blocknote.a1b2c3d4.min.jsSubsequent Requests: Browser serves from cache (1 year)
Code Update: New build creates
blocknote.x9y8z7w6.min.jsNext Request: Browser downloads new file (cache miss)
Old Cache: Remains until browser cleanup, but never requested
Development Workflow¶
Local Development¶
Run Vite Dev Server:
npm run dev # Serves assets with simple filenames
Django Development:
python manage.py runserver # Widget falls back to non-hashed filenames
Production Deployment¶
Build Assets:
npm run build # Creates hashed filenames and manifest
Collect Static Files:
python manage.py collectstatic # Copies manifest.json to STATIC_ROOT
Deploy Application:
Widget automatically reads new manifest
Templates generate new URLs
Users get fresh assets
Troubleshooting¶
Common Issues¶
Manifest Not Found:
# Check manifest location
manifest_path = Path(settings.STATIC_ROOT) / 'django_blocknote' / 'manifest.json'
print(f"Looking for manifest at: {manifest_path}")
print(f"Exists: {manifest_path.exists()}")
Asset Loading Errors:
# Enable debug output
settings.DEBUG = True
# Widget will print asset resolution details
Cache Issues:
# Clear browser cache or check Network tab
# Look for 304 (cached) vs 200 (fresh) responses
Debug Information¶
The widget provides debug output in development:
if getattr(settings, 'DEBUG', False):
print(f"BlockNote Widget Assets: {assets}")
# Output: {'js': {'blocknote': 'js/blocknote.a1b2c3d4.min.js'}, ...}
Performance Benefits¶
Metrics¶
Without Hashing:
Cache hit rate: ~70% (users clear cache, short expiration)
Page load time: Variable (depends on cache state)
Deployment issues: Users see old code until cache expires
With Hashing:
Cache hit rate: ~95% (long expiration, perfect invalidation)
Page load time: Consistent (predictable cache behavior)
Deployment issues: None (immediate updates)
Best Practices¶
Use Long Cache Headers: Set 1-year expiration for hashed assets
Monitor Manifest Size: Keep manifest.json small for faster parsing
Test Both Environments: Verify fallbacks work in development
Version Assets Together: Ensure JS/CSS compatibility across updates
Security Considerations¶
Source Map Exposure¶
Production builds disable source maps to prevent code exposure:
// vite.config.js
sourcemap: isProduction ? false : 'inline'
Asset Integrity¶
Consider adding Subresource Integrity (SRI) for additional security:
<script src="{% static 'django_blocknote/'|add:widget.assets.js.blocknote %}"
integrity="sha384-hash-value-here"
crossorigin="anonymous"></script>
Migration Guide¶
From Static Assets¶
Before (static asset references):
<link rel="stylesheet" href="{% static 'django_blocknote/css/blocknote.css' %}">
<script src="{% static 'django_blocknote/js/blocknote.js' %}"></script>
After (dynamic asset resolution):
<link rel="stylesheet" href="{% static 'django_blocknote/'|add:widget.assets.css.blocknote %}">
<script src="{% static 'django_blocknote/'|add:widget.assets.js.blocknote %}"></script>
Deployment Checklist¶
[ ] Vite build generates manifest.json
[ ] collectstatic includes manifest in STATIC_ROOT
[ ] Widget can read manifest from expected location
[ ] Templates use dynamic asset references
[ ] Cache headers set for long expiration
[ ] Monitoring for asset loading errors