Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Order of DOM Execution Issue

I have some JavaScript in the HEAD tag that dynamically inserts an asynchronously loading script tag before the last script on the page (that has been currently parsed). This dynamically included script tag contains JavaScript that needs to parse the DOM after the DOM is available, but before all images AND script tags have been loaded in. It's important that the JavaScript starts executing before all JS has been loaded in, because if there is a hanging script, this would lead to a bad user experience. This means I can't wait for the DOMContentLoaded event to fire. I don't have any flexibility as to where I place the first bit of JavaScript that is dynamically including the script tag.

My question is, is it safe for me to start parsing through the DOM right away, without waiting for the DOMContentLoaded event? If not, is there a way for me to do this without waiting for the DOMContentLoaded event?

like image 604
Justin Meltzer Avatar asked Jan 10 '14 20:01

Justin Meltzer


1 Answers

...JavaScript in the HEAD ... dynamically inserts an asynchronously loading script tag before the last script on the page...

I'm assuming the loader script is inline, meaning that the highlighted bit actually refers to the "current" script element i.e. the loader. This happens since only the html preceding the loader script tag has been parsed and interpreted, so the inserted script tag is actually still in the head and not at the bottom of the page. So the target script is limited to performing DOM operations on preceding elements only, unless you wrap the code into a DOM ready callback... which is what you're trying to avoid in the first place!

Basically you want to load all html so that the page is visible/scannable, start loading images/stylesheets (which occurs in non-blocking threads) and then load any javascript. One approach is to put your target script at the bottom of the page, just pick their order correctly (interactivity first, enhancements second, third party analytics/social media integration/anything else super-heavy last) and adjust for your needs. Technically it still blocks the page load, but there are only scripts left at the bottom of the page anyway (and since they are at the bottom, you would be able to directly manipulate DOM as soon as they're loaded, minus some IE7 quirks).

There is a relevant rant/overview I like to link to that provides decent examples and some timing trivia on use and abuse of DOM ready callbacks, as well as the "other side of the story" on why stellar performance could be of lower value than a sane dependency management framework. The subject of latter is far too broad to be exhausted in one answer, but something like requirejs documentation should give you a fair idea of how the pattern works.

Perhaps another pattern for to consider is building an SPA - single page application which leverages asynchronous access to content chunks rather than the "traditional" navigating between complete pages. The pattern comes with an underestimated but rather significant performance benefit from not having to parse and re-execute shared javascript on every page, which would also address your (valid) concern about third-party js performance. After all, just a good caching policy would do wonders for loading time, but poor javascript code or massive frameworks' execution overhead remains.

Update: Figured this out. With your specific scenario in mind (i.e. no control over markup per se, and wanting to be the last script to execute), you should wrap the insertion of the async script element into DOM into a 0ms setTimeout callback:

setTimeout(function(){

    //the rest is how GA operates
    var targetScript = document.createElement('script');
    targetScript.type = 'text/javascript';
    targetScript.async = true;
    targetScript.src = 'target.js';

    var s = document.getElementsByTagName('script')[0];
    s.parentNode.insertBefore(targetScript, s);

}, 0);

Due to the single-threaded nature of the environment, js setTimeout callback is basically added to a queue for 0ms-delayed execution as soon as the thread is no longer busy (more thorough explanation here). So the browser isn't even aware of the need to load, let alone execute, the target script until after all "higher priority" code has been completed! And since DOM is operational when the script tag is being added, you will not have to check for it explicitly in the target script itself (which is handy for when it's loaded "instantly" from cache).

like image 114
Oleg Avatar answered Oct 13 '22 10:10

Oleg