{"action":"created","issue":{"url":"https://api.github.com/repos/rails/rails/issues/23012","labels_url":"https://api.github.com/repos/rails/rails/issues/23012/labels{/name}","comments_url":"https://api.github.com/repos/rails/rails/issues/23012/comments","events_url":"https://api.github.com/repos/rails/rails/issues/23012/events","html_url":"https://github.com/rails/rails/pull/23012","id":125970208,"number":23012,"title":"Application.js should be generated as \"async defer\" by default.","user":{"login":"nateberkopec","id":845662,"avatar_url":"https://avatars.githubusercontent.com/u/845662?v=3","gravatar_id":"","url":"https://api.github.com/users/nateberkopec","html_url":"https://github.com/nateberkopec","followers_url":"https://api.github.com/users/nateberkopec/followers","following_url":"https://api.github.com/users/nateberkopec/following{/other_user}","gists_url":"https://api.github.com/users/nateberkopec/gists{/gist_id}","starred_url":"https://api.github.com/users/nateberkopec/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/nateberkopec/subscriptions","organizations_url":"https://api.github.com/users/nateberkopec/orgs","repos_url":"https://api.github.com/users/nateberkopec/repos","events_url":"https://api.github.com/users/nateberkopec/events{/privacy}","received_events_url":"https://api.github.com/users/nateberkopec/received_events","type":"User","site_admin":false},"labels":[{"url":"https://api.github.com/repos/rails/rails/labels/railties","name":"railties","color":"8BE06E"}],"state":"open","locked":false,"assignee":{"login":"chancancode","id":55829,"avatar_url":"https://avatars.githubusercontent.com/u/55829?v=3","gravatar_id":"","url":"https://api.github.com/users/chancancode","html_url":"https://github.com/chancancode","followers_url":"https://api.github.com/users/chancancode/followers","following_url":"https://api.github.com/users/chancancode/following{/other_user}","gists_url":"https://api.github.com/users/chancancode/gists{/gist_id}","starred_url":"https://api.github.com/users/chancancode/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/chancancode/subscriptions","organizations_url":"https://api.github.com/users/chancancode/orgs","repos_url":"https://api.github.com/users/chancancode/repos","events_url":"https://api.github.com/users/chancancode/events{/privacy}","received_events_url":"https://api.github.com/users/chancancode/received_events","type":"User","site_admin":false},"milestone":null,"comments":5,"created_at":"2016-01-11T15:35:18Z","updated_at":"2016-01-14T22:34:35Z","closed_at":null,"pull_request":{"url":"https://api.github.com/repos/rails/rails/pulls/23012","html_url":"https://github.com/rails/rails/pull/23012","diff_url":"https://github.com/rails/rails/pull/23012.diff","patch_url":"https://github.com/rails/rails/pull/23012.patch"},"body":"For a while now, pull requests (#15766 #5815) have been opened to move the default application.js script tag to the bottom of the document before the closing body tag.\r\n\r\nExternal scripts (indeed, any script tags) block the document parser, meaning that any DOM content *after* a `script` with a `src` cannot be parsed until after the script is downloaded and executed. This is Not A Good Thing. [Here's an example of problems this may cause - watch how the images do not appear until the script is downloaded (after 2 seconds of simulated download time)](http://stevesouders.com/cuzillion/?c0=hc1hfff0_0_f&c1=hj1hfff2_0_f&c2=bi1hfff0_0_f&c3=bi1hfff0_0_f&t=1452468665705). [Moving the script to the bottom, in this simplistic case](http://stevesouders.com/cuzillion/?c0=hc1hfff0_0_f&c1=bi1hfff0_0_f&c2=bi1hfff0_0_f&c3=bj1hfff2_0_f&t=1452468798181), allows the images to appear before the script is downloaded.\r\n\r\nEncouraging Rails developers to use an external script in the head tag, without `async` or `defer` attributes, is unnecessarily slowing down their page renders and encouraging bad JavaScript habits.\r\n\r\n## APPROACH\r\n\r\nMoving Javascript to the end of the document is, as far as I can tell, mostly outdated advice since the adoption of the `async` and `defer` attributes. \r\n\r\n* Old concerns about network blocking are no longer valid. Browsers allow 6 parallel connections per hostname, so application.js is not crowding out other requests. In addition, as Rails developers adopt HTTP/2 compatible `asset_host`s, like Cloudflare, parallel connections will be a thing of the past and all assets will be downloaded over a single connection, intelligently prioritized with stream priorities. \r\n* If a browser supports `async` or `defer` attributes (IE8+, all majors), adding those attributes to an external script in the HEAD unblocks the document parser, achieving a similar effect to moving it to the end of the document, with the added advantage that DOMContentLoaded is not blocked either. [As an example, see this page.](http://stevesouders.com/cuzillion/?c0=hc1hfff0_0_f&c1=bi1hfff0_0_f&c2=bi1hfff0_0_f&c3=bj1hfff2_0_t&t=1452525621434). DOMContentLoaded fires immediately, while \"scripts at the bottom\" still delays the event until that script is downloaded. Nearly all major websites, such as Github and Basecamp, use some combination of `async` and `defer` attributes.\r\n\r\nThe reason both async and defer attributes are added is for IE8->IE9 compatibility, which do not support `async`. These browsers will ignore the `async` attribute and execute the `defer` instruction. All other browsers will perform the `async` behavior and ignore `defer`. `defer`, while not *exactly* the same as `async`, also achieves the effect of unblocking document parsing. The slight difference is in that a) `defer` always delays until DOMContentLoaded and b) makes some promises about when scripts will be executed (in order), which `async` does not. \r\n\r\n## TURBOLINKS\r\n\r\nWe cannot move application.js to the end of the document because Turbolinks assumes application.js lives in the head tag. Moving application.js to the end of the body will cause application.js to be re-executed on every page:change. \r\n\r\nMarking application.js as `async` and `defer` will achieve document parser unblocking without breaking Turbolinks - in fact, I think it shows the great strength of Turbolinks' progressive enhancement, since Turbolinks is not required to render the page, we can go ahead and render the page without downloading it.\r\n\r\nFor an example of a Turbolinks-enabled site using `async`, [see my implementation of TodoMVC with Rails and Turbolinks](http://todomvc-turbolinks.herokuapp.com/).\r\n\r\n## PROBLEMS/CONCERNS\r\n\r\nA common (anti) pattern in Rails projects is to use JQuery selectors or event management inside inline script tags inserted in a particular view. [See this example in Rubygems.org](https://github.com/rubygems/rubygems.org/blob/master/app/views/pages/data.html.erb#L12). When this pattern is adopted, `application.js` must be loaded before the remainder of the document, slowing page render. This approach is incompatible with an `async` application.js. However, it appears that Rails may be taking a stance against inline scripts anyway with the possible adoption of #15777, though that looks stalled and may include the `unsafe-inline` option when merged. Users that continue to attempt to use inline scripts with dependencies inside application.js will find those functions are undefined, which is not a helpful error message and they will probably not understand why those functions are undefined.\r\n\r\n`async` makes no guarantees about the order of execution of scripts. Many developers depend on script order in the document being their implicit dependency ordering. Thankfully, the Javascript community has solved these concerns with things like AMD, but many Rails developers are unfamiliar with these approaches.\r\n\r\nThe guides should probably be updated to reflect working with an `async` application.js and how that changes the way developers should use Javascript in their applications. \r\n\r\nEven when application.js has an `async` attribute, there may be some small benefit to moving it to the closing body tag - if application.js finishes downloading before DOMContentLoaded has fired, it *may* execute and delay DOMContentLoaded and block further parsing. As an example, github.com puts their main Javascript file at the closing body tag *and* adds an `async` attribute. Again, this cannot be recommended by the default layout now because of Turbolinks, and even so, I'm not convinced of the benefits in the majority of cases.\r\n\r\n## CONCLUSION\r\n\r\n`async` and `defer` is the future of fast webpages, and already used in major production applications. It encourages other Javascript best practices and is compatible with Turbolinks. Adding it to `application.js` would be a net benefit to the Rails community."},"comment":{"url":"https://api.github.com/repos/rails/rails/issues/comments/171803478","html_url":"https://github.com/rails/rails/pull/23012#issuecomment-171803478","issue_url":"https://api.github.com/repos/rails/rails/issues/23012","id":171803478,"user":{"login":"apacala","id":9934,"avatar_url":"https://avatars.githubusercontent.com/u/9934?v=3","gravatar_id":"","url":"https://api.github.com/users/apacala","html_url":"https://github.com/apacala","followers_url":"https://api.github.com/users/apacala/followers","following_url":"https://api.github.com/users/apacala/following{/other_user}","gists_url":"https://api.github.com/users/apacala/gists{/gist_id}","starred_url":"https://api.github.com/users/apacala/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/apacala/subscriptions","organizations_url":"https://api.github.com/users/apacala/orgs","repos_url":"https://api.github.com/users/apacala/repos","events_url":"https://api.github.com/users/apacala/events{/privacy}","received_events_url":"https://api.github.com/users/apacala/received_events","type":"User","site_admin":false},"created_at":"2016-01-14T22:34:35Z","updated_at":"2016-01-14T22:34:35Z","body":"Came here to report @nateberkopec's issue."}}