Miroslav Slapka
Miroslav Slapka's Blog

Miroslav Slapka's Blog

Speed up your Wordpress by loading 3rd party scripts on interaction

Speed up your Wordpress by loading 3rd party scripts on interaction

Subscribe to my newsletter and never miss my upcoming articles

Save the bandwidth and load the resources dynamically

Wordpress has been a very popular content management system but its robustness has some downsides. You might face performance issues unless your website is very well optimized or you just use it on the backend because the frontend is statically generated. However, if you have full Wordpress running and you use 3rd party scripts, consider loading them dynamically.

Instead of loading all the resources on the initial load, we can lazily load the scripts on interaction using dynamic import(), for example when the user clicks a button or scrolls towards the component. (Well, this article is about Wordpress so can we actually use dynamic imports? Keep reading to find out.)

To be more specific, I am going to show you how to load dynamically 2 different libraries:

These libraries are commonly used but before I show you how to load them on interaction, let’s take a look at what’s the standard way of loading Javascript files in Wordpress and what challenges we might face.

Photo by [Launchpresso](https://cdn.hashnode.com/res/hashnode/image/upload/v1621430487194/SPLPwd1DW.html) on [Unsplash](https://unsplash.com?utm_source=medium&utm_medium=referral)Photo by Launchpresso on Unsplash

Enqueue WP scripts

There’s a functions.php file where we have to use *wp_enqueue_script* function. For example, to load a tiny-slider library, we could do the following:

*function* my_scripts() {

wp_enqueue_script( ‘tiny-slider’,   ‘https://cdnjs.cloudflare.com/ajax/libs/tiny-slider/2.9.3/min/tiny-slider.js', array(), ‘20210327’, true );

// other scripts
}
add_action( 'wp_enqueue_scripts', 'my_scripts' );

All the scripts in this function are then inserted at the end of the website (before the closing body tag). To reduce the number of requests made we could concatenate them and perhaps create vendor.min.js that would contain all our 3rd party code.

If we need the script only on a certain page (E.g. homepage), we could write if statement and enqueue it only there:

*if* (is_front_page()) { wp_enqueue_script(...) }

This is how we enqueue the scripts in Wordpress. It still leaves us with performance issues. How can we load them dynamically instead?

Javascript dynamic import() and Wordpress

If you are familiar with Javascript promises, you will also handle dynamic import with ease, because the import loads a module and returns a promise. In vanilla JS it looks like this:

import('module-name')
.then( //do something here )
.catch(err => { console.log(err) });

Note: The async/await syntax is also possible to use.

In Wordpress we are going to have a problem though. We can’t just use it like that in our main JS file due to WP nature and its file system. We would end up with the following error:

ReferenceError: require is not defined

**@wordpress/lazy-import**

I found @wordpress/lazy-import npm package that mimicks the dynamic import() behaviour, but there’s an important note to it:

Currently, this alignment to `import* is superficial, and the module resolution still uses [CommonJSrequire`](nodejs.org/docs/latest-v12.x/api/modules.ht..), rather than the newer ES Modules support.*

It seems like the WP and dynamic import don’t get on well together. Installing require.js or setting up webpack to enable code splitting looks also like a daunting task and it’s not ideal way to go.

**☛ **Custom script loader

There is a very good article ‘The Import On Interaction Pattern’ by an engineering manager at Google Addy Osmani, that talks in detail about usage of dynamic imports, 3rd party code and related techniques. As he says:

Third-party resources are often added to pages without full consideration for how they fit into the overall loading of a site. Synchronously-loaded third-party scripts block the browser parser and can delay hydration. If possible, 3P script should be loaded with async/defer (or other approaches) to ensure 1P scripts aren’t starved of network bandwidth. Unless they are critical, they can be a good candidate for shifting to deferred late-loading using patterns like import-on-interaction.

For Vanilla JS implementation, he provides us with the following script, which we can enqueue in Wordpress.

// Script loadeer by Addy Osmani taken from https://glitch.com/edit/#!/tree-fluffy-stop?path=script.js%3A2%3A5

function scriptLoader() {
  /**
   * Promise-based script loader
   * @param {string} url
   * @param {object=} attr
   * @returns {Promise}
   */
  const loader = (url, attr) => new Promise((resolve, reject) => {
    const script = window.document.createElement('script');
    script.src = url;
    script.async = true;
    script.crossOrigin = 'anonymous';
    attr = attr || {};

    for (const attrName in attr) {
      script[attrName] = attr[attrName];
    }

    script.addEventListener('load', () => {
      resolve(script);
    }, false);

    script.addEventListener('error', () => {
      reject(script);
    }, false);

    window.document.body.appendChild(script);
  });

  /**
   * Loads scripts asynchronously
   * @param {string|string[]} urls
   * @param {object=} attr Other script tag attributes
   * @returns {Promise}
   */
  this.load = (urls, attr) => {
    if (!Array.isArray(urls)) {
      urls = [urls];
    }

    return Promise.all(urls.map(url => loader(url, attr)));
  }

  /**
   * Loads scripts asynchronously. It supports multiple url arguments, so each one will be loaded right after the
   * previous is loaded. This is a way of chaining dependency loading.
   *
   * @param {string|string[]} urls, ...
   * @returns {Promise}
   */
  this.loadChain = function (urls) {
    const args = Array.isArray(arguments) ? arguments : Array.prototype.slice.call(arguments);
    const p = this.require(args.shift());
    const self = this;
    return args.length ? p.then(() => {
      self.requireChain(...args);
    }) : p;
  }
}

// Inspiration: https://gist.github.com/itsjavi/93cc837dd2213ec0636a

It creates a script tag with the module we need and appends it to the body. We can use it like this:

*const* loader = new scriptLoader();

loader.load(['moduleURL']).then(() => {});

Let’s now describe the specific scenarios utilizing this script with the facades.

Facades aka fake components

A facade is a placeholder component that simulates the basic experience. For example it can be an image of Youtube video player or a map. Instead of embedding and loading the necessary resources, we can save the bandwidth and load them only after user interacts with the facade. The user might click, hover, or just scrolls towards the component.

[Youtube facade save +540KB](https://addyosmani.com/blog/import-on-interaction/)Youtube facade save +540KB

Loading Colorbox

We have a gallery of images and we want to display them in colorbox after a click. A user might actually never click on the image to see its larger version in the popup. Why load the library then if we can save several kB?

The gallery is not a facade per se, but it’s connected to the functionality we can defer. In the example below, I load the colorbox when the user hover over any image in the gallery. After the click the library has been loaded and the popup is shown.

hovering over any image loads the colorbox libraryhovering over any image loads the colorbox library

var appendedColorbox = false;

$( 'body' ).on( 'mouseenter', '.gallery-image', function() {

  const loader = new lpa.scriptLoader();  
  // check if the library was already loaded
  if(appendedColorbox) {return};

loader.load(['https://cdnjs.cloudflare.com/ajax/libs/jquery.colorbox/1.6.4/jquery.colorbox-min.js'])
  .then(() => {
    appendedColorbox = true;
  });

});

In this code, we listen for “mouseenter” event and we initialize our scriptLoader and load colorbox library.

Delay loading dynamic map

A proper facade example is the following. Instead of fully loaded Google map there’s only an image of the map with a button that says Load dynamic map.

This example is taken from official Google website and it uses Static Map API. Unfortunately our custom script loader doesn’t work for Google maps (it throws CORS policy error) and Google has its own loader we have to use @googlemaps/js-api-loader.

Summary

The import on interaction pattern is very useful and important when it comes to loading 3rd party resources. In this article I focused mainly on integrating it in Wordpress, because it’s still one of the most popular CMS. Since it’s not headless we should consider performance implications.

By utilizing the facade technique, we can save a lot of kilobytes by loading the resources dynamically. Due to how Wordpress works, we had to overcome some challenges but thanks to the script loader from Addy Osmani we are able to load the scripts on interaction.

 
Share this