Разработка кастомной темы Magento 2
Тема Magento 2 — это набор XML-конфигураций, PHTML-шаблонов, Less/CSS и JavaScript, определяющих внешний вид и поведение фронтенда. В отличие от Shopify, здесь нет визуального редактора — разработка ведётся файлами с пониманием системы наследования тем и layout XML.
Система наследования тем
Magento темы строятся по цепочке наследования. Кастомная тема не копирует все файлы базовой — она переопределяет только нужные. Пример цепочки:
Magento/blank
└── Magento/luma
└── MyCompany/mytheme (кастомная тема)
При рендере Magento ищет шаблон сначала в кастомной теме, потом поднимается по цепочке к базовой. Это позволяет изменить один файл, не трогая остальные.
Структура файлов темы
app/design/frontend/MyCompany/mytheme/
├── etc/
│ └── view.xml # конфигурация изображений (размеры, качество)
├── i18n/
│ └── ru_RU.csv # переводы строк
├── media/
│ └── preview.jpg # превью в Admin
├── registration.php # регистрация темы
├── theme.xml # метаданные и parent
├── web/
│ ├── css/
│ │ ├── source/
│ │ │ ├── _theme.less # переменные темы
│ │ │ └── _extend.less # расширения базового CSS
│ │ └── _module.less
│ ├── fonts/
│ ├── images/
│ └── js/
│ └── theme.js
└── Magento_Catalog/ # переопределения модуля Catalog
├── layout/
│ └── catalog_product_view.xml
└── templates/
└── product/
└── view/
└── attributes.phtml
theme.xml:
<theme xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:framework:Config/etc/theme.xsd">
<title>MyCompany Theme</title>
<parent>Magento/luma</parent>
<media>
<preview_image>media/preview.jpg</preview_image>
</media>
</theme>
registration.php:
<?php
\Magento\Framework\Component\ComponentRegistrar::register(
\Magento\Framework\Component\ComponentRegistrar::THEME,
'frontend/MyCompany/mytheme',
__DIR__
);
Layout XML — основа системы рендеринга
Layout XML управляет структурой страницы: какие блоки где находятся, какие шаблоны используют. Без понимания Layout нельзя правильно изменить структуру страницы.
Типы layout-файлов:
-
default.xml— применяется ко всем страницам -
catalog_product_view.xml— страница продукта -
catalog_category_view.xml— страница категории -
checkout_cart_index.xml— корзина -
cms_index_index.xml— главная страница (CMS)
Пример — добавление кастомного блока на страницу продукта:
<!-- app/design/frontend/MyCompany/mytheme/Magento_Catalog/layout/catalog_product_view.xml -->
<?xml version="1.0"?>
<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
<body>
<!-- Удалить стандартный блок сравнения -->
<referenceBlock name="product.info.addtocart.additional" remove="true"/>
<!-- Добавить блок с гарантией после цены -->
<referenceContainer name="product.info.main">
<block class="MyCompany\Catalog\Block\Product\Warranty"
name="product.warranty.info"
template="MyCompany_Catalog::product/warranty.phtml"
after="product.info.price"/>
</referenceContainer>
<!-- Переместить отзывы выше вкладок -->
<move element="product.info.details" destination="content" before="product.info.media"/>
</body>
</page>
PHTML шаблоны
Шаблоны — это PHP-файлы с HTML-разметкой. Переопределение стандартного шаблона цены:
<?php
// app/design/frontend/MyCompany/mytheme/Magento_Catalog/templates/product/price/final_price.phtml
/** @var \Magento\Catalog\Block\Product\View\Description $block */
/** @var \Magento\Catalog\ViewModel\Product\Checker\AddToCompareAvailability $compareAvailability */
$priceType = $block->getPriceType('final_price');
$id = $block->getPriceId() ?: 'product-price-' . $block->getProduct()->getId();
?>
<span class="price-wrapper price-final_price"
data-price-amount="<?= $block->escapeHtmlAttr($priceType->getAmount()->getValue()) ?>"
data-price-type="finalPrice">
<?php if ($block->getProduct()->hasCustomOptions()): ?>
<span class="price-label"><?= $block->escapeHtml(__('As low as')) ?></span>
<?php endif; ?>
<span id="<?= $block->escapeHtmlAttr($id) ?>" class="price-container">
<span class="price">
<?= $block->renderAmount($priceType->getAmount(), ['display_label' => false]) ?>
</span>
</span>
</span>
Less/CSS система
Magento 2 использует Less с preprocessor-pipeline. Базовые переменные переопределяются в web/css/source/_theme.less:
// Фирменные цвета
@color-primary: #1a73e8;
@color-primary-darker: #1557b0;
@color-secondary: #ff6b35;
// Типографика
@font-family-name__base: 'Inter';
@font-size-base: 16px;
@line-height-base: 1.6;
@font-weight__regular: 400;
@font-weight__bold: 700;
// Кнопки
@button__background: @color-primary;
@button__border: 1px solid @color-primary;
@button__color: #ffffff;
@button__hover__background: @color-primary-darker;
// Хедер
@header__background-color: #ffffff;
@header-icons-color: @color-primary;
// Брейкпоинты
@screen__m: 768px;
@screen__l: 1024px;
@screen__xl: 1440px;
Расширения в _extend.less:
// Импорт кастомного шрифта
@font-face {
font-family: 'Inter';
src: url('../fonts/Inter-Regular.woff2') format('woff2'),
url('../fonts/Inter-Regular.woff') format('woff');
font-weight: 400;
font-display: swap;
}
// Кастомный стиль карточки товара
.product-item {
border: 1px solid @color-border;
border-radius: 8px;
transition: box-shadow 0.2s ease;
&:hover {
box-shadow: 0 4px 20px rgba(0,0,0,0.12);
}
&-photo {
border-radius: 8px 8px 0 0;
overflow: hidden;
}
}
JavaScript в теме
Magento 2 использует RequireJS для загрузки модулей. Конфигурация в requirejs-config.js:
// web/requirejs-config.js
var config = {
map: {
'*': {
// Заменяем стандартный компонент
'Magento_Catalog/js/product/view/product-info': 'MyCompany_Catalog/js/product-info-extended',
}
},
config: {
mixins: {
'Magento_Checkout/js/view/minicart': {
'MyCompany_Checkout/js/view/minicart-mixin': true
}
}
}
};
Mixin для расширения существующего компонента без замены:
// web/js/view/minicart-mixin.js
define(['jquery', 'mage/utils/wrapper'], function ($, wrapper) {
'use strict';
return function (Component) {
return Component.extend({
// Переопределение метода
getCartParam: wrapper.wrap(Component.prototype.getCartParam, function (original, name) {
if (name === 'summary_count') {
// Добавляем кастомную логику подсчёта
return this.cartData()[name] || 0;
}
return original(name);
})
});
};
});
Конфигурация изображений
Размеры изображений задаются в etc/view.xml:
<vars module="Magento_Catalog">
<var name="product_image_white_borders">1</var>
</vars>
<images module="Magento_Catalog">
<image id="product_page_image_large" type="image">
<width>1200</width>
<height>1200</height>
</image>
<image id="category_page_grid" type="image">
<width>400</width>
<height>400</height>
<constrain>true</constrain>
</image>
<image id="product_thumbnail_image" type="thumbnail">
<width>80</width>
<height>80</height>
</image>
</images>
Деплой статики
# Деплой для конкретной темы и локали
bin/magento setup:static-content:deploy ru_RU en_US \
--theme MyCompany/mytheme \
--jobs=4 \
-f
# При разработке — симлинки вместо копирования
bin/magento dev:source-theme:deploy \
--locale ru_RU \
--theme MyCompany/mytheme \
css/styles-m css/styles-l
# Компиляция Less вручную (для быстрой итерации)
grunt watch
Hyva — альтернатива Luma
Hyva Themes — современный стек для тем Magento 2: Tailwind CSS + Alpine.js + серверный рендеринг через Magento layout. Вместо сложного RequireJS + KnockoutJS + Less — простой, быстрый и понятный стек.
Коммерческая лицензия ($1000/сайт). Выдаёт Lighthouse 90+ без дополнительной оптимизации. Разработка быстрее в 2–3 раза по сравнению с Luma.
Сроки
Кастомная тема на базе Luma/Blank по готовому дизайну (10–20 типов страниц): 5–8 недель. Hyva-тема с той же сложностью: 3–5 недель. Кастомизация существующей темы (несколько шаблонов, брендинг): 1–2 недели.







