Файлы 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-файлами — дополнительные возможности для разработчиков шаблонов и плагинов

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

  • Работа с css- и js-файлами - дополнительные возможности для разработчиков шаблонов и плагинов
    В продолжении к статье Файлы js и css, а также прочие asset'ы – тонкости и нюансы работы с ними в Альто хочу рассказать о некоторых дополнительных возможностях по работе с css- и js-файлами, которые будут полезны ...
  • Как убрать assets
    Здравствуйте, вопрос в следующем Рисую шаблон, а конкретно — CSS-ки Убрал : $config['compress']['css']['merge'] = false; $config['compress']['js']['merge'] = false;...
  • Идея мобильного приложения для alto
    Всем привет, уже более полу года слежу за развитием altocms, даже есть собственный проект на ней. На мой взгляд, это самая оптимальная cms для создания блоговой платформы.
  • Новый функционал
    Нашел в комментариях разговор(2014 года) про расширение, написали прям в точку чего не хватает: Слишком размытый вопрос, чтоб на него можно было конкретно ответить. Возможности админа нужно расширять не для того,...

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
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.