cornerstone icon indicating copy to clipboard operation
cornerstone copied to clipboard

Blocking scripts delaying DomContentLoaded

Open Tiggerito opened this issue 3 years ago • 1 comments

Expected behavior

Where possible scripts should not block the render path as that leads to a delay in DomContentLoaded and a ripple on effect as scripts respond to that event. Resulting in a slower page load.

Actual behavior

Several core theme scripts are render blocking. There are probably more. e.g. when the consent manager is enabled.

head <script src="https://ajax.googleapis.com/ajax/libs/webfont/1.6.26/webfont.js"></script>

footer <script src="https://cdn11.bigcommerce.com/s-d70rbxs9pw/stencil/bf7ce9a0-3c27-013a-aa73-3e3502e24fa0/e/847ce670-3d4d-013a-ceda-322a12975137/dist/theme-bundle.main.js"> </script> <script type="text/javascript" src="https://cdn11.bigcommerce.com/shared/js/csrf-protection-header-b572e5526f6854c73a5e080ef15a771f963740ae.js"></script>

Steps to reproduce behavior

Use Chromes Network tool to view the waterfall sequence of events. It indicates DomContentLoaded as a blue line.

Blocking scripts have several effects. Lower priority requests are delayed until they are loaded. And DomContentLoaded can't happen until all blocking and non async scripts are loaded.

You can select one of the blocking scripts, right click and block its URL, then refresh the page. It will not be loaded and DomContentLoaded will be pulled forward to the next blocking script.

Note it seems scripts marked as defer but not async will still load async but still block the DomContentLoaded event.

Possible solution

The first blocking script is for fonts. Blocking may be reasonable as they are very important. But the related docs do provide a way to make the request non blocking.

The other two in the footer. Can they be set to async? Is there any reason they need to be blocking?

<script src="https://cdn11.bigcommerce.com/s-d70rbxs9pw/stencil/bf7ce9a0-3c27-013a-aa73-3e3502e24fa0/e/847ce670-3d4d-013a-ceda-322a12975137/dist/theme-bundle.main.js" async> </script> <script type="text/javascript" src="https://cdn11.bigcommerce.com/shared/js/csrf-protection-header-b572e5526f6854c73a5e080ef15a771f963740ae.js" async></script>

As mentioned defer only will block DomContentLoaded. Those could be made async defer to stop that. e.g.

<script type="text/javascript" src="https://cdn11.bigcommerce.com/shared/js/datatags-a6c27a4dafebddd5845000c8abc99b2096434171.js" defer></script>

Changed to:

<script type="text/javascript" src="https://cdn11.bigcommerce.com/shared/js/datatags-a6c27a4dafebddd5845000c8abc99b2096434171.js" async defer></script>

I made these changes on a stock Cornerstone theme and tested it on a simulated fast 3G network. DomContentLoaded fired as soon as the main CSS was loaded. In this test after less than 2 seconds. I removed the modifications and DomContentLoaded took 6 and a half seconds. This is the delay before and JavaScript based widgets can add their stuff.

Tiggerito avatar Dec 14 '21 05:12 Tiggerito

I noticed theme-bundle.main.js has some dependent code after it, meaning the way it is written, it has to block. This alteration seems to make it work async:

<script>
            {{!-- Exported in app.js --}}
            function onThemeBundleMain() {
                window.stencilBootstrap("{{page_type}}", {{jsContext}}).load();
            }
</script>
<script src="{{cdn 'assets/dist/theme-bundle.main.js'}}" onload="onThemeBundleMain()" async></script>

This would also mean it could be moved to the head and loaded earlier as it is no longer blocking. If it needs to be at the end so the DOM is ready, then adding defer to the script should solve that.

async scripts are low priority, so it may not start loading instantly. You would need to place it above the higher priority resources or add a preload:

<!-- DCL Critical Path. This script normally loads late as it is in the footer --> 
<link rel="preload" href="{{cdn 'assets/dist/theme-bundle.main.js'}}" as="script">

I also add a preload for theme-bundle.head_async.js So that it also starts loading instantly:

<!-- this one is async so this just helps it load/run earlier  -->
<link rel="preload" href="{{cdn 'assets/dist/theme-bundle.head_async.js'}}" as="script">

Some more scripts I found that get added with just defer:

<script type="text/javascript" src="https://cdn11.bigcommerce.com/shared/js/storefront/consent-manager-config-db81e5a134471155cf93f8aa3659cf1fff8c5057.js" defer></script>
<script type="text/javascript" src="https://cdn11.bigcommerce.com/shared/js/storefront/consent-manager-9356610ce15253804a8302812babe80e9824041b.js" defer></script>

async defer and even some preloads for them would help. I presume they power the consent banner which we'd like to show nice and quickly.

Tiggerito avatar Dec 24 '21 05:12 Tiggerito