I read a lot. I enjoy reading on black and white e-readers. Unfortunately many websites make it hard to read them on simple devices. https://markdown.download is my really simple solution to that. Prepend it to any website and curl:

curl https://markdown.download/https://dev.to/amnish04/introducing-the-idea-of-web-handlers-in-chatcraft-1b4i

That turns a user-hostile website into a thing that almost anything can render:

It’s amazing how much cleaner the web is when stripped down to essence.

Turns out markdown (or html restricted to what markdown can do) renders fantastic on readers of all types.

Since markdown.download is so utterly simple, I decided to try using it to learn a new-to-me dev tool: val.town. Rest of this post covers what I learned.

I initially was going to publish this project as an npm lib and deploy on cloudflare, but was putting it off because I loathe the amount of bureaucracy that involves. I love the type system in Typescript. I love Node.JS because it’s the least-bad way to write/deploy apps, but much room for improvement remains. My spare time is too valuable to spend it suffering.

val.town: brutally efficient microservice dev-ux

I had the pleasure of meeting Steve Krouse at the val.town HQ. I’ve seen him tweet about val.town, but never really understood why I should use it.

Everything clicked once I asked Steve to help me through getting an early ver of markdown.download to run on val.town. Watching him use it finally made val.town click for me: val.town is a way to write single-file microservices in production. It’s like an executable github gist. All I have to do to start making changes is open up the val.town tab for my webservice. This means that now I can edit my microservices inbetween meetings, while waiting for CI, etc. I don’t have to open the project in my editor, setup github actions, do github pushes. Creating/modifying a micro-service is as easy as creating a gist.

Because val.town is Deno, one can easily write/use libraries using esm.town! Imagine this, you can publish a typescript lib as easily as writing a github gist! Given that I’ve only been able to persevere to publish typescript with node.js once in my life (and it took a lot of help), that blew my mind.

So now I write a lot more microservices, because val.town erased the cognitive barriers between writing, testing, and deploying microservices.

val.town goes against good engineering practices

Steve asked me if I would pay for his service. My initial reaction was: pay for what? val.town paid tier has nothing to do with rapid microservice dev and everything with running the service. Why would I want to pay to run a service that can’t be versioned in github, can’t geo-distribute like cloudflare, can’t be automated with terraform? Why would I pay for a service runtime that charges a fixed price per month instead of per usage? There is also the technical limitation of val.town not supporting SSE streaming that’s required for https://chatcraft.org.

The thing is, val.town micro-effort dev-ex is only possible because it breaks the traditional dev-loop. (Also because it uses Deno which cuts down on transpilation/packaging bureaucracy that is modern node.js)

IMHO, given the incredible focus on productive UX at val.town, paying for this service should be tied to productivity, not to their immature runtime.

So I thought I’d forever remain a free user on val.town and never use it for anything commercial.

deno deploy

After getting sucked into the Deno ecosystem by val.town, I looked at deno deploy. It’s kind of a mix of Cloudflare workers + val.town (via their playground UX). The playground UX is much better than the Cloudflare equivalent, and the ability to import arbitrary packages into the playground UX is cool too. However, the playground is still very clunky compared to val.town. Also, the geo-distributed aspect has some issues, but more on that in another post. Luckily, I was able to reach a happy compromise by writing:

import * as lib from "https://esm.town/v/taras/markdown_download?v=51"

Deno.serve(lib.default);

So, with 2 lines of code, I get:

  • Geo-distributed equivalent of my val
  • Working streaming (not needed for this project, but it made possible to use free LLM models for https://chatcraft.org)
  • dev/prod split. Eg I could now develop fearlessly in val.town and update the import on deno once I feel I wont break any consumers.

I was still not seeing how either val.town + deno deploy experience could translate into production-grade code.

jsr.io

Everything changed once I saw how easy it is to publish code on jsr.io.

I wrote a simple Makefile to fetch val source + README and it’s pure magic!

My dev ux looks like:

  1. make clean deploy deno complains that I have uncommited changes. (This could also run tests prior to publishing to jsr.io)

  2. git commit commit the changes that got pulled from val.

  3. Once git is clean, make clean deploy publishes my lib to jsr.io & pulls it into my cloudflare worker and deploys the cloudflare worker.

Now I have:

graph LR
    A[dev ver on val.town] --> B[edge deployment via Deno Deploy]
    A --> C[github repo]
    C --> D[jsr.io package]
    D --> E[edge deployment via cloudflare worker]
  1. A [Rapid] Development branch on https://val.markdown.download/ . This is the only place I edit code.

  2. https://markdown.download on Deno deploy. I use this as the default for now because Deno has better Node compatibility than Cloudflare.

  3. https://github.com/tarasglek/markdown-download

  4. A low-latency https://cf.markdown.download version on Cloudflare workers. Here, I had to figure out an alternative to turndown because the Cloudflare esbuild process confuses this lib’s packaging. agentmarkdown seems alright so far.

Thanks to the incredible val.town + Deno ecosystem, I spent zero effort screwing around with esbuild, tsc, or other yuck. Instead, I learned a bunch of cool stuff that I can now start using at work too.

PS. I ended up paying for val.town.