I thought I knew how to use 'defer' attribute when referencing external scripts from my HTML pages.
I even thought there is no reason for me NOT to use it. But after a couple of unexpected things I started to research (even here) and I think I'm not 100% sure when it's safe to use it every time I use the script tag.
Is there somewhere a list of known use cases when defer should NOT be used?
The only thing defer does is run your script when the DOM has finished parsing, but before the DOMContentReady event is fired off.
So: if your code does not depend on the DOM (directly, or indirectly through access to document properties that can only be determined once the DOM is done), there is no reason to defer. For example: a utility library that adds a new namespace ComplexNumbers, with a ComplexNumber object type and associated utility functions for performing complex number maths, has no reason to wait for a DOM: it doesn't need to be deferred. Same for a custom websocket library: even if your own use of that library requires performing DOM updates, it does not depend on the DOM and doesn't need defer.
But for any code that tries to access anything related to the DOM: you need to use defer. And yes: you should pretty much have defer on any script that loads as part of the initial page load, and if you did your job right, none of those scripts interfere with each other when they try to touch the various pieces of the DOM they need to work with.
In fact, you should have both defer *and* async, so as not to block the page thread. The exception being if you're loading a type="module" script, in which case you don't get a choice in deferral: it's deferred by default. but it'll still need async.
Related
I'm using polyfill.io to polyfill Promise and fetch for older clients. On their website they recommend using a script loader or their callback to make sure the script has loaded completely before running the modern code:
We recommend the use of the async and defer attributes on
tags that load from the polyfill service, but loading from us in a
non-blocking way means you can't know for certain whether your own
code will execute before or after the polyfills are done loading.
To make sure the polyfills are present before you try to run your own
code, you can attach an onload handler to the https://cdn.polyfill.io
script tag, use a more sophisticated script loader or simply use our
callback argument to evaluate a global callback when the polyfills are
loaded:
However, shouldn't setting defer on both scripts already guarantee that they are loaded async but still in the order in which they appear in the document (unless the browser doesn't support defer)?
<script src="https://cdn.polyfill.io/v2/polyfill.min.js" defer></script>
<script src="modernscript.js" defer></script>
According to MDN documentation defer attribute just defines a point of page loading time when script loading will occur.
From documentation that you've citated it can be seen:
To make sure the polyfills are present before you try to run your own
code, you can attach an onload handler to the https://cdn.polyfill.io
script tag
Since (as pointed into comments to this answer) it can't be clearly seen if defer scripts will be executed (1, 2) and taking in mind possible browser implementation differences - it may be not the best idea to rely on such behavior.
So better way would be either:
to use some script loader (RequireJS for example)
to add proposed onload handler to first <script> tag and create dynamic <script> tag for loading your code inside this handler
to bundle your code together with Promise polyfill (manually or using bundler like webpack) and load as single bundle.
UPDATE: As pointed by #PeterHerdenborg in comment - MDN document now clearly states that:
Scripts with the defer attribute will execute in the order in which they appear in the document.
The Google Maps javascript does some heavy DOM manipulation. Even so, the fine docs suggest to load it with the defer flag:
<script src="https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY&callback=initMap" async defer></script>
Why would the defer flag be suggested for a script that performs DOM manipulations? I ask to learn both about the defer flag and to learn about the Google Maps API as I seem to have a misunderstanding about what one of them is doing.
Normally, a script tag tells the browser to stop parsing the HTML, fetch the script, run it, and only then continue parsing the HTML. This is because the script code may use document.write to output to the HTML token stream.
async and defer are both mechanisms for telling the browser that it's okay to go ahead and keep parsing the HTML in parallel with downloading the script file, and to run the script file later, not right away.
They slightly different, though; this diagram from the script section of the WHAT-WG version of the HTML spec is useful for envisioning the differences:
Full details in the linked spec above, but in brief, for "classic" scripts (the kind you're used to; but module scripts are coming soon!):
Both async and defer allow the parsing of the HTML to continue without waiting for the script to download.
defer will make the browser wait to execute the script until the parsing is complete.
async will only make the browser wait until the script download is complete, which means it may run the script either before parsing is complete or afterward, depending on when download finishes (and remember it could come from cache).
If async is present and supported by the browser, it takes precedence over defer.
async scripts may be run in any order, regardless of the order in which they appear in the HTML.
defer scripts will be run in the order they appear in the HTML, once parsing is complete.
async and defer are well-supported in even semi-modern browsers, but are not properly supported in IE9 and earlier, see here and here.
Why would the defer flag be suggested for a script that performs DOM manipulations?
Two reasons:
It allows the parsing to continue while the script is downloaded, and
It means the script isn't run until parsing is complete.
If you didn't use defer and you placed your script tags non-optimally, using defer helps the API script behave properly by letting the browser finish building the DOM before the script tries to manipulate it.
A lot of people still put script tags in the head section of the document, even though that's usually the worst place to put them unless you use defer (or async). In most cases, the best place (unless you have a reason to do something else) is at the very end, just before the closing </body> tag, so that A) Your site renders quickly, without waiting for scripts; and B) The DOM is fully built before you try to manipulate it. Recommending defer may be saving them support hassles from people putting their script tags too early in the HTML.
The google maps examples use both async and defer flags.
The async flag allows the script to load in parallel to the DOM
parsing, and to execute as soon as the API is ready.
The defer flag allows the script to load in parallel to the DOM
parsing, but guarantees that the script will not execute until the
DOM is finished parsing.
async is supported by modern HTML5 browsers, while defer support is universal. When the tags are used together, defer is just a fallback for older browsers, and will be ignored if async is supported.
In these simple examples, either async or defer will work, though neither are necessary. In this case it's for performance only.
Refs:
Speed up Google Maps(and everything else) with async & defer
async vs defer attributes - Growing with the Web
Is there any advantages of loading a script using HTMLScriptElement instead of loading it by just including it in the DOM?
Maybe for instance it would be easier to keep things clean in the DOM and also hide the scripts (make them less obvious)?
Didn't find any sources on this, that's why I am asking.
Normally, you would just include script tags directly in the HTML document. Unless you use the async attribute, subsequent scripts will won't load until the previous ones so you can safely rely on any dependencies to be available.
You could use the HTMLScriptElement interface programmatically to load scripts, if you wanted to keep your HTML cleaner. However, then you'd have to manually create onload and onerror handlers to asynchronously wait for the script(s) to load. This would get messy and complicated unless you build an abstraction around it. And then you're doing something done many times before, see RequireJS, SystemJS et al.
So, wanting to keep your HTML clean of script tags is a reasonable ambition, but you're probably best off looking into an off-the-shelf script loader to do that rather than rolling your own.
There is not really a choice here. HTMLScriptElement is the "interface" exposed by all HTML <script> nodes.
One creates script nodes by calling document.createElement('script') or by passing a <script>...</script> tag string through the HTML parser (this can happen in a variety of ways: from parsing a complete HTML document to setting the innerHTML of an existing element.) When a <script> element is created in an HTML document, HTMLScriptElement is in its prototype chain. Therefore, all properties and methods on HTMLScriptElement are accessible to the <script> element.
HTMLScriptElement is not a constructor function, however. This can be seen by attempting to invoke new HTMLScriptElement(), which throws an Illegal constructor TypeError.
All this is to say that your question does not really make sense, since one cannot load "a script using HTMLScriptElement instead of loading it by just including it in the DOM".
Despite the continued adoption of the script element's async attribute, there is continued advice to place script at the bottom of the document without the async attribute. If document.write is avoided, are there any possible pitfalls when using async?
For example, if I load a script that I wrap in jQuery's $(document).ready(...), is there any chance that I might experience any negative effects when adding the async attribute? Can I reliably specify async on all such scripts?
If your scripts rely on being run in a particular order you cannot use async. This typically means that they rely on each other.
A recent example of this that happened to me was using Google's prettyprint syntax highlighting library. You include the library and then make a call to prettyPrint() to apply the syntax highlighting to relevant blocks.
<script src='http://cdnjs.cloudflare.com/ajax/libs/prettify/r298/prettify.js' type='text/javascript'></script>
<script type='text/javascript'>prettyPrint();</script>
So if all your scripts are wrapped in a $(document).ready then they are most likely perfectly fine to go with async. The question you should be asking though is whether you can combine all your files so you only need to make one request.
The defer attribute is similar to async but waits until the HTML is parsed and then runs the scripts in the same order that they appear in your HTML file. This would work in the above situation provided the call the prettyPrint() was external instead of inline as I don't believe it applies to inline scripts.
My objective here is to load scripts asynchronously when the browser supports defer or async.
If the browser supports neither I don't care about asynchronous loading (not my bad).
I want to make sure that any script is only executed when the prerequisites for it are fulfilled e.g. jQuery loaded.
I want to load my scripts while other scripts are being loaded (only if the browser supports defer or async).
I want to do this using only the browsers' API. I don't want the browser to load any reliable (or not) scripts that do that for me no matter how small they are.
This must work with IE8+, Gecko v.1.9.1+ (e.g. firefox 3.5.* or firefox 9.0+), webkit (e.g. chrome), presto (e.g. Opera). For the ones I didn't mention the version, I mean the latest stable version.
If possible I don't want any non easy scripts. I just need something simple to do this job. This means:
If possible, I don't want stuff like AJAX calls or fancy Objects with some methods to do some workarounds like I've seen in other pages. Those are to force async loading of the script in browsers that do not support async or defer
I repeat: I don't need any fancy things to make a script asynchronous. If the browser does not support defer or async I don't care. I just care is that the script is loaded so that each part is executed after its prerequisites are met and use async or defer if the browser supports it.
First, using a library such as jQuery makes this whole process infinitely easier, and reliable across browsers. It may increase the download size of your pages (by a very small amount) but the speed gained by efficient script loading/executing will nearly always outweigh that.
Regarding script async and defer attributes:
async="async": on a script tag is not supported at all by IE8/9, script executes immediately (which is ok according to your question).
defer="defer": on a script tag will begin loading after everything in the order the defer scripts appear in the HTML, prior to DOM Ready. BUT, on Firefox, scripts will often execute AFTER dom ready. This difference makes defer unreliable as a means of ensuring that scripts are loaded before executing functions after dom ready.
General Guidelines when not using jQuery:
If a script has downstream dependancies you have to place it as a standard script tag at the end of the body tag and have your inline tags all execute after document ready. Otherwise there is no guarantee that the script will be executed prior to the execution of the dependencies. Firefox is the main issue here, a "defer" script may not have finished even after DOM ready.
If a script has no downstream dependnacies, then place it at the end of the body tag, and use async="async" attribute on the script tag. IE will render it immediately and the others will render it when they receive it.
General Guidelines when using jQuery:
Place only jQuery in your <head>.
Execute all other scripts as $.getScript().
If a script needs to execute ASAP (such as analytics) use a $.getScript at the top of the body (this will be a non-blocking request, but will process as soon as the client receives the file).
If a script can wait till DOM ready, wrap the $.getScript() call in $(function() {});
If a script has many downstream dependancies, have each one register itself to the callback function for a specific script.
$(function() {
$.getScript("script.js", function() {
for(var i = 0; i < myCallbacks.length;i++) {
myCallbacks[i]();
}
});
});