Быстрое прототипирование с помощью 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.
Плюсы и минусы
Плюсы:
- Производительность разработчика: Вы можете создавать современные пользовательские интерфейсы, не прикасаясь к JavaScript. Чтобы узнать больше об этом, ознакомьтесь с Альтернативой SPA.
- Впечатляющий: сама библиотека небольшая (~10 кб min.gz'd), не имеет зависимостей и может расширяться.
Минусы:
- Зрелость библиотеки: Поскольку библиотека довольно новая, документации и примеров реализации немного.
- Объем передаваемых данных: Как правило, 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. Сравните результаты.
Плюсы и минусы
Плюсы:
- Легко настраиваемый: Хотя Tailwind поставляется с готовыми классами, их можно перезаписать с помощью файла tailwind.config.js.
- Оптимизация: Вы можете настроить Tailwind для оптимизации вывода CSS, загружая только те классы, которые действительно используются.
- Темный режим: Реализовать темный режим не составит труда, например,
<div class="bg-white dark:bg-black">
.
Минусы:
- Компоненты: Tailwind не предоставляет никаких официальных готовых компонентов, таких как кнопки, карты, навигационные панели и так далее. Компоненты должны быть созданы с нуля. Существует несколько ресурсов сообщества для таких компонентов, как Tailwind CSS Components и Tailwind Toolbox, и это лишь некоторые из них. Существует также мощная, хотя и платная библиотека компонентов от создателей Tailwind под названием Tailwind UI.
- CSS является встроенным: он объединяет контент и дизайн, что увеличивает размер страницы и загромождает HTML.
Компрессор Django
Django Compressor - это расширение, предназначенное для управления (сжатия/кэширования) статическими ресурсами в приложении Django. С его помощью вы создаете простой конвейер ресурсов для:
- Компиляция Sass и Less в таблицы стилей CSS
- Объединение и сокращение нескольких файлов CSS и JavaScript до одного файла для каждого
- Создание пакетов ресурсов для использования в ваших шаблонах
Итак, давайте посмотрим, как работать с каждым из вышеперечисленных инструментов в 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',)
Примечания:
- COMPRESS_ROOT определяет абсолютное местоположение, из которого считываются файлы, подлежащие сжатию, и в которое записываются сжатые файлы.
- COMPRESS_ENABLED логическое значение, определяющее, произойдет ли сжатие. По умолчанию используется значение, противоположное
DEBUG
. - 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>
Примечания:
{% load compress %}
импортирует все необходимые теги для работы с Django Compressor.{% load static %}
загружает статические файлы в шаблон.{% 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"
>
- Входные данные отправляют запрос POST в конечную точку
/search
. - Запрос запускается с помощью keyup_event с задержкой в 250 мс. Таким образом, если новое событие ввода-вывода введено до истечения 250 мс после последнего ввода-вывода, запрос не запускается.
- Затем HTML-ответ на запрос отображается в элементе
#todo-results
. - У нас также есть индикатор, элемент загрузки, который появляется после отправки запроса и исчезает после получения ответа.
Добавьте 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 еще раз, чтобы протестировать его.
Заключение
В этом руководстве мы рассмотрели, как:
- Настройка 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 легко настраивается. Для получения дополнительной информации ознакомьтесь со следующими ресурсами:
- Настройка CSS для Tailwind
- Полное руководство по настройке темы Tailwind CSS
- Настройка системы проектирования Tailwind
- Краткое руководство по настройке в Tailwind CSS
При использовании Django обязательно сочетайте html и Tailwind с Django Compressor, чтобы упростить управление статическими активами.
Полный код можно найти в репозитории django-htmx-tailwind.
Вернуться на верх