I have an interactive tutorial that takes you step by step to make an interactive tutorial in d3.js and another one in vue.js. However, the code for those is more involved than I’d like, so I wanted to make something even simpler. I wrote the same example with several libraries and made some notes for myself. I often use these as a starting point when I’m working on a project.
1 Vanilla javascript#
No libraries, no build step.
Notes:
- Output is imperative style.
- Use createElement and appendChild, with setAttribute to set attributes and createTextNode to set text.
- Create and update are separate code. You have to keep track of whether something’s already created.
- Input is with addEventListener and callback functions.
- Redraw is explicit. Call redraw every time you change data.
- Modularity through your own abstractions in code.
A simpler alternative to createElement etc. is to construct a string and set innerHTML
. This is nice unless you have event listeners on those elements. Setting innerHTML
creates new elements. If you are dragging the mouse based on an event handler, and the mouse drag replaces the elements, that mouse drag might be running on an element that no longer exists, and that can be tricky to debug. You’ll also have to be careful to escape your HTML correctly if you are setting innerHTML
.
2 Lit-html v1#
Small output library (11k), no build step required.
Notes:
- Output is template style, described with html`…` in html syntax in the javascript file.
- Use attr=${value} to set attributes and ${value} to set text.
- Use javascript logic for branch/loop in templates.
- Create and update are unified. The system keeps track of whether something’s already created.
- Input is with @click in the html to attach a click event handler.
- Redraw is explicit. Call redraw() every time you change data.
- Modularity through your own abstractions in code.
Tricky:
- need to use svg`` for svg elements instead of html``.
- in html syntax, tags don’t self-close, so <element /> is only the open tag, and you still have to close it with </element>.
- there’s no bundled version of the library, so it loads slower than other libraries. It’s possible to bundle it yourself with Snowpack’s esinstall[3] but frustrating that it doesn’t come with a bundle like Vue does.
Haven’t yet investigated Lit v2:
- combines lit-element and lit-html
- reactive properties[4]: when you change the property, it re-renders the element. Only works for top level property changes, not changes to nested properties or array elements, so you still have to manually re-render for anything other than the top level.
3 Vue#
Medium sized input&output library (80k), no build step required.
Notes:
- Output is template style, described with html syntax in the html file or in the javascript file.
- Use :attr="value" to set attributes and {{value}} to set text.
- Use v-if and v-for for branch/loop in templates.
- Create and update are unified. The system keeps track of whether something’s already created.
- Input is with @click in the html to attach a click event handler.
- Redraw is automatic for state shared with Vue. Run this.x = 5 and Vue will automatically redraw. If you pass an object into Vue, you can still modify the state outside of Vue and it will pick it up.
- Modularity through custom elements like <my-gridworld>.
Tricky:
- need to use view-box.camel instead of viewBox because of naming convention differences (js uses camel case; html uses kebab case, except for this property); only applies when writing the template in the html, and not when writing the template in javascript.
- Vue 2 has some limitations on automatic tracking of updates. It tracks assignment to object fields expr.field = … including when the objects are inside other objects or arrays. It does not track assignment to array elements expr[index] = … but does handle array methods such as
push
andsplice
. Vue 2 does not know about the newSet
orMap
types. Vue 3 handles all of these situations. - Vue 3 is not backwards compatible with existing Vue 2 code; the Vue 2 code has to be migrated to work with Vue 3.
4 React#
Medium sized input&output library (116k), build step strongly recommended to convert code into regular Javascript.
Demo and code[7] (demo doesn’t use the build step)
Notes:
- Output is template style, described with xml syntax (jsx) in the javascript file.
- Use attr={value} to set attributes and {value} to set text.
- Use javascript logic for branch/loop in templates.
- Create and update are unified. The system keeps track of whether something’s already created.
- Input is with onClick in the html to attach a click event handler.
- Redraw is automatic for state kept inside React’s components. Call this.setState({x: 5}) and React will redraw. Redraw is manual for state kept outside of React. Copy it into React every time you change something so that React will see it.
- Modularity through custom elements like <Gridworld>.
- React Native lets you build native apps (especially for phones).
Tricky:
- need to use the javascript names of elements and attributes instead of the html names. For example, use className=… instead of class=… like you would in html.
- need to convert SVG kebab case attributes like fill-opacity=… into camel case for JSX like fillOpacity=… because JSX doesn’t support the original names. This means you can’t copy an SVG file from a visual editor into JSX and have it work.
- there are also other html elements that have to be changed to work with React, such as for=… becoming htmlFor=… and tabindex=… being changed to tabIndex=… ; see list[8].
- some but not all input elements need to use onChange=… instead of onInput=… like HTML5 uses, and there doesn’t seem to be a way to get HTML5’s
onChange
. - when using the components, setState({x: 5}) doesn’t immediately update
x
to 5 (this is unlike Vue, which does immediately update, making the logic simpler)
5 Preact + htm#
Small input&output library (10k), no build step needed if also using HTM (2k).
Demo and code[9] (demo doesn’t use the build step)
Notes:
- Output is template style, described with html`…` in xml syntax in the javascript file.
- Use attr=${value} to set attributes and ${value} to set text.
- Use javascript logic for branch/loop in templates.
- Create and update are unified. The system keeps track of whether something’s already created.
- Input is with onClick in the html to attach a click event handler.
- Redraw is automatic for state kept inside Preact’s components. Call this.setState({x: 5}) and React will redraw. Redraw is manual for state kept outside of React. Copy it into React every time you change something so that React will see it.
- Modularity through custom elements like <Gridworld>.
Preact is like React, but without the tricky items I listed under React: it allows html names like class=… rather than javascript names like className=…; and it allows svg names like fill-opacity=… rather than fillOpacity like React requires. This means you can use an SVG visual editor and export it directly into Preact.
It normally uses JSX like React does but Preact’s HTM is like lit-html’s format, without the tricky items I listed under lit-html: you don’t have to have both html`` and svg``; the templates support xml syntax; and there’s a prebundled version of the library.
Tricky:
- like React, setState doesn’t trigger right away
6 Svelte#
Medium sized input&output library, build step required to convert Svelte code into regular Javascript
TODO: demo
Notes:
- Output is template style, described with html syntax in its own Svelte code file.
- Use attr={value} to set attributes and {value} to set text.
- Use #{if} and {#each} for branch/loop in templates.
- Create and update are unified. The system keeps track of whether something’s already created.
- Input is with on:click in the html to attach a click event handler.
- Redraw is automatic for state kept inside Svelte. It tracks top level changes to your underlying data and automatically redraws.
- Modularity through custom elements like <Gridworld>.
Tricky:
- Svelte tracks changes based on assignment statements, but not methods like
push
.
7 ObservableHQ#
Notebook style interface, where top level definitions become reactive in other expressions. Think spreadsheets.
Demo[10] partially implemented ; see source by clicking to the left of any cell.
- Output is template style, described with html`…` in the notebook
- Use attr=${value} to set attributes and ${value} to set text.
- Redraw is automatic for top-level definitions.
Tricky:
- custom viewof syntax if you want to have output cells also provide input (e.g. drawing on the grid)
8 My thoughts#
The main idea with templates is that instead of writing commands to generate html, we describe the html we want, with some placeholders for values that come from Javascript values. For example:
<rect fill=red x=${col} y=${row} width=1 height=1 />
Compare this to the vanilla approach:
let rect = document.createElementNS("http://www.w3.org/2000/svg", 'rect"); rect.setAttribute("fill", "red"); rect.setAttribute("x", col); rect.setAttribute("y", row); rect.setAttribute("width", 1); rect.setAttribute("height", 1); svg.appendChild(rect);
or the d3.js approach:
let rect = svg.append("rect") .attr("fill", "red") .attr("x", col) .attr("y", row) .attr("width", 1) .attr("height", 1);
I find templates to be a big win. The major libraries in this space (React, Vue, Svelte, Preact, lit-html) all use templates, but the details differ.
<!-- react/preact/vue with jsx --> <rect fill=red x={col} y={row} width=1 height=1 /> <!-- vue templates --> <rect fill=red :x="col" :y="row" width=1 height=1 /> <!-- lit-html, and react/preact with htm --> <rect fill=red x=${col} y=${row} width=1 height=1 />
There’s some difference in how the templates are written. React uses an extension of Javascript called JSX to allow you to write html in your Javascript. You run a compiler to translate that into regular Javascript. Vue reads HTML from your document, or in strings in the source code. Lit-html uses a relatively new feature, Javascript template literals. Preact normally uses JSX but there’s an option to use HTM template literals. Svelte uses its own file format that is compiled into regular Javascript.
In addition, React, Preact, Vue, and Svelte offer a component system that allows you to create custom elements like <GridWorld> that are then expanded into HTML. Lit-html doesn’t do this, and instead leaves that to a separate library, LitElement. For my small projects, the component system doesn’t help me, as I can use regular Javascript functions and classes instead. However, for larger projects, it provides some modularity and also allows you to reuse components that others have written. LitElement uses standard web components that can be used with any other system, whereas React, Preact, Vue, Svelte components can only be used within their own system.
https://component-party.dev/[11] shows a comparison of the syntax used across Svelte, React, Vue, and others.
Tricky: in some of these template systems, it is hard to programatically construct the html in certain ways. For example in lit-html the tag name has to be specified statically.