I recently read a blog post from Luc Perkins at AppFog about ender.js and how it can be used to bundle and minify multiple JavaScript files in to one. It got me thinking that I should do a post to outline how we’ve built Appsecute using modern standards, some cutting edge thinking in the JavaScript world and some special sauce of our own.
So here it is, how to build a large ‘single page’ HTML5 web app without sacrificing performance. This post will be a high-level(ish) overview, I’ll go in to more detail about specific libraries and ways of doing things in future posts.
One of our key goals when building Appsecute was to deliver a 100% client-side web app, meaning we didn’t want to generate any JavaScript, HTML or CSS on the server. I know it can be awfully convenient sometimes but I fundamentally believe it’s the wrong way to do things. Our web app needed to be coded in JavaScript and talk to the REST API of our server via async http calls, it also needed to load fast and be maintainable as it grew. We looked at a number of MVC JavaScript libraries for building our views and handling routes between them and ultimately settled on backbone.js because it was the most flexible and didn’t to force anything on us, instead it just tries to guide you in the right direction.
Once we had settled on backbone.js we started building our views and before long we had accumulated a large number of JavaScript files, all of which were being loaded every time you used our app (we included every single one of them in our index.html). Things started to get slow. Enter RequireJS, an awesome JavaScript library for bundling core JavaScript files in to a single file, and then allowing additional JavaScript to be loaded on-demand as needed.
After refactoring our existing JavaScript to be AMD modules we ran it through RequireJS and ended up with the following:
- A main.js file which is the only JavaScript file loaded by default when you visit our web app (seriously, look at the source of https://dashboard.appsecute.com/index.html).
- The remainder of our JavaScript, minified and compressed, to be loaded on-demand only when it’s needed based on the view the user goes to.
- All of our CSS minified and compressed in to one file.
This significantly speed up our load times, I don’t have any numbers but subjectively it was a night and day difference. Using RequireJS also forced us to write clean, compartmentalised code which lead to a pure JavaScript app that is simple to maintain – you won’t find any spaghetti code typical of traditional JavaScript apps.
Now that we had the loading of our JavaScript sorted and a clean, maintainable structure for our app, we ran in to the next problem: web browsers have a ‘gentlemans’ agreement with web servers – they will only ever execute two simultaneous web requests to the same domain at any given time. The problem? Web requests to our API done in response to user input would often sit waiting behind a web request generated by our JavaScript that wasn’t so important – checking for notifications, updating the status of your apps, refreshing the current view etc. – and even worse, if the user browsed to a new view, previously submitted but unfinished web requests would all have to complete prior to the new view being able to make its calls to our API.
Problems like this don’t exist in traditional websites, when the user browses to a new view (clicks a link) the browser will cancel any existing http requests and do a full page reload on the new url. In a single page web app this doesn’t happen, as the browser never actually navigates to a new url, instead the JavaScript running on the page is responsible for changing the currently displayed content based on user actions.
So we had a problem, we wanted to prioritize user generated API calls and we needed to be able to cancel currently running or queued requests when the user navigated to a new view, as they were no longer relevant. The solution? Our special sauce. The first(?) priority queue for JavaScript that handles the queuing, prioritization, cancellation and life time of http requests in the browser.
We will open source this library in the near future as we believe it is extremely useful for building what we think is the next generation of pure JavaScript apps. The library itself is somewhat complicated, but trivial to use and implement in your apps.
Lets look at some code taken from the clouds view of Appsecute:
This is a typical view in our web app, it’s declared as an AMD module so it can be loaded on-demand when a user browses to it. It also has a function for rendering itself (thanks to backbone.js) and loads its HTML content from a template file. The cool thing it does is use the JavaScript queue we’ve created, at the top of the file you can see it creates a new TaggedApi object, then uses it to fetch the users clouds from the Appsecute API. If you look at the close function you can see we call the cancel function on the TaggedAPI object – this ensures that when the user navigates away from the view, any web requests the view started/queued will be cancelled.
That’s about all there is to it, the queue itself can be passed a priority for each message as well, we typically don’t specify a priority as the default priority is suitable for most requests, anything that occurs in the background though that wasn’t caused by a user is queued as low priority.
I know I’ve covered a lot in this post and didn’t go in to as many specifics as I would have liked to but there is a lot to it, the end result is an extremely performant and responsive ‘single page’ JavaScript app, and it’s worth it.
I’m curious to know if you guys optimised your RequireJS to make fewer requests, and if so were the savings worth the effort?
Hi Joe,
We haven’t optimised RequireJS at all yet, it’s smart enough by default to group common JavaScript in to the single main.js file which has been adequate so far.
There is a lot of room for us to start optimising by grouping JavaScript common to a certain view or area of the site in to single files, which we will tackle in our next round of speed improvements (we also need to make use of css sprite sheets for images).
Right now the ‘manual’ optimisations that we’ve done have centered on putting all HTML templates for a certain view/area in to a single HTML file (check out https://dashboard.appsecute.com/js/views/applications/templates/application.html or https://dashboard.appsecute.com/js/views/applications/templates/application-dialogs.html). Doing this significantly decreased the number of requests our views needed to do to render themselves.
We also pre-load JavaScript/HTML required to draw the main areas (applications, clouds, services etc.) by way of kicking off a timer a second or so after the user visits our site, when the timer fires we make calls to RequireJS to get it to load the views. So when the user does navigate somewhere else there’s a good chance the browser has already downloaded and parsed the JavaScript/HTML needed.
We also use CloudFlare and use their SPDY support as well. There’s still so much room to speed things up more but we’re off to a good start