Reactive Markdown

Published 2021-02-08

Many of my blog posts contain interactive demos or content that is generated via JavaScript. The prose of my posts, however, is written in Markdown. Shuttling data back and forth from the Markdown side to the JS side can be annoying, especially when data from JavaScript needs to be displayed inline with the markdown and updated in real time.

I've experimented with a few ways. The guiding principle around these experiments is to do as much as possible with as little code as possible.

One approach uses a data-bind attribute to bind an element to a JS variable. This was the approach I took when writing my Gambler's fallacy post.

Data-Bind Example

In the JS - export some vars from JS & bind them to the doc

// export vars
const exported_data = {
  num_coins: NUM_COINS.toLocaleString(),
    coin => render_coin(coin),
  run_chart: render_runs(coins),
  run_length: RUN_LENGTH,
  run_list: render_run_list(coins, run_list),
  num_runs: run_list.length,
// bind to doc

In the Markdown - leave placeholders for where exported variables will be injected

If <i data-bind="run_length"></i> coin tosses produced <i data-bind="run_length"></i> heads,
the next <i data-bind="run_length"></i> ...

The data bind approach has an incredibly simple implementation and is more than efficient enough for blog demos.

Side note - The browser seems to understand when the new innerHTML is equivalent to the old innerHTML and doesn't do any repaints in those cases. Profiling even shows some sort of diffing happening when the new and old innerHTMLs don't match but do overlap. All of this is for a post for another day, however.

data-bind implementation:

function bindit(exported_data) {
  const to_bind = document.querySelectorAll('[data-bind]');
  for (e of to_bind) {
    const x = e.dataset.bind;
    e.innerHTML = exported_data[x];

Readers that like such a simplistic approach to rendering JS values into HTML would love my 150 line TODO-MVC app which is written in pure JS.

The data-bind approach is super simple but:

  1. It is a bit verbose to write out <i data-bind="..."></i> every time you want to refer to a variable.
  2. The data being exported from JS has to be display ready. This can lead to a ton of exports if the same data is re-exported under many formats. I'd rather display-format the exported data in the Markdown doc, putting all display time concerns in one spot.

Given I'll be writing lots of blog posts, and thus spending lots of time writing the <i data-bind"..."></i> boilerplate as well as iterating on converting data to be "display ready", it makes sense to invest more into an ergonomic API.

Side note - investing in software infrastructure is an optimization problem. Under ideal circumstances: the more you invest in infra, the faster the work on top of it goes. If there is a limited amount of work to be done on top of that infra, however, the infra investment should be bounded. Once infra is "good enough", continued investments start producing diminishing returns (unless there is a paradigm shift. A post for another day.).

Reactive Markdown

My latest iteration to make authoring interactive blog posts easier is Reactive Markdown.

Reactive Markdown is the idea that your markdown can reference JavaScript variables and expressions. Whenever the underlying data being referenced changes, your Markdown document updates automtically to incorporate the new values or expressions.

Simplified example -

JS Markdown
setInterval( () => x += 1, 500, );
# the count is $x


The first version of reactive markdown is "JavaScript first." In other words, you author everything in a JS file. The next phase will likely end up being an extension, or re-write, of a markdown parser to make this "markdown first."

Why? A javascript-first is slightly awkward due to having to stick markdown into JS templates and, when writing a blog entry, the majority of characters being written are Markdown not JS.

Anyway, on to some actual reactive markdown!

Use It

The library is currently called "publisher" and is available here.

If/when it matures I'll likely split it out into its own repo and publish to npm.