Upon defer attribute MDN says:
This Boolean attribute is set to indicate to a browser that the script is meant to be executed after the document has been parsed, but before firing DOMContentLoaded. The defer attribute should only be used on external scripts.
On DOMContentLoaded
MDN also says:
The DOMContentLoaded event is fired when the initial HTML document has been completely loaded and parsed, without waiting for stylesheets...
So DOMContentLoaded
is fired before CSSOM
is ready. This means deferred scripts are executed before CSSOM
is ready. But if that's true the scrips must not be able to get correct CSS property values and must not apply CSS correctly. But it's not true, we know all deferred scripts work well.
P.S: Please note that google says that CSSOM is built before executing any inline javascript
But Google is technically incorrect. Inline JavaScript gets executed before CSSOM is ready. And from my tests, I found that MDN is correct and if js files(both deferred and non-deferred) are downloaded before CSS files(or js is inline) then js is executed before CSSOM is ready. So js might handle styles incorrectly. To avoid that we need a forced reflow before all js logic.
So if a user visits our website with all js required already cached and CSS not cached OR js gets downloaded before CSS then (s)he might see an incorrectly rendered page. To avoid this we should add force reflow in all our websites' js files.
DOMContentLoaded does not wait for stylesheets to load, however deferred scripts do wait for stylesheets, and DOMContentLoaded queues behind deferred scripts. Also, scripts which aren't deferred or async (e.g. <script> ) will wait for already-parsed stylesheets to load.
DOMContentLoaded event gets executed once the basic HTML document is loaded and its parsing has taken place. This event doesn't wait for the completion of the loading of add-ons such as stylesheets, sub-frames and images/pictures.
The defer attribute is a boolean attribute. If the defer attribute is set, it specifies that the script is downloaded in parallel to parsing the page, and executed after the page has finished parsing. Note: The defer attribute is only for external scripts (should only be used if the src attribute is present).
The DOMContentLoaded event fires when parsing of the current page is complete; the load event fires when all files have finished loading from all resources, including ads and images.
I use deferred script loading. There was a lengthy technical explanation from some guy who is a well known website performance guru. He clearly states that deferred is the way to go (for this and that technical reason, backed by all kinds of data and charts, that many people seemed to feel was wide open for debate, re: async).
So I started working with it. Deferred scripts have the advantage of downloading async, but executing in the order presented, which can be a problem with async (e.g. you can load your app bundle before your vendor bundle because you don't control the execution order of async scripts just by saying "in this order").
However, I found out right away that although this solves that problem, this could mean, depending on how you grab your bundles, the CSS bundle isn't loaded. So you can end up with unstyled content, depending on how you set things up. Note that for defer, they also say that you shouldn't be writing to the dom etc. in those scripts (which again makes sense in terms of your documentation).
So it would seem your documentation is correct. The effect is easily reproduced.
How do I get out of it; the most basic way, is like this:
<script src="css.bundle.js"></script> <script src="vendor.bundle.js" defer></script> <script src="angular.bundle.js" defer></script> <script src="app.bundle.js" defer></script>
This makes sure that the css loads in first, so your home page and so on will show up nicely, and also ensures that (although all three are loading async), that app.bundle will execute last, ensuring all other dependencies are in order.
So, you take the absolute bare minimum of CSS required to kick over the app, create that as a bundle, and load it first, before anything. Otherwise you can bundle in your CSS per module/component, and so on.
There's a lot more to this topic and I could probably be doing more, but again (I will try to find the reference), this was overtly recommended by that performance wizard, so I tried it and it seems pretty effective to me.
Edit: Fascinating, while looking for that reference (which I haven't found yet), I went through a handful of "experts" on the subject. The recommendations differ wildly. Some say async is far superior in all regards, some say defer. The jury really seems out on the topic, overall I'd say it probably has more to do with exactly how you build out your scripts than whether one is actually better than the other.
Edit again: Here's some more evidence. I ran a performance analyzer on a stub website using the above simple loading sequence, deliberately making the scripts naive so they'd be visible in a timeline.
Here's an SS of the result: there are four yellow boxes here. The first three are the evaluations of the scripts. The fourth one (when you mouse over it in the tool, this is just the SS remember) is the DOMContentLoaded event (the one with the red corner).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With