Data-driven Rendering in Native JavaScript
The front-end Speed thingy
Recently I was leading a project aiming at improving the performance of the search results section of our website and we usedWebPageTest and Google PageSpeed Insight to measure the metrics around the real-world browser-side performance, and we found that the biggest drag was actually the interactive form built with AngularJS — and the library we included took more than 70% over the total size of scripts. Actually, here’s a breakdown chart nicely done from chrome tools:

As you can see, we use quite a bit of angularJS libraries and the total is over 200k. For first time visitors to the site, we are forcing 200k extra download upon them.
On top of that, in the form we use a lot of directives and controllers for each interaction, and we end up with a 100k JS file by ourselves too. So each new user to the site is forced to download 300k before they can see the form, and before that, it’s a page with color blocks and empty spaces.
Then once the page is rendered, when you make interactions, it feels slow — reason being that we have massive dropdown selects and they are rendered by the script, doing massive DOM operations (and what we did really wasn’t doing justice to AngularJS — if you check the docs they tell you upfront it’s best to use AngularJS for CRUD operations, not interactive apps).
Why not ReactJS
I first tried ReactJS, it’s nice, and easy to use, very straightforward. One thing it improved was rendering speed, it was fast, really fast. Since ReactJS uses VirtualDOM, it avoids large number of DOM operations and only update the specific node(s) when applicable.
The only problem? The library, by itself, is already 300k, and it requires a gulp/grunt build process if you want to use the best feature in it: JSX, for templates. We are trying to make the page faster for first-time visitors and this won’t cut it.
And virtualDOM actually has its own issues — it builds the entire structure in memory as a JS object, and runs an (albeit fast) algorithm to compare with what’s in DOM to decide which one to change, so on fast computers it’s really fast, but on my 9 year old 24inch iMac with a core2duo CPU, it stutters quite a bit, as our website deals with selling/buying cars, many of our audience are actually in an age group that don’t have up-to-date computers, and more than 50% of them are on a mobile device.
Enter the native JS
Client-side JavaScript, as fancy as it may get, down to the very root of it, is really still just playing with HTML (and with occasional AJX calls or animation — but wait, animations are also HTML/CSS related really, plus you can do most animations with CSS3 transform now), so there’s really no reason for using a massive library/framework when you can just write your own little app with native javascript. You’ll gain two things:
- a much smaller JS file
- a much faster user experience
As mentioned above, you’ll just need to manipulate HTML, so initially you can just go
element.innerHTML = myapp.render()
to do the job.
But… how do you deal with DOM changes in rendering? innerHTML update will end up updating the entire DOM tree and every single node on it, that’s performance nightmare. And here’s my take on this issue:
Data Driven, targeted node rendering
First of all, let’s think about a real-world scenario: say we have a to-do list app, and we have these elements:
- an input form consists of text input and a button;
- when there’s to-do items, list out the item;
- when there’s no to-do item, show a message in place of the items.
- if we want to make it fancy, we can also offer different styles on to-do items, and even totally different HTML structure if we like.
Now, if you think about the possibilities in the actual rendered HTML, it’s gonna get so complex and guess what, if you write this in angular, you’ll have to write some logic in the controller and some of us may even use the evil:
if ($scope.localStorage.toDoItems.length === 0) {
$scope.trustedHtml = $sce.trustAsHtml('<p>please enter some todo items</p>');
} else {
$scope.trustedHtml = $sce.trustAsHtml('<ul>...');
}to insert HTML into an existing node.
Or, if you write in reactJS, you’ll have to put logic in the JSX template to do the same thing.
You may have already realised one thing: HTML templates changes all the time, but data structure? they rarely change! In fact, if your data structure is ever changing you are probably doing it wrong. This is why we have JSON schemas to verify the data returned by server.
So, it’s really anti-productive to compare the DOM and render, comparing to just read through the data structure and only change the HTML segments related to the updated data segments.
Data-driven rendering in a nuts shell
- the entire HTML segment for the app is split into two parts: the main template, and the sub templates;
- each of the sub templates are related to a part of the data structure.
- any change to the data structure will result in the related sub template being rendered.
And that’s it, very simple concept but very powerful and effective.
I have created a JS lib (only 7k uncompressed) following this concept and feel free to fork and test it out.
Let me know how it goes.