almdev Технический блог

Три способа собрать фронт Symfony: AssetMapper, Encore, Vite
В соседних проектах три разные сборки. Разница не в моде, а в цене.

Сравнение AssetMapper с ImportMap, Webpack Encore и Vite на живых проектах: старт, скорость правки, размер выдачи, деплой и отладка. Что ломается при переходе с Encore.

Подкатегория: Сборка

Чтение
5 мин
Технологии / версии
AssetMapper · Vite · Webpack · ImportMap
9 апр 2026 · 5 мин · 1 просмотр · Frontend
Три разных сборочных механизма создают один фронтенд-модуль
Сборка 04/2026

У меня семь проектов на Symfony и три разных способа собрать фронтенд. Это не бардак, а результат того, что каждый выбирался под свою ситуацию. Раз в год я думаю привести всё к одному и каждый раз отказываюсь.

Расскажу, что каждый вариант реально стоит.

AssetMapper: без Node в ежедневном цикле

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

// importmap.php
return [
    'app' => ['path' => './assets/app.js', 'entrypoint' => true],
    '@hotwired/stimulus' => ['version' => '3.2.2'],
    '@hotwired/turbo' => ['version' => '8.0.4'],
    'sortablejs' => ['version' => '1.15.2'],
];

Команда importmap:require sortablejs скачивает пакет в assets/vendor/ и дописывает строку. Ни node_modules, ни package.json, ни установки зависимостей в контейнере.

Для проекта в Docker это важнее, чем кажется. В моём PHP-образе установка Node добавляла 180 мегабайт. Это не обязательная цена Encore или Vite: Node можно оставить в отдельном build stage или контейнере. Но с AssetMapper его нет даже в процессе сборки. При деплое остаётся одна команда asset-map:compile, которая проставляет хеши в имена и складывает файлы в публичный каталог.

Ограничения настоящие. Нет сборки в один файл: двадцать модулей — как минимум двадцать запросов. По HTTP/2 это терпимо, но на плохой сети заметно. Минификации из коробки нет; при необходимости её можно добавить отдельным SensioLabs Minify Bundle. Нет TypeScript, JSX и всего, что требует преобразования.

Правило, к которому я пришёл: AssetMapper подходит, пока фронтенд — это Stimulus-контроллеры и немного вспомогательных модулей. Как только появляется что-то, что нужно компилировать, вариант отпадает.

Encore: привычный бандл

Webpack Encore — обёртка над Webpack, которая делает конфигурацию терпимой. Ниже фрагмент цепочки настройки:

Encore
    .setOutputPath('public/build/')
    .setPublicPath('/build')
    .addEntry('app', './assets/app.js')
    .splitEntryChunks()
    .enableStimulusBridge('./assets/controllers.json')
    .enablePostCssLoader()
    .enableSourceMaps(!Encore.isProduction())
    .enableVersioning(Encore.isProduction());

Держу его в одном проекте — том, где фронтенд начали до появления AssetMapper и где есть Vue-компоненты для пары экранов.

Что у Encore хорошо: он умеет всё. Разделение на чанки, ленивая загрузка, обработка изображений, любые загрузчики. Экосистема Webpack закрывает вообще любую задачу.

Что плохо: пересборка после правки одной строки в CSS занимает 4–6 секунд на этом проекте. За рабочий день это набегает. Плюс каталог node_modules здесь занимает 400 мегабайт. В рабочем образе его держать не обязательно, но устанавливать зависимости на этапе сборки и обновлять мажорные версии всё равно приходится.

Я бы не начинал новый проект на Encore. Не потому что он плохой, а потому что медленный на цикле правки.

Vite: быстрый цикл ценой второго сервера

Vite в разработке не собирает бандл вовсе — отдаёт модули напрямую и подменяет изменённые на лету. На этом проекте правка в CSS видна за 40–80 мс без перезагрузки страницы.

Минимальный фрагмент конфигурации самого Vite выглядит так; подключение manifest и dev-сервера к Twig зависит от выбранного Symfony-пакета и здесь опущено:

import { defineConfig } from 'vite';

export default defineConfig({
    build: {
        rollupOptions: {
            input: { app: './assets/app.js' },
        },
    },
});

Разница с Encore в скорости не в разы, а на порядок. Работать с интерфейсом становится приятно, и это не вкусовщина: когда цикл обратной связи меньше секунды, вёрстку крутишь до состояния «хорошо», а не до «сойдёт».

Цена — второй процесс в разработке. Приложение на одном адресе, сервер разработки на другом, и они должны договориться. В Docker это ещё и вопрос сети: сервер Vite должен слушать не только внутри контейнера, а браузер — достучаться до него по правильному имени.

Для моего прокси фрагмент блока server выглядит так:

server: {
    host: '0.0.0.0',
    port: 5173,
    hmr: { host: 'app.test', protocol: 'wss', clientPort: 443 },
}

Этот блок — самое частое место, где всё ломается. За HTTPS-прокси горячая замена не работает, пока не укажешь протокол, внешний хост и порт, который видит браузер. Конкретные значения зависят от прокси.

Сравнение по пяти критериям

Это мои замеры на трёх существующих проектах, а не универсальный benchmark: конфигурации и объём ассетов у них различаются.

AssetMapperEncoreVite
Старт проектаминуты, ничего не ставимчас с зависимостямиполчаса
Цикл правкимгновенно, обновление страницы4–6 с40–80 мс, без перезагрузки
Размер выдачибольше, без минификациинаименьшийблизко к Encore
Деплойодна команда, Node не нуженсборка с Nodeсборка с Node
Отладкаисходные модулиsource mapsпреобразованные модули и source maps

Строка про отладку недооценена. В AssetMapper браузер получает исходные модули почти без преобразований. Vite преобразует импорты и зависимости, но в разработке сохраняет связь с исходниками, поэтому точки останова ставятся в знакомом файле. Encore решает ту же задачу через source maps; будут ли они доступны в продакшне, зависит от конфигурации сборки.

Что ломается при переходе с Encore

Переносил один проект, ушло два вечера. Список неожиданностей.

Именованные импорты из CommonJS-пакетов. Webpack умеет разбирать такое, браузер — нет. Пакеты без сборки в формате модулей приходится менять или подключать целиком.

Импорт CSS из JS. import './styles/app.css' в Encore работает, в AssetMapper — нет. Стили подключаются отдельно, и это правильнее, но переписывать надо руками.

Разделение на чанки. Его просто нет. Один большой набор модулей, каждый грузится своим запросом. Если проект рассчитывал на ленивую загрузку тяжёлого редактора — придётся делать динамический импорт вручную.

Мост Stimulus. controllers.json в Encore автоматически регистрирует контроллеры из пакетов. В AssetMapper регистрация ручная, и это надо не забыть.

Обратный переход, с AssetMapper на Vite, гораздо проще: точка входа та же, импорты те же, добавляется конфиг и тег в шаблоне.

Сборка в Makefile

Независимо от выбора, имя цели в Makefile одно и то же, а команда внутри различается. Ниже три альтернативных фрагмента — в конкретном проекте остаётся один из них:

# AssetMapper
build-assets: ## Собрать фронтенд
	docker compose exec -T webserver php bin/console asset-map:compile

# Encore
build-assets: ## Собрать фронтенд
	docker compose run --rm node npm run build

# Vite
build-assets: ## Собрать фронтенд
	docker compose run --rm node npm run build

У Encore и Vite команда снаружи совпадает, но запускает разные конфигурации. Разработчик, переходящий между проектами, набирает make build-assets и не думает, что там внутри. Разные инструменты перестают быть проблемой, когда интерфейс к ним одинаковый.

Что бы посоветовал

Начинать с AssetMapper. Это не компромисс и не вариант для бедных — для серверного приложения с Turbo и Stimulus его достаточно, а отсутствие Node в контейнере экономит больше времени, чем даёт любая оптимизация бандла.

Переходить на Vite в тот момент, когда впервые захотелось скомпилировать что-то нетривиальное. Переход занимает вечер и не требует переписывать код.

И не смешивать без плана перехода. Такой проект приходится обслуживать с двумя manifest, двумя наборами правил деплоя и двумя способами подключать зависимости. Как временный этап миграции это терпимо; как постоянная архитектура — лишняя цена.

#assetmapper #vite #webpack #importmap