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

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

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

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

Обновление:

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

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

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

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

Ответы [ 48 ]

10 голосов
/ 01 октября 2014

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

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

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

8 голосов
/ 25 июня 2009

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

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

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

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

8 голосов
/ 13 января 2015

Вот чистое решение 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.

8 голосов
/ 03 марта 2017

В 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'))}}">
6 голосов
/ 07 января 2011

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

Я расширил его, чтобы использовать его как 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 /137392/kak-zastavit-brauzer-perezagruzhat-keshirovannye-faily-css-js
 *
 * 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;
            }
        }
    }

}

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

6 голосов
/ 31 мая 2016

Не найден подход 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>
5 голосов
/ 24 сентября 2008

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

/styles/screen.css

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

/styles/screen.css?v=1234

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

/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=1235 равен /styles/images/happy.gif, который не содержит никакой информации о версии.

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

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

Вы можете принудительно использовать «кэширование в течение сеанса», если добавляете идентификатор сеанса в качестве простого параметра файла 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 голосов
/ 04 июля 2013

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

example.css?randomNo=Math.random()
5 голосов
/ 03 августа 2013

Для 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();
    }
    ...
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...