При разработке веб-приложений существует одна общеизвестная проблема. Мы, программисты, пишем новый javascript-код, стили в css, меняем статику… И статика эта как правило кешируется браузером пользователя и может оставаться в кеше на довольно долгое время (и это на самом деле правильно, ибо может ускорить загрузку страниц в разы).
Но что же делать, если мы поменяли статику? Как заставить пользователя сбросить кеш и обновить эти файлы? Существуют некоторые общепринятые способы, например, добавлять версионную метку к имени файла, или добавлять временную метку в GET-параметре при подключении файла.
В случае, если вы используете фреймворк Yii, вы также можете указывать версии или временные метки у файлов скриптов и стилей при подключении, однако за этим постоянно нужно следить, а в случае Yii еще и следить за отсутствием конфликтов (когда, допустим, виджет и вьюшка используют один и тот же скрипт, но с разными временными метками).
Собственно говоря, в Yii можно организовать более цивилизованный подход к этому делу.
Шаг 1. Подготовка файлов.
Создаем подпапку assets в папке protected вашего веб-приложения.
Помещаем в эту папку всю статику. Какие-то файлы, возможно, придется оставить в корне веб-приложения, если на них нужны постоянные внешние ссылки (например, фавиконка, логотип, какие-то документы).
Структура файлов может выглядеть примерно так:
/protected /assets /css /main.css /img /bg.png /sprite.png /extra_icons.png /js /main.js /form.js /etc.js
Шаг 2. Подготовка инструментов.
Скорее всего у вас уже есть переопределенный класс Controller, который вы используете в качестве базового для всех остальных контроллеров.
Мы его немного изменим, чтобы обеспечить работу с ассетами:
class Controller extends CController { private $_assetsBase; public function getAssetsBase() { if ($this->_assetsBase === null) { $this->_assetsBase = Yii::app()->assetManager->publish( Yii::getPathOfAlias('application.assets'), false, -1, YII_DEBUG ); } return $this->_assetsBase; } public $menu=array(); public $items=array(); public $breadcrumbs=array(); }
Все просто.
Здесь мы добавили геттер, который в первый раз публикует ассеты из папки /protected/assets и сохраняет путь в приватное свойство _assetsBase, а в последующие разы просто возвращает значение этого приватного свойства.
У метода publish() класса CAssetManager есть несколько интересных параметров.
Вот сигнатура метода:
publish(string $path, boolean $hashByName=false, integer $level=-1, boolean $forceCopy=false)
И краткое описание параметров:
$path — собственно путь до папки ассетов, которую мы будем публиковать;
$hashByName — определяет, публиковать ли имя папки как есть, или хешировать. Благодаря этому параметру и происходит вся магия, о которой будет рассказано дальше;
$level — определяет степень вложенности папок для публикации (-1 — все подпапки рекурсивно);
$forceCopy — определяет, копировать ли файлы принудительно, даже если они уже существуют. Устанавливаем этот параметр в YII_DEBUG, за счет чего на локальной машинке наши скрипты будут постоянно обновляться, а на продакшене — только в случае необходимости (на продакшене YII_DEBUG должен быть выставлен в false).
Шаг 3. Орудуем подготовленными инструментами над подготовленными файлами.
То самое свойство assetsBase, для которого мы написали геттер чуть выше — можем теперь использовать везде.
Сейчас во вьюшках и лейаутах можем писать что-то типо:
<link rel="stylesheet" type="text/css" href="<?=$this->assetsBase?>/css/main.css" />
или
<?Yii::app()->clientScript->registerScriptFile($this->assetsBase.'/js/utils.js')?>
И мы можем писать именно так, потому что $this в данном случае — это экземпляр класса Controller от которого наследуются все наши контроллеры и который передается во вьюшки и лейауты.
С виджетами дело обстоит немного по-другому, нужен свой подход, поскольку вьюшки виджета выполняются не в контексте контроллера, а в контексте виждета, поэтому для подключения Javascript файла во вьюшке виджета будем писать:
<?Yii::app()->clientScript->registerScriptFile(Yii::app()->controller->assetsBase.'/js/widget.js')?>
Шаг 4. Обновляем версию на продакшене и принуждаем браузер пользователя загружать обновленные файлы.
Это самый важный момент и именно его упускают многие разработчики из виду, в том числе и я когда-то.
Читая многие статьи по Yii может показаться, что для того, чтобы пользователь получил обновленные файлы статики — достаточно залить их в /protected/assets почистить папочку /assets в корне сайта (если конечно используется AssetManager) и вуаля — у пользователя все обновится само собой.
Но это совсем не так!
Автогенерируемые имена подпапок в папочке /assets в корне веб-приложения основываются на хеше имени папки /protected/assets и не меняются от раза к разу, даже если вы чистите папочку /assets в корне веб-приложения. Они восстанавливаются в прежнем виде. А значит браузер пользователя не «просекает», что что-то поменялось до тех пор, пока не истечет время кеширования.
По крайней мере так было до недавнего времени. И эта проблема обсуждалась в этом топике, после чего Александр Макаров нашел обходной путь и своим коммитом от 8 ноября 2011 года исправил ситуацию.
Теперь, второй параметр метода publish() класса CAssetManager — параметр $hashByName, установленный в false — вынуждает Yii строить имена подпапок в папке /assets в корне веб-приложения уже на основании хеша имени папки со статикой, а также даты модификации этой папки.
Благодаря этому мы можем сделать очень простой финт при деплое файлов статики приложения:
touch /path/to/your/website/protected/assets
выполняем эту команду в консоли и при следующем обращении к статике Yii сгенерирует новые имена ассетов!
А значит, пользователь, открывший страницу загрузит абсолютно новенькие свеженькие улучшенные скрипты, стили, картинки и т.п.
Если говорить кратко
Нужно просто:
- Всю статику вынести в папку /protected/assets
- Расширить базовый класс контроллера так, как описано выше
- Везде использовать assetsBase в качестве основы путей до статики
- При обновлении статики на сайте выполнить команду
touch /path/to/your/website/protected/assets
которая приведет к изменению даты модификации папки /protected/assets, а значит генерации нового хеша в папке /assets в корне сайта
- Выгода: вы можете быть уверены, что пользователи получают самую последнюю версию ваших скриптов, стилей и т.п.
Немного о другом
Кстати говоря, насколько мне известно, например, в Ruby on Rails данная проблема уже решена.
В Yii версии 2, которая находится в стадии разработки данная проблема также скорее всего будет решена.
В обсуждении решения данной проблемы для Yii 2 можно поучастовать на форуме (доступ открыт только для активных участников).
Также, еще одной интересной фишкой является автоматическая минификация скриптов и css. На данный момент она не реализована на уровне фреймворка, но реализована в некоторых расширениях, но это уже тема другой статьи…
Полезные ссылки по теме
- Документация к классу CAssetManager.
- Обсуждение данной темы на форуме Yii
- Обсуждение данной проблемы для новой версии Yii 2 (доступно для только активных пользователей)
- Расширения для минификации:
- Английская версия данной статьи
- Еще одна статья, описывающая приемы работы с CAssetManager
PS: Отмечу, что данная статья не является ни переводом, ни кросспостом, ибо английскую версию статьи писал также я для базы знаний Yii, но русская версия выполнена более качественно и понятно (в силу разного уровня знания языков).
Автор: dhampik