Быстрое прототипирование с помощью Django, htmx и Tailwind CSS

В этом руководстве вы узнаете, как настроить Django с помощью htmx и Tailwind CSS. Цель как htmx, так и Tailwind - упростить современную веб-разработку, чтобы вы могли создавать дизайн и обеспечивать интерактивность, не выходя за рамки комфорта и легкости HTML. Мы также рассмотрим, как использовать Django Compressor для объединения и минимизации статических ресурсов в приложении Django.

htmx

htmx - это библиотека, которая позволяет вам получать доступ к современным функциям браузера, таким как AJAX, CSS-переходы, WebSockets и события, отправляемые сервером, непосредственно из HTML, а не с помощью JavaScript. Это позволяет вам быстро создавать пользовательские интерфейсы непосредственно в разметке.

html расширяет несколько функций, уже встроенных в браузер, таких как отправка HTTP-запросов и реагирование на события. Например, вместо того, чтобы отправлять запросы GET и POST только через элементы a и form, вы можете использовать атрибуты HTML для отправки запросов GET, POST, PUT, PATCH или DELETE для любого HTML-элемента:

<button hx-delete="/user/1">Delete</button>

Вы также можете обновить части страницы, чтобы создать одностраничное приложение (SPA):

 

Кодовая ссылка

Откройте вкладку сеть в инструментах разработки браузера. При нажатии на кнопку на конечную точку https://v2.jokeapi.dev/joke/Any?format=txt&safe-mode отправляется запрос XHR. Затем ответ добавляется к элементу p с выводом id.

Для получения дополнительных примеров ознакомьтесь с Примерами пользовательского интерфейса на странице официального html xdocs.

Плюсы и минусы

Плюсы:

  1. Производительность разработчика: Вы можете создавать современные пользовательские интерфейсы, не прикасаясь к JavaScript. Чтобы узнать больше об этом, ознакомьтесь с Альтернативой SPA.
  2. Впечатляющий: сама библиотека небольшая (~10 кб min.gz'd), не имеет зависимостей и может расширяться.

Минусы:

  1. Зрелость библиотеки: Поскольку библиотека довольно новая, документации и примеров реализации немного.
  2. Объем передаваемых данных: Как правило, SPA-фреймворки (такие как React и Vue) работают путем передачи данных между клиентом и сервером в формате JSON и обратно. Полученные данные затем обрабатываются клиентом. htmx, с другой стороны, получает обработанный HTML-код с сервера и заменяет целевой элемент ответом. HTML в отображаемом формате обычно больше по размеру, чем ответ в формате JSON.

Попутный ветер CSS

Tailwind CSS - это CSS-фреймворк, ориентированный в первую очередь на утилиты. Вместо того чтобы поставлять готовые компоненты (на которых специализируются такие фреймворки, как Bootstrap и Bulma, он предоставляет стандартные блоки в виде служебных классов, которые позволяют создавайте макеты и дизайны быстро и легко.

Для примера возьмем следующие HTML и CSS:


<style>
.hello {
  height: 5px;
  width: 10px;
  background: gray;
  border-width: 1px;
  border-radius: 3px;
  padding: 5px;
}
</style>

<div class="hello">Hello World</div>

Это может быть реализовано с помощью Tailwind следующим образом:


<div class="h-1 w-2 bg-gray-600 border rounded-sm p-1">Hello World</div>

Воспользуйтесь CSS Tailwind Converter, чтобы преобразовать исходный CSS в эквивалентные служебные классы в Tailwind. Сравните результаты.

Плюсы и минусы

Плюсы:

  1. Легко настраиваемый: Хотя Tailwind поставляется с готовыми классами, их можно перезаписать с помощью файла tailwind.config.js.
  2. Оптимизация: Вы можете настроить Tailwind для оптимизации вывода CSS, загружая только те классы, которые действительно используются.
  3. Темный режим: Реализовать темный режим не составит труда, например, <div class="bg-white dark:bg-black">.

Минусы:

  1. Компоненты: Tailwind не предоставляет никаких официальных готовых компонентов, таких как кнопки, карты, навигационные панели и так далее. Компоненты должны быть созданы с нуля. Существует несколько ресурсов сообщества для таких компонентов, как Tailwind CSS Components и Tailwind Toolbox, и это лишь некоторые из них. Существует также мощная, хотя и платная библиотека компонентов от создателей Tailwind под названием Tailwind UI.
  2. CSS является встроенным: он объединяет контент и дизайн, что увеличивает размер страницы и загромождает HTML.

Компрессор Django

Django Compressor - это расширение, предназначенное для управления (сжатия/кэширования) статическими ресурсами в приложении Django. С его помощью вы создаете простой конвейер ресурсов для:

  1. Компиляция Sass и Less в таблицы стилей CSS
  2. Объединение и сокращение нескольких файлов CSS и JavaScript до одного файла для каждого
  3. Создание пакетов ресурсов для использования в ваших шаблонах

Итак, давайте посмотрим, как работать с каждым из вышеперечисленных инструментов в Django!

Настройка проекта

Для начала создайте новую папку для нашего проекта, создайте и активируйте новую виртуальную среду и установите Django вместе с Django Compressor:


$ mkdir django-htmx-tailwind && cd django-htmx-tailwind
$ python3.10 -m venv venv
$ source venv/bin/activate
(venv)$

(venv)$ pip install Django==4.0.3 django-compressor==3.1

Далее давайте установим pytailwindcss и загрузим его двоичный файл:


(venv)$ pip install pytailwindcss==0.1.4
(venv)$ tailwindcss

Создайте новый проект Django и todos приложение:


(venv)$ django-admin startproject config .
(venv)$ python manage.py startapp todos

Добавьте приложения в список INSTALLED_APPS в разделе config/settings.py:


# config/settings.py

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'todos',  # new
    'compressor',  # new
]

Создайте папку "шаблоны" в корне вашего проекта. Затем обновите настройки TEMPLATES следующим образом:


# config/settings.py

TEMPLATES = [
    {
        ...
        'DIRS': [BASE_DIR / 'templates'], # new
        ...
    },
]

Давайте добавим некоторую конфигурацию в config/settings.py для compressor:


# config/settings.py

COMPRESS_ROOT = BASE_DIR / 'static'

COMPRESS_ENABLED = True

STATICFILES_FINDERS = ('compressor.finders.CompressorFinder',)

Примечания:

  1. COMPRESS_ROOT определяет абсолютное местоположение, из которого считываются файлы, подлежащие сжатию, и в которое записываются сжатые файлы.
  2. COMPRESS_ENABLED логическое значение, определяющее, произойдет ли сжатие. По умолчанию используется значение, противоположное DEBUG.
  3. STATICFILES_FINDERS должен включать программу поиска файлов Django Compressor при установке django.contrib.staticfiles.

Инициализируйте Tailwind CSS в вашем проекте:


(venv)$ tailwindcss init

Эта команда создала tailwind.config.js файл в корне вашего проекта. Все настройки, связанные с Tailwind, хранятся в этом файле.

Обновить tailwind.config.js вот так:


module.exports = {
  content: [
    './templates/**/*.html',
  ],
  theme: {
    extend: {},
  },
  plugins: [],
}

Обратите внимание на раздел содержимое. Здесь вы настраиваете пути к HTML-шаблонам вашего проекта. Tailwind CSS просканирует ваши шаблоны в поисках названий классов Tailwind. Сгенерированный выходной CSS-файл будет содержать только CSS для соответствующих имен классов, найденных в ваших файлах шаблонов. Это помогает уменьшить размер сгенерированных CSS-файлов, поскольку они будут содержать только те стили, которые фактически используются.

Далее в корне проекта создайте следующие файлы и папки:


static
└── src
    └── main.css

Затем добавьте следующее в static/src/main.css:


/* static/src/main.css */

@tailwind base;
@tailwind components;
@tailwind utilities;

Здесь мы определили все классы base, components, и utilities из Tailwind CSS.

Вот и все. Теперь у вас подключены Django Compressor и Tailwind. Далее мы рассмотрим, как создать index.html файл, чтобы увидеть CSS в действии.

Простой пример

Обновите todos/views.py файл следующим образом:


# todos/views.py

from django.shortcuts import render


def index(request):
    return render(request, 'index.html')

Добавьте представление в todos/urls.py:


# todos/urls.py

from django.urls import path

from .views import index

urlpatterns = [
    path('', index, name='index'),
]

Затем добавьте todos.urls к config/urls.py:


# config/urls.py

from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', include('todos.urls')), # new
]

Добавьте _base.html файл в раздел "шаблоны":


<!-- templates/_base.html -->

{% load compress %}
{% load static %}

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Django + HTMX + Tailwind CSS</title>

    {% compress css %}
      <link rel="stylesheet" href="{% static 'src/output.css' %}">
    {% endcompress %}

  </head>
  <body class="bg-blue-100">
    {% block content %}
    {% endblock content %}
  </body>
</html>

Примечания:

  1. {% load compress %} импортирует все необходимые теги для работы с Django Compressor.
  2. {% load static %} загружает статические файлы в шаблон.
  3. {% compress css %} применяет соответствующие фильтры к файлу static/src/main.css.

Кроме того, мы добавили немного цвета в текст HTML с помощью <body class="bg-blue-100">. bg-blue-100 используется для изменения цвета фона на светло-голубой.

Добавить index.html файл:


<!-- templates/index.html -->

{% extends "_base.html" %}

{% block content %}
  <h1>Hello World</h1>
{% endblock content %}

Теперь запустите следующую команду в корне проекта, чтобы отсканировать шаблоны для классов и сгенерировать CSS-файл:


(venv)$ tailwindcss -i ./static/src/main.css -o ./static/src/output.css --minify

Примените миграции и запустите сервер разработки:


(venv)$ python manage.py migrate
(venv)$ python manage.py runserver

Перейдите к http://localhost:8000 в вашем браузере, чтобы просмотреть результаты. Также обратите внимание на сгенерированный файл в папке "static/CACHE/css".

Настроив Tailwind, давайте добавим html и создадим интерактивный поиск, который будет отображать результаты по мере ввода текста.

Пример поиска в реальном времени

Вместо того, чтобы извлекать html-библиотеку из CDN, давайте загрузим ее и используем Django Compressor для ее компоновки.

Загрузите библиотеку с https://unpkg.com/htmx.org@1.7.0/dist/htmx.js и сохраните ее на static/src/html.js.

Чтобы у нас были данные для работы, сохраните https://github.com/testdrivenio/django-htmx-tailwind/blob/master/todos/todo.py в новый файл с именем todos/todo.py.

Теперь добавьте представление для реализации функции поиска в todos/views.py:


# todos/views.py

from django.shortcuts import render
from django.views.decorators.http import require_http_methods  # new

from .todo import todos  # new


def index(request):
    return render(request, 'index.html', {'todos': []}) # modified


# new
@require_http_methods(['POST'])
def search(request):
    res_todos = []
    search = request.POST['search']
    if len(search) == 0:
        return render(request, 'todo.html', {'todos': []})
    for i in todos:
        if search in i['title']:
            res_todos.append(i)
    return render(request, 'todo.html', {'todos': res_todos})

Мы добавили новое представление, search, которое выполняет поиск задач и отображает шаблон todo.html со всеми результатами.

Добавьте вновь созданное представление в todos/urls.py:


# todos/urls.py

from django.urls import path

from .views import index, search  # modified

urlpatterns = [
    path('', index, name='index'),
    path('search/', search, name='search'),  # new
]

Затем добавьте новый ресурс в _base.html файл:


<!-- templates/base.html -->

{% load compress %}
{% load static %}

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Django + HTMX + Tailwind CSS</title>

    {% compress css %}
      <link rel="stylesheet" href="{% static 'src/output.css' %}">
    {% endcompress %}

  </head>
  <body class="bg-blue-100">
    {% block content %}
    {% endblock content %}

    <!-- new -->
    {% compress js %}
      <script type="text/javascript" src="{% static 'src/htmx.js' %}"></script>
    {% endcompress %}

    <!-- new -->
    <script>
      document.body.addEventListener('htmx:configRequest', (event) => {
        event.detail.headers['X-CSRFToken'] = '{{ csrf_token }}';
      })
    </script>
  </body>
</html>

Мы загрузили html-библиотеку, используя тег {% compress js %}. К тегу js по умолчанию применяется (который, JSMinFilter в свою очередь, применяет rjsmin). Таким образом, мы сократим static/src/htmx.js и отправим его из папки "static/CACHE".

Мы также добавили следующий скрипт:


document.body.addEventListener('htmx:configRequest', (event) => {
  event.detail.headers['X-CSRFToken'] = '{{ csrf_token }}';
})

Этот прослушиватель событий добавляет токен CSRF в заголовок запроса.

Далее, давайте добавим возможность поиска по названию каждого задания.

Обновите index.html файл следующим образом:


<!-- templates/index.html -->

{% extends "_base.html" %}

{% block content %}
  <div class="w-small w-2/3 mx-auto py-10 text-gray-600">
    <input
      type="text"
      name="search"
      hx-post="/search/"
      hx-trigger="keyup changed delay:250ms"
      hx-indicator=".htmx-indicator"
      hx-target="#todo-results"
      placeholder="Search"
      class="bg-white h-10 px-5 pr-10 rounded-full text-2xl focus:outline-none"
    >
    <span class="htmx-indicator">Searching...</span>
  </div>
  <table class="border-collapse w-small w-2/3 mx-auto">
    <thead>
      <tr>
        <th class="p-3 font-bold uppercase bg-gray-200 text-gray-600 border border-gray-300 hidden lg:table-cell">#</th>
        <th class="p-3 font-bold uppercase bg-gray-200 text-gray-600 border border-gray-300 hidden lg:table-cell">Title</th>
        <th class="p-3 font-bold uppercase bg-gray-200 text-gray-600 border border-gray-300 hidden lg:table-cell">Completed</th>
      </tr>
    </thead>
    <tbody id="todo-results">
      {% include "todo.html" %}
    </tbody>
  </table>
{% endblock content %}

Давайте взглянем на атрибуты, определенные в htmx:


<input
  type="text"
  name="search"
  hx-post="/search/"
  hx-trigger="keyup changed delay:250ms"
  hx-indicator=".htmx-indicator"
  hx-target="#todo-results"
  placeholder="Search"
  class="bg-white h-10 px-5 pr-10 rounded-full text-2xl focus:outline-none"
>
  1. Входные данные отправляют запрос POST в конечную точку /search.
  2. Запрос запускается с помощью keyup_event с задержкой в 250 мс. Таким образом, если новое событие ввода-вывода введено до истечения 250 мс после последнего ввода-вывода, запрос не запускается.
  3. Затем HTML-ответ на запрос отображается в элементе #todo-results.
  4. У нас также есть индикатор, элемент загрузки, который появляется после отправки запроса и исчезает после получения ответа.

Добавьте templates/todo.html файл:


<!-- templates/todo.html -->

{% for todo in todos %}
  <tr
    class="bg-white lg:hover:bg-gray-100 flex lg:table-row flex-row lg:flex-row flex-wrap lg:flex-no-wrap mb-10 lg:mb-0">
    <td class="w-full lg:w-auto p-3 text-gray-800 text-center border border-b block lg:table-cell relative lg:static">
      {{todo.id}}
    </td>
    <td class="w-full lg:w-auto p-3 text-gray-800 text-center border border-b block lg:table-cell relative lg:static">
      {{todo.title}}
    </td>
    <td class="w-full lg:w-auto p-3 text-gray-800 text-center border border-b block lg:table-cell relative lg:static">
      {% if todo.completed %}
        <span class="rounded bg-green-400 py-1 px-3 text-xs font-bold">Yes</span>
      {% else %}
        <span class="rounded bg-red-400 py-1 px-3 text-xs font-bold">No</span>
      {% endif %}
    </td>
  </tr>
{% endfor %}

В этом файле отображаются задачи, соответствующие нашему поисковому запросу.

Сгенерировать новый файл src/output.css:


(venv)$ tailwindcss -i ./static/src/main.css -o ./static/src/output.css --minify

Запустите приложение с помощью python manage.py runserver и перейдите к http://localhost:8000 еще раз, чтобы протестировать его.

demo

Заключение

В этом руководстве мы рассмотрели, как:

  • Настройка Django Compressor и Tailwind CSS
  • Создайте приложение для поиска в реальном времени, используя Django, Tailwind CSS и htmx

html позволяет отображать элементы без перезагрузки страницы. Самое главное, вы можете добиться этого без написания какого-либо JavaScript. Хотя это уменьшает объем работы, требуемой на стороне клиента, объем данных, отправляемых с сервера, может быть выше, поскольку он отправляет визуализированный HTML.

Подобное использование частичных HTML-шаблонов было популярно в начале 2000-х годов. html придает этому подходу современный вид. В целом, использование частичных шаблонов снова становится популярным из-за сложности таких фреймворков, как React и Vue. Вы также можете добавить WebSockets для внесения изменений в режиме реального времени. Этот же подход используется знаменитым Phoenix LiveView. Вы можете прочитать больше о HTML через WebSockets в разделе Будущее веб-программного обеспечения - за HTML через WebSockets и HTML через WebSockets.

Библиотека еще молода, но будущее выглядит очень радужным.

Tailwind - это мощный CSS-фреймворк, который нацелен на повышение производительности разработчиков. Хотя в этом руководстве он не затрагивался, Tailwind легко настраивается. Для получения дополнительной информации ознакомьтесь со следующими ресурсами:

При использовании Django обязательно сочетайте html и Tailwind с Django Compressor, чтобы упростить управление статическими активами.

Полный код можно найти в репозитории django-htmx-tailwind.

Вернуться на верх