

Script-injected "async scripts" considered harmful - igrigorik
https://www.igvita.com/2015/05/20/script-injected-async-scripts-considered-harmful/

======
svmegatron
+1 for periodic re-examination of best practices.

~~~
pjmlp
The problem is to make them visible, specially to beginners.

~~~
bluejellybean
I agree with this, I had no idea about the "good" practice in the first
example. Is there any decent place that I can get truly up-to-date practices?

~~~
benjaminpv
You might argue that the HTML 5 Boilerplate[1] is a good starting place for
learning about best practices. The code & accompanying site contain a lot of
comments that explain _why_ things are the way they are.

Problem is, of course, that the technologies used by a common page nowadays go
far beyond the stuff present in H5B.

[1]: [http://html5boilerplate.com/](http://html5boilerplate.com/)

------
mweibel
Very nice article indeed.

One commonly used variant I missed in the article and I'd be interested what
it's positive/negative aspects are (if there are differences at all):

    
    
        <script>
            var script = document.createElement('script');
            script.src = "http://example.com/awesome-widget.js";
            script.async = true;
            document.getElementsByTagName('head')[0].appendChild(script);
        </script>
    

(Note the additional async param)

~~~
d_j_s
All browsers since FF 3.6 default inserted scripts to async=true

[http://mathiasbynens.be/notes/async-analytics-
snippet](http://mathiasbynens.be/notes/async-analytics-snippet)

------
ajtaylor
Thank for yet another gem of an article. As a mostly backend developer, I
depend a lot on these types of posts to help me keep up with the current
front-end best practices. Anything that is on igvita.com I pretty much absorb
without questioning due to the consistently high quality (and correctness!) of
the posts.

------
mike-cardwell
What if you stick this as the first thing in your body:

    
    
      <iframe src="/file.js" style="display:none">
    

And then at the very end of the body tag:

    
    
      <script src="/file.js"></script>
    

Wont that allow us to start a non-blocking fetch of the script early, but then
execute it later? I assume it wont be fetched twice. Especially if you send
future Expires headers.

~~~
troels
Could affect rendering though, since the browser may treat the js as content
to render. Probably should be benchmarked to make any conclusions.

~~~
eric_h
In my experience, anything with display:none is not rendered by the browser
and doesn't take up any real resources. I've used this trick with infinite
side scrolling slide show type interfaces when I found that beyond about 20
elements, things started slowing to a crawl. Setting offscreen elements to
display:none fixed it right up.

~~~
igrigorik
"Doesn't take up any real resources" is anything but true. An iframe in
particular is basically a new render process, which adds a lot of overhead.
Just because you've set "display:none" on an element doesn't mean it's free.

------
gue5t
What keeps browsers from running the scripts immediately and only blocking
if/when the script actually does access the CSSOM? It seems like an easy
latency win.

~~~
igrigorik
In order to know that you need to execute it - therein is the problem. One
idea we've been kicking around (on Chrome team) is to do "speculative
execution" where we create a snapshot and start executing the script, and
rollback if you start doing anything crazy with the CSSOM/DOM... As you can
imagine, this requires some careful engineering and has its own (long) list of
gotchas. No ETA, but definitely something that's on the table.

------
Terr_
I like Dojo/AMD because when you specify dependencies, you implicitly give an
execution order.

Now, that doesn't necessarily help for this kind of "top level" code, but
wouldn't it be cool if you could say: "Load this <script> tag asynchronously,
but only run it once the following other <script> tags finish"? (You can give
<script> tags HTML IDs just like anything else, right?)

~~~
andrewmunsell
You can kind of do that-- the defer attribute of the script tag behaves
similarly to async in that the scripts can be downloaded without blocking, but
they are supposed execute in the order that they are included in the page
(unlike async).

[http://stackoverflow.com/a/10731231/480033](http://stackoverflow.com/a/10731231/480033)

However, browser implementation isn't consistent so there's no guarantees
really.

------
icambron
I don't understand why the browser treats the script injection case
differently than the blocking script tag case, insofar as blocking on CSSOM is
concerned. Why doesn't the browser download the script right away and then
just hold up executing it while the CSSOM finishes? I can't think of why it
waits to _download_ it.

~~~
igrigorik
To clarify, both script-injected and regular <script> tags block on CSSOM:
there is no difference there. Only the "async" attribute on script (i.e.
<script async>) removes the dependency on CSSOM... which is why you should use
it.

As for why the browser doesn't download the script-injected scripts: they're
hidden inside of the inline script blocks. In order to figure out what you
intend to do inside of that block, the browser would have to execute the
JavaScript. The behavior that you want (download wait for CSSOM) is _exactly_
what you get when you use regular blocking tags - those are discovered by the
preloader and requests are dispatched. That's what the first and second
diagram in the blog post illustrate.

~~~
icambron
Oh I see. It's that it hasn't yet executed the injection itself. Not sure why
I missed that, thanks.

------
ssttoo
Very cool, thanks for revisiting the best practices of yesteryear!

Quick test in FF: seems it blocks either way, but at least schedules the fetch
in the async=true case
[http://i.imgur.com/AmNjhgz.png](http://i.imgur.com/AmNjhgz.png)

------
ejain
I'm using LAB.js, which lets me declare dependencies between scripts, and then
handles sequential or parallel retrieval and execution. Not sure how I could
easily replace this as suggested in the article, unless I merge all js into
one big file?

~~~
d_j_s
I'd assume LAB has this covered.

yepnope will load your script as an image away from the DOM so that it's
already cached by your browser.

------
thathonkey
Do these page performance optimizations check out across the modern browsers?
Chrome and Firefox tend to implement such things differently. Not sure about
Safari, IE11, etc.

------
putlake
Great stuff from Ilya as usual. But this tip is only important when you'd like
Javascript execution to happen as fast as possible. The problem with async is
that JS executes as soon as it is downloaded, and that may be before the page
has fully loaded or the DOM has been built. For cases where your core content
is in HTML and JS is only used for progressive enhancement, won't it be better
to use defer?

Why isn't defer more popular?

~~~
igrigorik
Unfortunately defer is broken in bunch of browsers - doesn't preserve order,
etc. As a result, if you need to preserve order, you're better off with the
async function queueing pattern.

Also, if you need to wait for DOM/CSSOM, then you can still use an async
script and just install an event listener for DomContentLoaded /
DomInteractive / etc., when it loads.

------
robocat
The underlying problem is the loading of the CSS is blocking (blocks DOM as
well).

However, that problem is complex to workaround.

Using the async flag doesn't fix that problem, and the async flag also means
that you need to make sure your scripts can run in any order (which will cause
problems unless you are very careful e.g. use a loader that manages
dependencies asynchronously).

------
radio4fan
Another option is to inject the script:

A. At onDOMContentLoaded and B. Inside a setTimeout(func, 0) block

Then the browser's loading indicator will stop before the script has been
loaded, executed and all its assets loaded.

Useful for social media widgets, etc.

------
dfabulich
I'd say this post is mildly hypocritical. You're using two script-injected
async scripts, one from Google Tag Manager and another from Disqus!

Which is to say: if it were _that_ harmful, then maybe you could try fixing
them yourself and tell us how it goes? It would be great to provide some
advice on how to convert a few popular script-injected async scripts from
third parties (especially Google Analytics) into non-"harmful" ones.

------
Kiro
What's wrong with just putting the script tag before the closing body tag?

~~~
igrigorik
If you put a blocking script at the bottom you're still stuck waiting for
CSSOM. If you put a script-injected tag at the bottom you're also blocked on
CSSOM and your script is not discoverable by the preloader. For more details ,
see the comparison table in the post.

~~~
Kiro
I'm not talking about a script injected tag, just a normal tag which doesn't
block CSSOM. If you put them right before the closing body tag you address
both the CSSOM issue and the DOM blocking since the DOM has already been
processed.

I understand that async also enables quicker execution of the script. What I
don't understand however is why anyone would use script injection when you can
just put the script tag at the bottom of the page to avoid DOM blocking.

What am I missing?

------
drudru11
Great - but what if I need to dynamically load some JS?

~~~
igrigorik
Then you're "stuck" with a script loader. Make sure your script loader is
itself not being blocked on CSSOM, and is not blocking DOM construction.

------
namelezz
Does this mean requirejs being harmful?

~~~
mxxx
Yes.

------
skrebbel
Meta, but if you're going to write an article named "X considered harmful",
then X really has to be as least as harmful as GOTO's prevalence was in 1968.
This article is about a little thing that's current between now and now+3y,
and that doesn't really hurt anybody when done wrong. Coding a script tag the
wrong way is not going to stop the software engineering industry from making
fundamental improvements.

~~~
andyroid
Agreed. The "X considered harmful" meme has really got to stop. There are a
couple of articles/essays in which that expression really makes sense, but
they are few and far between. This one is not one of them. The ones I've read
that actually resonated with me have all been written by authorities within
their field and regarding subjects of importance for software development as a
whole, not niche subjects such as "CSS drop shadows considered harmful", etc.

(A well written article none the less!)

~~~
escape_goat
Especially since the original "X considered harmful" was not formulated by the
author, Edsger Dijkstra. It was an editorial emphasis provided by none other
than Nikalaus Wirth, who had the qualification of having written or having
helped to write a long list of significant programming languages. Dijkstra's
origin title was the much more humble "A Case Against the Goto Statement."
Wirth's formulation provided a gravitas and finality that signalled a strong
endorsement of the essay. The important takeaway lesson, in my mind, is that
NOT EVEN EDSGER DIJKSTRA would have presented a paper with such a pretentious
title. It was the judicious endorsement of a journal's CHIEF EDITOR. And it is
quite literally, at this point, a pretentious title.

------
trhway
considered by whom? Saying "it is my opinion that ..." of course wouldn't
sound that important.

~~~
breischl
The "X considered harmful" construction has a long history in computer
science, going back to Dijkstra's "Go To Statements Considered Harmful" paper.
Wikipedia even has a page on it[1].

It can come off a little arrogant, but he's really just following tradition.

[1] -
[https://en.wikipedia.org/wiki/Considered_harmful](https://en.wikipedia.org/wiki/Considered_harmful)

~~~
nkozyra
And I think, like goto statements, there's a cost/benefit analysis to be done
when making this choice.

