Эффекты только через шаблонизатор - и почему это дисциплина, а не ограничение
Ключевой принцип этого сайта: инлайнить <script> и <link> сторонних библиотек прямо в разметку страницы запрещено. Не "не рекомендуется", а нельзя. Звучит как самоограничение - на деле это то, что держит ассеты под контролем.
Что ломается без единой точки
Как только каждая страница сама тащит свои <script>, происходит три вещи: одна и та же либа подключается дважды с разных страниц, порядок загрузки расходится, версии перестают совпадать. И самый тихий баг из всех: <script defer> в <head> исполняется после инлайновых скриптов в <body>. Инлайн-код, читающий глобал из defer-бандла, падает - без единой ошибки в консоли.
Три легальных канала
Affects::get_head_css_urls($domain)/get_head_js_urls($domain)- сторонние либы, привязанные к домену.- Скин - через тот же контракт
get_head_css_urls()/get_head_js_urls(), если ассет нужен всем страницам этого макета. Page()->add_head_css()/add_head_js()- точечно для одной страницы.
JS всегда описывается как {src, defer}, поэтому defer - централизованное решение, а не атрибут, забытый в одном из тегов:
final class Affects
{
/** @return array<int, string> */
public static function get_head_css_urls(string $domain): array
{
return match ($domain) {
'crm.sogerien.top' => ['/vendor/apexcharts/apexcharts.css'],
default => [],
};
}
/** @return array<int, array{src:string, defer:bool}> */
public static function get_head_js_urls(string $domain): array
{
return match ($domain) {
'crm.sogerien.top' => [
['src' => '/vendor/apexcharts/apexcharts.js', 'defer' => true],
],
default => [],
};
}
}Тяжёлый ассет не живёт в общем бандле. Апекс-чарты нужны одной странице - подключаем их на этой странице, а не в скине:
// Одна страница с графиком - подключаем точечно, не пихая либу в скин
// и не заставляя весь дашборд тянуть её на каждой вкладке:
Sogerien::Page()->add_head_js('/vendor/apexcharts/apexcharts.js', defer: true);
Sogerien::Page()->add_head_css('/page/dashboard/charts.css');Это то же правило для мегабайтных артефактов: .wasm, движок canvas, тяжёлый виджет грузятся постранично, defer, вне общего вендорного бандла. Иначе весь дашборд платит трафиком за фичу с одной вкладки.
Как не надо и как надо
Плохо - библиотека инлайном плюс инициализация, которая рассчитывает на уже готовый глобал:
<!-- ЗАПРЕЩЕНО: сторонняя библиотека инлайном в теле страницы -->
<link rel="stylesheet" href="/vendor/apexcharts/apexcharts.css">
<script src="/vendor/apexcharts/apexcharts.js"></script>
<script>
// window.ApexCharts тут может быть ещё не определён:
// defer-скрипт из <head> исполняется ПОСЛЕ инлайна в <body>
new ApexCharts(document.querySelector('#chart'), opts).render();
</script>Хорошо - загрузку либы отдаём движку, а свою инициализацию вешаем на DOMContentLoaded:
<?php
// Загрузку библиотеки отдаём движку - defer управляется централизованно:
Sogerien::Page()->add_head_js('/vendor/apexcharts/apexcharts.js', defer: true);
?>
<div id="chart"></div>
<script>
// Свою инициализацию вешаем на DOMContentLoaded:
// глобал из defer-бандла готов только к этому моменту
document.addEventListener('DOMContentLoaded', function () {
new ApexCharts(document.querySelector('#chart'), opts).render();
});
</script>Почему это дисциплина, а не клетка
Единая точка подключения даёт то, что вручную не собрать: детектор дублей ассетов, debug-оверлей с JS-ошибками прямо на экране (включается по Sogerien::$debag), контроль порядка вендорных скриптов и версионирование по mtime. Запрет на инлайн - это не про "нельзя", это про то, что порядок, дубли и версии кто-то наконец держит в одном месте.