74

I have various javascripts that are necessary plugins in one of my WordPress domains, and I know where in the php file it's called from.

I'm taking every measure I can take to speed up page loading times, and every speed tester on the web says to defer javascripts if possible.

I have read about the defer='defer' and the async functions in javascript and I think one of these will accomplish what I'm trying to accomplish. But I'm not understanding how I'd do so in a php file.

For instance, here is a snippet from one particular plugin's php file where the javascript file is being called:

function add_dcsnt_scripts() {

    wp_enqueue_script( 'jquery' );
    wp_enqueue_script( 'dcsnt', dc_jqsocialtabs::get_plugin_directory() . '/js/jquery.social.media.tabs.1.7.1.min.js' );

}

I've read that it's best to do something like this for faster page loading times:

<script defer async src="..."></script>

But I don't know how to accomplish that within a php file. I want to do this with all of my javascript files.

How would I accomplish deferring or asyncing this javascript snippet to is loads last and speeds up page load times? What would be the ideal way to increase page load times across all browsers? Thanks for any guidance anybody can offer!

3
  • For people just learning about this topic... "defer" and "async" are two separate things. Defered scripts execute after all the normal scripts are processed, and will be single-threaded in the order they were encountered. Async scripts execute in separate background threads, starting immediately-ish with no predictable time or order of completion. You wouldn't use them both on the same script. Commented Nov 16, 2021 at 20:21
  • A nice article about defer vs. async: javascript.info/script-async-defer Commented Apr 28, 2022 at 8:05
  • Important Update! This is now available without workaround code as of WP 6.3! See stackoverflow.com/a/78186419/947370 and give it an upvote if you appreciate this news. Commented May 13, 2024 at 17:57

12 Answers 12

66

Or more universal way:

function add_async_forscript($url)
{
    if (strpos($url, '#asyncload')===false)
        return $url;
    else if (is_admin())
        return str_replace('#asyncload', '', $url);
    else
        return str_replace('#asyncload', '', $url)."' async='async"; 
}
add_filter('clean_url', 'add_async_forscript', 11, 1);

so you can add async to any script without code changes, just add #asyncload to script url as:

wp_enqueue_script('dcsnt', '/js/jquery.social.media.tabs.1.7.1.min.js#asyncload' )
Sign up to request clarification or add additional context in comments.

5 Comments

Thanks user ... that's assuming plugin authors coded their plugins properly using the enqueue method. More and more, I'm deleting plugins that aren't coded properly, and sticking with better-coded plugins, even if they're not as old and trusted as more popular plugins.
There's a small, but important difference between async and defer: webkit.org/blog/1395/running-scripts-in-webkit See also: stackoverflow.com/questions/10808109/script-tag-async-defer
+1 This is a nice theme/plugin author-friendly solution
This should be the chosen answer, great solution
Although, this works I don't think this is the best filter to use. This filter should return a valid clean URL not URL plus some HTML attributes. I think script_loader_tag would be a better filter to use for injecting HTML attributes. Using clean_url may conflict with other URL cleaning filters that a plugin may add.
63

Trying to keep things somewhat modular and all encompassing, the following approach dynamically chooses how to embed the tag with the async or defer attributes by just appending a small identifier to the $handle name:

/**
* Add async or defer attributes to script enqueues
* @author Mike Kormendy
* @param  String  $tag     The original enqueued <script src="...> tag
* @param  String  $handle  The registered unique name of the script
* @return String  $tag     The modified <script async|defer src="...> tag
*/
// only on the front-end
if(!is_admin()) {
    function add_asyncdefer_attribute($tag, $handle) {
        // if the unique handle/name of the registered script has 'async' in it
        if (strpos($handle, 'async') !== false) {
            // return the tag with the async attribute
            return str_replace( '<script ', '<script async ', $tag );
        }
        // if the unique handle/name of the registered script has 'defer' in it
        else if (strpos($handle, 'defer') !== false) {
            // return the tag with the defer attribute
            return str_replace( '<script ', '<script defer ', $tag );
        }
        // otherwise skip
        else {
            return $tag;
        }
    }
    add_filter('script_loader_tag', 'add_asyncdefer_attribute', 10, 2);
}

Example usage:

function enqueue_my_scripts() {

    // script to load asynchronously
    wp_register_script('firstscript-async', '//www.domain.com/somescript.js', '', 2, false);
    wp_enqueue_script('firstscript-async');

    // script to be deferred
    wp_register_script('secondscript-defer', '//www.domain.com/otherscript.js', '', 2, false);
    wp_enqueue_script('secondscript-defer');


    // standard script embed
    wp_register_script('thirdscript', '//www.domain.com/anotherscript.js', '', 2, false);
    wp_enqueue_script('thirdscript');
}
add_action('wp_enqueue_scripts', 'enqueue_my_scripts', 9999);

Outputs:

<script async type='text/javascript' src='//www.domain.com/somescript.js'></script>
<script defer type='text/javascript' src='//www.domain.com/otherscript.js'></script>
<script type='text/javascript' src='//www.domain.com/anothercript.js'></script>

Thanks to @MattKeys @crissoca for inspiring my answer here.

4 Comments

Plain fantastic.
Best one I've seen around!
One small improvement - scripts can be loaded with BOTH async and defer attributes. Your loader function could handle that by taking changing the 'else if' to just 'if'.
I think this is a clean solution! (Amazed that WP doesn't offer async/defer built-in...)
42

This blog post links to two plugins of interest:

Asynchronous Javascript
Improve page load performance by asynchronously loading javascript using head.js

WP Deferred Javascripts
Defer the loading of all javascripts added with wp_enqueue_scripts, using LABJS (an asynchronous javascript library).

Haven't tested them but checked the code and they do pretty fancy stuff with WordPress scripts enqueuing process.

But then WPSE comes to rescue:

// Adapted from https://gist.github.com/toscho/1584783
add_filter( 'clean_url', function( $url ) {
    if ( FALSE === strpos( $url, '.js' ) ) {
        // not our file
        return $url;
    }
    // Must be a ', not "!
    return "$url' defer='defer";
}, 11, 1 );

4 Comments

Thank you for your time in helping me out, brasofilo ... appreciate your time and that did basically get the html output I needed
@brasofilo 'clean_url' has been deprecated now, in favour or 'esc_url' codex.wordpress.org/Function_Reference/clean_url
it has been deprecated but instead of 'esc_url' I would now use 'script_loader_tag' as of WP 4.1
Circling back on this. The Async plugin has fallen out of support and the defer probably isn't super helpful outside legacy Internet Explorer. Just throwing that out there in case someone is doing research.
20

Another solution using a different filter, which can be used to target a specific script handle:

function frontend_scripts()
{
    wp_enqueue_script( 'my-unique-script-handle', 'path/to/my/script.js' );
}
add_action( 'wp_enqueue_scripts', 'frontend_script' );

function make_script_async( $tag, $handle, $src )
{
    if ( 'my-unique-script-handle' != $handle ) {
        return $tag;
    }

    return str_replace( '<script', '<script async', $tag );
}
add_filter( 'script_loader_tag', 'make_script_async', 10, 3 );

1 Comment

Prefect solution for asyncing plugin scripts. Thanks :)
16

A simplified method. Add to your functions.php file to make make JavaScript asynchronous in Wordpress

// Make JavaScript Asynchronous in Wordpress
add_filter( 'script_loader_tag', function ( $tag, $handle ) {    
    if( is_admin() ) {
        return $tag;
    }
    return str_replace( ' src', ' async src', $tag );
}, 10, 2 );

Comments

6

To gain control over which js files to defer and avoid conflicts you can append a variable to the url in the wp_register_script function like below.

wp_register_script( 'menu', get_template_directory_uri() . '/js/script.js?defer', array('jquery'), '1.0', true );

Then change the line:

if ( FALSE === strpos( $url, '.js' ))

To:

if ( FALSE === strpos( $url, '.js?defer' ))

The new filter looks like this.

add_filter( 'clean_url', function( $url )
{
    if ( FALSE === strpos( $url, '.js?defer' ) )
    { // not our file
    return $url;
    }
    // Must be a ', not "!
    return "$url' defer='defer";
}, 11, 1 );

2 Comments

Thank you Nathan. I'll test this out for sake of reference, although I used one of the solutions already.
This is a clever idea. I wish there was a less "hacky" approach, but this is actually the best I've seen yet to maintain control of scripts individually.
6

Very little modification code Mike Kormendy, which allows you to add 2 attributes at once:

// Async load
function add_asyncdefer_attribute($tag, $handle)
{
    $param = '';
    if ( strpos($handle, 'async') !== false ) $param = 'async ';
    if ( strpos($handle, 'defer') !== false ) $param .= 'defer ';
    if ( $param )
        return str_replace('<script ', '<script ' . $param, $tag);
    else
        return $tag;
}

Result:

<script async defer type='text/javascript' src='#URL'></script>

2 Comments

I endorse this! ;-)
Note that browsers will follow only one of the two directives - async is the preferred one. So async defer has the same effect as only adding async. The usual exception: IE9 and earlier, will only handle defer.
3

I believe it's bad practice to defer/async WordPress jQuery. Better solution would be to exclude jQuery from the filter:

if (!is_admin()) {
    add_filter( 'script_loader_tag', function ( $tag, $handle ) {    
        if ( strpos( $tag, "jquery.js" ) || strpos( $tag, "jquery-migrate.min.js") ) {
            return $tag;
        }
        return str_replace( ' src', ' async src', $tag );
    }, 10, 2 );
}

You can use defer instead of async

Comments

2

There is no need to use workarounds anymore, as async and defer are now inherently supported by the wp_enqueue_scripts function after WP 6.3.

Try this for async:

wp_register_script( 
    'bar', 
    '/path/to/bar.js', 
    array(), 
    '1.0.0', 
    array(
        'in_footer' => true,
        'strategy'  => 'async',
    )
)

For defer

wp_register_script( 
    'foo', 
    '/path/to/foo.js', 
    array(), 
    '1.0.0', 
    array(
        'strategy' => 'defer'
    ) 
);

Source: https://make.wordpress.org/core/2023/07/14/registering-scripts-with-async-and-defer-attributes-in-wordpress-6-3/

Comments

1

Incorporate the async attribute to the scripts so that they are only loaded once the whole page is loaded

<script type="text/javascript">
    function ngfbJavascript( d, s, id, url ) {
        var js, ngfb_js = d.getElementsByTagName( s )[0];
        if ( d.getElementById( id ) ) return;
        js = d.createElement( s );
        js.id = id;
        js.async = true;
        js.src = url;
        ngfb_js.parentNode.insertBefore( js, ngfb_js );
    };
</script>

Source:Here

1 Comment

although this method is correct and considered best practice for many years, it should be noted that it's not anymore. According to Ilya Grigorik the async attribute performs much better, more info can be found here: igvita.com/2014/05/20/…
1

Something to add the clean_url filter solution, make sure to validate to use it only on the font-end maybe using if( ! is_admin() ){} popular plugins like ACF might give you a headache.

Update

Here is my modified version of the solution:

if( ! is_admin() ){
  add_filter( 'clean_url', 'so_18944027_front_end_defer', 11, 1 );
  function so_18944027_front_end_defer( $url ) {
    if ( FALSE === strpos( $url, '.js' ) )
    { // not our file
        return $url;
    }
    // Must be a ', not "!
    return "$url' defer='defer";
  }
}

2 Comments

This should have been a comment, not an answer. With a bit more rep, you will be able to post comments. Until then, please do not use answers as a workaround.
Thanks Guys I just wanted to add an extra help to the right answer since I ran into that problem and as you said I didn't had enough rep to add a comment to the right answer, but I think it's a valid addition, I'm not adding a critic more than trying to extend the right answer
0

Starting wie WordPress 6.3, you can – and should – now use the 5th attribute of wp_enqueue_script, which can now contain an array with additional arguments. The property is strategy and as value you can either use defer or async.

\wp_enqueue_script(
    'dcsnt',
    dc_jqsocialtabs::get_plugin_directory() . '/js/jquery.social.media.tabs.1.7.1.min.js',
    [ 'jquery' ],
    false,
    [ 'strategy' => 'defer' ]
);

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.