Saturday, June 7, 2014

How to speed up resource loading when using Require.js

If you're building a single-page AJAX application using Require.js, odds are your boot sequence for loading the application on production looks something like this:
  • First, your index.html gets loaded.
  • At the head of your index.html you link CSS resources which get loaded.
  • There's a script tag referencing Require.js, then some inline script for configuring it and finally you kick it off by specifying which modules should be loaded.
This is pretty much what booting our application at Speakap looks like and it results in the following graph for loading resources:

Here you see the index.html getting loaded at the top from The CSS gets loaded next and require.js is fetched in parallel because both are referenced straight from the index.html (please note all static files are padded with an MD5 hash for versioning in the graph above). The file nr-411.min.js can be ignored in this discussion as it's used for performance analysis and error reporting by New Relic, but it's not actually a module of our own application. Then finally we see our two modules - app.js and libs.js - that are getting loaded by Require.js.

This is not everything that is happening during boot, but it's the main part and with the resources you saw above, the application is able to render its skeleton and from a user's perspective, the application has loaded and is visible, though data fetching has still to occur. This all happens in under a second for a typical DSL connection (the graph above is measured from my home connection). It's certainly not bad, but it is up for improvement.

The most obvious way to improve the boot time is to improve the parallelization of the loading of resources. Unfortunately, in the current setup the inline script which configures Require.js and tells it which modules to load is blocked by both the loading of the CSS (see here if you want to know why) and the loading of Require.js itself. Because of this, app.js and libs.js cannot be loaded by Require.js until after these two files have been loaded. Fortunately, the workaround is easy; who says these two modules have to be loaded by Require.js? We already know upfront these two modules will be loaded, so why not just reference them straight from the HTML with a good old script tag, preferably with an async attribute.

This is exactly what I did, and the result is that app.js and libs.js are now fetched in parallel with the CSS and require.js. In a development setup (no latency) this saved me about 80ms, or about 15%, in total page load time. In the common case where latency is an issue the savings are even larger.

Before all is said and done however, there is one important caveat that will ruin the plan if you're using a vanilla Require.js library. Require.js expects to be in full control of the loaded libraries and will happily ignore the fact the modules it is instructed to load are already being loaded from a plain script tag. The result being we end up with two script tags for the same module, and depending on the order in which they are actually loaded, global state may be overwritten causing inconsistent JavaScript errors. The fix to this problem is a rather simple check to make Require.js aware of the already existing script tags, and re-use those when applicable. In hopes this might be useful to others, I have created a Pull Request for Require.js, which adds this fix. I hope it gets merged into Require.js proper, but until then, feel free to try it out!

No comments:

Post a Comment