Roots Discourse

Soil jQuery module - how load script ASYNC

Is there a way to set ASYNC to the script tags without modifying the plugin?
If not it would be great to add this in a future release.

Is there a reason to use the async attribute when the script is already at the bottom of the page?

Render Blocking when checking with Google PageSpeed Insights.

I usually prefer the defer attribute more than async as the latter has proven to cause issues with plugins in most cases.
Anyway, yes you can add the defer option to every script tag with the following filter:

function add_defer_attribute($tag, $handle) {
        return str_replace( ' src', ' defer src', $tag );

    return $tag;
add_filter('script_loader_tag', __NAMESPACE__ . '\\add_defer_attribute', 10, 2);

you can change defer to async or add any control statement that you need. I highly suggest excluding this filter in the admin area to avoid messing around with the core functionality of WordPress.


Unfortunately this will not work with the Soil plugin without touching core.
The jQuery fallback code is hardcoded:

if ($add_jquery_fallback) {
    echo '<script>window.jQuery || document.write(\'<script src="' . $add_jquery_fallback .'"><\/script>\')</script>' . "\n";
    $add_jquery_fallback = false;

I think there is no way to do that without change the plugin?

It is working on all my websites. That statement you quoted only gets executed if $add_jquery_fallback is true, which shouldn’t happen most of the times, given that this will evaluate to true only when the CDN is not available.

The instruction that actually includes jquery from the CDN is:
$add_jquery_fallback = apply_filters('script_loader_src', \includes_url('/js/jquery/jquery.js'), 'jquery-fallback'); which actually runs the normal filter you can hook into with what I posted.

Anyway, if this is not working in your environment, you can remove the support for soil-jquery-cdn and rewrite the same functions in your theme\plugin adding the async or defer option. At least that’s what I’d do :slight_smile:

1 Like

Fantastic snippet @Nicolo_Sacchi!

Something I may add: if you have any scripts loaded by plugins that need to be run immediately (like the Google Web Font API script, “webfontloader”) then you can modify the code to skip them like so:

function add_defer_attribute($tag, $handle) {
  if (!is_admin() && $handle !== 'webfontloader' ) {
    return str_replace(' src', ' defer src', $tag);
  return $tag;
add_filter('script_loader_tag', __NAMESPACE__ . '\\add_defer_attribute', 10, 2);


I know this is an old topic but I see something that I did not realized before:

Maybe I’m wrong but as far as I add defer or async jQuery gets downloaded twice using SOIL with add_theme_support('soil-jquery-cdn');. Once from CDN and again from /wp-includes/js/jquery/jquery.js, so document.write gets always executed and bumping the violation warning in console.

I’ve tested this in a couple sites in production, just to be sure it was not related to jquery CDN availability from a local or staging environment.

Anyone can confirm this?

@ibanlopez I’ve just been investigating jquery being called twice on my site, and for me it was another plugin (beaver builder) via FLBuilder::include_jquery which produced the /wp-includes/js/jquery/jquery.js one.

So I removed it remove_action('wp_footer', 'FLBuilder::include_jquery'); and add_theme_support('soil-jquery-cdn'); still works nicely to produce beautiful code!

I guess I’m saying how do you know it’s Soil causing your double jquery problem…

Thanks a lot for your reply. Well, I’m going to dig a little on my ecosystem, but you know, I don’t use plugins at all on front end.

Only plugins I use to use are backend. Apart from Gravity Forms I use one or two, not crucial with jQuery, but sure is something on my side.

I just wanted to check if anyone had experienced this “issue” of mine.

Anyway, thanks a lot again for your reply.

If I understand your problem correctly, I believe it’s caused by these lines in Soil:

  if ($add_jquery_fallback) {
    echo '<script>(window.jQuery && jQuery.noConflict()) || document.write(\'<script src="' . $add_jquery_fallback .'"><\/script>\')</script>' . "\n";
    $add_jquery_fallback = false;

These are how Soil determines whether or not to include the fallback. It does so by looking to see if window.jQuery and jQuery.noConflict() are available, i.e. that jQuery is available. If that test fails, then it inserts a script tag pointing to local jQuery into the document. I believe your problem is that when you load jQuery initially with an async or defer attribute, jQuery has probably not loaded by the time the browser begins parsing the above script, because the browser is waiting to download it (as per the async/defer request). That means that the fallback will generally always load, and then the original script will also load, because the async/defer mechanism has no way to determine if the same script has already been loaded.

I feel like I’ve run into similar issues before, and I generally either gave up on loading jQuery through Soil w/ async/defer, and either dropped Soil’s jQuery fallback module, or cannibalized the Soil code to load the CDN stuff in an defer-y way.

That makes total sense because it only does it when adding async/defer and stops doing it when removing async/defer.

That is basically why I thought it was nothing to do with other plugins even though I’ve been disabling everything and getting same result over and over, till I removed async/defer.

So thank you very much for the explanation and your time to make me understand this.