8 ms·
Script-injected "async scripts" considered harmful
- svmegatron 12y ago+1 for periodic re-examination of best practices.
- pjmlp 12y agoThe problem is to make them visible, specially to beginners.
- bluejellybean 12y agoI 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 12y agoYou 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/
- grrowl 12y agoIt's as important for the updated best practice to be taught to novices, and as important for industry leaders (such as Facebook[1]) to update their current code, which reflects the first (outdated best practice) example in the article. [1]: https://developers.facebook.com/docs/javascript/quickstart/v2.0 https://developers.facebook.com/docs/javascript/quickstart/v...
- trhway 12y agoconsidered by whom? Saying "it is my opinion that ..." of course wouldn't sound that important.
- breischl 12y agoThe "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
- username42 12y ago"X considered harmful" articles are harmful articles.
- nkozyra 12y agoAnd I think, like goto statements, there's a cost/benefit analysis to be done when making this choice.
- gue5t 12y agoWhat 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 12y agoIn 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.
- deleted 12y ago[deleted]
- deleted 12y ago[deleted]
- steveklabnik 12y agoYou are reading too much into a title pattern that's used to get eyeballs on the page.
- igrigorik 12y agoYes, and that's what I'm implying. Even if we did speculative execution, that still wouldn't give us the benefits of being preload friendly. We have a better solution that works in all modern (and even old) browsers, and we can now drop the unnecessary baggage of script-injected scripts.
- Kiro 12y agoWhat's wrong with just putting the script tag before the closing body tag?
- mcmillion 12y agoCouple this with the fact that you should already be concatenating and minifying your assets, and this seems like a no-brainer. I suppose you could argue that this approach helps with CDNs, but therein lies another issue of whether or not serving a library from a CDN really buys you anything over just including it with your site's script payload.
- couchand 12y agoFWIW serving libraries from CDN cache buys you and your users and the whole of the internet bandwidth and electricity.
- igrigorik 12y agoIf 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 12y agoI'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?
- deleted 12y ago[deleted]
- Terr_ 12y agoI 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 12y agoYou 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.
- mike-cardwell 12y agoWhat 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 12y agoCould affect rendering though, since the browser may treat the js as content to render. Probably should be benchmarked to make any conclusions.
- eric_h 12y agoIn 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 12y ago"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.
- igrigorik 12y agoIf you want to initiate an early fetch, just put the <script async> tag at the top - you won't block DOM construction or block on CSSOM. If you need ordered execution, put your inline script block above CSS and add your logic there (but please keep it lean).
- MBCook 12y agoThat seems awfully hacky given the availability (and reasonable fallback) of the async option.
- skrebbel 12y agoMeta, 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.
- falcolas 12y ago> that doesn't really hurt anybody Well, it does hurt conversion and retention rates, if it causes the page to be rendered slowly. Given how many sites don't load anything until javascript has executed, this could have a meaningful impact on someone's bottom line. And that does hurt businesses and their employees.
- pekk 12y agoAnd even with the most passionate possible argument, it isn't even comparable to GOTO in 1968.
- falcolas 12y agoPerhaps it's because I wasn't even alive, let alone programming, in 1968, but I'd be very interested to hear how losing millions of dollars is incomparably better than inconveniencing programmers.
- andyroid 12y agoAgreed. 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!)
- 12y ago
- ssttoo 12y agoVery 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
- mweibel 12y agoVery 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 12y agoAll 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
- radio4fan 12y agoJust tried it, and it makes no difference. Still waits for the CSS to load before requesting the script. At least, in Chrome.
- igrigorik 12y agoIt's a noop.
- namelezz 12y agoDoes this mean requirejs being harmful?
- mxxx 12y agoYes.
- icambron 12y agoI 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 12y agoTo 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 12y agoOh I see. It's that it hasn't yet executed the injection itself. Not sure why I missed that, thanks.
- thathonkey 12y agoDo 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.
- ajtaylor 12y agoThank 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.
- dfabulich 12y agoI'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.
- deleted 12y ago[deleted]
- putlake 12y agoGreat 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 12y agoUnfortunately 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.
- ejain 12y agoI'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 12y agoI'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.
- robocat 12y agoThe 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).
- deleted 12y ago[deleted]
- radio4fan 12y agoAnother 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.
- drudru11 12y agoGreat - but what if I need to dynamically load some JS?
- igrigorik 12y agoThen 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.