Project Peach
We need a modern browser to run our WebApp. Because you have either a legacy browser or have javascript disabled this is the best we can do!
Turbocharge Webapp Loading Time
06/06/2018
What's taking the time to load?
We're not a conventional website that simply uses a web server to get files and text in a standard server roundtrip. We do use the web server to source files like images, template HTML, CSS, etc but our data primarily flows down our smart websockets connections. Remember a normal website loses connection after each page load (well in truth there could be a little browser optimisation) but the concept is still there. In our world we maintain connectivity so we can push data both ways, from the browser to the server or visa versa. Hence our initialisation takes a little longer than the simple web server round trip. We need to make a second fixed connection in association with our security handshaking. We've always been reasonably happy with our load times but always thought that we could do a little better. So it's time to profile our loading and see if we can shave a few seconds off our initial page loading time.
Identifying the Optimisation Candidates
This is the simple bit and not so simple bit. We can get some way towards identifying slowness via the profiling tools in the development section of the browser. It tells us in a simple timeline what's taking a while. Once we've identified candidates we can zoom in and timestamp processes.
Client Side JavaScript Token Map Translation
In our web applications we have a UX (User eXperience) that runs in a browser and a full blown native code application that runs in the cloud. We use client side (browser) HTML, CSS & JavaScript simply for rendering the data that we process in our cloud application. Our webapps are NOT JavaScript applications trying to mimic real hard number crunching desktop applications. Our webapps are full blown hot rods performance wise. To enable us to build a user interface in the browser and to allow user customisation of literals and language translations we used tokenised HTML templates. There are NO literals (the words displayed to the user in pages and forms) embedded in our core templates, only tokens. When a page is loaded our translator, on the server, replaces these tokens on the fly using a complex process of inheritence since literals can be customised via our live CMS (Content Management System). You can imagine that this process takes a little time. Our initially loaded main page contains potentially thousands of these tokens. It's not constained to page and popup literals either since we've a full token to literal map of passed down to the client to help us optimise and reduce messaging round trips. So the main page token translation is quite intensive, we knew that and profiled it (checked the speed). Turns out it was pushing 600 milliseconds, which is over half a second, the majority of the time taken was processing the tokens in the JavaScript token to literal map. There's close to 1600 translations in our retail solution. So how do we improve the situation. We need the map but we could do a little refactoring to change the way it works following the golden rule "Don't optimise, well not yet". It works well and there's no shame in just over 1/2 second but we could do better. Before we refactor, change or create any new code we always have a design chat. You can't create anything without knowing exactly what you are going to do. So setting off tapping keys is generally a bad idea, though pretty common. Plan, design & excecute. The key to this refactor turned out to be quite simple based on what the translator was trying to achieve, produce a map of tokens to literals. What if the map pre-existed and was simply included. No translation is needed in the translator just a simply read of the file via some inclusion code. So where would this file be built and who would maintain it? Maybe our long running cloud webapps could build it on startup. Put the 1/2 second into a webapp startup that runs for months seems like a good idea. So a simple idea that takes 1/2 second for each new user browsing to one of our webapps and makes it a one time webapp startup event. But what if an admin user changes a literal via our live CMS, remember it's easy right click, edit & save. Other users of the same webapp will need to see the change. Since we've a live, bidirectional connection, it's very easy to handle. We simply push the admin literal change to the new server hosted map and using our observer model push the update live to anyone who's currently looking at a page containing that literal. It's slightly more complex but that is the gist of it. So did it work? We profiled our change again and wow. The main retail page with it's 1500 tokens now parses in under 40 milliseconds. We save over 1/2 second on our load time. Pretty significant on a sub 4 second load. Unbelievably with just this one change it's very noticeable. So there you go, 500ms is a noticeable time saving.
All the Pop Up Dialogs are Loaded Initially
All our user pop up dialogs are loaded all at once, or used to be until we refactored it. A little background into our page loads. In simplistic terms we have a main page and a sub page. Moving around our web application UI (User Interface) simply changes the sub page. We request the sub page from the translator and poke it directly into the DOM (Document Object Model or how the simply browser stores and renders HTML data in a hierarchical fashion). Using this mechanism page movement doesn't appear like a traditional server round trip and internal page movements are smooth. However our main page that initially loads with the home page as the sub page contained all the supporting HTML forms/popup dialogs. In our retail version there's nearly 50 popups. All these needed poking into the DOM to be available for use. Time consuming and there's also a finite amount of browser memory. What if we could somehow load on demand? So no popups are initially loaded and we poke into the DOM in response to user actions. There's no roundtrip since we have the Popup text but they're not in the browser DOM. In practice this DOM poke isn't that expensive for one action, or rather a single popup. It's the cumulative effect of many that consumes time on load. Turns out there's no perceivable difference in useability between the two methods so it's a win. Once we've loaded a form/popup into the DOM we simply leave it there. There's an interesting point here. Most of our forms are aimed at our Admin users for controlling the content of the web application via our CMS. Most of our visitors are customers or guests who never use most of the forms. So it speeds up guest loads and has negligible effects on admin users. Although this is harder to quantify we believe we shaved another 1/4 second on load using this technique.
Only load the Javascript Needed...
Our JavaScript file is pretty big in terms of size. It's currently over 1MB and it grows with new features that we add. Sometimes it shrinks with some clever refactoring but in general it grows. We've an obfuscation and minification process that we built that you can read more about in this blog on Security https://projectpeach.co.uk/ProjectPeach/Default/Static/def/blog_1.html. For our production release we shrink the size of our code by renaming to shorter machine generated names and combine our hundreds of JavaScript files into one monolithic version, hence obfuscation (you can't read meaning in it) and minification (we shorten as much as possible). So, even with all this, we have a pretty big file that needs loading before anything can start to work. Remember that we use JavaScript as it's meant to be used as a client side scripting engine to power our UI forms and controls. Our business logic and application coding is in the cloud as a native app. So how do we reduce the sizing of our JS (JavaScript) code base? The key to this is in the fact that we own our own code. We haven't simply glued the work of others together. Hence we own the minifier. It runs as part of our process to build release candidates. It generates a set of update files that our updater on the server uses to automatically install at the click of a button. Since we own the code we can modify the minifier to produce not 1 but 2 Javascript files. Once containing the main code to get the page working quickly and one containing the form/popup Javascript. The form processing contain quite a lot of chunky scripting which are fairly large in comparison with the rest of our pages. It a recurring theme. The same as the form/popup HTML files. We're download large chunks of code that only admin users will use. So the answer is easy. Download the JavaScript to get customers going quickly and once loaded, in the background, download the admin popup JavaScript. Faster startup for 99.9% of our users who happen to be our customers spending money and a tiny pain for admins who have to wait for the popup JavaScript. We've only just started with this concept. We made a simple refactor of the most easily separatable code. We could do much, much more in another more complex refactor. We've backlogged a task to revisit. That's how we work, tiny changes that improve our source code constantly over time. Another 1/4 second saving just from this simple starting refactor.
I See Dead Code
Once in a while we change our code base structure. Generally if we do some specific work for a client it goes initially into their project, if it's deemed useful to others we move it into our common code base. We lift and genericise to suit all our clients and specialise it by derivation into the original client requirement. It's a common coding technique to avoid copy/paste and rubber stamping of similar source code. We do our absolute best in refactoring and removing old code. We never lose it since it lives on in previous version in our source control system BUT it's no longer in our code base. But we're only human and what if the change we make replaces something established, maybe we just comment it out temporarily? No harm in our backend c++ work since the compiler simply ignores it but in our frontend world it's transported across the wire down to the browser UX. So how much dead code do we have? It could also be dead HTML, dead INCLUDES or dead JavaScript. Features that we deprecated in 2012 but needed to stay for support way back then. Another trawl through our code based yielded quite a few surprises. Some stuff had cobwebs on it. So we refactored but didn't nuke. More shrinkage, less data down the wire. We'll be more trusting of our source control system in the future, to save the pain.
INCLUDES in INCLUDES
When you have common snippets of definitions or code or both then it makes sense to make them into files that other files can simply reference or INCLUDE. This ripples through our entire source code base from our C++ to our front end HTML templates, CSS and JavaScript. But managing what includes what can become a bit of a recursive nightmare. What if something is included more than once. It's not too bad with our compiled native code c++ since it doesn't affect the size of the object code or executionable. But for our frontend source it does. We're particulartly vunerable with our CSS and templating. So we had a clean up. Not as much as we had expected but in some projects we shaved another sizeable portion of download size. Mind numbing stuff this.
The fight continues to improve load speed...
The Only Visual Change
Our mission with loading is that our end user never sees our loading page. From the moment that we are first functional, there's enough JavaScript to run, then we show a loading page. The point of this refactor was to reduce the length of time it's visible. The wait. Although we hope that moving from 4 seconds to 2 seconds is good we wanted to fix another problem. Our original loading page downloaded a company logo image. Two problems with this. Firstly during the very first website visit the image may never display until the browser caches the file. Also the download is slow. Secondly we simply didn't like it. It's from way, way back. We're not best known for our beautiful artistic flair, well not until now. So we made the graphic change. We ditched the image download and went for a pure CSS solution. CSS animation and transitions is built into the browser so there's nothing to download. It appears instantly, zero download and we can be as creative as possible with what is essentially manipulating stock browser visual coding.
Our old image based loading page that shows during our loading process.
CSS based animation with zero download that we hope you don't see for long. We're aiming towards you never see it!
##BLOGWRITTENBY##
John Ince
Comments