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

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

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

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

Обновить:

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

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

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

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

58 ответов

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

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

Если вы используете ASP.NET MVC, вы можете проверить код в моем другом ответе здесь.

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

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

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

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

Для получения дополнительной информации см.: См.: https://developer.mozilla.org/nl/docs/Web/HTML/Applicatie_cache_gebruiken

Отключите кеш script.js только для локальной разработки в чистом JS

вставляет случайный скрипт.js?wizardry=1231234 и блокирует обычный скрипт.js

<script type="text/javascript">
  if(document.location.href.indexOf('localhost') !== -1) {
    const scr = document.createElement('script');
    document.setAttribute('type', 'text/javascript');
    document.setAttribute('src', 'scripts.js' + '?wizardry=' + Math.random());
    document.head.appendChild(scr);
    document.write('<script type="application/x-suppress">'); // prevent next script(from other SO answer)
  }
</script>
<script type="text/javascript" src="scripts.js">

Один из лучших и быстрых подходов, которые я знаю, - это изменить имя папки, в которой у вас есть файлы CSS или JS. ИЛИ для разработчиков. Измените имя ваших файлов CSS/js, например, на версии.

<link rel="stylesheet" href="cssfolder/somecssfile-ver-1.css"/>

Сделайте то же самое для ваших файлов JS.

Извините за возвращение мертвой нити.

@ Tomas Andrle прав.

Использование метода querystring не будет кэшироваться, как указано Стивом Соудерсом ниже:

... что Squid, популярный прокси, не кэширует ресурсы с помощью строки запроса.

@ Tomas Andrle предложение использовать style.TIMESTAMP.css - это хорошо, но MD5 будет намного лучше, поскольку только когда содержимое действительно изменилось, MD5 также изменится.

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

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

Для среды сервлетов Java вы можете обратиться к библиотеке Jawr. Страница функций объясняет, как она обрабатывает кэширование:

Jawr сделает все возможное, чтобы заставить ваших клиентов кешировать ресурсы. Если браузер спрашивает, изменился ли файл, заголовок 304 (не измененный) отправляется обратно без содержимого. С другой стороны, с Jawr вы будете на 100% уверены, что новые версии ваших пакетов загружаются всеми клиентами. Каждый URL-адрес ваших ресурсов будет содержать автоматически сгенерированный, основанный на контенте префикс, который изменяется автоматически при каждом обновлении ресурса. После развертывания новой версии URL-адрес пакета также изменится, поэтому клиент не сможет использовать более старую кэшированную версию.

Библиотека также выполняет минификацию js/css, но вы можете отключить ее, если не хотите.

Я добавляю этот ответ в виде конкретного ответа SilverStripe http://www.silverstripe.org/ который я искал и никогда не находил, но сработал из прочтения: http://api.silverstripe.org/3.0/source-class-SS_Datetime.html

Надеемся, что это поможет кому-то использовать шаблон SilverStripe и пытаться принудительно перезагрузить кэшированное изображение при каждом посещении / обновлении страницы. В моем случае это gif-анимация, которая воспроизводится только один раз и поэтому не воспроизводится после ее кэширования. В моем шаблоне я просто добавил:

?$Now.Format(dmYHis)

до конца пути к файлу, чтобы создать уникальную метку времени и заставить браузер обрабатывать его как новый файл.

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

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

Например, вот простой скрипт bash, который читает список имен файлов из stdin и записывает в stdout файл json, содержащий хэши:

#!/bin/bash
# create a json map from filenames to md5s
# run as hashes.sh < inputfile.list > outputfile.json

echo "{"
delim=""
while read l; do
    echo "$delim\"$l\": \"`md5 -q $l`\""
    delim=","
done
echo "}"

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

Я пришел к этому вопросу, когда искал решение для моего SPA, в котором есть только один index.html, в котором перечислены все необходимые файлы. В то время как я получил некоторые подсказки, которые помогли мне, я не мог найти быстрое и простое решение.

В конце я написал короткую страницу (включая весь код), необходимую для автоверсии html/js index.html, как часть процесса публикации. Он отлично работает и обновляет только новые файлы, основываясь на дате последнего изменения.

Вы можете увидеть мой пост на http://blueskycont.com/wp/2016/05/12/autoversion-your-spa-index-html/. Там также есть бесплатный рабочий winapp.

Суть кода

       private void ParseIndex(string inFile, string addPath, string outFile)
    {
        string path = Path.GetDirectoryName(inFile);
        HtmlAgilityPack.HtmlDocument document = new HtmlAgilityPack.HtmlDocument();
        document.Load(inFile);
        foreach (HtmlNode link in document.DocumentNode.Descendants("script"))
        {
            if (link.Attributes["src"]!=null)
            {
                resetQueryString(path, addPath, link, "src");
            }
        }
        foreach (HtmlNode link in document.DocumentNode.Descendants("link"))
        {
            if (link.Attributes["href"] != null && link.Attributes["type"] != null)
            {
                if (link.Attributes["type"].Value == "text/css" || link.Attributes["type"].Value == "text/html")
                {
                    resetQueryString(path, addPath, link, "href");
                }
            }
        }
        document.Save(outFile);
        MessageBox.Show("Your file has been processed.", "Autoversion complete");
    }

    private void resetQueryString(string path, string addPath, HtmlNode link, string attrType)
    {
        string currFileName = link.Attributes[attrType].Value;

        string uripath = currFileName;
        if (currFileName.Contains('?')) uripath = currFileName.Substring(0, currFileName.IndexOf('?'));
        string baseFile = Path.Combine(path, uripath);
        if (!File.Exists(baseFile)) baseFile = Path.Combine(addPath, uripath);
        if (!File.Exists(baseFile)) return;
        DateTime lastModified = System.IO.File.GetLastWriteTime(baseFile);
        link.Attributes[attrType].Value = uripath + "?v=" + lastModified.ToString("yyyyMMddhhmm");
    }

Ну, я заставил его работать, меняя версию js каждый раз при загрузке страницы, добавляя случайное число в версию файла js следующим образом:

// Add it to the top of the page
<?php
srand();
$random_number = rand();
?>

Затем примените случайное число к версии js следующим образом:

<script src="file.js?version=<?php echo $random_number;?>"></script>

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

" />

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

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

Еще одно предложение для сайтов ASP.Net,

  1. Установите разные параметры управления кэшем: значения максимального возраста для разных статических файлов.
  2. Для файлов css/js шансы на изменение этих файлов на сервере высоки, поэтому установите минимальный контроль кэша: максимальное значение возраста 1 или 2 минуты или что-то, что соответствует вашим потребностям.
  3. Для изображений установите дальнюю дату в качестве элемента управления кэшем: значение максимального возраста, скажем, 360 дней.
  4. Таким образом, когда мы делаем первый запрос, все статическое содержимое загружается на клиентский компьютер с ответом 200-OK.
  5. При последующих запросах и через две минуты мы видим 304-неизмененные запросы к файлам css и js, что позволяет нам избежать версий css/js.
  6. Файлы изображений не будут запрашиваться, так как они будут использоваться из кэшированной памяти до истечения срока действия кэша.
  7. Используя приведенные ниже конфигурации web.config, мы можем добиться описанного выше поведения,

Небольшое улучшение от существующих ответов...

Использование случайного числа или идентификатора сеанса приведет к перезагрузке при каждом запросе. В идеале нам может потребоваться изменить только если некоторые изменения кода были сделаны в любом файле js/css. При использовании общего файла JSP в качестве шаблона для многих других файлов jsp и js Добавьте ниже в общий файл JSP

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<c:set var = "version" scope = "application" value = "1.0.0" />

Теперь используйте вышеуказанную переменную во всех местах, как показано ниже в ваших файлах js.

<script src='<spring:url value="/js/myChangedFile.js?version=${version}"/>'></script>

Преимущества:

1) Этот подход поможет вам изменить номер версии только в одном месте.

2) Поддержание правильного номера версии (обычно номер сборки / выпуска) поможет вам проверить / проверить правильность развертывания изменений кода (из консоли разработчика браузера).

Еще один полезный совет:

Если вы используете браузер Chrome, вы можете отключить кэширование, когда Dev Tool открыт. В Chrome Hit F12 > F1 выделите Настройки> Настройки> Сеть> Отключить кэширование (когда DevTools открыт)

Chrome DevTools

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

      <script src="<?php echo get_template_directory_uri(); ?>/js/main.js?v=<?= time() ?>"></script>

В ASP.NET Core этого можно добиться, добавив asp-append-version:

<link rel="stylesheet" href="~/css/xxx.css" asp-append-version="true" />

 <script src="~/js/xxx.js" asp-append-version="true"></script>

Он сгенерирует HTML:

<link rel="stylesheet" href="/css/xxx.css?v=rwgRWCjxemznsx7wgNx5PbMO1EictA4Dd0SjiW0S90g" />

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

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

<script>
    var script = document.createElement('script');
    script.src = "js/app.js?v="+Math.random();
    document.getElementsByTagName('head')[0].appendChild(script);
</script>
      location.reload(true)

Или используйте «Сеть» в инспекторе ([CTRL] + [I]), нажмите «отключить кеш», нажмите значок корзины, нажмите «загрузить»/«получить».

Вот мое решение для очистки кеша на основе bash-скриптов:

  1. Я предполагаю, что у вас есть файлы css и js, указанные в вашем index.html
  2. Добавьте временную метку в качестве параметра для.js и.css в index.html, как показано ниже (только один раз)
  3. создайте файл timestamp.txt с указанной выше отметкой времени.
  4. После любого обновления файла.css или.js просто запустите приведенный ниже сценарий.sh

образец index.html записей для.js и.css с отметкой времени

<link rel="stylesheet" href="bla_bla.css?v=my_timestamp">
<script src="scripts/bla_bla.js?v=my_timestamp"></script>

timestamp.txt должен содержать только ту же метку времени 'my_timestamp' (будет найден и заменен скриптом позже)

наконец, вот скрипт (назовем его cache_buster.sh:D)

old_timestamp=$(cat timestamp.txt)
current_timestamp=$(date +%s)
sed -i -e "s/$old_timestamp/$current_timestamp/g" index.html
echo "$current_timestamp" >timestamp.txt

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

Я решил эту проблему с помощью ETag

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

datatables?v=1

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

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

datatables?v=Guid.NewGuid()

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

при изменении файла также измените версию. проверьте следующий код:

<script src="~/scripts/main.js?v=@File.GetLastWriteTime(Server.MapPath("/scripts/main.js")).ToString("yyyyMMddHHmmss")"></script>

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

Мой способ сделать это состоит в том, чтобы элемент link на стороне сервера включал:

<!--#include virtual="/includes/css-element.txt"-->

где содержимое css-element.txt

<link rel="stylesheet" href="mycss.css"/>

поэтому в тот день, когда вы захотите сделать ссылку на my-new-css.css или что-то еще, вы просто измените include.

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

      .....
<script type="text/javascript" src="js/tinysort.js"></script>
<script type="text/javascript" src="js/webfolder.js"></script>
<script type="text/javascript" src="js/ui.js"></script>
<div style="padding-bottom:3px;padding-top:3px"><h3 style="display:inline">
....

В окончательной веб-сборке будет выглядеть так:

      ....
<script>
  // from tinysort.js 
  var TINY={};

  function T$(i){return document.getElementById(i)}
  function T$$(e,p){return p.getElementsByTagName(e)}
  ....
  // from webfolder.js
  function selectAll() {
   if (document.forms.folder.elements.files.length == undefined)
      document.forms.folder.elements.files.checked = document.forms.folder.elements.files.checked == false;
   else  
     for(var el=0 ...
  .....
</script>
<div style="padding-bottom:3px;padding-top:3px"><h3 style="display:inline">
....

Вам не стоит беспокоиться, что веб-страница станет огромной, весь этот контент в любом случае будет загружен браузером. Некоторый параллелизм в загрузке не имеет смысла при использовании http 2. Поскольку полностью отлаживаются только части кода js или css, их можно снова разделить. Номер версии передается в самом имени файла, чтобы избежать использования старого кода. Вышеупомянутый подход хорошо работает для SPA, который я использую совсем недавно.

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

Другой способ, о котором я не упоминал для файлов js, - это использование jQuery. $.getScript в сочетании с $.ajaxSetup вариант cache: false,

Вместо:

<script src="scripts/app.js"></script>

Ты можешь использовать:

$.ajaxSetup({
  cache: false
});

$.getScript('scripts/app.js'); // GET scripts/app.js?_1391722802668

Изменение имени файла будет работать. Но обычно это не самое простое решение.

Как вы заметили, заголовок "без кэширования" для HTTP-элемента управления кэшем не всегда работает. Спецификация HTTP 1.1 предоставляет возможность для пользовательских агентов решать, стоит ли запрашивать новую копию. (Это не интуитивно понятно, если вы просто посмотрите на имена директив. Прочитайте фактическую спецификацию HTTP 1.1 для кеша... это имеет немного больше смысла в контексте.)

В двух словах, если вы хотите использовать железо-жесткий контроль кэша

Cache-Control: no-cache, no-store, must-revalidate

в заголовках вашего ответа.

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

<?php
//Replace the 'style.css' with the link to the stylesheet.
echo "<style type='text/css'>".file_get_contents('style.css')."</style>";
?>

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

Другие вопросы по тегам