Сборка статики

Витрина обычно тянет десятки мелких .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.


Преобразователь файлов

Бандл только склеивает. Если код нужно ещё и минифицировать, обфусцировать или как-то иначе преобразовать, для этого есть отдельный механизм — преобразователь файлов, Ctrl+B или кнопка на панели редактирования.

Работает он так: среда отправляет на сервер имя файла и текущее содержимое редактора — вместе с несохранёнными правками, — сервер вызывает по HTTP ваш скрипт-преобразователь, а то, что тот вернёт, целиком заменяет текст в редакторе. Сам результат никуда не сохраняется: посмотрите, что получилось, и сохраните файл обычным способом.

Настройка

Адрес скрипта задаётся в «Настройки редактора → Оптимизация → Преобразователь файлов»:

Это настройка среды разработки, а не проекта: она не лежит в config.json, и каждый разработчик задаёт её у себя.

Скрипт вызывает сервер, а не программа, — как и задачи планировщика. Поэтому в адресе стоит localhost: сервер обращается сам к себе, и преобразователь работает сразу, без настройки домена. Если вынести скрипт на отдельный хост, адрес должен быть доступен с веб-сервера магазина, а не с рабочей станции.

Что получает скрипт

Обычный POST с двумя полями:

Поле Что в нём
name имя файла — по расширению удобно решать, что с ним делать
content текущее содержимое редактора

Вернуть нужно готовый текст в теле ответа — без обёрток, заголовков и JSON. Ответ с кодом 400 и выше среда покажет как ошибку, а содержимое редактора не тронет.

Пример: minify.php

В демонстрационном магазине лежит готовый преобразователь — минификатор 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() (см. «Планировщик задач»).