Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Immutable resource URLs in WordPress
<?php
/**
* Create immutable asset URLs.
*
* @package Immutable_Assets
* @author bjornjohansen
* @version 0.1.0
* @license https://www.gnu.org/licenses/old-licenses/gpl-2.0.html GNU General Public License version 2 (GPLv2)
*/
add_action( 'init', function() {
$ia = new Immutable_Assets();
$ia->wp_setup();
} );
/**
* Immutable assets class
*/
class Immutable_Assets {
/* @var string|null Holds our base URL. */
private $_base_url = null;
/* @var string|null Holds our base URL hostname. */
private $_base_host = null;
/**
* Init dynamic configuration.
*
* @return bool Whether initialization was OK.
*/
private function _init_config() {
$result = false;
$base_url = site_url();
if ( ! $base_url || ! strlen( $base_url ) ) {
$base_url = wp_guess_url();
}
// We have to know who we are, before we try to be clever.
if ( strlen( $base_url ) ) {
$base_urlparts = parse_url( $base_url );
if ( isset( $base_urlparts['host'] ) ) {
$this->_base_url = $base_url;
$this->_base_host = $base_urlparts['host'];
$result = true;
}
}
return $result;
}
/**
* Setup WP filters
*/
public function wp_setup() {
if ( $this->_init_config() ) {
add_filter( 'script_loader_src', [ $this, 'modify_url' ], 10, 2 );
add_filter( 'style_loader_src', [ $this, 'modify_url' ], 10, 2 );
}
}
/**
* Create an immutable asset URL
*
* Takes the version string, normalizes it with base64 encoding if needed, and
* injects it into the asset URL if the source points to our installation.
* The new URLs must be handled with rewrite rules in the web server (Apache/Nginx).
*
* @param string $str The source URL.
* @param string $handle The asset’s handle.
* @return string The possibly modified URL.
*/
public function modify_url( $src, $handle ) {
if ( is_null( $this->_base_url ) ) {
if ( ! $this->_init_config() ) {
return $src;
}
}
// Parse the URL so we can understand it a little better.
$urlparts = parse_url( $src );
if ( ! isset( $urlparts['path'] ) ) {
// We could obviously not parse this URL.
return $src;
}
// Check if the asset URL points to this installation.
if ( ! isset( $urlparts['host'] ) || $urlparts['host'] === $this->_base_host ) {
$ver = null;
if ( isset( $urlparts['query'] ) ) {
// Get the version string from the query vars.
$queryvars = [];
parse_str( $urlparts['query'], $queryvars );
if ( isset( $queryvars['ver'] ) ) {
$ver = $queryvars['ver'];
}
} else {
// Try to get the filemtime as version.
if ( defined( 'WP_CONTENT_URL' ) && defined( 'WP_CONTENT_DIR' ) && false !== strpos( $src, WP_CONTENT_URL ) ) {
$path = str_replace( WP_CONTENT_URL, WP_CONTENT_DIR, $src );
$filemtime = @filemtime( $path );
if ( false !== $filemtime ) {
$var = $filemtime;
}
}
}
// Normalize the version string if needed.
if ( strlen( $ver ) && ! preg_match( '/^[a-zA-Z0-9]+$/', $ver ) ) {
$ver = rtrim( base64_encode( $ver ), '=' );
}
// Check if we actually has a version string.
if ( strlen( $ver ) ) {
// Get the filename part and inject our version string.
$pathparts = explode( '/', $urlparts['path'] );
$file = array_pop( $pathparts );
$file = preg_replace( '/\.(css|js)$/', sprintf( '.%s.$1', $ver ), $file );
$pathparts[] = $file;
// Set src to the modified path.
$src = implode( '/', $pathparts );
// If a hostname was given in the original asset URL we’ll prepend the hostname.
if ( isset( $urlparts['host'] ) && strlen( $urlparts['host'] ) ) {
$src = '//' . $urlparts['host'] . $src;
}
// If a scheme was given in the original asset uRL, we’ll prepend the scheme.
if ( isset($urlparts['scheme']) && strlen( $urlparts['scheme'] )) {
$src = $urlparts['scheme'] . ':' . $src;
}
}
}
return $src;
}
}
@bjornjohansen

This comment has been minimized.

Copy link
Owner Author

commented Feb 3, 2017

You want something like this in your Nginx config server context:

location ~* (.+)\.(?:[a-zA-Z0-9]+)\.(js|css)$ {
    try_files $uri $1.$2 =404;
    expires max;
    add_header Cache-Control "public, immutable";
}

or something like this in your Apache VirtualHost section (or .htaccess):

<IfModule mod_rewrite.c>
    RewriteEngine On
    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteRule (.+)\.(?:[a-zA-Z0-9]+)\.(js|css)$ $1.$2 [L]
</IfModule>
<IfModule mod_headers.c>
    <FilesMatch "\.(js|css)$">
        Header set Expires "Thu, 31 Dec 2037 23:55:55 GMT"
        Header set Cache-Control "max-age=315360000, public, immutable"
    </FilesMatch>
</IfModule>
@renatonascalves

This comment has been minimized.

Copy link

commented Aug 8, 2017

I did some tests and it goes 404. There is something wrong with the Nginx version.
I didn't test the Apache version though.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.