Everyone knows that you shouldn’t block page rendering by synchronously loading a big chunk of javascript in the head of your page right? Hence you might be tempted to change the default Javascript include tag, from this:
<%= javascript_include_tag 'application' %> |
To this:
<%= javascript_include_tag 'application', async: true %> |
Which makes perfect sense, when serving all Javascript in one big file, as is the case in production, meaning everything is defined at the same time. What about development though?
Well, in development rails is kind enough to let you work on individual Javascript files, which means it will recompile only as needed, when a single file is changed. To this effect, each file is included separately via their own script tag in the header. E.g:
<script src="/assets/jquery-87424--.js?body=1"></script> <script src="/assets/jquery_ujs-e27bd--.js?body=1"></script> <script src="/assets/turbolinks-da8dd--.js?body=1"></script> <script src="/assets/somepage-b57f2--.js?body=1"></script> <script src="/assets/application-628b3--.js?body=1"></script> |
* Tags intentionally shortened in example.
There is a subtlety here that is quite important. All the scripts are loaded synchronously, one after the other, as specified by the order they appear in the application.js manifest. This means we’re guaranteed that jQuery, etc. is available once we get to our own scripts.
Now consider the the same scripts, but with async=true:
<script src="/assets/jquery-87424--.js?body=1" async="async"></script> <script src="/assets/jquery_ujs-e27bd--.js?body=1" async="async"></script> <script src="/assets/turbolinks-da8dd--.js?body=1" async="async"></script> <script src="/assets/somepage-e23b4--.js?body=1" async="async"></script> <script src="/assets/application-628b3--.js?body=1" async="async"></script> |
Since all scripts in this case is loaded *asynchronously*, all previous guarantees are now lost, and we’ll very likely start seeing errors like this:
Uncaught ReferenceError: $ is not defined
Oops!
The fix is simple though: Don’t load Javascript assets asynchronously in development mode!
Here’s one way of doing it:
<%= javascript_include_tag 'application', async: Rails.env.production? %> |
Happy hacking!
Brilliant! – took me a while to find out it was the async setting, especially since the behavior was intermittent. Did not understand what was happening until finding your post.
Thanks!
You’re welcome!
I implemented this but I’m getting that “Uncaught ReferenceError: $ is not defined” in Production, in fact.
Did you figure out how to get that fixed? I am getting the same error in production.
Thanks!
The problem seems to be page scripts running before Jquery is loaded asynchronously, even in production. Do you know of a good way of deferring page scripts from running until the manifest file has been loaded? Thanks
I tend not to use jQuery myself, but I think I’d solve the problem by inlining a dom loaded function and then using that one instead of jQuery’s ready function.
A minimal one such as this should suffice: https://github.com/dperini/ContentLoaded.
Shouldn’t the punch line read `Don’t load Javascript assets asynchronously in development mode!` instead of `…synchronously in development mode!`? 🙂
Right you are! Can’t believe that one has gone unnoticed till now. Fixed. 🙂
I was not able to figure this out for months! Thanks for the useful tip.