Обновление веб-сайтов Symfony на AssetMapper
Исходная ситуация: Webpack Encore
Все сайты Symfony использовали Webpack Encore для управления своими ресурсами. Encore — это небольшой слой поверх Webpack, облегчающий его настройку и управление. Этот вариант хорошо нам подошёл, но у него есть несколько недостатков.
Во-первых, для этого необходимо установить, настроить и поддерживать нетривиальную настройку JavaScript с помощью таких инструментов, как Node.js, Babel, Webpack и т. д. Благодаря Webpack Encore многие из этих сложностей становятся прозрачными.
Однако при развёртывании приложения нам нужно было воспроизвести эту систему в продакшне. Мы используем Platform.sh для развёртывания наших сайтов, где сборка ресурсов с помощью Webpack Encore автоматизирована. Но необходимость собирать ресурсы при каждом развёртывании делает развёртывание медленнее (и неоправданно расходует ресурсы).
Второй важный недостаток Webpack Encore — необходимость сборки ресурсов перед их использованием (с помощью таких команд, как npx encore production
). При разработке приложения на локальной машине это неудобно, даже если вы можете запустить команду npx encore dev --watch
для автоматической пересборки ресурсов, если в них что-то изменилось.
После появления AssetMapper в Symfony 6.3 и улучшений AssetMapper в Symfony 6.4 мы решили попробовать AssetMapper. Обещание звучало почти магически: вы могли бы получить все положительные стороны Webpack Encore и ни одной отрицательной, и при этом всё это было бы гораздо проще.
Отучение
Прежде чем продолжить, необходимо кое-что усвоить. Многие веб-разработчики согласны с этим: лучшей практикой является использование бандлера (например, Webpack) для объединения и минимизации ресурсов перед их использованием
. Теперь это уже не так.
- Вам не нужен бандлер, чтобы делать грандиозные и сложные вещи в JavaScript, связанные с импортом. Теперь все браузеры поддерживают импорт нативно. Подробнее об этом Myth: JS imports need a Build System]
- Вам не нужно минифицировать содержимое веб-ресурсов (удалять белые пробелы, преобразовывать свойства CSS для их оптимизации и т. д.) Сжатие веб-ресурсов на вашем сервере перед их передачей даёт почти тот же результат. Подробнее об этом Stop Combining CSS & JS! + Performance Revisited
- Вам не нужно объединять ресурсы в один большой ресурс, чтобы уменьшить количество HTTP-запросов. При использовании HTTP/2 и HTTP/3 можно отправлять браузеру десятки файлов с ресурсами. Вы всё равно сможете получить 100/100 баллов за скорость. Подробнее об этом Stop Combining CSS & JS! + Performance Revisited
Теперь мы готовы к миграции на AssetMapper.
Миграция с Webpack Encore на AssetMapper
Реальный и полный пример переноса приложения с Webpack Encore на AssetMapper можно посмотреть в репозитории Symfony Demo в этом Pull Request: Upgrading to use AssetMapper 6.4 #1449.
Инсталляция
Сначала установите AssetMapper, как описано в документации по AssetMapper (для нас это было просто выполнение команды composer require symfony/asset-mapper
и проверка изменений, сделанных связанным рецептом).
Файл importmap.php
Вместо файла webpack.config.js
в AssetMapper используется файл importmap.php
в корневом каталоге проекта. В этом файле указывается, какие ресурсы используются в вашем приложении.
Вместо того чтобы создавать этот файл вручную, выполните команду importmap:require
и добавьте все необходимые ресурсы. В нашем случае мы взяли список ресурсов в файле package.json
и добавили зависимости одну за другой с помощью команды importmap:require
:
php bin/console importmap:require bootstrap
php bin/console importmap:require clipboard
php bin/console importmap:require tom-select
...
Помните, что вам не нужно устанавливать зависимости, которые использовались только Webpack Encore, а не вашим приложением (в нашем случае: @babel/core
, @babel/preset-env
, file-loader
, webpack
и т. д.).
Если вы откроете файл importmap.php
после установки ресурсов, вы увидите что-то вроде этого:
<?php
return [
// ...
'bootstrap/dist/css/bootstrap.min.css' => [
'version' => '5.3.2',
'type' => 'css',
],
'tom-select' => [
'version' => '2.3.1',
],
'clipboard' => [
'version' => '2.0.11',
],
// ...
Этот файл определяет только версии ресурсов. Чтобы загрузить ресурсы, необходимо выполнить команду importamp:install
, которая загрузит ресурсы в каталог <ваш проект>/assets/vendor/
. Это похоже на старый каталог node_modules/
, но если вы сравните оба каталога, то увидите, что новый каталог assets/vendor
содержит всего несколько файлов. Это происходит потому, что AssetMapper загружает полностью скомпилированные файлы CSS/JavaScript, а не все исходные файлы, необходимые для их создания.
Точки входа
В файле webpack.config.js
вы определили записи Webpack Encore, являющиеся конечными файлами ресурсов, которые будут сгенерированы при сборке ресурсов: в этом файле вы определите, какие файлы будут использоваться при сборке ресурсов:
Encore
// ...
.addEntry('app', './assets/app.js')
.addEntry('admin', './assets/admin.js')
.addEntry('schedule', './assets/conference-schedule.js')
;
В AssetMapper существует аналогичная концепция, называемая точками входа (entrypoints). При обновлении AssetMapper мы сопоставили записи с точками входа 1 к 1. Вам нужно сделать это вручную, отредактировав файл importmap.php
:
<?php
return [
'app' => [
'path' => './assets/app.js',
'entrypoint' => true,
],
'admin' => [
'path' => './assets/admin.js',
'entrypoint' => true,
],
'schedule' => [
'path' => './assets/conference-schedule.js',
'entrypoint' => true,
],
'bootstrap/dist/css/bootstrap.min.css' => [
'version' => '5.3.2',
'type' => 'css',
],
// ...
Мы поняли, что у нас, вероятно, слишком много точек входа. Это связано с тем, что в прошлом мы хотели загружать на каждую страницу минимальное количество CSS/JavaScript. Поэтому мы создавали точку вход для каждой значимой страницы и загружали только то, что ей требовалось. Сегодня, благодаря массовому сжатию веб-ресурсов, для обычных страниц этого делать не стоит. Поэтому в будущем мы удалим некоторые точки входа и оставив точки входам только для очень сложных страниц.
Изменения в файлах ресурсов
Ваши файлы в каталоге assets/
не требуют особых изменений, но может потребоваться внести некоторые коррективы. Теперь каждый раз, импортируя файлы, вы должны указывать расширение файла:
// assets/some-file.js
- import './bootstrap';
- import './code';
+ import './bootstrap.js';
+ import './code.js';
Ещё одно изменение мы внесли в способ загрузки Bootstrap CSS стилей. Раньше мы выбирали, какие части Bootstrap использовать:
// assets/styles/app.scss
@import "~bootstrap/scss/root";
@import "~bootstrap/scss/reboot";
@import "~bootstrap/scss/utilities";
@import "~bootstrap/scss/helpers/visually-hidden";
@import "~bootstrap/scss/images";
@import "~bootstrap/scss/breadcrumb";
// ...
Мы делали это, чтобы максимально оптимизировать ресурсы и включить только те части, которые будут использоваться. Теперь мы включаем все Bootstrap CSS стили:
// assets/app.js
import 'bootstrap/dist/css/bootstrap.min.css';
// ...
Это приемлемый компромисс, поскольку мы используем большинство Bootstrap CSS стилей, а также потому, что благодаря сжатию браузерам нужно загрузить всего 34 КБ, чтобы получить полный набор стилей Bootstrap.
Изменения в шаблонах
Наши шаблоны следовали очень типичной иерархии и загружали записи Webpack Encore следующим образом:
{# templates/base.html.twig #}
{% block stylesheets %}
{{ encore_entry_link_tags('app') }}
{% endblock %}
{% block javascripts %}
{{ encore_entry_script_tags('app') }}
{% endblock %}
{# templates/conference/schedule.html.twig #}
{% extends 'base.html.twig' %}
{% block stylesheets %}
{{ parent() }}
{{ encore_entry_link_tags('schedule') }}
{% endblock %}
{% block javascripts %}
{{ parent() }}
{{ encore_entry_script_tags('schedule') }}
{% endblock %}
Всем страницам нужны ресурсы app.css
и app.js
, поэтому мы загружаем запись app
в шаблон base.html.twig
. Затем, если какому-то шаблону нужны дополнительные ресурсы, они загружают другие записи Webpack Encore, используя наследование шаблонов Twig.
Теперь те же шаблоны выглядят так:
{# templates/base.html.twig #}
{% block importmap %}
{{ importmap('app') }}
{% endblock %}
{# templates/conference/schedule.html.twig #}
{% extends 'base.html.twig' %}
{% block importmap %}
{{ importmap(['app', 'schedule']) }}
{% endblock %}
Теперь всё выглядит гораздо проще, поскольку для получения файлов CSS и JavaScript достаточно импортировать в importmap()
одну точку входа. Однако есть и недостаток. Вы не можете использовать наследование шаблона для добавления новых точек входа на страницу. Вы можете вызвать importmap()
только один раз на странице. Таким образом, базовые точки входа, используемые всеми страницами (например, app
), должны повторяться во всех вызовах importmap()
шаблонов, добавляющих новые точки входа.
Saas и CSS
Мы использовали Sass во всех приложениях вместо чистого CSS. Sass хорошо служил нам все эти годы, но больше он нам не нужен. Единственными двумя функциями Sass, которые мы использовали, были:
- Переменные: больше не нужны благодаря пользовательским свойствам CSS;
- Вложенные селекторы: больше не нужны благодаря вложенности CSS.
На большинстве сайтов мы переработали Sass-файлы в чистые CSS-файлы. Учитывая, что вложенность CSS всё ещё поддерживается браузерами на 80 %, мы убрали вложенные селекторы и добавим их снова, когда поддержка станет более распространённой.
На другом сайте мы сохранили файлы .scss
, потому что переход на CSS был слишком трудоёмким. В том проекте мы установили symfonycasts/sass-bundle
, упрощающий использование Sass с компонентом AssetMapper от Symfony (он не требует использования Node.js).
Tailwind CSS
На некоторых сайтах вместо Bootstrap мы используем Tailwind CSS. К счастью, есть symfonycasts/tailwind-bundle
, который позаботится обо всем за вас. Этот бандл не требует Node.js и основан на бинарном файле, компилирующем Tailwind CSS стили.
Изменения в CI и развёртывании
Это была одна из самых простых частей процесса обновления. Нужно было обновить только команды, выполняемые для сборки/компиляции ресурсов:
// .github/workflows/tests.yaml
- name: Build and compile assets
run: |
- npm install
- npx encore production
+ php bin/console importmap:install
+ php bin/console sass:build
+ php bin/console asset-map:compile
Процесс развёртывания не потребовал никаких изменений. Благодаря тесной интеграции с Symfony, Platform.sh уже определяет, используете ли вы AssetMapper, SassBundle и/или TailwindBundle, и выполняет необходимые команды за вас.
Заключение
Мы очень довольны обновлением на AssetMapper. Это похоже на обман, потому что в итоге вы получаете те же результаты, что и при использовании Webpack Encore, но при этом затрачиваете лишь малую часть усилий.
Благодаря удалению таких файлов, как package.json
, Pull Requests привели к удалению множества строк кода:
live.symfony.com
: +981 строка добавлена, -9971 строка удалена.certification.symfony.com
: +718 добавлено, -10519 удалено.symfony.com
: +1105 добавлено, -10088 удалено.
И эта статистика не включает удаление каталога node_modules/
, что сэкономит вам кучу места на диске.
Ещё одно приятное улучшение — изменения в ресурсах теперь мгновенно передаются в браузер: внесите изменения, сохраните файлы, перезагрузите страницу. Больше никакого ожидания при создании ресурсов; за исключением случаев, когда вы используете Sass или Tailwind CSS, в этом случае вам нужно запустить sass:build --watch
или tailwind:build --watch
, чтобы пересобрать ресурсы при их изменении.
Наконец, AssetMapper сохраняет высокую производительность сайтов. Даже если теперь мы передаём браузерам гораздо больше CSS/JavaScript-ресурсов, на большинстве страниц производительность близка к 100/100.
AssetMapper — это настоящее и будущее управления ресурсами в приложениях Symfony. Если вы сможете убедить своего начальника/клиента выделить ресурсы, необходимые для перехода ваших собственных проектов на AssetMapper, не думайте дважды и переходите как можно скорее.