Руководство Melbis Shop: оглавление
Витрина обычно тянет десятки мелких .js и
.css: библиотеки оформления, стили, скрипты отдельных
модулей. Каждый файл — отдельный запрос к серверу. Бандл склеивает их в
один файл, и происходит это прямо при сохранении в среде разработки —
без внешних сборщиков, конфигов и отдельной команды сборки.
Откройте .js или .css в среде разработки.
Над редактором две строки: синяя — описание файла, серая — параметры. В
параметрах указывается, в какие бандлы попадёт этот файл:
base.js: 4
Слева от двоеточия — имя бандла, справа — приоритет. Один файл может входить в несколько бандлов, они перечисляются через запятую:
base.js: 4, print.css: 2
Приоритет необязателен: без него файл получает 0. Имя
бандла становится именем итогового файла, поэтому расширение указывается
прямо в нём — base.js, melbis.css,
melbis.auth.js. В имени допустимы латинские буквы, цифры,
точка и подчёркивание; остальные символы вырезаются — в частности дефис,
так что melbis-auth.js превратится в
melbisauth.js.
В бандл попадает любой .js или .css внутри
группы шаблонов — и из общего каталога statics/, и
из каталогов отдельных модулей:
templates/default/
statics/
base/bootstrap.js base.js: 2
base/bootbox.js base.js: 4
melbis/main.js melbis.js: 1
units/
melbis_cataloge/scripts.js melbis.js: 10
melbis_base_page/scripts.js melbis.js: 15
В этом и смысл: модуль держит свой скрипт рядом со своими шаблонами, где его удобно править и искать, а на витрину код приезжает в общем файле, не добавляя запросов.
Собранный файл кладётся в statics/ той же группы
шаблонов, с префиксом bundle.:
templates/default/statics/bundle.melbis.js
Пересборка запускается при каждом сохранении файла, у которого заполнена строка бандлов, и пересобирает все бандлы группы целиком.
В начало файла дописывается отчёт о составе:
/* Melbis Shop auto bundle report */
/* Create: 2026-07-17 15:39:56 */
/* #1 main.js 32 ln 1 kb /templates/default/statics/melbis/main.js */
/* #10 scripts.js 57 ln 1 kb /templates/default/units/melbis_cataloge/scripts.js */
/* #15 scripts.js 77 ln 3 kb /templates/default/units/melbis_base_page/scripts.js */
По нему видно, что и в каком порядке вошло, — это первое, куда стоит заглянуть, когда непонятно, чей код отработал раньше.
Сборка — это только склейка. Минификации, транспиляции и переписывания путей внутри CSS не происходит: файлы соединяются в порядке приоритета как есть. Если нужен минифицированный вендорный код, кладите в бандл уже минифицированную версию файла.
Файлы сортируются по возрастанию приоритета. Сами числа условные — значение имеют только их отношения, поэтому удобно оставлять зазоры, чтобы потом вставлять файлы между уже существующими.
Приоритетами удобно разделять логические группы. Например, в
демонстрационном магазине до 10 идут базовые файлы из
statics/, после 10 — скрипты модулей: так библиотеки
заведомо загрузятся раньше кода, который на них опирается. Это не
правило платформы, а просто способ навести порядок.
Не оставляйте одинаковый приоритет файлам, порядок которых важен. Сортировка идёт только по числу, а файлы с равными значениями встают в том порядке, в каком движок обошёл каталог метаданных, — предсказать его нельзя.
<link rel="stylesheet" type="text/css" href="{PATH}/statics/bundle.base.css">
<link rel="stylesheet" type="text/css" href="{PATH}/statics/bundle.melbis.css?{BUILD}">
<script defer src="{PATH}/statics/bundle.base.js"></script>
<script defer src="{PATH}/statics/bundle.melbis.js?{BUILD}"></script>{BUILD} — номер сборки проекта, системный тег, доступный
в любом шаблоне. Он работает меткой версии: пока номер не изменился,
браузер берёт файл из своего кеша, а как только номер вырос — скачивает
заново.
Меняется он двумя способами: вручную — параметром
MELBIS_BUILD в диалоге «Проектирование →
Инсталляция» (см. «Конфигурация»), либо автоматически — кнопкой
в среде разработки, которая включает инкремент MELBIS_BUILD
при каждом сохранении файла.
Ставить ?{BUILD} можно у любого бандла. Вендорные
библиотеки меняются редко, поэтому их часто подключают без метки, — но
это дело вкуса, а не требование.
Чтобы убрать файл из бандла, сотрите имя бандла из строки параметров и сохраните файл: при ближайшей сборке он из бандла исчезнет.
Если из бандла ушёл последний файл, сам бандл остаётся на диске. Собирать его больше не из чего, поэтому движок его просто не трогает — прежний
statics/bundle.имяпродолжает лежать и отдаваться витрине. То же самое при переименовании бандла: появится файл с новым именем, а старый останется. Такие файлы удаляются вручную.
Удалять и переименовывать саму статику лучше в дереве среды
разработки: тогда метаданные о бандлах переносятся или снимаются вместе
с файлом. Если удалить файл мимо IDE — например, по FTP, — метаданные о
нём останутся, и очередная сборка сообщит об ошибке
Can't include file to bundle.
.psvCSS не умеет считать. Цвет наведения на кнопку — это основной цвет,
затемнённый на 7.5%; фон уведомления — он же, разбавленный белым на 80%.
Готовые фреймворки считают такое при своей сборке и приезжают к нам с
уже посчитанными значениями: в bootstrap.css один основной
цвет разложен в полтора десятка литералов и встречается в 77 местах.
Перекрасить магазин означает найти их все.
Чтобы этого не делать, в бандл кладётся файл
.psv — php style vars. Это
обычный PHP-файл, который возвращает массив ключей; остальные файлы
бандла подставляют эти ключи по имени.
base/theme.psv:
<?php
$primary = '#007bff';
return [
'PRIMARY' => $primary,
'PRIMARY_HOVER' => '#0069d9'
];
?>base/buttons.css:
.btn-primary { background-color: {PRIMARY}; border-color: {PRIMARY}; }
.btn-primary:hover { background-color: {PRIMARY_HOVER}; }Файл .psv создаётся так же, как .css и
.js — в дереве среды разработки, а .psv
выбирается в списке расширений. Редактор подсвечивает его как PHP, и
строка бандлов у него такая же:
base.css: 0
Ключи пишутся заглавными. В шаблоне подставляется
всё, что подходит под {ИМЯ}: заглавные латинские буквы,
цифры и подчёркивание. Именно регистр и делает запись безопасной среди
фигурных скобок CSS: ни {color:red}, ни
@media (min-width: 576px) { под неё не попадают.
Ключи действуют на весь бандл, а не на файл. Один
.psv с малым приоритетом задаёт палитру, все файлы после
него ею пользуются. Поэтому вендорный bootstrap.css
остаётся общим для всех магазинов, а свой у каждого — только
theme.psv.
Приоритет решает, кто победит. Файлы
.psv подключаются в порядке приоритета, и одинаковый ключ,
встреченный дважды, берёт значение из последнего. На этом строится
переопределение: базовая тема с приоритетом 0, магазинная с приоритетом
1, и во второй перечислено только то, что отличается.
Сам .psv в собранный файл не попадает.
Он не CSS — он только приносит ключи. В отчёте о составе бандла он
виден, в теле — нет.
Ключ, которого нет, остаётся как написан. Сборка не прерывается, а среда разработки после сохранения показывает предупреждение:
Warning! Unknown: PRIMARY_HOWER (buttons.css);
Обычно это опечатка в имени. Если в бандле нет ни одного
.psv, подстановка не запускается вовсе — бандл из чистого
JS не будет ругаться на свои {...}.
Подстановка живёт только в бандле. Файл, подключённый в шаблон напрямую, минуя бандл, уедет в браузер с сырыми
{PRIMARY}.
PhpStyleVarСчитать оттенки руками не нужно: в движке для этого есть класс. Файл
.psv подключается движком, а подключённый файл не
наследует пространство имён того, кто его подключил, поэтому в
начале указывается, что он берёт:
<?php
use Melbis\MelbisShop\PhpStyleVar as PSV;
$primary = '#007bff';
return [
'PRIMARY' => $primary,
'PRIMARY_HOVER' => PSV::darken($primary, 7.5),
'PRIMARY_BG' => PSV::mix('#fff', $primary, 80),
'PRIMARY_RGB' => PSV::rgb($primary)
];
?>Цвет передаётся так, как он пишется в CSS — #007bff или
#fff, — и возвращается так же.
| Метод | Что делает |
|---|---|
darken($hex, $percent) |
темнее на столько-то процентов |
lighten($hex, $percent) |
светлее |
mix($front, $back, $percent) |
столько-то процентов первого цвета поверх второго |
saturate($hex, $percent) |
насыщеннее |
desaturate($hex, $percent) |
ближе к серому |
hue($hex, $degrees) |
поворот тона по цветовому кругу |
yiq($hex, $dark, $light) |
который из двух текстов читается на этом фоне |
rgb($hex) |
0, 123, 255 — числа для rgba() |
rgba($hex, $alpha) |
целиком rgba(0, 123, 255, 0.25) |
url($hex) |
%23007bff — цвет внутри картинки, нарисованной в самом
CSS |
rem($pixels) |
пиксели в rem |
stack(['Inter', 'Segoe UI']) |
список шрифтов; кавычит только имена с пробелом |
svg($markup) |
разметку картинки в готовый
url("data:image/svg+xml,...") |
Считает класс так же, как это делает SASS, — вплоть до округления половины, поэтому фреймворк, собранный где-то там, сохраняет здесь свои собственные оттенки.
Три метода нужны там, где цвет попадает в CSS не сам по себе:
.shadow { box-shadow: 0 0 0 0.2rem rgba({PRIMARY_RGB}, 0.25); }
.select { background-image: url("data:image/svg+xml,%3csvg%3e%3cpath fill='{DARK_URL}'/%3e%3c/svg%3e"); }Массив собирается обычным PHP, поэтому однотипные оттенки удобнее не перечислять, а посчитать:
$base = [
'PRIMARY' => '#007bff',
'SUCCESS' => '#28a745',
'DANGER' => '#dc3545'
];
$vars = $base;
foreach ( $base as $name => $color )
{
$vars[$name.'_HOVER'] = PSV::darken($color, 7.5);
$vars[$name.'_BG'] = PSV::mix('#fff', $color, 80);
$vars[$name.'_RGB'] = PSV::rgb($color);
}
return $vars;Так список цветов вверху файла остаётся единственным местом, куда потом заглядывают, а число ключей роли не играет: лишние никому не мешают, а нужный всегда оказывается на месте.
Файл обязан вернуть массив. Если он вернёт что-то другое, сборка остановится:
second.psv gives no array of keys back
Ошибка в самом PHP — опечатка, несуществующий метод — тоже останавливает сборку и называет настоящий файл и настоящую строку:
File: .../templates/default/statics/base/theme.psv : 105
Uncaught Error: Call to undefined method Melbis\MelbisShop\PhpStyleVar::url()
В обоих случаях прежний собранный бандл остаётся нетронутым: витрина продолжает отдавать последнюю удачную сборку, пока файл не поправят.
Бандл только склеивает. Если код нужно ещё и минифицировать,
обфусцировать или как-то иначе преобразовать, для этого есть отдельный
механизм — преобразователь файлов, Ctrl+B
или кнопка на панели редактирования.
Работает он так: среда отправляет на сервер имя файла и текущее содержимое редактора — вместе с несохранёнными правками, — сервер вызывает по HTTP ваш скрипт-преобразователь, а то, что тот вернёт, целиком заменяет текст в редакторе. Сам результат никуда не сохраняется: посмотрите, что получилось, и сохраните файл обычным способом.
Адрес скрипта задаётся в «Настройки редактора → Оптимизация → Преобразователь файлов»:
http://localhost/minify.php.Ctrl+B случайно
легко.Это настройка среды разработки, а не проекта: она не лежит в
config.json, и каждый разработчик задаёт её у себя.
Скрипт вызывает сервер, а не программа, — как и задачи планировщика. Поэтому в адресе стоит
localhost: сервер обращается сам к себе, и преобразователь работает сразу, без настройки домена. Если вынести скрипт на отдельный хост, адрес должен быть доступен с веб-сервера магазина, а не с рабочей станции.
Обычный POST с двумя полями:
| Поле | Что в нём |
|---|---|
name |
имя файла — по расширению удобно решать, что с ним делать |
content |
текущее содержимое редактора |
Вернуть нужно готовый текст в теле ответа — без обёрток, заголовков и
JSON. Ответ с кодом 400 и выше среда покажет как ошибку, а
содержимое редактора не тронет.
В демонстрационном магазине лежит готовый преобразователь — минификатор JS и CSS. Это обычный PHP-файл в корне проекта, движок в нём не участвует.
Начинается он с проверки, что запрос пришёл с самого сервера:
// Преобразователь вызывает движок, поэтому пускаем только локальные запросы
$ip = $_SERVER['REMOTE_ADDR'] ?? '';
if ( $ip !== '127.0.0.1' && $ip !== '::1' )
{
header('HTTP/1.1 403 Forbidden');
die('Only local allowed');
}Дальше — собственно преобразование:
switch ( pathinfo($_POST['name'], PATHINFO_EXTENSION) )
{
case 'js':
$data = array('input' => $_POST['content']);
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, 'https://www.toptal.com/developers/javascript-minifier/api/raw');
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($data));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 60);
echo curl_exec($ch);
curl_close($ch);
break;
case 'css':
// То же самое, но обращение к минификатору CSS
break;
default:
echo $_POST['content'];
}Разбор по расширению — не требование платформы, а удобное соглашение:
один адрес обслуживает все типы файлов. Ветка default
возвращает содержимое как есть, поэтому Ctrl+B на файле
неизвестного типа ничего не испортит; в собственном преобразователе это
правило стоит сохранить.
Внутри может быть что угодно: минификация, обфускация, автоформатирование, прогон через сторонний сервис или локальную утилиту. Платформа не знает и не проверяет, что именно происходит, — ей важен только текст в ответе.
Проверку на локальный вызов не выбрасывайте. Преобразователь лежит в корне сайта, принимает произвольный текст и выполняется на вашем сервере — без неё запустить его сможет кто угодно из интернета. Тот же приём используют cron-модули, там для этого есть готовый метод
CronLocalOnly()(см. «Планировщик задач»).