javascript - force - 如何強制瀏覽器重新加載緩存的CSS/JS文件?




javascript clear cache (20)

RewriteRule需要對js或css文件進行小小的更新,最後包含點符號版本。 例如json-1.3.js。

我在正則表達式中添加了一個點否定類[^。],所以.number。 被忽略。

RewriteRule ^(.*)\.[^.][\d]+\.(css|js)$ $1.$2 [L]

我注意到一些瀏覽器(特別是Firefox和Opera)非常熱衷於使用.css.js文件的緩存副本,即使在瀏覽器會話之間也是如此。 當您更新其中一個文件但用戶的瀏覽器持續使用緩存副本時,這會導致問題。

問題是:當文件發生變化時,強制用戶瀏覽器重新加載文件的最優雅方式是什麼?

理想情況下,解決方案不會強制瀏覽器在每次訪問頁面時重新加載文件。 我會發布自己的解決方案作為答案,但我很好奇,如果有人有更好的解決方案,我會讓你的選票決定。

更新:

在經過一段時間的討論後,我發現John Millikinda5id的建議很有用。 事實證明,這是一個術語: 自動版本控制

我在下面發布了一個新的答案,這是我原來的解決方案和John的建議的結合。

SCdF提出的另一個想法是將偽造的查詢字符串附加到文件中。 (某些Python代碼自動使用時間戳作為偽造查詢字符串,由pi提交)。 但是,有一些關於瀏覽器是否會用查詢字符串緩存文件的討論。 (請記住,我們希望瀏覽器緩存文件並在將來的訪問中使用它,我們只希望它在文件更改後再次獲取文件。)

由於不清楚假冒查詢字符串會發生什麼情況,因此我不接受該答案。


更新:重寫包含John Millikinda5id的建議。 這個解決方案是用PHP編寫的,但應該很容易適應其他語言。

更新2:納入Nick Johnson的評論,原始的.htaccess正則表達式可能導致像json-1.3.js這樣的文件出現問題。 解決方案是只在最後有10位數字時才重寫。 (因為10位數字涵蓋了從9/9/2001到11/20/2286的所有時間戳。)

首先,我們在.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一起使用。 基本上任何不是動態生成的。



I put an MD5 hash of the file's contents in its URL. That way I can set a very long expiration date, and don't have to worry about users having old JS or CSS.

I also calculate this once per file at runtime (or on file system changes) so there's nothing funny to do at design time or during the build process.

If you're using ASP.NET MVC then you can check out the code in my other answer here .


I'm adding this answer as a SilverStripe http://www.silverstripe.org specific answer which I was looking for and never found but have worked out from reading: http://api.silverstripe.org/3.0/source-class-SS_Datetime.html#98-110

Hopefully this will help someone using a SilverStripe template and trying to force reload a cached image on each page visit / refresh. In my case it is a gif animation which only plays once and therefor did not replay after it was cached. In my template I simply added:

?$Now.Format(dmYHis)

to the end of the file path to create a unique time stamp and to force the browser to treat it as a new file.



Sorry for bringing back a dead thread.

@ TomA is right.

Using "querystring" method will not be cached as quoted by Steve Souders below:

...that Squid, a popular proxy, doesn't cache resources with a querystring.

@ TomA suggestion of using style.TIMESTAMP.css is good, but MD5 would be much better as only when the contents were genuinely changed, the MD5 changes as well.


google chrome has Hard Reload as well as Empty Cache and Hard Reload option.You can click and hold the reload button (In Inspect Mode) to select one .



你可以簡單地添加一些隨機數與CSS / JS網址一樣

example.css?randomNo=Math.random()

在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'))}}">

如果您使用的是git + php,那麼您可以使用以下代碼在每次更改git repo時從緩存中重新加載腳本:

exec('git rev-parse --verify HEAD 2> /dev/null', $gitLog);
echo '  <script src="/path/to/script.js"?v='.$gitLog[0].'></script>'.PHP_EOL;

對於2008年左右的網站,30個左右的現有答案是很好的建議。 然而,當涉及到一個現代的單頁面應用程序 (SPA)時,重新思考一些基本的假設可能是時候了......特別是對於Web服務器來說只需要單個最新版本的文件。

想像一下,你是一個將SPA的版本M加載到瀏覽器中的用戶:

  1. 您的CD管道將新版本N的應用程序部署到服務器上
  2. 您可以在SPA中導航,然後將XHR發送到服務器以獲取/some.template
    • (您的瀏覽器尚未刷新頁面,因此您仍在運行版本M
  3. 服務器響應/some.template的內容 - 你想讓它返回版本M還是N的模板?

如果/some.template的格式在版本MN之間更改(或文件被重命名或其他) ,則可能不希望將模板的版本N發送到運行解析器的舊版本M的瀏覽器

滿足兩個條件時,Web應用程序會遇到此問題:

  • 資源在初始頁面加載後的某個時間被異步請求
  • 應用程序邏輯假定有關資源內容的內容(可能會在未來版本中更改)

一旦您的應用需要並行提供多個版本, 解決緩存和“重新加載”就變得微不足道:

  1. 將所有站點文件安裝到版本化的目錄中: /v<release_tag_1>/…files…/v<release_tag_2>/…files…
  2. 設置HTTP頭讓瀏覽器永遠緩存文件
    • (或者更好的是,把所有內容放入CDN)
  3. 更新所有<script><link>標記等,以指向其中一個版本化目錄中的文件

最後一步聽起來很棘手,因為它可能需要為服務器端或客戶端代碼中的每個URL調用URL構建器。 或者你可以巧妙地使用<base>標籤並在一個地方更改當前版本。

†解決這個問題的一個方法是在發布新版本時強制瀏覽器重新加載所有內容。 但為了讓任何正在進行的操作完成,並行支持至少兩個版本仍然是最簡單的:v-current和v-previous。


對於ASP.NET 4.5及更高版本,您可以使用腳本捆綁

請求http://localhost/MvcBM_time/bundles/AllMyScripts?v=r0sLDicvP58AIXN_mc3QdyVvVj5euZNzdsa2N1PKvb81用於包AllMyScripts並包含查詢字符串對v = r0sLDicvP58AIXN_mc3QdyVvVj5euZNzdsa2N1PKvb81。 查詢字符串v有一個值標記,它是用於緩存的唯一標識符。 只要該包不更改,ASP.NET應用程序就會使用此令牌請求AllMyScripts包。 如果包中的任何文件發生更改,則ASP.NET優化框架將生成一個新的令牌,以確保瀏覽器對該包的請求將獲得最新的捆綁包。

捆綁還有其他好處,包括首次加載頁面時縮小的性能。


您可以將?foo=1234放在css / js導入的末尾,將1234更改為任何您喜歡的。 查看示例中的SO html源代碼。

那裡的想法是, 無論如何,參數在請求中被丟棄/忽略,當您推出新版本時,您可以更改該數字。

注意:關於這如何影響緩存,有一些爭論。 我相信它的一般要點是GET請求,無論有沒有參數都應該是可以緩存的,所以上面的解決方案應該可以工作。

然而,Web服務器決定是否要遵守該規範的一部分以及用戶使用的瀏覽器,因為它可以直接前進,並且無論如何都要求新版本。


感謝Kip提供他完美的解決方案!

我將其擴展為將其用作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://.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;
            }
        }
    }

}

乾杯和謝謝。


我會建議您使用實際CSS文件的MD5散列,而不是手動更改版本。

所以你的URL會是這樣的

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

您仍然可以使用重寫規則去除散列,但優點是現在您可以將緩存策略設置為“永久緩存”,因為如果URL相同,則表示文件未更改。

然後,您可以編寫一個簡單的shell腳本來計算文件的散列並更新您的標記(您可能希望將其移至單獨的文件以供包含)。

每次CSS改變時你都可以簡單地運行該腳本,而且你很棒。 瀏覽器只會在文件被修改時重新加載文件。 如果您進行編輯然後撤消它,那麼在確定您需要返回哪個版本以避免您的訪問者重新下載方面沒有任何痛苦。



谷歌的Apache mod_pagespeed插件會為你自動版本化。 這真的很光滑。

它在離開web服務器的路上解析HTML(與PHP,rails,python,靜態HTML一起工作 - 任何東西)並重寫鏈接到CSS,JS,圖像文件,以便包含一個id代碼。 它提供修改後的URL上的文件,並對它們使用非常長的緩存控制。 當文件發生變化時,它會自動更改URL,以便瀏覽器必須重新獲取它們。 它基本上可以正常工作,不需要對代碼進行任何更改。 它甚至可以在出路上縮減代碼。


這是一個純粹的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>第一個腳本,因此在加載任何其他內容之前對其進行評估。 如果需要重新加載,用戶幾乎不會注意到。

我正在使用本地存儲在瀏覽器上存儲上次訪問時間戳,但如果您希望支持較早版本的IE,則可以將Cookie添加到組合中。





auto-versioning