Как заставить браузер перезагружать кэшированные файлы CSS / JS? - PullRequest
933 голосов
/ 23 сентября 2008

Я заметил, что некоторые браузеры (в частности, Firefox и Opera) очень усердно используют кэшированные копии файлов .css и .js , даже между сеансами браузера. Это приводит к проблеме при обновлении одного из этих файлов, но браузер пользователя продолжает использовать кэшированную копию.

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

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

Обновление:

После некоторого обсуждения здесь я нашел предложение Джона Милликина и da5id полезным. Оказывается, есть термин для этого: автоматическое управление версиями .

Я разместил новый ответ ниже, который представляет собой сочетание моего первоначального решения и предложения Джона.

Другая идея, предложенная SCdF , заключается в добавлении в файл фиктивной строки запроса. (Некоторый код Python для автоматического использования метки времени в качестве фиктивной строки запроса был передан pi .). Тем не менее, существует некоторое обсуждение относительно того, будет ли браузер кэшировать файл со строкой запроса. (Помните, мы хотим, чтобы браузер кэшировал файл и использовал его при будущих посещениях. Мы хотим, чтобы он снова извлекал файл только после его изменения.)

Поскольку неясно, что происходит с фиктивной строкой запроса, я не принимаю этот ответ.

Ответы [ 48 ]

438 голосов
/ 23 сентября 2008

Обновление: Переписано с учетом предложений Джона Милликина и da5id . Это решение написано на PHP, но должно быть легко адаптировано к другим языкам.

Обновление 2: Включение комментариев Ника Джонсона о том, что оригинальное регулярное выражение .htaccess может вызвать проблемы с файлами, такими как json-1.3.js. Решение состоит в том, чтобы переписать, только если в конце ровно 10 цифр. (Поскольку 10 цифр охватывают все метки времени с 9.09.2001 по 20.11.22.)

Сначала мы используем следующее правило перезаписи в .htaccess:

RewriteEngine on
RewriteRule ^(.*)\.[\d]{10}\.(css|js)$ $1.$2 [L]

Теперь мы напишем следующую функцию PHP:

/**
 *  Given a file, i.e. /css/base.css, replaces it with a string containing the
 *  file's mtime, i.e. /css/base.1221534296.css.
 *  
 *  @param $file  The file to be loaded.  Must be an absolute path (i.e.
 *                starting with slash).
 */
function auto_version($file)
{
  if(strpos($file, '/') !== 0 || !file_exists($_SERVER['DOCUMENT_ROOT'] . $file))
    return $file;

  $mtime = filemtime($_SERVER['DOCUMENT_ROOT'] . $file);
  return preg_replace('{\\.([^./]+)$}', ".$mtime.\$1", $file);
}

Теперь, куда бы вы ни включили свой CSS, измените его следующим образом:

<link rel="stylesheet" href="/css/base.css" type="text/css" />

На это:

<link rel="stylesheet" href="<?php echo auto_version('/css/base.css'); ?>" type="text/css" />

Таким образом, вам больше никогда не придется изменять тег ссылки, и пользователь всегда будет видеть последнюю версию CSS. Браузер сможет кэшировать файл CSS, но когда вы внесете какие-либо изменения в свой CSS, браузер увидит его как новый URL, поэтому он не будет использовать кэшированную копию.

Это также может работать с изображениями, значками и JavaScript. В основном все, что не генерируется динамически.

179 голосов
/ 23 сентября 2008

Простая клиентская техника

В целом, кэширование - это хорошо. Поэтому есть несколько методов, в зависимости от того, решаете ли вы проблему для себя при разработке веб-сайта или пытаетесь контролировать кэш в производственной среде.

Обычные посетители вашего сайта не будут иметь того опыта, который вы испытываете при разработке сайта. Так как среднестатистический посетитель посещает сайт реже (может быть, только несколько раз в месяц, если вы не являетесь сетью Google или hi5), тогда он с меньшей вероятностью будет хранить ваши файлы в кэше, и этого может быть достаточно. Если вы хотите принудительно ввести новую версию в браузер, вы всегда можете добавить строку запроса к запросу и увеличить номер версии при внесении серьезных изменений:

<script src="/myJavascript.js?version=4"></script>

Это гарантирует, что каждый получит новый файл. Это работает, потому что браузер просматривает URL-адрес файла, чтобы определить, есть ли у него копия в кэше. Если ваш сервер не настроен на выполнение каких-либо действий со строкой запроса, он будет игнорироваться, но имя будет выглядеть как новый файл для браузера.

С другой стороны, если вы разрабатываете веб-сайт, вам не нужно менять номер версии каждый раз, когда вы сохраняете изменения в своей версии для разработки. Это было бы утомительно.

Поэтому, когда вы разрабатываете свой сайт, хорошим трюком будет автоматическое создание параметра строки запроса:

<!-- Development version: -->
<script>document.write('<script src="/myJavascript.js?dev=' + Math.floor(Math.random() * 100) + '"\><\/script>');</script>

Добавление строки запроса в запрос является хорошим способом создания версии ресурса, но для простого веб-сайта это может быть ненужным. И помните, кэширование - это хорошо.

Стоит также отметить, что браузер не скупится на хранение файлов в кеше. Браузеры имеют политики для такого рода вещей, и они обычно играют по правилам, изложенным в спецификации HTTP. Когда браузер делает запрос к серверу, часть ответа представляет собой заголовок EXPIRES ... дата, которая сообщает браузеру, как долго он должен храниться в кэше. В следующий раз, когда браузер встречает запрос на тот же файл, он видит, что у него есть копия в кеше, и смотрит на дату истечения срока действия, чтобы решить, следует ли его использовать.

Так что, хотите верьте, хотите нет, но на самом деле ваш сервер делает этот кеш браузера таким постоянным. Вы можете настроить параметры сервера и изменить заголовки EXPIRES, но небольшая техника, которую я написал выше, вероятно, намного проще для вас. Поскольку кэширование хорошо, обычно вы хотите установить эту дату далеко в будущее («Заголовок истекающего далекого будущего») и использовать метод, описанный выше, для принудительного изменения.

Если вас интересует дополнительная информация о HTTP или о том, как выполняются эти запросы, хорошая книга - «Высокопроизводительные веб-сайты» Стива Соудерса. Это очень хорошее введение в предмет.

112 голосов
/ 08 апреля 2011

Плагин mod_pagespeed Google для Apache сделает автоматическое управление версиями для вас. Это действительно гладко.

Он анализирует HTML на выходе из веб-сервера (работает с PHP, rails, python, статическим HTML - что угодно) и переписывает ссылки на CSS, JS, файлы изображений, чтобы они содержали код id. Он обслуживает файлы по измененным URL-адресам с очень длинным контролем кэша. Когда файлы изменяются, он автоматически меняет URL-адреса, поэтому браузер должен повторно их получить. В основном это работает, без каких-либо изменений в вашем коде. Он даже сократит ваш код на выходе.

90 голосов
/ 23 сентября 2008

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

Таким образом, ваш URL будет выглядеть примерно так:

http://mysite.com/css/[md5_hash_here]/style.css

Вы все еще можете использовать правило перезаписи для удаления хэша, но преимущество заключается в том, что теперь вы можете установить политику кэширования на «кеширование навсегда», поскольку, если URL-адрес один и тот же, это означает, что файл не изменяется.

Затем вы можете написать простой сценарий оболочки, который будет вычислять хэш файла и обновлять ваш тег (возможно, вы захотите переместить его в отдельный файл для включения).

Просто запускайте этот скрипт каждый раз, когда меняется CSS, и все хорошо. Браузер перезагрузит только ваши файлы, когда они будут изменены. Если вы сделаете правку, а затем отмените ее, вам не составит труда выяснить, к какой версии вам нужно вернуться, чтобы ваши посетители не перезагружали ее.

61 голосов
/ 26 января 2013

Не знаю, почему вы, ребята, так стараетесь реализовать это решение.

Все, что вам нужно сделать, если получить измененную временную метку файла и добавить ее в качестве строки запроса к файлу

В PHP я бы сделал это так:

<link href="mycss.css?v=<?= filemtime('mycss.css') ?>" rel="stylesheet">

filemtime - это функция PHP, которая возвращает измененную временную метку файла.

51 голосов
/ 23 сентября 2008

Вы можете просто поставить ?foo=1234 в конце импорта css / js, изменив 1234 на то, что вам нравится. Взгляните на источник SO html для примера.

Идея в том, что? параметры игнорируются / игнорируются по запросу в любом случае, и вы можете изменить это число при развертывании новой версии.


Примечание: Есть некоторые аргументы относительно того, как именно это влияет на кеширование. Я считаю, что основная суть в том, что запросы GET с параметрами или без параметров должны быть кэшируемыми, поэтому вышеприведенное решение должно работать.

Тем не менее, и веб-сервер должен решить, хочет ли он придерживаться той части спецификации и браузера, который использует пользователь, поскольку он может просто пойти дальше и запросить новую версию в любом случае.

39 голосов
/ 23 сентября 2008

Я слышал, это называется "автоматическое управление версиями". Наиболее распространенный способ - включить mtime статического файла где-то в URL-адресе и удалить его с помощью обработчиков перезаписи или URL-адресов:

Смотри также:

24 голосов
/ 03 августа 2015

30 или около того существующих ответов - отличный совет для сайта около 2008 года. Однако, когда речь идет о современном одностраничном приложении (SPA), возможно, пришло время переосмыслить некоторые фундаментальные предположения ... в частности, идею, что веб-серверу желательно обслуживать только один , самая последняя версия файла.

Представьте, что вы - пользователь, у которого в вашем браузере загружена версия M SPA:

  1. Ваш конвейер CD развертывает новую версию N приложения на сервере
  2. Вы перемещаетесь в SPA, который отправляет XHR на сервер, чтобы получить /some.template
    • (Ваш браузер не обновил страницу, поэтому вы все еще используете версию M )
  3. Сервер отвечает содержимым /some.template - хотите ли вы вернуть версию M или N шаблона?

Если формат /some.template изменился между версиями M и N (или файл был переименован или как-то еще) вам, вероятно, не нужна версия N шаблона, отправленного в браузер, на котором установлена ​​старая версия M синтаксического анализатора . †

Веб-приложения сталкиваются с этой проблемой при выполнении двух условий:

  • Ресурсы запрашиваются асинхронно через некоторое время после начальной загрузки страницы
  • Логика приложения предполагает вещи (которые могут измениться в будущих версиях) относительно содержимого ресурса

Как только ваше приложение должно обслуживать несколько версий параллельно, решение кеширования и «перезагрузки» становится тривиальным:

  1. Установить все файлы сайта в версионные каталоги: /v<release_tag_1>/…files…, /v<release_tag_2>/…files…
  2. Установите заголовки HTTP, чтобы браузеры могли кешировать файлы навсегда
    • (или еще лучше, положить все в CDN)
  3. Обновите все теги <script> и <link> и т. Д., Чтобы они указывали на этот файл в одном из версионных каталогов

Этот последний шаг звучит сложно, так как он может потребовать вызова построителя URL для каждого URL в коде на стороне сервера или на стороне клиента. Или вы можете просто использовать тег <base> и изменить текущую версию в одном месте.

† Одним из способов решения этой проблемы является проявление агрессии, заставляя браузер перезагружать все, когда выходит новая версия. Но для того, чтобы позволить завершению любых выполняемых операций, все же может быть проще поддерживать как минимум две версии параллельно: v-current и v-previous.

14 голосов
/ 23 сентября 2008

Не используйте foo.css? Version = 1! Браузеры не должны кэшировать URL с помощью переменных GET. Согласно http://www.thinkvitamin.com/features/webapps/serving-javascript-fast,, хотя IE и Firefox игнорируют это, Opera и Safari этого не делают! Вместо этого используйте foo.v1234.css и используйте правила перезаписи для удаления номера версии.

10 голосов
/ 05 августа 2010

RewriteRule нуждается в небольшом обновлении для файлов js или css, которые в конце содержат версию с точечной нотацией. Например. JSON-1.3.js.

Я добавил класс отрицания точек [^.] В регулярное выражение, так что .number. игнорируется.

RewriteRule ^(.*)\.[^.][\d]+\.(css|js)$ $1.$2 [L]
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...