When writing small tools that support me with my daily work, I usually opted to shell-scripting with sh, bash or even zsh because I find the concept of piping text from one command to another pretty appealing. It's a straightforward and powerful concept that doesn't require a lot of set up: most CLI tools operate on a per-line basis, so it's a bliss to combine them.

The situation becomes more challenging when I need to work on JSONs, which are more natural in the JavaScript world, where I often work in. Usually, I opted for jq to parse JSON into line-based data, but I keep forgetting its syntax, which also can turn out complex and difficult to read.

So far, the intricacies of getting JavaScript code that is meant for the browser to run using node.js on the command line, kept me from actually considering this option. Usually, I would run into issues because the code I want to reuse for a quick command-line session used browser APIs that don't work in node.js.

When I learned about bun.js, claiming that it makes running JavaScript code just work on the command-line, I got very interested. And it turns out it holds its promises---at least for the specific use case that I had some days ago. I had implemented a function that provides a suggestion for a misspelled email domain and wanted to run it with GNU parallel on a bunch of misspelled domains. (Using GNU parallel saved me from dealing with JavaScript's single-threaded-ness and async/awaits.)

I ended up writing a short JavaScript file that takes in a single parameter and calls my function with the parameter as an argument. Then, I was able to run bun run my-script.js and it took care of different styles of imports, node_modules and whatever would have caused my headaches using nodejs for executing the file. 🎉

Since bun run would reparse the file on every call, I decided to use bun build to compile the script to a binary. I continued running the binary in parallel, this time avoiding the unnecessary reparsing. Creating a binary from a JavaScript turned out to be straightforward as well using bun build:

bun build my-script.js --compile --outfile my-script

Before running the binary in parallel, I wanted to know whether, as I had anticipated, the compiled version would run faster than calling bun run again and again. As it turned out, that day I would make yet another discovery that would make this quest pretty easy to solve: hyperfine for command line benchmarking:

hyperfine --warmup 5 'bun run my-script-cli.js' './my-script-cli'
Benchmark 1: bun run my-script-cli.js
  Time (mean ± σ):      32.6 ms ±   0.6 ms    [User: 47.7 ms, System: 20.3 ms]
  Range (min … max):    31.1 ms …  33.8 ms    84 runs

Benchmark 2: ./my-script-cli
  Time (mean ± σ):      15.2 ms ±   0.6 ms    [User: 15.5 ms, System: 4.4 ms]
  Range (min … max):    14.2 ms …  18.6 ms    168 runs

  ./my-script-cli ran
    2.14 ± 0.10 times faster than bun run suggest-domain-cli.js

In the end, with the new tooling that I had found, it was straightforward for me to convert existing business logic that was ingrained in a frontend project to a command line utility. In addition to that, some more keystrokes later, I had performed a basic benchmark of that utility too!