Migrating b8r code to the New World Order

I’ve got three small projects built with b8r lying around (one is something I did for a job interview a few months ago, so it’s not in a public repo). Anyway, all three simply load the version of b8r in the github master directly which means they broke hard when I pushed my latest changes.

Two of the projects are my galaxy generator (which is heavy on libraries but has no components) and my dnd spell list.

Anyway, updating the galaxy generator took about five minutes. Updating the dnd spell list took about thirty seconds. If I recall correctly, this is the second change to b8r in roughly two years that has required fixing the galaxy generator. (Obviously this should not matter, since only a crazy person would load dependencies into production code this way, but I don’t take breaking changes lightly.)

I’ve updated bindinator.com and attempted to document as much as possible about the migration process. Finally, I’ve replaced require.js with a tiny module that shows an alert() and redirects the user to the improved documentation on migrating to import.

More on web-components and perf

tl;dr web-components are only slow if they have a shadowRoot.

Here’s an updated version of the previous graph, showing two new data points — one where I replace two spans with a simple b8r component, and another where I modified makeWebComponent to allow the creation of custom-elements without a shadowRoot (and hence without styling).

My intention with the upcoming version of b8r is to replace b8r’s components with web-components, and my previous test showed that web-components were kind of expensive. But it occurred to me I hadn’t compared them to b8r components, so I did.

In a nut, a b8r component is about as expensive as a web-component with a shadowRoot (the b8r component in question was styled; removing the style didn’t improve performance, which isn’t surprising since b8r deals with component styles in a very efficient way), and a web-component without a shadowRoot is just plain fast. This is great news since it means that switching from b8r components (which do not have a shadow DOM) to web-components is a perf win.

D&D5e, Web-components, & Performance

tl;dr — web-components (edit: with shadowRoots) are kind of slow.

The graph shows the average of five trials (post warmup) in Chrome on my Macbook Pro (it’s a maxed-out 2015 model). I saw nothing to suggest that a larger number of trials would significantly change the results.

Over the long weekend I was hoping to play some D&D with my kids (it never ended up happening). One of the things I find pretty frustrating with the new (well, 2014) Players Handbook is that the spells are all mixed together in strict alphabetical order. (It’s not like earlier editions were better organized. It’s also not clear how they could really fix this, since any arrangement will either require repeating spell descriptions or some kind of tedium.) So to figure out what spells are available to, say, a first level Druid, and what they do, you need to do a lot of page-flipping and cross-referencing.

As an aside, in the late-lamented DragonQuest (SPI, 1980), magic is divided up into “colleges” with a given character having access to the spells of only one college. Each college, along with its spells, is described in one place, with the only page-flipping being required for a relatively small number of spells that are common between colleges. A typical college has access to 20-30 different spells which are thematically unified (e.g. Adepts of the College of Fire have fire-related spells).

This kind of organization is not possible for D&D because the overlap between classes is so great. Sorcerors essentially get a subset of Wizard spells, as do Bards. And so on.

So it struck me that some kind of easily filterable list of spells which let you decide how you wanted them sorted and what you wanted shown would be handy, and this seemed like a nice test for b8r’s web-component system.

Now, b8r is the third rewrite of a library I originally developed with former colleagues for USPTO. Initially we had a highly convoluted “data-table” (not an original name, of course) that essentially let you point a descriptive data-structure at an array and get a table with all kinds of useful bells and whistles. This quickly became unmanageable, and was particularly horrible when people started using it to create quite simple tables (e.g. essentially short lists of key/value pairs) and then add fancy behaviors (such as drag-reordering) to them.

So, I developed a much simpler binding system (“bind-o-matic”) that allowed you to do fine-grained bindings but didn’t try to do bells and whistles, and it quickly proved itself better even for the fancy tables. b8r‘s data-binding is the direct descendant of bind-o-matic.

Anyway, all-purpose tables are hard. So I looked around for something off-the-shelf that would just work. (I’m trying to purge myself of “not invented here” syndrome, and one of the joys of web-components is that they’re supposed to be completely interoperable, right?) Anyway, the closest thing I could find (and damn if I could find a compelling online demo of any of them) had been abandoned, while the next closest thing was “inspired by” the abandoned thing. Not encouraging.

So I decided to write a minimal conceptual set of custom elements — in essence placeholder replacements for <table> <tr> <td> and <th> — which would have zero functionality (they’d just be divs or spans with no behavior) that I could later add CSS and other functionality to in order to get desired results (e.g. the ability to switch a view from a virtual grid of “cards” to a virtual table with a non-scrolling header and resizable columns — i.e. “the dream” of data-table implementations, right?

Now, I have implemented a function named makeWebComponent that will make you a custom element with one line of code, e.g. makeWebComponent('foo-bar', {}). So I made a bunch of components exactly that way and was horrified to see that my test page, which had been loading in ~1000ms was suddenly taking a lot longer. (And, at the time it was loading b8r from github and loading the spell list from dnd5eapi.co and then making an extra call for each of the 300-odd spells in the list.)

So, I ended up implementing my interactive D&D spell list using old school b8r code and got on with my life.

Hunting down the problem

I’m pretty enthusiastic about web-components, so this performance issue was a bit of a shock. So I went to the bindinator benchmark page (which lets me compare the performance of b8r to special-purpose vanilla javascript code doing the same thing with foreknowledge of the task.

In other words, how fast is b8r compared with javascript hand-coded to do the exact same thing in a completely specific way? (An example of how unfair the comparison can be is swapping two items in a large array and then updating the DOM vs. simply moving the two items.)

So the graph at the top of the page compares creating a table of 10,000 rows with a bunch of cells in each row using vanilla js to b8r, and then the other three columns show b8r with two simple <a> tags replaced with <simple-span>, where <simple-span> is defined using makeWebComponent vs. hand-coded (but still with a style node) vs. hand-coded (with nothing except a slot to contain its children).

class SimpleSpan extends HTMLElement {
  constructor() {
    super();

    // const style = document.createElement('style');
    // style.textContent = ':host { font-style: italic; }';
    const shadow = this.attachShadow({mode: 'open'});
    const slot = document.createElement('slot');
    // shadow.appendChild(style);
    shadow.appendChild(slot);
  }
}
window.customElements.define('simple-span', SimpleSpan);

Above is the hand-coded <simple-span> so you can see exactly what I’m doing and second-guess me. The commented-out lines are the difference between the fourth and fifth columns.

I should add that I tried rewriting the above code to use cloneNode(true) instead of creating the new nodes in the constructor, but performance was no different in this case. If the nodes being created were more complex there would likely be an advantage. (I make extensive use of cloneNode(true) in b8r since past experimentation showed a benefit, and makeWebComponent makes use of it.)

I should add that even the simplest component made with makeWebComponent has a style node because Google’s best practices suggest that all custom elements should support the hidden attribute, and doing this with styles is by far the simplest (and I would hope the most performant) way to do this.

It also occurred to me that having the <style> contain :host { font-style: italic; } might be more expensive that :host([hidden]) { display: none; } but, again, that was a wash. Similarly, it occurred to me that initializing the shadowRoot as {mode: 'closed'} might help. It didn’t.

So, this appears to show that the overhead for just two trivial custom elements in each of 10,000 rows is comparable to the overhead for b8r in its entirety for the entire table row. Now, b8r is creating a given table row by cloning, ad-hoc, the hierarchy it’s using as a template, and then querying that hierarchy to find bound elements and then inserting values as appropriate.

When you consider the kinds of uses to which browsers put custom elements internally (e.g. <video> and <input type=”data”>) these are generally not something you’re going to have 20,000 of in the DOM at once. It’s easy, however, to imagine wanting every item in a pretty huge list to include a custom checkbox or popup menu. It’s worth knowing that even simple custom elements have a non-trivial overhead (and we’re talking 1ms for 20 of them on a fast, high-end laptop).

Addendum

I didn’t realize that you could create a web-component with no shadowRoot. (This isn’t exactly something anyone makes obvious — I’ve found literally zero examples of this in tutorials, etc.)

If you do this, the performance issue mostly goes away.

Now, you do lose all the “benefits” of the shadow DOM but you gain performance and still can attach style rules to custom (unique!) tagNames, which makes managing the resulting CSS easier. This is particularly nice given my desire to replace b8r components with custom elements, since there needn’t be any performance overhead (you can “seal” the styling in a container with just one shadowRoot rather than ten thousand instances).

Announcing bindinator.js

Having recently set up bindinator.com, I am “officially” announcing my side-project Bind-O-Matic.js bindinator.js. It’s a small (currently 7kB gzipped and minified) Javascript library that is designed to make developing in vanilla javascript better in every way than using one or more frameworks. It embodies my current ideas about Javascript, Web, and UI development, and programming — for whatever that’s worth.

Also, I’m having a ton of fun hacking on it.

By way of “dogfooding”, I’m simultaneously building a skunkworks version of my main work project (which is an Electron-based desktop application) with it, adapting any code I can over to it, building b8r’s own demo environment, and slowly porting various other components and code snippets to it.

Above is my old galaxy generator, updated with a bunch of SVG goodness, and implemented using b8r (it was originally cobbled together using jQuery).

Why another framework?

I’ve worked with quite a few frameworks over the years, and in the end I like working in “vanilla” js (especially now that modern browsers have made jQuery pretty much unnecessary). Bindinator is intended to provide a minimal set of tools for making vanilla js development more:

  • productive
  • reusable
  • debuggable
  • maintainable
  • scalable

Without ruining the things that make vanilla js development as pleasant as it already is:

  • Leverage debugging tools
  • Leverage browser behavior (e.g. accessibility, semantic HTML)
  • Leverage browser strengths (e.g. let it parse and render HTML)
  • Be mindful of emerging ideas (e.g. semantic DOM, import)
  • Super fast debug cycle (no transpiling etc.) — see “leverage debugging tools”
  • Don’t require the developer to have to deal with different, conflicting models

The last point is actually key: pretty much every framework tries to abstract away the behavior of the browser (which, these days, is actually pretty reasonable) with some idealized behavior that the designer(s) of the framework come up with. The downside is that, like it or not, the browser is still there, so you (a) end up having to unlearn your existing, generally useful knowledge of how the browser works, (b) learn a new — probably worse — model, and then (c) reconcile the two when the abstraction inevitably leaks.

Being Productive

Bindinator is designed to make programmers and designers more (and separately) productive, decouple their activities, and be very easy to pick up.

To make something appear in a browser you need to create markup or maybe SVG. The easiest way to create markup or SVG that looks exactly like what you want is — surprise — to create what you want directly, not write a whole bunch of code that — if it works properly — will create what you want.

Guess what? Writing Javascript to create styled DOM nodes is slower, more error-prone, less exact, probably involves writing in pseudo-languages, adds compilation/transpilation steps, doesn’t leverage something the browser is really good at (parsing and rendering markup), and probably involves adding a mountain of dependencies to your code.

Bindinator lets you take HTML and bind it or turn it into reusable components without translating it into Javascript, some pseudo-language, a templating language, or transpilation. It also follows that a designer can style your markup.

Here’s a button:

<button class="awesome">Click Me</button>

Now it’s bound — asynchronously and by name.

<button class="awesome" data-event="click:reactor.selfDestruct">
  Click Me
</button>

When someone clicks on it, an object registered as “reactor” will have its “selfDestruct” property (presumably a function) called. If the controller object hasn’t been loaded, b8r’s event handler will store the event and replay it when the controller is registered.

Here’s an input:

<inpu type="range">

And now its value is bound to the fuel_rod_position of an object registered as “reactor”:

<input type="range" data-bind="value=reactor.fuel_rod_position">

And maybe we want to allow the user to edit the setting manually as well, so something like this:

<input type="range" data-bind="value=reactor.fuel_rod_position">
<input type="number" data-bind="value=reactor.fuel_rod_position">

…just works.

Suppose later we find ourselves wanting lots of sliders like this, so we want to turn it into a reusable component. We take that markup, and modify it slightly and add some setup to make it behave nicely:

<input type="range" data-bind="value=_component_.value">
<input type="number" data-bind="value=_component_.value">
<script>
 const slider = findOne('[type="range"]');
 slider.setAttribute('min', component.getAttribute('min') || 0);
 slider.setAttribute('max', component.getAttribute('max') || 10);
 register(data || {value: 0});
</script>

This is probably the least self-explanatory step. The script tag of a component executes in a private context where there are some useful local variables:

component is the element into which the component is loaded; find and findOne are syntax sugar for component.querySelector and component.querySelectorAll (converted to a proper array) respectively, and register is syntax sugar for registering the specified object as having the component’s unique id.

And save it as “slider-numeric.component.html”. We can invoke it thus:

<span 
  data-component="slider-numeric"
  data-bind="component(value)=reactor.fuel_rod_position"
></span>

And load it asynchronously thus:

const {component} = require('b8r');
component('slider-numeric');

To understand exactly what goes on under the hood, we can look at the resulting markup in (for example) the Chrome debugger:

Chrome debugger view of a simple b8r component

Some things to note: data-component-id is human-readable and tells you what kind of component it is. The binding mechanism (change and input event handlers) is explicit and self-documented in the DOM, and the binding has become concrete (_component_ has been replaced with the id of that component’s instance). No special debugging tools required.

Code Reuse

Bindinator makes it easy to separate presentation (and presentation logic) from business logic, making each individually reusable with little effort. Components are easily constructed from pieces of markup, making “componentization” much like ordinary refactoring.

A bindinator component looks like this:

<style>
  /* style rules go here */
</style>
<div>
  <!-- markup goes here -->
</div>
<script>
  /* component logic goes here */
</script>

All the parts are optional. E.g. a component need not have any actual

When a component is loaded, the HTML is rendered into DOM nodes, the script is converted into the body of a function, and the style sheet is inserted into the document head. When a component is instanced, the DOM elements are cloned and the factory function is executed in a private context.

Debugging

Bindinator is designed to have an incredibly short debug cycle, to add as little cognitive overhead as possible, and work well with debugging tools.

To put it another way, it’s designed not to slow down the debug cycle you’d have if you weren’t using it. Bindinator requires no transpilation, templating languages, parallel DOM implementations, it’s designed to leverage your existing knowledge of the browser’s behavior rather than subvert and complicate it, and if you inspect or debug code written with bindinator you’ll discover the markup and code you wrote where you expect to. You’ll be able to see what’s going on by looking at the DOM.

Maintenance

If you’re productive, write reusable (and hence DRY) code, and your code is easier to debug, your codebase is likely to be maintainable.

Scale

Bindinator is designed to make code scalable:

Code reuse is easy because views are cleanly separated from business logic.

Code is smaller because bindinator is small, bindinator code is small, and code reuse leads to less code being written, served, and executed.

Bindinator is designed for asynchrony, making optimization processes (like finessing when things are served, when they are loaded, and so forth) easy to employ without worrying about breaking stuff.

Core Concepts

Bindinator’s core concepts are event and data binding (built on the observation that data-binding is really just event-binding, assuming that changes to bound objects generate events) and object registration (named objects with properties accessed by path).

Bindinator provides a bunch of convenient toTargets — DOM properties to which you might want to write a value, in particular value, text, attr, style, class, and so forth. In most cases bindings are self-explanatory, e.g.

data-bind="style(fontFamily)=userPrefs.uiFont"

There are fewer fromTargets (value, text, and checked) which update bound properties based on user changes — for more complex cases you can always bind to methods by name and path.

Components are simply snippets of web content that get inserted the way you’d want and expect them to, with some syntax sugar for allowing each snippet to be bound to a uniquely named instance object.

And, finally, b8r provides a small number of convenience methods (which it needs to do what it does) to make it easier to work with ajax (json, jsonp), the DOM, and events.

The Future

I’m still working on implementing literate programming (allowing the programmer to mix documentation, examples, and tests into source code), providing b8r-specific lint tools, building out the standard control library (although mostly vanilla HTML elements work just fine), and adding more tests in general. I’m tracking progress publicly using Trello.