Pull to refresh

AssetManager: как форсировать получение пользователем обновленной статики

Reading time 5 min
Views 19K
При разработке веб-приложений существует одна общеизвестная проблема. Мы, программисты, пишем новый 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 сгенерирует новые имена ассетов!
А значит, пользователь, открывший страницу загрузит абсолютно новенькие свеженькие улучшенные скрипты, стили, картинки и т.п.

Если говорить кратко


Нужно просто:
  1. Всю статику вынести в папку /protected/assets
  2. Расширить базовый класс контроллера так, как описано выше
  3. Везде использовать assetsBase в качестве основы путей до статики
  4. При обновлении статики на сайте выполнить команду
    touch /path/to/your/website/protected/assets
    которая приведет к изменению даты модификации папки /protected/assets, а значит генерации нового хеша в папке /assets в корне сайта
  5. Выгода: вы можете быть уверены, что пользователи получают самую последнюю версию ваших скриптов, стилей и т.п.


Немного о другом


Кстати говоря, насколько мне известно, например, в Ruby on Rails данная проблема уже решена.
В Yii версии 2, которая находится в стадии разработки данная проблема также скорее всего будет решена.
В обсуждении решения данной проблемы для Yii 2 можно поучастовать на форуме (доступ открыт только для активных участников).

Также, еще одной интересной фишкой является автоматическая минификация скриптов и css. На данный момент она не реализована на уровне фреймворка, но реализована в некоторых расширениях, но это уже тема другой статьи…

Полезные ссылки по теме


  1. Документация к классу CAssetManager.
  2. Обсуждение данной темы на форуме Yii
  3. Обсуждение данной проблемы для новой версии Yii 2 (доступно для только активных пользователей)
  4. Расширения для минификации:
  5. Английская версия данной статьи
  6. Еще одна статья, описывающая приемы работы с CAssetManager


PS: Отмечу, что данная статья не является ни переводом, ни кросспостом, ибо английскую версию статьи писал также я для базы знаний Yii, но русская версия выполнена более качественно и понятно (в силу разного уровня знания языков).
Tags:
Hubs:
+35
Comments 15
Comments Comments 15

Articles