Skip to main content
Baldur Bjarnason

Notetaking, Tagged Templates, and How Deno is a Clear Improvement Over Node

Baldur Bjarnason
This page was originally published elsewhere, but is republished here for archival purposes.

A while ago, one of my projects reached the point where the initial exploratory prototyping had done its job. I’ve figured out an overall technological approach that will be its foundation—and the time came to begin work on its initial version.

Unlike most of the other projects I’ve done, this is explicitly an experimental research project. I have other projects for paying my bills, which take priority and are the main reason why progress on the project is so slow, but this one is entirely there for the new.

I’m a big fan of genre literature—stories that have boundaries and expectations that the author can play with and challenge, as long as the end result is recognisable as an entry in the genre. Software has its genres too, and this is an experiment in making a new spin on the notetaking or writing app genre.

I set myself a few guidelines:

Figuring out an overall technical approach that would work within the boundaries set by these guidelines took a while. I looked into CRDTs, various syncing strategies such as JMAP or the Dropbox API, but nothing seemed to fit.

It wasn’t until I was forced to take six weeks off work (COVID, followed by a few weeks of brain fog so bad that I was unable to work) that a clear path forward came to me. I came up with an approach that I think is genuinely new: I combined aspects of distributed version control systems and CRDTs in a way that should work within the capabilities of modern web browsers. I have no idea whether it will be usable in practice, but I’ve been having a lot of fun implementing it.

As I said above, this is a research project. Its purpose is to try strange new things and see if they work.

The prototype #

I wrote the initial, very rough prototype in Node.

The prototype proved that the overall idea is implementable even though I don’t yet know whether it’s practical.

This was a very frustrating experience. Making a performant, offline-first web app that nonetheless is lightweight on the front end, is necessarily going to be based on various kinds of Workers (Service, Web, and Shared). Node kind of sucks for writing isomorphic code that’s supposed to run both on the server and in the browser. The Node environment, even after recent efforts to add support for more browser-compatible features, is subtly incompatible with the browser environment in several ways. When you use Blob or streams, Typescript will complain because it’s trying to reconcile two incompatible environments.

Accommodating the two is full of tiny points of friction that drag the work. I had to switch most modules that imported isomorphic dependencies to plain JavaScript. This was fine, but it meant that Typescript didn’t benefit the project as much as it could.

For the first dev version that isn’t a proof-of-concept prototype, I wanted to try a new approach that didn’t have the same friction. I considered dropping Typescript entirely. After all, that was the source of most of the friction. I looked at Cloudflare, but they have a strong event horizon of proprietariness (for lack of a better word). Its proprietary services (like Durable Objects) exert a strong pull that inevitably leads you to build apps that only work with their systems. This would be fine if I trusted Cloudflare’s management, but I don’t, so it’s not.

I then had another look at Deno, tested it out by porting a bit of code, and it turned out to be perfect for this sort of project. There is little friction in developing isomorphic code, even with Typescript.

Deno #

Working with Deno has, so far, a few benefits beyond “works a bit more like a browser”.

Because it has built-in tools for testing (with snapshots and coverage), linting, formatting, benchmarking, and generating documentation, there is much less variation from project to project than with Node. For any given Deno project, you can expect to be familiar with all of the tools it’s using. None of the “uvu? WTF is uvu?” kind of surprises you often get when checking out a Node project.

This, in turn, simplifies project and work environment setup. There’s a Deno plugin for Visual Studio Code, for example, that does a great job of integrating all of Deno’s tools with VS Code. There is much less complexity in getting up and running from scratch with Deno than with Node. No faffing about. When in doubt, it’s probably going to work the same as the browser.

With esbuild for deno and the http-fetch plugin, you get Deno-compatible bundling for the browser.

(I would forgo bundling for the front end if it weren’t for Firefox, which is the only major browser that hasn’t shipped support for modules in workers, and this project is out of necessity heavy on workers.)

A small experiment I put together a couple of days ago demonstrates a few of Deno’s advantages.

I had the idea that reliance on ternary operators was one of the things holding JS tagged templates back.

Maybe something like this:

html`<html>
  <head>
    <title></title>
    <meta charset="UTF-8" />
  </head>
  <body>
    <p>Test text</p>
    ${IF(secondary)}
    <p>${trusted(secondary)}</p>
    ${ELSEIF(text)}
    <p>${text}</p>
    ${ELSE()}
    <p>Otherwise</p>
    ${END()}
  </body>
</html>`;

Would be more readable than this:

html`<html>
  <head>
    <title></title>
    <meta charset="UTF-8" />
  </head>
  <body>
    <p>Test text</p>
    ${secondary
      ? html`<p>${trusted(secondary)}</p>`
      : text
      ? html`<p>${text}</p>`
      : html`<p>Otherwise</p>`}
  </body>
</html>`;

This wasn’t going to work for DOM-based tagged template libraries like uhtml or lit-html but should be doable for generating HTML strings.

So, I put together a naive but straightforward benchmark using Deno’s benchmarking tool and compared a few pre-existing approaches. Important comparison points were existing string template libraries that didn’t use tagged templates and tools based on JSX that integrate more ergonomically with JavaScript control flow (albeit in a non-standard way):

The JSX-based approaches were 5-40x slower than any of the string template approaches, which all performed roughly the same.

Then I forked one of the tagged template modules and implemented the control logic, with tests, while keeping one eye on the benchmarking results to make sure I wasn’t making a big performance mistake somewhere.

Doing this in Node would have added friction to multiple steps in this experiment. It would have immediately become A Bit of A Project, just from having to choose benchmarking and testing tools.

In the end, I got a small utility that implemented the conditional rendering I’d hoped for while also:

You can find the end result here, extracted into its own repository.

All of this is a long way of saying that working with Deno is a joy. I recommend it highly. It isn’t a question of one thing or the proverbial ‘killer app’ but of a large number of sensible decisions where the overall experience is improved in thoughtful ways.

I’m definitely going to choose Deno for new projects in the future, whenever I can.