Serving b8r

In a former life I worked on optimizing delivery of a fairly large website. I won’t pretend I understood a fraction of the detail, but I had a pretty good idea of the big picture and in a couple of places I drilled down to the bottom-most details.

This isn’t new to anyone who pays attention, but scale makes simple things hard.

The basic tricks to getting a web page to load fast are:

Do as little as possible:

  • Make everything as small as possible.
  • Make everything as simple as possible.
  • Be as asynchronous as possible.

Do it as infrequently, fast, and in parallel as possible:

  • Minimize the round-trip time from client to server.
  • Parallelize everything as much as possible.
  • Split stuff up enough to make it parallel, but not so much as to increase the number of round-trips. (To some extent, SPDY/http2 is solving this for us.)
  • Minimize the number of round trips.
  • Maximize cache utilization.

The grandparent of bindinator was bind-o-matic, which was not designed with all of these things in mind. In particular, it made no real attempt to leverage parallelism or asynchony. When Craig, Josiah, and I wrote bindinator, the “state of the art” was:

  • Figure out what your dependencies are.
  • Compile them into a big blob.
  • Minify the blob.
  • Give the client the blob.

Bind-o-matic’s approach was: “be so small and light that you don’t need to do clever shit to get good performance” (during development) because you can always do that later. We actually compiled our LESS on the client and it didn’t cause performance problems (once we forked less.js and sped it up a bit, and cached compiled CSS in localStorage).

While almost any javascript web application architecture can be served thus, more fine-grained optimization (e.g. trying to become interactive as fast as possible) is a tougher problem, especially when you have little or no ability to do less (e.g. suppose hypothetically that almost every person in your organization is incentivized to make your application bigger, slower, and more complex…)

And you might be committed to using a big, complicated framework that is virtually guaranteed to make anything it touches, bigger, more complex, and less asynchronous.

Anyway, I designed b8r to be as small, simple, and asynchronous as possible but left delivery optimization “for later”. I assumed I could just point webpack (or webpack2) or some more sophisticated tool (such as the stuff I had worked on) at anything I built later.

I did do one thing, though.

I wrote my own require implementation because I started reading the documentation for existing implementations and my eyes glazed over. In particular, none of them seemed as straightforward to use as the one I’d gotten used to in my former life (and with which I had deep familiarity).

I used to be able to write:

const foo = require('foo'); // don't even need to know the path to foo!

I think not needing to know the (relative) path to foo is an anti-feature by the way. The point is that this kind of thing normally needs a build-process to work, which adds a bunch of pain to development. I want the good part — require that just works — and no bad part.

Now, my require is purely client-side, which means that it is a big performance problem. Consider the following code:

const foo = require('path/to/foo.js');
foo(17);

The call to require must be synchronous. But what does require do behind the scenes?

  1. Use an XMLHttpRequest to pull “foo.js” from the server.
  2. Wrap a Function instance around the code inside it.
  3. Pass an object to the function.
  4. Return the “exports” property of the object when the function returns.

It really doesn’t matter how asynchronous your code is if every dependency involves halting execution while round-tripping to your server… recursively in the case of a file that, itself, has dependencies.

This behavior actually throws warnings in Chrome…

Chrome no likee
Chrome no likee

Chrome only complains about this the first time it happens, but it happens a lot.

Now the solution for this is to compile your javascript code on the server and deliver some kind of optimized blob — site.min.js say. This is exactly what tools like webpack do — they actually watch your code tree and trigger recompiles on-the-fly. Webpack offers a dev server that actually sets up a backchannel to the client and refreshes the browser automagically when there are code changes.

Sounds pretty good, right? — but it’s about 1/10 as responsive as using b8r and just forcing refresh. I fucking hate having to compile my code all the time, even if all the compiler does is walk a tree and concatenate a shitload of files wrapped in function calls and assignment statements and then call uglifyjs.

But that’s on a local dev server. What happens when you stick this code on a real server and the round-trip goes from ~0ms to ~100ms? It turns out that on the project I’m working on it changes my web application’s spin-up (with nothing cached) from ~600ms to ~1500ms. (Aside: this is a real web application with a shitload of functionality talking to production servers with no back-end optimization. In my past life, loading in ~1500ms from a real server would have caused spontaneous orgasms. When I told people that performance like this was achievable I was assumed to be a naive fool. No, I’m not bitter!)

So, how to do all this stuff on the client?

  1. Make b8r’s use of require asynchronous. E.g. b8r synchronously loads b8r.dom.js before it finishes loading, so load its dependencies asynchronously before loading b8r itself asynchronously.
  2. Get require to warn whenever it loads a module synchronously. Ick.
  3. OK, get require to return a JSON structure of synchronously loaded modules and load them asynchronously before doing anything else.
  4. Repeat step 3 until no warnings. (This took three iterations.)

Loading time from the stage server went from ~1500ms to ~600ms with no server-side optimization whatsoever. Not bad for a late night hack.

Now wouldn’t it be nice if this were all automatic?

I started writing this post on Friday evening, but my first stab at automating this didn’t work and my brain was too fried to fix it.

In order to function, require tracks all the modules it loads, and it already replaces itself recursively to handle nested requires (to allow for relative paths in require statements) so all I needed to do was track one level of dependencies, and then generate a list of preload “waves” where each wave comprises all modules with no unloaded dependencies. (Circular dependencies will be detected and throw errors.)

Oh, and this eliminates the need for b8r to do anything clever internally. The new solution is general and fixes b8r as well as everything else.

const preload_data =/* data from require.preloadData() */;
require.preload(preload_data).then(() => {
  /* ... */
});

So, now the steps are:

  1. Run require.preloadData() in the console, which spits out JSON data.
  2. Now call require.preload(), passing the data from step 1, which will generate a promise of everything loaded asynchronously.

If dependencies change, everything will still work, but dependencies that are force a synchronous request will generate console warnings.

As a nice bonus, this improves the load time of the b8r demo page by over 80%.

And One More Thing…

If you don’t mind your web app not loading asynchronously the first time, I’ve added another feature to bindinator (and it’s used in he demo page)…

require.autoPreload(10000).then(() => { 9/* ... */ });

This automates all of the above, storing preloadData() in localStorage (the number is the delay in milliseconds before preloadData is written to localStorage; the default is 2000). If code changes cause a file to load synchronously or break the page load, the preloadData is updated or destroyed as appropriate.

So, the first time you load the page it looks like this:

Browser network tab showing b8r files loading one-at-a-time

But from then on it looks like this:

network tab shows b8r files loading in parallel "waves"

You may notice that the last few items are sequential. These are chained promises. At some point I’ll add components to preloadData…

HyperCard, Visual Basic, Real Basic, and Me

When the Mac first appeared it was a revelation. A computer with a powerful, consistent user-interface (with undo!) that allowed users to figure out most programs without ever reading a manual.

I can remember back in 1984 sitting outside the Hayden-Allen Tank (a cylindrical lecture theater on the ANU campus that tended to house the largest humanities classes and many featured speakers) playing with a Mac on display while Apple reps inside introduced the Mac to a packed house. (My friends and I figured we’d rather spend time with the computer than watch a pitch.)

How did undo work? It wasn’t immediately obvious.

When we quit an application or closed a document, how did the program know we had unsaved changes? We checked, if the document had no changes, or the changes were saved, the computer knew.

We were hardcore math and CS geeks but computers had never, in our experience, done these kinds of things before so it took us a while to reverse-engineer what was going on. It was very, fucking, impressive.

But it was also really hard to do with the tools of the time. Initially, you couldn’t write real Mac software on a Mac. At best, there was MacPascal, which couldn’t use the toolbox and couldn’t build standalone applications, and QuickBasic, which provided no GUI for creating a GUI, and produced really clunky results.

To write Mac programs you needed a Lisa, later a Mac XL (same hardware, different software). It took over a year for the Mac SDK to appear (via pirate copies), and it was an assembler that spanned multiple disks. Eventually we got Consulair-C and Inside Macintosh but, to give you an idea, the equivalent of “hello world” was a couple of pages of C or Pascal most of which was incomprehensible boilerplate. The entire toolbox relied heavily on function pointers, really an assembly-language concept, and in some cases programmers had to manually save register state.

No-one’s to blame for this — Xerox provided much cleaner APIs for its much more mature (but less capable) GUI and far better tooling — the cost was a computer that ran dog slow, no-one could afford, and which actually was functionally far inferior to the Mac.

The first really good tool for creating GUI programs was HyperCard. I can remember being dragged away from a computer lab at ADFA (where a friend was teaching a course on C) which had been stocked with new Mac SEs running HyperCard.

For all its many flaws and limitations, HyperCard was easy to use, fast, stable, and forgiving (it was almost impossible to lose your work or data, and it rarely crashed in an era when everything crashed all the time). Its programming language introduced a yet-to-be-equalled combination of being easy to read, easy to write, and easy to debug (AppleScript, which followed it, was horribly inferior). When HyperCard 2 brought a really good debugger (but sadly no color) and a plugin architecture, things looked pretty good. But then, as Apple was wont to do in those days, Apple’s attention wandered and HyperCard languished. (Paul Allen’s clone of HyperCard, Toolbook for Windows, was superb but it was a Windows product so I didn’t care.)

Eventually I found myself being forced to learn Visual Basic 3, which, despite its many flaws, was also revolutionary in that it took HyperCard’s ease of use and added the ability to create native look and feel (and native APIs if you knew what you were doing, which I did not). With Visual Basic 3 you could essentially do anything any Windows application could do, only slower. (HyperCard was notably faster than VB, despite both being interpreted languages, owing to early work on JIT compilers.)

After using VB for a year or two, I told my good friend (and a great programmer) Andrew Barry that what the Mac really needed was its own VB. The result was Realbasic (now Xojo) of which I was the first user (and for a long time I ran a website, realgurus.com, that provided the best source of support for Realbasic programmers). Realbasic was far more than a VB for the Mac since it was truly and deeply Object-Oriented and also cross-platform. I could turn an idea into a desktop application with native look and feel (on the Mac at least) in an evening.

When MP3 players started proliferating on Windows, I wrote an MP3 player called QuickMP3 in a couple of hours after a dinner conversation about the lousy state of MP3 players on the Mac. By the next morning I had a product with a website on the market (I distributed it as shareware; registration was $5 through Kagi — RIP — which was the lowest price that made sense at the time, I think Kagi took about $1.50 of each sale, and I had to deal with occasional cash and checks in random currencies).

Over the years, I wrote dozens of useful programs using Realbasic, and a few commercially successful ones (e.g. Media Mover 3,  and RiddleMeThis) and an in-house tool that made hundreds of thousands of dollars (over the course of several years) with a few days’ effort.

Today, I find Xojo (which Realbasic rebranded itself to) to have become bloated, unstable, and expensive, and Xojo has never captured native look and feel in the post-Carbon world on the Mac, and anything that looks OK on Windows looks like crap on the Mac and vice versa, which undercuts its benefits as a cross-platform application. Also, my career has made me an expert on Javascript and web development.

So my weapon of choice these days for desktop development became nwjs and Electron. While web-apps don’t have desktop look and feel (even if you go to extremes with frameworks like Sproutcore or Cappuccino), neither do many desktop apps (including most of Microsoft’s built-in apps in Windows 10). Many successful commercial apps either are web apps (e.g. Slack) or might as well be (e.g. Lightroom).

I mention all of this right now because it closes the loop with my work on bindinator — anything that makes web application development faster and better thus helps desktop application development. I think it also clarifies my design goals with bindinator: I feel that in many ways ease of development peaked with Realbasic, and bindinator is an attempt to recreate that ease of development while adding wrinkles such as automatic binding and literate programming that make life easier and better.

Heterogeneous Lists

b8r's demo site uses a heterogeneous list to display source files with embedded documentation, tests, and examples
b8r’s demo site uses a heterogeneous list to display source files with embedded documentation, tests, and examples

One of the things I wanted to implement in bindinator was heterogeneous lists, i.e. lists of things that aren’t all the same. Typically, this is implemented by creating homogeneous lists and then subclassing the list element, even though the individual list elements may have nothing more in common with one another than the fact that, for the moment, they’re in the same list.

This ended up being pretty easy to implement in two different ways.

The first thing I tried was a effectively an empty (as in no markup) “router” component. Instead of binding the list to a superclass component, you bind to a content-free component (code, no content) which figures out which component you really want programmatically (so it can be arbitrarily complex) and inserts one of those over itself. This is a satisfactory option because it handles both simple cases and complex cases quite nicely, and didn’t actually touch the core of bindinator.

Here’s the file-viewer code in its entirety:

<script>
    switch (data.file_type || data.url.split('.').pop()) {
        case 'md':
        case 'markdown':
            b8r.component('components/markdown-viewer').then(viewer => {
                b8r.insertComponent(viewer, component, data);
            });
            break;

        case 'text':
            b8r.component('components/text-viewer').then(viewer => {
                b8r.insertComponent(viewer, component, data);
            });
            break;

        case 'js':
            b8r.component('components/literate-js-viewer').then(viewer => {
                b8r.insertComponent(viewer, component, data);
            });
            break;
    }
</script>

(I should note that this router is not used for a list in the demo site, since the next approach turned out to meet the specific needs for the demo site.)

The example of this approach in the demo code is the file viewer (used to display markdown, source files, and so on). You pass it a file and it figures out what type of file it is from the file type and then picks the appropriate viewer component to display it with. In turn this means that a PNG viewer, say, need have nothing in common with a markdown viewer, or an SVG viewer. Or, to put it another way, we can use a standalone viewer component directly, rather than creating a special list component and mixing-in or subclassing the necessary stuff in.

You’ll note that this case is pretty trivial — we’re making a selection based directly on the file_type property, and I thought it should be necessary to use a component or write any code for such a simple case.

The second approach was that I added a toTarget called component_map that let you pick a component based on the value of a property. This maps onto a common JSON pattern where you have a heterogeneous array of elements, each of which has a common property (such as “type”). In essence, it’s a toTarget that acts like a simple switch statement, complete with allowing a default option.

The example of this in the demo app is the source code viewer which breaks up a source file into a list of different kinds of things (source code snippets, markdown fragments, tests, and demos). These in turn are rendered with appropriate components.

This is what a component_map looks like in action:

<div
  data-list="_component_.parts"
  data-bind="component_map(
    js:js-viewer|
    markdown:markdown-viewer|
    component:fiddle|
    test:test
  )=.type"
>
  <div data-component="loading"></div>
</div>

From my perspective, both of these seem like pretty clean and simple implementations of a common requirement. The only strike against component_map is obviousness, and in particular the quasi-magical difference between binding to _component_.parts vs. .type, which makes me think that while the latter is convenient to type, forcing the programmer to explicitly bind to _instance_.type might be clearer in the long run.

P.S.

Anyone know of a nice way to embed code in blog posts in WordPress? All I can find are tools for embedding hacks in wordpress.