Making bsqr

January 2, 2020

Sometimes I have the thought "Damn, someone should make this into an npx utility" ... well, a few months ago I actually listened to that voice in my head! In the rest of this post I'll go over what this little utility is & the thought process that went into creating it.

Table of Contents:


First, bsqr:

It's a little script that combines BrowserSync with qrcode-terminal — I'm definitely standing on the shoulders of giants here, not reinventing the wheel. Still, I think it's a nifty utility because it solves one nagging issue I have with BrowserSync: the external URL should be represented a QR code! I almost always want to use the external URL on my phone. Universal Clipboard is nice when I'm working on my personal machines but that won't cut it on my work machine where I'm not signed into iCloud.

So, with that thought in mind, I decided to make a utility that would:

  1. Get the same external URL BrowserSync is using
  2. Display the URL as a QR code
  3. Call BrowserSync like normal

Here's a GIF of bsqr doing exactly that on my latop:

Screen recording of bsqr in action

GIF of bsqr on desktop

& then on my phone:

Screen recording of bsqr being scanned
GIF of bsqr being scanned on an iPhone

So what did it take to create?

How I got the idea

I've known about BrowserSync for a long time but I didn't really appreciate how great it is until I started using 11ty a lot! Whenever I'm working on this very website I would see this:

GIF recording of the terminal output showing BrowserSync URLs

See the BrowserSync output at the end? That's what inspired me!

So my first thought was to go to the BrowserSync API docs — sure enough, it's designed to be scripted in addition to being run from the command line 🎉!

Writing the actual script

Writing step 1: a nice template

Here all I really did was paste & modify a version of the docs API sample:

#!/usr/bin/env node
// require the module as normal
let bs = require("browser-sync").create();

// .init starts the server
bs.init({
server: "." // NOTE: I use the current working directory instead of './app'
});

// Now call methods on bs instead of the
// main browserSync module export
bs.reload("*"); // NOTE: Here I chose to reload everything

This got the basic BrowserSync features running, so yay!

Next, step 2: a dash of QR code

The next easiest piece was taking the other off-the-shelf dependency — qrcode-terminal — off the proverbial shelf like so:

#!/usr/bin/env node
// require the module as normal
let bs = require("browser-sync").create();
let qrcode = require('qrcode-terminal');

// .init starts the server
bs.init({
server: "."
});

/*
* NOTE: Here we're just adding a text QR code to prove it's possible
*/

qrcode.generate('lorem ipsum', {small: true});


// Now call methods on bs instead of the
// main browserSync module export
bs.reload("*"); // NOTE: Here I chose to reload everything`

This little script indeed starts a BrowserSync server right after outputting the QR code for lorem ipsum:

A screenshot of the lorem ipsum QR code in the terminal

This QR code is generated by the script above & encodes the words 'lorem ipsum'

Last, but not least, step 3: putting it all together

Finally, I knew I needed to fill in the QR code text with the actual external URL. I saved it for last because I was pretty sure this would take the most work 😬.

There's a note on the docs page about using "a tool like dev-ip" (italics mine) to find the correct IP address, which I thought was interesting but I ignored when I first read it.

First I tried to figure out where external was coming from. I cloned the Browser Sync repo & ripgrep-ed the browser-sync lib directory.

Running rg external in that directory looked like this:

Output of running rg external in the browser-sync
lib

& that lead me to look at utils.js. This sent me on a brief detour into xip but once I got past the magic[1] of that I noticed the line above references getHostIp. Finally, it turned out to be this line that shows you where the external IP comes from: the first member of the array that dev-ip returns. Giants standing on the shoulders of giants! If we add that in to our little script we get:

#!/usr/bin/env node
let qrcode = require('qrcode-terminal');
let bs = require("browser-sync").create();
let devIp = require('dev-ip');

// .init starts the server
bs.init({
server: "."
});

/*
* NOTE: We're assuming the port is 3000, this isn't a safe assumption though
*/

qrcode.generate(`http://${devIp()[0]}:3000`, {small: true});


// Now call methods on bs instead of the
// main browserSync module export
bs.reload("*"); // NOTE: Here I chose to reload everything`

Indeed, this is the bones of what bsqr is right now. There's a little more sugar to option flags, error checking & such, but it was a relatively straight forward process to get from this script to the first published version.


The Future

While this was a relatively simple utility because a lot of the hard work is done by other utilities I hope this post helps you understand how easy it is to make useful scripts with JavaScript! In the future I also hope to write this as a BrowserSync plugin so that people can get the QR code functionality without the limitations of my utility.

I hope you'll try bsqr & if you'd like to submit issues, feature requests, or even a PR the GitHub repo is always waiting for your help!.



  1. it's really just a suffix that acts as a very clever DNS resolver ↩︎

◼️ January 2, 2020 @cwervo

← Home