Разработка кастомной темы Drupal

Наша компания занимается разработкой, поддержкой и обслуживанием сайтов любой сложности. От простых одностраничных сайтов до масштабных кластерных систем построенных на микро сервисах. Опыт разработчиков подтвержден сертификатами от вендоров.
Разработка и обслуживание любых видов сайтов:
Информационные сайты или веб-приложения
Сайты визитки, landing page, корпоративные сайты, онлайн каталоги, квиз, промо-сайты, блоги, новостные ресурсы, информационные порталы, форумы, агрегаторы
Сайты или веб-приложения электронной коммерции
Интернет-магазины, B2B-порталы, маркетплейсы, онлайн-обменники, кэшбэк-сайты, биржи, дропшиппинг-платформы, парсеры товаров
Веб-приложения для управления бизнес-процессами
CRM-системы, ERP-системы, корпоративные порталы, системы управления производством, парсеры информации
Сайты или веб-приложения электронных услуг
Доски объявлений, онлайн-школы, онлайн-кинотеатры, конструкторы сайтов, порталы предоставления электронных услуг, видеохостинги, тематические порталы

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

Предлагаемые услуги
Показано 1 из 1 услугВсе 2065 услуг
Разработка кастомной темы Drupal
Средняя
~1-2 недели
Часто задаваемые вопросы
Наши компетенции:
Этапы разработки
Последние работы
  • image_website-b2b-advance_0.png
    Разработка сайта компании B2B ADVANCE
    1262
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1171
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    874
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1094
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    831
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Разработка веб-сайта для компании ФИКСПЕР
    851

Разработка кастомной темы Drupal

Темы Drupal — это набор Twig-шаблонов, CSS, JS и конфигурационных YAML-файлов. Разработка с нуля или на основе базовой темы. Contrib-темы типа Bootstrap или Gin используются как стартовые точки или для админки.

Структура темы

web/themes/custom/my_theme/
├── my_theme.info.yml          # описание темы
├── my_theme.libraries.yml     # CSS/JS библиотеки
├── my_theme.theme             # PHP хуки темы
├── config/
│   └── install/               # конфиг, устанавливаемый с темой
├── css/
│   ├── base.css
│   ├── layout.css
│   └── components/
├── js/
│   └── main.js
├── images/
├── fonts/
├── templates/
│   ├── layout/
│   │   ├── html.html.twig
│   │   └── page.html.twig
│   ├── content/
│   │   ├── node.html.twig
│   │   ├── node--article.html.twig
│   │   └── node--article--teaser.html.twig
│   ├── block/
│   │   └── block.html.twig
│   └── field/
│       └── field--body.html.twig
└── screenshot.png

my_theme.info.yml

name: My Theme
type: theme
description: 'Кастомная тема проекта'
core_version_requirement: ^10
base theme: stable9   # или false если с нуля
libraries:
  - my_theme/global
regions:
  header: Header
  primary_menu: 'Primary menu'
  breadcrumb: Breadcrumb
  highlighted: Highlighted
  content: Content
  sidebar_first: 'Sidebar first'
  footer: Footer

Библиотеки CSS/JS

# my_theme.libraries.yml
global:
  version: VERSION
  css:
    base:
      css/base.css: {}
    layout:
      css/layout.css: {}
      css/components/header.css: {}
  js:
    js/main.js: { defer: true }
  dependencies:
    - core/drupal
    - core/jquery

# Отдельная библиотека для слайдера — загружается только при необходимости
slider:
  version: VERSION
  css:
    component:
      css/components/slider.css: {}
  js:
    js/slider.js: {}
  dependencies:
    - core/once

Библиотеки подключаем в шаблоне:

{# В шаблоне node--landing.html.twig #}
{{ attach_library('my_theme/slider') }}

Twig шаблоны

Drupal использует иерархию шаблонов — чем специфичнее имя, тем выше приоритет:

node.html.twig                      # все ноды
node--article.html.twig             # тип article
node--article--full.html.twig       # тип article, вид full
node--123.html.twig                 # конкретная нода по id

Пример переопределения шаблона статьи:

{# templates/content/node--article--full.html.twig #}
<article{{ attributes.addClass('article', 'article--full') }}>
  {% if label %}
    <h1{{ title_attributes.addClass('article__title') }}>
      {{ label }}
    </h1>
  {% endif %}

  <div class="article__meta">
    {% if display_submitted %}
      <span class="article__author">{{ author_name }}</span>
      <time class="article__date" datetime="{{ date.attributes.datetime }}">
        {{ date }}
      </time>
    {% endif %}

    {% if content.field_tags %}
      <div class="article__tags">
        {{ content.field_tags }}
      </div>
    {% endif %}
  </div>

  {% if content.field_image %}
    <div class="article__cover">
      {{ content.field_image }}
    </div>
  {% endif %}

  <div class="article__body prose">
    {{ content.body }}
  </div>

  {# Рендерим остальные поля кроме тех, что уже вывели #}
  {{ content|without('body', 'field_tags', 'field_image', 'links') }}
</article>

PHP хуки в .theme файле

// my_theme.theme

/**
 * Добавляем переменные в шаблон страницы.
 */
function my_theme_preprocess_page(array &$variables): void {
  $variables['site_name'] = \Drupal::config('system.site')->get('name');
  $variables['is_front'] = \Drupal::service('path.matcher')->isFrontPage();

  // Хлебные крошки с кастомной логикой
  $route = \Drupal::routeMatch();
  if ($node = $route->getParameter('node')) {
    $variables['node_type'] = $node->bundle();
  }
}

/**
 * Переменные для шаблона ноды.
 */
function my_theme_preprocess_node(array &$variables): void {
  $node = $variables['node'];

  if ($node->bundle() === 'article') {
    $variables['reading_time'] = my_theme_calculate_reading_time($node->get('body')->value);
  }
}

function my_theme_calculate_reading_time(string $html): int {
  $text = strip_tags($html);
  $words = str_word_count($text);
  return (int) ceil($words / 200); // 200 слов в минуту
}

/**
 * Переопределяем предложения шаблонов — для дебага.
 */
function my_theme_theme_suggestions_node_alter(array &$suggestions, array $variables): void {
  $node = $variables['elements']['#node'];
  $view_mode = $variables['elements']['#view_mode'];

  // Добавляем suggestion по полю типа материала
  if ($node->hasField('field_material_type') && !$node->get('field_material_type')->isEmpty()) {
    $type = $node->get('field_material_type')->value;
    $suggestions[] = 'node__' . $node->bundle() . '__' . $type;
  }
}

/**
 * Alter для form — добавляем классы.
 */
function my_theme_form_alter(array &$form, FormStateInterface $form_state, string $form_id): void {
  if ($form_id === 'contact_message_feedback_form') {
    $form['#attributes']['class'][] = 'contact-form';
    $form['actions']['submit']['#attributes']['class'][] = 'btn btn--primary';
  }
}

Responsive images и image styles

# config/install/image.style.article_cover.yml
langcode: en
status: true
id: article_cover
label: 'Article cover'
effects:
  uuid1:
    id: image_scale_and_crop
    data:
      anchor: center-center
      width: 1200
      height: 630

В шаблоне используем responsive_image вместо обычного image:

{% if content.field_image %}
  {{- content.field_image -}}
{% endif %}

Responsive image group настраивается в /admin/config/media/responsive-image-style через UI или YAML.

Сборка фронтенда (опционально)

Если тема использует npm-сборку:

// package.json
{
  "scripts": {
    "build": "postcss css/src -o css --map",
    "watch": "postcss css/src -o css --watch"
  }
}

Или Vite/Webpack если нужны ES-модули, TypeScript:

// vite.config.js
export default {
  build: {
    outDir: 'dist',
    rollupOptions: {
      input: { main: 'js/src/main.ts' },
      output: { entryFileNames: 'js/[name].js' }
    }
  },
  css: { postcss: './postcss.config.js' }
}

Дебаг шаблонов

В settings.local.php включаем Twig дебаг:

$config['system.performance']['css']['preprocess'] = FALSE;
$config['system.performance']['js']['preprocess'] = FALSE;
$settings['cache']['bins']['render'] = 'cache.backend.null';

// Показывает имена шаблонов в HTML комментариях
$config['twig.settings']['debug'] = TRUE;

После включения в source code страницы будут видны все использованные шаблоны и доступные suggestions. Это основной инструмент при разработке.

Сроки

Базовая тема с шаблонами для основных типов контента, адаптивная, с библиотеками: 5–8 дней. С анимациями, сложным JS, responsive images, кастомными блоками: 10–15 дней.