Как заставить CSS и JS обновляться автоматически при обновлении файлов

У нас на сервере включена полезная опция объединения всех JS и CSS файлов. Задается она в конфиге

/**
 * Параметры компрессии css-файлов
 */
$config['compress']['css']['merge'] = true;       // указывает на необходимость слияния файлов по указанным блокам.
$config['compress']['js']['merge']  = true; 


Однако при обновлении исходников JS и CSS файлов пользователи не получают обновления. Я видел есть плагин ReloadCSS однако он не занимается JS файлами, плюс тыкать кнопку мне тоже не охота совсем. В итоге я полез в код движка и нашел место, где формируется название скомпрессированных файлов

engine/modules/viewer/Viewer.class.php функция Compress
        /**
	 * Сжимает все переданные файлы в один,
	 * использует файловое кеширование
	 *
	 * @param  array  $aFiles	Список файлов
	 * @param  string $sType	Тип файла - js, css
	 * @return array
	 */
	protected function Compress($aFiles,$sType) {
		$sCacheDir  = $this->sCacheDir."/".Config::Get('view.skin');
		$sCacheName = $sCacheDir."/".md5(serialize($aFiles).'_head').".{$sType}";
		$sPathWeb    = Config::Get('path.root.web');
		/**
		 * Если кеш существует, то берем из кеша
		 */
		if(!file_exists($sCacheName)) {


Видно, что в формировании ключа участвуют только имена файлов. Я добавил туда еще и размеры, заодно проверил скорость формирования ключа. Вот такой код у меня получился:

	/**
	 * Сжимает все переданные файлы в один,
	 * использует файловое кеширование
	 *
	 * @param  array  $aFiles	Список файлов
	 * @param  string $sType	Тип файла - js, css
	 * @return array
	 */
	protected function Compress($aFiles,$sType) {
		$sPathWeb    = Config::Get('path.root.web');
		$aFileDates = array();
		$iStart = microtime(true);
		foreach ($aFiles as $sFile) {
			// если файл локальный
			if (strpos($sFile, $sPathWeb)!==false) {
				$sFile=$this->GetServerPath($sFile);
				list($sFile,)=explode('?',$sFile,2);
				$aFileDates[] = filectime($sFile);
			}
		}
		$sCacheDir  = $this->sCacheDir."/".Config::Get('view.skin');
		$sCacheName = $sCacheDir."/".md5(implode($aFiles).implode($aFileDates).'_head').".{$sType}";
		echo "Key calculation took ".(microtime(true) - $iStart)." secs\n";
		/**
		 * Если кеш существует, то берем из кеша
		 */
		if(!file_exists($sCacheName)) {


Проверка показала, что время пренебрежимо мало:

Key calculation took 0,0016648769378662 secs
Key calculation took 0,0010089874267578 secs


Убираем отладочный вывод и радумеся обновлениям JS и CSS.

Можно было бы это сделать плагином, но в плагине пришлось бы переопределить функцию Compress. Может, лучше это изменение просто включить в следующую версию LS.

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

avatar
Вместо этого нужно просто сбрасывать кеш, нечего на продакшене постоянно что-то менять.
avatar
А сброс кэша на продакшене не приводит к сбросу кэша в браузере. У меня браузер даже не обращается за этими файлами — сразу берет их своего браузерного кэша. Чтобы его принудить обноситься, самый тупой и дедовский метод — поменять имя файлов. Кто-то делает версию сss как параметр типа main.css?ver=1 кто еще как извращается.
avatar
У меня браузер даже не обращается за этими файлами — сразу берет их своего браузерного кэша.
Так своим кодом вы его не заставите взять. Но он вообще-то должен обращатся, как минимум за заголовками.
avatar
Нет не должен. Например если при первой отдаче css/js файла в респонс-хедере было вписано Expires / Cache-Control. Браузер в этом случае никаких реквестов генерить не будет, просто достанет файл из собственного кэша и пометит его «HTTP 304 Not Modified». Такой эффект возможен из-за настроек HTTP сервиса, что к LS на прямую не имеет отношения.
avatar
респонс-хедере было вписано Expires / Cache-Control
Вот именно, что ЛС их не устанавливает, поэтому следует искать причину в другом месте.
avatar
Ну, в частности, это можно сделать в .htaccess в корне установки LS, но это, совершенно точно, не установка LS «по умолчанию», да.
avatar
Пожалуй, теперь поставлю в NGINX чтобы отдавал что Expires +1 год. Хорошая оптимизация.
avatar
Для картинок — да. Очень хорошая. Must have. -30% трафика.

Для CSS/JS я бы не ставил — много ничего не выиграете, это все равно ничтожные копейки учитывая сжатие респонса.
avatar
При включенном last-modified и etag — браузер только пингует файлы, получает 304 ответ и успокаивается. Так что и трафика особо тоже нет.
avatar
Если меняется имя файла (а оно меняется почти 100% вероятностью, т.к. содержит md5 от имен и дат изменений файлов в массиве) то браузер совершенно точно загрузит новую версию.

У меня JS и CSS выдает nginx без каких-либо заголовков типа expires или no-cache. В итоге, браузер сам решает на сколько кэшировать. Вот что-то невнятное пишут в интернетах на этот счет http://stackoverflow.com/questions/9740822/how-long-does-google-chrome-cache-a-resource-if-expires-and-or-no-cache-headers

Можете зайти своим любимым браузером на http://www.7dach.ru и проверить, как часто браузер проверяет не обновились ли подключенные css/js.
avatar
Это в том случае если объединение в один файл включено, а если нет?
avatar
В моем случае это дев-сервер, и на нем можно задать
expires -1;
в конфиге nginx
avatar
не вы не поняли, если у LS НЕ включено объединение CSS в один файл, то будут сохранятся оригинальные имена CSS, а не «md5 от имен и дат изменений файлов в массиве». А, по умолчанию, оно как раз не включено, кажется.
avatar
Ну как оно по умолчанию, меня мало волнует. У меня дев сервер и продакшн. На деве не объединяются, так проще отлаживать. На продакшене объединяются. Пост мой был адресован тем, кто делает уже тонкую настройку, и хорошо понимает архитектуру кэширования в http.
avatar
И про опцию компрессии (про ее включение) — я написал в самом начале топика.
avatar
Совместил эту разработку, плагин ReloadCSS и получилось мегашикарно.

Сохраняю измененый css-файл в Coda переключаю активное окно и сразу вижу изменения! Восхитительно!
  • Rush
  • 0
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.