Как заставить браузер перезагрузить кэшированные файлы CSS / JS?

993

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

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

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

Обновить :

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

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

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

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

Pmpr
источник
У меня есть это в моей .htaccess, и никогда никаких проблем с кэшированных файлов: ExpiresActive On ExpiresDefault "modification".
Фрэнк Конейн
2
Я бы определенно согласился, что добавление информации о версиях к URL-адресу файла - безусловно, лучший путь. Это работает, все время, для всех. Но если вы не используете его, и вам просто нужно время от времени перезагружать этот файл CSS или JS в вашем собственном браузере ... просто откройте его на своей вкладке и нажмите SHIFT-reload (или CTRL-F5)! Вы можете эффективно сделать то же самое, используя JS, загрузив файл в (скрытый) iframe, дождавшись его загрузки и затем вызвав iframe.contentWindow.location.reload(true). См. Метод (4) stackoverflow.com/a/22429796/999120 - это касается изображений, но то же самое относится.
Doin
2
Я действительно ценю способ, которым этот вопрос был задан и был обновлен с тех пор. Это полностью описало то, что я должен ожидать в ответах. Я собираюсь следовать этому подходу в моих вопросах отныне. Ура!
rd22

Ответы:

455

Обновление: переписано для включения предложений от Джона Милликина и 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. В основном все, что не генерируется динамически.

койка
источник
16
Мой собственный статический контент-сервер делает то же самое, за исключением того, что я использую параметр для управления версиями (base.css? V = 1221534296) вместо изменения имени файла (base.1221534296.css). Я подозреваю, что ваш путь может быть немного более эффективным, хотя. Очень круто.
Дженс Роланд
4
@Kip: очень удобное решение. Переписывание URL, очевидно, может предложить гораздо больше, чем просто красивые ссылки.
Джеймс П.
37
Я вижу проблему в том, что она обращается к файловой системе много раз - точно - количество ссылок * количество запросов / сек ..., что может быть или не быть проблемой для вас.
Томаш Фейфар
3
@AlixAxel: Нет, браузеры будут повторно извлекать его при изменении параметра, но некоторые публичные прокси не будут кэшировать файлы с параметрами url, поэтому лучше всего указывать версию в пути. И издержки на mod_rewrite незначительны по сравнению с любыми другими узкими местами производительности в WPO
Дженс Роланд,
8
Первая file_existsпроверка действительно необходима? filemtimeвернет false в случае неудачи, так почему бы просто не присвоить значение filemtime переменной и проверить, является ли оно false перед переименованием файла? Это сократило бы одну ненужную файловую операцию, которая действительно сложилась бы.
Гэвин
184

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

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

Обычные посетители вашего сайта не будут иметь того опыта, который вы испытываете при разработке сайта. Так как среднестатистический посетитель посещает сайт реже (может быть, только несколько раз в месяц, если вы не являетесь сетью 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 или о том, как выполняются эти запросы, хорошая книга - «Высокопроизводительные веб-сайты» Стива Соудерса. Это очень хорошее введение в предмет.

keparo
источник
3
Быстрый прием генерации строки запроса с помощью Javascript прекрасно работает во время активной разработки. Я сделал то же самое с PHP.
Алан Тьюринг
2
Это самый простой способ достичь желаемого результата оригинального плаката. Метод mod_rewrite хорошо работает, если вы хотите принудительно перезагрузить файл .css или .js КАЖДЫЙ раз, когда вы загружаете страницу. Этот метод все еще позволяет кэшировать, пока вы действительно не измените файл и не захотите принудительно перезагрузить его.
scott80109
@keparo, у меня достаточно страниц jquery на всех страницах, если я собираюсь изменить это вручную, это займет месяц. Если вы можете помочь мне решить все без кодирования для каждой страницы.
взломщик
1
Кажется, это не работает для моего CSS, когда я использую:<link href='myCss.css?dev=14141'...>
Noumenon
3
Это не жизнеспособное решение. Большое количество браузеров просто откажется кэшировать что-либо со строкой запроса. По этой причине Google, GTMetrix и другие подобные инструменты будут поднимать флаг, если у вас есть строки запроса на ссылки на статический контент. Хотя это, безусловно, достойное решение для развития, это абсолютно не решение для производства. Кроме того, браузер управляет кэшированием, а не сервером. Сервер просто предлагает, когда он должен быть обновлен; браузер НЕ ДОЛЖЕН слушать сервер (и часто этого не делает). Мобильные устройства являются ярким примером этого.
пятнадцатого
113

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

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

оборота Леопд
источник
1
Это здорово, но все еще в бета-версии. Можно ли использовать его для обслуживания предприятия?
Санхьюн Ли
26
Это НЕПРАВИЛЬНО (автоматическое переключение с источником), когда это явно проблема браузера. Дайте нам (разработчикам) настоящее обновление мозгов: <ctrl> + F5
T4NK3R
25
mod_pagespeed функционально эквивалентен полностью автоматическому шагу сборки / компиляции для html / css / js. Я думаю, что вам будет трудно найти серьезных разработчиков, которые думают, что системы сборки по своей сути неверны, или что что-то не так с их полной автоматизацией. Аналогом чистой сборки является очистка кеша mod_pagespeed : code.google.com/p/modpagespeed/wiki/… ?
Леопд
3
@ T4NK3R mod_pagespeed ничего не должен делать с вашим источником для управления кешем, просто упоминалось, что он может помочь с такими вещами, как минификация. Что касается того, является ли это "НЕПРАВИЛЬНЫМ", это совершенно субъективно. Это может быть неправильно для вас, но это не значит, что это изначально плохо .
Madbreaks
2
Он также работает с nginx, хотя вам нужно собрать его из источника: developers.google.com/speed/pagespeed/module/…
Rohit
93

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

Таким образом, ваш URL будет что-то вроде

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

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

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

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

Левик
источник
1
к сожалению я не знаю как это реализовать. Советы, пожалуйста ... подробнее ...
Майкл Фелпс
Реализация в shell, ruby ​​и т. Д. Была бы великолепна
Питер
3
Очень хорошее решение ... но я думаю, что для вычисления хеша файла при каждом запросе файла (css, js, images, html..etc) требуется каждый ресурс.
DeepBlue
Это стандартное решение для тех, кто использует js или css-связку с gulp, grunt или webpack, реализация отличается для каждого решения, но хэширование ваших файлов на этапе сборки является обычным явлением и предлагается для современных связанных приложений
Brandon Søren Culley
@DeepBlue - в ответе написано «запускать этот скрипт каждый раз, когда меняется CSS» . Это НЕ на каждом посещении страницы. OTOH Ответ опускает основные детали - как измененный хеш становится частью URL? Я не знаю ...
ToolmakerSteve
71

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

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

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

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

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

Phantom007
источник
Вы можете просто использовать mycss.css?1234567890.
Гэвин
3
очень элегантно, хотя я немного изменил его <link rel="stylesheet" href="mycss.css?<?php echo filemtime('mycss.css') ?>"/>, на случай, если некоторые аргументы в этой теме о кэшировании URL-адресов с помощью переменных GET (в предлагаемом формате) верны
luke_mclachlan
В дополнение к моему последнему комментарию, я видел, что WordPress использует ?ver=так, кто знает!
luke_mclachlan
Отличное решение. Кроме того, я обнаружил, что filemtime не работает для полного доменного имени (FQDN), поэтому я использовал FQDN для части href и $ _SERVER ["DOCUMENT_ROOT"] для части filemtime. Пример: <link rel = "stylesheet" href = "http: //theurl/mycss.css? V = <? Php echo filemtime ($ _ SERVER [" DOCUMENT_ROOT "]. '/Mycss.css')?>" />
rrtx2000
Большое спасибо. Просто и хорошо. Вот это в Python: progpath = os.path.dirname (sys.argv [0]) def versionize (файл): timestamp = os.path.getmtime ('% s /../ web /% s'% (progpath) , file)) return '% s? v =% s'% (file, timestamp) print <link href = "% s" rel = "stylesheet" '' type = "text / css" /> '\% versionize ( 'css / main.css')
dlink
52

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

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


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

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

SCdF
источник
Бред какой то. Строка запроса (также называемая параметрами GET) является частью URL. Они могут и будут кешироваться. Это хорошее решение.
troelskn
9
@troelskn: спецификация HTTP 1.1 говорит иначе (в отношении запросов GET и HEAD с параметрами запроса): кэши НЕ ДОЛЖНЫ обрабатывать ответы на такие URI как свежие, если сервер не предоставит явное время истечения. См. W3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.9
Майкл Джонсон,
4
Я попробовал тип строки запроса версий со всеми основными браузерами, и они действительно кэшируют файл, спецификации или нет. Тем не менее, я думаю, что лучше использовать формат style.TIMESTAMP.css, не злоупотребляя строками запросов, так как есть вероятность, что кеширующее прокси-ПО НЕ БУДЕТ кэшировать файл.
Томас Андрле
34
Стоит отметить, по какой-либо причине, что Stackoverflow сам использует метод строки запроса.
Джейсон
2
Убедитесь, что использование параметра? = Не заставит браузеры повторно извлекать кэшированный файл при изменении параметра. Единственный способ - изменить имя файла программно на стороне сервера, как ответил Кип
Арунскриш
41

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

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

Джон Милликин
источник
3
Спасибо, я думаю, это был еще один случай, когда моя идея обсуждалась, я просто не знал, как она называется, поэтому я не нашел ее в поиске Google.
Кип
27

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.

Майкл Кропат
источник
Майкл - твой комментарий очень актуален. Я пытаюсь найти решение для своего SPA. Я получил несколько советов, но сам должен был найти решение. В конце концов, я был очень доволен тем, что я придумал, поэтому я написал пост в блоге и ответил на этот вопрос (включая код). Спасибо за указатели
statler
Отличный комментарий. Я не могу понять, в то время как люди продолжают говорить о разрушении кэша и HTTP-кэшировании как о реальном решении проблем кэширования веб-сайтов, не комментируя новые проблемы SPA, как если бы это был незначительный случай.
Дэвид Касильяс
1
Отличный ответ и абсолютно идеальная стратегия! И бонусные баллы за упоминание baseтега! Что касается поддержки старого кода: это не всегда возможно и не всегда хорошая идея. Новые версии кода могут поддерживать критические изменения в других частях приложения или могут включать в себя экстренные исправления, исправления уязвимостей и так далее. Мне еще предстоит реализовать эту стратегию самостоятельно, но я всегда чувствовал, что общая архитектура должна позволять развертываниям помечать старую версию obsoleteи принудительно перезагружать при следующем асинхронном вызове (или просто принудительно отключать все сеансы через WebSockets). ).
Джонни Асмар
Приятно видеть хорошо продуманный ответ в отношении одностраничных приложений.
I
Это «сине-зеленое развертывание», если вы хотите найти больше информации.
Фил
15

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

airrob
источник
1
Во-первых, браузеры не кешируют, это функция HTTP. Почему http заботится о структуре URI? Есть ли официальная ссылка на спецификацию, в которой говорится, что кэширование HTTP должно понимать семантику URI, чтобы не кэшировать элементы со строкой запроса?
AnthonyWJones
13
Веб-браузер, который включает в себя функции кэширования объектов (проверьте каталог кэша вашего браузера). HTTP - это протокол, включающий директивы от серверов к клиентам (прокси, браузеры, пауки и т. Д.), Предлагающий управление кэшем.
Цот
13

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

<script src="{{ asset('/js/your.js?v='.filemtime('js/your.js')) }}"></script>

И похоже на CSS

<link rel="stylesheet" href="{{asset('css/your.css?v='.filemtime('css/your.css'))}}">

Пример вывода html ( filemtimeвремя возврата в виде метки времени Unix )

<link rel="stylesheet" href="assets/css/your.css?v=1577772366">
оборота Камиль Келчевски
источник
что вывод этой команды в HTML? А что если мне нужно обновить только такие версии, как? V = 3
Гедиминас
filemtime : «Эта функция возвращает время, когда блоки данных файла были записаны, то есть время, когда содержимое файла было изменено». src: php.net/manual/en/function.filemtime.php
Камил Келчевски
11

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

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

RewriteRule ^(.*)\.[^.][\d]+\.(css|js)$ $1.$2 [L]
Ник Джонсон
источник
2
Спасибо за вклад! С тех пор как я написал этот пост, меня это тоже сожгло. Моим решением было переписать только если последняя часть имени файла содержит ровно десять цифр. (10 цифр охватывают все временные метки с 9 сентября 2001 года по 20 ноября 2286 года.) Я обновил свой ответ, включив в него следующее регулярное выражение:^(.*)\.[\d]{10}\.(css|js)$ $1.$2
Кип
Я понимаю регулярные выражения, но я не понимаю, какую проблему вы решаете [^.]здесь. Кроме того, нет смысла писать \dвнутри класса символов - \d+будет делать то же самое. После публикации ваш шаблон будет соответствовать любому количеству символов (жадно), затем буквальной точке, затем не точке, затем одной или нескольким цифрам, затем точке, затем cssили js, затем концу имени файла. Нет совпадений для вашего примера ввода: regex101.com/r/RPGC62/1
mickmackusa
10

Для ASP.NET 4.5 и выше вы можете использовать связывание скриптов .

Запрос http://localhost/MvcBM_time/bundles/AllMyScripts?v=r0sLDicvP58AIXN_mc3QdyVvVj5euZNzdsa2N1PKvb81предназначен для пакета AllMyScripts и содержит пару строк запроса v = r0sLDicvP58AIXN_mc3QdyVvVj5euZNzdsa2N1PKvb81. Строка запроса v имеет маркер значения, который является уникальным идентификатором, используемым для кэширования. Пока пакет не изменяется, приложение ASP.NET будет запрашивать пакет AllMyScripts, используя этот токен. Если какой-либо файл в пакете изменяется, среда оптимизации ASP.NET сгенерирует новый токен, гарантируя, что запросы браузера для пакета получат последний пакет.

Есть и другие преимущества связывания, в том числе повышение производительности при первой загрузке страницы с минимизацией.

user3738893
источник
Пожалуйста, помогите мне, я не делаю никаких изменений в bundle.config, просто меняю файлы css или js, тогда как я могу решить проблему с кэшированием?
Веданкита Кумбхар
10

Вот чистое решение JavaScript

(function(){

    // Match this timestamp with the release of your code
    var lastVersioning = Date.UTC(2014, 11, 20, 2, 15, 10);

    var lastCacheDateTime = localStorage.getItem('lastCacheDatetime');

    if(lastCacheDateTime){
        if(lastVersioning > lastCacheDateTime){
            var reload = true;
        }
    }

    localStorage.setItem('lastCacheDatetime', Date.now());

    if(reload){
        location.reload(true);
    }

})();

Выше будет выглядеть последний раз, когда пользователь посетил ваш сайт. Если последний визит был до того, как вы выпустили новый код, он используется location.reload(true)для принудительного обновления страницы с сервера.

Обычно я использую его как самый первый сценарий внутри, <head>поэтому он оценивается перед загрузкой любого другого содержимого. Если требуется перезагрузка, это вряд ли заметно для пользователя.

Я использую локальное хранилище для хранения отметки времени последнего посещения в браузере, но вы можете добавить файлы cookie в список, если вы хотите поддерживать более старые версии IE.

Ллойд Бэнкс
источник
Я пробовал что-то вроде этого, это будет работать только на перезагруженной странице, но если на сайте есть несколько страниц с одинаковыми css / изображениями, то другие страницы все равно будут использовать старые ресурсы.
DeepBlue
9

Интересный пост. Прочитав все ответы здесь, в сочетании с тем фактом, что у меня никогда не было проблем со «поддельными» строками запросов (что я не уверен, почему все так неохотно используют это), я думаю, что решение (которое устраняет необходимость в правилах переписывания apache как и в принятом ответе) - вычислить короткий HASH содержимого файла CSS (вместо файла datetime) в виде фиктивной строки запроса.

Это приведет к следующему:

<link rel="stylesheet" href="/css/base.css?[hash-here]" type="text/css" />

Конечно, решения datetime также выполняют свою работу в случае редактирования файла CSS, но я думаю, что это касается содержимого файла css, а не файла datetime, так зачем смешивать их?

Михель
источник
8

Для моего развития я считаю, что хром имеет отличное решение.

https://developer.chrome.com/devtools/docs/tips-and-tricks#hard-reload

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

Это мой лучший друг, и это супер легкий способ получить то, что вы хотите!

Фрэнк Брайс
источник
И если вы используете Chrome в качестве среды разработки, другое неинвазивное решение - отключить кэш: в разделе «Настройки» можно отключить кэш диска, выбрав «Отключить кэш» (примечание: DevTools должен быть видимым / открытым чтобы это работало).
Velojet
7

Спасибо Кипу за его идеальное решение!

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

Надеюсь, это поможет кому-то еще.

/**
 * Extend filepath with timestamp to force browser to
 * automatically refresh them if they are updated
 *
 * This is based on Kip's version, but now
 * also works on virtual hosts
 * @link http://stackoverflow.com/questions/118884/what-is-an-elegant-way-to-force-browsers-to-reload-cached-css-js-files
 *
 * Usage:
 * - extend your .htaccess file with
 * # Route for My_View_Helper_AutoRefreshRewriter
 * # which extends files with there timestamp so if these
 * # are updated a automatic refresh should occur
 * # RewriteRule ^(.*)\.[^.][\d]+\.(css|js)$ $1.$2 [L]
 * - then use it in your view script like
 * $this->headLink()->appendStylesheet( $this->autoRefreshRewriter($this->cssPath . 'default.css'));
 *
 */
class My_View_Helper_AutoRefreshRewriter extends Zend_View_Helper_Abstract {

    public function autoRefreshRewriter($filePath) {

        if (strpos($filePath, '/') !== 0) {

            // path has no leading '/'
            return $filePath;
        } elseif (file_exists($_SERVER['DOCUMENT_ROOT'] . $filePath)) {

            // file exists under normal path
            // so build path based on this
            $mtime = filemtime($_SERVER['DOCUMENT_ROOT'] . $filePath);
            return preg_replace('{\\.([^./]+)$}', ".$mtime.\$1", $filePath);
        } else {

            // fetch directory of index.php file (file from all others are included)
            // and get only the directory
            $indexFilePath = dirname(current(get_included_files()));

            // check if file exist relativ to index file
            if (file_exists($indexFilePath . $filePath)) {

                // get timestamp based on this relativ path
                $mtime = filemtime($indexFilePath . $filePath);

                // write generated timestamp to path
                // but use old path not the relativ one
                return preg_replace('{\\.([^./]+)$}', ".$mtime.\$1", $filePath);
            } else {

                return $filePath;
            }
        }
    }

}

Приветствия и спасибо.

Lony
источник
7

Не нашли подход DOM на стороне клиента для динамического создания элемента узла сценария (или css):

<script>
    var node = document.createElement("script"); 
    node.type = "text/javascript";
    node.src = 'test.js?'+Math.floor(Math.random()*999999999);
    document.getElementsByTagName("head")[0].appendChild(node);
</script>
GreQ
источник
6

В Google Chrome есть « Жесткая перезагрузка», а также « Очистить кэш» и «Жесткая перезагрузка». Вы можете нажать и удерживать кнопку перезагрузки (в режиме проверки), чтобы выбрать ее.

оборота ajithes111
источник
Для того, чтобы уточнить, на "Проверьте режим", они имеют в виду "Dev Tools" ака F12, ака Ctrl + Shift + I, он же ant menu> More Tools> Developer Toolsака right click> Inspect Element. Есть также настройка, скрытая где-то в инструментах разработчика (я забыл местоположение), чтобы перезагружать каждую перезагрузку.
Джонни Асмар
5

Вы можете принудительно использовать «кеширование всей сессии», если добавляете идентификатор сессии как простой параметр файла js / css:

<link rel="stylesheet" src="myStyles.css?ABCDEF12345sessionID" />
<script language="javascript" src="myCode.js?ABCDEF12345sessionID"></script>

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

<link rel="stylesheet" src="myStyles.css?20080922_1020" />
<script language="javascript" src="myCode.js?20080922_1120"></script>
Гелиос
источник
5

Скажем, у вас есть файл, доступный по адресу:

/styles/screen.css

Вы можете добавить параметр запроса с информацией о версии в URI, например:

/styles/screen.css?v=1234

или вы можете добавить информацию о версии, например:

/v/1234/styles/screen.css

ИМХО, второй метод лучше подходит для CSS-файлов, потому что они могут ссылаться на изображения с использованием относительных URL, что означает, что если вы укажете background-imageподобное, то так:

body {
    background-image: url('images/happy.gif');
}

его URL будет эффективно:

/v/1234/styles/images/happy.gif

Это означает, что если вы обновите номер используемой версии, сервер будет рассматривать это как новый ресурс и не будет использовать кэшированную версию. Если вы основываете свой номер версии на Subversion / CVS / и т.д. пересмотр означает, что будут замечены изменения в изображениях, на которые есть ссылки в файлах CSS. Это не гарантируется первой схеме, то есть URL images/happy.gifотносительно /styles/screen.css?v=1235IS , /styles/images/happy.gifкоторый не содержит никакой информации о версии.

Я реализовал решение для кэширования, используя эту технику с сервлетами Java, и просто обрабатываю запросы /v/*с помощью сервлета, который делегирует базовый ресурс (т /styles/screen.css. Е. ). В режиме разработки я установил кэширование заголовков , которые говорят клиенту всегда проверять свежесть ресурса с сервером (обычно это приводит к 304 , если вы делегировать для Tomcat DefaultServletи .css, .jsи т.д. файл не изменился) , а в режиме развертывания Я установил заголовки, которые говорят «кешировать навсегда».

оборота Уолтер Рамсби
источник
Простое добавление папки, которую вы можете переименовать при необходимости, будет работать, если вы используете только относительные URL-адреса. И тогда вы убедитесь , что для перенаправления на нужную папку из базовой папки, то есть в PHP: <?php header( 'Location: folder1/login.phtml' ); ?>.
Грубер
1
Используя второй метод, изменение CSS сделает недействительными кэшированные копии всех изображений, на которые есть ссылки с относительными URL-адресами, что может быть или не быть желательным.
TomG
5

Вы можете просто добавить случайное число с помощью CSS / JS url, например

example.css?randomNo=Math.random()
Понмуди В.Н.
источник
5

Для ASP.NET я предполагаю следующее решение с расширенными настройками (режим отладки / выпуска, версии):

Файлы Js или Css включены таким образом:

<script type="text/javascript" src="Scripts/exampleScript<%=Global.JsPostfix%>" />
<link rel="stylesheet" type="text/css" href="Css/exampleCss<%=Global.CssPostfix%>" />

Global.JsPostfix и Global.CssPostfix вычисляются в Global.asax следующим образом:

protected void Application_Start(object sender, EventArgs e)
{
    ...
    string jsVersion = ConfigurationManager.AppSettings["JsVersion"];
    bool updateEveryAppStart = Convert.ToBoolean(ConfigurationManager.AppSettings["UpdateJsEveryAppStart"]);
    int buildNumber = System.Reflection.Assembly.GetExecutingAssembly().GetName().Version.Revision;
    JsPostfix = "";
#if !DEBUG
    JsPostfix += ".min";
#endif      
    JsPostfix += ".js?" + jsVersion + "_" + buildNumber;
    if (updateEveryAppStart)
    {
        Random rand = new Random();
        JsPosfix += "_" + rand.Next();
    }
    ...
}
Иван Кочуркин
источник
4

Я недавно решил это с помощью Python. Вот код (должен быть легко адаптирован к другим языкам):

def import_tag(pattern, name, **kw):
    if name[0] == "/":
        name = name[1:]
    # Additional HTML attributes
    attrs = ' '.join(['%s="%s"' % item for item in kw.items()])
    try:
        # Get the files modification time
        mtime = os.stat(os.path.join('/documentroot', name)).st_mtime
        include = "%s?%d" % (name, mtime)
        # this is the same as sprintf(pattern, attrs, include) in other
        # languages
        return pattern % (attrs, include)
    except:
        # In case of error return the include without the added query
        # parameter.
        return pattern % (attrs, name)

def script(name, **kw):
    return import_tag("""<script type="text/javascript" """ +\
        """ %s src="/%s"></script>""", name, **kw)

def stylesheet(name, **kw):
    return import_tag('<link rel="stylesheet" type="text/css" ' +\
        """%s href="/%s">', name, **kw) 

Этот код добавляет метку времени файлов в качестве параметра запроса к URL-адресу. Вызов следующей функции

script("/main.css")

приведет к

<link rel="stylesheet" type="text/css"  href="/main.css?1221842734">

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

Пи.
источник
может os.stat () создать узкое место?
Ходжу
@Richard stat может быть узким местом, если диск очень медленный и запросов очень много. В этом случае вы можете кэшировать метку времени где-нибудь в памяти и очищать этот кеш при каждом новом развертывании. Тем не менее, эта сложность не будет необходимой в большинстве случаев использования.
пи.
4

Если вы используете git + PHP, вы можете перезагружать скрипт из кэша каждый раз, когда происходит изменение в git-репо, используя следующий код:

exec('git rev-parse --verify HEAD 2> /dev/null', $gitLog);
echo '  <script src="/path/to/script.js"?v='.$gitLog[0].'></script>'.PHP_EOL;
readikus
источник
4

Если вы разработчик и хотите избежать кеширования, на вкладке Chrome Network есть опция отключения кеша. В противном случае вы можете сделать это без инфраструктуры рендеринга сервера, используя два тега script.

<script type="text/javascript">
    document.write('<script type="text/javascript" src="myfile.js?q=' + Date.now() + '">
    // can't use myfile.js stuff yet
</script>')
<script type="text/javascript">
    // do something with myfile.js
</script>
оборота астронавт
источник
4

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

Как и многие другие, я просто хотел кратко удалить кеширование.

"keep caching consistent with the file" .. слишком много хлопот ..

Вообще говоря, я не против загружать больше - даже загружать снова файлы, которые не изменились - в большинстве проектов - практически не имеет значения. При разработке приложения - мы в основном загружаем с диска, localhost:port так что - эта increase in network trafficпроблема не является проблемой .

Большинство небольших проектов просто играют - они никогда не попадают в производство. так что для них вам больше ничего не нужно ..

Таким образом, если вы используете Chrome Dev Tools , вы можете использовать этот метод отключения кэширования, как показано на рисунке ниже: Как заставить Chrome перезагрузить кэшированные файлы

И если у вас есть проблемы с кэшированием Firefox : как заставить перезагрузить актив на firefox

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

Да, эта информация уже есть в предыдущих ответах, но мне все еще нужно было выполнить поиск в Google, чтобы найти ее.

Надеюсь, этот ответ очень ясен, и теперь вам не нужно.

AIon
источник
ОП спросил что-то и ответил что-то еще. Речь идет не о принудительной нагрузке на локальном компьютере, а о работе, и вы не можете попросить конечных пользователей выполнить вышеизложенное, чтобы отключить кэш и т. Д.
Jitendra Pancholi
3

Кажется, что все ответы здесь предполагают какое-то управление версиями в схеме именования, которая имеет свои недостатки.

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

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

Более подробное объяснение того, как это работает, можно найти здесь https://www.mnot.net/cache_docs/#WORK.

commonpike
источник
3

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

 $( window ).load(function() {
   location.reload(true);
});
Sandeep Ranjan
источник
Не работает на Chrome. Все еще загружаю активы из дискового кэша
Джейсон Ким
3

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

В ASP.NET

<link rel="stylesheet" href="~/css/custom.css?d=@(System.Text.RegularExpressions.Regex.Replace(File.GetLastWriteTime(Server.MapPath("~/css/custom.css")).ToString(),"[^0-9]", ""))" />

<script type="text/javascript" src="~/js/custom.js?d=@(System.Text.RegularExpressions.Regex.Replace(File.GetLastWriteTime(Server.MapPath("~/js/custom.js")).ToString(),"[^0-9]", ""))"></script>    

Это может быть упрощено до:

<script src="<%= Page.ResolveClientUrlUnique("~/js/custom.js") %>" type="text/javascript"></script>

Добавив метод расширения в свой проект, чтобы расширить страницу:

public static class Extension_Methods
{
    public static string ResolveClientUrlUnique(this System.Web.UI.Page oPg, string sRelPath)
    {
        string sFilePath = oPg.Server.MapPath(sRelPath);
        string sLastDate = System.IO.File.GetLastWriteTime(sFilePath).ToString();
        string sDateHashed = System.Text.RegularExpressions.Regex.Replace(sLastDate, "[^0-9]", "");

        return oPg.ResolveClientUrl(sRelPath) + "?d=" + sDateHashed;
    }
}
майк
источник
2

Я предлагаю реализовать следующий процесс:

  • Версии ваших файлов CSS / JS при каждом развертывании, например: screen.1233.css (номер может быть вашей версией SVN, если вы используете систему управления версиями)

  • минимизируйте их, чтобы оптимизировать время загрузки

Дэн Бурзо
источник