12

I'm trying to develop a Wordpress theme and figure out how to provide a local fallback for Font Awesome if CDN fails or I develop my theme on a local server without internet connection.

The solution I have in mind is something like this (pseudo code):

if ( $CDN_IS_AVAILABLE ) { 
        wp_enqueue_style( 'font-awesome', '//netdna.bootstrapcdn.com/font-awesome/4.0.3/css/font-awesome.min.css', false );
    } else {
        wp_enqueue_style('font-awesome', get_template_directory_uri() . '/css/font-awesome/css/font-awesome.min.css', false, '4.0.3' );
    }

Thank you!

  • 1
    I'm afraid the Question is not WordPress-specific. Perhaps this question fits you. – Mayeenul Islam Apr 24 '14 at 8:06
  • Why do you think that? I'm talking about developing a WordPress theme and certain(tf) standards that states that if a CDN version of a library is included, a local copy must be provided as a fallback. Thanks anyway. – Knott Apr 24 '14 at 8:12
  • 1
    wp_enqueue_style() and how to work with it is on-topic. – fuxia Apr 24 '14 at 9:51
  • 2
    There are - at least- a couple of ways, however only reason to use a CDN is performance, but I'm pretty sure any way you'll find will make performance worse, so imho makes no sense. If theme is for sharing / sell I'd go for local copy only, being sure to leave users an easy way to switch to CDN version, if they prefer. If theme is for yourself only, choose one or another. Consider that most famous CDNs have a very low % of down time (and bootstrapcdn is one of the most reliable, according to cdnperf.com). – gmazzap Apr 24 '14 at 14:58
  • 1
    Well of course you do, I would never make extra request from PHP for that. That's the challenge here — for starters I can't think of good way to check for CSS load at all. – Rarst Apr 24 '14 at 17:02
14

The problem is that I'm pretty sure it's impossible to check if CSS is effectively added to a page via PHP: CSS is parsed by the browser, so client side, and has completely no effect on server side.

Of course, in PHP it's possible to check if CDN is responsive or not...

Option 1

Send a request and if it responds with HTTP status 200, use it. Something like:

function font_awesome_css() {
    $url = 'http://netdna.bootstrapcdn.com/font-awesome/4.0.3/css/font-awesome.min.css';
    $cdn = wp_remote_get( $url );
    if ( (int) wp_remote_retrieve_response_code( $cdn) !== 200 ) {
        $url = get_template_directory_uri() . '/css/font-awesome/css/font-awesome.min.css';
    }
    wp_enqueue_style( 'font-awesome', $url, false );
}

that results in 2 HTTP requests, one for the check, the second for embedded CSS: really bad.

Option 2

function font_awesome_css() {
    $url = 'http://netdna.bootstrapcdn.com/font-awesome/4.0.3/css/font-awesome.min.css';
    $cdn = wp_remote_get( $url );
    if ( (int) wp_remote_retrieve_response_code( $cdn ) === 200 ) {
        $css = wp_remote_retrieve_body( $cdn );
        add_action( 'wp_head', function() use( $css ) {
            $absolute = "//netdna.bootstrapcdn.com/font-awesome/4.0.3/fonts/";
            $css = str_replace( "../fonts/", $absolute, $css );
            echo '<style type="text/css">' . $css . '</style>';
        } );
    } else {
        $url = get_template_directory_uri() . '/css/font-awesome/css/font-awesome.min.css';
        wp_enqueue_style( 'font-awesome', $url, false );
    }
}

This is even worse:

  • It ruins the wp_enqueue_style workflow: if a plugin adds Font Awesome, it will be added 2 times.
  • Number of HTTP requests is the same, however normally the 2 requests run in parallel, so in this way the PHP generation of page slows down because it needs to wait for the first request response.
  • This also prevents the browser from caching the CSS, so if you use the same style in different pages, you force CDN request on every page visited. When using the normal workflow, the pages after first CSS are taken from cache.

So, really, don't do this at home.

What really matters is that using PHP you can check CDN request, but not verify CSS, so all your efforts end up in worse performance, instead of better.

Sincerely, if yours is a public theme, I suggest you use only the local copy, providing users a way to choose a CDN:

if ( ! function_exists( 'font_awesome_css' ) ) {
    function font_awesome_css() {
        $_url = get_template_directory_uri() . '/css/font-awesome/css/font-awesome.min.css';
        $url = apply_filters( 'font_awesome_css_url', $_url );
        wp_enqueue_style( 'font-awesome', $url, false );
    }
}

So users can completely override the function using a child theme, and can also use the 'font_awesome css_url' filter to change the URL.

Also consider that some high-end hosting providers automatically convert local assets to CDN ones, and there are plugins that allow CDN all the things; this is the reason a public theme should not use CDN at all.

If the theme is for yourself, then make a choice. Consider that most famous CDNs have a very low % of down time (and bootstrapcdn is one of the most reliable, according to cdnperf.com). I'm pretty sure your hosting has a downtime % greater than bootstrapcdn, so people have more probability to not see your site at all, than see it with broken icons.

The dirty way

As said, PHP can't check CSS, because CSS rendering happens client side, but you can use client side check: JavaScript.

First embed CSS using CDN:

function font_awesome_css() {
    $url =  '//netdna.bootstrapcdn.com/font-awesome/4.0.3/css/font-awesome.min.css';
    wp_enqueue_style( 'font-awesome', $url, false );
} 

After that, add some JavaScript to your footer:

/*
Normally the JS should be properly enqueued and the URL
passed via wp_enqueue_script, but this is a proof of concept,
more than real code.
*/
add_action( 'wp_footer', function() {
    $cssurl = get_template_directory_uri() . '/css/';
    ?>
    <span id="facheck" data-cssuri="<?php echo $cssurl; ?>" class="fa" style="display:none">
    </span>
    <script>
        jQuery(document).ready(function($) {
            var $check = $('#facheck');
            if ( $check.css('fontFamily') !== 'FontAwesome' ) {
                // Font Awesome not loaded!
                // Remove current CSS link
                $('#font-awesome-css').remove;
                // Add the local version
                var local = '<link rel="stylesheet" type="text/css" href="' +
                    $check.data('cssuri') + // This is the theme CSS folder URL
                    'font-awesome/css/font-awesome.min.css" />';
                $('head').append( local );
            }
        });
    </script>
    <?php
});

This code runs when the page is loaded and checks if the invisible span added to footer with class 'fa' has the font-family property set to 'FontAwesome'. This is set by Font Awesome, so if it isn't true, it means that CSS is not loaded. If it happens, the code uses JavaScript to append the local CSS to head.

(To test this code you can embed via wp_enqueue_style a wrong CDN URL, and see what happens)

So, in the rare case a CDN is not available, all the styles will be shown as expected (for some milliseconds users will see 'broken' CSS icons, because CSS is added after the page loads).

Now, considering that CDNs are very reliable, is it worth doing this hack for the < 1% of people that will see broken icons? Answering this question is left to you.

  • Long time I have not seen such a complex and deep approach on a topic like this. That cleared me completely. Now I guess I'll use CDN just as a theme option, leaving the user freedom to choose. Thank you! – Knott Apr 24 '14 at 21:39
  • I have been long looking for a solution like this, so regarding the last sentence that asks "is it worth doing this hack for the < 1% of people that will see broken icons?" Maybe adding a loading spinner would work out? – Carl Alberto May 21 '16 at 13:48
1

A server-side check isn't bullet-proof, either. If your server is located in California, your check will use the California CDN's Data Center. If your user is located in China, they would likely be using a completely different Data Center. At least, that's how I think it works.

Anyways, here's an improved jquery solution:

http://jsfiddle.net/skibulk/fp1gqnyc/

<link href="//maxcdn.bootstrapcdn.com/font-awesome/4.2.0/css/font-awesome.min.css" rel="stylesheet">
<script>
    (function($){
        var $span = $('<span class="fa" style="display:none"></span>').appendTo('body');
        if ($span.css('fontFamily') !== 'FontAwesome' ) {
            // Fallback Link
            $('head').append('<link href="/css/font-awesome.min.css" rel="stylesheet">');
        }
        $span.remove();
    })(jQuery);
</script>
  • brilliant, and a valid approach for a lot more than font-awesome, thanks for sharing. I should add, also agnostic unlike the other solutions here and elsewhere in SO for instance. – oucil Feb 19 '15 at 18:58
  • 1
    Since there's no timeout, this will end up loading Font Awesome twice if the CDN instance finishes loading after the check for fontFamily. – Dan Dascalescu Aug 29 '15 at 22:47
  • @DanDascalescu Aren't CSS files loaded synchronously (blocking) by default? I believe the page would not continue until the CDN is loaded or fails? – skibulk Aug 30 '15 at 1:46
  • CSS does load synchronously, but the font itself loads only when text using it is rendered. Here's a JSbin showing that. – Dan Dascalescu Aug 30 '15 at 2:46

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.