Reactive Markdown
Published 2021-02-08Many 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_chart: coins.map(
coin => render_coin(coin),
).join(''),
run_chart: render_runs(coins),
run_length: RUN_LENGTH,
run_list: render_run_list(coins, run_list),
after_run_heads,
after_run_tails,
num_runs: run_list.length,
...
};
// bind to doc
bindit(exported_data);
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:
- It is a bit verbose to write out
<i data-bind="..."></i>
every time you want to refer to a variable. - 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 theMarkdown
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 |
v0.1
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.