How to load External JavaScript to improve page speed score in Hyva themes?
Chapters Close

How to load External JavaScript to improve page speed score in Hyva themes?

In this post, I will be guiding you on how to load External JavaScript to improve page speed scores in Hyva themes.

Generally, the main problem with loading external javascript files is that it usually has a negative impact on Google page ranking metrics, such as PageSpeed Insights.

This happens because script block rendering slows down the overall and perceived visual page-load of the page. So to increase the page speed, we should defer loading external library/javascript files until they are actually required.

Depending on the external library/javascript files, it is best to defer loading until one of the below events.

  1. User’s interaction with the page (anywhere on the page)
  2. User interacts with a particular part of the page (For example, clicks on a video ‘play’ button, or opens a modal popup)
  3. The user scrolls down to a certain part of the page (example given, where the script/library is used on the page)

Deferring scripts until a user interacts with the page

Many external scripts have nothing to do with the user interface(UI), or user experience(UX), yet they block the rendering process of the page, which will negatively impact page performance.

So Instead of loading the external scripts on page load, it is better to defer these scripts/libraries until the user interacts with the page. We can use below kind of scripts like:

  • Google Tag Manager
  • Google Analytics
  • Bing Universal Event Tracking
  • Facebook Pixel
  • Digital marketing/email campaign tracking scripts (e.g. Mailchimp)
  • User monitoring scripts (e.g. HotJar)

This is essentially any touch, mouse, or keyboard interaction, which you can listen for using native JavaScript event listeners.

A further, unintended benefit to this approach is it helps filter out bots from your analytics, given they don’t make the same interactions as a human user.

In Hyva, by default, they have created one init-external-scripts event for deferring scripts until any user interaction.

The init-external-scripts event is triggered when any of the standard interaction events (touch start, mouseover, wheel, scroll, key down) are fired, and it also handles the cleanup, so the event is only fired once.

Please check the below example of Deferring the GTM script until a user interaction.

// The below shows an example of loading Google Tag Manager
// However, you could load/run any external script you need here
window.addEventListener('init-external-scripts', () => {

  // Load Google Tag Manager (where `GTM-XXXXXX` is your container ID)
  (function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
      new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0], j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src='https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
  })(window,document,'script','dataLayer','GTM-XXXXXX');

}, {once: true, passive: true});

Given many use cases for these scripts are analytical, on the order success page, the event is fired on page load, rather than waiting for interaction. This is to ensure conversion data is always tracked(even though it’s almost impossible to not interact with the page, including if you are closing the tab/window).

Note: The init-external-scripts event is only available in Hyva Themes versions 1.1.20 and 1.2.0 and above. We can’t use this in the earlier version.

For compatibility with earlier Hyva versions, we can use the below way to differ scripts until any user interaction. We can also use this approch in the default Magento Luma websites.

// differ scripts until any user interaction
(events => {
  const loadMyLibrary = () => {
    events.forEach(type => window.removeEventListener(type, loadMyLibrary))
    // Load the library programatically here
  };
  events.forEach(type => window.addEventListener(type, loadMyLibrary, {once: true, passive: true}))
})(['touchstart', 'mouseover', 'wheel', 'scroll', 'keydown']);

Example with GTM:

<script>
    (events => {
  const initGtm = () => {
    events.forEach(type => window.removeEventListener(type, initGtm));
// Load Google Tag Manager (where `GTM-XXXXXX` is your container ID)
    (function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
})(window,document,'script','dataLayer','GTM-XXXXXX');

  };
  events.forEach(type => window.addEventListener(type, initGtm, {once: true, passive: true}))
})(['touchstart', 'mouseover', 'wheel', 'scroll', 'keydown'])

</script>

New to Hyvä?? Learn about the Hyvä Theme from basics!

Programmatically loading a script when needed

It’s common for external library vendors to request you add an external script request to the <head>.

<script type="text/javascript" src="https://www.example.com/render-blocking-script.js"/>

Directly loading scripts in the head will lower the page rank metrics and have a negative impact on page performance. 

Instead, load the script programmatically when it is needed. To achieve this we can use the below approach.

const script = document.createElement('script')
script.src = 'https://www.example.com/render-blocking-script.js';
script.type = 'text/javascript';
document.head.append(script);

Deferring scripts until a specific user interaction

The following example will load a script when a form input is focused.

Other user interactions won’t trigger the library to load.

<form x-data="initMyForm($el)" x-init="init()">
    <input type="text" name="example">
    <!-- other fields -->
</form>

<script>

    function initMyForm(form)
    {
      // Function to load external script. Return promise to be able to take action when loaded
      function load() {
        return new Promise(resolve => {
            const script = document.createElement('script');
            script.type = 'text/javascript';
            script.src = '<?= $escaper->escapeJs($block->getViewFileUrl('Example_Module::js/library.js')) ?>';
            script.async = true;
            script.onload = resolve;
            document.head.appendChild(script);
        })
      }

      return {
        init() {
          const inputs = Array.from(form.elements);

          const initExternalScript = () => {
            // Remove event listener from all fields, so it is loaded once only
            inputs.forEach(input => {
              input.removeEventListener('focus', initExternalScript)
            });

            // Load the external library and then call a method that does something with it
            load().then(() => this.doSomethingWithTheLibrary());
          };

          // Add onfocus event listener to every form element
          inputs.forEach(input => {
            input.addEventListener('focus', initExternalScript, {once: true, passive: true})
          })
        },
        doSomethingWithTheLibrary() {...}
      }
    }
</script>

The above code works. However, often scripts need to be loaded on any user interaction.

To simplify and standardize this process, Hyvä has its own custom init-external-scripts event you can listen to.

Facade Approach

In some cases external scripts may not only hamper performance on page load, but also impact the UI/UX of the page and can’t be loaded after user interaction, because then some elements on the page may be missing, and adding them later would cause layout shifts.

In these cases, it’s still better to load/run the external scripts on user interaction, either on the entire page (i.e., any interaction), or on a specific element.

However, a facade should be created that takes up the same amount of space on the page to avoid shifts. In addition, it could also be styled to look visually similar.

The facade is displayed on page load and then swapped out with the ‘real’ version following interaction.

Examples of external resources that can benefit from a facade approach include:

  • Live chat widgets
  • Videos (e.g., YouTube, Vimeo)
  • Search providers

Example: live chat facade

Most live chat solutions have a button/icon/link that triggers opening the live chat.

However, this button is often rendered after the external script has loaded and run. This in itself can cause layout shifts.

Therefore, if you want to defer loading/running of the live chat script, you need to recreate the button, which the user can then see and interact with (which will then trigger the external script loading).

<button data-role="init-live-chat"
        class="btn btn-primary /* add styles here to reserve space, recreate button display and avoid layout shifts */">
    <?= $escaper->escapeHtml(__('Click to chat!')) ?>
</button>

<script>
    const liveChatButton = document.querySelector('[data-role="init-live-chat"]');
    liveChatButton.addEventListener('click', () => {
        // Implement live chat 
        // This may be a script include or embed code, depending on the vendor

        // Programmatically trigger 'click' of actual live chat button to open panel/window (but ensure live chat library has loaded first)
        liveChatButton.click();
    }, {once: true});
</script>

Example: Youtube video facade

For videos, more details and recommended libraries for creating facades can be found on Chrome Developers.

Magento specific and Hyvä supported implementation for YouTube, MageQuest has published an open-source module that adds support for use within Page Builder (or manually): magequest/magento2-module-lite-youtube.

You can install this extension by the below command.

composer require magequest/magento2-module-lite-youtube
php bin/magento module:enable MageQuest_LiteYouTube
php bin/magento setup:upgrade
php bin/magento c:f

You can get more details regarding the extension on their github page.

Demo: 

After installing the extension you will get the Lite Youtube element in pagebuilder. You can use this in CMS block and CMS pages via Pagebuilder or you can also manually add <lite-youtube> element via HTML code. 

Lite Youtube element in pagebuilder

Same thing as live chat we can implement for third-party script Search providers using the facade approach.

More resources on Hyva themes:

Speak your Mind

Post a Comment

Got a question? Have a feedback? Please feel free to leave your ideas, opinions, and questions in the comments section of our post! ❤️

* This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.

Grow your online business like 3,219 subscribers

    * This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.
    envelope

    Thank You!

    We are reviewing your submission, and will be in touch shortly.