На этой неделе, помимо прочих задач, я занимался клиентской оптимизацией на своем рабочем проекте. И узнал кое-что новенькое, чем и спешу с Вами поделиться.
Статья называется "Управление ресурсами в Symfony2". Поэтому хочу сразу оговориться, что я понимаю под словом "ресурс" (англ. asset). В данной статье под термином "ресурс" я подразумеваю HTTP ресурс, запрашиваемый браузером во время загрузки документа, т.е. это javascript файлы, css файлы, изображения и т.д.
Итак, приступим. В
официальной документации по Symfony2 можно прочитать, что включать ресурсы в шаблоны нужно так:
// css
<link href="{{ asset('/css/main.css') }}" type="text/css" />
// javascript
<script src="{{ asset('/js/main.js') }}" type="text/javascript"></script>
// images
<img src="{{ asset('/images/header.png') }}" alt="" />
Но для чего же нужна функция asset()? Почему не желательно указывать в атрибутах href и src относительный путь к файлу на сервере? Давайте разберемся.
Я взял для демонстрации несколько файлов из
Blueprint CSS Framework и подключил их в шаблоне таким образом:
<link href="{{ asset('/css/blueprint/reset.css') }}" type="text/css" media="screen, projection">
<link href="{{ asset('/css/blueprint/grid.css') }}" type="text/css" media="screen, projection">
<link href="{{ asset('/css/blueprint/forms.css') }}" type="text/css" media="screen, projection">
<link href="{{ asset('/css/blueprint/forms.css') }}" type="text/css" media="screen, projection">
<link href="{{ asset('/css/blueprint/typography.css') }}" type="text/css" media="print">
<!--[if lt IE 8]><link href="{{ asset('/css/blueprint/ie.css') }}" type="text/css" media="screen, projection"><![endif]-->
Если больше ничего не настраивать в конфиге, то Symfony отрендерит приведенный кусок шаблона в такой html:
<link href="/css/blueprint/reset.css" type="text/css" media="screen, projection">
<link href="/css/blueprint/grid.css" type="text/css" media="screen, projection">
<link href="/css/blueprint/forms.css" type="text/css" media="screen, projection">
<link href="/css/blueprint/forms.css" type="text/css" media="screen, projection">
<link href="/css/blueprint/typography.css" type="text/css" media="print">
<!--[if lt IE 8]><link href="/css/blueprint/ie.css" type="text/css" media="screen, projection"><![endif]-->
В принципе, этого достаточно. Но, Symfony2 может сделать для вас кое-какую дополнительную работу. Стоит всего лишь задать несколько опций в конфигурации проекта.
1. Множественные домены
Symfony2 позволяет задать список доменов, а точнее список базовых URI. Т.е. в конфигурации можно прописать адреса источников из которых доступна загрузка ресурсов для нашего проекта.
Так как у меня всего один сервер, да и тот локальный, я просто прописал несколько доменов в /etc/hosts, а затем добавил эти же алиасы в конфигурации виртуального хоста. Теперь в конфигурационном файле я могу задать базовые URI для загрузки ресурсов:
framework:
templating:
assets_base_urls:
- http://asset1.test.local
- http://asset2.test.local
- http://asset3.test.local
- http://asset4.test.local
- http://asset5.test.local
Тогда наш кусочек шаблона выведется как-то так:
<link href="http://asset2.test.local/css/blueprint/reset.css" type="text/css" media="screen, projection">
<link href="http://asset3.test.local/css/blueprint/grid.css" type="text/css" media="screen, projection">
<link href="http://asset1.test.local/css/blueprint/forms.css" type="text/css" media="screen, projection">
<link href="http://asset1.test.local/css/blueprint/forms.css" type="text/css" media="screen, projection">
<link href="http://asset5.test.local/css/blueprint/typography.css" type="text/css" media="print">
<!--[if lt IE 8]><link href="http://asset5.test.local/css/blueprint/ie.css" type="text/css" media="screen, projection"><![endif]-->
Т.е. Symfony добавит к каждому относительному пути указанному в функции asset() один из базовых URI из конфига. Я не разбирался с тем, в каком порядке будут подставлены URI из конфигурационного файла, могу лишь сказать, что последовательность всегда одна и та же. Т.е. если мы использовали один домен для загрузки ресурса, то этот ресурс для пользователя всегда будет загружаться с выбранного домена. В другом случае эта фича не имела бы смысла, т.к. все ресурсы кешируются браузером. И браузер понимает ресурс с другого домена как уникальный и скачивает его вместо того, чтобы использовать закешированный.
Таким образом используя конфигурационную опцию assets_base_urls можно разнести ресурсы на несколько серверов или использовать CDN. Но даже, если у вас всего один сервер, как в моем случае. То все равно имеет смысл сконфигурировать для вашего сервера дополнительные домены и использовать их как алиасы для загрузки ресурсов. И сейчас объясню зачем.
Дело в том, что браузеры имеют ограничения на количество открытых соединений к одному домену. В таблице ниже приведено количество возможных одновременных соединений с одним доменом для разных браузеров (информация не проверена).
Browser |
HTTP/1.1 |
HTTP/1.0 |
IE 6,7 |
2 |
4 |
IE 8 |
6 |
6 |
Firefox 2 |
2 |
8 |
Firefox 3 |
6 |
6 |
Safari 3,4 |
4 |
4 |
Chrome 1,2 |
6 |
? |
Chrome 3 |
4 |
4 |
Chrome 4+ |
6 |
? |
iPhone 2 |
4 |
? |
iPhone 3 |
6 |
? |
iPhone 4 |
4 |
? |
Opera 9.63,10.00alpha |
4 |
4 |
Opera 10.51+ |
8 |
? |
Таким образом, использование нескольких URI для загрузки ресурсов с одного физического сервера позволяет эти ограничения обойти, т.е. можно использовать большее количество соединений. Откуда следует, что ваши ресурсы будут загружаться в большее число потоков, а значит быстрее. Один дополнительный домен позволяет открывать в два раза больше соединений, если задано в конфиге пять доменов - можно использовать в пять раз больше соединений.
2. Сache busting
Как я уже говорил, все ресурсы кешируются на стороне клиента (т.е. браузером). Но что делать, если вы, скажем, обновили стили и пользователям нужно загрузить новую версию стилей, а старая версия находится в кеше. Решение в данном случае простое. В запрос нужно просто добавить какое-нибудь уникальное значение. URI ресурса в этом случае поменяется и браузер загрузит новую актуальную версию. И совсем не обязательно сообщать пользователям: "Мы там стили обновили. Почистите в Вашем браузере кеш, пожалуйста!" :)
В Symfony2 можно просто указать в конфиге версию ресурсов. Например, я добавил в свой конфиг такие строки:
framework:
templating:
assets_version: 1.0.0
и получил такой html:
<link href="http://asset2.test.local/css/blueprint/reset.css?1.0.0" type="text/css" media="screen, projection">
<link href="http://asset3.test.local/css/blueprint/grid.css?1.0.0" type="text/css" media="screen, projection">
<link href="http://asset1.test.local/css/blueprint/forms.css?1.0.0" type="text/css" media="screen, projection">
<link href="http://asset1.test.local/css/blueprint/forms.css?1.0.0" type="text/css" media="screen, projection">
<link href="http://asset5.test.local/css/blueprint/typography.css?1.0.0" type="text/css" media="print">
<!--[if lt IE 8]><link href="http://asset5.test.local/css/blueprint/ie.css?1.0.0" type="text/css" media="screen, projection"><![endif]-->
Можно обновлять версию автоматически при каждом деплойменте, чтобы не забыть.
Но и это еще не все. Предположим, что нашему серверу нужно сохранить старые версии ресурсов в другой директории, к примеру обновления интерфейса происходит только для части пользователей. И чтобы получить актуальную версию ресурсов, нам нужно передать серверу именованный параметр. Пуcть, он будет называться "v". По этому параметру мы и будем определять какая версия файлов нужна.
Чтобы реализовать такое поведение, снова отредактируем конфиг:
framework:
templating:
assets_version_format : %%s?v=%%s
И проверим, что же у нас получилось:
<link href="http://asset2.test.local/css/blueprint/reset.css?v=1.0.0" type="text/css" media="screen, projection">
<link href="http://asset3.test.local/css/blueprint/grid.css?v=1.0.0" type="text/css" media="screen, projection">
<link href="http://asset1.test.local/css/blueprint/forms.css?v=1.0.0" type="text/css" media="screen, projection">
<link href="http://asset1.test.local/css/blueprint/forms.css?v=1.0.0" type="text/css" media="screen, projection">
<link href="http://asset5.test.local/css/blueprint/typography.css?v=1.0.0" type="text/css" media="print">
<!--[if lt IE 8]><link href="http://asset5.test.local/css/blueprint/ie.css?v=1.0.0" type="text/css" media="screen, projection"><![endif]-->
Как вы поняли в параметре assets_version_format задается шаблон для sprintf(). Только знак процента нужно дважды указывать для экранирования.
На этом на сегодня все. И сделаем свои приложения лучше... с Symfony2!