Перейти к основному содержанию
Login

Современная разработка тем для Drupal: работа с SDC, Tailwind, Vite и Storybook

Denys Kravchenko profile picture
Автор: Denys Kravchenko
Drupal тему с SDC и Storybook

Если вы следили за развитием 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 ожидает, что пользовательские темы будут находиться в определенном месте:

bash
mkdir -p web/themes/custom

Теперь начинается интересная часть. У Drush есть генератор тем, который задаст вам кучу вопросов. Не волнуйтесь — я скажу вам, что отвечать:

bash
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 (пока оставим все просто)

После создания темы давайте установим ее и сделаем темой по умолчанию для вашего сайта:

bash
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:

bash
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. Вы будете делать это часто, так что привыкайте к этой команде:

bash
ddev drush cr

Заставляем ваш компонент работать

Теперь начинается интересное. Нам нужно подключить наш компонент к реальному контенту Drupal. Сначала давайте настроим отладку Twig, чтобы вы могли видеть, что происходит под капотом.

Скопируйте файл services по умолчанию:

bash
cp web/sites/default/default.services.yml web/sites/default/services.yml

Откройте web/sites/default/services.yml и найдите секцию twig.config. Вы меняете только три значения здесь — не заменяйте всю секцию:

yaml
parameters:
  twig.config:
    debug: true
    auto_reload: true
    cache: false

Эти настройки покажут вам полезные комментарии в исходном коде HTML и будут автоматически перезагружать templates. Спасители жизни во время разработки.

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

bash
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 с повторяющимся именем. Это странно, но это реальное имя пакета из-за конфликта с другим пакетом.

Сгенерируйте тестовую статью для работы:

bash
ddev drush devel-generate:content 1 --bundles=article

Найдите ID вашей статьи (вам это понадобится):

bash
ddev drush sqlq "SELECT nid, title FROM node_field_data WHERE type='article' LIMIT 1"

Теперь давайте переопределим template статьи, чтобы использовать наш компонент card. Создайте директорию template и скопируйте базовый template:

bash
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:

plaintext
{% 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:

yaml
22 lines
'$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

Снова очистите кеш:

bash
ddev drush cr

Разработка template компонента

Прежде чем добавлять Tailwind, давайте убедимся, что наш компонент действительно что-то отображает. Отредактируйте components/card/card.twig:

plaintext
<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):

css
33 lines
[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;
}

Очистите кеш и протестируйте:

bash
ddev drush cr

Посетите вашу статью (вероятно, по адресу /node/1) и вы должны увидеть ваш компонент card в действии! Если вы выполнили тот SQL запрос ранее, вы будете точно знать, какой URL посетить.

Подключение Tailwind CSS v4 и Vite

Здесь все становится действительно интересным. Мы собираемся настроить современную систему сборки с Vite и Tailwind CSS v4. Перейдите в директорию вашей темы:

bash
cd web/themes/custom/blog_sdc
bun init -y

Установите необходимые зависимости:

bash
bun add -D vite @tailwindcss/vite tailwindcss
bun add -D prettier prettier-plugin-tailwindcss @ttskch/prettier-plugin-tailwindcss-anywhere

Prettier будет автоматически форматировать ваш код, включая сортировку классов Tailwind. Второй плагин Prettier особенный — он позволяет Prettier понимать Twig templates, что обычный Prettier не может.

Создайте файл .prettierrc:

json
{
  "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 в корне вашей темы:

jsx
29 lines
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 файлов:

bash
mkdir -p src

Создайте src/main.css с директивами Tailwind:

css
@import 'tailwindcss';

@layer base {
  body {
    @apply text-gray-900;
  }
}

@layer components {
  /* Your custom component styles */
}

@layer utilities {
  /* Your custom utilities */
}

Вот где все становится умным. Создайте src/main.js с автоматическим обнаружением компонентов:

jsx
44 lines
/**
 * @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 полезными скриптами:

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 файл:

bash
rm components/card/card.css

Теперь давайте пересоберем компонент card с утилитами Tailwind. Отредактируйте components/card/card.twig:

plaintext
37 lines
<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:

jsx
25 lines
/**
 * @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()}`);
    }
  });
}

Теперь соберите все:

bash
bun run build

Обратите внимание на вывод. Вы увидите что-то вроде:

plaintext
dist/main-BuEGDbdA.css
dist/main-BicIW-eB.js

Эти значения хеша (BuEGDbdA и BicIW-eB) важны. Скопируйте их — они вам понадобятся через секунду.

Отредактируйте blog_sdc.libraries.yml и добавьте ваши собранные ресурсы с реальными значениями хеша:

yaml
# 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:

yaml
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

Очистите кеш еще раз:

bash
ddev drush cr

Хотите, чтобы ваш код выглядел красиво? Запустите форматтер:

bash
bun run format

Добавление Storybook для разработки компонентов

Вот где рабочий процесс действительно сияет. Storybook позволяет разрабатывать компоненты изолированно, без необходимости навигации через административный интерфейс Drupal или создания тестового контента. Инициализируйте его:

bash
npx storybook@latest init --builder vite --type html --no-dev --yes

Очистите примеры stories, которые они включают:

bash
rm -rf stories

Установите accessibility addon (это очень полезно):

bash
bun add -D @storybook/addon-a11y@10.0.7

Удалите конфигурации TypeScript (мы оставляем все просто):

bash
rm .storybook/main.ts .storybook/preview.ts

Создайте .storybook/main.js:

jsx
23 lines
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:

jsx
34 lines
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):

jsx
import { defineConfig } from 'vite';
import tailwindcss from '@tailwindcss/vite';

export default defineConfig({
  plugins: [tailwindcss()],
  optimizeDeps: {
    include: [],
  },
});

Теперь создайте вашу первую story. Создайте components/card/card.stories.js:

jsx
92 lines
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:

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\"",
    "storybook": "storybook dev -p 6006",
    "build-storybook": "storybook build"
  }
}

Запустите Storybook:

bash
bun run storybook

Откройте http://localhost:6006 в вашем браузере. Вы должны увидеть ваш компонент card с тремя различными вариантами, с которыми вы можете взаимодействовать и изменять в реальном времени.

Storybook 10 running in browser

Хотите собрать статическую версию для развертывания или обмена с вашей командой?

bash
bun run build-storybook

Ваш ежедневный рабочий процесс разработки

Теперь, когда все настроено, вот как вы будете работать изо дня в день.

Создание новых компонентов

Давайте пройдем через добавление нового компонента с нуля. Скажем, вы хотите добавить компонент Button:

  1. Сгенерируйте компонент:

bash
ddev drush generate sdc
# Theme: blog_sdc, Component: Button, machine: button
  • Удалите CSS файл (мы используем Tailwind, помните?):

  • bash
    rm components/button/button.css
  • Стилизуйте его с помощью Tailwind в components/button/button.twig:

  • plaintext
    <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:

  • jsx
    export default function initButton() {
      console.log('Button component initialized');
      // Your button logic here
    }
  • Создайте story в components/button/button.stories.js с различными вариантами
  • Протестируйте в Storybook:

  • bash
    bun run storybook
  • Соберите ваши ресурсы:

  • bash
    bun run build
  • Обновите хеши библиотеки в blog_sdc.libraries.yml новыми значениями из вывода сборки
  • Очистите кеш Drupal:

  • bash
    ddev drush cr
  • Отформатируйте ваш код:

  • bash
    bun run format

    Общие ежедневные команды

    Вот команды, которые вы будете использовать постоянно:

    bash
    # Запустить 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 существуют

    Изменения не отображаются

    Классическая проблема. Попробуйте это по порядку:

    1. Очистите кеш Drupal: ddev drush cr
    2. Жесткое обновление вашего браузера: Cmd+Shift+R на Mac или Ctrl+Shift+R на Windows/Linux
    3. Проверьте консоль вашего браузера на наличие ошибок JavaScript
    4. Убедитесь, что вы редактируете файлы в правильной директории темы

    Ошибка модуля Kint

    Если вы получаете ошибку при установке Kint, используйте точное имя пакета: drupal/kint-kint (да, "kint" появляется дважды). Это из-за конфликта имен с другим пакетом. Это запутанно, но так оно и есть.

    Краткий справочник команд

    Для тех случаев, когда вам просто нужно быстро скопировать и вставить что-то:

    bash
    32 lines
    # Очистить кеш (используйте это постоянно)
    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

    Лучшие практики

    Разработка темы

    1. Используйте базовую тему, такую как stable9, для прочной основы
    2. Часто очищайте кеш с помощью drush cr (серьезно, делайте это часто)
    3. Держите SDC компоненты сфокусированными и переиспользуемыми
    4. Следуйте соглашениям об именовании: строчные буквы с подчеркиваниями
    5. Тестируйте перед развертыванием в production

    Разработка компонентов

    1. Удаляйте сгенерированные CSS файлы при использовании Tailwind
    2. Храните JavaScript компонентов рядом с Twig templates
    3. Документируйте props в метаданных component.yml
    4. Используйте семантическое версионирование для зависимостей
    5. Держите компоненты организованными и сфокусированными

    Процесс сборки

    1. Всегда используйте bun run build для production развертываний
    2. Обновляйте хеши библиотек после каждой сборки
    3. Используйте HMR с bun run dev во время разработки
    4. Форматируйте код перед коммитом с помощью bun run format
    5. Собирайте для production перед отправкой изменений

    Лучшие практики Storybook

    1. Один файл story на компонент
    2. Используйте описательные имена story
    3. Тестируйте граничные случаи (пустые состояния, ошибки, загрузка)
    4. Включайте autodocs с tags: ['autodocs']
    5. Всегда проверяйте панель a11y перед коммитом
    6. Тестируйте компоненты на разных цветах фона
    7. Держите 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 по-другому.


    Приятной разработки тем! 🎨