Cache-Busting durch Versionierung im Dateinamen

Von Haus aus versioniert WordPress eingebundene Skripte und Styles. So sieht der Output für die jQuery Bibliothek beispielhaft folgendermaßen aus:
http://example.com/wp-includes/js/jquery/jquery.js?ver=1.8.3

Wofür die Versionierung? Statische Dateien, die eine hohe Auslaufzeit (Expires-Header) haben, werden vom Browser nicht mehr vom Server geladen, sondern direkt aus dem Browsercache eingebunden. Wird die Resource aber auf dem Server geändert, bekommt der Browser dies nicht mit und lädt weiterhin die alte Version aus dem Browsercache. Abhilfe schafft die Versionierung.

WordPress hängt an die Resource eine Query String mit dem Parameter ver. Diese Art von Versionierung wird allerdings nicht empfohlen.
Google schreibt dazu:

Most proxies, most notably Squid up through version 3.0, do not cache resources with a “?” in their URL even if a Cache-control: public header is present in the response. To enable proxy caching for these resources, remove query strings from references to static resources, and instead encode the parameters into the file names themselves.

Die Version soll also mit in den Dateinamen gepackt werden.

Plugin: Dateinamen um die Version erweitern

Um die Dateien nicht physisch zu ändern hilft das mod_rewrite Modul und folgendes Plugin, welches sich in die WordPress Filter script_loader_src und style_loader_src einklinkt.

PHP / RAW / github:gist
<?php
/**
 * Plugin Name: Filename-based cache busting
 * Version: 0.3
 * Description: Filename-based cache busting for WordPress scripts/styles.
 * Author: Dominik Schilling
 * Author URI: http://wphelper.de/
 * Plugin URI: https://dominikschilling.de/880/
 *
 * License: GPLv2 or later
 * License URI: http://www.gnu.org/licenses/gpl-2.0.html
 *
 *
 * Extend your .htaccess file with these lines:
 *
 *   <IfModule mod_rewrite.c>
 *     RewriteEngine On
 *     RewriteBase /
 *
 *     RewriteCond %{REQUEST_FILENAME} !-f
 *     RewriteCond %{REQUEST_FILENAME} !-d
 *     RewriteRule ^(.+)\.(.+)\.(js|css)$ $1.$3 [L]
 *   </IfModule>
 */

/**
 * Moves the `ver` query string of the source into
 * the filename. Doesn't change admin scripts/styles and sources
 * with more than the `ver` arg.
 *
 * @param  string $src The original source.
 * @return string
 */
function ds_filename_based_cache_busting( $src ) {
	// Don't touch admin scripts.
	if ( is_admin() ) {
		return $src;
	}

	$_src = $src;
	if ( '//' === substr( $_src, 0, 2 ) ) {
		$_src = 'http:' . $_src;
	}

	$_src = parse_url( $_src );

	// Give up if malformed URL.
	if ( false === $_src ) {
		return $src;
	}

	// Check if it's a local URL.
	$wp = parse_url( home_url() );
	if ( isset( $_src['host'] ) && $_src['host'] !== $wp['host'] ) {
		return $src;
	}

	return preg_replace(
		'/\.(js|css)\?ver=(.+)$/',
		'.$2.$1',
		$src
	);
}
add_filter( 'script_loader_src', 'ds_filename_based_cache_busting' );
add_filter( 'style_loader_src', 'ds_filename_based_cache_busting' );

Wie im Plugin Header dokumentiert, muss die .htaccess ergänzt werden.
Wer auf nginx setzt kann folgende Zeilen nutzen, danke Sergej:

location ~ ^(.+)\.(.+)\.(js|css)$ {
    alias $1.$3;
}

Zum Thema

7 thoughts on “Cache-Busting durch Versionierung im Dateinamen”

  1. Hallo Dominik,

    danke für die Idee. Habe ich gleich eingebaut :-)

    Zwei kleine Korrekturen:
    '/.(js|css)?ver=(.+)$/',
    muss
    '/.(js|css)\?ver=(.+)$/',
    heißen, und in der RewriteRule würde ich die Punkte zwischen den Groups mit \ escapen, damit auch nur Punkte erfasst werden.

    Viele Grüße
    Tobias

    1. Danke für den Hinweis. Ist beides im Gist eigentlich drin, werden aber beim Import in den Beitrag geschluckt. Muss ich mal prüfen.

      Edit:
      Gefixt. update_post_meta hatte mir die Zeichen geklaut.

  2. Praktisches kleines Plugin. Danke dafür. Ich nehme den Artikel gleich mal in meine Bookmarks auf, dass ich es nicht beim nächsten großen Updates meines blogs vergesse :)

    Viele Grüße,
    Benni

  3. Hi Dominik,

    Great little plugin, thank you :-)

    The filter function currently targets all CSS file references, including those from other domains. In the case of GistPress or other plugins that allow styled embedded Gists, or just any style sheet held somewhere where the .htaccess reference doesn’t apply, the filter causes an incorrect URL.

    I was able to work around it with a second part to the conditional:

    // Don't touch admin scripts
    if ( is_admin() || false === strstr( $src, home_url() ) ) {
    return $src;
    }

    It may be that site_url() is better to catch more edge case installations, or not.

    Would this be a sensible addition for all users?

Leave a Reply