How to preserve script execution order with defer and inline scripts? - javascript

Is there anyway to preserve execution order of scripts that are a mix of either 'deferred' or inline ?
For eg. consider the following scenario -
<head>
<script src="/polyfills.js" />
<script>
// Small inline script that needs polyfills to work
</script>
<script src="/feature1.js" defer>
<script src="/feature2.js" defer>
</head>
My goal is to make all the scripts have defer behaviour and maintain execution order. However, here, I cannot add defer to the polyfills script as doing so will break the inline script.
Expected execution order
polyfills (defer) => inline-script (how?) => feature1 => feature2
The inline script is a tiny code fragment, and not worth wasting a request over.
Could I for example write a function that would wrap the inline script and execute if only after polyfills have loaded)?

If you want it to retain the order of a sandwiched inline script, then, with regard to deferring, I think you are stuffed.
an inline script won't defer, therefore loses its order with regard to deferred "before" and "after" scripts.
you can use the trick from this answer, but a window.onload listener will wait for all deferred scripts, not just those before the sandwiched script (your polyfills). You can't benefit from deferring the "befores", and not the "afters".
If all three src'd scripts are deferred, then there's no naturally-occurring intermediate event (after the polyfills but before the features) on which to trigger a handler - which is what you want.
For the record, here's how to delay an inline script until all deferred scripts have loaded, which will possibly offer some benefit but no as much as you were hoping for.
<head>
<script src="/polyfills.js" defer></script>
<script src="/feature1.js" defer></script>
<script src="/feature2.js" defer></script>
<script>
window.addEventListener('load', function() {
// Small inline script that needs polyfills to work
});
</script>
</head>

As defer attribute works only with external scripts tag with src. Here is what you can do mimic defer for inline scripts. Use DOMContentLoaded event.
<script src="/polyfills.js" defer></script>
<script src="/feature1.js" defer></script>
<script src="/feature2.js" defer></script>
<script>
document.addEventListener("DOMContentLoaded", function() {
// Your inline scripts which uses methods from polyfills.js.
});
</script>
This is because, DOMContentLoaded event fires after defer attributed scripts are completely loaded. You might not have to wait for 'load' event.
This is the closest you could do.

According to this answer it's technically possible, as long as you're willing to base64-encode your script and set it as a data source. This will be a nightmare to debug if something goes wrong, but might be your only shot if you have a small inline fragment that must be included in the proper deferred order.

Related

Do modules prevent the need to use the DOMContentLoaded listener?

document.addEventListener('DOMContentLoaded', () => {
});
I read that this event listener made sure, for regular scripts, that the JS wasn't going to reference nodes that hadn't been loaded yet. The content executes after DOMContentLoaded has been fired).
I've also read that a module is executed before DOMContentLoaded is fired (due to the defer attribute it has built in).
The modules I've used seem to not need the DOMContentLoaded listener. Can I confirm the DOMContentLoaded listener isn't needed by them to access nodes correctly?
Also, I can't think of how to test this so I'm asking here. If you know how I could, please do share!
I think this article should clear things for you, it has great pictures https://flaviocopes.com/javascript-async-defer/#the-position-matters
When defer is present, it specifies that the script is executed when the page has finished parsing, therefore you can guarantee that the script gets access to the nodes without DOMContentLoaded
When using defer, the scripts will indeed execute after the page has been fully downloaded in the order that they appeared.
You can see here a schema representing the behavior.
<script>
<script defer>
In any case, scripts are always executed before DOMContentLoaded, you can test that theory here :
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.5.1/jquery.min.js" integrity="sha512-bLT0Qm9VnAYZDflyKcBaQ2gg0hSYNQrJ8RilYldYQ1FxQYoCLtUjuuRuZo+fjqhx/qtq/1itJ0C2ejDxltZVFg==" crossorigin="anonymous"></script>
<script type="text/javascript">
document.addEventListener('DOMContentLoaded', () => {
alert("Alert from script")
});
$(document).ready(function() {
alert("Alert from jQuery")
});
</script>
<!-- Alerts "Alert from defer -->
<script defer="defer" type="text/javascript" src="https://pastebin.com/raw.php?i=5tF5s4mB"></script>
And unless you remove the alert from the ready state, all scripts will execute before them, otherwise they get executed in the order they appear in the DOM.
So you can be sure that all code inside DOMContentLoaded will be able to access the fully loaded DOM.
On an end note, do watch out of defer's compatibility across all browsers.
Script attributes async and defer, don’t block DOMContentLoaded. JavaScript modules behave like defer, they don’t block it too.
This can be tested using type="module"
<script type="module">
alert("Alert from script")
</script>
<script type="text/javascript">
alert("Page loaded")
</script>
As you can see, the page loads before executing any modules, no matter the order.

CSS and deferred JS execution order

For example, I have a webpage with such structure in <head>, which is pretty common i guess:
<link rel="stylesheet" href="styles.css"> // here goes big css bundle
<script defer src="main.js"></script> // relatively small javascript bundle with defer attribute
And there is <span id="element"></span> on the page.
CSS bundle contains #element { display: none; }.
JS bundle contains (using jquery here):
$(document).ready(() => {
console.log($('#element').css('display'));
});
The result will be different from time to time. Sometimes JS executes earlier than CSS and the result is 'inline', sometimes JS executes later and the result is 'none' as I want it to be.
I want my JS bundle to be non-blocking so I use deffered attribute. I am not able to simply put my JS bundle in the end of a page because I use turbolinks and it doesn't allow me to do it.
window:load is not a best option too, because it will be fired when not only css but all resources will be downloaded including images.
So I want JS to be not-blocking and be executed after CSS to get consistent and predictable results. Maybe there is something I can do?
One option is to add a load event handler for the link element which will then insert the script into the head. Since the script is dynamically added, it will automatically be async. Therefore, it would be non-blocking and executed after the CSS is loaded.
<link id="stylesheet" rel="stylesheet" href="styles.css">
<script>
var link = document.getElementById('stylesheet');
link.addEventListener('load', function () {
var script = document.createElement('script');
script.src = 'main.js';
document.head.appendChild(script);
});
</script>
However, this could cause you a problem. If the stylesheet is cached, then it might not emit a load event (because it's already loaded). For cases like that, you could try checking for link.sheet.cssRules.
Load events on <link> elements seem to historically be a troublesome issue, so I don't know how well this will work.
Here is a CodePen demonstrating the JS loading with a check for link.sheet.cssRules. It currently works for me in Chrome, FireFox, and Edge.
I found another solution. You can just add a script without src attribute to the <head> with some code, an empty comment for example: <script>//</script>
And that's it. Now all the scripts, even deferred, will wait for styles to apply.
I'm not sure how it works, but I think deferred scripts are queued after a script without src which by standard must wait for css to apply.

Render blocking defer vs moving script at bottom

I assume moving script at bottom is same as using defer or async attribute. Since defer and async are not fully legacy browser compliant, I gone with loading script at the bottom of the page.
<html>
<body>
<!-- whole block of html -->
<script type='text/javascript' src='app.js'></script>
</body>
</html>
Before doing this, I ran performance benchmark tools like GTmetrix and Google PageSpeed insight. Both shown 'render blocking' parameter as the main problem. I am bit confused now, as even after I moving these scripts at the bottom to allow content/html to load first; these tools still report render blocking as a main problem.
I did look at the other StackOverflow posts highlighting that though scripts loaded at the bottom has to have 'defer' attribute.
I have several questions:
is above true?
are these tools specifically look for 'defer' or 'async' attribute?
if I have to give a fallback w.r.t defer ( specifically for IE browsers), Do I need to use conditional statements to load non-defered scripts for IE?
Kindly suggest the best approach. Thank you in advance.
Yes, the scripts loaded even at the bottom has to have defere attribute, if it's possible in your site design and requirements
no, those tools looks for the completion of parsing
depends on the version of IE that you want to support, they will have different recommendations
Now explaining simple script, defer and async a bit, to help you understand the reasons:
Script
Simple <script> tag will stop the parsing at that point, until the script download and executes.
Async
If you will use async, then the script will not stop parsing for download as it will continue downloading in parallel with the rest of the html content. However, the script will stop the parsing for execution and only then the parsing of html will continue or complete
Defer
If you use defer, the script will not halt the parsing of html data for downloading or executing the script. So it's sort of best way to avoid any time addition to the loading time of the web page.
Please note, defer is good for reducing parsing time of html, however not always best or appropriate in every webdesign flow, so be careful when you use it.
Instead of async, maybe something like this (thanks #guest271314 for the idea)
<!DOCTYPE html>
<html>
<body>
<!-- whole block of html -->
<!-- inline scripts can't have async -->
<script type='text/javascript'>
function addScript(url){
document.open();
document.write("<scrip" + "t src = \"" + url + "\"></scr" + "ipt>");//weird quotes to avoid confusing the HTML parser
document.close();
}
//add your app.js last to ensure all libraries are loaded
addScript("jquery.js");
addScript("lodash.js");
addScript("app.js");
</script>
</body>
</html>
Is this what you wanted? You can add the async or defer attributes on the document.write call if you want.
According to HTML Spec 1.1 The script block in the html page would block the rendering of the page till the javascript file in the url is downloaded and processed.
Adding Script at the end of the page : allow the browser to continue with the page rendering and hence the user will be able to see the page rendering asap.
[Preferred] Adding defer to the script tag : promises the browser that the script does not contain any document.write or document altering code in it and hence allows it to continue rendering.
As previous thread may be useful to you
Is it necessary to put scripts at the bottom of a page when using the "defer" attribute?
Why should the scripts mentioned at last must have defer attribute?
Well, the answer is that by adding defer to last script you are actually reducing the number of critical resources that needs to be loaded before the page is painted which reduces the critical rendering path.
Yes, you are correct by the time you reach the last DOM is parsed but browser has not yet started painting the DOM and hence domContentLoadedEvent is blocked until it finishes the paint and render activity.
By adding async/defer we are telling the browser that the resource is not critical for rendering and it can be loaded and executed after dom content has loaded.
This will trigger the domContentLoaded event earlier and the sooner the domContentLoaded event fires, the sooner other application logic can begin executing.
Refer the google link below it clearly demonstrate the concept.
https://developers.google.com/web/fundamentals/performance/critical-rendering-path/analyzing-crp

Improve page speed by async and defer attribute

I need page speed improvement in my webpage.I read a lot about using async and defer attribute for improve initial page speed.All the js scripts are defined just above the </body> tag.Please suggest how effectively use these attributes in my page?
<html>
<head>
<link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap.min.css">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.5.0/css/font-awesome.min.css">
<link rel="stylesheet" href="/static/css/style/mystyle.css">
</head>
<body>
<!--HTML content-->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.10/angular.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.10/angular-touch.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.10/angular-cookies.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular-ui-bootstrap/0.11.0/ui-bootstrap-tpls.min.js"></script>
<script src = "https://ajax.googleapis.com/ajax/libs/angularjs/1.2.0/angular-route.min.js"></script>
google analytics script
</body>
</html>
To use defer:
<script src="path" defer="defer"></script>
To use async:
<script src="path" async="async"></script>
When to use defer/async?
If you need your scripts to be loaded asynchronously ie. while loading the html and css the script with asyc attribute will fore the browser to load them in background ie. they will be loaded while other things work.
And if you need your scripts to be loaded only after fully loaded the html and css then you may use defer attribute.
So, you need to be careful to use this technique because of these attributes some javascript code may not work as you wish it should have to be.
How to use them effectively?
I do not suggest you to use async because it may load any script in any order because of the filesize and/or scripts (as it loads asynchronously) so your functionality would be hampered.
So, just use the defer to fulfill the requirement as per the google page speed which will load the scripts as you wish it should have in that order.
Although, using defer, you may have impact on your website because you may have called some scripts should have run before the document is ready. In that case, you should not follow the instruction of the google page speed so let it be how it was.
Finally, it depends upon you and your scripts.
You shouldn't change anything or use defer for all.
Bootstrap requires jQuery to be loaded (although you're not really loading Bootstrap here, but some Angular-Bootstrap template). So you cannot use async to load any of these resources. Additionally you're loading a lot of Angular resources which depend upon the main angular.min.js resource, so you can't use async here either.
You could use defer for all, but the page won't render faster. The only difference is that the scripts will be executed once they're all loaded, rather than one by one (executed once they're downloaded), but that doesn't change anything regarding page load time.

Quick question about javascript (body-tag)

Does the page load faster if i use the javascript before the </body> tag? Example:
<body>
balbllb content
<script type="text/javascript" src="jQuery.js"></script>
<script type="text/javascript">
$(function(){
});
</script>
</body>
The page will still load in the same amount of time, but it might be perceived as loading faster (i.e. you might see DOM element(s) appearing quicker).
If it was me, I would leave your jQuery.js reference in the <head>, and keep your custom stuff before the end of <body>.
I don't know whether it loads faster (I would be surprised) but in this case you no longer need to wrap your code in a $(document).ready as at that moment the document will be ready to be manipulated:
<body>
balbllb content
<script type="text/javascript" src="jQuery.js"></script>
<script type="text/javascript">
// directly manipulate the DOM here
</script>
</body>
Its not about anything happening faster. Its the order in which things happen. Putting scripts at the bottom (right before the closing body tag) makes it so the rest of your content loads before loading the scripts, making it appear that its loading faster.
The total page load time will be the same. But the page will be perceived as loading faster since it will appear to the user faster. The "perception of loading faster" is not a conjecture, it is a fact, proven many times by psychologists.
Remember that if you load your JS libraries at the bottom of the page (as you should), then any dependent scripts must follow the libraries at the bottom.

Categories