Static Assets and Frontend Integration

Integrating CSS and JavaScript

Modern web applications require sophisticated CSS and JavaScript integration to deliver rich user experiences. This chapter covers advanced techniques for organizing, processing, and optimizing CSS and JavaScript in Django applications, including preprocessors, module systems, and build workflows.

Integrating CSS and JavaScript

Modern web applications require sophisticated CSS and JavaScript integration to deliver rich user experiences. This chapter covers advanced techniques for organizing, processing, and optimizing CSS and JavaScript in Django applications, including preprocessors, module systems, and build workflows.

CSS Integration Strategies

CSS Architecture and Organization

/* static/css/base.css - Base styles and CSS custom properties */
:root {
  /* Color palette */
  --primary-color: #3498db;
  --secondary-color: #2ecc71;
  --accent-color: #e74c3c;
  --text-color: #2c3e50;
  --text-muted: #7f8c8d;
  --background-color: #ffffff;
  --border-color: #ecf0f1;
  
  /* Typography */
  --font-family-base: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
  --font-family-heading: 'Inter', var(--font-family-base);
  --font-size-base: 16px;
  --line-height-base: 1.6;
  
  /* Spacing */
  --spacing-xs: 0.25rem;
  --spacing-sm: 0.5rem;
  --spacing-md: 1rem;
  --spacing-lg: 1.5rem;
  --spacing-xl: 2rem;
  
  /* Breakpoints */
  --breakpoint-sm: 576px;
  --breakpoint-md: 768px;
  --breakpoint-lg: 992px;
  --breakpoint-xl: 1200px;
}

/* Reset and base styles */
*,
*::before,
*::after {
  box-sizing: border-box;
}

body {
  font-family: var(--font-family-base);
  font-size: var(--font-size-base);
  line-height: var(--line-height-base);
  color: var(--text-color);
  background-color: var(--background-color);
  margin: 0;
  padding: 0;
}

/* Typography */
h1, h2, h3, h4, h5, h6 {
  font-family: var(--font-family-heading);
  font-weight: 600;
  line-height: 1.2;
  margin-top: 0;
  margin-bottom: var(--spacing-md);
}

h1 { font-size: 2.5rem; }
h2 { font-size: 2rem; }
h3 { font-size: 1.75rem; }
h4 { font-size: 1.5rem; }
h5 { font-size: 1.25rem; }
h6 { font-size: 1rem; }

p {
  margin-top: 0;
  margin-bottom: var(--spacing-md);
}

/* Links */
a {
  color: var(--primary-color);
  text-decoration: none;
  transition: color 0.2s ease;
}

a:hover,
a:focus {
  color: var(--accent-color);
  text-decoration: underline;
}

Component-Based CSS Architecture

/* static/css/components/button.css */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: var(--spacing-sm) var(--spacing-md);
  border: 1px solid transparent;
  border-radius: 4px;
  font-size: var(--font-size-base);
  font-weight: 500;
  text-decoration: none;
  cursor: pointer;
  transition: all 0.2s ease;
  user-select: none;
}

.btn:focus {
  outline: 2px solid var(--primary-color);
  outline-offset: 2px;
}

.btn--primary {
  background-color: var(--primary-color);
  color: white;
  border-color: var(--primary-color);
}

.btn--primary:hover {
  background-color: #2980b9;
  border-color: #2980b9;
}

.btn--secondary {
  background-color: transparent;
  color: var(--primary-color);
  border-color: var(--primary-color);
}

.btn--secondary:hover {
  background-color: var(--primary-color);
  color: white;
}

.btn--large {
  padding: var(--spacing-md) var(--spacing-lg);
  font-size: 1.125rem;
}

.btn--small {
  padding: var(--spacing-xs) var(--spacing-sm);
  font-size: 0.875rem;
}

CSS Preprocessors Integration

// static/scss/variables.scss
$primary-color: #3498db;
$secondary-color: #2ecc71;
$accent-color: #e74c3c;

$font-sizes: (
  'xs': 0.75rem,
  'sm': 0.875rem,
  'base': 1rem,
  'lg': 1.125rem,
  'xl': 1.25rem,
  '2xl': 1.5rem,
  '3xl': 1.875rem,
  '4xl': 2.25rem
);

$breakpoints: (
  'sm': 576px,
  'md': 768px,
  'lg': 992px,
  'xl': 1200px
);

// Mixins
@mixin respond-to($breakpoint) {
  @if map-has-key($breakpoints, $breakpoint) {
    @media (min-width: map-get($breakpoints, $breakpoint)) {
      @content;
    }
  }
}

@mixin button-variant($bg-color, $text-color: white) {
  background-color: $bg-color;
  color: $text-color;
  border-color: $bg-color;
  
  &:hover {
    background-color: darken($bg-color, 10%);
    border-color: darken($bg-color, 10%);
  }
  
  &:focus {
    box-shadow: 0 0 0 3px rgba($bg-color, 0.25);
  }
}
// static/scss/components/card.scss
@import '../variables';

.card {
  background: white;
  border-radius: 8px;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
  overflow: hidden;
  transition: transform 0.2s ease, box-shadow 0.2s ease;
  
  &:hover {
    transform: translateY(-2px);
    box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
  }
  
  &__header {
    padding: 1.5rem;
    border-bottom: 1px solid #f0f0f0;
    
    h3 {
      margin: 0;
      font-size: map-get($font-sizes, 'xl');
    }
  }
  
  &__body {
    padding: 1.5rem;
  }
  
  &__footer {
    padding: 1rem 1.5rem;
    background: #f8f9fa;
    border-top: 1px solid #f0f0f0;
  }
  
  // Responsive variations
  @include respond-to('md') {
    &--horizontal {
      display: flex;
      
      .card__image {
        flex: 0 0 200px;
      }
      
      .card__content {
        flex: 1;
        display: flex;
        flex-direction: column;
      }
    }
  }
}

JavaScript Integration Patterns

Modern JavaScript Module System

// static/js/utils/api.js
class ApiClient {
  constructor(baseURL = '/api/') {
    this.baseURL = baseURL;
    this.defaultHeaders = {
      'Content-Type': 'application/json',
    };
  }
  
  async request(endpoint, options = {}) {
    const url = `${this.baseURL}${endpoint}`;
    const config = {
      headers: { ...this.defaultHeaders, ...options.headers },
      ...options
    };
    
    // Add CSRF token for non-GET requests
    if (options.method && options.method !== 'GET') {
      const csrfToken = document.querySelector('[name=csrfmiddlewaretoken]')?.value;
      if (csrfToken) {
        config.headers['X-CSRFToken'] = csrfToken;
      }
    }
    
    try {
      const response = await fetch(url, config);
      
      if (!response.ok) {
        throw new Error(`HTTP error! status: ${response.status}`);
      }
      
      const contentType = response.headers.get('content-type');
      if (contentType && contentType.includes('application/json')) {
        return await response.json();
      }
      
      return await response.text();
    } catch (error) {
      console.error('API request failed:', error);
      throw error;
    }
  }
  
  get(endpoint, params = {}) {
    const url = new URL(endpoint, this.baseURL);
    Object.keys(params).forEach(key => 
      url.searchParams.append(key, params[key])
    );
    
    return this.request(url.pathname + url.search);
  }
  
  post(endpoint, data = {}) {
    return this.request(endpoint, {
      method: 'POST',
      body: JSON.stringify(data)
    });
  }
  
  put(endpoint, data = {}) {
    return this.request(endpoint, {
      method: 'PUT',
      body: JSON.stringify(data)
    });
  }
  
  delete(endpoint) {
    return this.request(endpoint, {
      method: 'DELETE'
    });
  }
}

export default new ApiClient();

Component-Based JavaScript Architecture

// static/js/components/Modal.js
export class Modal {
  constructor(element, options = {}) {
    this.element = element;
    this.options = {
      closeOnEscape: true,
      closeOnBackdrop: true,
      ...options
    };
    
    this.isOpen = false;
    this.init();
  }
  
  init() {
    this.bindEvents();
    this.createBackdrop();
  }
  
  bindEvents() {
    // Close button
    const closeBtn = this.element.querySelector('[data-modal-close]');
    if (closeBtn) {
      closeBtn.addEventListener('click', () => this.close());
    }
    
    // Escape key
    if (this.options.closeOnEscape) {
      document.addEventListener('keydown', (e) => {
        if (e.key === 'Escape' && this.isOpen) {
          this.close();
        }
      });
    }
    
    // Backdrop click
    if (this.options.closeOnBackdrop) {
      this.element.addEventListener('click', (e) => {
        if (e.target === this.element) {
          this.close();
        }
      });
    }
  }
  
  createBackdrop() {
    this.element.classList.add('modal');
    this.element.setAttribute('role', 'dialog');
    this.element.setAttribute('aria-hidden', 'true');
  }
  
  open() {
    if (this.isOpen) return;
    
    this.isOpen = true;
    document.body.classList.add('modal-open');
    this.element.classList.add('modal--active');
    this.element.setAttribute('aria-hidden', 'false');
    
    // Focus management
    this.previousActiveElement = document.activeElement;
    const focusableElement = this.element.querySelector('[autofocus], input, button, textarea, select');
    if (focusableElement) {
      focusableElement.focus();
    }
    
    // Emit custom event
    this.element.dispatchEvent(new CustomEvent('modal:open'));
  }
  
  close() {
    if (!this.isOpen) return;
    
    this.isOpen = false;
    document.body.classList.remove('modal-open');
    this.element.classList.remove('modal--active');
    this.element.setAttribute('aria-hidden', 'true');
    
    // Restore focus
    if (this.previousActiveElement) {
      this.previousActiveElement.focus();
    }
    
    // Emit custom event
    this.element.dispatchEvent(new CustomEvent('modal:close'));
  }
  
  toggle() {
    this.isOpen ? this.close() : this.open();
  }
}

Form Enhancement with JavaScript

// static/js/components/FormValidator.js
export class FormValidator {
  constructor(form, rules = {}) {
    this.form = form;
    this.rules = rules;
    this.errors = {};
    
    this.init();
  }
  
  init() {
    this.bindEvents();
    this.setupFields();
  }
  
  bindEvents() {
    this.form.addEventListener('submit', (e) => {
      if (!this.validate()) {
        e.preventDefault();
        this.showErrors();
      }
    });
    
    // Real-time validation
    this.form.addEventListener('blur', (e) => {
      if (e.target.matches('input, textarea, select')) {
        this.validateField(e.target);
      }
    }, true);
    
    // Clear errors on input
    this.form.addEventListener('input', (e) => {
      if (e.target.matches('input, textarea, select')) {
        this.clearFieldError(e.target);
      }
    });
  }
  
  setupFields() {
    const fields = this.form.querySelectorAll('input, textarea, select');
    fields.forEach(field => {
      // Add ARIA attributes
      field.setAttribute('aria-describedby', `${field.name}-error`);
      
      // Create error container
      const errorContainer = document.createElement('div');
      errorContainer.id = `${field.name}-error`;
      errorContainer.className = 'field-error';
      errorContainer.setAttribute('role', 'alert');
      field.parentNode.appendChild(errorContainer);
    });
  }
  
  validate() {
    this.errors = {};
    let isValid = true;
    
    Object.keys(this.rules).forEach(fieldName => {
      const field = this.form.querySelector(`[name="${fieldName}"]`);
      if (field && !this.validateField(field)) {
        isValid = false;
      }
    });
    
    return isValid;
  }
  
  validateField(field) {
    const fieldName = field.name;
    const fieldRules = this.rules[fieldName];
    
    if (!fieldRules) return true;
    
    const value = field.value.trim();
    
    // Required validation
    if (fieldRules.required && !value) {
      this.setFieldError(field, fieldRules.messages?.required || 'This field is required');
      return false;
    }
    
    // Skip other validations if field is empty and not required
    if (!value && !fieldRules.required) {
      this.clearFieldError(field);
      return true;
    }
    
    // Email validation
    if (fieldRules.email && !this.isValidEmail(value)) {
      this.setFieldError(field, fieldRules.messages?.email || 'Please enter a valid email address');
      return false;
    }
    
    // Min length validation
    if (fieldRules.minLength && value.length < fieldRules.minLength) {
      this.setFieldError(field, fieldRules.messages?.minLength || `Minimum ${fieldRules.minLength} characters required`);
      return false;
    }
    
    // Custom validation
    if (fieldRules.custom && !fieldRules.custom(value, field)) {
      this.setFieldError(field, fieldRules.messages?.custom || 'Invalid value');
      return false;
    }
    
    this.clearFieldError(field);
    return true;
  }
  
  setFieldError(field, message) {
    const errorContainer = document.getElementById(`${field.name}-error`);
    if (errorContainer) {
      errorContainer.textContent = message;
      errorContainer.style.display = 'block';
    }
    
    field.classList.add('field--error');
    field.setAttribute('aria-invalid', 'true');
    this.errors[field.name] = message;
  }
  
  clearFieldError(field) {
    const errorContainer = document.getElementById(`${field.name}-error`);
    if (errorContainer) {
      errorContainer.textContent = '';
      errorContainer.style.display = 'none';
    }
    
    field.classList.remove('field--error');
    field.setAttribute('aria-invalid', 'false');
    delete this.errors[field.name];
  }
  
  showErrors() {
    const firstErrorField = this.form.querySelector('.field--error');
    if (firstErrorField) {
      firstErrorField.focus();
    }
  }
  
  isValidEmail(email) {
    const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
    return emailRegex.test(email);
  }
}

Template Integration

Dynamic CSS and JavaScript Loading

<!-- templates/base.html -->
{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>{% block title %}My Django App{% endblock %}</title>
    
    <!-- Critical CSS inline -->
    <style>
        {% include "css/critical.css" %}
    </style>
    
    <!-- Preload important resources -->
    <link rel="preload" href="{% static 'css/main.css' %}" as="style">
    <link rel="preload" href="{% static 'js/main.js' %}" as="script">
    
    <!-- Load CSS asynchronously -->
    <link rel="stylesheet" href="{% static 'css/main.css' %}" media="print" onload="this.media='all'">
    <noscript><link rel="stylesheet" href="{% static 'css/main.css' %}"></noscript>
    
    <!-- Page-specific CSS -->
    {% block extra_css %}{% endblock %}
</head>
<body>
    <div id="app">
        {% block content %}{% endblock %}
    </div>
    
    <!-- Essential JavaScript -->
    <script src="{% static 'js/main.js' %}" defer></script>
    
    <!-- Page-specific JavaScript -->
    {% block extra_js %}{% endblock %}
    
    <!-- Initialize components -->
    <script>
        document.addEventListener('DOMContentLoaded', function() {
            // Initialize global components
            window.app = new App();
        });
    </script>
</body>
</html>

Component Templates with JavaScript Integration

<!-- templates/components/modal.html -->
<div class="modal" id="{{ modal_id }}" aria-hidden="true" role="dialog">
    <div class="modal__backdrop"></div>
    <div class="modal__container">
        <div class="modal__header">
            <h2 class="modal__title">{{ title }}</h2>
            <button class="modal__close" data-modal-close aria-label="Close modal">
                <span aria-hidden="true">&times;</span>
            </button>
        </div>
        <div class="modal__body">
            {{ content|safe }}
        </div>
        {% if actions %}
        <div class="modal__footer">
            {% for action in actions %}
                <button class="btn btn--{{ action.type }}" 
                        data-action="{{ action.name }}">
                    {{ action.label }}
                </button>
            {% endfor %}
        </div>
        {% endif %}
    </div>
</div>

<script>
    document.addEventListener('DOMContentLoaded', function() {
        const modal = new Modal(document.getElementById('{{ modal_id }}'), {
            closeOnEscape: {{ close_on_escape|yesno:"true,false" }},
            closeOnBackdrop: {{ close_on_backdrop|yesno:"true,false" }}
        });
        
        {% if auto_open %}
        modal.open();
        {% endif %}
    });
</script>

Asset Optimization and Build Process

CSS Optimization Techniques

# utils/css_optimizer.py
import re
import cssmin
from django.contrib.staticfiles.storage import StaticFilesStorage
from django.core.files.base import ContentFile

class OptimizedCSSStorage(StaticFilesStorage):
    """Custom storage that optimizes CSS files"""
    
    def _save(self, name, content):
        if name.endswith('.css'):
            # Read CSS content
            css_content = content.read()
            if isinstance(css_content, bytes):
                css_content = css_content.decode('utf-8')
            
            # Optimize CSS
            optimized_css = self.optimize_css(css_content)
            
            # Create new content file
            content = ContentFile(optimized_css.encode('utf-8'))
        
        return super()._save(name, content)
    
    def optimize_css(self, css_content):
        """Optimize CSS content"""
        # Remove comments
        css_content = re.sub(r'/\*.*?\*/', '', css_content, flags=re.DOTALL)
        
        # Minify CSS
        css_content = cssmin.cssmin(css_content)
        
        # Add critical CSS optimizations
        css_content = self.optimize_critical_css(css_content)
        
        return css_content
    
    def optimize_critical_css(self, css_content):
        """Extract and optimize critical CSS"""
        # This would implement critical CSS extraction logic
        # For now, just return the minified CSS
        return css_content

# settings.py
STATICFILES_STORAGE = 'utils.css_optimizer.OptimizedCSSStorage'

JavaScript Module Bundling

// static/js/main.js - Entry point
import { Modal } from './components/Modal.js';
import { FormValidator } from './components/FormValidator.js';
import { ApiClient } from './utils/api.js';
import { debounce, throttle } from './utils/helpers.js';

class App {
  constructor() {
    this.components = new Map();
    this.init();
  }
  
  init() {
    this.initializeComponents();
    this.bindGlobalEvents();
    this.setupServiceWorker();
  }
  
  initializeComponents() {
    // Auto-initialize components based on data attributes
    document.querySelectorAll('[data-component]').forEach(element => {
      const componentName = element.dataset.component;
      const componentOptions = element.dataset.options ? 
        JSON.parse(element.dataset.options) : {};
      
      this.initializeComponent(element, componentName, componentOptions);
    });
  }
  
  initializeComponent(element, name, options = {}) {
    switch (name) {
      case 'modal':
        this.components.set(element, new Modal(element, options));
        break;
      case 'form-validator':
        const rules = this.parseValidationRules(element);
        this.components.set(element, new FormValidator(element, rules));
        break;
      default:
        console.warn(`Unknown component: ${name}`);
    }
  }
  
  parseValidationRules(form) {
    const rules = {};
    const fields = form.querySelectorAll('[data-validation]');
    
    fields.forEach(field => {
      const validationData = JSON.parse(field.dataset.validation);
      rules[field.name] = validationData;
    });
    
    return rules;
  }
  
  bindGlobalEvents() {
    // Global keyboard shortcuts
    document.addEventListener('keydown', (e) => {
      if (e.ctrlKey || e.metaKey) {
        switch (e.key) {
          case 'k':
            e.preventDefault();
            this.openSearch();
            break;
        }
      }
    });
    
    // Global click handlers
    document.addEventListener('click', (e) => {
      // Handle data-action attributes
      if (e.target.dataset.action) {
        this.handleAction(e.target.dataset.action, e.target, e);
      }
    });
  }
  
  handleAction(action, element, event) {
    switch (action) {
      case 'toggle-theme':
        this.toggleTheme();
        break;
      case 'copy-to-clipboard':
        this.copyToClipboard(element.dataset.text || element.textContent);
        break;
      default:
        console.warn(`Unknown action: ${action}`);
    }
  }
  
  toggleTheme() {
    const currentTheme = document.documentElement.dataset.theme || 'light';
    const newTheme = currentTheme === 'light' ? 'dark' : 'light';
    
    document.documentElement.dataset.theme = newTheme;
    localStorage.setItem('theme', newTheme);
  }
  
  async copyToClipboard(text) {
    try {
      await navigator.clipboard.writeText(text);
      this.showNotification('Copied to clipboard!');
    } catch (err) {
      console.error('Failed to copy text: ', err);
    }
  }
  
  showNotification(message, type = 'info') {
    // Implementation for showing notifications
    const notification = document.createElement('div');
    notification.className = `notification notification--${type}`;
    notification.textContent = message;
    
    document.body.appendChild(notification);
    
    setTimeout(() => {
      notification.remove();
    }, 3000);
  }
  
  async setupServiceWorker() {
    if ('serviceWorker' in navigator) {
      try {
        const registration = await navigator.serviceWorker.register('/sw.js');
        console.log('Service Worker registered:', registration);
      } catch (error) {
        console.error('Service Worker registration failed:', error);
      }
    }
  }
}

// Initialize app
window.App = App;

Performance Optimization

Lazy Loading and Code Splitting

// static/js/utils/lazy-loader.js
export class LazyLoader {
  constructor() {
    this.loadedModules = new Set();
    this.loadingPromises = new Map();
  }
  
  async loadComponent(componentName) {
    if (this.loadedModules.has(componentName)) {
      return;
    }
    
    if (this.loadingPromises.has(componentName)) {
      return this.loadingPromises.get(componentName);
    }
    
    const loadPromise = this.dynamicImport(componentName);
    this.loadingPromises.set(componentName, loadPromise);
    
    try {
      await loadPromise;
      this.loadedModules.add(componentName);
    } catch (error) {
      console.error(`Failed to load component ${componentName}:`, error);
      this.loadingPromises.delete(componentName);
      throw error;
    }
    
    return loadPromise;
  }
  
  async dynamicImport(componentName) {
    const componentMap = {
      'chart': () => import('./components/Chart.js'),
      'editor': () => import('./components/Editor.js'),
      'calendar': () => import('./components/Calendar.js'),
      'image-gallery': () => import('./components/ImageGallery.js')
    };
    
    const importFunction = componentMap[componentName];
    if (!importFunction) {
      throw new Error(`Unknown component: ${componentName}`);
    }
    
    return importFunction();
  }
  
  async loadCSS(href) {
    return new Promise((resolve, reject) => {
      const link = document.createElement('link');
      link.rel = 'stylesheet';
      link.href = href;
      link.onload = resolve;
      link.onerror = reject;
      document.head.appendChild(link);
    });
  }
  
  observeElements() {
    const observer = new IntersectionObserver((entries) => {
      entries.forEach(entry => {
        if (entry.isIntersecting) {
          const element = entry.target;
          const componentName = element.dataset.lazyComponent;
          
          if (componentName) {
            this.loadComponent(componentName).then(() => {
              // Initialize component after loading
              const event = new CustomEvent('component:loaded', {
                detail: { componentName, element }
              });
              element.dispatchEvent(event);
            });
            
            observer.unobserve(element);
          }
        }
      });
    });
    
    document.querySelectorAll('[data-lazy-component]').forEach(element => {
      observer.observe(element);
    });
  }
}

// Initialize lazy loader
const lazyLoader = new LazyLoader();
lazyLoader.observeElements();

Critical CSS Extraction

# utils/critical_css.py
import requests
from bs4 import BeautifulSoup
from django.conf import settings
from django.core.management.base import BaseCommand
import css_parser
import logging

class CriticalCSSExtractor:
    """Extract critical CSS for above-the-fold content"""
    
    def __init__(self):
        self.viewport_width = 1200
        self.viewport_height = 800
    
    def extract_critical_css(self, url, css_files):
        """Extract critical CSS for a given URL"""
        
        # Get HTML content
        html_content = self.fetch_html(url)
        if not html_content:
            return ""
        
        # Parse HTML
        soup = BeautifulSoup(html_content, 'html.parser')
        
        # Get all CSS rules
        all_css_rules = self.parse_css_files(css_files)
        
        # Extract elements in viewport
        critical_elements = self.get_critical_elements(soup)
        
        # Match CSS rules to critical elements
        critical_css = self.match_css_rules(critical_elements, all_css_rules)
        
        return critical_css
    
    def fetch_html(self, url):
        """Fetch HTML content from URL"""
        try:
            response = requests.get(url, timeout=10)
            response.raise_for_status()
            return response.text
        except requests.RequestException as e:
            logging.error(f"Failed to fetch HTML from {url}: {e}")
            return None
    
    def parse_css_files(self, css_files):
        """Parse CSS files and extract rules"""
        all_rules = []
        
        for css_file in css_files:
            try:
                with open(css_file, 'r', encoding='utf-8') as f:
                    css_content = f.read()
                
                sheet = css_parser.parseString(css_content)
                for rule in sheet:
                    if rule.type == rule.STYLE_RULE:
                        all_rules.append({
                            'selector': rule.selectorText,
                            'declarations': rule.style.cssText
                        })
            except Exception as e:
                logging.error(f"Failed to parse CSS file {css_file}: {e}")
        
        return all_rules
    
    def get_critical_elements(self, soup):
        """Get elements that are likely above the fold"""
        critical_selectors = [
            'header', 'nav', '.header', '.navbar',
            'h1', 'h2', '.hero', '.banner',
            '.above-fold', '[data-critical]'
        ]
        
        critical_elements = set()
        
        for selector in critical_selectors:
            elements = soup.select(selector)
            for element in elements:
                critical_elements.add(element.name)
                if element.get('class'):
                    for class_name in element.get('class'):
                        critical_elements.add(f'.{class_name}')
                if element.get('id'):
                    critical_elements.add(f'#{element.get("id")}')
        
        return critical_elements
    
    def match_css_rules(self, critical_elements, css_rules):
        """Match CSS rules to critical elements"""
        critical_css = []
        
        for rule in css_rules:
            selector = rule['selector']
            
            # Simple matching - in production, use a proper CSS selector parser
            if any(element in selector for element in critical_elements):
                critical_css.append(f"{selector} {{ {rule['declarations']} }}")
        
        return '\n'.join(critical_css)

# Management command to extract critical CSS
class Command(BaseCommand):
    help = 'Extract critical CSS for specified URLs'
    
    def add_arguments(self, parser):
        parser.add_argument('urls', nargs='+', help='URLs to extract critical CSS for')
        parser.add_argument('--css-files', nargs='+', help='CSS files to analyze')
        parser.add_argument('--output', help='Output file for critical CSS')
    
    def handle(self, *args, **options):
        extractor = CriticalCSSExtractor()
        
        all_critical_css = []
        
        for url in options['urls']:
            self.stdout.write(f'Extracting critical CSS for {url}...')
            
            critical_css = extractor.extract_critical_css(
                url, 
                options.get('css_files', [])
            )
            
            if critical_css:
                all_critical_css.append(critical_css)
        
        # Combine and deduplicate CSS
        combined_css = '\n'.join(all_critical_css)
        
        if options.get('output'):
            with open(options['output'], 'w', encoding='utf-8') as f:
                f.write(combined_css)
            self.stdout.write(f'Critical CSS saved to {options["output"]}')
        else:
            self.stdout.write(combined_css)

Progressive Enhancement

Feature Detection and Polyfills

// static/js/utils/feature-detection.js
export class FeatureDetector {
  constructor() {
    this.features = new Map();
    this.detectFeatures();
  }
  
  detectFeatures() {
    // CSS Features
    this.features.set('css-grid', this.supportsCSSGrid());
    this.features.set('css-custom-properties', this.supportsCSSCustomProperties());
    this.features.set('css-flexbox', this.supportsCSSFlexbox());
    
    // JavaScript Features
    this.features.set('es6-modules', this.supportsES6Modules());
    this.features.set('intersection-observer', this.supportsIntersectionObserver());
    this.features.set('service-worker', this.supportsServiceWorker());
    
    // Browser APIs
    this.features.set('local-storage', this.supportsLocalStorage());
    this.features.set('session-storage', this.supportsSessionStorage());
    this.features.set('web-workers', this.supportsWebWorkers());
    this.features.set('fetch', this.supportsFetch());
    
    // Add feature classes to document
    this.addFeatureClasses();
  }
  
  supportsCSSGrid() {
    return CSS.supports('display', 'grid');
  }
  
  supportsCSSCustomProperties() {
    return CSS.supports('--custom-property', 'value');
  }
  
  supportsCSSFlexbox() {
    return CSS.supports('display', 'flex');
  }
  
  supportsES6Modules() {
    const script = document.createElement('script');
    return 'noModule' in script;
  }
  
  supportsIntersectionObserver() {
    return 'IntersectionObserver' in window;
  }
  
  supportsServiceWorker() {
    return 'serviceWorker' in navigator;
  }
  
  supportsLocalStorage() {
    try {
      const test = 'test';
      localStorage.setItem(test, test);
      localStorage.removeItem(test);
      return true;
    } catch (e) {
      return false;
    }
  }
  
  supportsSessionStorage() {
    try {
      const test = 'test';
      sessionStorage.setItem(test, test);
      sessionStorage.removeItem(test);
      return true;
    } catch (e) {
      return false;
    }
  }
  
  supportsWebWorkers() {
    return typeof Worker !== 'undefined';
  }
  
  supportsFetch() {
    return 'fetch' in window;
  }
  
  addFeatureClasses() {
    const html = document.documentElement;
    
    this.features.forEach((supported, feature) => {
      const className = supported ? `supports-${feature}` : `no-${feature}`;
      html.classList.add(className);
    });
  }
  
  hasFeature(feature) {
    return this.features.get(feature) || false;
  }
  
  async loadPolyfill(feature, polyfillUrl) {
    if (this.hasFeature(feature)) {
      return Promise.resolve();
    }
    
    return new Promise((resolve, reject) => {
      const script = document.createElement('script');
      script.src = polyfillUrl;
      script.onload = resolve;
      script.onerror = reject;
      document.head.appendChild(script);
    });
  }
}

// Initialize feature detection
const featureDetector = new FeatureDetector();

// Load polyfills as needed
(async () => {
  const polyfills = [
    {
      feature: 'intersection-observer',
      url: 'https://polyfill.io/v3/polyfill.min.js?features=IntersectionObserver'
    },
    {
      feature: 'fetch',
      url: 'https://polyfill.io/v3/polyfill.min.js?features=fetch'
    }
  ];
  
  for (const polyfill of polyfills) {
    try {
      await featureDetector.loadPolyfill(polyfill.feature, polyfill.url);
    } catch (error) {
      console.warn(`Failed to load polyfill for ${polyfill.feature}:`, error);
    }
  }
})();

export default featureDetector;

Graceful Degradation CSS

/* static/css/progressive-enhancement.css */

/* Base styles that work everywhere */
.card {
  border: 1px solid #ddd;
  padding: 1rem;
  margin-bottom: 1rem;
}

/* Enhanced styles for modern browsers */
.supports-css-grid .card-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
  gap: 1rem;
}

/* Fallback for browsers without CSS Grid */
.no-css-grid .card-grid .card {
  display: inline-block;
  width: 300px;
  vertical-align: top;
  margin-right: 1rem;
}

/* Custom properties with fallbacks */
.button {
  background-color: #007bff; /* Fallback */
  background-color: var(--primary-color, #007bff);
  color: white;
  border: none;
  padding: 0.5rem 1rem;
  border-radius: 4px;
  cursor: pointer;
}

/* Flexbox with fallbacks */
.navigation {
  /* Fallback for older browsers */
  text-align: center;
}

.navigation li {
  display: inline-block;
  margin: 0 0.5rem;
}

/* Enhanced layout for flexbox-capable browsers */
.supports-css-flexbox .navigation {
  display: flex;
  justify-content: space-between;
  align-items: center;
  text-align: left;
}

.supports-css-flexbox .navigation ul {
  display: flex;
  list-style: none;
  margin: 0;
  padding: 0;
}

.supports-css-flexbox .navigation li {
  margin: 0 1rem;
}

/* Progressive enhancement for animations */
@media (prefers-reduced-motion: no-preference) {
  .supports-css-custom-properties .card {
    transition: transform var(--transition-duration, 0.2s) ease;
  }
  
  .supports-css-custom-properties .card:hover {
    transform: translateY(-2px);
  }
}

/* Respect user preferences */
@media (prefers-reduced-motion: reduce) {
  * {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
}

@media (prefers-color-scheme: dark) {
  .supports-css-custom-properties {
    --background-color: #1a1a1a;
    --text-color: #ffffff;
    --border-color: #333333;
  }
}

Next Steps

With CSS and JavaScript integration mastered, you're ready to explore modern build tools that can automate and optimize your frontend workflow. The next chapter will cover integrating build tools like Vite and Webpack with Django, enabling advanced features like hot module replacement, code splitting, and automated optimization.

Key concepts covered:

  • Modern CSS architecture with custom properties and component-based organization
  • JavaScript module systems and component architecture
  • Template integration with dynamic asset loading
  • Performance optimization techniques including lazy loading and critical CSS
  • Progressive enhancement and feature detection
  • Graceful degradation strategies

These techniques provide a solid foundation for building sophisticated, performant frontend experiences while maintaining compatibility across different browsers and devices.