Skip to content

Instantly share code, notes, and snippets.

@bjornjohansen
Created February 3, 2017 23:29
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save bjornjohansen/2e0af9e7452501a46a1b8ae4d0260569 to your computer and use it in GitHub Desktop.
Save bjornjohansen/2e0af9e7452501a46a1b8ae4d0260569 to your computer and use it in GitHub Desktop.
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
Copy link
Author

bjornjohansen 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
Copy link

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