Файлы js и css, а также прочие asset'ы – тонкости и нюансы работы с ними в Альто

Я не знаю, как коротко и однозначно перевести с английского слово «assets». Но те, кто работают со всякого рода фреймворками или занимаются версткой, как правило, сталкиваются с этим термином. Он обычно (в данном контексте) означает наборы файлов, которые используются на HTML-странице – это файлы стилей (css), скрипты (javascript), различные изображения и шрифты. Нередко термином assets называют только наборы css- и js-файлов.

Разумеется, эти «наборы» активно и в Альто используются. И в этой статье я расскажу о некоторых особенностях их обработки, которые присущи именно нашему движку. И это чрезвычайно будет полезно знать и тем, кто занимается версткой, и тем, кто будет писать плагины под движок, да и вообще всем, кто делает или собирается делать сайты на Alto CMS и хотел бы больше знать, как они устроены изнутри.
Общий подход при работе с css- и js-файлами
Основной подход – все css- и js-файлы собираются в папке /_run/assets/, там они (если это задано в настройках сайта), объединяются, и уже оттуда они подключаются в HTML-код.

При объединении файлов может использоваться сжатие (минификация) – удаление лишних пробелов, переводов строк, комментариев и т.д.

Например, задано подключение файлов:
$config['head']['default']['css'] = array(
    '___path.frontend.dir___/bootstrap-3/css/bootstrap.css',
    '___path.skin.dir___/assets/css/default.css',
    '___path.skin.dir___/themes/___view.theme___/style.css',
);

Предположим, мы работаем со скином Start-Kit и у него выбрана тема red. Тогда в папку /_run/assets/ будут скопированы следующие файлы:
  • /common/templates/frontend/ bootstrap-3/css/bootstrap.css
  • /common/templates/skin/ start-kit/ assets/css/default.css
  • /common/templates/skin/ start-kit/ themes/red/style.css
А в HTML-код они попадут примерно в таком виде:
<link rel='stylesheet' type='text/css' href='http://site.com/_run/assets/106be19c/bootstrap.css' />
<link rel='stylesheet' type='text/css' href='http:// site.com /_run/assets/7204ad63/default.css' />
<link rel='stylesheet' type='text/css' href='http:// site.com /_run/assets/0abc6294/style.css' />

Т.е. в общем случае принцип такой: css-, js- и прочие включаемые в HTML-код файлы не дергаются из сотни разных мест движка (шаблоны, плагины, библиотеки и т.д.), а должны быть аккуратно сложены в одну папочку /_run/assets/ и подключаться оттуда.

Важный нюанс: если у подключаемого файла указан не путь на диске, а веб-URL, то этот файл будет подключен в HTML-код без всякой обработки. Например, эти файлы будут подключены как есть:
$config['head']['default']['css'] = array(
    '___path.frontend.url___/bootstrap-3/css/bootstrap.css', // указан URL к библиотеке фронтенда
    '___path.skin.url___/assets/css/default.css', // указан URL к папке шаблона
    'http://cdn.google.com/style.css', // указан внешний URL
);


Объединение и сжатие
В настройках сайта задать опции для объединения и сжатия файлов. Это можно сделать либо в админке (Настройки сайта / CSS и javascript), либо добавив в конфиг-файл /app/config/config.local.php
$config['compress']['css']['merge'] = true; // css-файлы будут объединяться
$config['compress']['css']['use']   = false; // если задано объединение, то они будут и сжиматься

$config['compress']['js']['merge']  = true; // js-файлы будут объединяться
$config['compress']['js']['use']    = false;// если задано объединение, то они будут и сжиматься
Надо отметить, что сжатие js иногда приводит к появлению ошибок при исполнении js-кода, поэтому пользоваться этой опцией надо осторожно.

При подключении файлов могут задаваться и дополнительные опции.
$config['head']['default']['js'] = array(
    // файл обрабатывается с опциями по умолчанию
    '___path.frontend.dir___/libs/vendor/prettify/prettify.js',

    '___path.frontend.dir___/libs/vendor/jquery-1.10.2.min.js' => array(
        'name' => 'jquery', // указываем имя во избежании двойного подключения
        'asset' => 'mini',  // указывает на один набор при слиянии файлов
    ),
    '___path.frontend.dir___/libs/vendor/swfobject/plugin/swfupload.js' => array(
        'name'    => 'swfobject/plugin/swfupload.js',
        'prepare' => true, // файл только копируется в папку /_run/asset/, но не подключается
    ),
    '___path.frontend.dir___/libs/vendor/swfupload/swfupload.swf' => array(
        'name'     => 'swfupload/swfupload.swf', // можно указывать файлы любых типов
        'prepare'  => true,  // в HTML-не подключаем, просто копируем для дальнейшего использования
        'merge'    => false, // объединять не надо, даже если такая опция задана в конфиге
    ),
);
Параметр name, как сказано, используется во избежании двойного подключения. Если встречается несколько файлов пусть и с разными путями, но с одинаковым значением name, то подключен будет только первый из них. Но если у повторного файла с тем же параметром name указать еще и параметр 'replace' => true, то он заменит в наборе предыдущий файл с таким именем.
$config['head']['default']['js'] = array(
    'https://code.jquery.com/jquery-1.11.1.min.js' => array(
        'name' => 'jquery', // указываем имя
        'replace' => true,  // если уже есть файл с name=jquery, то он будет замещен этим
    ),
);

И у этого параметра (name) есть еще одно назначение — можно по нему получить URL к нужному файлу из javascipt'а на странице. Например:
<script>
$.ajax({
    url: ls.getAssetUrl('swfobject/plugin/swfupload.js'),
    dataType: 'script'
});
<script>
Здесь функция ls.getAssetUrl() вернет полный URL к загружаемому скрипту. Обратите внимание — в качестве аргумента используется не имя файла, и именно значение параметра name, как это было задано в коде выше.

Разработка и отладка
При разработке сайта (плагина, шаблона) есть необходимость сделать так, чтобы js- и/или css-файлы обновлялись при каждой загрузке страницы. Это задается в админке опцией принудительного обновления css- и js- (Настройки сайта / CSS и javascript) или указать это в конфиг-файле /app/config/config.local.php:
$config['compress']['css']['force']  = true;
$config['compress']['js']['force']  = true;


Есть и другой способ: в процессе разработки использовать URL (а не дисковый путь) к подключаемым файлам. Тогда они будут вставляться в HTML-код прямо с того места, где находятся, без всякой обработки. А когда разработка будет завершена, то вместо URL следует указать уже дисковый путь.

UPD Продолжение этой статьи: Работа с css- и js-файлами — дополнительные возможности для разработчиков шаблонов и плагинов

Похожие статьи


25 комментариев

0
Применительно к вебдеву это обычно называется ресурсами.

Кстати, если я изменю какой-либо файл, изменится ли путь к asset-у, в котором он состоит? Если да, то можно заставить браузер всегда брать файлы по этому пути из кэша — это сэкономит десятки запросов при каждом переходе по страницам:
<IfModule mod_expires.c>
ExpiresActive On
ExpiresDefault "access plus 3 months"
</IfModule>
<IfModule mod_headers.c>
Header set Cache-Control "max-age=7257600"
</IfModule>
0
Кстати, если я изменю какой-либо файл, изменится ли путь к asset-у, в котором он состоит?
Имя результирующего asset-файла строится на основе хеша имен файлов, которые входят в набор. Т.е. если изменится имя подключаемого файла, то изменится и имя asset'а. А если изменилось только содержимое, то ничего не меняется. Для этого и нужны опции принудительного обновления.
0
хеш от имени файлов + размеров + времени последнего обновления? или по скорости невыгодно, много раз ФС дергается?
0
Была такая мысль. Но тормознулся именно из-за большого числа дерганий. Тут, наверное, лучше по другому пути пойти — если уж и дергать ФС, то сравнивать даты исходных файлов и результирующих и так определять, изменилось ли содержимое набора. Но при этом обязательно предусмотреть опцию, которая будет отключать это сравнение (Смарти примерно так работает с компилированными шаблонами).

Тогда можно будет, с одной стороны, получить автообновление с опциами по умолчанию, а с другой — отключить лишние проверки на рабочем проекте, когда известно, что ничего не меняется
0
Добавить просто к собранным ассетам хэши от исходного содержимого и Большую Красную Кнопку «очистить кэш», которая также будет автоматически нажиматься при включении/выключении плагинов. От инвалидации кэша на лету вы потеряете весь выигрыш в производительности.
0
и Большую Красную Кнопку «очистить кэш», которая также будет автоматически нажиматься при включении/выключении плагинов
А вот обе эти фичи есть уже сегодня
0
Когда кэш включен, ФС вы дергать не сможете ни под каким видом — браузер будет брать уже собранные файлы. А когда он только собирается, торопиться особо некуда, и возможные проблемы от оптимизаций не стоят ускорения.
0
Еще возможное улучшение — заранее архивировать asset-ы в gzip, чтобы это не приходилось каждый раз делать веб-серверу.
0
Кстати, да, интересный совет. А на сервере для этого какие-то допнастройки нужны?
0
Да, понадобится что-то типа такого в .htaccess:
<FilesMatch "\.(css|js|html)$">
Header set Content-Encoding gzip
</FilesMatch>

Сделал архив с простым примером. У меня на хостинге он, правда, не завелся — для распаковки gzip браузерам (по крайней мере, chrome) требуется правильный Content-Length, а там фронтэндом стоит nginx, который его не указывает :( Так что по умолчанию эту оптимизацию включать нельзя.
+1
Я вам предлагаю узнать всю инфу по gzip и сделать предложение aVadim -у, каким образом это можно внедрить в Альто.
0
У меня, прямо скажу, в очереди доработок для Alto конкретно эта даже не в первой десятке, т.к. лично мне она не особо нужна — у меня сейчас вообще нет своих сайтов, а разгрузка gzip нужна только для высоконагруженных проектов. Да и в систему asset-ов я еще толком не вникал, не мне ее улучшать. Почему бы этим не заняться вам? :)
0
Ну я просто слышал про gzip, что это типа круто и быстро — но еще никогда с этим не работал. А тут речь зашла за это — поэтому и предложил. Если честно, мне пока с miniMarket-ом работы хватает :)
0
1.
Если встречается несколько файлов пусть и с разными путями, но с одинаковым значением name, то подключен будет только первый из них.
Может, более логичней было-бы подключать не первый, а последний встречающийся файл, если есть несколько с одинаковыми именами?

2.
При объединении файлов может использоваться сжатие (минификация) – удаление лишних пробелов, переводов строк, комментариев и т.д.
А можно более подробно о том, как должны быть оформлены комментарии, что бы в процессе сжатия они были вырезаны?

3. Можно еще в статье указать о возможности подключения js/css файлов из PHP кода, непосредственно в экшене.
Отредактирован:
+2
Может, более логичней было-бы подключать не первый, а последний встречающийся файл, если есть несколько с одинаковыми именами?
Основное предназначение — избежать дублей, поэтому и логика такая: если уже есть, то не трогаем. Плюс такой еще нюанс: как писал выше, имя результирующего asset-файла строится на основе хеша массива полных имен файлов набора. Поэтому, если есть возможность не менять состав набора — лучше не менять, чтоб не перестраивать результирующий ассет.

Но иногда замена может явно потребоваться. Для этого есть доп.параметр replace (забыл про него в статье написать, сейчас добавил). Если его указать, то предыдущий файл в наборе с таким именем будет заменен.

А можно более подробно о том, как должны быть оформлены комментарии, что бы в процессе сжатия они были вырезаны?
Врать не буду — не знаю, надо смотреть. Для минификации используются CSSTidy и JSMin, поэтому надо там копать. Кстати, возможно, сегодня это не лучшие библиотеки в своем классе, не исследовал, а просто взял, как есть, поэтому если кто-то подскажет варианты получше, то в последующих версиях движка можно будет их заменить.

3. Можно еще в статье указать о возможности подключения js/css файлов из PHP кода, непосредственно в экшене
Да, упустил этот момент. Я посмотрю, либо сюда добавлю, либо, если объем большой получится, отдельной статьей расскажу.
0
Вадим, можно посмотреть, как работают профессионалы на mun.ee/
ну или просто использовать эту библиотеку.
0
Они не профессионалы, и использовать эту библиотеку не надо.
0
а почему? есть какие-то реальные факты?
практический интерес, сам планировал поковырять эту разработку.
0
Сама идея этой библиотеки порочна: вместо того, чтобы сохранять ассеты в доступной по вебу папке, она требует на каждое подключение ассетов дергать её php-файл (+0.5мб расхода памяти на запрос МИНИМУМ). Этим она берет на себя функции веб-сервера, и выполняет их из рук вон плохо: не поддерживает докачку при обрыве, делает совершенно невозможным снижение числа запросов способом, предложенным мною выше, не дают серверу использовать кэширование файлов в памяти. Да ещё и gzip делает исключительно на лету :)

А программистов я считаю просто восторженной пионерией — они напихали в код модные молодёжные паттерны, написав в три раза больше кода, чем нужно, сделали наитупейшие архитектурные ошибки типа двухфазной инициализации, и при этом у них не предусмотрен элементарнейший вариант, что обрабатываемый скрипт/картинка/etc не лежит в готовом виде в файловой системе. Не завидую их работодателю.
Отредактирован:
0
Ах да, иллюстрация. Адов ужас, я считаю:
class HeaderSetter
{
    public function statusCode($protocol, $code, $message)
    {
        header("{$protocol} {$code} {$message}");
        return $this;
    }

    public function headerField($field, $value)
    {
        header("{$field}: {$value}");
        return $this;
    }
}
0
спс, значит не имеет смысла даже трогать

я такую поделку (заменим сервер, сожмем, отдадим) лет 10 назад велосипедил для своих ЦМСок
+1
Аналогично, только года на два попозже вас :) Не думаю, что найдется много стоящих веб-программистов, у которых хотя бы идеи такой не возникало.

Вообще, сейчас странные вещи в мире вебдева творятся — почему-то теперь даже для самых примитивных библиотек авторы считают нужным выделить отдельный сайт, создать фан-клуб и наладить выпуск футболок с символикой. Да и с кодом какая-то чертовщина — там, где можно обойтись одним файлом, они делают DI и десяток классов по два метода (и еще дюжину пустых классов, с именами, заканчивающимися на Exception) в отдельных файлах. Раньше тоже высокомерно относился к старикам, а теперь вот сам стал ужас как консервативен.
0
я думал это я один такой недовольный!
0
А нас много, недовольных. Я думаю, что в той или иной мере недовольно все поколение, начавшее программировать в 2003-2007 годах. Просто я когда-то прочитал статью Спольски «Огонь и движение» и примерил ее на себя, и теперь не считаю себя неправым только на том основании, что я в меньшинстве.
+1
Решил отдельной статьей выложить, т.к. «внезапно» оказалось, что много чего еще можно про это написать :)
altocms.ru/blog/inside/600.html
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.