Are deferred scripts executed before DOMContentLoaded event?
Solution 1
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).
Solution 2
I didn't really read the spec though. The following are based on actual behaviors of Chrome (observed on Chromium 68, Ubuntu). Behaviors may vary among browsers, if they were just undefined in specifications. For example in 2010 scripts don't always wait for proceeding stylesheets. I assume agreements had been achieved and behaviors had been standardized over the years.
The defer
scripts are executed after domInteractive
, before domContentLoaded
; it's sequential.
domInteractive
and domContentLoaded
are two timestamps which could be viewed in Chrome devtools' Performance (previously Timeline) tab. Probably also in other similar tools, but I haven't tried.
domInteractive
is the point when HTML parsing and initial DOM construction are finished (and all "sync" scripts have finished executing). document.readyState
changes from 'loading'
to 'interactive'
; a readystatechange
event fires on document
accordingly.
All defer
scripts are executed in their appearing order. Then comes domContentLoaded
, a DOMContentLoaded
event fires on document
.
DOM & CSSOM construction don't rely on each other; but sync scripts may introduce dependencies.
Each sync script, internal or external, waits for preceding stylesheets to be parsed (of course, after fetched).
Yes, sync scripts are not blocked by subsequent stylesheets. MDN and Google and other articles say "scripts depend on CSSOM to be ready"; they (probably) didn't mention that only preceding parts are depended.
P.S: Please not that google says that CSSOM is build before executing any inline javscript
Google didn't say that (at least, as of the time I read this article).
On the contrary, before one sync script is fetched (if external) and executed, any code following it, HTML, stylesheets or other scripts, can't be parsed/executed/constructed. They block anything subsequent to them.
So, in specific cases, eg. without sync scripts, DOMContentLoaded
event may fire before or after CSSOM is ready. That's what MDN means by saying "without waiting for stylesheets".
defer
/async
scripts don't care about stylesheets at all.
Different from sync scripts, defer
/async
scripts don't wait for preceding stylesheets, and don't block subsequent stylesheets/scripts either. They are removed from those "dependency chains" completely. You can't rely on any proceeding stylesheets to have been parsed.
The differences between defer
/async
:
- as stated above,
defer
scripts have predictable execution time; the DOM has been ready. They are also promised to execute in order.Update:
defer
scripts are added to the end of the list, says W3C's spec (the 20th item)
(also in WHATWG's spec) -
async
scripts have no promise on execution order; eachasync
script would be "queued to execute" as soon as it is fetched; once the render process is idle, they are executed. (To be exact, different types of resources have different priorities. The spec provides precious requirements)
These should well explain hinok's two examples, the former async
(from Google) and the latter defer
。
I don't have much experience on working with CSSOM at page loading (I do operate on DOM at page loading, though), so I can't provide reliable advises. It seems "load
event on window
" or "force reflow early" might work.
Solution 3
DOMContentLoaded
can be fired before CSSOM, source
The domContentLoaded event fires shortly after the HTML is parsed; the browser knows not to block on JavaScript and since there are no other parser blocking scripts the CSSOM construction can also proceed in parallel.
Article on Google Developer describes async
instead of defer
but in the case of your question, it doesn't change anything because based on Steve Sourders article on perfplanet
DEFER scripts execute after DOM Interactive.
and his comment under his article
[...] the spec says that DEFER scripts run after
domInteractive
but beforedomContentLoaded
.
You can make your own experiment, look below for code using defer
and timeline
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>JS Bin</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.0.0-alpha.6/css/bootstrap.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/angular-material/1.1.3/angular-material.css">
</head>
<body>
<h1>App</h1>
<script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.0.0-alpha.6/js/bootstrap.js" defer></script>
</body>
</html>
user31782
http://crimewiki.in/ https://stackoverflow.com/questions/59729820/are-functions-defined-in-a-class-are-by-default-static-if-yes-then-why#59729820 How to permanently typecast class properties in PHP? https://stackoverflow.com/questions/59804066/what-are-the-limitations-of-inheriting-from-abstract-class-that-are-remedied-by https://stackoverflow.com/questions/59954412/in-javascript-can-we-return-non-objects-by-reference https://stackoverflow.com/questions/59668056/what-are-parent-and-child-methods-in-php-and-how-can-we-override-that-parent-met https://stackoverflow.com/questions/59804066/what-are-the-particular-features-of-inheriting-from-abstract-class-that-are-not https://stackoverflow.com/questions/59729820/why-are-non-static-functions-assessible-without-object-initialization-in-php7
Updated on June 04, 2022Comments
-
user31782 about 2 years
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 beforeCSSOM
is ready. This means deferred scripts are executed beforeCSSOM
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.- So is MDN documentation technically incorrect?
- Where can I find the official documentation of DOMContentLoaded`? I searched in https://dom.spec.whatwg.org/ but couldn't find it.
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.
-
user31782 over 7 yearsI performed the same tests with many heavy stylesheets and only inline javascript.
DOMContentLoaded
fired after downloading the stylesheets and before applying the stylesheets(CSSOM ready). This means by without waiting for stylesheets... MDN doesn't mean without downloading stylesheets; it means without applying styelsheets. -
Dawid Karabin over 7 yearsI'm not sure if I understood you correctly but inline scripts should be executed after CSSOM, not before. What if we replace our external script with an inline script? Even if the script is inlined directly into the page, the browser can't execute it until the CSSOM is constructed. In short, inlined JavaScript is also parser blocking.
-
user31782 over 7 yearsHinok, thats exactly what I observed. inline javscript executes before CSSOM is ready. I asked the question here stackoverflow.com/q/42891628/3429430
-
user31782 over 7 yearsSo if we have small jquery inline or external in our webpage and many heavy css files in head. And browser takes more time than jquery to load css files the jquery ready and native domcontentloaded is fired before CSSOM is ready. To prevent this now I will add
document.body.offSet
to all my websites at the beginning of ready function to cause a reflow to fire CSSOM ready. -
user31782 over 7 yearsThe goggle's image developers.google.com/web/fundamentals/performance/… is technically incorrect as javscript tuns before CSSOM, even if we add jquery-ready/domready or not.
-
user31782 over 7 yearsIn your second App image the
DOMContentLoaded
fires on47ms
butangular-material.css
finishes downloading at120ms
. So in your resultDOMContentLoaded
fires before completing downloading of css resources. I ran similar tests many time but always I getDOMContentLoaded
fires after downloading css resources and may fire before building CSSOM. -
user31782 over 7 yearsWhat does
css.bundle.js
do? Does omittingdefer
on it force browser to first downloadcss resources
and thencss.bundle.js
(till now CSSOM is ready) and are all the deferred scripts supposed to download aftercss.bundle.js
? -
Tim Consolazio over 7 yearsCSS Bundle is a bundled version of your CSS files (I use webpack). The idea is get all those script/css tags out of your index page and bundle them intelligently so that you have precise control over how and when they load. In this sample, you'd assume css.bundle has styles used to style the homepage while the other scripts are loading. Scripts without defer or async tag will download and eval in the order you place them. So yes, the css bundle here will load in first, then everything else will load in deferred (so async), but those scripts will eval (execute) in the order specified.
-
user31782 over 7 yearsIf I understand defer correctly.
defer
oncss.bundle
wouldn't change the execution order. I haven't used webpack. But I guesscss.bundle
is supposed to addstyle
tags to head and append the styles in there. My question is if this is whatcss.bundle
does then supposingvendor.bundle.js
downloads beforecss.bundle.js
, wouldvendor.bundle
excetute before CSSOM is ready? -
Tim Consolazio over 7 yearsIf components in the vendor bundle require stylesheet data, and your stylesheet data is all in that bundle that hasn't executed yet, you'll see unstyled content (or worse depending on how much logic is in your css). So you want to make sure that doesn't happen. I see people put their homepage styles inline to deal with this (you put everything else in the css bundle).
-
user31782 over 7 yearsPutting all styles inline(which is not always possible, e.g. I am using bootstrap cdn) will solve the problem only when I have at least one non-inline js file and that too is not cached on user agent. Otherwise I have force reflow as a perfect solution.
-
Flimm over 5 yearsI'm not sure this sentence is true in all browsers: "So I started working with it. Deferred scripts have the advantage of downloading async, but executing in the order presented". Does the spec require this?
-
Flimm over 5 yearsYou said: "All defer scripts are executed in their appearing order. " I'm not sure this is true in all browsers. Does the spec require this?
-
Admin over 5 years@Flimm Answser updated. The
defer
scripts are added to the end of the list, so they are executed in order.