79 comments

[ 3.1 ms ] story [ 166 ms ] thread
About time. Just do as Google/Facebook do:

    var _script = document.createElement('script');

    _script.src = '//blablabla.bla/bla.js';

    /* _script.async = '' */

    (document.body || document.getElementsByTagName('body')[0])
      .appendChild(_script);
You could append to the head instead (a la Google Analytics universal snippet) with the async attribute set to '' or 'async' or true or !0 or whatever you fancy.

Works for CSS too, though it'd be better to append your link element to the head despite what Google says.

Handy for asynchronously loading fonts without link rel="preload" which is poorly supported.

You could also do this with a single style element in the body containing your @font-face, but that's a crap idea if you want to use Google Fonts, for instance.

Obviously with CSS, it's worth including a regular link element in a noscript in the head.

Why not just use "eval" to load new symbols into Javascript's global namespace?
Not exactly sure why you got downvoted. Perhaps people should actually say why they think eval is a bad idea.

My guess is it's something to do with cross site scripting. I can see advantages to using XMLHttpRequest in conjuction with eval in some cases, such as being able to monitor script download progress with the onprogress event.

The fun thing is that XMLHttpRequest is much better designed as an API. The poor design of the script element objects caused problems when HTML5 tried to standardize it: https://hsivonen.fi/script-execution/
You shouldn't be downvoted by the eval-is-evil folks for this; it's a legitimate question that has nothing to do with the sins of eval[1], since you're giving the page 100% full control anyway.

The answer is actually a bit common-sense after a moment: the difference between injecting a <script> and a eval() is that in the former case the code is in a file on some server, whereas in the latter case it's in a string. To hack up a solution with eval requires converting the file into a string, but to do that you need an XMLHttpRequest and to deal with origin policies and it rapidly explodes into a pointless mess because you're explicitly requesting that the loaded script have the power to interact with the context you're in, even if you're not using this power by boxing it into a context. (For example when you eval() in `function () { var secret = "this string is totally private!"; eval(code) }` that code has a power which no script tag has.)

[1] The sins of eval include ugly metaprogramming where it's not needed like `eval("someObject." + property + ".otherThing")` rather than `someObject[property].otherThing`, and accidental remote code exploits where none was intended like `var json = eval(thing_i_dont_actually_trust)`: whoops, someone passed `(function() { ... }())` instead of JSON and it went straight through my parser.

No need for an XMLHttpRequest when you've got a websocket open to retrieve such scripts. In fact, loading your scripts via a websocket can be faster than loading them via parallel vanilla HTTP requests because the websocket won't have the overhead associated with GETs (no new TCP connections or headers to slow you down).

Furthermore, if you cache said scripts in, say, IndexedDB you can just perform out-of-date checks via the websocket and only re-sync those scripts that have changed on the server resulting in even faster load times.

This works and it's great. How do I know? I implemented such a solution in Gate One a few years ago:

https://github.com/liftoff/GateOne

It also has the added benefit of making embedding a web application into another so much easier. Because you can just load a tiny little script on a page that makes just one connection to an external websocket and can load everything from that in an instant.

eval is not necessarily evil in this case indeed. However, it'll force you to make concessions in your Content Security Policy and probably not very good for caching.
If you're using eval() you can cache scripts wherever you want! I personally prefer IndexedDB but there's nothing stopping you from using localStorage or a even a service worker (with a little bit of cache trickery).

Also, if you use a websocket you can pretty much forget about the content security restrictions. Websockets have no origin restrictions so you can load whatever you want from anywhere.

I don't use eval so maybe I'm wrong but I guess it's easier to debug an error message when it contains both the filename and the line number.
What does the commented line accomplish?

Edit: I am getting upvotes but no answer - it is a reasonable question. Why include it if it is commented out? Does it accomplish something, even though it is in comments?

Edit: answered below!

(comment deleted)
I just meant to indicate that if nothing depends on the script, you can also set the async attribute to prevent even the slightest bit of blocking.

Not really necessary if you append the script to the body, but it's good to do it if you append the script to the head.

Scripts injected via JavaScript are implicitly async so you don't need to specify that (at least from Firefox 4.0 and Opera 15 onwards, all other browsers have always behaved this way).
On modern browsers you don't even need this method. A simple

    <script src="//blablabla.bla/bla.js" async></script>
will get you the same effect with the added advantage that the browser's lookahead parser can detect the script and start downloading it even before the DOM has been constructed.

The problem with both methods, however, is that they will still block the onload event from firing until after the script has finished downloading.

There are only two ways to get around that.

The first is silly in how obvious it is... if you load a script after onload has happened, then it cannot block onload. This has the downside of not using parallelism.

The second method is to load the script in an iframe whose onload event has already fired. It's a bit more involved, and absolutely requires the use of document.write inside the iframe. I've documented the method here, and it works on all browsers: http://www.lognormal.com/blog/2012/12/12/the-script-loader-p...

I'm not sure I understand your point. Isn't the goal of this technique to add somewhat dynamic scripts to the page? Specifically, the page includes "foo" but it basically just bootstraps "bar" and "baz."

That is, if you already know the script you want to load, yes. Just load it. This is for the case where you are loading a script that will, in turn, decide what scripts need loading.

I think that's confused because a lot of people read a blog post from a decade ago about a dynamic script loader and IE6, and have retained the mistaken belief that a script loader is necessary for performance. I regularly encounter websites which wait for a loader script to initialize before starting to request a static set of JS+CSS files, where a simple <script defer> or <script async> would have been both easier and considerably faster.

(That's not to say that dynamic loading isn't a useful tool, only that a non-trivial number of web developers have fossilized impressions of the modern web performance environment)

Well, if you want to conditionally add polyfills it's useful to use the method I showed.

Plus, in my case, there's the misery of having to accommodate IE6 for the requirements of a couple of clients.

upvoted as compensation for still having to deal with IE6 :)
perhaps what we really need is a capabilities attribute on all `script` nodes, so the browser can determine if it needs to be loaded or not.

For example:

    <script src="usertiming-polyfill.js" provides="usertiming"></script>
If the browser supports user timing, it does not load the script. If the browser does not support user timing (or does not support the `provides` attribute), it loads the script.
Kudos for thinking that up. But it has to be said -- what an utterly demented programming environment.

We're 20 years in now with JS/CSS/HTML, and still no light at the end of the tunnel.

Script injection via document.write is bad practice of course, but I'm not sure what Google's rationale is for making this backwards-incompatible move.

"Your site is slow, so we'll break your functionality. There you go, isn't it faster?"

It's not the browser's fault that a badly written page loads slowly. Has the speed competition become so important that browser vendors would rather load a non-functional page than a slow page?

Well, it seems that publishers would prefer working page over a slow page. If all the education efforts about doing the "right thing" aren't working, then take away the bad thing. I bet a lot of publishers will fix their pages, and as consumers we'all benefit, even if there's some short term pain. It's not like the fix is particularly difficult.
You assume this technique is always bad.

It is not. It is a simple way to load scripts from JS with browser forcing serialization of the script exectution.

It should be developer's decision to choose whether he prefers speed or simplicity in a given context.

Pages with adblockers might be considered "nonfunctional" by the author but perfectly fine according to the user. I've long since null-routed google analytics, which speeds up almost all page loads.

Graceful degradation works well enough that you can usually get what you want from a web page even if some of the JS is broken.

> "Your site is slow, so we'll break your functionality. There you go, isn't it faster?"

If your site is unfunctional without JavaScript, it is already broken.

If you use JavaScript for progressive enhancement, then not loading scripts on a slow connexion is a great idea.

I think that's for the author and/or user to decide, not the browser.
The browser is the user agent: literally, it's the user's agent. If he doesn't wish it to do something, he can configure it to do something else, or he can fire it and get a different agent.

And no, it's not for a web site author to decide to require JavaScript when his functionality doesn't require it: if he does that, he's simply wrong.

> it's the user's agent

I completely agree, but if I configure my agent to break some assumption you made in your code then I have no one but myself to blame.

One can break just about any piece of software by messing with the environment in which it runs. It's a shame that we rarely check or document these kinds of dependencies.

You are, of course, free to make assumptions about the user agent and make your page break on some user agents, but that's very unprofessional and gives a bad first impression. Progressive enhancement isn't hard if you design the page right, so most people will get the fancier site while at least having a working minimal page in other cases.

This is really just checking for capabilities before using them and checking return values for errors, which everybody should be doing anyway. Assuming JS (or any browser feature) is available is similar to skipping the check for NULL after calling fopen(3). Skipping error handling is an indicator of shoddy programming.

I'm generally okay with this, but it broke one of my sites and is the second time in recent months that Chrome has made a sweeping, possibly breaking change with its browser with basically zero warning (the other blocking location API requests over non-SSL connections). Not a fan of a browser vendor making these decisions with what AFAICT little consultation with standards bodies.
As the person referenced in the article, I am keen to work out how we make developers aware of these changes. We've had the warning on the console for a while now, and it probably still won't be a little while before it roles out, so it's not like they are happening instantly... But we want to make sure developers are not surprised.
I swear I've never seen a warning for this or the Location API change.. And I have my console open always. Are the warnings only in Canary? I develop in Chrome stable.
Warnings are on all browser versions.. Location was a silly one though, you had to invoke the API to see the error, if you never actaully had devtools open when you called the API then you wouldn't know there was an issue.

There has been a warning about sync 3rd party scripts injected with document.write for quite a while now..

I see it all the time (some of our wonderful ad partners have been slow to drop `document.write`), but they easily become noise.
I was working just days ago on a page which injects a script with `document.write`, console open, in Chrome latest. Never saw a warning.
As the announcement stated, the warnings were only pushed to Stable this month. Less than 30 days does seem like much of a warning.

The initial plan is to execute this intervention when we detect the criteria being met. We started with showing just a warning in the Developer Console in Chrome 53. (Beta was in July 2016. We expect Stable to be available for all users in September 2016.)

Hey, Kinlan. Here's what you do. Once a month, on the same day every month, publish a browser change update. List every known upcoming change, and your best understanding of when it will happen. List every change that has already happened, and when it happened. Include dates for these changes in both Canary and Production releases.

Publish this update to Hacker News on the same day (and ideally, the same time) each month. It will not take too long before every dev in the world will be eagerly awaiting your update.

The key is consistency. If you publish this update 100% reliably (Neither snow nor rain nor heat nor gloom of night...), then it will become the go-to way of hearing about browser issues.

Extra Credit - Get your friends at Mozilla, Microsoft, Apple, etc. to contribute to the update for their respective browsers. If there was one place where ALL browser updates were documented, you will change the world.

The abandonment of backwards compatibility is concerning to me. Especially if the features are still on official standards or work on other browsers. Oftentimes I stumble across very old websites and everything works just fine a decade or more later. I love using web archive especially.

Once I came across an amazing list of old websites from the 90s, when there was a bunch of excitement around genetic algorithms. They had all sorts of little Java applets to simulate evolution of virtual organisms and pictures. A month later I wanted to show it to someone and suddenly none of them worked. Java had changed its security policies and there was no way to get them to work, that I could find.

Is it still okay to use document.write for object and embed tags?
vou virar o melhor hacker do brasil
Would be nice if affected end users actually complained to google when they randomly break a website, based on the network speed. Then the Chrome devs would perhaps think twice about breaking old APIs.
Do any serious websites use document.write anymore? It's a bad idea, it's been a bad idea for a long time, and you really should be using proper dom nodes. Just grab JQuery, or do it yourself.

Apparently advertisers are still using it. So that's another benefit to disabling it in this case!

For once, google, I'm on your side.

Now if only you didn't support EME...

Many Google JS things use it, for example the Maps API.
A lot of hosted products that require extreme backward compatibility like Adobe Analytics still do it.
7.6% of all page loads inject scripts with document.write() according to Google. 1 in 13 pages.
Who are the morons using document.write?! SHOOT THEM

    document.write('<script src="https://example.com/script.js"></script>');
Since when is this valid? Last I recall, you had to break up the `</script>` end tag into two strings.

I think a better solution to this problem would be to remove `document.write` completely. It's useless. In all cases, you're better off using something else.

re: removing document.write completely is a hard balance, soooo much of the web still uses it especially for 1st party script loading that it would be a more impactful issue.

I do believe the consensus across all browser vendors is that we should remove it, it's just incredibly hard to do and smaller incremental steps is a better solution that keeps users happy.

Personally I worry about complete document.write removal that it would ideally need to be done by all browsers at the same time because for the first browser to do it will get the majority of the user and developer pain :\",

> Last I recall, you had to break up the `</script>` end tag into two strings.

Only if you're an inline script. If your document.write() call is in an external script, you don't need to do anything like that.

Well, found below code in https://maps.googleapis.com/maps/api/js

  function getScript(src) {
    document.write('<' + 'script src="' + src + '"><' + '/script>');
  }
Yep, so we will likely be breaking our own maps on slow connections... (remember it is not really slow sites that we measure)... but the broader point is, if that script blocked rendering, which it would, then a user on a slow connection would likely not even see the content of the page, let alone the map. So the broader question is, for someone who wouldn't get the content normally is it better to break a smaller section of the site vs not see the site at all? If the user is on a fast connection then everything would still work.

Although a quick search in that code, I can't see getScript actually being called.

That you're only breaking a small section of a site is a false assumption. You really have no way of knowing that. You will be breaking the entire site in the case of a site that writes all of its content using JavaScript obtain from that resource. This is fairly common with jQuery fallback document.write scripts.

document.write() calls are dependencies of the site, in many cases. Some times they are just worthless ad stuff, but you shouldn't make any assumptions here. Not breaking the web should be the priority.

Hehe, as a slightly jaded participant in the advertising industry, I note that Chrome only breaks technologies used for ad-serving that Google's stopped using.

That said, I'm glad they are doing this, because finally we can wean our advertisers off their document.write obsession.

(comment deleted)
Off topic.

The original developers blog post was submitted 17-22 days ago, multiple times. Previously multiple submits would trigger upvotes, but in this case they created multiple new stories and in turn greatly decreased the chances of front page. Bug or working as intended?

https://hn.algolia.com/?query=intervening%20document.write&s...

If the submission is old then a repost is a new submission. Multiple submits in a short period are merged.
Curious -- if you are using XMLHttpRequests to your own local server to progressively load logic into your app -- what is the perceived danger?
This is changing behavior for performance reasons, which I think is almost always a bad idea. Yes it's better to use the DOM, but is setting a node's text really all that much different than setting the document's text? We should really be talking about scope (which nodes are changed) not method. I'm concerned that adding things like timeouts to rendering logic will make rendering nondeterministic.
So this is no longer valid ?

    <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
    <script>
       if(!window.jQuery) {
            document.write('<script src="../lib/js/jquery-2.1.1.min.js"><\/script>')
         }
    </script>
I think this is the correct way to do this now:

  <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
  <script>
    (function(){ if(!window.jQuery) {
      var s = document.createElement("script"),
          s0 = document.getElementsByTagName('script')[0];
      s.src = "../lib/js/jquery-2.1.1.min.js";
      s.async = false;
      s0.parentNode.insertBefore(s, s0);
    }})();
  </script>

EDIT:

Saving a few bytes although a little less readable:

  <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
  <script>(function(w,d,x,t,p,s,z){if(!w[t]){s=d.createElement(x);s.src=p;
  s.async=false;z=d.getElementsByTagName(x)[0];z.parentNode.insertBefore(s,z);}
  })(window,document,'script','jQuery','../lib/js/jquery-2.1.1.min.js');</script>
Yeah I believe this is the best approach, in my case I would have never seen the warning from the console, the only way I would have known that this is no longer valid in Chrome is for Google to stop serving my version of JQuery + my fail over failing on me for using a bad practice.

Thank you!

Setting async to false doesn't make it synchronous. Try adding another <script> after this one that uses $ and see what I mean.

document.write() is the only way to synchronously inject a script.

Has the W3C commented on this proposed change?

Has ECMA?

Was there an RFC on this proposed standards-breaking change that we missed?

I ask these questions sincerely. This seems too much like "hey, trust us, we know better than you do", and I really don't want to believe that's true.

News from the future: "Google Chrome browser, which recently grabbed 96% market share, is going to only allow JavaScript for websites that have EV SSL certificates issued by Google Inc. The change is effective tomorrow September 24, 2021. This all done with a better security and performance in mind, so no need to worry. Third-party developers can enroll into certification program for just $99 per year and 30% cut from the revenue, which we all know is bargain. Just about time. Internet users eagerly waited for improved robustness and security of the websites. JavaScript was used to deliver malware, so now we all can sleep well and take a deep breath of the fresh air."
Still cheaper than developing for Apple...
Maybe a hackish override of document.write? Something like:

  function write(str) {
    var rE = /<scri.*src=['"]([\w\/]*).js/i;
    if (rE.test(str)) {
      var m = rE.exec(str);
      // Do script injection here
    } else {
      document.$old_write(str);
    }
  }

  document.$old_write = document.write;
  document.write = write;
I haven't tried this. I don't even know if you can override document.write()
A few years ago, I co-wrote a book on third-party scripting that includes best practices for loading JavaScript applications on other people's websites: https://manning.com/vinegar

It definitely includes a discussion on why document.write is undesirable, and suggests async, defer, and other alternatives instead.

/shameless plug

I reluctantly clicked the link, thinking I'd learn nothing new, but I learnt that Google is ad-hoc deprecating some bad-yet-established JS API. Maybe the title of the submission should be updated accordingly to attract more views as it's quite significant.
Growth hacking expert here: Add a link to your site directly in the header to increase traffic to your product.
(Rant) if you site's JS file is not minified, , your script will be blocked (Chrome 99)