Если вы следили за развитием front-end мира с React, Vue и всеми этими современными JavaScript фреймворками, вы можете задаваться вопросом, может ли Drupal не отставать. Хорошие новости? Определенно может. С Single Directory Components (SDC), Tailwind CSS v4, Vite и Storybook, вы можете создавать компонентные Drupal темы, которые выглядят удивительно современно.
Это руководство проведет вас через настройку полного современного рабочего процесса. Я проверил каждую команду здесь, исправил подводные камни и задокументировал то, что обычно вызывает проблемы. Давайте начнем.
Перед началом работы
Вам понадобится несколько вещей, уже установленных на вашей машине:
- Drupal 10 или выше
- DDEV (для локальной разработки)
- Drush (инструмент командной строки для Drupal)
- Bun или npm (я использую Bun здесь, потому что он быстрее, но npm тоже отлично работает)
Если у вас чего-то не хватает, сначала настройте это. Поверьте, это избавит вас от головной боли позже.
Настройка основы вашей темы
Сначала давайте создадим подходящее место для вашей пользовательской темы. Drupal ожидает, что пользовательские темы будут находиться в определенном месте:
mkdir -p web/themes/customТеперь начинается интересная часть. У Drush есть генератор тем, который задаст вам кучу вопросов. Не волнуйтесь — я скажу вам, что отвечать:
ddev drush generate themeВот что вам будет предложено:
- Theme name:
Blog SDC(это читаемое человеком имя) - Theme machine name:
blog_sdc(только строчные буквы и подчеркивания) - Base theme:
stable9(это важно — не пропускайте это, иначе позже получите ошибки зависимостей) - Description:
A custom theme for testing SDC components - Package:
Custom - Create breakpoints?
No(мы будем использовать Tailwind для адаптивного дизайна) - Create theme settings form?
No(пока оставим все просто)
После создания темы давайте установим ее и сделаем темой по умолчанию для вашего сайта:
ddev drush theme:install blog_sdc -y
ddev drush config-set system.theme default blog_sdc -y
ddev drush config-get system.theme defaultПоследняя команда просто подтверждает, что ваша тема теперь активна. Вы должны увидеть blog_sdc в выводе.
Создание вашего первого компонента
Single Directory Components (SDC) — это прорыв. Вместо того, чтобы разбрасывать файлы компонентов по нескольким директориям, все живет вместе — template, CSS, JavaScript и конфигурация все в одной папке. Это компонентная архитектура, которую вы хотели в Drupal.
Давайте сгенерируем компонент Card:
ddev drush generate sdcОтветьте на вопросы:
- Theme machine name:
blog_sdc - Component name:
Card Component - Component machine name:
card - Component description:
A reusable card component - Library dependencies: (просто нажмите Enter, чтобы пропустить)
- Need CSS?
y - Need JS?
n(мы добавим это позже) - Need component props?
n - Need slots?
n
После генерации очистите кеш Drupal. Вы будете делать это часто, так что привыкайте к этой команде:
ddev drush crЗаставляем ваш компонент работать
Теперь начинается интересное. Нам нужно подключить наш компонент к реальному контенту Drupal. Сначала давайте настроим отладку Twig, чтобы вы могли видеть, что происходит под капотом.
Скопируйте файл services по умолчанию:
cp web/sites/default/default.services.yml web/sites/default/services.ymlОткройте web/sites/default/services.yml и найдите секцию twig.config. Вы меняете только три значения здесь — не заменяйте всю секцию:
parameters:
twig.config:
debug: true
auto_reload: true
cache: falseЭти настройки покажут вам полезные комментарии в исходном коде HTML и будут автоматически перезагружать templates. Спасители жизни во время разработки.
Теперь давайте установим несколько инструментов для разработчиков, которые облегчат вашу жизнь:
ddev composer require drupal/devel drupal/twig_tweak 'drupal/kint-kint:^2.2'
ddev drush en devel devel_generate twig_tweak kint -yЭти модули необходимы для разработки: Devel предоставляет инструменты отладки, Twig Tweak добавляет полезные функции Twig, а Kint дает вам красивый вывод переменных.
Быстрое замечание: Да, это drupal/kint-kint с повторяющимся именем. Это странно, но это реальное имя пакета из-за конфликта с другим пакетом.
Сгенерируйте тестовую статью для работы:
ddev drush devel-generate:content 1 --bundles=articleНайдите ID вашей статьи (вам это понадобится):
ddev drush sqlq "SELECT nid, title FROM node_field_data WHERE type='article' LIMIT 1"Теперь давайте переопределим template статьи, чтобы использовать наш компонент card. Создайте директорию template и скопируйте базовый template:
mkdir -p web/themes/custom/blog_sdc/templates/node
cp web/core/themes/stable9/templates/content/node.html.twig web/themes/custom/blog_sdc/templates/node/node--article.html.twigОткройте templates/node/node--article.html.twig и замените все одним оператором include:
{% include 'blog_sdc:card' with {
title: label,
content: content.body,
image: content.field_image,
tags: content.field_tags,
url: url
} %}Вот где происходит магия. Вы говорите Drupal "используй мой компонент card и передай ему эти значения". Обратите внимание, насколько это чисто по сравнению с традиционными переопределениями template.
Но нам нужно сказать нашему компоненту, какие данные ожидать. Отредактируйте components/card/card.component.yml:
'$schema': 'https://git.drupalcode.org/project/drupal/-/raw/10.1.x/core/modules/sdc/src/metadata.schema.json'
name: Card Component
status: stable
description: A reusable card component
props:
type: object
properties:
title:
type: string
title: Card Title
content:
type: string
title: Card Content
image:
type: string
title: Card Image
tags:
type: string
title: Card Tags
url:
type: string
title: Card URLСнова очистите кеш:
ddev drush crРазработка template компонента
Прежде чем добавлять Tailwind, давайте убедимся, что наш компонент действительно что-то отображает. Отредактируйте components/card/card.twig:
<article {{ attributes.addClass('card') }}>
{% if title %}
<h2 class="card__title">
{% if url %}
<a href="{{ url }}">{{ title }}</a>
{% else %}
{{ title }}
{% endif %}
</h2>
{% endif %}
{% if content %}
<div class="card__content">
{{ content }}
</div>
{% endif %}
</article>И добавьте базовые стили в components/card/card.css (скоро мы заменим это на Tailwind):
[data-component-id="blog_sdc:card"] {
border: 1px solid #ddd;
border-radius: 8px;
padding: 1.5rem;
margin-bottom: 1.5rem;
background: #fff;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
transition: box-shadow 0.3s ease;
}
[data-component-id="blog_sdc:card"]:hover {
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
}
[data-component-id="blog_sdc:card"] .card__title {
margin: 0 0 1rem 0;
font-size: 1.5rem;
color: #333;
}
[data-component-id="blog_sdc:card"] .card__title a {
color: #0073e6;
text-decoration: none;
}
[data-component-id="blog_sdc:card"] .card__title a:hover {
text-decoration: underline;
}
[data-component-id="blog_sdc:card"] .card__content {
color: #666;
line-height: 1.6;
}Очистите кеш и протестируйте:
ddev drush crПосетите вашу статью (вероятно, по адресу /node/1) и вы должны увидеть ваш компонент card в действии! Если вы выполнили тот SQL запрос ранее, вы будете точно знать, какой URL посетить.
Подключение Tailwind CSS v4 и Vite
Здесь все становится действительно интересным. Мы собираемся настроить современную систему сборки с Vite и Tailwind CSS v4. Перейдите в директорию вашей темы:
cd web/themes/custom/blog_sdc
bun init -yУстановите необходимые зависимости:
bun add -D vite @tailwindcss/vite tailwindcss
bun add -D prettier prettier-plugin-tailwindcss @ttskch/prettier-plugin-tailwindcss-anywherePrettier будет автоматически форматировать ваш код, включая сортировку классов Tailwind. Второй плагин Prettier особенный — он позволяет Prettier понимать Twig templates, что обычный Prettier не может.
Создайте файл .prettierrc:
{
"plugins": ["prettier-plugin-tailwindcss", "@ttskch/prettier-plugin-tailwindcss-anywhere"],
"overrides": [
{
"files": ["*.twig", "*.html.twig"],
"options": {
"parser": "anywhere",
"regex": "(?:class(?:Name)?|addClass)\\\\s*(?:[=:]|\\\\()\\\\s*[\"']([^\"'{}~]*)"
}
}
],
"printWidth": 100,
"semi": true,
"singleQuote": true,
"tabWidth": 2,
"trailingComma": "es5"
}Теперь настройте Vite. Создайте vite.config.js в корне вашей темы:
import { defineConfig } from 'vite';
import tailwindcss from '@tailwindcss/vite';
import { resolve } from 'path';
export default defineConfig({
plugins: [tailwindcss()],
build: {
outDir: 'dist',
manifest: true,
emptyOutDir: true,
rollupOptions: {
input: {
main: resolve(__dirname, 'src/main.js'),
},
output: {
entryFileNames: '[name]-[hash].js',
chunkFileNames: '[name]-[hash].js',
assetFileNames: '[name]-[hash].[ext]',
},
},
},
server: {
host: '0.0.0.0',
port: 5173,
strictPort: true,
cors: true,
},
base: process.env.NODE_ENV === 'production' ? '/themes/custom/blog_sdc/dist/' : '/',
});Эта конфигурация обрабатывает сборку ваших ресурсов с хешами для кеширования и настраивает dev сервер с горячей заменой модулей.
Создайте исходную директорию для ваших основных JavaScript и CSS файлов:
mkdir -p srcСоздайте src/main.css с директивами Tailwind:
@import 'tailwindcss';
@layer base {
body {
@apply text-gray-900;
}
}
@layer components {
/* Your custom component styles */
}
@layer utilities {
/* Your custom utilities */
}Вот где все становится умным. Создайте src/main.js с автоматическим обнаружением компонентов:
/**
* @file
* Main JavaScript entry point for blog_sdc theme.
*/
// Import main CSS (includes Tailwind)
import './main.css';
// Auto-discover component JavaScript files
const componentModules = import.meta.glob('../components/**/*.js', { eager: false });
// Initialize components
async function initializeComponents() {
const componentPaths = Object.keys(componentModules);
console.log(`[blog_sdc] Found ${componentPaths.length} component JS files`);
for (const path of componentPaths) {
const componentName = path.match(/components\/([^/]+)\//)?.[1];
const componentSelector = `[data-component-id="blog_sdc:${componentName}"]`;
if (document.querySelector(componentSelector)) {
console.log(`[blog_sdc] Loading component: ${componentName}`);
try {
const module = await componentModules[path]();
if (module.default && typeof module.default === 'function') {
module.default();
}
} catch (error) {
console.error(`[blog_sdc] Error loading component ${componentName}:`, error);
}
}
}
}
// Initialize when DOM is ready
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initializeComponents);
} else {
initializeComponents();
}
export { initializeComponents };Этот скрипт автоматически находит и загружает JavaScript для любого компонента, который есть на странице. Больше не нужно вручную импортировать каждый файл компонента!
Обновите ваш package.json полезными скриптами:
{
"name": "blog_sdc",
"type": "module",
"private": true,
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview",
"format": "prettier --write \"src/**/*.{js,css}\" \"components/**/*.{js,twig}\" \"templates/**/*.twig\""
}
}Поскольку мы теперь используем Tailwind, нам больше не нужен этот CSS файл:
rm components/card/card.cssТеперь давайте пересоберем компонент card с утилитами Tailwind. Отредактируйте components/card/card.twig:
<article {{ attributes.addClass('bg-white rounded-lg shadow-md hover:shadow-lg transition-shadow duration-300 overflow-hidden mb-6') }}>
{% if image %}
<div class="aspect-video overflow-hidden">
{{ image }}
</div>
{% endif %}
{% if title %}
<div class="p-6">
<h2 class="text-xl font-semibold text-gray-900 mb-3">
{% if url %}
<a href="{{ url }}" class="text-blue-600 hover:text-blue-800 transition-colors">
{{ title }}
</a>
{% else %}
{{ title }}
{% endif %}
</h2>
</div>
{% endif %}
{% if content %}
<div class="px-6 pb-4">
<div class="text-gray-600 leading-relaxed">
{{ content }}
</div>
</div>
{% endif %}
{% if tags %}
<div class="px-6 pb-6 border-t border-gray-100 pt-4">
<div class="flex flex-wrap gap-2">
{{ tags }}
</div>
</div>
{% endif %}
</article>Посмотрите на это — CSS файл не нужен. Все стили прямо там в template, используя утилитарные классы Tailwind.
Давайте добавим интерактивность. Создайте components/card/card.js:
/**
* @file
* Card component JavaScript.
*/
export default function initCard() {
const cards = document.querySelectorAll('[data-component-id="blog_sdc:card"]');
console.log(`[Card] Initializing ${cards.length} card(s)`);
cards.forEach((card) => {
card.addEventListener('mouseenter', () => {
card.classList.add('card--hover');
});
card.addEventListener('mouseleave', () => {
card.classList.remove('card--hover');
});
const title = card.querySelector('.card__title');
if (title) {
console.log(`[Card] Initialized: ${title.textContent.trim()}`);
}
});
}Теперь соберите все:
bun run buildОбратите внимание на вывод. Вы увидите что-то вроде:
dist/main-BuEGDbdA.css
dist/main-BicIW-eB.jsЭти значения хеша (BuEGDbdA и BicIW-eB) важны. Скопируйте их — они вам понадобятся через секунду.
Отредактируйте blog_sdc.libraries.yml и добавьте ваши собранные ресурсы с реальными значениями хеша:
# Vite-built assets with Tailwind CSS
vite-main:
css:
theme:
dist/main-BuEGDbdA.css: {} # Replace with your actual hash
js:
dist/main-BicIW-eB.js: {} # Replace with your actual hash
dependencies:
- core/drupal
# Legacy theme library (if it exists)
global:
css:
theme:
css/style.css: {}Важно: Эти значения хеша меняются каждый раз, когда вы собираете. Если вы изменяете исходные файлы и пересобираете, вам нужно будет обновить эти хеши в вашем файле библиотек. Это раздражает, но так работает кеш-busting.
Теперь скажите вашей теме загрузить эту библиотеку. Отредактируйте blog_sdc.info.yml:
name: Blog SDC
type: theme
description: 'A custom theme for testing SDC components'
package: Custom
core_version_requirement: ^10 || ^11
base theme: stable9
libraries:
- blog_sdc/vite-mainОчистите кеш еще раз:
ddev drush crХотите, чтобы ваш код выглядел красиво? Запустите форматтер:
bun run formatДобавление Storybook для разработки компонентов
Вот где рабочий процесс действительно сияет. Storybook позволяет разрабатывать компоненты изолированно, без необходимости навигации через административный интерфейс Drupal или создания тестового контента. Инициализируйте его:
npx storybook@latest init --builder vite --type html --no-dev --yesОчистите примеры stories, которые они включают:
rm -rf storiesУстановите accessibility addon (это очень полезно):
bun add -D @storybook/addon-a11y@10.0.7Удалите конфигурации TypeScript (мы оставляем все просто):
rm .storybook/main.ts .storybook/preview.tsСоздайте .storybook/main.js:
import { join } from 'node:path';
import { cwd } from 'node:process';
const config = {
stories: ['../components/**/*.stories.js'],
addons: [
'@storybook/addon-a11y',
'@storybook/addon-docs',
],
framework: {
name: '@storybook/html-vite',
options: {
builder: {
viteConfigPath: './.storybook/vite.config.js',
},
},
},
core: {
disableTelemetry: true,
},
};
export default config;Создайте .storybook/preview.js для настройки отображения stories:
import '../src/main.css';
export const parameters = {
actions: { argTypesRegex: '^on[A-Z].*' },
controls: {
matchers: {
color: /(background|color)$/i,
date: /Date$/,
},
},
backgrounds: {
default: 'light',
values: [
{ name: 'light', value: '#ffffff' },
{ name: 'dark', value: '#1a1a1a' },
{ name: 'gray', value: '#f3f4f6' },
],
},
layout: 'padded',
a11y: {
config: {
rules: [{ id: 'color-contrast', enabled: true }],
},
},
};
export const decorators = [
(Story) => {
const wrapper = document.createElement('div');
wrapper.style.padding = '1rem';
wrapper.appendChild(Story());
return wrapper;
},
];Создайте .storybook/vite.config.js (отдельно от вашей основной конфигурации Vite):
import { defineConfig } from 'vite';
import tailwindcss from '@tailwindcss/vite';
export default defineConfig({
plugins: [tailwindcss()],
optimizeDeps: {
include: [],
},
});Теперь создайте вашу первую story. Создайте components/card/card.stories.js:
export default {
title: 'Components/Card',
tags: ['autodocs'],
parameters: { layout: 'padded' },
argTypes: {
title: { control: 'text', description: 'The title displayed in the card header' },
content: { control: 'text', description: 'The main content of the card' },
image: { control: 'text', description: 'Optional image HTML for the card' },
tags: { control: 'text', description: 'Optional tags HTML for the card' },
url: { control: 'text', description: 'Optional URL for the title link' },
},
};
const renderCard = (args) => {
const article = document.createElement('article');
article.className = 'mb-6 overflow-hidden rounded-lg bg-white shadow-md transition-shadow duration-300 hover:shadow-lg';
article.setAttribute('data-component-id', 'blog_sdc:card');
if (args.image) {
const imageDiv = document.createElement('div');
imageDiv.className = 'aspect-video overflow-hidden';
imageDiv.innerHTML = args.image;
article.appendChild(imageDiv);
}
if (args.title) {
const titleDiv = document.createElement('div');
titleDiv.className = 'p-6';
const h2 = document.createElement('h2');
h2.className = 'mb-3 text-xl font-semibold text-gray-900';
if (args.url) {
const link = document.createElement('a');
link.href = args.url;
link.className = 'text-blue-600 transition-colors hover:text-blue-800';
link.textContent = args.title;
h2.appendChild(link);
} else {
h2.textContent = args.title;
}
titleDiv.appendChild(h2);
article.appendChild(titleDiv);
}
if (args.content) {
const contentDiv = document.createElement('div');
contentDiv.className = 'px-6 pb-4';
const innerDiv = document.createElement('div');
innerDiv.className = 'leading-relaxed text-gray-600';
innerDiv.innerHTML = args.content;
contentDiv.appendChild(innerDiv);
article.appendChild(contentDiv);
}
if (args.tags) {
const tagsDiv = document.createElement('div');
tagsDiv.className = 'border-t border-gray-100 px-6 pt-4 pb-6';
const innerDiv = document.createElement('div');
innerDiv.className = 'flex flex-wrap gap-2';
innerDiv.innerHTML = args.tags;
tagsDiv.appendChild(innerDiv);
article.appendChild(tagsDiv);
}
return article;
};
export const Basic = {
args: { title: 'Basic Card', content: 'This is a basic card with minimal styling.' },
render: renderCard,
};
export const WithImage = {
args: {
title: 'Card with Image',
content: 'This card includes a featured image at the top.',
image: '<img src="https://placehold.co/600x400" alt="Placeholder" class="w-full h-auto" />',
url: '/example',
},
render: renderCard,
};
export const Complete = {
args: {
title: 'Complete Card',
content: 'This card showcases all available features.',
image: '<img src="https://placehold.co/600x400" alt="Complete" class="w-full h-auto" />',
tags: '<span class="px-2 py-1 bg-blue-100 text-blue-800 rounded text-sm">Featured</span>',
url: '/complete',
},
parameters: { backgrounds: { default: 'gray' } },
render: renderCard,
};Обновите ваш package.json еще раз, чтобы добавить скрипты Storybook:
{
"name": "blog_sdc",
"type": "module",
"private": true,
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview",
"format": "prettier --write \"src/**/*.{js,css}\" \"components/**/*.{js,twig}\" \"templates/**/*.twig\"",
"storybook": "storybook dev -p 6006",
"build-storybook": "storybook build"
}
}Запустите Storybook:
bun run storybookОткройте http://localhost:6006 в вашем браузере. Вы должны увидеть ваш компонент card с тремя различными вариантами, с которыми вы можете взаимодействовать и изменять в реальном времени.
Хотите собрать статическую версию для развертывания или обмена с вашей командой?
bun run build-storybookВаш ежедневный рабочий процесс разработки
Теперь, когда все настроено, вот как вы будете работать изо дня в день.
Создание новых компонентов
Давайте пройдем через добавление нового компонента с нуля. Скажем, вы хотите добавить компонент Button:
Сгенерируйте компонент:
ddev drush generate sdc
# Theme: blog_sdc, Component: Button, machine: buttonУдалите CSS файл (мы используем Tailwind, помните?):
rm components/button/button.cssСтилизуйте его с помощью Tailwind в components/button/button.twig:
<button {{ attributes.addClass('px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700 transition-colors') }}>
{{ label }}
</button>Добавьте JavaScript при необходимости в components/button/button.js:
export default function initButton() {
console.log('Button component initialized');
// Your button logic here
}components/button/button.stories.js с различными вариантамиПротестируйте в Storybook:
bun run storybookСоберите ваши ресурсы:
bun run buildblog_sdc.libraries.yml новыми значениями из вывода сборкиОчистите кеш Drupal:
ddev drush crОтформатируйте ваш код:
bun run formatОбщие ежедневные команды
Вот команды, которые вы будете использовать постоянно:
# Запустить Vite dev сервер (горячая перезагрузка, намного быстрее, чем пересборка)
bun run dev
# Запустить Storybook (обычно в отдельном терминале)
bun run storybook
# Сгенерировать тестовый контент, когда вам нужно больше статей для работы
ddev drush devel-generate:content 5 --bundles=article
# Найти ID узлов для посещения конкретного контента
ddev drush sqlq "SELECT nid, title FROM node_field_data WHERE type='article' LIMIT 5"
# Очистить кеш (серьезно, делайте это постоянно)
ddev drush cr
# Проверить, какие темы установлены
ddev drush pm:list --type=theme
# Удалить тему, если нужно начать заново
ddev drush theme:uninstall blog_sdcУстранение распространенных проблем
Давайте будем честны — что-то пойдет не так. Вот как исправить наиболее распространенные проблемы.
Ошибка "Unmet Dependencies" при установке темы
Это обычно означает, что ваш blog_sdc.info.yml имеет проблему. Проверьте, что base theme: установлен на допустимое имя темы, например stable9. Генератор Drush иногда путается и смешивает порядок входных данных во время генерации.
Компонент не отображается
Это почти всегда проблема кеширования. Запустите ddev drush cr и попробуйте снова. Если это не работает:
- Проверьте, что файлы компонента действительно существуют в
web/themes/custom/blog_sdc/components/card/ - Проверьте, что ваша тема установлена:
ddev drush pm:list --type=theme - Подтвердите, что ваша тема установлена по умолчанию:
ddev drush config-get system.theme default
Стили Tailwind не загружаются
Несколько вещей для проверки:
- Вы запустили
bun run build? - Вы обновили значения хеша в
blog_sdc.libraries.yml? - Библиотека действительно загружается? Проверьте
blog_sdc.info.ymlна наличие секцииlibraries: - Откройте инструменты разработчика вашего браузера, перейдите на вкладку Network и поищите ваш CSS файл
- Очистите кеш Drupal:
ddev drush cr
Сборка Vite не удается
Обычно это означает проблему с зависимостями:
- Запустите
bun installдля переустановки всего - Проверьте, что
src/main.jsиsrc/main.cssсуществуют - Проверьте, что
vite.config.jsнаходится в корневой директории вашей темы - Посмотрите на сообщение об ошибке — Vite обычно точно говорит, что не так
Storybook не запускается
- Проверьте вашу версию Node.js (рекомендуется 18 или выше)
- Удалите
node_modulesи запуститеbun installснова - Убедитесь, что ничто другое не использует порт 6006
- Проверьте, что
.storybook/main.jsи.storybook/preview.jsсуществуют
Изменения не отображаются
Классическая проблема. Попробуйте это по порядку:
- Очистите кеш Drupal:
ddev drush cr - Жесткое обновление вашего браузера:
Cmd+Shift+Rна Mac илиCtrl+Shift+Rна Windows/Linux - Проверьте консоль вашего браузера на наличие ошибок JavaScript
- Убедитесь, что вы редактируете файлы в правильной директории темы
Ошибка модуля Kint
Если вы получаете ошибку при установке Kint, используйте точное имя пакета: drupal/kint-kint (да, "kint" появляется дважды). Это из-за конфликта имен с другим пакетом. Это запутанно, но так оно и есть.
Краткий справочник команд
Для тех случаев, когда вам просто нужно быстро скопировать и вставить что-то:
# Очистить кеш (используйте это постоянно)
ddev drush cr
# Сгенерировать новый SDC компонент
ddev drush generate sdc
# Собрать ресурсы для production
bun run build
# Запустить dev сервер с горячей перезагрузкой
bun run dev
# Запустить Storybook
bun run storybook
# Отформатировать ваш код
bun run format
# Сгенерировать тестовый контент
ddev drush devel-generate:content 5 --bundles=article
# Найти ID узлов
ddev drush sqlq "SELECT nid, title FROM node_field_data WHERE type='article' LIMIT 5"
# Проверить установленные темы
ddev drush pm:list --type=theme
# Получить текущую тему по умолчанию
ddev drush config-get system.theme default
# Собрать статический Storybook для развертывания
bun run build-storybookЛучшие практики
Разработка темы
- Используйте базовую тему, такую как
stable9, для прочной основы - Часто очищайте кеш с помощью
drush cr(серьезно, делайте это часто) - Держите SDC компоненты сфокусированными и переиспользуемыми
- Следуйте соглашениям об именовании: строчные буквы с подчеркиваниями
- Тестируйте перед развертыванием в production
Разработка компонентов
- Удаляйте сгенерированные CSS файлы при использовании Tailwind
- Храните JavaScript компонентов рядом с Twig templates
- Документируйте props в метаданных
component.yml - Используйте семантическое версионирование для зависимостей
- Держите компоненты организованными и сфокусированными
Процесс сборки
- Всегда используйте
bun run buildдля production развертываний - Обновляйте хеши библиотек после каждой сборки
- Используйте HMR с
bun run devво время разработки - Форматируйте код перед коммитом с помощью
bun run format - Собирайте для production перед отправкой изменений
Лучшие практики Storybook
- Один файл story на компонент
- Используйте описательные имена story
- Тестируйте граничные случаи (пустые состояния, ошибки, загрузка)
- Включайте autodocs с
tags: ['autodocs'] - Всегда проверяйте панель a11y перед коммитом
- Тестируйте компоненты на разных цветах фона
- Держите stories простыми и сфокусированными
Что вы создали
Это не просто тема — это полная современная среда разработки:
- Пользовательская тема Drupal с поддержкой SDC
- Tailwind CSS v4 со стилизацией utility-first
- Система сборки Vite с горячей перезагрузкой
- Автоматическое обнаружение JavaScript компонентов
- Storybook для изолированной разработки компонентов
- Prettier для форматирования кода
- Встроенное тестирование доступности
- Полный рабочий процесс разработки
График разработки
Основываясь на этом рабочем процессе, вот что вы можете ожидать:
Начальная настройка
- Генерация темы: ~5 минут
- Настройка Tailwind + Vite: ~10 минут
- Интеграция Storybook: ~10 минут
- Всего: ~25-30 минут
На компонент
- Генерация компонента: ~30 секунд
- Дизайн с Tailwind: ~5-10 минут
- Добавление JavaScript: ~5-10 минут (при необходимости)
- Создание stories: ~5-10 минут
- Сборка и тестирование: ~2-3 минуты
- Всего: ~15-30 минут на компонент
Это значительно быстрее, чем традиционные рабочие процессы разработки тем Drupal.
Ключевые выводы
- Современная разработка тем Drupal объединяет SDC для переиспользуемых компонентов, Tailwind для utility-first стилизации, Vite для быстрых сборок и Storybook для изолированной разработки
- Полная настройка занимает 25-30 минут, новые компоненты готовы за 15-30 минут
- Этот рабочий процесс конкурирует с современными JavaScript фреймворками, оставаясь в экосистеме Drupal
- Автоматическое обнаружение компонентов масштабируется по мере добавления новых компонентов
- Интеграция Prettier поддерживает ваш код чистым и последовательным
- Отдельные CSS файлы не нужны — утилиты Tailwind заменяют пользовательский CSS
- Ручная интеграция дает вам полный контроль над загрузкой ресурсов
- Тестирование доступности встроено с самого начала
- Опыт разработки быстрый, современный и приятный
Заключение
Теперь у вас есть готовый к production современный рабочий процесс разработки тем, который выводит Drupal в ту же лигу, что и современные JavaScript фреймворки. Комбинация SDC, Tailwind, Vite и Storybook дает вам изоляцию компонентов, быструю итерацию и опыт разработчика, который действительно приятен.
Настоящая сила здесь не только в инструментах — это то, как они работают вместе. Вы можете прототипировать в Storybook, стилизовать с помощью утилитарных классов и развертывать в Drupal без переключения контекста. Ваши компоненты портативны, ваши сборки быстры, и ваш рабочий процесс масштабируется по мере роста вашего проекта.
Начните с малого. Создайте несколько компонентов, освойтесь с рабочим процессом, затем расширяйте вашу библиотеку компонентов. Вскоре вы будете задаваться вопросом, как вы вообще создавали темы Drupal по-другому.
Приятной разработки тем! 🎨