<?xml version="1.0" encoding="utf-8"?>

<feed xmlns="http://www.w3.org/2005/Atom">
  <title>r0b&#39;s random ramblings</title>
  <subtitle>I&#39;m a research software engineer exploring ... things</subtitle>
  <link href="https://blog.r0b.io/feed.xml" rel="self" type="application/atom+xml" />
  <link href="https://blog.r0b.io/" rel="alternate" type="text/html" />
  <updated>2025-11-19T19:00:00Z</updated>
  <id>https://blog.r0b.io/feed.xml</id>
  <author>
    <name>Rob Anderson</name>
    <email>rob@andrsn.uk</email>
  </author>
  <generator
    uri="https://www.11ty.io"
    version="2.0.1"
    >Eleventy</generator
  > 
  <entry>
    <title>Notes on restoring my iPod Classic</title>
    <link
      href="https://blog.r0b.io/post/notes-on-restoring-my-ipod/"
      rel="alternate"
      type="text/html"
      title="Notes on restoring my iPod Classic"
    />
    <updated>2025-11-19T19:00:00Z</updated>
    <id>https://blog.r0b.io/post/notes-on-restoring-my-ipod/</id>
    <content
      type="html"
      >&lt;h1 id=&quot;notes-on-restoring-my-ipod-classic-(4th-generation)&quot; tabindex=&quot;-1&quot;&gt;Notes on restoring my iPod Classic (4th Generation)&lt;/h1&gt;
&lt;p&gt;I recently got into iPods again. I wanted to have an mp3 player again, be a bit less reliant on streaming services and add some intentionality to my music listening. I knew my parents had at least one old iPod laying around their house so I got them to dig one out! What they found was an iPod Photo, a “classic” iPod a.k.a the 4th generation.&lt;/p&gt;
&lt;figure class=&quot;figureImage&quot;&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://blog.r0b.io/img/n5trZ6wvrC-1200.webp 1200w&quot; /&gt;&lt;img alt=&quot;My new old iPod Classic!.&quot; loading=&quot;lazy&quot; src=&quot;https://blog.r0b.io/img/n5trZ6wvrC-1200.jpeg&quot; width=&quot;1200&quot; height=&quot;1600&quot; /&gt;&lt;/picture&gt;&lt;figcaption&gt;My new old iPod Classic!.&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;After browsing the excellent iFixit guides, I found they have an &lt;a href=&quot;https://www.ifixit.com/Device/iPod_4th_Generation_or_Photo&quot;&gt;entire series&lt;/a&gt; on this specific iPod so I got to work ordering up some parts and restoring the device. After a fair few hurdles, it’s all working and I’m listening to it right now as I’m writing this. What follows are some of my notes and key points from the process, in the hopes it might help someone else. I won’t go into specifics, I fix it has you covered there.&lt;/p&gt;
&lt;h3 id=&quot;what-i-modified&quot; tabindex=&quot;-1&quot;&gt;What I modified&lt;/h3&gt;
&lt;p&gt;I wanted to keep this as “pure” an iPod experience as possible, I avoided flashing &lt;a href=&quot;https://www.rockbox.org/&quot;&gt;Rockbox&lt;/a&gt;. Instead opting to keep the original OS, often referred to as &lt;strong&gt;Pixo&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;The iPod wouldn’t boot or stay charged so I set about changing the battery and it seemed relatively easy to swap the HDD for an SD card so I swapped that too. The device went from a 20GB HDD to a 64GB SD card! I’m also hoping that the SD will be more battery efficient. It’s a slightly convoluted 50 Pin to CF Adapter into a CF card to SD card adapter and finally an SD card to go in that.&lt;/p&gt;
&lt;p&gt;For the 50 pin adapter, I missed the iFixit step to trim down one of the nodules to make it fit and it wasn’t obvious that not all the pins are needed, it’s ok for it to overlap on one side. I also bent over a dual jumper header so it fit better inside the reassembled iPod case.&lt;/p&gt;
&lt;h3 id=&quot;the-boot-menu&quot; tabindex=&quot;-1&quot;&gt;The boot menu&lt;/h3&gt;
&lt;p&gt;I spent a fair while exploring the BIOS-like debug menu to see what was going on with the iPod. I didn’t really learn much here. It was useful to understand how it works.&lt;/p&gt;
&lt;p&gt;You hold down &lt;strong&gt;select + menu&lt;/strong&gt; to reboot the iPod, then while booting hold down &lt;strong&gt;prev track + select&lt;/strong&gt; and you’ll get a glitchy animation into the boot menu. The most useful tool I’ve found here is to look at the battery stats, you can test most bits of the hardware here too. There are lots of commands for testing the hard drive, but as I swapped that for an SD card they weren’t useful to me and didn’t work.&lt;/p&gt;
&lt;h3 id=&quot;charging-the-device&quot; tabindex=&quot;-1&quot;&gt;Charging the device&lt;/h3&gt;
&lt;p&gt;I followed this &lt;a href=&quot;https://www.ifixit.com/Guide/iPod+4th+Generation+or+Photo+Battery+Replacement/393&quot;&gt;iFixit guide&lt;/a&gt; and got my battery &lt;a href=&quot;https://www.ebay.co.uk/itm/305739629649&quot;&gt;from eBay&lt;/a&gt;. The main issue I’ve had after replacing the battery and HDD was getting the device to charge. From looking around online, a lot of people claim the iPod doesn’t charge very well over USB and people recommend using a Firewire connection instead. In 2025 firewire doesn’t really seem like a good option!&lt;/p&gt;
&lt;p&gt;People also claim the JST connector pins (the one used to plug the battery into the PCB) gets a bit loose and some recommend bending the pins a bit to get a better connection. For me, it helped to arrange the battery wire so it was feeding from “the back”, so the cable loops around the left and comes down into the socket, where “down” is where the 30-pin socket is. Also ensuring a tight bend to keep things together.&lt;/p&gt;
&lt;p&gt;The iPod seems fussy about what will charge it. Mine won’t charge on a PC, even though data works, and it won’t charge on many USB power adapters. Definitely try a few combinations before abandoning the iPod/cable. Mine works best from an Anker multi-USB power hub that I use to power my Raspberry Pis, of all things.&lt;/p&gt;
&lt;h3 id=&quot;replacing-the-hdd&quot; tabindex=&quot;-1&quot;&gt;Replacing the HDD&lt;/h3&gt;
&lt;p&gt;I followed this &lt;a href=&quot;https://www.ifixit.com/Guide/iPod+4th+generation+or+Photo+hard+drive+replacement+by+micro+SD+card/148097&quot;&gt;iFixit guide&lt;/a&gt; and got this &lt;a href=&quot;https://www.amazon.co.uk/dp/B08JYXNW22&quot;&gt;Compact Flash adapter&lt;/a&gt; and this &lt;a href=&quot;https://www.amazon.co.uk/dp/B00S6AK592&quot;&gt;50 pin adapter&lt;/a&gt; from Amazon. It was weird that not all the pins are used, it needs to “overhang” on the left side. If you trace the lines on the circuit you can see that those pins aren’t connected to anything anyway. There is a surprisingly bright LED when the card is being read, but I guess you never really see that. I also bent/folded the jumper so it fit inside the iPod nicely. I do wonder if it affects the battery.&lt;/p&gt;
&lt;p&gt;Because the SD card setup is so much smaller, I stuck a bit of anti-static foam to the underside of the 50-pin adapter. This presses the adapters up against the case a bit and stops anything from rattling around. I had some lying around, it was probably 5mm thick and quite compressible.&lt;/p&gt;
&lt;p&gt;I think the iPod can handle macOS and Windows formatting of the SD cad, I went with fat 32 because I thought it would be most compatible. I ended up using windows to sync the iPod, more on that below, so fat 32 worked well for that.&lt;/p&gt;
&lt;h3 id=&quot;synchronising-the-device&quot; tabindex=&quot;-1&quot;&gt;Synchronising the device&lt;/h3&gt;
&lt;p&gt;I started out using macOS and was pretty surprised to see a neat iPod icon in Finder! The wonder stopped there though, I couldn’t get the integration to work very well. It often struggled to recognise the device or would get into a stuck state where the only way to fix it was to reboot my entire mac. Not ideal.&lt;/p&gt;
&lt;p&gt;Once I tried synchronising with Windows and iTunes I never went back. Opening iTunes and installing the “iPod Support” was very simple and I haven’t run into many issues.&lt;/p&gt;
&lt;p&gt;The other benefit of using Windows for me was that I’m still in the progress of ripping my old CDs. When I did this on Music on macOS, it very quickly started messing up my streaming albums and it broke a few things on my iPhone. Using windows was a lot cleaner and didn’t interrupt my normal iPhone music streaming through Apple Music. I’m still to decide where to put my developing music collection, it’s currently a big folder in iCloud.&lt;/p&gt;
&lt;p&gt;There are a few options when you start synchronising an iPod, and I had no idea what they mean:&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;convert bitrate&lt;/strong&gt;&lt;/em&gt;&lt;br /&gt;
It turns out the iPod will store whatever you throw at it, be it AAC, MP3 or Apple Lossless. You can use this option to transcode audio to AAC at a bitrate at the time of synchronising. I’ve had weird issues where this option would lose album artworks, it might be worth transcoding yourself with something like &lt;a href=&quot;https://www.ffmpeg.org/&quot;&gt;ffmpeg&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I also started making &lt;a href=&quot;https://github.com/robb-j/m4b-editor/blob/main/scripts/itool.js&quot;&gt;a script&lt;/a&gt; to transcode an entire directory (recursively) of music files into a new directory formatted to AAC 256kbps while preserving metadata &amp;amp; artwork and removing Album Artists (which don’t work on iPods)&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;manually manage this iPod&lt;/strong&gt;&lt;/em&gt;&lt;br /&gt;
This forgoes iTune’s synchronisation UI so you can just drag and drop music onto the device. This is very useful. You can also inspect the tracks on the iPod within iTunes so you can see if the artwork or metadata is set correctly. You don’t even need to have the music in iTunes to do this.&lt;/p&gt;
&lt;p&gt;Another note on synchronisation is that iPods, at least mine, don’t support “album artist” which for me makes the Artists menu pretty useless. A track has an Artist and Album Artist field which you can use to set who’s in the song but also how the tracks should be grouped by separately. For example most tracks in &lt;a href=&quot;https://fredagainagain.bandcamp.com/album/ten-days&quot;&gt;ten days by Fred Again…&lt;/a&gt; feature different artists, so for each track it includes every artist in the “Artist” field but each track just has Fred Again… in the “Album Artist” field. But unfortunately the iPod doesn’t do this grouping so I ended up overwriting all the Artists fields.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;two other options&lt;/strong&gt;&lt;/em&gt;&lt;br /&gt;
I can’t remember the other to options, I don’t think they were as important.&lt;/p&gt;
&lt;h3 id=&quot;battery-life&quot; tabindex=&quot;-1&quot;&gt;Battery Life&lt;/h3&gt;
&lt;p&gt;The battery controller on the iPod tries to work out the maximum and minimum charge that can be stored in the battery. It does this when it is charged to the maximum and drained to the minimum. I think it then uses these values to inform the battery level it displays.&lt;/p&gt;
&lt;p&gt;When you first put a new battery in, I’d advise continuously charging it up even after it claims it is full. Then use it until it is completely empty and won’t play any more. That way it will get to its true “full” value and “empty” value and the battery indicator will work properly.&lt;/p&gt;
&lt;p&gt;I started ripping my music with Apple Lossless (ALAC). I’m not sure if that was a good idea or not yet. It seems that is not best for the iPod and it uses significantly more power to play these tracks. Reddit recommends storing tracks as AAC at 256kbps, I’m still testing this theory but it makes sense to me.&lt;/p&gt;
&lt;h3 id=&quot;audio-books&quot; tabindex=&quot;-1&quot;&gt;Audio books&lt;/h3&gt;
&lt;p&gt;If you want to use audio books, it turns out they are just a regular m4a file but renamed m4b (b for book I guess). I had a CD and wanted to rip each track as a chapter so I made this tool: &lt;a href=&quot;https://m4b.r0b.io/&quot;&gt;m4b Editor&lt;/a&gt;. You drop in your audio files and it’ll generate you a single m4b preserving the metadata and setting each track as a chapter.&lt;/p&gt;
&lt;p&gt;It all runs completely in the browser, there’s no server here! It runs ffmpeg using WASM which is pretty cool. It seems significantly slower than running the CLI, but hey you only need to do this a few times.&lt;/p&gt;
&lt;h3 id=&quot;apple-usb-superdrive&quot; tabindex=&quot;-1&quot;&gt;Apple USB SuperDrive&lt;/h3&gt;
&lt;p&gt;If you want to use the SuperDrive with Windows, you’ll need to install the driver for it. You can get them as part of the Bootcamp support package. &lt;a href=&quot;https://support.apple.com/en-us/106378&quot;&gt;Download that&lt;/a&gt; and install AppleODDInstaller64 and it should work and show up in iTunes. It&#39;s here-ish:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;bootcamp{version}&#92;BootCamp&#92;Drivers&#92;Apple&#92;AppleODDInstaller64.exe
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;useful-links&quot; tabindex=&quot;-1&quot;&gt;Useful links&lt;/h3&gt;
&lt;p&gt;Here are some miscellaneous resources that were also useful during this whole process.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.ifixit.com/Device/iPod_4th_Generation_or_Photo&quot;&gt;iPod Photo iFixit guides&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.ifixit.com/en-gb/products/essential-electronics-toolkit&quot;&gt;The iFixit tools I have&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.reddit.com/r/ipod/comments/j2zaq1/what_is_the_best_music_format_that_the_ipod_can/&quot;&gt;Reddit post on iPod music quality&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.iflash.xyz/&quot;&gt;iFlash&lt;/a&gt; — seems like a good idea but I didn’t try them&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.reddit.com/r/ipod/comments/ut8wmo/ive_done_a_battery_replacement_on_my_4th_gen_ipod/&quot;&gt;iPod won’t charge post&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://ia800702.us.archive.org/13/items/iPod_test_procedures/iPod%204th%20gen%20color.pdf&quot;&gt;iPod gen 4 service guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://everymac.com/systems/apple/ipod/specs/ipod_4thgen.html&quot;&gt;iPod gen 4 spec&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/fadingred/libgpod&quot;&gt;potential C library for talking to iPods&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://bendodson.com/projects/itunes-artwork-finder/&quot;&gt;iTunes artwork finder&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://discussions.apple.com/thread/254485233?sortBy=rank&quot;&gt;Apple SuperDrive on Windows&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Thanks for reading, I hope this was useful! Drop me a line on &lt;a href=&quot;https://social.lol/@r0b&quot;&gt;Mastodon&lt;/a&gt; if you have any questions.&lt;/p&gt;
</content
    >
  </entry> 
  <entry>
    <title>Using Explicit Resource Management with TypeScript and Postgres</title>
    <link
      href="https://blog.r0b.io/post/using-explicit-resource-management-with-typescript-and-postgres/"
      rel="alternate"
      type="text/html"
      title="Using Explicit Resource Management with TypeScript and Postgres"
    />
    <updated>2025-01-29T12:30:00Z</updated>
    <id>https://blog.r0b.io/post/using-explicit-resource-management-with-typescript-and-postgres/</id>
    <content
      type="html"
      >&lt;h2 id=&quot;overview&quot; tabindex=&quot;-1&quot;&gt;Overview&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/tc39/proposal-explicit-resource-management&quot;&gt;Explicit Resource Management&lt;/a&gt;
is a newish JavaScript feature that lets you add tear-down functionality to function scopes.
This means if your code sets something up, you can have it automatically clean things when that code completes.&lt;/p&gt;
&lt;p&gt;It&#39;s available in &lt;a href=&quot;https://nodejs.org/en/blog/release/v20.4.0&quot;&gt;Node.js 20.4.0 +&lt;/a&gt;,
&lt;a href=&quot;https://deno.com/blog/v1.38#using-with-deno-apis&quot;&gt;Deno 1.38+&lt;/a&gt; and &lt;a href=&quot;https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-2.html&quot;&gt;TypeScript 5.2+&lt;/a&gt;, so you should be able to use it on newer projects.&lt;/p&gt;
&lt;p&gt;It looks something like this:&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;using file &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;openFile&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;notes.txt&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then &lt;code&gt;openFile&lt;/code&gt; implements this API to close the file when it is not used anymore.&lt;/p&gt;
&lt;h2 id=&quot;my-code&quot; tabindex=&quot;-1&quot;&gt;My Code&lt;/h2&gt;
&lt;p&gt;So I had some code, I wanted to see how the footprint changed.
Roughly my code:&lt;/p&gt;
&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; postgres &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;postgres&#39;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; getPostgresMigrator &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;gruber&#39;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;runMigrations&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;direction&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; sql &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;postgres&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;postgres://...&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; migrator &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getPostgresMigrator&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; sql&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; directory &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;direction &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;up&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; migrator&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;up&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;direction &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;down&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; migrator&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;down&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Error&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;Unknown direction &amp;lt;up|down&gt;&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;finally&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; sql&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;end&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And it turned into this:&lt;/p&gt;
&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;runMigrations&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;direction&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; using sql &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getPostgresClient&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;postgres://...&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; migrator &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getPostgresMigrator&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; sql&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; directory &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;direction &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;up&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; migrator&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;up&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;direction &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;down&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; migrator&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;down&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Error&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;Unknown direction &amp;lt;up|down&gt;&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Which needed this magic:&lt;/p&gt;
&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getPostgresClient&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;url&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; sql &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;postgres&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;url&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

  Object&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;assign&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;sql&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;Symbol&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;asyncDispose&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; sql&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;end&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; sql
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;notes&quot; tabindex=&quot;-1&quot;&gt;Notes&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;It takes an extra level of nesting out of the code&lt;/li&gt;
&lt;li&gt;I wish TypeScript let you assign symbols to things without complaining&lt;/li&gt;
&lt;li&gt;I could remove the use of &lt;code&gt;return&lt;/code&gt; to simplify the code&lt;/li&gt;
&lt;li&gt;Synchronous and async are different, one uses &lt;code&gt;Symbol.dispose&lt;/code&gt; and the other &lt;code&gt;Symbol.asyncDispose&lt;/code&gt;
and requires the &lt;code&gt;await using&lt;/code&gt; keywords.&lt;/li&gt;
&lt;li&gt;It feels like this could be very powerful, especially when all your favourite libraries start using it.&lt;/li&gt;
&lt;/ul&gt;
</content
    >
  </entry> 
  <entry>
    <title>Async iteration for JavaScript EventTargets using ReadableStreams</title>
    <link
      href="https://blog.r0b.io/post/javascript-async-iteration-with-readable-streams-from-events/"
      rel="alternate"
      type="text/html"
      title="Async iteration for JavaScript EventTargets using ReadableStreams"
    />
    <updated>2024-06-28T17:00:00Z</updated>
    <id>https://blog.r0b.io/post/javascript-async-iteration-with-readable-streams-from-events/</id>
    <content
      type="html"
      >&lt;p&gt;Working on an &lt;a href=&quot;https://hub.openlab.dev/&quot;&gt;internal app platform&lt;/a&gt; at work I found a way to iterate a set of events that get fired on an EventTarget. I did a cursory web search and only found some old StackOverflow posts so promptly dived in to try and implement it myself.&lt;/p&gt;
&lt;p&gt;An &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/EventTarget&quot;&gt;EventTarget&lt;/a&gt; is what a lot of JavaScript primitives are based on. It lets you listen to event on &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement&quot;&gt;DOM elements&lt;/a&gt;, or &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/WebSocket&quot;&gt;WebSockets&lt;/a&gt; or &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/EventSource&quot;&gt;EventSources&lt;/a&gt;. You&#39;ll probably be familiar with the call sign:&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;something&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;click&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;event&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I wanted my code to look more like:&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; event &lt;span class=&quot;token keyword&quot;&gt;of&lt;/span&gt; somethingElse&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;event&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I&#39;m sure I&#39;ve seen something like this before, but I cannot remember where that was.
I&#39;ve been slowly getting my head around the &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Streams_API&quot;&gt;Web Streams APIs&lt;/a&gt; for the Hub project and I remembered that a &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream&quot;&gt;ReadableStream&lt;/a&gt; also implements &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol/asyncIterator&quot;&gt;Symbol.asyncIterable&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;That symbol allows you to use &lt;code&gt;for await&lt;/code&gt; with things, so ReadableStream opens the door to use it in a custom way.
A ReadableStream is a definition of an asynchronous stream of data that can be piped somewhere and there is a notion of a buffer/queue of that data so that it doesn&#39;t use up all your memory. So we can do this:&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; stream &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ReadableStream&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;start&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;controller&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    something&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;click&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      controller&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;enqueue&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;event&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; event &lt;span class=&quot;token keyword&quot;&gt;of&lt;/span&gt; stream&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;event&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I thought this was pretty neat, for EventTarget you could event declare a method that takes a set of event names and creates the generator for you:&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;stream&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;eventNames&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ReadableStream&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;start&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;controller&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; name &lt;span class=&quot;token keyword&quot;&gt;of&lt;/span&gt; eventNames&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        something&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;click&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
          controller&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;enqueue&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;event&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; event &lt;span class=&quot;token keyword&quot;&gt;of&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;stream&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;click&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;mouseover&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;...&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;type &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;click&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;click&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That is a bit over-the-top but you can see it can start to create a friendlier interface over those events while keeping that flow of code. In this case the code will keep listening forever and never exit the for loop.&lt;/p&gt;
&lt;p&gt;It isn&#39;t quite as simple as above, it should also clean-up after itself and remove those event listeners:&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;stream&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;eventNames&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// Store the listeners so they can later be removed&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; listeners &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ReadableStream&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;start&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;controller&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token comment&quot;&gt;// Generate a listener for each event and hook it up&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; name &lt;span class=&quot;token keyword&quot;&gt;of&lt;/span&gt; eventNames&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        listeners&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; controller&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;enqueue&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;event&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        something&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;click&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; listeners&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;cancel&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token comment&quot;&gt;// Remove each of the listeners from the event target&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; callback&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;of&lt;/span&gt; Object&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;entries&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;listeners&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        something&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;removeEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; callback&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; countdown &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;5&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; event &lt;span class=&quot;token keyword&quot;&gt;of&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;stream&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;click&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;mouseover&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;...&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;type &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;click&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;click&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token comment&quot;&gt;// Break out of the loop after 5 events have been listened to&lt;/span&gt;
  countdown&lt;span class=&quot;token operator&quot;&gt;--&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;countdown &lt;span class=&quot;token operator&quot;&gt;&amp;lt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;break&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;stop&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This time it stores the event listeners when it starts the ReadableStream so they can be removed when the stream is cancelled. That cancel method is called when the stream is no longer being used, in this case it is when we break out of the &lt;code&gt;for await&lt;/code&gt; loop.&lt;/p&gt;
&lt;p&gt;The only thing I&#39;m not sure about this approach is how the internal queuing/buffering works. I think it has an upper limit of things it will buffer and will start to drop things if that limit is reached. I think that is configurable but I will have to read up more on ReadableStreams.&lt;/p&gt;
</content
    >
  </entry> 
  <entry>
    <title>Learning a bit of Für Elise</title>
    <link
      href="https://blog.r0b.io/post/learning-fur-elise/"
      rel="alternate"
      type="text/html"
      title="Learning a bit of Für Elise"
    />
    <updated>2024-06-09T17:00:00Z</updated>
    <id>https://blog.r0b.io/post/learning-fur-elise/</id>
    <content
      type="html"
      >&lt;p&gt;I recently tooted that I was &lt;a href=&quot;https://hyem.tech/@rob/112525251520946192&quot;&gt;learning Für Elise&lt;/a&gt; on the piano.
I&#39;ve also been doing recordings and sharing them with my Mum, how cute!&lt;/p&gt;
&lt;p&gt;I thought it was kind of interesting to hear the difference in progress,
this is probably only interesting to me.
But here it is.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;I was also curious about how I could incorporate audio on my blog.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;first-recording&quot; tabindex=&quot;-1&quot;&gt;First recording&lt;/h2&gt;
&lt;figure class=&quot;figureAudio&quot;&gt;&lt;audio controls=&quot;&quot;&gt;&lt;source src=&quot;https://media.r0b.io/audio/fur-elise/01.m4a&quot; type=&quot;audio/mp4&quot; /&gt;&lt;/audio&gt;&lt;figcaption&gt;Recording number one&lt;/figcaption&gt;&lt;/figure&gt;
&lt;ul&gt;
&lt;li&gt;I&#39;m clearly still learning the notes and finger work here&lt;/li&gt;
&lt;li&gt;I think I&#39;d just been able to make it all the way through&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;second-recording&quot; tabindex=&quot;-1&quot;&gt;Second recording&lt;/h2&gt;
&lt;figure class=&quot;figureAudio&quot;&gt;&lt;audio controls=&quot;&quot;&gt;&lt;source src=&quot;https://media.r0b.io/audio/fur-elise/02.m4a&quot; type=&quot;audio/mp4&quot; /&gt;&lt;/audio&gt;&lt;figcaption&gt;Recording number two&lt;/figcaption&gt;&lt;/figure&gt;
&lt;ul&gt;
&lt;li&gt;I&#39;m getting more confident, but still missing a few notes&lt;/li&gt;
&lt;li&gt;I&#39;m still struggling with the second phrase, so much stretching fingers!&lt;/li&gt;
&lt;li&gt;I added a little bit on the end playing with the bass notes&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;third-recording&quot; tabindex=&quot;-1&quot;&gt;Third recording&lt;/h2&gt;
&lt;figure class=&quot;figureAudio&quot;&gt;&lt;audio controls=&quot;&quot;&gt;&lt;source src=&quot;https://media.r0b.io/audio/fur-elise/03.m4a&quot; type=&quot;audio/mp4&quot; /&gt;&lt;/audio&gt;&lt;figcaption&gt;Recording number three&lt;/figcaption&gt;&lt;/figure&gt;
&lt;ul&gt;
&lt;li&gt;This is where I&#39;m at at time of writing&lt;/li&gt;
&lt;li&gt;I&#39;ve started using the sustain pedal for the base bits&lt;/li&gt;
&lt;li&gt;Its all flowing a lot better&lt;/li&gt;
&lt;li&gt;I&#39;ve also learnt I can plug my phone into my Piano&#39;s USB-C and get a perfect recording&lt;/li&gt;
&lt;li&gt;I can&#39;t believe I messed up that last note 😔&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Well that&#39;s that, hope you found it interesting! 🤷&lt;/p&gt;
</content
    >
  </entry> 
  <entry>
    <title>Get to da Terminal</title>
    <link
      href="https://blog.r0b.io/post/get-to-da-terminal/"
      rel="alternate"
      type="text/html"
      title="Get to da Terminal"
    />
    <updated>2024-02-23T10:50:00Z</updated>
    <id>https://blog.r0b.io/post/get-to-da-terminal/</id>
    <content
      type="html"
      >&lt;p&gt;I wanted a single shortcut to open a new Terminal so first I had to pick one. I had been using &lt;code&gt;CMD+OPT+C&lt;/code&gt; in VSCode for a while to bring up the terminal pane and while dabbling with Nova I set up the same shortcut to open a new local terminal too. This had worked well for a while, but it was actually overriding the macOS default keyboard shortcut to show the colour picker, so it wouldn’t work as a global shortcut.&lt;/p&gt;
&lt;p&gt;I spent a hot minute scanning my keyboard to work out what to use until I landed on the &lt;code&gt;§/±&lt;/code&gt; key. I’m on a UK ISO macOS keyboard, so that is the button to the left of my &lt;code&gt;1&lt;/code&gt; and below my &lt;code&gt;ESC&lt;/code&gt;. To my knowledge &lt;code&gt;CMD+§&lt;/code&gt; isn’t a well-known shortcut so that’s what I’ve adopted.&lt;/p&gt;
&lt;p&gt;First I moved my VSCode + Nova keyboard shortcuts to that new combination and VSCode will sync it between both of my Macs too. Nova I just had to configure it in both.&lt;/p&gt;
&lt;p&gt;For a global keyboard shortcut I created a Shortcut in the Shortcuts app. This just runs and opens Terminal.app. You might be able to do something clever so that it gets the “current folder” if available and pass it to the Terminal to open at that location, but I couldn’t work out how to do that. Finally, I assigned that a keyboard shortcut to the Shortcut under the little “info” tab.&lt;/p&gt;
&lt;p&gt;With application-specific shortcuts, they take precedence of the global ones so if I use it in Nova or VSCode then it opens a terminal in-app and if I use it in another app it opens the macOS terminal. Perfect.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Misc&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;I’ve been trying Prompt 3 for a bit and currently have the Shortcut opening that while I trial it.&lt;/li&gt;
&lt;li&gt;With my Keycron keyboards, I tried remapping the § button with &lt;a href=&quot;https://usevia.app/&quot;&gt;Via&lt;/a&gt; to actually just trigger the old CMD+ALT+C keyboard shortcut. That did work for a while but was annoying in laptop mode and where the shortcut wasn’t available.&lt;/li&gt;
&lt;li&gt;I’m not sure if Terminal.app (or Prompt.app) support opening them at a certain folder, at least through Shortcuts. That would be really cool if it could capture my location in Finder and open the terminal there instead.&lt;/li&gt;
&lt;li&gt;After setting up the keyboard shortcut in Shortcuts.app, it doesn&#39;t seem to take effect in apps that are already open. e.g. If Mail was already open when the shortcut is set, it won&#39;t work until Mail has restart.&lt;/li&gt;
&lt;/ul&gt;
</content
    >
  </entry> 
  <entry>
    <title>Isomorphic JavaScript, Web Standards and Documentation Driven Design</title>
    <link
      href="https://blog.r0b.io/post/isomorphic-javascript-web-standards-and-documentation-driven-design/"
      rel="alternate"
      type="text/html"
      title="Isomorphic JavaScript, Web Standards and Documentation Driven Design"
    />
    <updated>2024-01-08T22:00:00Z</updated>
    <id>https://blog.r0b.io/post/isomorphic-javascript-web-standards-and-documentation-driven-design/</id>
    <content
      type="html"
      >&lt;p&gt;I was posting on Mastodon about a documentation driven design process recently
and thought I&#39;d do a post to share the thing I was thinking about at the time
and dig into the process and design a bit.&lt;/p&gt;
&lt;h2 id=&quot;web-standards&quot; tabindex=&quot;-1&quot;&gt;Web Standards&lt;/h2&gt;
&lt;p&gt;I&#39;ve been thinking about server-side JavaScript a bit recently
and especially the move towards web-standards on the backend.
My first experience was when Deno came along with a really nice &amp;quot;serve&amp;quot; API.
It is literally just a function that takes a &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API&quot;&gt;fetch&lt;/a&gt; Request
and you return a Response.&lt;/p&gt;
&lt;p&gt;I really like this API for two reasons:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;It&#39;s minimalistic, there is no &lt;code&gt;res&lt;/code&gt; to mutate, no &lt;code&gt;next&lt;/code&gt; to call or &lt;code&gt;ctx&lt;/code&gt; to worry about.&lt;/li&gt;
&lt;li&gt;It&#39;s based on a feature-rich web standard; parse JSON, stream bytes, read headers. It&#39;s all there.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Generally, that got me thinking about other standards but also the promise of them.
Web standards don&#39;t come and go every night.
They&#39;re well thought out and don&#39;t really get deprecated.&lt;/p&gt;
&lt;h2 id=&quot;background&quot; tabindex=&quot;-1&quot;&gt;Background&lt;/h2&gt;
&lt;p&gt;At work I work on lots of small to medium Node.js projects often by myself and each project is somewhat similar
but I&#39;ve often tweaked something or tried out something new.
I love that I can do this and get to explore lots of things.
But, I have lots of similar but not quite the same code which does make it difficult to go back to projects.&lt;/p&gt;
&lt;p&gt;This time I took some time to think about what my ideal common ground for a project would be
and how a common ground could help compose projects together in the future.
If I have added magic link auth to one project, how can I reuse that in another project, for instance.&lt;/p&gt;
&lt;h2 id=&quot;documentation-driven-design&quot; tabindex=&quot;-1&quot;&gt;Documentation driven design&lt;/h2&gt;
&lt;p&gt;This all lead me to writing documentation for my ideal library through documentation-driven-design.
There might be a more formal name for it, I&#39;m not sure.
The idea is to design a thing from the perspective of someone first using and learning about it.&lt;/p&gt;
&lt;p&gt;It really gets you to think about the APIs and contracts you&#39;re creating.
How there could be modules and how those modules could work together.
Then you also need to think about how you explain those concepts
which really forces you understand what is important and what isn&#39;t.&lt;/p&gt;
&lt;p&gt;It is also freeing.
There is no burden of any code being written.
If you want to change an API to be cleaner or simpler, you can.
Straight away.
You don&#39;t have to worry about how that effects your codebase, how tests will need to change or things be reorganised.&lt;/p&gt;
&lt;p&gt;You do still need to ground yourself and remember what is possible.
That&#39;s why I think it works best when you have a strong idea of what you want to make
and a rough of idea of how it will work internally.
I think if you went in completely blue sky, you would design something that wouldn&#39;t be possible to make.&lt;/p&gt;
&lt;h2 id=&quot;gruber&quot; tabindex=&quot;-1&quot;&gt;Gruber&lt;/h2&gt;
&lt;p&gt;So, the thing I was thinking about was a hypothetical server-side JavaScript library.
Funnily enough, there is some interesting documentation to look at.
Check it out at &lt;a href=&quot;https://github.com/robb-j/gruber&quot;&gt;robb-j/gruber&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I&#39;d love to know what you think.&lt;/p&gt;
&lt;p&gt;→ &lt;a href=&quot;https://social.lol/@r0b&quot;&gt;Rob&lt;/a&gt;&lt;/p&gt;
</content
    >
  </entry> 
  <entry>
    <title>Eleventy WebC in a nutshell</title>
    <link
      href="https://blog.r0b.io/post/eleventy-webc-in-a-nutshell/"
      rel="alternate"
      type="text/html"
      title="Eleventy WebC in a nutshell"
    />
    <updated>2023-12-02T00:00:00Z</updated>
    <id>https://blog.r0b.io/post/eleventy-webc-in-a-nutshell/</id>
    <content
      type="html"
      >&lt;p&gt;I&#39;ve been learning about WebC recently and I&#39;m pretty sure I&#39;m converted.
So I thought I&#39;d put together a set of notes from my experience of learning it.
To do this, let&#39;s create a little project together. From scratch.&lt;/p&gt;
&lt;h2 id=&quot;prerequisites&quot; tabindex=&quot;-1&quot;&gt;Prerequisites&lt;/h2&gt;
&lt;p&gt;Before starting, I&#39;m going to assume:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;You are happy to write some JavaScript&lt;/li&gt;
&lt;li&gt;You&#39;re familiar Eleventy&lt;/li&gt;
&lt;li&gt;You maybe understand custom elements (a.k.a web components)&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;language-sh&quot;&gt;&lt;code class=&quot;language-sh&quot;&gt;&lt;span class=&quot;token function&quot;&gt;mkdir&lt;/span&gt; 11ty-webc-nutshell
&lt;span class=&quot;token builtin class-name&quot;&gt;cd&lt;/span&gt; 11ty-webc-nutshell

&lt;span class=&quot;token function&quot;&gt;npm&lt;/span&gt; init &lt;span class=&quot;token parameter variable&quot;&gt;-y&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;npm&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;install&lt;/span&gt; @11ty/eleventy @11ty/eleventy-plugin-webc&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That&#39;ll create us a project folder, set it up to use NPM and install our dependencies.&lt;/p&gt;
&lt;h2 id=&quot;configuration&quot; tabindex=&quot;-1&quot;&gt;Configuration&lt;/h2&gt;
&lt;p&gt;Now we need to configure Eleventy to use WebC. This is pretty simple, we&#39;ll add our eleventy config file.
I&#39;m going to use &lt;strong&gt;eleventy.config.cjs&lt;/strong&gt;,
but there are &lt;a href=&quot;https://www.11ty.dev/docs/config/#default-filenames&quot;&gt;several of other names&lt;/a&gt; you can use.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;My current thinking is this is more important than a &amp;quot;dot file&amp;quot; like &lt;code&gt;.gitignore&lt;/code&gt; and I&#39;d like to be specific this is CommonJS.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; eleventyWebc &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;@11ty/eleventy-plugin-webc&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

module&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function-variable function&quot;&gt;exports&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;eleventyConfig&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  eleventyConfig&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addPlugin&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;eleventyWebc&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;pages&quot; tabindex=&quot;-1&quot;&gt;Pages&lt;/h2&gt;
&lt;p&gt;Pretty simple so far. Now lets create our first page. This uses the new &lt;code&gt;.webc&lt;/code&gt; extension (hint: it&#39;s just HTML, so you can tell your editor about that to make it prettier).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;index.webc&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;---
layout: html.webc
---

&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;main&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;h1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;Hello, there&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;h1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;

  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;my-first-component&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;my-first-component&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;main&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This all looks familiar so far, if you&#39;ve used Eleventy before that is. It starts of with Front Matter which is meta-information to tell Eleventy things. Here we say we&#39;d like to use another file as the layout. So this page will be rendered inside the &lt;code&gt;html.webc&lt;/code&gt; which we&#39;ll create a bit later.&lt;/p&gt;
&lt;p&gt;After the Front Matter, we create our page. It has a nice big title and it uses a custom element! WebC is based on web standards, it hooks in to &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Web_Components/Using_custom_elements&quot;&gt;custom elements&lt;/a&gt; and starts to do it&#39;s magic. So let&#39;s create our first WebC component!&lt;/p&gt;
&lt;h2 id=&quot;components&quot; tabindex=&quot;-1&quot;&gt;Components&lt;/h2&gt;
&lt;p&gt;By default, WebC will automatically pick up any &lt;code&gt;.webc&lt;/code&gt; file you put into the &lt;code&gt;_components&lt;/code&gt; folder. Let&#39;s create &lt;strong&gt;_components/my-first-component.web&lt;/strong&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;You can configure where Eleventy/WebC looks for these components or you can use the default location if you want.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;p&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;call&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;General Kenobi&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;p&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Thats what a WebC component is, it&#39;s just HTML. There are lots of other clever things you can do, but at it&#39;s purest it can just be HTML. Eleventy will replace &lt;code&gt;&amp;lt;my-first-component&amp;gt;&lt;/code&gt; in the page with our &lt;code&gt;&amp;lt;p&amp;gt;&lt;/code&gt; element for you. If you just want a way of reusing HTML code, WebC is a fine way to do that.&lt;/p&gt;
&lt;p&gt;So to be a bit more interesting, let&#39;s give our component some &lt;em&gt;style&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;_components/my-first-component.web&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;p&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;call&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;General Kenobi&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;p&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;style&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token style&quot;&gt;&lt;span class=&quot;token language-css&quot;&gt;
  &lt;span class=&quot;token selector&quot;&gt;.call&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;color&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; rebeccapurple&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;text-decoration&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; underline&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token selector&quot;&gt;.call::after&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;content&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;!&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token selector&quot;&gt;.response&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;color&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; blue&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;text-decoration&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; underline&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;style&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here we&#39;ve made the text look cooler with some CSS. By default, WebC will see that we&#39;ve used a &lt;code&gt;&amp;lt;style&amp;gt;&lt;/code&gt; tag and will take it out of our template to be bundled up. We&#39;ll tell it where to put it later in our layout.&lt;/p&gt;
&lt;p&gt;This style is global so we&#39;re using a class to make it more specific. You can alternatively add &lt;code&gt;webc:scoped&lt;/code&gt; to the &lt;code&gt;&amp;lt;style&amp;gt;&lt;/code&gt; tag to auto-generate a name for you and make it so the style only applies to things inside the WebC file.&lt;/p&gt;
&lt;p&gt;By adding CSS (or JavaScript) it changes how WebC behaves a little. Without it, WebC will take the HTML content and straight replace it anywhere it is referenced. This is known as a &lt;a href=&quot;https://www.11ty.dev/docs/languages/webc/#html-only-components&quot;&gt;html-only component&lt;/a&gt;. With scripts or styles, the component remains a web-component so the &lt;code&gt;&amp;lt;my-first-component&amp;gt;&lt;/code&gt; remains in your HTML and it&#39;s contents becomes everything else in your in your &lt;code&gt;.webc&lt;/code&gt; file.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;You can configure how the content is inserted with a &lt;code&gt;&amp;lt;slot&amp;gt;&lt;/code&gt; tag, &lt;a href=&quot;https://www.11ty.dev/docs/languages/webc/#slots&quot;&gt;more info&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;So let&#39;s add some JavaScript too, add this script tag to the &lt;strong&gt;existing&lt;/strong&gt; file, as long as it is at the top-level of the HTML document in your component file.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;_components/my-first-component.web&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;&amp;lt;!--
  previous example code here...
--&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;script&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token script&quot;&gt;&lt;span class=&quot;token language-javascript&quot;&gt;
  &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;MyFirstComponent&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;HTMLElement&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;connectedCallback&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token function&quot;&gt;setTimeout&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;appendMessage&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;This will make a fine addition to my collection&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2_000&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;appendMessage&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;message&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; p &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;createElement&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;p&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
      p&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;textContent &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; message
      p&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;classList&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;message&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;appendChild&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;p&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;customElements&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;define&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;my-first-component&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; MyFirstComponent&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;script&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Because WebC is not in html-only mode, it keeps the &lt;code&gt;&amp;lt;my-first-component&amp;gt;&lt;/code&gt; element in your rendered HTML and you can attach code to that. We create our client-side custom element, &lt;code&gt;MyFirstComponent&lt;/code&gt; by subclassing &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement&quot;&gt;HTMLElement&lt;/a&gt;. Then we use &lt;code&gt;define&lt;/code&gt; on the custom elements registry to get it hooked up. When someone visits the page, the browser attach our code onto the HTML element and call &lt;code&gt;connectedCallback&lt;/code&gt; to let us know.&lt;/p&gt;
&lt;p&gt;As a demonstration, we add another styled message after waiting an extra 2 seconds.&lt;/p&gt;
&lt;h2 id=&quot;layouts&quot; tabindex=&quot;-1&quot;&gt;Layouts&lt;/h2&gt;
&lt;p&gt;Ok so what happens to these scripts and styles, and how do they fit into a nice HTML document? It&#39;s completely up to you! We&#39;ll use a layout to add the styles and scripts into our rendered HTML, create &lt;strong&gt;_includes/layout.webc&lt;/strong&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;token doctype&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;!&lt;/span&gt;&lt;span class=&quot;token doctype-tag&quot;&gt;doctype&lt;/span&gt; &lt;span class=&quot;token name&quot;&gt;html&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;html&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;head&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;title&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;My first WebC&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;title&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;meta&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;charset&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;utf8&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;meta&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;viewport&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;content&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;width=device-width&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;style&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;@raw&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;getBundle(&#39;css&#39;)&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;&lt;span class=&quot;token namespace&quot;&gt;webc:&lt;/span&gt;keep&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token style&quot;&gt;&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;style&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;head&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;body&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;template&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;@html&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;this.content&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;&lt;span class=&quot;token namespace&quot;&gt;webc:&lt;/span&gt;nokeep&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;template&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;footer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;My first WebC 2023&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;footer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;script&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;module&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;@raw&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;getBundle(&#39;js&#39;)&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;&lt;span class=&quot;token namespace&quot;&gt;webc:&lt;/span&gt;keep&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token script&quot;&gt;&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;script&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;body&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;html&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;There&#39;s are a few bits going on here, let&#39;s break it down.&lt;/p&gt;
&lt;p&gt;As mentioned above, one of the powers of WebC is that it can bundle up your JavaScript and CSS for you. WebC takes out those &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;style&amp;gt;&lt;/code&gt; tags from your &lt;code&gt;.webc&lt;/code&gt; files and we have to get them into to our HTML document. We do this with some familiar &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;style&amp;gt;&lt;/code&gt; elements! These ones though have special attributes to the files generated by WebC and insert them into the document. Other brief notes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The scripts and styles are per-page so only the resources you need are on each page.&lt;/li&gt;
&lt;li&gt;As WebC removes &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;style&amp;gt;&lt;/code&gt; elements, here we need to tell it not to by adding the &lt;code&gt;webc:keep&lt;/code&gt; attribute.&lt;/li&gt;
&lt;li&gt;Any &lt;code&gt;webc:&lt;/code&gt; attributes are removed from the HTML you publish.&lt;/li&gt;
&lt;li&gt;The value of &lt;code&gt;@raw&lt;/code&gt; can actually be anything from Eleventy, e.g. things from the Front Matter or &lt;a href=&quot;https://www.11ty.dev/docs/data-cascade/&quot;&gt;Data Cascade&lt;/a&gt;. In this case it&#39;s a shortcode provided by &lt;a href=&quot;https://github.com/11ty/eleventy-plugin-bundle&quot;&gt;eleventy-plugin-bundle&lt;/a&gt; (which WebC uses under the hood).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The second interesting bit is that we&#39;re using a &lt;code&gt;@html&lt;/code&gt; attribute on a &lt;code&gt;&amp;lt;template&amp;gt;&lt;/code&gt; element. This takes the HTML generated from processing our page and puts it inside the &lt;code&gt;&amp;lt;template&amp;gt;&lt;/code&gt; element. There is a special &lt;code&gt;webc:nokeep&lt;/code&gt; attribute on there too. This tells WebC that we aren&#39;t interested in the template itself and to get rid of it, replacing it with whatever our page has rendered.&lt;/p&gt;
&lt;h2 id=&quot;round-up&quot; tabindex=&quot;-1&quot;&gt;Round up&lt;/h2&gt;
&lt;p&gt;To summerise, we:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Set up a fresh Eleventy project and added the WebC plugin&lt;/li&gt;
&lt;li&gt;Created a page using WebC as the templating language&lt;/li&gt;
&lt;li&gt;Added the WebC component that the page uses&lt;/li&gt;
&lt;li&gt;Made the component more interesting with styles and scripts&lt;/li&gt;
&lt;li&gt;Laid out the whole page using a WebC layout&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you want to skip to the end, who doesn&#39;t, see all the code in one place at &lt;a href=&quot;https://github.com/robb-j/r0b-blog/tree/master/examples/eleventy-webc&quot;&gt;examples/eleventy-webc&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;current-questions&quot; tabindex=&quot;-1&quot;&gt;Current questions&lt;/h2&gt;
&lt;p&gt;A few things I&#39;ve been thinking and don&#39;t currently have answers for:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Can a &lt;code&gt;webc:setup&lt;/code&gt; access &amp;quot;props&amp;quot;?&lt;/li&gt;
&lt;li&gt;How can you modify the attributes on non-html-component?&lt;/li&gt;
&lt;li&gt;I keep getting a &lt;code&gt;Uncaught SyntaxError: Identifier &#39;...&#39; has already been declared&lt;/code&gt; &amp;amp; have to restart the 11ty.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;next-steps&quot; tabindex=&quot;-1&quot;&gt;Next steps&lt;/h2&gt;
&lt;p&gt;My team has a custom-elements based design system, partly using &lt;a href=&quot;https://every-layout.dev/&quot;&gt;every-layout&lt;/a&gt;, and it has an existing &lt;a href=&quot;https://alembic.openlab.dev/install/ssg/&quot;&gt;Eleventy plugin&lt;/a&gt; to generate styles and scripts. I wonder how I could migrate or provide a way to do this via WebC instead. There are currently some pretty big regexes I&#39;d rather not have to maintain!&lt;/p&gt;
&lt;p&gt;Here are some places you could go next:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.11ty.dev/docs/languages/webc/#webc-reference&quot;&gt;The WebC reference&lt;/a&gt; is the obvious place to look for more information and inspiration. You can do stuff like if statements or for loops or provide slots to describe how WebC will embed content in your component.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.11ty.dev/docs/languages/webc/#dynamic-attributes-and-properties&quot;&gt;Props and Attributes&lt;/a&gt; are really powerful and let you process Eleventy data into your templated content.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/11ty/eleventy-plugin-bundle&quot;&gt;11ty/eleventy-plugin-bundle&lt;/a&gt; is a good place to look if you&#39;re interested in how the script and style bundling works. A big snoop through the code helped me work out what was going on.&lt;/li&gt;
&lt;li&gt;MDN has a good guide on &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Web_Components/Using_custom_elements&quot;&gt;getting started with custom elements&lt;/a&gt; if you want to learn more about those.&lt;/li&gt;
&lt;li&gt;I haven&#39;t dived into it yet, but &lt;a href=&quot;https://www.11ty.dev/docs/languages/webc/#using-javascript-to-setup-your-component&quot;&gt;webc:setup&lt;/a&gt; looks really interesting&lt;/li&gt;
&lt;li&gt;I&#39;m working on a &amp;quot;year in review&amp;quot; type thing for a coffee club I&#39;m in and it&#39;s using WebC, &lt;a href=&quot;https://github.com/robb-j/year-in-rebrew&quot;&gt;robb-j/year-in-rebrew&lt;/a&gt; if you&#39;re interested.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Found this useful or spotted a mistake? Let me know on &lt;a href=&quot;https://social.lol/@r0b&quot;&gt;Mastodon&lt;/a&gt;!.&lt;/p&gt;
</content
    >
  </entry> 
  <entry>
    <title>Creating a HTTP proxy with Deno</title>
    <link
      href="https://blog.r0b.io/post/creating-a-proxy-with-deno/"
      rel="alternate"
      type="text/html"
      title="Creating a HTTP proxy with Deno"
    />
    <updated>2023-08-12T00:00:00Z</updated>
    <id>https://blog.r0b.io/post/creating-a-proxy-with-deno/</id>
    <content
      type="html"
      >&lt;p&gt;I&#39;ve been playing around with &lt;a href=&quot;https://deno.land/&quot;&gt;Deno&lt;/a&gt; quite a bit recently. I&#39;m really liking all of the integrated tooling and it&#39;s web-standards based approach.&lt;/p&gt;
&lt;p&gt;I wanted to create a little proxy so that I could access our Grafana dashboard on a Raspberry Pi-based display in our office. The problem is that Grafana requires authentication, but there was no way to configure the kiosk to provide it.&lt;/p&gt;
&lt;p&gt;So I set about to create a proxy server that could inject the authentication needed to access the dashboard. I ran the proxy on the Raspberry Pi and pointed the kiosk to the proxy rather that grafana directly. Now it easily boots up and shows our dashboard.&lt;/p&gt;
&lt;h2 id=&quot;the-code&quot; tabindex=&quot;-1&quot;&gt;The code&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;proxy.ts&lt;/em&gt;&lt;/p&gt;
&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;Deno&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;serve&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; port&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;8080&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;request&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; pathname&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; search &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token constant&quot;&gt;URL&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;request&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;url&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; url &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token constant&quot;&gt;URL&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;.&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; pathname&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;https://example.com&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  url&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;search &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; search

  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; headers &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Headers&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;request&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;headers&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  headers&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;Host&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; url&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;hostname&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  headers&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;Authorization&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; Deno&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;env&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;PROXY_AUTHORIZATION&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;fetch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;url&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    method&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; request&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;method&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    headers&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    body&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; request&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;body&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    redirect&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;manual&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That&#39;s all you need. I&#39;ll break it down and talk about some of the nice Deno things I found along the way.&lt;/p&gt;
&lt;p&gt;First, we&#39;re using the new &lt;code&gt;Deno.serve&lt;/code&gt; API which I really like. It&#39;s nicely based on web-standards and uses the same &lt;code&gt;Request&lt;/code&gt; and &lt;code&gt;Response&lt;/code&gt; objects that the &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API&quot;&gt;fetch API&lt;/a&gt; uses. This is really useful later.&lt;/p&gt;
&lt;p&gt;The API is pretty minimal too. You say what port you want to run on and provide a callback to process the request and return a response.
One gripe is the &lt;code&gt;request.url&lt;/code&gt; is not a URL object but just a string representation of the URL.&lt;/p&gt;
&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; pathname&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; search &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token constant&quot;&gt;URL&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;request&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;url&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; url &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token constant&quot;&gt;URL&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;.&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; pathname&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;https://example.com&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
url&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;search &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; search&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This bit grabs the pathname from the request being made, e.g. &lt;code&gt;/some/path&lt;/code&gt;, and combines it with the target URL to decide where to proxy to. There is a little hack here, because we know the &lt;code&gt;pathname&lt;/code&gt; on the URL will always start with a &lt;code&gt;/&lt;/code&gt;, we can prefix it with a &lt;code&gt;.&lt;/code&gt; to make it a relative pathname. So if the target URL also has a pathname in it, they will be combined together.&lt;/p&gt;
&lt;p&gt;It also copies the URL search parameters across to the new URL too, so they are preserved.&lt;/p&gt;
&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; headers &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Headers&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;request&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;headers&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
headers&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;Host&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; url&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;hostname&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
headers&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;Authorization&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; Deno&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;env&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;PROXY_AUTHORIZATION&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;One key part of the proxy is that it needs to force the &lt;code&gt;Host&lt;/code&gt; header to be set to the host being proxied to. If the server at the other end of the proxy uses a reverse proxy, this needs to be set so it knows how to handle the request. If we didn&#39;t do this, the host would be set to &lt;code&gt;localhost&lt;/code&gt; and that would confuse the target server.&lt;/p&gt;
&lt;p&gt;The other bit here is that it injects the &lt;code&gt;Authorization&lt;/code&gt; header, which is what we wanted to do all along. It grabs the value from an environment variable and puts it onto the headers, ready to be part of the proxy request.&lt;/p&gt;
&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;fetch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;url&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  method&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; request&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;method&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  headers&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  body&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; request&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;body&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  redirect&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;manual&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The final bit is to return a new &lt;code&gt;Response&lt;/code&gt; (or promise of one) to the &lt;code&gt;Deno.serve&lt;/code&gt; handler. Because the API uses exactly the same objects as the fetch API we can do this directly without any custom processing needed. Here it returns a promise for a request that shares the same HTTP method, uses the customised headers, streams the request body and tweaks the redirection logic.&lt;/p&gt;
&lt;p&gt;That&#39;s it, all you need to do is run it with a deno command:&lt;/p&gt;
&lt;pre class=&quot;language-sh&quot;&gt;&lt;code class=&quot;language-sh&quot;&gt;deno run --allow-net proxy.ts&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I hope you found this interesting. I&#39;ve been quite liking Deno recently, I think it&#39;s worth a try. I&#39;ve also been playing around more in-depth with a little proxy server for some work and personal infrastructure, &lt;a href=&quot;https://r.r0b.io/goldiprox&quot;&gt;goldiprox&lt;/a&gt;, if you&#39;re interested to see some more Deno proxy stuff. There is also a more fleshed out example in &lt;a href=&quot;https://github.com/robb-j/r0b-blog/blob/main/examples/proxy.ts&quot;&gt;examples/proxy.ts&lt;/a&gt; to check out.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;Spotted a mistake or have some feedback, &lt;a href=&quot;https://social.lol/@r0b&quot;&gt;let me know on Mastodon!&lt;/a&gt;.&lt;/p&gt;
</content
    >
  </entry> 
  <entry>
    <title>Host an ics calendar feed with Eleventy</title>
    <link
      href="https://blog.r0b.io/post/host-an-ics-calendar-feed-with-eleventy/"
      rel="alternate"
      type="text/html"
      title="Host an ics calendar feed with Eleventy"
    />
    <updated>2023-05-21T00:00:00Z</updated>
    <id>https://blog.r0b.io/post/host-an-ics-calendar-feed-with-eleventy/</id>
    <content
      type="html"
      >&lt;p&gt;I recently wanted to host an ical feed for some events that I was organising. For the last MozFest, we added a feature to let you subscribe to the events in your MySchedule in your own calendar. People quite liked this feature and I thought it could work nicely in an Eleventy website.&lt;/p&gt;
&lt;p&gt;Under the hood a calendar feed is a &lt;a href=&quot;https://www.rfc-editor.org/rfc/rfc5545&quot;&gt;ical&lt;/a&gt; file that is hosted on the web. In a calendar client, you subscribe to that URL and it will periodically fetch that file, parse out the events in there and show them in your calendar.&lt;/p&gt;
&lt;p&gt;You &lt;em&gt;could&lt;/em&gt; manually create or template that ical file, but that would be pretty fiddly and rife for mistakes, so let&#39;s use the &lt;a href=&quot;https://github.com/sebbo2002/ical-generator&quot;&gt;ical-generator&lt;/a&gt; package instead.&lt;/p&gt;
&lt;h2 id=&quot;set-up&quot; tabindex=&quot;-1&quot;&gt;Set up&lt;/h2&gt;
&lt;p&gt;Let&#39;s create a fresh Eleventy project to see how this all works. First we&#39;ll need an NPM to install Eleventy and &lt;code&gt;ical-generator&lt;/code&gt;.&lt;/p&gt;
&lt;pre class=&quot;language-sh&quot;&gt;&lt;code class=&quot;language-sh&quot;&gt;&lt;span class=&quot;token function&quot;&gt;mkdir&lt;/span&gt; eleventy-ical
&lt;span class=&quot;token builtin class-name&quot;&gt;cd&lt;/span&gt; eleventy-ical

&lt;span class=&quot;token function&quot;&gt;npm&lt;/span&gt; init &lt;span class=&quot;token parameter variable&quot;&gt;-y&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;npm&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;install&lt;/span&gt; @11ty/eleventy ical-generator&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;All of the code is at &lt;a href=&quot;https://github.com/robb-j/r0b-blog/tree/main/examples/eleventy-ical&quot;&gt;examples/eleventy-ical&lt;/a&gt; if you want to jump ahead and see it all in one place.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;events-collection&quot; tabindex=&quot;-1&quot;&gt;Events collection&lt;/h3&gt;
&lt;p&gt;In this set up, each event will be a page in an &lt;a href=&quot;https://www.11ty.dev/docs/collections/&quot;&gt;Eleventy collection&lt;/a&gt;. In Eleventy you can group pages using tags to later query for them in other pages. We can also tell Eleventy not to render these pages by setting &lt;code&gt;permalink: false&lt;/code&gt; in their front-matter. We can do both of these easily by setting it using a &lt;a href=&quot;https://www.11ty.dev/docs/data-template-dir/&quot;&gt;directory data file&lt;/a&gt; which means there is less to configure in each event page.&lt;/p&gt;
&lt;p&gt;So let&#39;s create the &lt;code&gt;events/events.json&lt;/code&gt; in a new &lt;code&gt;events&lt;/code&gt; directory like below:&lt;/p&gt;
&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;tags&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;events&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;permalink&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Let&#39;s put an event in our new collection. You can name these whatever you want, for my calendar I&#39;ve numbered them and padded the start so they&#39;re nice and alphabetical in my IDE. Each event has a name and location along with the start and end dates. Then the page body can be used for the even description. Here&#39;s &lt;code&gt;events/001.md&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-md&quot;&gt;&lt;code class=&quot;language-md&quot;&gt;&lt;span class=&quot;token front-matter-block&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;---&lt;/span&gt;
&lt;span class=&quot;token front-matter yaml language-yaml&quot;&gt;&lt;span class=&quot;token key atrule&quot;&gt;title&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Meeting 001
&lt;span class=&quot;token key atrule&quot;&gt;start&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token datetime number&quot;&gt;2023-05-02T15:00:00Z&lt;/span&gt;
&lt;span class=&quot;token key atrule&quot;&gt;end&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token datetime number&quot;&gt;2023-05-02T16:00:00Z&lt;/span&gt;
&lt;span class=&quot;token key atrule&quot;&gt;location&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; User Study Space&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;---&lt;/span&gt;&lt;/span&gt;

This is gonna be the best event ever!&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;calendar-metadata&quot; tabindex=&quot;-1&quot;&gt;Calendar metadata&lt;/h3&gt;
&lt;p&gt;To generate a feed, its useful to define some of the metadata so it can also be used within other Eleventy templates. So let&#39;s create &lt;code&gt;_data/calendar.json&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;title&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;My calendar&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;description&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Events about all the cool things&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;organisation&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Rob&#39;s calendars&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;url&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;https://feed.r0b.io&quot;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This isn&#39;t necessary just to generate the feed but it is useful to share these values with other Eleventy templates, like pages that showcase the feed.&lt;/p&gt;
&lt;h3 id=&quot;eleventy-template&quot; tabindex=&quot;-1&quot;&gt;Eleventy template&lt;/h3&gt;
&lt;p&gt;Now we have an event and the metadata, we can finally generate our feed. To do this, we&#39;ll use an Eleventy &lt;a href=&quot;https://www.11ty.dev/docs/languages/javascript/#classes&quot;&gt;class-based template&lt;/a&gt;. This means that we can dynamically generate a page with the exact contents we want.&lt;/p&gt;
&lt;p&gt;Here&#39;s the &lt;code&gt;feed.11ty.js&lt;/code&gt; to create:&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  ICalCalendar&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  ICalAlarmType&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  ICalCalendarMethod&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;ical-generator&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

module&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;exports &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;FeedTemplate&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// Setup Eleventy data for this template,&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// namely set the name of the file to be generated&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token literal-property property&quot;&gt;permalink&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;feed.ics&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token comment&quot;&gt;// The render method is called&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;render&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; calendar&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; collections &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// Generate a calendar object based on the calendar configuration&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// plus information provided by eleventy&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; cal &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ICalCalendar&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token literal-property property&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; calendar&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;title&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token literal-property property&quot;&gt;description&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; calendar&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;description&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token literal-property property&quot;&gt;prodId&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token literal-property property&quot;&gt;company&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; calendar&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;organisation&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token literal-property property&quot;&gt;product&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;Eleventy&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token literal-property property&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; calendar&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;url &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;page&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;url&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token literal-property property&quot;&gt;method&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; ICalCalendarMethod&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;PUBLISH&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;// Loop through of each of our events using the collection&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; page &lt;span class=&quot;token keyword&quot;&gt;of&lt;/span&gt; collections&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;events&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token comment&quot;&gt;// Create a calendar event from each page&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; event &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; cal&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;createEvent&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token literal-property property&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;calendar&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;url&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;page&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;fileSlug&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token literal-property property&quot;&gt;start&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; page&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;data&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;start&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token literal-property property&quot;&gt;end&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; page&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;data&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;end&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token literal-property property&quot;&gt;summary&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; page&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;data&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;title&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token literal-property property&quot;&gt;description&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; page&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;template&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;frontMatter&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;content&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token literal-property property&quot;&gt;location&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; page&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;data&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;location&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

      &lt;span class=&quot;token comment&quot;&gt;// Add an alert to the event&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; alarm &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Date&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;page&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;data&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;start&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
      alarm&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setMinutes&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;alarm&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getMinutes&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;15&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
      event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;createAlarm&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token literal-property property&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; ICalAlarmType&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;display&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token literal-property property&quot;&gt;trigger&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; alarm&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;// Generate the ical file and return it for Eleventy&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; cal&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toString&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;There a few bits going on here. The file is exporting a class which is our Eleventy template. Eleventy will create an instance of this class for us and call the methods when it needs to.&lt;/p&gt;
&lt;p&gt;Eleventy will first call &lt;code&gt;data()&lt;/code&gt; on our class to generate the data for the page, similar to the front-matter in a markdown file. We use this to set the &lt;code&gt;permalink&lt;/code&gt; to tell Eleventy what we want our file to be called.&lt;/p&gt;
&lt;p&gt;Next Eleventy will call the &lt;code&gt;render(data)&lt;/code&gt; method. This takes all the data Eleventy has generated from the cascade and passes it as a parameter for use in JavaScript. Here we destructure the &lt;code&gt;calendar&lt;/code&gt; field, generated from the data above, and &lt;code&gt;collections&lt;/code&gt;. It&#39;s also useful to remember that you have access to the &lt;code&gt;this&lt;/code&gt; inside these methods which have extra information, like the page&#39;s URL.&lt;/p&gt;
&lt;p&gt;Inside the render, it first creates a calendar object and sets up the metadata for the feed.&lt;/p&gt;
&lt;p&gt;After setting up the calendar it loops through each page in the &lt;code&gt;events&lt;/code&gt; collection. For each page it creates a corresponding event object and adds it to the calendar. One important point about events is the &lt;code&gt;id&lt;/code&gt; attribute, this should be unique and persistent so calendar clients know whether to create a new event or update an existing one. To keep this unique it combines the URL of the feed itself and the identifier of the event.&lt;/p&gt;
&lt;p&gt;I added a bit of logic here to create an alert on the event 15 minutes before the event starts. This doesn&#39;t have to be hardcoded and you could define this on more of a per-page basis if you like.&lt;/p&gt;
&lt;p&gt;Finally, the render function generates the ics and returns it for Eleventy to create a file using the &lt;code&gt;cal.toString()&lt;/code&gt; method.&lt;/p&gt;
&lt;h3 id=&quot;timezones&quot; tabindex=&quot;-1&quot;&gt;Timezones&lt;/h3&gt;
&lt;p&gt;You can set the &lt;code&gt;timezone&lt;/code&gt; on the calendar object or on the events themselves, but it isn&#39;t as simple as that. An ical feed needs a &lt;em&gt;VTimezone&lt;/em&gt; object which you need a generator for. This can be provided by a library like &lt;a href=&quot;https://github.com/touch4it/ical-timezones&quot;&gt;@touch4it/ical-timezones&lt;/a&gt;. There are some good docs in the &lt;a href=&quot;https://github.com/sebbo2002/ical-generator/tree/develop#-date-time--timezones&quot;&gt;ical-generator readme&lt;/a&gt; for more information.&lt;/p&gt;
&lt;p&gt;For my use-case I didn&#39;t need this. I used a little Node.js script to generate the pages which creates the date objects for me and automatically writes them with the correct offset in UTC so they&#39;re at the right time. By default all dates in the calendar are assumed to be in UTC.&lt;/p&gt;
&lt;h2 id=&quot;generating-the-feed&quot; tabindex=&quot;-1&quot;&gt;Generating the feed&lt;/h2&gt;
&lt;p&gt;Now that everything is set up, you should have a directory structure like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;.
├── _data
│   └── calendar.json
├── events
│   ├── 001.md
│   └── events.json
├── feed.11ty.js
├── node_modules
│   ├── ...
├── package-lock.json
└── package.json
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We can run Eleventy and it will generate our ical feed for us! This will create &lt;code&gt;_site/feed.ics&lt;/code&gt; which contains the content of our feed, ready to host on the internet.&lt;/p&gt;
&lt;pre class=&quot;language-sh&quot;&gt;&lt;code class=&quot;language-sh&quot;&gt;npx eleventy&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;next-steps&quot; tabindex=&quot;-1&quot;&gt;Next steps&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;timezones&lt;/strong&gt; — As mentioned above, I didn&#39;t really get into timezones, so things could definitely be improved here. The &lt;a href=&quot;https://github.com/sebbo2002/ical-generator/tree/develop#-date-time--timezones&quot;&gt;ical-generator&lt;/a&gt; docs are the best place to start for this.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;create a site&lt;/strong&gt; — You might want to host a little static website alongside your feed. It could simply link to the feed URL with some subscribe instructions or it could pull in event information in a fun dynamic way.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;host the site&lt;/strong&gt; — The website needs to be hosted for people to subscribe to your events!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;better event descriptions&lt;/strong&gt; — This set up dumps the markdown content into the body of the events but markdown won&#39;t work in calendar clients.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;per-event alerts&lt;/strong&gt; — You could define an &lt;code&gt;alerts&lt;/code&gt; section in an event&#39;s front-matter and use that to set different alerts on different events.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;generator script&lt;/strong&gt; — My feed was Tuesday-based so I created a script to create an event on the next occurring Tuesday and fill in the front-matter for me. You could do something similar or even take arguments for how you want to create the events. If you were feeling fancy you could hook up something like &lt;a href=&quot;https://github.com/decaporg/decap-cms&quot;&gt;decap-cms&lt;/a&gt; to edit the events using a web UI.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;Hit me up on &lt;a href=&quot;https://social.lol/@r0b&quot;&gt;Mastodon&lt;/a&gt; if you liked this, have feedback or just want to know more!&lt;/p&gt;
</content
    >
  </entry> 
  <entry>
    <title>Embed JSDoc comments in an Eleventy website</title>
    <link
      href="https://blog.r0b.io/post/embed-jsdoc-comments-in-an-eleventy-website-with-ts-morph/"
      rel="alternate"
      type="text/html"
      title="Embed JSDoc comments in an Eleventy website"
    />
    <updated>2023-05-03T00:00:00Z</updated>
    <id>https://blog.r0b.io/post/embed-jsdoc-comments-in-an-eleventy-website-with-ts-morph/</id>
    <content
      type="html"
      >&lt;p&gt;You can use &lt;a href=&quot;https://jsdoc.app/&quot;&gt;JSDoc&lt;/a&gt; to automatically generate a documentation website to fully describe the contents of your API. This works by adding special comments around your JavaScript or TypeScript code that describe the code in more detail. For JavaScript you can even describe the types to better help the people using your API.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;An example JSDoc comment&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;/**
  It erm ... adds two numbers together
  
  @param {number} a The first number
  @param {number} b The number to add to the first number
  @returns {number} The sum of the two numbers
*/&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;a&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; b&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/blockquote&gt;
&lt;p&gt;JSDoc can then generate a website to showcase your API for you. Personally, I&#39;ve found these generated sites hard to navigate and difficult to get to the information I need. You can create your own template but that requires learning the ins and outs of JSDoc and how it&#39;s templating works.&lt;/p&gt;
&lt;p&gt;For a recent project, I was creating a site to demonstrate and document a design system. I wanted to reference the JSDoc I&#39;d already written in the code, rather than duplicate it. I found the &lt;a href=&quot;https://github.com/dsherret/ts-morph&quot;&gt;ts-morph&lt;/a&gt; package on GitHub which makes it easier to parse TypeScript&#39;s Abstract Syntax Tree (&lt;em&gt;AST&lt;/em&gt;). My ideal integration was to only pull out the JSDoc comments and embed them as markdown on the site.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;AST&lt;/em&gt; is a data structure that represents the code in a file, rather than the raw text string. It allows it to be queried and modified in-place. &lt;a href=&quot;https://astexplorer.net/&quot;&gt;AST Explorer&lt;/a&gt; is a good tool to play around and inspect the trees generated from code files.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;There are a couple of benefits to this general approach:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;These doc comments only need to be written once. The same comment can be seen in an IDE code completions and on the documentation website. Those comments only need to be updated in one place, so can&#39;t get out of sync.&lt;/li&gt;
&lt;li&gt;The comments are close to the code that they document. So it&#39;s easier to update them when the code the document changes. This feels in the vein of &lt;strong&gt;Locality&lt;/strong&gt;, from The Unicorn Project&#39;s &lt;a href=&quot;https://itrevolution.com/articles/five-ideals-of-devops/&quot;&gt;Five Ideals&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;You have complete control of how your documentation site looks and feels. It&#39;s important to properly think through documentation. I&#39;ve sometimes tried &amp;quot;Documentation driven development&amp;quot; where the docs are written before any code to get a feel for how something should work from a consumer&#39;s perspective, rather than jumping into the technical implementation.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;how-it-works&quot; tabindex=&quot;-1&quot;&gt;How it works&lt;/h2&gt;
&lt;p&gt;Ok you&#39;re sold, or still interested to learn more? To show how it works, we&#39;ll create a fresh Eleventy website, along with an &amp;quot;API&amp;quot; to document and hook up. In a terminal, let&#39;s scaffold the project and fetch NPM dependencies:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;mkdir&lt;/span&gt; eleventy-jsdoc
&lt;span class=&quot;token builtin class-name&quot;&gt;cd&lt;/span&gt; eleventy-jsdoc

&lt;span class=&quot;token function&quot;&gt;npm&lt;/span&gt; init &lt;span class=&quot;token parameter variable&quot;&gt;-y&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;npm&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;install&lt;/span&gt; @11ty/eleventy typescript ts-morph @11ty/eleventy-plugin-syntaxhighlight markdown-it slugify&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;All of the code is at &lt;a href=&quot;https://github.com/robb-j/r0b-blog/tree/main/examples/eleventy-jsdoc&quot;&gt;examples/eleventy-jsdoc&lt;/a&gt; if you want to jump ahead see it all in one place.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Let&#39;s make our library, &lt;code&gt;lib.js&lt;/code&gt;, this is the API we&#39;re creating and want to document. We&#39;re going to pull these JSDoc comments through into our website:&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;/**
  It ermm ... adds two numbers together
  
  ```js
  import { add } from &quot;my-api&quot;
  
  const answer = add(40, 2);
  ```
  
  @param {number} a The first number to add
  @param {number} b The number to add to the first number
  @returns {number} The sum of the two arguments
*/&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;a&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; b&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; a &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; b
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;/**
  Greet the nice person by their name
  
  ```js
  import { greet } from &quot;my-api&quot;
  
  const message = greet(&#39;Geoff Testington&#39;)
  ```
  
  @param {string} name The name of the person to greet
  @returns {string} The boring greeting message
 */&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;hello&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;name &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;General Kenobi&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;Hello there, &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;name&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now add an Eleventy configuration file, &lt;code&gt;eleventy.config.js&lt;/code&gt;, to add some custom logic:&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; markdownIt &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;markdown-it&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; syntaxHighlight &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;@11ty/eleventy-plugin-syntaxhighlight&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; slugify &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;slugify&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// Create our own markdown-it instance to be used in a few places&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; md &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;markdownIt&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;html&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
md&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;disable&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;code&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// A snippet to generate some HTML for a given API export&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;apiDoc&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;item&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;
&amp;lt;div class=&quot;apiDoc&quot;&gt;
&amp;lt;h3&gt;&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;item&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&amp;lt;/h3&gt;
&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;md&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;render&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;item&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;content&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;
&amp;lt;/div&gt;
&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;

module&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function-variable function&quot;&gt;exports&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;eleventyConfig&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  eleventyConfig&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addPlugin&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;syntaxHighlight&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  eleventyConfig&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setLibrary&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;md&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; md&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;token comment&quot;&gt;// Manually watch out API so that&lt;/span&gt;
  eleventyConfig&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addWatchTarget&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;./lib.js&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;token comment&quot;&gt;// NOTE: There is a bit of a hack here in that Eleventy mutates&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// this instance so it magically gets syntax-highlighting applied.&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// This is also exploited in the `apiDoc` shortcode below.&lt;/span&gt;
  eleventyConfig&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addFilter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;md&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;content&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; md&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;render&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;content&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;token comment&quot;&gt;// A shortcode to render the JSDoc comment for an export from a given entry-point&lt;/span&gt;
  eleventyConfig&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addShortcode&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;apiDoc&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;api&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; entrypoint&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; item &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; api&lt;span class=&quot;token operator&quot;&gt;?.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;entrypoint&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;item&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Error&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;Unknown API item &#39;&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;name&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39; in &#39;&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;entrypoint&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;apiDoc&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;item&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;token comment&quot;&gt;// A filter to generate a URL-friendly slug for a text string&lt;/span&gt;
  eleventyConfig&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addFilter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;slug&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;text&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;slugify&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;text&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;token comment&quot;&gt;// A utility to pretty-print JSON&lt;/span&gt;
  eleventyConfig&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addFilter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;json&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;JSON&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;stringify&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;value&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;token comment&quot;&gt;// Tell Eleventy to use Nunjucks&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;markdownTemplateEngine&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;njk&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;htmlTemplateEngine&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;njk&#39;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Next let&#39;s create a HTML base layout for our site, &lt;code&gt;_includes/html.njk&lt;/code&gt;, which our pages can use:&lt;/p&gt;
&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;token doctype&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;!&lt;/span&gt;&lt;span class=&quot;token doctype-tag&quot;&gt;DOCTYPE&lt;/span&gt; &lt;span class=&quot;token name&quot;&gt;html&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;html&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;head&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;title&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;My Fancy API&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;title&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;meta&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;charset&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;utf8&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;meta&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;viewport&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;content&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;width=device-width&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;&amp;lt;!-- Some base styles so raw HTML doesn&#39;t look gross --&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;link&lt;/span&gt;
      &lt;span class=&quot;token attr-name&quot;&gt;rel&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;stylesheet&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
      &lt;span class=&quot;token attr-name&quot;&gt;href&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;https://cdn.jsdelivr.net/npm/water.css@2/out/water.css&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;&amp;lt;!-- A light-theme for our code --&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;link&lt;/span&gt;
      &lt;span class=&quot;token attr-name&quot;&gt;rel&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;stylesheet&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
      &lt;span class=&quot;token attr-name&quot;&gt;href&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;https://cdn.jsdelivr.net/npm/prismjs@1.29.0/themes/prism.min.css&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
      &lt;span class=&quot;token attr-name&quot;&gt;media&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;(prefers-color-scheme: light)&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;&amp;lt;!-- A dark-theme for our code --&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;link&lt;/span&gt;
      &lt;span class=&quot;token attr-name&quot;&gt;rel&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;stylesheet&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
      &lt;span class=&quot;token attr-name&quot;&gt;href&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;https://cdn.jsdelivr.net/npm/prismjs@1.29.0/themes/prism-tomorrow.min.css&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
      &lt;span class=&quot;token attr-name&quot;&gt;media&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;(prefers-color-scheme: dark)&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;style&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token style&quot;&gt;&lt;span class=&quot;token language-css&quot;&gt;
      &lt;span class=&quot;token comment&quot;&gt;/** Something to make our code snippets POP */&lt;/span&gt;
      &lt;span class=&quot;token selector&quot;&gt;.apiDoc&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token property&quot;&gt;padding-left&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 1em&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token property&quot;&gt;border-left&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 5px solid &lt;span class=&quot;token function&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;--text-bright&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;style&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;head&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;body&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    {{ content | safe }}
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;body&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;html&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And now, we can use that layout to add our first page, &lt;code&gt;index.md&lt;/code&gt;, a basic homepage to link to our other pages:&lt;/p&gt;
&lt;pre class=&quot;language-md&quot;&gt;&lt;code class=&quot;language-md&quot;&gt;&lt;span class=&quot;token front-matter-block&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;---&lt;/span&gt;
&lt;span class=&quot;token front-matter yaml language-yaml&quot;&gt;&lt;span class=&quot;token key atrule&quot;&gt;layout&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; html.njk&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;---&lt;/span&gt;&lt;/span&gt;

&lt;span class=&quot;token title important&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;#&lt;/span&gt; My Fancy API&lt;/span&gt;

This is the site that will tell you all about the API and how to use it.

&lt;span class=&quot;token list punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token url&quot;&gt;[&lt;span class=&quot;token content&quot;&gt;Guide&lt;/span&gt;](&lt;span class=&quot;token url&quot;&gt;/guide&lt;/span&gt;)&lt;/span&gt;
&lt;span class=&quot;token list punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token url&quot;&gt;[&lt;span class=&quot;token content&quot;&gt;Docs&lt;/span&gt;](&lt;span class=&quot;token url&quot;&gt;/docs&lt;/span&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now for the first proper page, &lt;code&gt;guide.md&lt;/code&gt;. This is a page to showcase specific bits of the API.
This uses the &lt;code&gt;apiDoc&lt;/code&gt; shortcode registered in the Eleventy config to directly embed our JSDoc comments!
The shortcode needs the &lt;code&gt;api&lt;/code&gt; data object passed to it, more on that later, along with the entry-point and the named export you want to embed.&lt;/p&gt;
&lt;pre class=&quot;language-md&quot;&gt;&lt;code class=&quot;language-md&quot;&gt;&lt;span class=&quot;token front-matter-block&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;---&lt;/span&gt;
&lt;span class=&quot;token front-matter yaml language-yaml&quot;&gt;&lt;span class=&quot;token key atrule&quot;&gt;layout&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; html.njk&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;---&lt;/span&gt;&lt;/span&gt;

&lt;span class=&quot;token url&quot;&gt;[&lt;span class=&quot;token content&quot;&gt;Home&lt;/span&gt;](&lt;span class=&quot;token url&quot;&gt;/&lt;/span&gt;)&lt;/span&gt;

&lt;span class=&quot;token title important&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;#&lt;/span&gt; Getting started&lt;/span&gt;

&lt;span class=&quot;token blockquote punctuation&quot;&gt;&gt;&lt;/span&gt; This page demonstrates pulling specific api exports using the shortcode

This is a detailed guide to using the API, these methods might be useful:

{% apiDoc api, &#39;lib.js&#39;, &#39;add&#39; %}

Once you&#39;ve got the hang of that, you can try the &lt;span class=&quot;token code-snippet code keyword&quot;&gt;`hello`&lt;/span&gt; method:

{% apiDoc api, &#39;lib.js&#39;, &#39;hello&#39; %}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To show another use of the integration, we can make a page that enumerates the whole API to document everything, &lt;code&gt;docs.md&lt;/code&gt;.
This shows how you can do whatever you like with that &lt;code&gt;api&lt;/code&gt; data object, if you pull in extra information from &lt;code&gt;ts-morph&lt;/code&gt; you can access that here.
For this example, it loops through each entry-point and their corresponding named exports to dump them all out into the page.&lt;/p&gt;
&lt;p&gt;There is also a little &amp;quot;Debug&amp;quot; section so you can see what was put onto that &lt;code&gt;api&lt;/code&gt; object.&lt;/p&gt;
&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;---
layout: html.njk
---

&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;section&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;

&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;p&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;a&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;href&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;/&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;Home&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;a&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;p&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;

&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;h1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;API Docs&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;h1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;

&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;p&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;This is all the things the API does, in alphabetical order &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;p&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;

&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;blockquote&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;This page shows how to enumerate each entry point in the API and render each export from them&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;blockquote&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;

&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;details&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;summary&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;Debug&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;summary&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;pre&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;{{ api | json }}&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;pre&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;details&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;section&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;

{% for entrypoint, items in api %}

&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;section&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;h2&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;{{ entrypoint | slugify }}&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;{{ entrypoint }}&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;h2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  
  {% for name, item in items  %}
  {% if item.content %}
  
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;div&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;apiDoc&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;h3&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;{{ name | slugify }}&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;{{ name }}&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;h3&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    
    {{ item.content | md | safe }}
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  
  {% endif %}
  {% endfor %}
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;section&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;

{% endfor %}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That is the basic site setup, now we need to start linking it up with TypeScript with &lt;code&gt;ts-morph&lt;/code&gt;. For that we&#39;ll need to add a TypeScript config file, &lt;code&gt;tsconfig.json&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;compilerOptions&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;allowJs&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To get this &lt;code&gt;api&lt;/code&gt; object we&#39;ve seen in the templates, we&#39;ll use an Eleventy &lt;a href=&quot;https://www.11ty.dev/docs/data-global/&quot;&gt;global data file&lt;/a&gt;, &lt;code&gt;_data/api.js&lt;/code&gt;. It runs ones to generate a data object with JavaScript. This is the brunt of the integration, there is quite a bit going on here and the TypeScript &lt;em&gt;AST&lt;/em&gt; is quite complex.&lt;/p&gt;
&lt;p&gt;It loads the predefined entry-points up and parses their &lt;em&gt;AST&lt;/em&gt; nodes into memory. With those nodes we go through and find the code which is exported, i.e. code that uses JavaScript&#39;s &lt;code&gt;export&lt;/code&gt; modifier, and then collect the JSDoc comments from them. This version is setup here to ignore any exports marked with &lt;code&gt;@internal&lt;/code&gt;, but you could change that check for anything you like.&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; path &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;path&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; Project&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; Symbol &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;ts-morph&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; jsDocText &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token regex&quot;&gt;&lt;span class=&quot;token regex-delimiter&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token regex-source language-regex&quot;&gt;&#92;/&#92;*&#92;*([&#92;s&#92;S]+)&#92;*&#92;/&lt;/span&gt;&lt;span class=&quot;token regex-delimiter&quot;&gt;/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; jsDocTag &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token regex&quot;&gt;&lt;span class=&quot;token regex-delimiter&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token regex-source language-regex&quot;&gt;^[ &#92;t]*?@.*$&lt;/span&gt;&lt;span class=&quot;token regex-delimiter&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token regex-flags&quot;&gt;gm&lt;/span&gt;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// Which files should be imported and inspected&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; entrypoints &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;lib.js&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;api&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; project &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Project&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;tsConfigFilePath&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;tsconfig.json&#39;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;token comment&quot;&gt;// This is where our processed AST is going to get stored and return&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; output &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token comment&quot;&gt;// Loop through each of our entry-points&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; entry &lt;span class=&quot;token keyword&quot;&gt;of&lt;/span&gt; project&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getSourceFiles&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;entrypoints&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// Create a friendly name for the entry-point (by removing the absolute path)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; entryName &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; path&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;relative&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;process&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;cwd&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; entry&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getFilePath&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;// Start compiling the entry-point&lt;/span&gt;
    output&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;entryName&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;// Loop through each symbol that is exported from the file&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// NOTE: these may be export symbols in the entry-point&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// and not the actual code definitions with useful information on them&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; symbol &lt;span class=&quot;token keyword&quot;&gt;of&lt;/span&gt; entry&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getExportSymbols&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token comment&quot;&gt;// Skip any symbol marked with @internal&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;symbol&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getJsDocTags&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;some&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; t&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;internal&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;continue&lt;/span&gt;

      &lt;span class=&quot;token comment&quot;&gt;// If it is an alias, make sure to get what is aliased instead&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;symbol&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;isAlias&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; symbol &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; symbol&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getAliasedSymbolOrThrow&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

      &lt;span class=&quot;token comment&quot;&gt;// Put the export into the entry-point&lt;/span&gt;
      &lt;span class=&quot;token comment&quot;&gt;// You could do more processing here to capture more information if you like&lt;/span&gt;
      output&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;entryName&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;symbol&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getEscapedName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token literal-property property&quot;&gt;entryPoint&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; entryName&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token literal-property property&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; symbol&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getEscapedName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token literal-property property&quot;&gt;content&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;joinDocComments&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;symbol&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token literal-property property&quot;&gt;tags&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; symbol&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getJsDocTags&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; output
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;/**
  Compose all doc comments on a Symbol together into one markdown string.
  It gets the text out of a JSDoc comment,
  then strips out the annotations and joins them all as markdown paragraphs
  
  @param {Symbol} symbol
*/&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;joinDocComments&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;symbol&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; sections &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;

  &lt;span class=&quot;token comment&quot;&gt;// Each symbol might have one or more declarations,&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// each of which might have zero or more JSDoc comment regions&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; declaration &lt;span class=&quot;token keyword&quot;&gt;of&lt;/span&gt; symbol&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getDeclarations&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; range &lt;span class=&quot;token keyword&quot;&gt;of&lt;/span&gt; declaration&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getLeadingCommentRanges&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; match &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; jsDocText&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;exec&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;range&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getText&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;match&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;continue&lt;/span&gt;
      sections&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;push&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;match&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;replaceAll&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;jsDocTag&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token comment&quot;&gt;// Join all sections together with two newlines to make sure they are&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// seperate paragraphs in markdown&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; sections&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;join&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;&#92;n&#92;n&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

module&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;exports &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;api&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You should now have a directory structure something like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;.
├── _data
│   └── api.js
├── _includes
│   └── html.njk
├── docs.njk
├── eleventy.config.js
├── guide.md
├── index.md
├── lib.js
├── package-lock.json
├── package.json
└── tsconfig.json
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With all that setup, we can build and serve our site with &lt;code&gt;npx eleventy --serve&lt;/code&gt; and open it up in the browser 🥳. It should look like the pictures below:&lt;/p&gt;
&lt;p&gt;The first page is a &amp;quot;guide&amp;quot;, which shows how to embed specific bits of the API using the &lt;code&gt;apiDoc&lt;/code&gt; shortcode.&lt;/p&gt;
&lt;figure class=&quot;figureImage&quot;&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://blog.r0b.io/img/uFCLvkw12h-1200.webp 1200w&quot; /&gt;&lt;img alt=&quot;The guide page with some crafted notes and JSDoc snippets embedded in-between.&quot; loading=&quot;lazy&quot; src=&quot;https://blog.r0b.io/img/uFCLvkw12h-1200.jpeg&quot; width=&quot;1200&quot; height=&quot;753&quot; /&gt;&lt;/picture&gt;&lt;figcaption&gt;The guide page with some crafted notes and JSDoc snippets embedded in-between.&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;The second page is a catch-all &amp;quot;docs&amp;quot; page that dumps the entire API grouped by entry-point.&lt;/p&gt;
&lt;figure class=&quot;figureImage&quot;&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://blog.r0b.io/img/aHyULEzahx-1200.webp 1200w&quot; /&gt;&lt;img alt=&quot;The docs page listing out each entry-point and each named export in them.&quot; loading=&quot;lazy&quot; src=&quot;https://blog.r0b.io/img/aHyULEzahx-1200.jpeg&quot; width=&quot;1200&quot; height=&quot;753&quot; /&gt;&lt;/picture&gt;&lt;figcaption&gt;The docs page listing out each entry-point and each named export in them.&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;To recap,&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;There is an API we want to document in &lt;code&gt;lib.js&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;That file has JSDoc comments in, documenting the code in-place&lt;/li&gt;
&lt;li&gt;From &lt;code&gt;_data/api.js&lt;/code&gt;, we parse the &lt;em&gt;AST&lt;/em&gt; of the code and the JSDoc comments to make it available in Eleventy&lt;/li&gt;
&lt;li&gt;That data is available in templates globally as &lt;code&gt;api&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;There is an &lt;code&gt;apiDoc&lt;/code&gt; shortcode to quickly render a named export from a specific entry-point&lt;/li&gt;
&lt;li&gt;It all gets built into a nice website with syntax highlighting from &lt;a href=&quot;https://prismjs.com/&quot;&gt;prism&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;next-steps&quot; tabindex=&quot;-1&quot;&gt;Next steps&lt;/h2&gt;
&lt;p&gt;My use case was just getting those JSDoc comments out of the code and into the website, but in exploring that there are more things I think you could do.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;better embedding&lt;/strong&gt; — I left the &lt;code&gt;apiDoc&lt;/code&gt; shortcode quite brief on purpose, you might want a url-slug &lt;code&gt;id&lt;/code&gt; in there or to use a different heading tag.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Actually use TypeScript&lt;/strong&gt; — The example &lt;code&gt;lib.js&lt;/code&gt; is just JavaScript, you can of course use it with TypeScript too. I was just trying to keep things simple here.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;More TypeScript integration&lt;/strong&gt; — There is a load more information in the TypeScript &lt;em&gt;AST&lt;/em&gt; that isn&#39;t being used, I tried getting it to generate code signatures before but didn&#39;t get very far. There are also some cool things that could be done with the &amp;quot;tags&amp;quot; in JSDoc comments, e.g. &lt;code&gt;@param&lt;/code&gt;, maybe they could be processed more and put into HTML tables or something.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;A library&lt;/strong&gt; — with some more iteration, and some interest, there could be a nice library/eleventy-plugin here to make this a lot easier in the future, it&#39;s quite a lot of code in this post 🙄.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Work out the &amp;quot;hack&amp;quot; in eleventy.config.js&lt;/strong&gt; — I&#39;m not sure how properly get the syntax highlighting to work without relying on the mutation of my &lt;code&gt;md&lt;/code&gt; instance in there.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Configure the Watch Target&lt;/strong&gt; — there is only one file in the API here but with multiple you&#39;ll want to pass a glob pattern to &lt;code&gt;eleventyConfig.addWatchTarget&lt;/code&gt; to make it reload when any of your source code changes.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;Props to &lt;a href=&quot;https://fosstodon.org/@eleventy/110300521096431755&quot;&gt;Zach&lt;/a&gt; for prompting me to do this.&lt;/p&gt;
&lt;p&gt;Hit me up on &lt;a href=&quot;https://social.lol/@r0b&quot;&gt;Mastodon&lt;/a&gt; if you liked this, have feedback or just want to know more!&lt;/p&gt;
</content
    >
  </entry> 
  <entry>
    <title>Yoath released</title>
    <link
      href="https://blog.r0b.io/post/yoath-released/"
      rel="alternate"
      type="text/html"
      title="Yoath released"
    />
    <updated>2023-03-08T00:00:00Z</updated>
    <id>https://blog.r0b.io/post/yoath-released/</id>
    <content
      type="html"
      >&lt;figure class=&quot;figureImage&quot;&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://blog.r0b.io/img/MOeKh86gzW-1200.webp 1200w&quot; /&gt;&lt;img alt=&quot;A macOS status bar menu showing available OATH accounts ready to generate codes for&quot; loading=&quot;lazy&quot; src=&quot;https://blog.r0b.io/img/MOeKh86gzW-1200.png&quot; width=&quot;1200&quot; height=&quot;750&quot; /&gt;&lt;/picture&gt;&lt;figcaption&gt;A macOS status bar menu showing available OATH accounts ready to generate codes for&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;If you’ve got a YubiKey and you want to quickly get OATH codes from your macOS status bar it’s the app for you!
Check out Yoath on the &lt;a href=&quot;https://r0b.url.lol/yoath&quot;&gt;App Store&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Oh and it’s &lt;a href=&quot;https://github.com/robb-j/MiniYubiOath&quot;&gt;open source&lt;/a&gt;, because I wouldn’t trust an app that talks to my #YubiKey that wasn’t!&lt;/p&gt;
</content
    >
  </entry> 
  <entry>
    <title>Using URLPattern to add &quot;edit on GitHub&quot; support to my blog</title>
    <link
      href="https://blog.r0b.io/post/using-urlpattern-to-add-edit-on-github-support-to-my-blog/"
      rel="alternate"
      type="text/html"
      title="Using URLPattern to add &quot;edit on GitHub&quot; support to my blog"
    />
    <updated>2023-02-18T00:00:00Z</updated>
    <id>https://blog.r0b.io/post/using-urlpattern-to-add-edit-on-github-support-to-my-blog/</id>
    <content
      type="html"
      >&lt;p&gt;While wanting to make a quick change to a blog post, I wondered how hard it could be to add a simple &amp;quot;edit on GitHub&amp;quot; shortcut to my site.
On &lt;a href=&quot;https://github.com/&quot;&gt;github.com&lt;/a&gt; while in a repository, you can press &amp;quot;.&amp;quot; which opens the same repo on &lt;a href=&quot;https://github.dev/&quot;&gt;github.dev&lt;/a&gt; which is a lightweight vscode web app that lets you edit and commit changes back. I wanted to do something similar with my blog, but I only need to edit one post at a time.&lt;/p&gt;
&lt;figure class=&quot;figureVideo&quot;&gt;&lt;video controls=&quot;&quot; loop=&quot;&quot; width=&quot;640&quot; height=&quot;360&quot;&gt;&lt;source src=&quot;https://blog.r0b.io/video/edit-on-github-with-peek.mov&quot; type=&quot;video/mp4&quot; /&gt;&lt;/video&gt;&lt;figcaption&gt;Here is the whole thing in action, opening on GitHub with Arc&#39;s Peak&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;I started off adding an event listener to know when the &amp;quot;.&amp;quot; key is pressed:&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;keyup&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;key &lt;span class=&quot;token operator&quot;&gt;!==&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;.&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt;

  &lt;span class=&quot;token comment&quot;&gt;// Code goes here ...&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Next I needed to know if the visitor (me) is on a blog post, luckily I remembered that &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/URLPattern&quot;&gt;URLPattern&lt;/a&gt; is a thing!
Its relatively new thing (in 2023) and there isn&#39;t much support for it. As this feature is just for myself and I know what browser I&#39;m running on that isn&#39;t an issue, but it should still be implemented with progressive enhancement. So the feature is ignored if the API isn&#39;t available:&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// If URL pattern doesn&#39;t exist, go no further&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;URLPattern&#39;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; window&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now to use the API, you create a &lt;code&gt;URLPattern&lt;/code&gt; object and use it like a regex to match URLs. There is a string-based and object-based ways of defining a &lt;code&gt;URLPattern&lt;/code&gt;, I went for object-based. As I only wanted to match the pathname part, with object-based other attributes (like the host or protocol) are treated as wildcards so will not be considered in the matching.&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; pattern &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;URLPattern&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;pathname&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;/post/:slug/&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This pattern will match when any URL is passed to it that follows that pathname convention AND you can use it to get the &amp;quot;slug&amp;quot; part out too, which is useful soon.&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; result &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; pattern&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;exec&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;location&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;href&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;result&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I used the pattern like this, testing the current location of the visitor to see if it matches the pattern. I&#39;m a fan of early-exiting to avoid loads of nesting, so the callback stops here if the current URL does not match.&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;location&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;href &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;https://github.com/robb-j/r0b-blog/edit/main/content/post/&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;result&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;pathname&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;groups&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;slug&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;.md&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The final step is to redirect the user to the &amp;quot;edit&amp;quot; page on GitHub, which is templated with the &amp;quot;slug&amp;quot; extracted from the URLPattern. You can use the pattern-matching in any of the components of a URL, here it grabs the &lt;code&gt;slug&lt;/code&gt; from the matched pathname and uses it to create the edit on GitHub URL.&lt;/p&gt;
&lt;p&gt;I wasn&#39;t sure if it should open a new tab or redirect the current page. Because of &lt;a href=&quot;https://www.youtube.com/watch?v=biPlFWl64ws&quot;&gt;Arc&#39;s new Peek&lt;/a&gt; I decided to keep it on the same page and it all works very nicely.&lt;/p&gt;
&lt;p&gt;I hope this was interesting, let me know what you think on &lt;a href=&quot;https://social.lol/@r0b&quot;&gt;Mastodon&lt;/a&gt;!&lt;/p&gt;
</content
    >
  </entry> 
  <entry>
    <title>Trying to make a vanilla web app</title>
    <link
      href="https://blog.r0b.io/post/trying-to-make-a-vanilla-web-app/"
      rel="alternate"
      type="text/html"
      title="Trying to make a vanilla web app"
    />
    <updated>2023-01-28T00:00:00Z</updated>
    <id>https://blog.r0b.io/post/trying-to-make-a-vanilla-web-app/</id>
    <content
      type="html"
      >&lt;p&gt;At work we have a Coffee Club. It goes back a while but we cooperatively bought a coffee machine and have been trying to work out the best way to organise coffee buying ever since.&lt;/p&gt;
&lt;details&gt;
&lt;summary&gt;A brief history of the club&lt;/summary&gt;
&lt;p&gt;In our old office we were split on two floors and upstairs had an &amp;quot;official&amp;quot; lab-sanctioned and paid machine and on our floor we had naught. One Black Friday a few of us clubbed together to buy a bean-to-cup machine and a bunch of coffee beans.&lt;/p&gt;
&lt;p&gt;We had this brilliant idea that we could ask people who wanted to join to buy 3 bags of beans to be in the club. This solved our need for beans running out and we had more people to talk to on coffee breaks. Yes, we just made a Ponzi scheme.&lt;/p&gt;
&lt;p&gt;When we, very quickly, realised that Ponzi schemes don’t work, we set about making a fair system to decide who should be the next person to buy coffee beans. We hooked up a Raspberry Pi and RFID sensor to self-report cups drank to a Google sheet and a Google form to register purchases.&lt;/p&gt;
&lt;p&gt;The machine now knew who had bought the least amount of beans for the number of cups they had drank and sent them a nice email to tell them about it.&lt;/p&gt;
&lt;/details&gt;
&lt;p&gt;Last year we rebuilt the system to be a native piece of software that ran entirely on-device and this included adding a nice 7&amp;quot; touchscreen. One of my goals was to see how feasible it was to build a web app without any frameworks. (Cue the rest of this post)&lt;/p&gt;
&lt;figure class=&quot;figureImage&quot;&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://blog.r0b.io/img/KCiAkqRT47-1200.webp 1200w&quot; /&gt;&lt;img alt=&quot;The home screen of the coffee club, above our bean-to-cup machine.&quot; loading=&quot;lazy&quot; src=&quot;https://blog.r0b.io/img/KCiAkqRT47-1200.jpeg&quot; width=&quot;1200&quot; height=&quot;900&quot; /&gt;&lt;/picture&gt;&lt;figcaption&gt;The home screen of the coffee club, above our bean-to-cup machine.&lt;/figcaption&gt;&lt;/figure&gt;
&lt;h2 id=&quot;how-it-works&quot; tabindex=&quot;-1&quot;&gt;How it works&lt;/h2&gt;
&lt;p&gt;The app is made up of a few pages:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The &lt;strong&gt;home&lt;/strong&gt; page which shows general stats, daily messages and collective usage information.&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;scan&lt;/strong&gt; page which registers a cup drank and shows personal stats, navigated to when you scan your card.&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;product&lt;/strong&gt; page to log a coffee purchase and set the weight if it is a new product, triggered by scanning a barcode when on the &lt;strong&gt;scan&lt;/strong&gt; page.&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;register&lt;/strong&gt; page which is shown when a new RFID card is scanned. You can associate with a person on our &lt;a href=&quot;https://openlab.ncl.ac.uk/people/&quot;&gt;group website&lt;/a&gt; or be anonymous.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;The register somewhat-cheekily pulls down the &lt;a href=&quot;https://openlab.ncl.ac.uk/search.json&quot;&gt;search.json&lt;/a&gt; from our group website to populate it&#39;s list, we need to make this a little more formal.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Each page is a HTML document, CSS stylesheet and a bit of JavaScript. The structure of the page is all in the HTML file and dynamic elements are put in &lt;code&gt;&amp;lt;template&amp;gt;&lt;/code&gt; tags to be populated when needed.&lt;/p&gt;
&lt;h2 id=&quot;page-based-navigation&quot; tabindex=&quot;-1&quot;&gt;Page-based navigation&lt;/h2&gt;
&lt;p&gt;One of the things that SPAs (Single Page Applications) break is natural browser navigation. With coffee club it had the static html files and some query parameters, so normal page navigation could occur.&lt;/p&gt;
&lt;p&gt;The system defaults to the home page (&lt;code&gt;index.html&lt;/code&gt;) then when someone scans an RFID card it goes to &lt;code&gt;/scan.html?card=abcdef123456&lt;/code&gt;, all good. You can’t do any fancy url-parameters like &lt;code&gt;/scan/abcdef123456/&lt;/code&gt; which I was hung up on for a while but it doesn’t really matter does it, no one sees this! The client side javascript can pick up the &lt;code&gt;URLSearchParams&lt;/code&gt; easily by creating a URL from &lt;code&gt;location.href&lt;/code&gt;.&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; url &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;URL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;location&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;href&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;data-binding&quot; tabindex=&quot;-1&quot;&gt;Data binding&lt;/h2&gt;
&lt;p&gt;The first thing I missed was data-binding which &lt;em&gt;SPA&lt;/em&gt; frameworks rely heavily upon. I toyed with lightweight ones like &lt;a href=&quot;https://alpinejs.dev/&quot;&gt;alpine&lt;/a&gt; or &lt;a href=&quot;https://github.com/vuejs/petite-vue&quot;&gt;petite-vue&lt;/a&gt; but held firm and ended up making my own minimalistic version. It looks like this:&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; state &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;reactive&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;profile&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;token function&quot;&gt;bind&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;state&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;.totalCups&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;state&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; elem&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  elem&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;innerText &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; state&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;profile&lt;span class=&quot;token operator&quot;&gt;?.&lt;/span&gt;cups &lt;span class=&quot;token operator&quot;&gt;??&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;~&quot;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Whenever &lt;code&gt;state.profile&lt;/code&gt; changes it will call the callback with the latest state and the element that matches the query selector. It also calls the callback straight away to render the initial state.&lt;/p&gt;
&lt;p&gt;Internally it uses &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy&quot;&gt;Proxy&lt;/a&gt; objects, and it was quite a fun exercise to learn how they work and play around with them.&lt;/p&gt;
&lt;h2 id=&quot;kiosk-display-affordances&quot; tabindex=&quot;-1&quot;&gt;Kiosk display affordances&lt;/h2&gt;
&lt;p&gt;Something that was different to design for was that this device was to run in a kiosk-like mode and I put in extra effort to think through the UI components we used so that the app didn’t get stuck anywhere.&lt;/p&gt;
&lt;p&gt;The main idea was to make sure it always got back to the home page. This meant each page needed a countdown to either perform an action or cancel. I ended up with buttons with countdowns on them to try to show their relation. I say try because from watching people use the app, they still tend to press the button even when the countdown indicates that it will happen anyway.&lt;/p&gt;
&lt;h2 id=&quot;html-templates&quot; tabindex=&quot;-1&quot;&gt;HTML templates&lt;/h2&gt;
&lt;p&gt;In HTML you can have &lt;code&gt;&amp;lt;template&amp;gt;&lt;/code&gt; tags which are not shown to viewers and let you start to do things programatically. I used these in the app for dynamic bits of the page which I wanted to control when they were shown without navigating to a new page. For example if you&#39;d scanned your card but didn&#39;t want to register a cup so pressed &amp;quot;no coffee&amp;quot;, it would stay on the same page but swap out the main element with something different. I created a wrapper to grab a template and fill it in:&lt;/p&gt;
&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;template&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;noCoffee&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;span&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;…&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;span&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;button&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;done&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;Finish&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;button&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;template&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// Create a template hydrator&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; noCoffee &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;hydrateTemplate&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;#noCoffee&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;elem&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; name &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; elem&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;querySelector&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;#name&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  name&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;innerText &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;Geoff Testington&#39;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; done &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; elem&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;querySelector&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;#done&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  done&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;click&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;/* ... */&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// Add it to the DOM&lt;/span&gt;
rootElement&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;appendChild&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;noCoffee&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;These templates worked pretty well, definitely a bit more verbose than their Vue or React counterparts but pretty serviceable. Being more-specific to implement did make me re-think what needed to be dynamic and what could be built into the page itself which was interesting. In retrospect this is a bit of a precursor to &amp;quot;islands architecture&amp;quot; that tools like &lt;a href=&quot;https://github.com/11ty/is-land&quot;&gt;11ty/is-land&lt;/a&gt; and &lt;a href=&quot;https://fresh.deno.dev/docs/concepts/islands&quot;&gt;fresh&lt;/a&gt; are proposing.&lt;/p&gt;
&lt;h2 id=&quot;the-only-dependency&quot; tabindex=&quot;-1&quot;&gt;The only dependency&lt;/h2&gt;
&lt;p&gt;Eventually I did concede and add a client-side dependency. It was for charts because they are just a hard problem and there has been lots of good effort gone into solving that already. Until now everything could be made to work with the browser but charts are a different beast.&lt;/p&gt;
&lt;p&gt;I went for &lt;a href=&quot;https://www.chartjs.org/&quot;&gt;chart.js&lt;/a&gt; to show some nice usage graphs on the home screen, it’s nice to see the average cups for each day of the week compared to the current week. Why are Tuesdays our most popular day?&lt;/p&gt;
&lt;h2 id=&quot;event-driven-issues&quot; tabindex=&quot;-1&quot;&gt;Event-driven issues&lt;/h2&gt;
&lt;p&gt;Another oddity about this interface is that it is not driven by cursor/pointer interactions. As mentioned above it’s designed to work with countdowns so you don’t have to touch the screen very often. The other part is the RFID reader and barcode scanner (used to register purchases of beans).&lt;/p&gt;
&lt;p&gt;Because it is not a Single Page App, it’s a bit harder to handle those events and difficult to handle across actual page navigations. The sensors work by emulating a keyboard under-the-hood so you need to keep state to know what’s been typed and that can get lost if the page navigates away.&lt;/p&gt;
&lt;p&gt;For the current version the app has a common &amp;quot;Scanner&amp;quot; class that can be easily created to listen and process the raw events then emit specific events like “card scanned” or “barcode detected”. I think this could be better handled with &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/SharedWorker&quot;&gt;SharedWorkers&lt;/a&gt; in the future but we haven’t got around to trying them yet.&lt;/p&gt;
&lt;h2 id=&quot;honourable-mentions&quot; tabindex=&quot;-1&quot;&gt;Honourable mentions&lt;/h2&gt;
&lt;p&gt;A few things in modern web dev that make things easier:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API&quot;&gt;fetch&lt;/a&gt; API&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/URL/URL&quot;&gt;URL constructors&lt;/a&gt; (especially that second &lt;code&gt;base&lt;/code&gt; parameter)&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams&quot;&gt;URLSearchParams&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules&quot;&gt;ESM&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat&quot;&gt;Intl.NumberFormatter&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And a mention to &lt;a href=&quot;https://danjackson.dev/&quot;&gt;Dan Jackson&lt;/a&gt;, who&#39;s the main brain behind this system, designed our human-friendly csv-based filesystem and made it easy to talk to the various input devices. Also &lt;a href=&quot;https://every-layout.dev/&quot;&gt;EveryLayout&lt;/a&gt; for taking layout out of the equation.&lt;/p&gt;
&lt;h2 id=&quot;conclusions&quot; tabindex=&quot;-1&quot;&gt;Conclusions&lt;/h2&gt;
&lt;p&gt;I think it’s safe to say you can make a web app without an &lt;em&gt;SPA&lt;/em&gt; framework like React or Vue. You definitely loose a bit of developer experience, but all the tools are there for you to try it yourself. The main loss I found was data-binding and I don’t think most people would be interested in creating their own on top of Proxy objects.&lt;/p&gt;
&lt;p&gt;This specific app is a bit of an outlier in the whole &lt;em&gt;SPA&lt;/em&gt; debate as it’s running on very precise hardware and software. In this case it might have actually made sense to use something like Vue.js to speed up development and avoid those event hang-ups. We have considered this a few times! But in writing this I think it has been useful to explore the space and question the norm.&lt;/p&gt;
&lt;p&gt;Thanks for reading, please &lt;a href=&quot;https://social.lol/@r0b&quot;&gt;let me know&lt;/a&gt; what you think!&lt;/p&gt;
</content
    >
  </entry> 
  <entry>
    <title>Creating drag interactions with setPointerCapture in JavaScript</title>
    <link
      href="https://blog.r0b.io/post/creating-drag-interactions-with-set-pointer-capture-in-java-script/"
      rel="alternate"
      type="text/html"
      title="Creating drag interactions with setPointerCapture in JavaScript"
    />
    <updated>2023-01-07T00:00:00Z</updated>
    <id>https://blog.r0b.io/post/creating-drag-interactions-with-set-pointer-capture-in-java-script/</id>
    <content
      type="html"
      >&lt;p&gt;Pointer events are the web&#39;s answer to mouse vs touch interactions, so you can add one event and it will be triggered consistently whether you are touching on mobile or clicking on a computer. I recently played around with some fun cards on my personal website.&lt;/p&gt;
&lt;figure class=&quot;figureImage&quot;&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://blog.r0b.io/img/yjY1gikBTL-1200.webp 1200w&quot; /&gt;&lt;img alt=&quot;The project cards on my website. Click to flip over the card or click &amp;amp; drag to move them about.&quot; loading=&quot;lazy&quot; src=&quot;https://blog.r0b.io/img/yjY1gikBTL-1200.png&quot; width=&quot;1200&quot; height=&quot;850&quot; /&gt;&lt;/picture&gt;&lt;figcaption&gt;The project cards on my website. Click to flip over the card or click &amp; drag to move them about.&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;The main issue with implementing a drag like this is that as soon as the cursor exits the target element, it stops receiving the events it needs.
This is where &lt;code&gt;setPointerCapture&lt;/code&gt; comes in.&lt;/p&gt;
&lt;h2 id=&quot;how-it-works&quot; tabindex=&quot;-1&quot;&gt;How it works&lt;/h2&gt;
&lt;p&gt;To get this working you listen for &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Pointer_events&quot;&gt;pointer events&lt;/a&gt; or set the &lt;code&gt;.onpointer*&lt;/code&gt; methods to your callback. I&#39;ve always been one to prefer the event listeners over setting callbacks but recently have come to realise that if you only have one exclusive interaction with an element, it makes sense to use the methods. It&#39;s easier to handle (pun intended) and it&#39;s simpler to manage in more dynamic environments (you can&#39;t re-add the same listener by accident).&lt;/p&gt;
&lt;p&gt;So, to create a drag effect, let&#39;s add some HTML to manipulate:&lt;/p&gt;
&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;div&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;projectBoard&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;article&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;projectCard&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;article&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And give it a rough style:&lt;/p&gt;
&lt;pre class=&quot;language-css&quot;&gt;&lt;code class=&quot;language-css&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;/* Create a space to move the card about in */&lt;/span&gt;
&lt;span class=&quot;token selector&quot;&gt;.projectBoard&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;min-height&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 960px&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;min-width&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 1048px&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;position&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; relative&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;/* Give the card a size and something to see */&lt;/span&gt;
&lt;span class=&quot;token selector&quot;&gt;.projectCard&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;position&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; absolute&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;width&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 320px&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;height&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 180px&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token property&quot;&gt;background&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; royalblue&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;border-radius&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 1em&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now let&#39;s add the drag with JavaScript:&lt;/p&gt;
&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// Start by iterating through all the cards that we want to add the interaction too&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; card &lt;span class=&quot;token keyword&quot;&gt;of&lt;/span&gt; document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;querySelectorAll&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;.projectCard&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// Keep track of where interactions started, remember that this is scoped for each card&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; startPosition &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;

  &lt;span class=&quot;token comment&quot;&gt;// Listen for &quot;pointerdown&quot; events, when a touch/click starts on that element&lt;/span&gt;
  card&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function-variable function&quot;&gt;onpointerdown&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;event&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// Ignore this event if it is not a left-click or touch&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// or if clicking an anchor tag on the back of the card&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// https://developer.mozilla.org/en-US/docs/Web/API/Pointer_events#determining_button_states&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;button &lt;span class=&quot;token operator&quot;&gt;!==&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;target &lt;span class=&quot;token keyword&quot;&gt;instanceof&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;HTMLAnchorElement&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;// This stops normal browser&#39; drag behaviour&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// Note: It can cause trouble if you have an anchor on the card&lt;/span&gt;
    event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;preventDefault&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;// This is the key bit, it binds this `PointerEvent` to the element until you tell it to stop&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// So if the pointer leaves the element, it still receives the relevant events.&lt;/span&gt;
    card&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setPointerCapture&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;pointerId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;// Lets remember where the card started to track if the pointer moved or not&lt;/span&gt;
    startPosition &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;screenX&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;screenY&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;// Only add the &quot;move&quot; event handler once the interaction has started&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// Using the &quot;on&quot; method means we can easily remove it later&lt;/span&gt;
    card&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function-variable function&quot;&gt;onpointermove&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;event&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token comment&quot;&gt;// In this setup the cards are positioned absolutely in a container&lt;/span&gt;
      &lt;span class=&quot;token comment&quot;&gt;// so setting the top/left will move them about in their container&lt;/span&gt;
      &lt;span class=&quot;token comment&quot;&gt;// It adds the &quot;movement&quot; from the event to it&#39;s existing offset in that direction&lt;/span&gt;
      card&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;style&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;left &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;card&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;offsetLeft &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;movementX&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;px&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;
      card&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;style&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;top &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;card&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;offsetTop &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;movementY&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;px&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token comment&quot;&gt;// Listen for &quot;pointerup&quot; events, when a touch/click that started on the element and was captured ended&lt;/span&gt;
  card&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function-variable function&quot;&gt;onpointerup&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;event&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; dx &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;screenX &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; startPosition&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; dy &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;screenY &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; startPosition&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;// If the pointer didn&#39;t move at all since it started, treat it as a click&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// I used css elsewhere to do a flip animation based on the `data-side=&quot;front&quot;` vs back property.&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// Ideally it would be a vector distance algorithm (A^2 + B^2 = C^2 stuff)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;dx &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; dy &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      card&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;dataset&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;side &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; card&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;dataset&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;side &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;front&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;back&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;front&#39;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;// Remove the &quot;move&quot; event now&lt;/span&gt;
    card&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;onpointermove &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;// Important! remember to release the pointer from the element&lt;/span&gt;
    card&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;releasePointerCapture&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;pointerId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Thats an extra verbose implementation, not too many steps!?&lt;/p&gt;
&lt;p&gt;First we add the &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Element/pointerdown_event&quot;&gt;onpointerdown&lt;/a&gt; callback to listen for pointer events starting. In that callback it captures the pointer which binds all events to that element, even if they aren&#39;t pointing to it. Then it adds a &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Element/pointermove_event&quot;&gt;onpointermove&lt;/a&gt; callback to update the relative position of the card when the pointer moves.&lt;/p&gt;
&lt;p&gt;Lastly, it sets the &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Element/pointerup_event&quot;&gt;onpointerup&lt;/a&gt; callback to handle when the pointer event finishes. It releases the captured pointer and removes the &lt;code&gt;onpointermove&lt;/code&gt; callback. If the pointer didn&#39;t move since it started, it treats it as a click and flips over the card.&lt;/p&gt;
&lt;p&gt;You can find the source for my &lt;a href=&quot;https://github.com/robb-j/r0b-home/blob/134abc134b6a768056a89bedb4b229b522060b42/src/js/app.ts#L144-L194&quot;&gt;homepage&#39;s version here&lt;/a&gt;,
if you want to dig a little deeper.&lt;/p&gt;
&lt;h2 id=&quot;bonus%3A-card-flip-animation&quot; tabindex=&quot;-1&quot;&gt;Bonus: card flip animation&lt;/h2&gt;
&lt;p&gt;I adapted this &lt;a href=&quot;https://www.w3schools.com/howto/howto_css_flip_card.asp&quot;&gt;w3schools tutorial&lt;/a&gt; to create a nice flip effect for my cards. Instead of on-hover though, I wanted to be able to toggle the cards with a click. To store that state I turned to HTML data attributes which can be used in CSS styling. You could also use a HTML aria attribute, as long as the meaning matches up with the interaction you&#39;re creating.&lt;/p&gt;
&lt;figure class=&quot;figureVideo&quot;&gt;&lt;video controls=&quot;&quot; loop=&quot;&quot; width=&quot;640&quot; height=&quot;360&quot;&gt;&lt;source src=&quot;https://blog.r0b.io/video/r0b-card-flip.mov&quot; type=&quot;video/mp4&quot; /&gt;&lt;/video&gt;&lt;figcaption&gt;The card flip animation like a ... card flip. Very skeuomorphic&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;The code above will toggle the &lt;code&gt;data-side&lt;/code&gt; attribute on the card between &lt;code&gt;front&lt;/code&gt; or &lt;code&gt;back&lt;/code&gt; when you click on it. It needs more markup that the above example:&lt;/p&gt;
&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;&amp;lt;!-- The card is now a wrapper that sets up the 3d position --&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;article&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;projectCard&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;data-side&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;front&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;&amp;lt;!-- There is an inner element that is responsible for animating rotation on the Y axis --&gt;&lt;/span&gt;
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;div&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;projectCard-inner&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;&amp;lt;!-- The front-side of the card, an image in this case --&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;div&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;projectCard-front&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
      &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;img&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;src&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;https://example.com/image.png&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;width&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;320&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;height&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;180&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;&amp;lt;!-- The back-side of the card, more infromation about the project --&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;div&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;projectCard-back&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
      &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;p&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
        My fancy project — it was really cool and goes over at least one line
      &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;p&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
      &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;p&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;a&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;href&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;https://example.com&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;More info&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;a&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;p&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;article&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;!-- Then you can style that something like this: --&gt;
&lt;p&gt;This effect needs a few elements to take place, the wrapper (&lt;code&gt;projectCard&lt;/code&gt;), an &lt;code&gt;inner&lt;/code&gt; element and the &lt;code&gt;front&lt;/code&gt; and &lt;code&gt;back&lt;/code&gt; elements.&lt;/p&gt;
&lt;p&gt;Let&#39;s style them:&lt;/p&gt;
&lt;pre class=&quot;language-css&quot;&gt;&lt;code class=&quot;language-css&quot;&gt;&lt;span class=&quot;token selector&quot;&gt;.projectCard&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;position&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; absolute&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;width&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 320px&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;height&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 180px&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;background-color&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; transparent&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;perspective&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 1000px&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;box-shadow&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 0 3px 1px &lt;span class=&quot;token function&quot;&gt;rgba&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;0&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; 0&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; 0&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; 0.05&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The wrapper element sets the &lt;code&gt;perspective&lt;/code&gt; attribute which controls how far the object is away from the user when using 3d transforms. A smaller number puts it closer to the user, &lt;code&gt;1000px&lt;/code&gt; looked nice for these size cards. If the number is less than the width of the card, then the animation will clip through the &amp;quot;camera&amp;quot; (where the viewer is) and look glitchy.&lt;/p&gt;
&lt;pre class=&quot;language-css&quot;&gt;&lt;code class=&quot;language-css&quot;&gt;&lt;span class=&quot;token selector&quot;&gt;.projectCard-inner&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;position&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; relative&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;width&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 100%&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;height&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 100%&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;transition&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; transform 500ms ease&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;transform-style&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; preserve-3d&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;text-align&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; center&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The inner element sets up the animation by setting &lt;code&gt;transition&lt;/code&gt; and &lt;code&gt;transform-style&lt;/code&gt;. The transition will animate the transform property on the element whenever it changes.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;transform-style&lt;/code&gt; statement makes child elements exist in their own 3d space which lets the back be rotated 180 degrees away from the front in the 3d world.&lt;/p&gt;
&lt;pre class=&quot;language-css&quot;&gt;&lt;code class=&quot;language-css&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;/* Show the card&#39;s backside when that data property is set */&lt;/span&gt;
&lt;span class=&quot;token selector&quot;&gt;.projectCard[data-side=&#39;back&#39;] .projectCard-inner&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;transform&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;rotateY&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;180deg&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When the inner element has the attribute &lt;code&gt;data-side=&amp;quot;back&amp;quot;&lt;/code&gt; it rotates itself (and it&#39;s children) 180 degrees on the y axis. These transforms composite, so when the parent is rotated 180 degrees, the back is rotated 180 degrees + 180 degrees which is a full rotation so it is facing forwards again.&lt;/p&gt;
&lt;p&gt;Because there is also a transition setup, it animates! These dataset attributes can easily be set from JavaScript using the &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/dataset&quot;&gt;dataset&lt;/a&gt; field, shown earlier.&lt;/p&gt;
&lt;pre class=&quot;language-css&quot;&gt;&lt;code class=&quot;language-css&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;/* Style the front back back of the card the same way */&lt;/span&gt;
&lt;span class=&quot;token selector&quot;&gt;.projectCard-front,
.projectCard-back&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;aspect-ratio&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 16 / 9&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;position&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; absolute&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;width&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 100%&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;height&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 100%&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;backface-visibility&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; hidden&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;border-radius&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 6px&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;overflow&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; hidden&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The front &amp;amp; back elements share styles so they are exactly the same and don&#39;t get misaligned.&lt;/p&gt;
&lt;pre class=&quot;language-css&quot;&gt;&lt;code class=&quot;language-css&quot;&gt;&lt;span class=&quot;token selector&quot;&gt;.projectCard-back&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;/* Flip the back of the card (relatively to the whole card) */&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;transform&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;rotateY&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;180deg&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token comment&quot;&gt;/* Style the text and surround with a border */&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;background-color&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; white&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;color&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; black&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;border&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 5px solid black&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token comment&quot;&gt;/* Center child elements */&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;display&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; flex&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;flex-direction&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; column&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;justify-content&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; center&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;align-items&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; center&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;gap&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; rem&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The back element has the final piece of the puzzle, it is rotated away from the visitor. Because both front and back have &lt;code&gt;backface-visibility: hidden;&lt;/code&gt; set, you cannot see the reverse of the elements and they don&#39;t bleed through each other.&lt;/p&gt;
&lt;h2 id=&quot;wrapping-up&quot; tabindex=&quot;-1&quot;&gt;Wrapping up&lt;/h2&gt;
&lt;p&gt;That&#39;s my card dragging and a cheeky flip animation too, you can see at the bottom of &lt;a href=&quot;https://r0b.io/&quot;&gt;my homepage&lt;/a&gt; and I&#39;ve also attached a &lt;a href=&quot;https://blog.r0b.io/hacks/card&quot;&gt;demo&lt;/a&gt; into the blog too!&lt;/p&gt;
&lt;p&gt;If you have any questions or feedback, reach out to me on &lt;a href=&quot;https://social.lol/@r0b&quot;&gt;Mastodon&lt;/a&gt;.&lt;/p&gt;
</content
    >
  </entry> 
  <entry>
    <title>Deploying ESP32 with SPIFFS using github actions</title>
    <link
      href="https://blog.r0b.io/post/deploying-esp32-with-spiffs-using-github-actions/"
      rel="alternate"
      type="text/html"
      title="Deploying ESP32 with SPIFFS using github actions"
    />
    <updated>2022-11-25T00:00:00Z</updated>
    <id>https://blog.r0b.io/post/deploying-esp32-with-spiffs-using-github-actions/</id>
    <content
      type="html"
      >&lt;p&gt;Generating and flashing firmware onto an ESP32 can be a bit difficult.
I&#39;ve been using one for a new project and here is what I&#39;ve learned.
This post is in two parts, the pipeline I ended up creating
and some key things I&#39;ve learnt that I didn&#39;t think was obvious.&lt;/p&gt;
&lt;h2 id=&quot;background&quot; tabindex=&quot;-1&quot;&gt;Background&lt;/h2&gt;
&lt;p&gt;This is my first time developing firmware for a chip like the ESP32 so my approach
may be a little different from firmware veterans.
My goal is to automate as much of the process as possible
and have the entire pipeline codified for reproducible builds that run automatically.&lt;/p&gt;
&lt;p&gt;I&#39;ve not got on well with &lt;a href=&quot;https://www.arduino.cc/en/software&quot;&gt;Arduino IDE&lt;/a&gt;
and the recent v2 seems to have broken some plugins too.
I also didn&#39;t like the global nature of library dependencies and lack of a manifest to define them.
I opted instead for a command line based approach that could run on my computer or on GitHub Actions.&lt;/p&gt;
&lt;h2 id=&quot;the-pipeline&quot; tabindex=&quot;-1&quot;&gt;The pipeline&lt;/h2&gt;
&lt;p&gt;The pipeline I eventually setup looked like this:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;A new version is pushed to GitHub, like &lt;code&gt;v1.2.3&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;It compiles the Arduino firmware&lt;/li&gt;
&lt;li&gt;Generate static files that are bundled into a SPIFFS partition&lt;/li&gt;
&lt;li&gt;Create a static website that can be used to flash the device or download assets&lt;/li&gt;
&lt;li&gt;Deploy the static website to GitHub pages&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;1. Create a release&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;A release is triggered using the &lt;code&gt;npm version&lt;/code&gt; command locally,
which bumps the version in &lt;code&gt;package.json&lt;/code&gt;, commits the change as &lt;code&gt;x.y.x&lt;/code&gt;
and tags the commit as &lt;code&gt;vx.y.z&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2. Setup GitHub actions&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;When a tag with a &lt;code&gt;v&lt;/code&gt; prefix is pushed to GitHub it runs an action to build and publish the release.
It first sets up the environment, with a recursive git checkout.
thirdy-party libraries are added as git submodules so they can be kept track of and upgraded dependably.
It then installs and sets up &lt;code&gt;arduino-cli&lt;/code&gt; and &lt;code&gt;node.js&lt;/code&gt; and installs NPM dependencies.
&lt;code&gt;arduino-cli&lt;/code&gt; is then used to install published libraries and add the ESP32 package.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;3. Generate the firmware&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The ESP32 I&#39;m using is a captive portal that serves a web app, so next it builds the web-app using &lt;a href=&quot;https://parceljs.org/&quot;&gt;parcel&lt;/a&gt;.
This packages everything up nicely, minifies the code and makes it compatible with older browsers.
Then it uses ESP32&#39;s &lt;code&gt;mkspiff&lt;/code&gt; tool to wrap all those files up into a &lt;code&gt;spiffs.bin&lt;/code&gt; partition.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;4. Generate flashing app&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Next it creates the flash tool which is a website that uses &lt;a href=&quot;https://esphome.github.io/esp-web-tools/&quot;&gt;esp-web-tools&lt;/a&gt;
to create an interface to flash the ESP32 from a browser. Sadly only Chrome is supported at this time,
it needs the &lt;a href=&quot;https://caniuse.com/web-serial&quot;&gt;Web Serial API&lt;/a&gt;.
This is a little html file which loads the tool and has the firmware binaries adjacent to it for the tool to load them in.
It&#39;s all joined together with a manifest file, which specifies the firmware partitions and where to put each binary.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;5. Deploy flash tool&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;With the flash tool built, a GitHub action takes those html, js, css, json and binaries and deploys them to GitHub pages,
so you can access the tool or directly download the firmware and &lt;code&gt;manifest.json&lt;/code&gt;.&lt;/p&gt;
&lt;details&gt;
&lt;summary&gt;Example GitHub workflow&lt;/summary&gt;
&lt;p&gt;&lt;strong&gt;workflow.yml&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Release

&lt;span class=&quot;token key atrule&quot;&gt;on&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;push&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;tags&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;v*&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;

&lt;span class=&quot;token key atrule&quot;&gt;jobs&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;build&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Compile web flash
    &lt;span class=&quot;token key atrule&quot;&gt;runs-on&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; ubuntu&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;latest

    &lt;span class=&quot;token key atrule&quot;&gt;permissions&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token key atrule&quot;&gt;pages&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; write
      &lt;span class=&quot;token key atrule&quot;&gt;id-token&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; write
      &lt;span class=&quot;token key atrule&quot;&gt;contents&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; read

    &lt;span class=&quot;token key atrule&quot;&gt;environment&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; github&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;pages
      &lt;span class=&quot;token key atrule&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;${{ steps.deployment.outputs.page_url }}&#39;&lt;/span&gt;

    &lt;span class=&quot;token key atrule&quot;&gt;steps&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Checkout
        &lt;span class=&quot;token key atrule&quot;&gt;uses&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; actions/checkout@v3
        &lt;span class=&quot;token key atrule&quot;&gt;with&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;token key atrule&quot;&gt;submodules&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; recursive

      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;uses&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; actions/setup&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;node@v2
        &lt;span class=&quot;token key atrule&quot;&gt;with&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;token key atrule&quot;&gt;node-version&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;16&#39;&lt;/span&gt;

      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Install Arduino CLI
        &lt;span class=&quot;token key atrule&quot;&gt;uses&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; arduino/setup&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;arduino&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;cli@v1

      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Setup Pages
        &lt;span class=&quot;token key atrule&quot;&gt;uses&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; actions/configure&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;pages@v2

      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Install dependencies
        &lt;span class=&quot;token key atrule&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; npm install &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;no&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;audit

      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Build the app
        &lt;span class=&quot;token key atrule&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; npm run &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;w app build

      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Setup arduino
        &lt;span class=&quot;token key atrule&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; ./bin/esp32.sh

      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Compile firmware
        &lt;span class=&quot;token key atrule&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; ./bin/esp32.sh

      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Build the flash tool
        &lt;span class=&quot;token key atrule&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; npm run &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;w web_flash build
        &lt;span class=&quot;token key atrule&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;token key atrule&quot;&gt;NODE_ENV&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; production

      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Upload pages artifacts
        &lt;span class=&quot;token key atrule&quot;&gt;uses&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; actions/upload&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;pages&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;artifact@v1
        &lt;span class=&quot;token key atrule&quot;&gt;with&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;token key atrule&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; web_flash/dist

      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Deploy to GitHub Pages
        &lt;span class=&quot;token key atrule&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; deployment
        &lt;span class=&quot;token key atrule&quot;&gt;uses&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; actions/deploy&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;pages@v1&lt;/code&gt;&lt;/pre&gt;
&lt;/details&gt;
&lt;h2 id=&quot;hurdles&quot; tabindex=&quot;-1&quot;&gt;Hurdles&lt;/h2&gt;
&lt;h3 id=&quot;what-is-a-sketch&quot; tabindex=&quot;-1&quot;&gt;What is a sketch&lt;/h3&gt;
&lt;p&gt;An Arduino sketch is a folder with a same-named file inside it with a &lt;code&gt;.ino&lt;/code&gt; extension.
So for &lt;code&gt;MyProject/MyProject.ino&lt;/code&gt;, the sketch is the folder &lt;code&gt;MyProject&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The Arduino docs also reference a &amp;quot;sketchbook&amp;quot; which I think is just a folder that has multiple Arduino sketches in it,
but it is a different thing.&lt;/p&gt;
&lt;h3 id=&quot;ide-setup&quot; tabindex=&quot;-1&quot;&gt;IDE setup&lt;/h3&gt;
&lt;p&gt;My reccomendation for an IDE is &lt;a href=&quot;https://code.visualstudio.com/&quot;&gt;Visual Studio Code&lt;/a&gt; with these extensions:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://marketplace.visualstudio.com/items?itemName=ms-vscode.cpptools&quot;&gt;C++ tools&lt;/a&gt; to get intellisense for Arduino code&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://marketplace.visualstudio.com/items?itemName=redhat.vscode-yaml&quot;&gt;YAML&lt;/a&gt; to validate GitHub workflow or other YAML files&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://marketplace.visualstudio.com/items?itemName=EditorConfig.EditorConfig&quot;&gt;EditorConfig&lt;/a&gt; to make sure all your indentations match up&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode&quot;&gt;Prettier&lt;/a&gt; to auto-format html/css/ts/js/yaml/md files&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;To get &lt;em&gt;C++ tools&lt;/em&gt; working with Arduino, create a &lt;code&gt;.vscode/c_cpp_properties.json&lt;/code&gt;
and modify the default configuration to include:&lt;/p&gt;
&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;configurations&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      ...
      &lt;span class=&quot;token property&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;ESP32&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token property&quot;&gt;&quot;includePath&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
        &lt;span class=&quot;token string&quot;&gt;&quot;${workspaceFolder}/arduino/**&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token string&quot;&gt;&quot;~/Library/Arduino15/packages/esp32/hardware/esp32/**&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token string&quot;&gt;&quot;~/Documents/Arduino/libraries/**&quot;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token property&quot;&gt;&quot;defines&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;ESP32=1&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      ...
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This will set the required hash-defines for ESP32 development
and tell the extension where to load the Arduino libraries.
It&#39;s not perfect and not all imports works but it gets most of the way.&lt;/p&gt;
&lt;h3 id=&quot;setting-and-configuring-arduino-cli&quot; tabindex=&quot;-1&quot;&gt;Setting and configuring arduino-cli&lt;/h3&gt;
&lt;p&gt;The first step in automation was getting the build done from the command line.
&lt;a href=&quot;https://arduino.github.io/arduino-cli/&quot;&gt;arduino-cli&lt;/a&gt; can be quickly installed
and you can create an &lt;code&gt;ardunio-cli.yaml&lt;/code&gt; file which will automatically pass parameters for you.
This file has to be in the same directory as you use the cli
and you can use &lt;code&gt;arduino-cli config dump&lt;/code&gt; to quickly see what is configured.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;ardunio-cli.yaml&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token key atrule&quot;&gt;board_manager&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;additional_urls&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; https&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;//dl.espressif.com/dl/package_esp32_index.json&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The configuration is useful for ESP32 development because you can set &lt;code&gt;board_manager.additional_urls&lt;/code&gt;
which is needed to install the ESP32 packages.
You can also install the libraries you need to.
I ended up having &lt;code&gt;bin/setup.sh&lt;/code&gt; to do this one-time config so it can easily be ran locally and on GitHub actions.
It sort-of serves as a definition of the dependencies of the Arduino code.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;bin/setup.sh&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token shebang important&quot;&gt;#!/usr/bin/env sh&lt;/span&gt;

arduino-cli core update-index
arduino-cli core &lt;span class=&quot;token function&quot;&gt;install&lt;/span&gt; esp32:esp32@1.0.6
arduino-cli lib &lt;span class=&quot;token function&quot;&gt;install&lt;/span&gt; ArduinoJson@6.19.4 AnotherPackage@x.y.x&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;There is a &lt;code&gt;sketch.yaml&lt;/code&gt; which lets you specify libraries but it didn&#39;t work with my custom libraries setup.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;custom-libraries-as-submodules&quot; tabindex=&quot;-1&quot;&gt;Custom libraries as submodules&lt;/h3&gt;
&lt;p&gt;The ESP32 library I was using has some third-party dependencies so I wanted to codify those too
I didn&#39;t want them installed globally, I wanted them dependably in one place at a specific version.&lt;/p&gt;
&lt;p&gt;The best way I found to codify these dependencies was to add them as git submodules to the project,
so a specific version or commit can be pinned within the parent git repository.
It does add an extra step to &lt;code&gt;git submodule init&lt;/code&gt; during setup and &lt;code&gt;git submodule update&lt;/code&gt; needs to be run every so often.&lt;/p&gt;
&lt;p&gt;I hoped Arduino IDE would automatically load them if they were in &lt;code&gt;lib&lt;/code&gt;, &lt;code&gt;libraries&lt;/code&gt; or the &lt;code&gt;src&lt;/code&gt; folder
which it claims to check and compile, but I couldn&#39;t get this working.
It needed an extra argument to the &lt;code&gt;arduino-cli compile&lt;/code&gt; command instead.
You can pass a &lt;code&gt;--libraries&lt;/code&gt; option which is a custom folder of libraries,
which worked out well because all the submodules were in a folder together.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;3/11/22 I&#39;ve not found a way to get this working with the Arduino IDE yet...&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;arduino-partitions&quot; tabindex=&quot;-1&quot;&gt;Arduino partitions&lt;/h3&gt;
&lt;p&gt;I had to learn how flashing an ESP worked.
You have a set amount of flash storage on the device and several binaries that need placing at specific places in that storage.
These are called partitions and one of the partitions tells the device where the other partitions are.&lt;/p&gt;
&lt;p&gt;When you compile with the IDE or &lt;code&gt;arduino-cli&lt;/code&gt; it creates two binaries &lt;code&gt;app.bin&lt;/code&gt; and &lt;code&gt;app.partitions.bin&lt;/code&gt;,
the first is your compiled firmware, the second is the partitions you&#39;re going to use.&lt;/p&gt;
&lt;p&gt;You can choose different partitions by passing &lt;code&gt;--build-property build.partitions=scheme&lt;/code&gt;,
where &lt;code&gt;scheme&lt;/code&gt; is the partition scheme you want to use. More on these below.
You might want to have more space for your app, or a larger spiffs section for example.&lt;/p&gt;
&lt;p&gt;The partitions table &lt;strong&gt;must&lt;/strong&gt; be flashed at &lt;a href=&quot;https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-guides/partition-tables.html&quot;&gt;0x8000&lt;/a&gt;
and be 0xC00 bytes long, where the other binaries go depends on which partition scheme you use.
By default &lt;code&gt;arduino-cli&lt;/code&gt; uses the ... &amp;quot;default&amp;quot; schema.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;If you want to use a custom bootloader, it needs to be flashed at &lt;a href=&quot;https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-guides/bootloader.html&quot;&gt;0x1000&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The ESP32 package uses csv files to define the partitions and you can find them in:
&lt;code&gt;$ARDUINO_DIR/packages/esp32/hardware/esp32/1.0.6/tools/partitions/&lt;/code&gt;.
Below is the contents of &lt;code&gt;default.csv&lt;/code&gt; in that directory.&lt;/p&gt;
&lt;pre class=&quot;language-csv&quot;&gt;&lt;code class=&quot;language-csv&quot;&gt;&lt;span class=&quot;token value&quot;&gt;# Name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token value&quot;&gt;   Type&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token value&quot;&gt; SubType&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token value&quot;&gt; Offset&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token value&quot;&gt;  Size&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token value&quot;&gt; Flags&lt;/span&gt;
&lt;span class=&quot;token value&quot;&gt;nvs&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token value&quot;&gt;      data&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token value&quot;&gt; nvs&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token value&quot;&gt;     0x9000&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token value&quot;&gt;  0x5000&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token value&quot;&gt;otadata&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token value&quot;&gt;  data&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token value&quot;&gt; ota&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token value&quot;&gt;     0xe000&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token value&quot;&gt;  0x2000&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token value&quot;&gt;app0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token value&quot;&gt;     app&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token value&quot;&gt;  ota_0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token value&quot;&gt;   0x10000&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token value&quot;&gt; 0x140000&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token value&quot;&gt;app1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token value&quot;&gt;     app&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token value&quot;&gt;  ota_1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token value&quot;&gt;   0x150000&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token value&quot;&gt;0x140000&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token value&quot;&gt;spiffs&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token value&quot;&gt;   data&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token value&quot;&gt; spiffs&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token value&quot;&gt;  0x290000&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token value&quot;&gt;0x170000&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here we can find that our app should be at 0x10000 and be 0x140000 bytes long
and our spiffs should be at 0x290000 and 0x170000 bytes long.
These numbers become very useful as we &lt;a href=&quot;https://blog.r0b.io/post/deploying-esp32-with-spiffs-using-github-actions/#creating-a-spiff-partition&quot;&gt;create SPIFFS&lt;/a&gt;
and &lt;a href=&quot;https://blog.r0b.io/post/deploying-esp32-with-spiffs-using-github-actions/#flashing-from-the-cli&quot;&gt;flash them&lt;/a&gt; below.&lt;/p&gt;
&lt;h3 id=&quot;creating-a-spiffs-partition&quot; tabindex=&quot;-1&quot;&gt;Creating a SPIFFS partition&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/storage/spiffs.html&quot;&gt;SPIFFS&lt;/a&gt;
is an in-memory filesystem the ESP32 can use to store extra files alongside the app.
For my app it&#39;s where web assets (html, js, css and images) go that the ESP serves as a captive portal.&lt;/p&gt;
&lt;p&gt;There is a &lt;a href=&quot;https://github.com/me-no-dev/arduino-esp32fs-plugin&quot;&gt;plugin&lt;/a&gt; for Arduino IDE to generate a SPIFFS partition.
It takes the contents of the &lt;code&gt;data/&lt;/code&gt; folder inside your sketch, creates a partition for you and flashes it to the ESP.
I couldn&#39;t get this working and the manual process didn&#39;t fit with my automation goals.&lt;/p&gt;
&lt;p&gt;To generate one from the CLI you need need to know a few things.
First, where the ESP32 Arduino package gets installed, this is &lt;code&gt;$HOME/Library/Arduino15/packages/esp32&lt;/code&gt; on my mac.
You can use &lt;code&gt;arduino-cli config dump&lt;/code&gt; to see where it looks, the path is under &lt;code&gt;directories.data&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Next you need to find the &lt;code&gt;tools&lt;/code&gt; directory in there and find the &lt;code&gt;mkspiffs&lt;/code&gt; binary.
There are a few hard-coded versions in there so it makes sense to have them as script variables.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;You can get the Arduino directory from the CLI with a little help from &lt;a href=&quot;https://stedolan.github.io/jq/&quot;&gt;jq&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;bin/build.sh&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token shebang important&quot;&gt;#!/usr/bin/env sh&lt;/span&gt;

&lt;span class=&quot;token assign-left variable&quot;&gt;ESP_VERSION&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;${ESP_VERSION&lt;span class=&quot;token operator&quot;&gt;:-&lt;/span&gt;1.0.6}&lt;/span&gt;
&lt;span class=&quot;token assign-left variable&quot;&gt;SPIFFS_VERSION&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;${SPIFFS_VERSION&lt;span class=&quot;token operator&quot;&gt;:-&lt;/span&gt;0.2.3}&lt;/span&gt;
&lt;span class=&quot;token assign-left variable&quot;&gt;ARDUINO_DIR&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;`&lt;/span&gt;arduino-cli config dump &lt;span class=&quot;token parameter variable&quot;&gt;--format&lt;/span&gt; json &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; jq &lt;span class=&quot;token parameter variable&quot;&gt;-r&lt;/span&gt; .directories.data&lt;span class=&quot;token variable&quot;&gt;`&lt;/span&gt;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;# Compile firmware&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;# → My sketch is named `arduino`, not very original&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;# → Surprisingly you need --export-binaries otherwise it does nothing&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;# → My custom libraries are submodules in &quot;arduino/libraries&quot;&lt;/span&gt;
arduino-cli compile &lt;span class=&quot;token parameter variable&quot;&gt;--verbose&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;
  &lt;span class=&quot;token parameter variable&quot;&gt;--fqbn&lt;/span&gt; esp32:esp32:esp32 &lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;
  &lt;span class=&quot;token parameter variable&quot;&gt;--libraries&lt;/span&gt; arduino/libraries &lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;
  --export-binaries &lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;
  arduino

&lt;span class=&quot;token comment&quot;&gt;# Generate the spiff&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;# → &quot;arduino/data&quot; is where my web assets are&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;# → I chose to put the binary alongside the binaries arduino-cli built&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;# → Lots of magic numbers here, more below&lt;/span&gt;
&lt;span class=&quot;token variable&quot;&gt;${ARDUINO_DIR}&lt;/span&gt;/packages/esp32/tools/mkspiffs/&lt;span class=&quot;token variable&quot;&gt;${SPIFFS_VERSION}&lt;/span&gt;/mkspiffs &lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;
  &lt;span class=&quot;token parameter variable&quot;&gt;-c&lt;/span&gt; arduino/data &lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;
  &lt;span class=&quot;token parameter variable&quot;&gt;-b&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;4096&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;
  &lt;span class=&quot;token parameter variable&quot;&gt;-p&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;256&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;
  &lt;span class=&quot;token parameter variable&quot;&gt;-s&lt;/span&gt; 0x170000 &lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;
  arduino/build/esp32.esp32.esp32/arduino.ino.spiffs.bin&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;There are three very-specific numbers here and they have to be exactly right.
So it took a while to find out exactly what these numbers should be.
I found the block-size, &lt;code&gt;-b&lt;/code&gt; , and page size, &lt;code&gt;-p&lt;/code&gt; in the
&lt;a href=&quot;https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/storage/spiffs.html#mkspiffs&quot;&gt;official docs&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The size parameter was a little harder to track down.
The size is important, it needs to be the exact size of the &lt;code&gt;spiffs&lt;/code&gt; partition you want to mount, otherwise it won&#39;t work.
More info on this in &lt;a href=&quot;https://blog.r0b.io/post/deploying-esp32-with-spiffs-using-github-actions/#arduino-partitions&quot;&gt;Arduino partitions&lt;/a&gt; above.&lt;/p&gt;
&lt;h3 id=&quot;flashing-from-the-cli&quot; tabindex=&quot;-1&quot;&gt;Flashing from the CLI&lt;/h3&gt;
&lt;p&gt;The final hurdle was getting a consistent flash from the CLI, to quickly develop the firmware and try out different fixes.
This took the form of another bash script:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;bin/flash.sh&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token shebang important&quot;&gt;#!/usr/bin/env sh&lt;/span&gt;

&lt;span class=&quot;token assign-left variable&quot;&gt;ESP_VERSION&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;${ESP_VERSION&lt;span class=&quot;token operator&quot;&gt;:-&lt;/span&gt;1.0.6}&lt;/span&gt;
&lt;span class=&quot;token assign-left variable&quot;&gt;SPIFFS_VERSION&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;${SPIFFS_VERSION&lt;span class=&quot;token operator&quot;&gt;:-&lt;/span&gt;0.2.3}&lt;/span&gt;
&lt;span class=&quot;token assign-left variable&quot;&gt;ESPTOOL_VERSION&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;${ESPTOOL_VERSION&lt;span class=&quot;token operator&quot;&gt;:-&lt;/span&gt;3.0.0}&lt;/span&gt;
&lt;span class=&quot;token assign-left variable&quot;&gt;ARDUINO_DIR&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;`&lt;/span&gt;arduino-cli config dump &lt;span class=&quot;token parameter variable&quot;&gt;--format&lt;/span&gt; json &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; jq &lt;span class=&quot;token parameter variable&quot;&gt;-r&lt;/span&gt; .directories.data&lt;span class=&quot;token variable&quot;&gt;`&lt;/span&gt;&lt;/span&gt;

&lt;span class=&quot;token assign-left variable&quot;&gt;BOOTLOADER&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token variable&quot;&gt;${ARDUINO_DIR}&lt;/span&gt;/packages/esp32/hardware/esp32/&lt;span class=&quot;token variable&quot;&gt;${ESP_VERSION}&lt;/span&gt;/tools/sdk/bin/bootloader_dio_80m.bin&quot;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;# You can flash the app, bootloader and partions with arduino-cli,&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;# but I preferred a single command to do it all&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;# arduino-cli upload -p &quot;/dev/cu.usb...&quot; --fqbn esp32:esp32:esp32 arduino&lt;/span&gt;

&lt;span class=&quot;token variable&quot;&gt;$ARDUINO_DIR&lt;/span&gt;/packages/esp32/tools/esptool_py/&lt;span class=&quot;token variable&quot;&gt;${ESPTOOL_VERSION}&lt;/span&gt;/esptool &lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;
  &lt;span class=&quot;token parameter variable&quot;&gt;--chip&lt;/span&gt; esp32 &lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;
  &lt;span class=&quot;token parameter variable&quot;&gt;--before&lt;/span&gt; default_reset &lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;
  &lt;span class=&quot;token parameter variable&quot;&gt;--after&lt;/span&gt; hard_reset &lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;
  write_flash &lt;span class=&quot;token parameter variable&quot;&gt;-z&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;
    0x1000 &lt;span class=&quot;token variable&quot;&gt;$BOOTLOADER&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;
    0x8000 arduino/build/esp32.esp32.esp32/arduino.ino.partitions.bin &lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;
    0x10000 arduino/build/esp32.esp32.esp32/arduino.ino.bin &lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;
    0x290000 arduino/build/esp32.esp32.esp32/arduino.ino.spiffs.bin&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is a bit of a monster of hard-coded configuration and more specific numbers are needed again!
&lt;code&gt;--chip&lt;/code&gt; is simple, I&#39;m building for an ESP32, its not the &amp;quot;board name&amp;quot; (esp32:esp32) though.
&lt;code&gt;--before&lt;/code&gt; and &lt;code&gt;--after&lt;/code&gt; run their operations at their respective times,
so it does a regular reset before flashing then a hard reset after flashing.&lt;/p&gt;
&lt;p&gt;There is also a new &lt;code&gt;BOOTLOADER&lt;/code&gt; variable, which is the path to where the ESP32 package has the bootloader I wanted to use. This lets you flash the bootloader onto the ESP too. It makes sure the latest versio of the bootloader is installed and that its compatible with the compilled firmware.&lt;/p&gt;
&lt;p&gt;The main chunk of this command is information from the &lt;a href=&quot;https://blog.r0b.io/post/deploying-esp32-with-spiffs-using-github-actions/#arduino-partitions&quot;&gt;partitions&lt;/a&gt; file,
namely the address&#39; of where to put the app and spiffs.
The bootloader and partitions go at hardcoded locations for the ESP as previously discussed.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;A nicety of &lt;code&gt;esptool&lt;/code&gt; over &lt;code&gt;arduino-cli upload&lt;/code&gt; is that you don&#39;t need to specify a USB device, it picks it for you.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;where-is-esptool-and-mkspiffs&quot; tabindex=&quot;-1&quot;&gt;Where is esptool and mkspiffs&lt;/h3&gt;
&lt;p&gt;When you install the ESP32 package with &lt;code&gt;arduino-cli&lt;/code&gt; it also installs &lt;code&gt;esptool&lt;/code&gt; and &lt;code&gt;mkspiffs&lt;/code&gt;
which are handy CLI tools for ESP32 development.&lt;/p&gt;
&lt;p&gt;You can use this little &lt;a href=&quot;https://stedolan.github.io/jq&quot;&gt;jq&lt;/a&gt; trick to get your &lt;code&gt;ARDUINO_DIR&lt;/code&gt;
and then find the binaries, as long as you know what versions it installed.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;I only found the versions through trial and error.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token assign-left variable&quot;&gt;SPIFFS_VERSION&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;${SPIFFS_VERSION&lt;span class=&quot;token operator&quot;&gt;:-&lt;/span&gt;0.2.3}&lt;/span&gt;
&lt;span class=&quot;token assign-left variable&quot;&gt;ESPTOOL_VERSION&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;${ESPTOOL_VERSION&lt;span class=&quot;token operator&quot;&gt;:-&lt;/span&gt;3.0.0}&lt;/span&gt;
&lt;span class=&quot;token assign-left variable&quot;&gt;ARDUINO_DIR&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;`&lt;/span&gt;arduino-cli config dump &lt;span class=&quot;token parameter variable&quot;&gt;--format&lt;/span&gt; json &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; jq &lt;span class=&quot;token parameter variable&quot;&gt;-r&lt;/span&gt; .directories.data&lt;span class=&quot;token variable&quot;&gt;`&lt;/span&gt;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;# esptool location&lt;/span&gt;
&lt;span class=&quot;token variable&quot;&gt;${ARDUINO_DIR}&lt;/span&gt;/packages/esp32/tools/esptool_py/&lt;span class=&quot;token variable&quot;&gt;${ESPTOOL_VERSION}&lt;/span&gt;/esptool

&lt;span class=&quot;token comment&quot;&gt;# mkspiffs location&lt;/span&gt;
&lt;span class=&quot;token variable&quot;&gt;${ARDUINO_DIR}&lt;/span&gt;/packages/esp32/tools/mkspiffs/&lt;span class=&quot;token variable&quot;&gt;${SPIFFS_VERSION}&lt;/span&gt;/mkspiffs&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&quot;https://docs.espressif.com/projects/esptool/en/latest/esp32/&quot;&gt;esptool docs →&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;misc&quot; tabindex=&quot;-1&quot;&gt;Misc&lt;/h3&gt;
&lt;p&gt;One of the commands needed the &lt;code&gt;python&lt;/code&gt; to be on my &lt;code&gt;$PATH&lt;/code&gt;, but that is no longer the case on macOS.
So I had to create a symlink to the &lt;code&gt;python3&lt;/code&gt; binary. On an M1 mac with Homebrew it looked like:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;ln&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-s&lt;/span&gt; /opt/homebrew/bin/python3/opt/homebrew/bin/python&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It seems the &lt;code&gt;esp32&lt;/code&gt; package for Arduino CLI has specific versions of &lt;code&gt;esptool&lt;/code&gt; and &lt;code&gt;mkspiffs&lt;/code&gt; bundled with it,
but it still makes sense to keep those as variables so the script can be updated at a later date.&lt;/p&gt;
&lt;h3 id=&quot;bonus%3A-monitor-serial-from-the-cli&quot; tabindex=&quot;-1&quot;&gt;Bonus: monitor serial from the CLI&lt;/h3&gt;
&lt;p&gt;If you&#39;re using the &lt;code&gt;Serial&lt;/code&gt; within your firmware code you can use the &lt;code&gt;arduino-cli&lt;/code&gt; to get the serial output
right in your own terminal.
You&#39;ll need to know your baud rate, which is the number you pass to &lt;code&gt;Serial.begin(...)&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;bin/monitor.sh&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token shebang important&quot;&gt;#!/usr/bin/env sh&lt;/span&gt;
&lt;span class=&quot;token builtin class-name&quot;&gt;set&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-e&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;# This is a little hack,&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;# If you call the command without an argument, it suggests one that is probably an ESP32&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-z&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token variable&quot;&gt;$1&lt;/span&gt;&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;then&lt;/span&gt;
  &lt;span class=&quot;token builtin class-name&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Usage:&lt;span class=&quot;token entity&quot; title=&quot;&#92;n&quot;&gt;&#92;n&lt;/span&gt;  &lt;span class=&quot;token variable&quot;&gt;$0&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;token builtin class-name&quot;&gt;echo&lt;/span&gt; /dev/cu.usbserial-*&lt;span class=&quot;token variable&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&quot;&lt;/span&gt;
  &lt;span class=&quot;token builtin class-name&quot;&gt;exit&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;fi&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;# Put your own chip and baud rate here&lt;/span&gt;
arduino-cli monitor &lt;span class=&quot;token parameter variable&quot;&gt;-b&lt;/span&gt; esp32:esp32:esp32 &lt;span class=&quot;token parameter variable&quot;&gt;-p&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token variable&quot;&gt;$1&lt;/span&gt;&quot;&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-c&lt;/span&gt; &lt;span class=&quot;token assign-left variable&quot;&gt;baudrate&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;115200&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;next-steps&quot; tabindex=&quot;-1&quot;&gt;Next steps&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Generate a GitHub release and attach the firmware binaries directly to it.&lt;/li&gt;
&lt;li&gt;Explore merging all binaries into one and see if there are any benefits&lt;/li&gt;
&lt;/ul&gt;
</content
    >
  </entry> 
  <entry>
    <title>ESM Node.js TypeScript with subpath exports</title>
    <link
      href="https://blog.r0b.io/post/esm-nodejs-typescript-with-subpath-exports/"
      rel="alternate"
      type="text/html"
      title="ESM Node.js TypeScript with subpath exports"
    />
    <updated>2022-11-05T00:00:00Z</updated>
    <id>https://blog.r0b.io/post/esm-nodejs-typescript-with-subpath-exports/</id>
    <content
      type="html"
      >&lt;p&gt;ES Modules is the future for JavaScript.
TypeScript support has been lagging behind, but it is finally catching up.
Here&#39;s what you need to use them with TypeScript:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;src/index.ts&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token builtin&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;This is the main export&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;src/another-module.ts&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;alternativeMain&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token builtin&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;This is an alternative export&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;tsconfig.json&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;compilerOptions&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;outDir&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;dist&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;moduleResolution&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Node16&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;module&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;ES2020&quot;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;src&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;src&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;package.json&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;@robb_j/my-module&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;version&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;1.2.3&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;module&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;scripts&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;build&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;tsc&quot;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;files&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;dist/*&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;exports&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;.&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token property&quot;&gt;&quot;types&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;./dist/main.d.ts&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token property&quot;&gt;&quot;import&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;./dist/main.js&quot;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;./*.js&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token property&quot;&gt;&quot;types&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;./dist/*.d.ts&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token property&quot;&gt;&quot;import&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;./dist/*.js&quot;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;devDependencies&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;typescript&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;^4.8.4&quot;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;type: module&lt;/code&gt; and &lt;code&gt;exports&lt;/code&gt; are the key parts here.
The &lt;code&gt;type&lt;/code&gt; field tells Node.js that this module will use ESM, the obvious first step.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;exports&lt;/code&gt; is a newer field and is used to tell Node.js how to import this module.
It lets you customise which files to load depending on the module-type
and TypeScript uses it to tell it how to load associated types.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;&amp;quot;.&amp;quot;&lt;/code&gt; export is the files that are imported when no subpath is provided, in this case it will load the main.js file.
The &lt;code&gt;&amp;quot;./*.js&amp;quot;&lt;/code&gt; export is used when a subpath is used that ends with &amp;quot;js&amp;quot;, that same wildcard is used to find the file too.
&lt;a href=&quot;https://nodejs.org/api/packages.html#subpath-exports&quot;&gt;More info →&lt;/a&gt;.
If you had commonjs files too, you can link them up here too.&lt;/p&gt;
&lt;p&gt;You need to use &lt;code&gt;.js&lt;/code&gt; when importing files from within TypeScript (even when importing another TypeScript file),
so it makes sense to use the &lt;code&gt;.js&lt;/code&gt; here too.
It helps to locate the type definitions too.&lt;/p&gt;
&lt;h2 id=&quot;running-the-example&quot; tabindex=&quot;-1&quot;&gt;Running the example&lt;/h2&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# cd to the project&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;# Install dependencies&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;npm&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;install&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;# Build the code to see what happens&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;npm&lt;/span&gt; run build&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;full-example&quot; tabindex=&quot;-1&quot;&gt;Full example&lt;/h2&gt;
&lt;p&gt;To see all the code, see the &lt;a href=&quot;https://github.com/robb-j/r0b-blog/tree/main/examples/nodejs-typescript-esm&quot;&gt;examples folder&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;bonus%3A-testing-locally&quot; tabindex=&quot;-1&quot;&gt;Bonus: testing locally&lt;/h2&gt;
&lt;p&gt;To test a module locally, you can use local relative dependencies.
This works better than the &lt;code&gt;npm link&lt;/code&gt; method, which I&#39;ve always struggled with.&lt;/p&gt;
&lt;p&gt;In another folder:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;package.json&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;private&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;module&quot;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Install the module relatively:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;npm&lt;/span&gt; i &lt;span class=&quot;token punctuation&quot;&gt;..&lt;/span&gt;/relative/path/to/module&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then you can test it and you should have types and IDE help too.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;consumer.js&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; main &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;@robb_j/my-module&#39;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; alternativeMain &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;@robb_j/my-module/another-module.js&#39;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
</content
    >
  </entry> 
  <entry>
    <title>Getting started with kube-prometheus-stack</title>
    <link
      href="https://blog.r0b.io/post/getting-started-with-kube-prometheus-stack/"
      rel="alternate"
      type="text/html"
      title="Getting started with kube-prometheus-stack"
    />
    <updated>2022-02-02T00:00:00Z</updated>
    <id>https://blog.r0b.io/post/getting-started-with-kube-prometheus-stack/</id>
    <content
      type="html"
      >&lt;p&gt;I recently added monitoring and alerting to my Kubernetes stack to discover and respond to failures faster.
It took me a lot longer I than expected to get my head around &lt;code&gt;kube-prometheus-stack&lt;/code&gt; and get it doing what I wanted it to,
so here&#39;s what I found out.&lt;/p&gt;
&lt;h2 id=&quot;background&quot; tabindex=&quot;-1&quot;&gt;Background&lt;/h2&gt;
&lt;p&gt;I&#39;ve spent the last year building and running infrastructure to host virtual conferences
(&lt;a href=&quot;https://climate-red.openlab.dev/&quot;&gt;Climate:Red&lt;/a&gt;, &lt;a href=&quot;https://schedule.mozillafestival.org/plaza&quot;&gt;MozFest &#39;21 &amp;amp; &#39;22&lt;/a&gt; &amp;amp; &lt;a href=&quot;https://planetredsummit.com/atrium&quot;&gt;Planet:Red&lt;/a&gt;).
Running these large deployments by myself has led to a lot of automation.
I start off automating testing and integration,
then building and releasing containers
and then deployment, configuration and scaling of those containers.&lt;/p&gt;
&lt;p&gt;I&#39;d previously used &lt;a href=&quot;https://github.com/prometheus-community/helm-charts/tree/main/charts/kube-prometheus-stack&quot;&gt;kube-prometheus-stack&lt;/a&gt;
to see what was going on inside my Kubernetes clusters,
but always knew there was a lot more to it than the fancy Grafana graphs.
For MozFest 2022 I decided it was time to look into this properly.&lt;/p&gt;
&lt;p&gt;The conference infrastructure relies on several background tasks, running as Kubernetes &lt;code&gt;CronJob&lt;/code&gt; resources,
which can fail very quietly.
If one of the jobs failed, the schedule might become stale or new users might not be able to sign in.
I needed something to quickly surface those background errors and report them.&lt;/p&gt;
&lt;h2 id=&quot;setup&quot; tabindex=&quot;-1&quot;&gt;Setup&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;kube-promethus-stack&lt;/strong&gt; is pretty easy to get installed,
DigitalOcean even offers a one-click install after deploying a Kubernetes cluster!
That internally uses Helm to deploy &lt;a href=&quot;https://github.com/prometheus-community/helm-charts/tree/main/charts/kube-prometheus-stack&quot;&gt;prometheus-community/kube-prometheus-stack&lt;/a&gt;. So you can:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;helm repo &lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt; prometheus-community https://prometheus-community.github.io/helm-charts
helm repo update
helm &lt;span class=&quot;token function&quot;&gt;install&lt;/span&gt; kube-prometheus-stack prometheus-community/kube-prometheus-stack&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This deploys &lt;a href=&quot;https://prometheus-operator.dev/&quot;&gt;prometheus-operator/kube-prometheus&lt;/a&gt;,
which is an &lt;a href=&quot;https://kubernetes.io/docs/concepts/extend-kubernetes/operator/&quot;&gt;operator&lt;/a&gt; that uses &lt;a href=&quot;https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definitions/&quot;&gt;CRDs&lt;/a&gt; to deploy and configure instances of Prometheus and Alertmanager.
It uses them to deploy a standard instance of Prometheus and Alertmanager and adds a tonne of rules for monitoring standard Kubernetes resources.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;One confusion point was that internally kube-prometheus-stack is deployed using &lt;a href=&quot;https://jsonnet.org/&quot;&gt;Jsonnet templates&lt;/a&gt;
and the helm offering is community-operated, so there are some differences between both sets of documentation.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;how-it-works&quot; tabindex=&quot;-1&quot;&gt;How it works&lt;/h2&gt;
&lt;p&gt;Prometheus is a time series database which periodically scrapes different servers to fetch metrics.
It offers a query language, &lt;a href=&quot;https://prometheus.io/docs/prometheus/latest/querying/basics/&quot;&gt;PromQL&lt;/a&gt;,
to query those metrics over time and a rule system that triggers alerts based on queries.
Alerts have a name and key-value labels of information about them,
like the &lt;code&gt;namespace&lt;/code&gt; the resource was in or name of the related &lt;code&gt;container&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The stack comes setup to monitor the resources within Kubernetes using &lt;a href=&quot;https://github.com/kubernetes/kube-state-metrics&quot;&gt;kube-state-metrics&lt;/a&gt; and has lots of rules already setup to monitor things like Pods crashing.&lt;/p&gt;
&lt;p&gt;Alertmanager takes Prometheus&#39; alerts and lets you group and filter them based on the associated labels.
It provides routing to send different alerts to different receivers, like an email or Slack message.&lt;/p&gt;
&lt;h2 id=&quot;configuring-alertmanager&quot; tabindex=&quot;-1&quot;&gt;Configuring Alertmanager&lt;/h2&gt;
&lt;p&gt;The default Prometheus instance is already configured to collect metrics on Kubernetes resources and alert on failures.
I was interested in &lt;code&gt;KubeJobFailed&lt;/code&gt; and &lt;code&gt;KubePodCrashLooping&lt;/code&gt; rules to detect failed jobs or bad deployments.
Alertmanager needs to be configured with a receiver to send them somewhere.
&lt;code&gt;prometheus-operator&lt;/code&gt; provides the &lt;em&gt;AlertmanagerConfig&lt;/em&gt; custom resource to do just that.
The default Alertmanager instance automatically regenerates it&#39;s internal configuration
whenever &lt;strong&gt;any&lt;/strong&gt; AlertmanagerConfig is changed in &lt;strong&gt;any&lt;/strong&gt; namespace.
So you can create a &lt;code&gt;custom-alertmanagerconfig.yml&lt;/code&gt; and &lt;code&gt;kubectl apply -f&lt;/code&gt; -it:&lt;/p&gt;
&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token key atrule&quot;&gt;apiVersion&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; monitoring.coreos.com/v1alpha1
&lt;span class=&quot;token key atrule&quot;&gt;kind&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; AlertmanagerConfig
&lt;span class=&quot;token key atrule&quot;&gt;metadata&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; custom&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;config
  &lt;span class=&quot;token key atrule&quot;&gt;namespace&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; production
&lt;span class=&quot;token key atrule&quot;&gt;spec&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;route&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;groupBy&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;job&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;groupWait&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 30s
    &lt;span class=&quot;token key atrule&quot;&gt;groupInterval&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 5m
    &lt;span class=&quot;token key atrule&quot;&gt;repeatInterval&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 12h
    &lt;span class=&quot;token key atrule&quot;&gt;receiver&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;sendgrid-smtp&#39;&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;matchers&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; severity
        &lt;span class=&quot;token key atrule&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; warning

  &lt;span class=&quot;token key atrule&quot;&gt;receivers&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;sendgrid-smtp&#39;&lt;/span&gt;
      &lt;span class=&quot;token key atrule&quot;&gt;emailConfigs&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;to&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; notify@example.com
          &lt;span class=&quot;token key atrule&quot;&gt;from&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; noreply@example.com
          &lt;span class=&quot;token key atrule&quot;&gt;smarthost&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; smtp.sendgrid.net&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;587&lt;/span&gt;
          &lt;span class=&quot;token key atrule&quot;&gt;authUsername&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; apikey
          &lt;span class=&quot;token key atrule&quot;&gt;authPassword&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; sendgrid&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;alerts
            &lt;span class=&quot;token key atrule&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; SENDGRID_API_KEY&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;It&#39;s worth pointing out AlertmanagerConfig is a &lt;code&gt;v1alpha1&lt;/code&gt; CRD so it will most likely change in the future.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This configuration is a camel-case version of regular &lt;a href=&quot;https://prometheus.io/docs/prometheus/latest/configuration/configuration/&quot;&gt;Alertmanager config&lt;/a&gt;
and the operator merges them together into one configuration.
This creates a &lt;code&gt;route&lt;/code&gt; that groups jobs by their &lt;code&gt;job&lt;/code&gt; label and sends them to the &lt;code&gt;sendgrid-smtp&lt;/code&gt; receiver.
You can use &lt;code&gt;matchers&lt;/code&gt; to filter the alerts the route receives based on their labels.&lt;/p&gt;
&lt;p&gt;There is a bit of hidden logic in how &lt;code&gt;prometheus-operator&lt;/code&gt; merges these configurations back together.
It will ignore any &lt;code&gt;namespace&lt;/code&gt; matchers you set
and instead match the namespace the AlertmanagerConfig itself is in.
In this case it would be &lt;code&gt;namespace=production&lt;/code&gt;,
so make sure to put the config in the same namespace you want alerts from.&lt;/p&gt;
&lt;p&gt;Receivers configure how to actually send alerts, in this example via smtp.
This one will email alerts to &lt;code&gt;notify@example.com&lt;/code&gt; via SendGrid&#39;s smtp server.
It references a secret, which needs to be in the same namespace as the AlertmanagerConfig, to specify the password.
So you don&#39;t have to put credentials in your CRD.&lt;/p&gt;
&lt;h2 id=&quot;debugging&quot; tabindex=&quot;-1&quot;&gt;Debugging&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Detecting errors&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;First, you want to make sure the operator is accepting accepting your AlertmanagerConfig.
It will log any errors from the config or let you know that it successfully reloaded.
You can watch those logs:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;kubectl logs &lt;span class=&quot;token parameter variable&quot;&gt;-f&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-n&lt;/span&gt; kube-prometheus-stack deploy/kube-prometheus-stack-operator&lt;/code&gt;&lt;/pre&gt;
&lt;br /&gt;
&lt;p&gt;&lt;strong&gt;Inspecting generated config&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;If the configuration is loading correctly,
you can inspect the generated secret that it is stored in:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;kubectl get secret &lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;
  &lt;span class=&quot;token parameter variable&quot;&gt;-n&lt;/span&gt; kube-prometheus-stack &lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;
  &lt;span class=&quot;token parameter variable&quot;&gt;-o&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;jsonpath&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;{.data.alertmanager&#92;.yaml}&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;
  alertmanager-kube-prometheus-stack-alertmanager-generated &lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;
    &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; base64 &lt;span class=&quot;token parameter variable&quot;&gt;-d&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;br /&gt;
&lt;p&gt;&lt;strong&gt;Configuration options&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;If you&#39;re unsure about what you can put into an AlertmanagerConfig,
&lt;a href=&quot;https://doc.crds.dev/&quot;&gt;doc.crds.dev&lt;/a&gt; is a great website that describes CRDs based on their schema.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://doc.crds.dev/github.com/prometheus-operator/prometheus-operator&quot;&gt;doc.crds.dev/github.com/prometheus-operator/prometheus-operator →&lt;/a&gt;&lt;/p&gt;
&lt;br /&gt;
&lt;p&gt;&lt;strong&gt;Inspecting prometheus &amp;amp; alertmanager&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;You can &lt;code&gt;port-forward&lt;/code&gt; Prometheus and Alertmanager to see what is going on in a browser.
Looking at Prometheus is useful to explore the default alerts and their generated labels and also inspect the raw metrics.
Looking at Alertmanager lets you see the grouping that is applied and the &lt;a href=&quot;http://localhost:9090/alerts&quot;&gt;alerts&lt;/a&gt; that have been triggered.&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# Inspect prometheus&lt;/span&gt;
kubectl &lt;span class=&quot;token parameter variable&quot;&gt;-n&lt;/span&gt; kube-prometheus-stack port-forward svc/prometheus-operated &lt;span class=&quot;token number&quot;&gt;9090&lt;/span&gt;:9090
&lt;span class=&quot;token function&quot;&gt;open&lt;/span&gt; http://localhost:9090

&lt;span class=&quot;token comment&quot;&gt;# Inspect alertmanager&lt;/span&gt;
kubectl &lt;span class=&quot;token parameter variable&quot;&gt;-n&lt;/span&gt; kube-prometheus-stack port-forward svc/alertmanager-operated &lt;span class=&quot;token number&quot;&gt;9093&lt;/span&gt;:9093
&lt;span class=&quot;token function&quot;&gt;open&lt;/span&gt; http://localhost:9093&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;testing&quot; tabindex=&quot;-1&quot;&gt;Testing&lt;/h2&gt;
&lt;p&gt;To make sure alerting is setup, create an obviously bad workload and see if it triggers an alert.
e.g. this &lt;code&gt;deployment.yml&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token key atrule&quot;&gt;apiVersion&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; apps/v1
&lt;span class=&quot;token key atrule&quot;&gt;kind&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Deployment
&lt;span class=&quot;token key atrule&quot;&gt;metadata&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; bad&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;app
  &lt;span class=&quot;token key atrule&quot;&gt;namespace&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; production
&lt;span class=&quot;token key atrule&quot;&gt;spec&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;selector&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;matchLabels&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token key atrule&quot;&gt;app.kubernetes.io/app&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; bad&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;app
  &lt;span class=&quot;token key atrule&quot;&gt;template&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;metadata&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token key atrule&quot;&gt;labels&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;token key atrule&quot;&gt;app.kubernetes.io/app&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; bad&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;app
    &lt;span class=&quot;token key atrule&quot;&gt;spec&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token key atrule&quot;&gt;containers&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; bad&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;app
          &lt;span class=&quot;token key atrule&quot;&gt;image&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; busybox
          &lt;span class=&quot;token key atrule&quot;&gt;command&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;exit&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;1&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After you &lt;code&gt;kubectl apply -f&lt;/code&gt; it, you should first see a &amp;quot;Pending&amp;quot; alert in &lt;a href=&quot;http://localhost:9090/&quot;&gt;Prometheus&lt;/a&gt;
After the &lt;code&gt;for&lt;/code&gt; duration has passed, it should fire the alert.
Once fired, you will see the new alert in &lt;a href=&quot;http://localhost:9093/&quot;&gt;Alertmanager&lt;/a&gt;
and receive it in whatever receiver(s) you have setup.&lt;/p&gt;
&lt;h2 id=&quot;next-steps&quot; tabindex=&quot;-1&quot;&gt;Next steps&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Choosing rules&lt;/strong&gt; is the next logical step. This setup will alert on all of kube-prometheus-stack&#39;s default alerts, which are a good first step. But if that&#39;s too much info, you need to find the alerts that are useful to your specific stack and setup matchers. Looking through prometheus&#39;s default alerts should be a good first step, i.e. &lt;a href=&quot;http://localhost:9090/alerts&quot;&gt;localhost:9090/alerts&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Meta alerting&lt;/strong&gt; is useful to ensure confidence that monitoring and alerting is all going as expected. Prometheus constantly fires a &lt;code&gt;Watchdog&lt;/code&gt; alert so you could use a &lt;a href=&quot;https://en.wikipedia.org/wiki/Dead_man%27s_switch&quot;&gt;dead-man&#39;s-switch&lt;/a&gt; type service that alerts you when the alert stops firing.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Different receivers&lt;/strong&gt; let you send different notifications to different places, perhaps based on severity or the team responsible. You could have seperate alerts for frontend or backend teams for example.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Setup persistence&lt;/strong&gt;, the default &lt;code&gt;kube-prometheus-stack&lt;/code&gt; doesn&#39;t persist any metrics beyond a reboot. For alerting purposes this is fine but if you need to keep those metrics around you&#39;ll want to look into configuring some &lt;a href=&quot;https://github.com/prometheus-community/helm-charts/blob/main/charts/kube-prometheus-stack/README.md#persistent-volumes&quot;&gt;PersistentVolumeClaims&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Custom metrics&lt;/strong&gt; is a very interesting next step. You&#39;ll need to tell your Prometheus instance to start scraping your services or pods directly using a &lt;code&gt;ServiceMonitor&lt;/code&gt; or &lt;code&gt;PodMonitor&lt;/code&gt; and define custom &lt;code&gt;/metrics&lt;/code&gt; endpoints on your containers that expose &lt;a href=&quot;https://prometheus.io/docs/concepts/metric_types/&quot;&gt;prometheus metrics&lt;/a&gt; specific to your stack. For MozFest there is a &#39;site-visitors&#39; widget and it could be interesting to track that over time and have a custom alert when it is over a certain value.&lt;/p&gt;
&lt;h2 id=&quot;resources&quot; tabindex=&quot;-1&quot;&gt;Resources&lt;/h2&gt;
&lt;p&gt;These were helpful links I used while getting this setup:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://prometheus-operator.dev/&quot;&gt;prometheus-operator.dev&lt;/a&gt;
provides a relatively good overview of whats going on, but does miss out on some of the nuances that go on behind the scenes.
The &lt;a href=&quot;https://prometheus-operator.dev/docs/operator/design/&quot;&gt;design&lt;/a&gt; page is a very good starting point explaining what each CRD is for.
The &lt;a href=&quot;https://github.com/prometheus-operator/prometheus-operator/tree/main/Documentation/user-guides&quot;&gt;user-guides&lt;/a&gt;
section in their GitHub repo has some good guides for getting started and configuration.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://doc.crds.dev/github.com/prometheus-operator/prometheus-operator&quot;&gt;doc.crds.dev&lt;/a&gt;
was really helpful debugging what needs to go inside the AlertmanagerConfig resource
and the descriptions helped to describe what different fields do and how they operate together.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://prometheus.io/docs/alerting/latest/configuration/&quot;&gt;Alertmanager Configuration&lt;/a&gt;
is useful to know what should and shouldn&#39;t be going into those configs
and debugging what the generated configurations do.
The &lt;strong&gt;visual editor&lt;/strong&gt; was kind of useful, but not very.&lt;/li&gt;
&lt;li&gt;The &lt;a href=&quot;https://artifacthub.io/packages/helm/prometheus-community/kube-prometheus-stack&quot;&gt;Artifact Hub page&lt;/a&gt;
has some useful information on it too.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;conclusion&quot; tabindex=&quot;-1&quot;&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;These were the steps I ended up with to get notifications when things start going wrong inside my Kubernetes cluster.
It helpfully surfaces things that are going wrong and lets you know about them.
If you found this helpful or have questions, let me know on &lt;a href=&quot;https://twitter.com/robbb_j&quot;&gt;Twitter&lt;/a&gt;!&lt;/p&gt;
</content
    >
  </entry> 
  <entry>
    <title>Creating custom JavaScript errors</title>
    <link
      href="https://blog.r0b.io/post/creating-custom-javascript-errors/"
      rel="alternate"
      type="text/html"
      title="Creating custom JavaScript errors"
    />
    <updated>2021-09-12T00:00:00Z</updated>
    <id>https://blog.r0b.io/post/creating-custom-javascript-errors/</id>
    <content
      type="html"
      >&lt;p&gt;Creating custom errors in JavaScript can be very useful. If you want to handle those errors in a &lt;code&gt;catch&lt;/code&gt; block you can use the &lt;code&gt;instanceof&lt;/code&gt; operator to check for that specific error. And with TypeScript, you can then safely use the fields and methods on your custom error.&lt;/p&gt;
&lt;p&gt;Another benefit of custom errors is that you can provide your own constructor which lets you pass more information to the error. Which is information you can use in an error handler.&lt;/p&gt;
&lt;p&gt;Custom JavaScript errors are almost as simple as creating an &lt;code&gt;Error&lt;/code&gt; subclass, but there are two things you can do to make them even more useful.&lt;/p&gt;
&lt;h2 id=&quot;set-the-name&quot; tabindex=&quot;-1&quot;&gt;Set the name&lt;/h2&gt;
&lt;p&gt;The first thing to enhance custom errors is to set &lt;code&gt;this.name&lt;/code&gt; inside the constructor. This means your error name will appear in stack traces rather than the generic &lt;code&gt;Error:&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id=&quot;re-capture-the-stack-trace&quot; tabindex=&quot;-1&quot;&gt;Re-capture the stack trace&lt;/h2&gt;
&lt;p&gt;An unfortunate side-effect of creating a custom error is that your custom constructor will appear in the stack trace. This is because your &lt;code&gt;super&lt;/code&gt; call is technically creating the error and the JavaScript VM records that in the generated stack trace. This isn&#39;t useful and makes stack traces messier.&lt;/p&gt;
&lt;p&gt;Removing this makes it easier to get to the code that&#39;s thrown the error. You can do this with the &lt;code&gt;Error.captureStackTrace&lt;/code&gt; static method.&lt;/p&gt;
&lt;h2 id=&quot;an-example&quot; tabindex=&quot;-1&quot;&gt;An example&lt;/h2&gt;
&lt;p&gt;To demonstrate this, let&#39;s create a custom error that captures extra information for a http API, so we can throw a desired http status and an error identifier.&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ApiError&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Error&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;constructor&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;status&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; apiCode&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;super&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;There was an error with your API request: &quot;&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;apiCode&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;ApiError&#39;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;status &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; status
    &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;apiCode &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; apiCode
    Error&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;captureStackTrace&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; ApiError&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That&#39;s all you need, you could use it like this:&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ApiError&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;404&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;notFound&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// or&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ApiError&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;400&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;login.emailNotProvided&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;and this will produce a stack trace like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/◦◦◦/custom-errors/main.js:4
  throw new ApiError(404, &amp;quot;notFound&amp;quot;);
  ^

ApiError: There was an error with your API request: &amp;quot;notFound&amp;quot;
    at runApp (/◦◦◦/custom-errors/main.js:4:9)
    at main (/◦◦◦/custom-errors/main.js:12:3)
    at Object.&amp;lt;anonymous&amp;gt; (/◦◦◦/custom-errors/main.js:15:1)
    ◦◦◦ {
  status: 404,
  apiCode: &#39;notFound&#39;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;See &lt;a href=&quot;https://github.com/robb-j/r0b-blog/tree/master/examples/custom-errors&quot;&gt;examples/custom-errors&lt;/a&gt; for the exact source code.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The output shows that it has namespaced the error with &lt;code&gt;ApiError:&lt;/code&gt; which is from us setting &lt;code&gt;this.name&lt;/code&gt;.
The first line of the stack trace is not &lt;strong&gt;ApiError&lt;/strong&gt;&#39;s constructor but &lt;code&gt;runApp&lt;/code&gt; which was the method which threw the error.
Because of this, it now shows you the exact line of code that threw the error in the excerpt.&lt;/p&gt;
&lt;p&gt;You can also see that &lt;code&gt;status&lt;/code&gt; and &lt;code&gt;apiCode&lt;/code&gt; have been stored on the error.&lt;/p&gt;
&lt;h2 id=&quot;catching-errors&quot; tabindex=&quot;-1&quot;&gt;Catching errors&lt;/h2&gt;
&lt;p&gt;To complete this post, here&#39;s an example of catching an &lt;strong&gt;ApiError&lt;/strong&gt; and using the extra fields.&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; express &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;express&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; app &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;express&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// From above&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ApiError&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Error&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;/* ... */&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// A route which results in an error&lt;/span&gt;
app&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;/&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;req&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; res&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; next&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;next&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ApiError&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;404&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;notFound&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// An Express error handler, more info at:&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;// http://expressjs.com/en/guide/error-handling.html&lt;/span&gt;
app&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;use&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;error&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; req&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; res&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; next&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;Handled error&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; error&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;error &lt;span class=&quot;token keyword&quot;&gt;instanceof&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ApiError&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    res&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;status&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;error&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;status&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;send&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;apiCode&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; error&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;apiCode &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    res&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;status&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;500&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;send&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;apiCode&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;unknownError&#39;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

app&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;listen&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;3000&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This shows throwing an &lt;strong&gt;ApiError&lt;/strong&gt; in an ExpressJs context and handling it with an error middleware.
It checks for an &lt;strong&gt;ApiError&lt;/strong&gt; with the &lt;code&gt;instanceof&lt;/code&gt; operator, and then safely uses the fields on it to generate a http response.
The logic also nicely allows a generic http 500 error to be sent when unknown errors are thrown.&lt;/p&gt;
&lt;h2 id=&quot;bonus%3A-static-methods&quot; tabindex=&quot;-1&quot;&gt;Bonus: static methods&lt;/h2&gt;
&lt;p&gt;When using custom errors another pattern I&#39;ve used is to add static methods to easily create common errors. The does bring back the stack trace issue, so another &lt;code&gt;Error.captureStackTrace&lt;/code&gt; is needed.&lt;/p&gt;
&lt;p&gt;To demonstrate this, lets add some common errors to &lt;strong&gt;ApiError&lt;/strong&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ApiError&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Error&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;notFound&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ApiError&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;404&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;general.notFound&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;trimStack&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;unauthorized&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ApiError&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;401&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;general.unauthorized&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;trimStack&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token comment&quot;&gt;// Same as above&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;constructor&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token comment&quot;&gt;/* ... */&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;/* ... */&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token function&quot;&gt;trimStack&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    Error&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;captureStackTrace&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; ApiError&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I found the utility function &lt;code&gt;trimStack&lt;/code&gt; helps keeps these static methods easier to read and understand.&lt;/p&gt;
&lt;p&gt;Now you can quickly create common errors with &lt;code&gt;throw ApiError.notFound()&lt;/code&gt;, which is easier to read and hopefully leads to less mistakes. For the full source code see See &lt;a href=&quot;https://github.com/robb-j/r0b-blog/tree/master/examples/custom-errors&quot;&gt;examples/custom-errors&lt;/a&gt;.&lt;/p&gt;
</content
    >
  </entry> 
  <entry>
    <title>Creating a Nova Extension with TypeScript</title>
    <link
      href="https://blog.r0b.io/post/creating-a-nova-extension-with-typescript/"
      rel="alternate"
      type="text/html"
      title="Creating a Nova Extension with TypeScript"
    />
    <updated>2021-07-25T00:00:00Z</updated>
    <id>https://blog.r0b.io/post/creating-a-nova-extension-with-typescript/</id>
    <content
      type="html"
      >&lt;p&gt;I&#39;ve been quite getting into &lt;a href=&quot;https://nova.app/&quot;&gt;Nova&lt;/a&gt; recently, a macOS-only IDE developed by &lt;a href=&quot;https://panic.com/&quot;&gt;Panic&lt;/a&gt;. It&#39;s a &lt;a href=&quot;https://daringfireball.net/linked/2020/03/20/mac-assed-mac-apps&quot;&gt;&amp;quot;Mac-assed&amp;quot;&lt;/a&gt; editor and I feel it fits a lot more naturally into my flow.&lt;/p&gt;
&lt;p&gt;I&#39;ve gone far enough into making &lt;a href=&quot;https://github.com/robb-j?tab=repositories&amp;amp;q=nova-&amp;amp;type=&amp;amp;language=&amp;amp;sort=&quot;&gt;a few extensions&lt;/a&gt; for Nova to make it work for me more. Here I want to share my experiences in doing that, specifically around setting up a project with TypeScript. Writing an Extension with TypeScript requires a few different steps to the &lt;a href=&quot;https://library.panic.com/nova/npm-packages-in-extensions/&quot;&gt;recommended setup&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;prerequisites&quot; tabindex=&quot;-1&quot;&gt;Prerequisites&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Familiarity with TypeScript&lt;/li&gt;
&lt;li&gt;Understanding of terminals, i.e. the Mac&#39;s default Terminal.app&lt;/li&gt;
&lt;li&gt;Knowledge of NPM and using packages&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;Any time I use &lt;strong&gt;A&lt;/strong&gt; → &lt;strong&gt;B&lt;/strong&gt; → &lt;strong&gt;C&lt;/strong&gt; notation, it means to navigate through macOS menus&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;setup&quot; tabindex=&quot;-1&quot;&gt;Setup&lt;/h2&gt;
&lt;p&gt;First, create a folder to put your Extension in, this will be referred to as the &lt;em&gt;root folder&lt;/em&gt; going forwards.&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# Create a folder to put the project in&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;mkdir&lt;/span&gt; nova-example
&lt;span class=&quot;token builtin class-name&quot;&gt;cd&lt;/span&gt; nova-example

&lt;span class=&quot;token comment&quot;&gt;# Quickly setup NPM&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;npm&lt;/span&gt; init &lt;span class=&quot;token parameter variable&quot;&gt;-y&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;# Install development dependencies&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;npm&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;install&lt;/span&gt; --save-dev esbuild typescript @types/nova-editor-node

&lt;span class=&quot;token comment&quot;&gt;# If you have the Nova CLI installed...&lt;/span&gt;
nova &lt;span class=&quot;token builtin class-name&quot;&gt;.&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now open this folder in Nova and create a Nova Extension inside of it. The reason for this nesting is to keep TypeScript source-files and development tooling outside of the Extension, then compile JavaScript files into it.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;A Nova Extension is essentially a folder with a &lt;code&gt;.novaextension&lt;/code&gt; file extension.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;In Nova, select &lt;strong&gt;Extensions&lt;/strong&gt; → &lt;strong&gt;Create New Extension..&lt;/strong&gt;. Then choose the type of extension you want to make, for this tutorial I will choose &lt;strong&gt;Blank&lt;/strong&gt;. Make sure to put the new extension inside the folder we created above. Remember to close the new window Nova opened after creating an extension, because we want to have the root folder open in Nova.&lt;/p&gt;
&lt;p&gt;If you don&#39;t see &lt;strong&gt;Extensions&lt;/strong&gt; in the Nova menu, make sure you have enabled development mode in &lt;strong&gt;Nova&lt;/strong&gt; → &lt;strong&gt;Preferences&lt;/strong&gt; → &lt;strong&gt;General&lt;/strong&gt; → &lt;strong&gt;Extension Development&lt;/strong&gt;.&lt;/p&gt;
&lt;h2 id=&quot;configure-typescript&quot; tabindex=&quot;-1&quot;&gt;Configure TypeScript&lt;/h2&gt;
&lt;p&gt;Create &lt;strong&gt;tsconfig.json&lt;/strong&gt; in the root folder:&lt;/p&gt;
&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;$schema&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;https://json.schemastore.org/tsconfig&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;display&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Nova&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;

  &lt;span class=&quot;token property&quot;&gt;&quot;compilerOptions&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;target&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;es6&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;module&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;ES2020&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;moduleResolution&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;node&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;newLine&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;lf&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;strict&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;noEmitOnError&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;skipLibCheck&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;include&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;src&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A few points to note:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;We&#39;re targeting ES6 which lets us use newer JavaScript features and still &lt;a href=&quot;https://caniuse.com/?search=es6&quot;&gt;support older macs&lt;/a&gt;.
It should support macs from 2016 onwards.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;noEmit&lt;/code&gt; is set to true because we&#39;re using esbuild to bundle things, not TypeScript&#39;s compiler, &lt;code&gt;tsc&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;$schema&lt;/code&gt; section helps JSON Extension to validate and suggest completions&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.typescriptlang.org/tsconfig&quot;&gt;typescriptlang.org/tsconfig&lt;/a&gt; has great info about possible options&lt;/li&gt;
&lt;/ul&gt;
&lt;details&gt;
&lt;summary&gt;For improved linting:&lt;/summary&gt;
&lt;p&gt;Add these to your &lt;code&gt;compilerOptions&lt;/code&gt;&lt;/p&gt;
&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;    &lt;span class=&quot;token property&quot;&gt;&quot;noImplicitReturns&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;noFallthroughCasesInSwitch&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;forceConsistentCasingInFileNames&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/details&gt;
&lt;h2 id=&quot;create-a-build-task&quot; tabindex=&quot;-1&quot;&gt;Create a build task&lt;/h2&gt;
&lt;p&gt;Next, create a build Task which will take TypeScript, bundle any imported files together and output JavaScript into the Extension folder. Go to &lt;strong&gt;Project&lt;/strong&gt; → &lt;strong&gt;Project Settings...&lt;/strong&gt; then create a Task called &lt;strong&gt;Development&lt;/strong&gt; (or whatever you want). Then in the &lt;strong&gt;Build&lt;/strong&gt; section, enter the script below. Make sure to update &lt;code&gt;--outfile&lt;/code&gt; with whatever your Extension is called.&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token shebang important&quot;&gt;#!/usr/bin/env sh&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;# Ensure the build fails if TypeScript fails&lt;/span&gt;
&lt;span class=&quot;token builtin class-name&quot;&gt;set&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-e&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;# Lint TypeScript source code&lt;/span&gt;
npx tsc &lt;span class=&quot;token parameter variable&quot;&gt;--noEmit&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;--pretty&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;# Bundle into JavaScript&lt;/span&gt;
npx esbuild &lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;
  &lt;span class=&quot;token parameter variable&quot;&gt;--bundle&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;
  &lt;span class=&quot;token parameter variable&quot;&gt;--format&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;cjs &lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;
  &lt;span class=&quot;token parameter variable&quot;&gt;--outfile&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;Example.novaextension/Scripts/main.dist.js &lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;
  src/Scripts/main.ts&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;You could put this in a script like &lt;code&gt;bin/build.sh&lt;/code&gt; if you want to run it outside Nova, then link the script in &lt;strong&gt;Project Settings...&lt;/strong&gt; using the &lt;code&gt;path&lt;/code&gt; option.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Now you can compile TypeScript with a &lt;strong&gt;cmd+B&lt;/strong&gt; or by pressing the build button. The output file is called &lt;code&gt;main.dist.js&lt;/code&gt; so you can add it to your gitignore with a &lt;code&gt;*.dist.js&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id=&quot;write-some-typescript&quot; tabindex=&quot;-1&quot;&gt;Write some TypeScript&lt;/h2&gt;
&lt;p&gt;Finally we can write some TypeScript, let&#39;s create a script which will be the Extension entry point. If you have the TypeScript Extension enabled, you should start seeing the auto suggestions and linting while typing this out.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;src/Scripts/main.ts&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;activate&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; message &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;NotificationRequest&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;hi&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  message&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;title &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;Hello, world!&#39;&lt;/span&gt;
  message&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;body &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;Lorem ipsum sil dor amet&#39;&lt;/span&gt;
  nova&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;notifications&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;message&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;deactivate&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;configure-the-extension&quot; tabindex=&quot;-1&quot;&gt;Configure the Extension&lt;/h2&gt;
&lt;p&gt;To link everything up, configure your Extension&#39;s &lt;strong&gt;extension.json&lt;/strong&gt;, to ensure it uses our &lt;code&gt;main.dist.js&lt;/code&gt;. For the purpose of this tutorial, set &lt;code&gt;activationEvents&lt;/code&gt; so your code always gets run. In production, you&#39;d want to configure that to only activate the Extension when it is needed, so users aren&#39;t running it unnecessarily.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;More information about &lt;a href=&quot;https://docs.nova.app/extensions/#activation-events&quot;&gt;activationEvents&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;Example.novaextension/extension.json:&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;main&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;main.dist.js&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;activationEvents&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;*&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;run-the-extension&quot; tabindex=&quot;-1&quot;&gt;Run the Extension&lt;/h2&gt;
&lt;p&gt;Run a build with &lt;strong&gt;cmd+B&lt;/strong&gt; and it should generate your code into &lt;code&gt;Example.novaextension/Scripts/main.dist.js&lt;/code&gt;, providing there are no TypeScript errors.&lt;/p&gt;
&lt;p&gt;Run the Extension locally with &lt;strong&gt;Extensions&lt;/strong&gt; &amp;gt; &lt;strong&gt;Activate Project as Extension&lt;/strong&gt;. This will activate this Extension for any active Nova windows and will automatically reload it when you rebuild (or if any other files inside your Extension folder change).&lt;/p&gt;
&lt;p&gt;When activated, you should see a notification in the top right of Nova 🎉 it&#39;s all working!&lt;/p&gt;
&lt;h3 id=&quot;notes&quot; tabindex=&quot;-1&quot;&gt;Notes&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;If you change the strings and re-build, you should see the notification again with the new message.&lt;/li&gt;
&lt;li&gt;Esbuild will bundle any &lt;code&gt;node_modules&lt;/code&gt; that you import, but you should be careful about what those modules do. The JavaScript that Nova run&#39;s isn&#39;t the same as a Node.js environment and that might break some packages. You only have &lt;a href=&quot;https://docs.nova.app/api-reference/web-apis/&quot;&gt;Web APIs&lt;/a&gt; and the &lt;a href=&quot;https://docs.nova.app/api-reference/environment/&quot;&gt;nova global&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Make sure to update your &lt;a href=&quot;https://docs.nova.app/extensions/#activation-events&quot;&gt;activationEvents&lt;/a&gt; before publishing.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;p&gt;If you found this useful, &lt;a href=&quot;https://twitter.com/robbb_j&quot;&gt;Tweet me&lt;/a&gt; and let me know!&lt;/p&gt;
</content
    >
  </entry> 
  <entry>
    <title>Bundle JavaScript with Eleventy and esbuild</title>
    <link
      href="https://blog.r0b.io/post/bundle-javascript-with-eleventy-and-esbuild/"
      rel="alternate"
      type="text/html"
      title="Bundle JavaScript with Eleventy and esbuild"
    />
    <updated>2021-06-27T00:00:00Z</updated>
    <id>https://blog.r0b.io/post/bundle-javascript-with-eleventy-and-esbuild/</id>
    <content
      type="html"
      >&lt;p&gt;&lt;a href=&quot;https://blog.r0b.io/post/compile-sass-with-eleventy/&quot;&gt;Static site generators are great...&lt;/a&gt;
as I have previously mentioned,
here is how to bundle JavaScript into your Eleventy site too.&lt;/p&gt;
&lt;p&gt;When you&#39;re making a website with a static site generator,
you have already chosen to not to create a Single Page App (SPA).
But you might still want to add JavaScript to add dynamic features.&lt;/p&gt;
&lt;p&gt;You can use an &lt;a href=&quot;https://www.11ty.dev/docs/languages/javascript/#classes&quot;&gt;Eleventy JavaScript class template&lt;/a&gt;
to bundle up multiple JavaScript files into a single backwards-compatible file using esbuild.
Esbuild is a newer JavaScript bundler written in Go
and it&#39;s very performant compared to tools like WebPack and Parcel.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The only downside to esbuild I&#39;ve found is that it only goes back to es6, so no IE support...&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;setup&quot; tabindex=&quot;-1&quot;&gt;Setup&lt;/h2&gt;
&lt;p&gt;Let&#39;s start with an empty project to see all that’s required.&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# Create a project folder&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;mkdir&lt;/span&gt; esbuild-eleventy
&lt;span class=&quot;token builtin class-name&quot;&gt;cd&lt;/span&gt; esbuild-eleventy

&lt;span class=&quot;token comment&quot;&gt;# Create an npm project&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;npm&lt;/span&gt; init &lt;span class=&quot;token parameter variable&quot;&gt;-y&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;# Install Eleventy and esbuild&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;npm&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;install&lt;/span&gt; --save-dev @11ty/eleventy esbuild&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;create-some-javascript&quot; tabindex=&quot;-1&quot;&gt;Create some JavaScript&lt;/h2&gt;
&lt;p&gt;Next, create &lt;strong&gt;src/js/app.js&lt;/strong&gt; using some modern JavaScript features,
this is the file we want to bundle.&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;resolve&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;setTimeout&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1000&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; resolve&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;done&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token function&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then create an Eleventy JavaScript class template &lt;strong&gt;scripts.11ty.js&lt;/strong&gt;,
this is in charge of bundling JavaScript for the website.&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; esbuild &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;esbuild&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;NODE_ENV&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;production&#39;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; process&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;env

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; isProduction &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;NODE_ENV&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;production&#39;&lt;/span&gt;

module&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;exports &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token literal-property property&quot;&gt;permalink&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token literal-property property&quot;&gt;eleventyExcludeFromCollections&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;render&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; esbuild&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;build&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token literal-property property&quot;&gt;entryPoints&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;src/js/app.js&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token literal-property property&quot;&gt;bundle&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token literal-property property&quot;&gt;minify&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; isProduction&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token literal-property property&quot;&gt;outdir&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;_site/js&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token literal-property property&quot;&gt;sourcemap&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;isProduction&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token literal-property property&quot;&gt;target&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; isProduction &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;es6&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;esnext&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;details&gt;
&lt;summary&gt;A Note on JSX&lt;/summary&gt;
&lt;p&gt;If you want to use JSX,
like in &lt;a href=&quot;https://blog.r0b.io/post/using-jsx-without-react/&quot;&gt;Using jsx WITHOUT React&lt;/a&gt;,
you can add these parameters to the &lt;code&gt;esbuild.build&lt;/code&gt; call:&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;jsxFactory&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;createElement&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;jsxFragment&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&#39;fragment&#39;&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/details&gt;
&lt;p&gt;Similar to compiling Sass, this is an Eleventy class-based JavaScript template,
that exports a class which Eleventy will instantiate.
Eleventy will call the data method first, which is the same as the front-matter of a markdown file.
We use the &lt;a href=&quot;https://www.11ty.dev/docs/permalinks/&quot;&gt;permalink&lt;/a&gt;
to tell Eleventy not to create a file for this template, as esbuild does this for us.
Eleventy then calls the render method which we use to call esbuild.&lt;/p&gt;
&lt;p&gt;The esbuild JavaScript API handles outputting files
and we tell it to put them in the same place the Eleventy does.
A benefit of this is that the Eleventy template can be used to bundle multiple JavaScript entry points in one go, by passing &lt;code&gt;entryPoints&lt;/code&gt;.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;See &lt;a href=&quot;https://esbuild.github.io/api/&quot;&gt;esbuild&#39;s API docs&lt;/a&gt;
for more information about the options you can pass here,
like configuring source-maps or minifying code.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;configure-eleventy&quot; tabindex=&quot;-1&quot;&gt;Configure Eleventy&lt;/h2&gt;
&lt;p&gt;Finally, configure Eleventy to use our template by creating &lt;strong&gt;.eleventy.js&lt;/strong&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;module&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function-variable function&quot;&gt;exports&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;eleventyConfig&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;eleventyConfig&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  eleventyConfig&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addWatchTarget&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;./src/js/&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token literal-property property&quot;&gt;templateFormats&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;md&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;11ty.js&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This does two things.
First, it tells Eleventy to watch for file changes in the &lt;strong&gt;src/js&lt;/strong&gt; folder.
So in development, when you edit a JavaScript file Eleventy will rebuild and reload the development server.
Second it tells Eleventy to look for our &lt;code&gt;.11ty.js&lt;/code&gt; templates and load them.&lt;/p&gt;
&lt;h2 id=&quot;build-the-site&quot; tabindex=&quot;-1&quot;&gt;Build the site&lt;/h2&gt;
&lt;p&gt;This site doesn&#39;t have any markdown, but it will still generate our bundled JavaScript.&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;npx eleventy&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You should see the JavaScript generated into the _site folder now, all done! 🎉&lt;/p&gt;
</content
    >
  </entry> 
  <entry>
    <title>Compile Sass with eleventy</title>
    <link
      href="https://blog.r0b.io/post/compile-sass-with-eleventy/"
      rel="alternate"
      type="text/html"
      title="Compile Sass with eleventy"
    />
    <updated>2021-05-25T00:00:00Z</updated>
    <id>https://blog.r0b.io/post/compile-sass-with-eleventy/</id>
    <content
      type="html"
      >&lt;p&gt;Static site generators are great for generating HTML from markdown files.
Eleventy (&lt;a href=&quot;https://www.11ty.dev/&quot;&gt;11ty&lt;/a&gt;) is a simple JavaScript based generator, named after the 11 templating languages it supports.&lt;/p&gt;
&lt;p&gt;Static HTML is great, but websites need CSS too! My go to css preprocessor is Sass,
so I wanted to find a way to compile Sass directly in 11ty projects.
After a bit of experimentation, the best way I found to do this was with
&lt;a href=&quot;https://www.11ty.dev/docs/languages/javascript/#classes&quot;&gt;11ty class templates&lt;/a&gt;.
Using 11ty means there is only one process to worry about
and it auto reload on sass changes.&lt;/p&gt;
&lt;h2 id=&quot;setup&quot; tabindex=&quot;-1&quot;&gt;Setup&lt;/h2&gt;
&lt;p&gt;Let&#39;s start with an empty project to show what’s required:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# Create a project folder&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;mkdir&lt;/span&gt; sass-eleventy
&lt;span class=&quot;token builtin class-name&quot;&gt;cd&lt;/span&gt; sass-eleventy

&lt;span class=&quot;token comment&quot;&gt;# Create an empty npm project&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;npm&lt;/span&gt; init &lt;span class=&quot;token parameter variable&quot;&gt;-y&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;# Install 11ty and sass&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;npm&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;install&lt;/span&gt; —save-dev @11ty/eleventy sass&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;create-some-sass&quot; tabindex=&quot;-1&quot;&gt;Create some sass&lt;/h2&gt;
&lt;p&gt;Next, create &lt;strong&gt;src/sass/theme.scss&lt;/strong&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-scss&quot;&gt;&lt;code class=&quot;language-scss&quot;&gt;&lt;span class=&quot;token property&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;$primary&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; #096e4f&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token selector&quot;&gt;h1 &lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;color&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$primary&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then back at the root of the project create &lt;strong&gt;theme.11ty.js&lt;/strong&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; path &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;path&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; Sass &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;sass&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

module&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;exports &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;SassTemplate&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;permalink&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;/styles.css&#39;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token function&quot;&gt;render&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; Sass&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;renderSync&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token literal-property property&quot;&gt;file&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; path&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;join&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;__dirname&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;./src/sass/theme.scss&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token literal-property property&quot;&gt;includePaths&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;path&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;join&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;__dirname&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;./node_modules&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token literal-property property&quot;&gt;outputStyle&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;compressed&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;css
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is an 11ty &lt;a href=&quot;https://www.11ty.dev/docs/languages/javascript/#classes&quot;&gt;class-based JavaScript template&lt;/a&gt;,
11ty will create an instance of our class and call the data method.
This provides the front matter for the template, similar to the top bit of a markdown file.
We use this to set the
&lt;a href=&quot;https://www.11ty.dev/docs/permalinks/&quot;&gt;permalink&lt;/a&gt;
value to tell 11ty we want to create a file called &lt;code&gt;styles.css&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;11ty then calls &lt;code&gt;render&lt;/code&gt; with the front matter data.
This method returns what we want to put in the file.
Here we use the Sass library to render the input file
and return the css to put into the file.
Pass &lt;code&gt;includePaths&lt;/code&gt; here if you want to import sass from npm dependencies,
like &lt;a href=&quot;https://bulma.io/&quot;&gt;bulma&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;configure-11ty&quot; tabindex=&quot;-1&quot;&gt;Configure 11ty&lt;/h2&gt;
&lt;p&gt;Finally, configure 11ty to use our sass template with an &lt;code&gt;.eleventy.js&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;module&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function-variable function&quot;&gt;exports&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;eleventyConfig&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;eleventyConfig&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  eleventyConfig&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addWatchTarget&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;./sass/&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token literal-property property&quot;&gt;templateFormats&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;md&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;11ty.js&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This does two things.
First, it tells 11ty to watch for file changes in the &lt;code&gt;src/sass&lt;/code&gt; folder.
So in development, when you edit a Sass file 11ty will know to rebuild and reload the development server.
Second it tells 11ty to look for our &lt;code&gt;11ty.js&lt;/code&gt; files, so the sass template gets loaded.&lt;/p&gt;
&lt;h2 id=&quot;build-the-site&quot; tabindex=&quot;-1&quot;&gt;Build the site&lt;/h2&gt;
&lt;p&gt;This site doesn&#39;t have any markdown, but it will still generate our css.&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;npx eleventy&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You should see the &lt;code&gt;style.css&lt;/code&gt; file generated into the &lt;code&gt;_site&lt;/code&gt; folder now, all done! 🎉&lt;/p&gt;
&lt;hr /&gt;
&lt;details&gt;
&lt;summary&gt;Future improvements&lt;/summary&gt;
&lt;p&gt;This setup could be improved, but its a good starting point and fine for lots of setups.&lt;/p&gt;
&lt;p&gt;Things to improve:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Generate sass source maps, this might need Sass to generated in the &lt;code&gt;data&lt;/code&gt; method
and using the &lt;a href=&quot;https://www.11ty.dev/docs/pagination/&quot;&gt;pagination&lt;/a&gt; api to create multiple files.&lt;/li&gt;
&lt;li&gt;It might be improved by using asynchronous &lt;code&gt;data&lt;/code&gt; / &lt;code&gt;render&lt;/code&gt; methods,
rather than using &lt;code&gt;Sass.renderSync&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/details&gt;
</content
    >
  </entry> 
  <entry>
    <title>Quick Array to Map Conversion in JavaScript</title>
    <link
      href="https://blog.r0b.io/post/quick-concise-array-to-map-conversion-in-javascript/"
      rel="alternate"
      type="text/html"
      title="Quick Array to Map Conversion in JavaScript"
    />
    <updated>2021-02-28T19:50:00Z</updated>
    <id>https://blog.r0b.io/post/quick-concise-array-to-map-conversion-in-javascript/</id>
    <content
      type="html"
      >&lt;h1 id=&quot;quick-array-to-map-conversion-in-javascript&quot; tabindex=&quot;-1&quot;&gt;Quick Array to Map Conversion in JavaScript&lt;/h1&gt;
&lt;h2 id=&quot;tldr&quot; tabindex=&quot;-1&quot;&gt;TLDR&lt;/h2&gt;
&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; people &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; id&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;geoff&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; name&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;Geoff&#39;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; id&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;helen&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; name&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;Helen&#39;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; id&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;tim&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; name&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;Tim&#39;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; peopleMap &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Map&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;people&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;item&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;item&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;id&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; item&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;token builtin&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;peopleMap&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;tim&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;// { id: &#39;tim&#39;, name: &#39;Tim&#39; }&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Converting an array to a map is a useful tool to turn an &lt;code&gt;O(n)&lt;/code&gt; operation into &lt;code&gt;O(1)&lt;/code&gt;.
If you&#39;re often looking up a value in an array that isn&#39;t changing, you can optimise it like this.
Instead of looping through the entire array to find a value, you can look up an item by some key.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;It&#39;s worth noting that this &lt;strong&gt;is not&lt;/strong&gt; useful if you only have to do a single lookup of the array,
you should just loop through it in that case.
If you are often looking up values in the same array, that is when this is beneficial.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;how-does-it-work&quot; tabindex=&quot;-1&quot;&gt;How does it work&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map&quot;&gt;JavaScript&#39;s Map&lt;/a&gt;
has been around for a while and is designed for situations where an object has dynamic keys.
The constructor takes an &lt;code&gt;entries&lt;/code&gt; value, which may seem a little strange at first.
This entries value is an array of key-value pairs which it can use to initially fill in the map.&lt;/p&gt;
&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; entries &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;geoff&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; id&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;geoff&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; name&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;Geoff&#39;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;helen&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; id&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;helen&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; name&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;Helen&#39;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;tim&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; id&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;tim&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; name&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;Tim&#39;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Each item is an array where the first subitem is the key and the second is a value.
When passed to the Map constructor, the key will be the index to lookup by and the value is the thing to lookup.&lt;/p&gt;
&lt;p&gt;To quickly create these entries, we can use
&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map&quot;&gt;Array&#39;s map&lt;/a&gt;
method which converts each item of an array into something else, based on a function you provide.
You can use this to easily convert an array of objects into an array of entries.&lt;/p&gt;
&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; people &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; id&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;geoff&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; name&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;Geoff&#39;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; id&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;helen&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; name&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;Helen&#39;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; id&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;tim&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; name&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;Tim&#39;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; entries &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; people&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;object&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;object&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;id&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; object&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The arrow function passed to &lt;code&gt;map&lt;/code&gt; takes an object and converts it into an entry
where the key is the object&#39;s &lt;code&gt;id&lt;/code&gt; and the value is the object itself.&lt;/p&gt;
&lt;p&gt;Putting it all back together again:&lt;/p&gt;
&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;//&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;// These are the objects we want to create a map of&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;//&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; people &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; id&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;geoff&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; name&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;Geoff&#39;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; id&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;helen&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; name&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;Helen&#39;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; id&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;tim&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; name&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;Tim&#39;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;//&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;// This creates a map from our array of people&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;//&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; peopleMap &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Map&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;people&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;item &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;item&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;id&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; item&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;//&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;// Now we can find Tim&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;//&lt;/span&gt;
&lt;span class=&quot;token builtin&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;peopleMap&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;tim&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;// outputs: { id: &#39;tim&#39;, name: &#39;Tim&#39; }&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now you have a map which you can quickly and efficiently lookup from.
I&#39;ve found this especially useful in Vue computed properties.
When you have an array of models that you want to lookup values from,
you can setup a computed property that generates a map.
When the array changes it will automatically recompile the map for you based on the new array.&lt;/p&gt;
</content
    >
  </entry> 
  <entry>
    <title>Running Node.Js as a systemd service</title>
    <link
      href="https://blog.r0b.io/post/running-node-js-as-a-systemd-service/"
      rel="alternate"
      type="text/html"
      title="Running Node.Js as a systemd service"
    />
    <updated>2020-11-16T09:58:50Z</updated>
    <id>https://blog.r0b.io/post/running-node-js-as-a-systemd-service/</id>
    <content
      type="html"
      >&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Systemd&quot;&gt;systemd&lt;/a&gt;
is a software suite that provides an array of system components for &lt;a href=&quot;https://en.wikipedia.org/wiki/Linux&quot; title=&quot;Linux&quot;&gt;Linux&lt;/a&gt; operating systems.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;For us, it lets you run services, start them on boot then check and manage the status of them.
This is useful for making sure a node.js script is running whenever a raspberry pi is running.
To run through this I&#39;ll be installing a node.js app called &lt;code&gt;blinkit&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id=&quot;1---installing-node.js&quot; tabindex=&quot;-1&quot;&gt;1 - Installing Node.js&lt;/h2&gt;
&lt;p&gt;Assuming you&#39;re starting from scratch, say from Raspbian lite, you&#39;ll need to install node first.&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token assign-left variable&quot;&gt;VERSION&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;v10.20.1-linux-armv6l

&lt;span class=&quot;token comment&quot;&gt;# Create and enter a temporary directory&lt;/span&gt;
&lt;span class=&quot;token assign-left variable&quot;&gt;TMP&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;`&lt;/span&gt;mktemp &lt;span class=&quot;token parameter variable&quot;&gt;-d&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;`&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token builtin class-name&quot;&gt;cd&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$TMP&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;# In the temp dir, get the node binaries and extract them&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;curl&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-sLO&lt;/span&gt; https://nodejs.org/dist/latest-v10.x/node-&lt;span class=&quot;token variable&quot;&gt;$VERSION&lt;/span&gt;.tar.gz
&lt;span class=&quot;token function&quot;&gt;tar&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-xzf&lt;/span&gt; node-&lt;span class=&quot;token variable&quot;&gt;$VERSION&lt;/span&gt;.tar.gz
&lt;span class=&quot;token function&quot;&gt;mv&lt;/span&gt; node-&lt;span class=&quot;token variable&quot;&gt;$VERSION&lt;/span&gt; /usr/src/node

&lt;span class=&quot;token comment&quot;&gt;# Link the binaries onto the $PATH&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;ln&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-s&lt;/span&gt; /usr/src/node/bin/node /usr/bin/node
&lt;span class=&quot;token function&quot;&gt;ln&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-s&lt;/span&gt; /usr/src/node/bin/npm /usr/bin/npm
&lt;span class=&quot;token function&quot;&gt;ln&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-s&lt;/span&gt; /usr/src/node/bin/npx /usr/bin/npx

&lt;span class=&quot;token comment&quot;&gt;# Clean the temporary directory&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;rm&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-r&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$TMP&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Feel free to change the version of course, I used this older one as it supports armv6 on the Pi Zero I was installing on.&lt;/p&gt;
&lt;h2 id=&quot;2---setup-a-user&quot; tabindex=&quot;-1&quot;&gt;2 - Setup a user&lt;/h2&gt;
&lt;p&gt;We&#39;ll need a system user that the app will run as, we&#39;ll keep it simple and call them &lt;code&gt;node&lt;/code&gt;.
Then we&#39;ll give them &lt;strong&gt;sudo&lt;/strong&gt; and &lt;strong&gt;gpio&lt;/strong&gt; access (this is a special group on Raspberry Pis)
and create a folder for the app to be in.&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# Create the unix user&lt;/span&gt;
adduser &lt;span class=&quot;token function&quot;&gt;node&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;# Add them to groups&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;usermod&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-aG&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;node&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;usermod&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-aG&lt;/span&gt; gpio &lt;span class=&quot;token function&quot;&gt;node&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;# Create a node-owned folder to put the app in&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;# You call this whatever you like, probably the name of your application&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;mkdir&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-p&lt;/span&gt; /usr/src/blinkit
&lt;span class=&quot;token function&quot;&gt;chown&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-R&lt;/span&gt; node:node /usr/src/blinkit&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;3---checkout-the-the-app&#39;s-repo&quot; tabindex=&quot;-1&quot;&gt;3 - Checkout the the app&#39;s repo&lt;/h2&gt;
&lt;p&gt;Now you need to get the code that you want to run,
and put it in that new folder.&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# Become our new node user&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;su&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;node&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;# Clone the repo&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;git&lt;/span&gt; clone git@github.com:robb-j/blinkit.git /usr/src/blinkit
&lt;span class=&quot;token builtin class-name&quot;&gt;cd&lt;/span&gt; /usr/src/blinkit

&lt;span class=&quot;token comment&quot;&gt;# Install the app&#39;s production dependencies&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;# There could be trouble here if any of your dependencies require extra binaries, like python&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;# You might need to debug you packages and see what they require a bit&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;npm&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;install&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;--production&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;4---create-a-systemctl-service&quot; tabindex=&quot;-1&quot;&gt;4 - Create a systemctl service&lt;/h2&gt;
&lt;p&gt;To run with systemd we need a file which tells systemd how to run and manage our service.
For &lt;code&gt;blinkit&lt;/code&gt; I put this file inside the repo which is pulled down,
so it is available at &lt;code&gt;/usr/src/blinkit/blinkit.service&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;blinkit.service&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[Unit]
Description=blinkit
Documentation=https://github.com/robb-j/blinkit/
After=network.target

[Service]
Type=simple
User=node
ExecStart=/usr/bin/node /usr/src/blinkit/src/cli.js serve
WorkingDirectory=/usr/src/blinkit
Restart=on-failure

[Install]
WantedBy=multi-user.target
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This service file is what systemd uses to know our application exists and what to do with it.
You should change the &lt;code&gt;Description&lt;/code&gt;, &lt;code&gt;Documentation&lt;/code&gt;, &lt;code&gt;ExecStart&lt;/code&gt;
and &lt;code&gt;WorkingDirectory&lt;/code&gt; statements to match your application.&lt;/p&gt;
&lt;p&gt;Next we need to tell systemd about our new service.
We will symlink the service file into the systemd directory,
then restart the daemon and enable and start the new service.&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# Your service name will be what you set `Description` to above&lt;/span&gt;
&lt;span class=&quot;token assign-left variable&quot;&gt;SERVICE&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;blinkit

&lt;span class=&quot;token comment&quot;&gt;# Link the service file into place&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;ln&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-s&lt;/span&gt; /usr/src/blinkit/blinkit.service /lib/systemd/system/blinkit.service

&lt;span class=&quot;token comment&quot;&gt;# Reload the daemon so it knows about the new file&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; systemctl daemon-reload

&lt;span class=&quot;token comment&quot;&gt;# Enable our new service&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; systemctl &lt;span class=&quot;token builtin class-name&quot;&gt;enable&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$SERVICE&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;# Start the service&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; systemctl start &lt;span class=&quot;token variable&quot;&gt;$SERVICE&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now the new service should be running and doing whatever it does.
You can check its output with:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# -f follows the logs in real time&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;# -u reverses the order so its newest first&lt;/span&gt;
journalctl &lt;span class=&quot;token parameter variable&quot;&gt;-fu&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$SERVICE&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you restart your host, the service should now start up during the boot.
Congratulations! 🎉&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;Useful links:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/robb-j/blinkit/&quot;&gt;github.com/robb-j/blinkit&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://nodesource.com/blog/running-your-node-js-app-with-systemd-part-1/&quot;&gt;nodesource.com/blog/running-your-node-js-app-with-systemd-part-1&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content
    >
  </entry> 
  <entry>
    <title>My first generator function</title>
    <link
      href="https://blog.r0b.io/post/my-first-generator-function/"
      rel="alternate"
      type="text/html"
      title="My first generator function"
    />
    <updated>2020-07-25T00:00:00Z</updated>
    <id>https://blog.r0b.io/post/my-first-generator-function/</id>
    <content
      type="html"
      >&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;myFirstGenerator&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I&#39;ve always strayed away from generator functions, they are foregin, different and generally confusing.
From debugging babel-ified code I&#39;ve understood that async code gets
transpilled into generator code, but I never understood what it meant or how they worked.
I found my first use of one and was pretty pleased with myself, so I&#39;m going to share what happened.&lt;/p&gt;
&lt;p&gt;For context, I was building something to make a git repo into a headless cms.
It uses &lt;a href=&quot;https://www.netlifycms.org/&quot;&gt;netlify-cms&lt;/a&gt; to directly edit a git repo then use node.js to serve that content as API.
The bit I was working on was a command that clones the repo,
reads and validates the files,
then puts them into redis to be served by http.&lt;/p&gt;
&lt;p&gt;So why did I use a generator?
The problem I was facing was when reading in files in specific folders
and validating them against a &lt;a href=&quot;https://github.com/ianstormtaylor/superstruct#readme&quot;&gt;structure&lt;/a&gt;.
&lt;strong&gt;But&lt;/strong&gt;, I only wanted to write to redis if all the files are valid.
You can write a nice method to read in a folder using a glob,
validate each file&#39;s frontmatter against a structure and write to redis.
But to seperate out the logic made the code more complex and much more verbose.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;I also used &lt;a href=&quot;https://github.com/ianstormtaylor/superstruct/blob/master/Changelog.md#0100--june-6-2020&quot;&gt;superstruct version 0.10.x&lt;/a&gt;
for the first time and it&#39;s really good&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Say we have this method, which uses a glob pattern (e.g. &lt;code&gt;src/pages/*.md&lt;/code&gt;) to match local files,
read in the markdown frontmatter,
validate it against the struct
and return a tuple of validation errors and parsed records.&lt;/p&gt;
&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token generic-function&quot;&gt;&lt;span class=&quot;token function&quot;&gt;readAndParse&lt;/span&gt;&lt;span class=&quot;token generic class-name&quot;&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
  pattern&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  struct&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Struct&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;Error&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It would be very easy to add extra arguments so that function could also save the resulting records somewhere,
but it couldn&#39;t wait until other calls of the function have also validated.&lt;/p&gt;
&lt;p&gt;I wondered if I could define what I want it to do as data, then run it more manually:&lt;/p&gt;
&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; contentToParse &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; pattern&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;pages/*.md&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; key&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;pages&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; struct&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; PageStruct &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; pattern&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;events/*.md&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; key&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;events&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; struct&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; EventStruct &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; pattern&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;tags/*.md&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; key&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;tags&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; struct&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; TagStruct &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; pattern&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;posts/*.md&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; key&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;posts&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; struct&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; PostStruct &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is when I wondered about generators, was this what they&#39;re useful for?
I had a quick browse through the
&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function*&quot;&gt;MDN docs&lt;/a&gt;,
then came up with this:&lt;/p&gt;
&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// Create a generator for each content type&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; generators &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; contentToParse&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;job&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// In the first run, read and validate files that match the pattern&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;errors&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; records&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;readAndParse&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;job&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;pattern&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; job&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;struct&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;token comment&quot;&gt;// End the first run by yielding the errors&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;yield&lt;/span&gt; errors

  &lt;span class=&quot;token comment&quot;&gt;// In the second run, save the validated records to our redis cache&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; redis&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;job&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;key&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;JSON&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;stringify&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;records&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With a generator function you get an object which you can programatically
tell to run up to the next &lt;code&gt;yield&lt;/code&gt; statement.
This is good for my use case as I can create a generator for each content type
and run each one upto but before saving the records.
From there you can check for errors and then decide to proceed or not.&lt;/p&gt;
&lt;p&gt;As code, it looks like this:&lt;/p&gt;
&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// You can asynchronously run each generator in parallel&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; firstRun &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;all&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;generators&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;gen&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; gen&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;next&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// Then grab all the errors out and flatten into a single array&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; allErrors &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; firstRun&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;result&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; result&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;value&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;flat&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now I could exit early if there are any errors,
or if there aren&#39;t any continue on:&lt;/p&gt;
&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// Run the generators again, which will continue on after the `yield` above&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;all&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;generators&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;gen&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; gen&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;next&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And it works!
I&#39;m sure generators are much more powerful than my little use case,
but I nerdily enjoyed my first encounter with them.&lt;/p&gt;
</content
    >
  </entry> 
  <entry>
    <title>Automating developer operations for Node.js</title>
    <link
      href="https://blog.r0b.io/post/automating-developer-operations-for-nodejs/"
      rel="alternate"
      type="text/html"
      title="Automating developer operations for Node.js"
    />
    <updated>2020-04-10T00:00:00Z</updated>
    <id>https://blog.r0b.io/post/automating-developer-operations-for-nodejs/</id>
    <content
      type="html"
      >&lt;p&gt;Dev agility is all about getting your code into production as fast as possible
and turning the time you invested into product value.
The faster you can securely and reliably get your code into the hands of your users, the better.&lt;/p&gt;
&lt;p&gt;One part of this is packaging up code into containers, ready to be deployed on servers.
Containers are great at defining the exact environment to run code in
and automating the creation of them means you can get code live faster.&lt;/p&gt;
&lt;p&gt;Automating this process means less time manually running tests and building containers.
To do this you can hook into the git flow you&#39;re already using.&lt;/p&gt;
&lt;p&gt;The workflow is as follows:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;structure commits to describe changes to the application&lt;/li&gt;
&lt;li&gt;semantically version the app based on those commits&lt;/li&gt;
&lt;li&gt;[not covered] automatically build container images based on those versions&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;There are a couple of npm packages to help with this.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.npmjs.com/package/yorkie&quot;&gt;yorkie&lt;/a&gt; lets you define scripts that run on when you interact with git&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.npmjs.com/package/@commitlint/cli&quot;&gt;@commitlint/cli&lt;/a&gt; is a utility for linting commit messages&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.npmjs.com/package/standard-version&quot;&gt;standard-version&lt;/a&gt; analyses commits to automatically version your app and generate changelogs&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;semantic-versioning&quot; tabindex=&quot;-1&quot;&gt;Semantic versioning&lt;/h2&gt;
&lt;p&gt;This workflow uses semantic versioning to denote the changes to the application.
From this you can see the relation between different versions
and work out what changes are safe to be deployed.&lt;/p&gt;
&lt;p&gt;There are three parts to a semantic version &lt;code&gt;major.minor.patch&lt;/code&gt;.
If the application is a &lt;strong&gt;major&lt;/strong&gt; change, e.g. 1.2.3 to 2.0.0,
you know there is some extra work needed to migrate a deployment.
If there is a &lt;strong&gt;minor&lt;/strong&gt; or &lt;strong&gt;patch&lt;/strong&gt; change, e.g. going from 2.0.0 to 2.0.1 or 2.1.0
you know it&#39;s safe to deploy the new version
as it (should) only contain backwards compatable changes.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&quot;https://semver.org/&quot;&gt;More about Semantic versioning →&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;conventional-commits&quot; tabindex=&quot;-1&quot;&gt;Conventional commits&lt;/h2&gt;
&lt;p&gt;Conventional commits are a standard format for commit messages that describe the type of changes made.
It also forces commits towards atomic single-concern commits,
i.e. only working on one bug/change/feature at a time.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;fix: stop user from dropping users table&lt;/code&gt; is a backwards-compatible fix and generates a semver &lt;strong&gt;patch&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;feat: add new fancy buttons&lt;/code&gt; is a backwards-compatible enhancement and triggers a semver &lt;strong&gt;minor&lt;/strong&gt; version&lt;/li&gt;
&lt;li&gt;&lt;code&gt;BREAKING CHANGE: redesign login form&lt;/code&gt; is a breaking change and triggers a &lt;strong&gt;major&lt;/strong&gt; semver change&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You can also supply a commit body and/or footer to add more info to your commit.
Here&#39;s a full example:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;BREAKING CHANGE: redesign login form

We reviewed user feedback and moved all the buttons around
to make the login flow as easy as possible

resolves #3
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I find the footer works nicely with
&lt;a href=&quot;https://help.github.com/en/enterprise/2.16/user/github/managing-your-work-on-github/closing-issues-using-keywords&quot;&gt;github&#39;s fix/resolves syntax&lt;/a&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&quot;https://www.conventionalcommits.org/en/v1.0.0-beta.2/&quot;&gt;More info on conventional commits →&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;standard-version&quot; tabindex=&quot;-1&quot;&gt;Standard version&lt;/h2&gt;
&lt;p&gt;The final piece of the puzzle is standard version,
it is a command that parses git commits to work out whats changed since the last version.
Because the commits are &amp;quot;conventional&amp;quot; it can work out what the next semantic version of the app should be
and it can accurately generate a changelog from commit messages.&lt;/p&gt;
&lt;p&gt;For example, if you&#39;d commited three &lt;code&gt;fix:&lt;/code&gt;-es and a one &lt;code&gt;feat:&lt;/code&gt;
it would increase the semver&#39;s &lt;strong&gt;minor&lt;/strong&gt; part.
It&#39;ll then go away and generate a changelog with those commit messages
and run the &lt;a href=&quot;https://docs.npmjs.com/cli/version&quot;&gt;npm version&lt;/a&gt;
command to increament that &lt;strong&gt;minor&lt;/strong&gt; version.
It creates a commit for those changes and tags it with the new version.&lt;/p&gt;
&lt;p&gt;When you push up a tag you then have a pipeline of your choice to build a container image.&lt;/p&gt;
&lt;h2 id=&quot;the-process&quot; tabindex=&quot;-1&quot;&gt;The process&lt;/h2&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# Commit your atomic change&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;git&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt; somefile.txt
&lt;span class=&quot;token function&quot;&gt;git&lt;/span&gt; commit &lt;span class=&quot;token parameter variable&quot;&gt;-m&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;feat: add that really cool thing you wanted&quot;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;# Generate a new version&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;npm&lt;/span&gt; run release&lt;/code&gt;&lt;/pre&gt;
&lt;details&gt;
&lt;summary&gt;Or a more complex example:&lt;/summary&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;git&lt;/span&gt; checkout &lt;span class=&quot;token parameter variable&quot;&gt;-b&lt;/span&gt; feature-branch

&lt;span class=&quot;token comment&quot;&gt;# work on a feature ...&lt;/span&gt;

&lt;span class=&quot;token function&quot;&gt;git&lt;/span&gt; commit &lt;span class=&quot;token parameter variable&quot;&gt;-m&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;fix: add check for divide by zero&quot;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;# some sort of code review ...&lt;/span&gt;

&lt;span class=&quot;token function&quot;&gt;git&lt;/span&gt; checkout master
&lt;span class=&quot;token function&quot;&gt;git&lt;/span&gt; merge feature-branch
&lt;span class=&quot;token function&quot;&gt;npm&lt;/span&gt; run release
&lt;span class=&quot;token function&quot;&gt;git&lt;/span&gt; push --follow-tags origin master

&lt;span class=&quot;token comment&quot;&gt;# ci magic to build image ...&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;Most of these can be done inside your favourite IDE&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/details&gt;
&lt;h2 id=&quot;project-setup&quot; tabindex=&quot;-1&quot;&gt;Project setup&lt;/h2&gt;
&lt;p&gt;Start by adding these dependencies&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;npm&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;install&lt;/span&gt; --save-dev &lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;
  yorkie &lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;
  @commitlint/cli &lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;
  @commitlint/config-conventional &lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;
  standard-version&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then add this to your &lt;strong&gt;package.json&lt;/strong&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;token property&quot;&gt;&quot;commitlint&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;extends&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;@commitlint/config-conventional&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token property&quot;&gt;&quot;gitHooks&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;commit-msg&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;commitlint -e $HUSKY_GIT_PARAMS&quot;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;First it sets up commit lint to ensure your commits meet the conventional commits standard.
This means you can&#39;t commit unless you&lt;/p&gt;
&lt;p&gt;Second it sets up a git hook to run commitlint on &lt;code&gt;commit-msg&lt;/code&gt;,
which means your commit will fail if it doens&#39;t meet the standard.&lt;/p&gt;
&lt;p&gt;Next add this script to your &lt;strong&gt;package.json&lt;/strong&gt;.&lt;/p&gt;
&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;token property&quot;&gt;&quot;release&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;standard-version&quot;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now you can perform a release with &lt;a href=&quot;https://docs.npmjs.com/cli/run-script&quot;&gt;npm run&lt;/a&gt;.
Like this:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;npm&lt;/span&gt; run release&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Thats it.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Write commit messages that are &lt;a href=&quot;https://www.conventionalcommits.org/en/v1.0.0-beta.2/&quot;&gt;conventional commits&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;npm run release&lt;/code&gt; to version releases and generate changelogs&lt;/li&gt;
&lt;li&gt;Next setup a CI/CD to generate a docker image based on git tags&lt;/li&gt;
&lt;/ul&gt;
</content
    >
  </entry> 
  <entry>
    <title>Tales from the bashrc: d1</title>
    <link
      href="https://blog.r0b.io/post/tales-from-the-bashrc-d1/"
      rel="alternate"
      type="text/html"
      title="Tales from the bashrc: d1"
    />
    <updated>2020-03-28T00:00:00Z</updated>
    <id>https://blog.r0b.io/post/tales-from-the-bashrc-d1/</id>
    <content
      type="html"
      >&lt;pre&gt;&lt;code&gt;alias d1=&#39;docker container run -it --rm&#39;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Sometimes you just want to pop into a container to see whats in there,
run an arbitrary command or maybe avoid something entering your bash history.
I made it nice and short and pass a couple of params, from the &lt;code&gt;--help&lt;/code&gt; page:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;-i, --interactive    Keep STDIN open even if not attached
-t, --tty            Allocate a pseudo-TTY
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The first gotcha of &lt;code&gt;docker container run&lt;/code&gt; is the it doesn&#39;t start interactively,
any input you enter after starting a container doesn&#39;t get passed to it.
&lt;code&gt;-i&lt;/code&gt; and &lt;code&gt;-t&lt;/code&gt; solves this by attaching STDIN of your terminal to the container.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;--rm                 Automatically remove the container when it exits
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The second bit is &lt;code&gt;--rm&lt;/code&gt;, this one is more of a preemtive housecleaning.
If you&#39;ve been running containers for a while you&#39;ll realise how quickly they build up.
If you use &lt;code&gt;--rm&lt;/code&gt; it automatically removes the container after it finishes,
which is perfect for these little ephemeral containers.&lt;/p&gt;
&lt;p&gt;Having this means there are less stopped containers on your system
and also less unused images too.
Images will stick around while they have a container attached to them.&lt;/p&gt;
&lt;p&gt;For more maintenannce you can always do a &lt;code&gt;docker system prune&lt;/code&gt;
which picks up these things too.&lt;/p&gt;
&lt;h2 id=&quot;examples&quot; tabindex=&quot;-1&quot;&gt;Examples&lt;/h2&gt;
&lt;p&gt;Here are a couple of things I regularly use this for:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# Start a node.js repl using node.js version 12 in alpine&lt;/span&gt;
d1 node:12-alpine

&lt;span class=&quot;token comment&quot;&gt;# Start (b)ash in nginx to inspect the default config / filesystem&lt;/span&gt;
d1 nginx:1-alpine ash

&lt;span class=&quot;token comment&quot;&gt;# Run an unsecure local mongo database&lt;/span&gt;
d1 &lt;span class=&quot;token parameter variable&quot;&gt;-p&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;27017&lt;/span&gt;:27017 mongo:3

&lt;span class=&quot;token comment&quot;&gt;# Quickly serve a directory using nginx, this could be another alias ;)&lt;/span&gt;
d1 &lt;span class=&quot;token parameter variable&quot;&gt;-p&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;80&lt;/span&gt;:80 &lt;span class=&quot;token parameter variable&quot;&gt;-v&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token builtin class-name&quot;&gt;pwd&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;`&lt;/span&gt;&lt;/span&gt;:/usr/share/nginx/html nginx:1-alpine&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That&#39;s &lt;code&gt;d1&lt;/code&gt; – a simple alias which I actually use every day.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;alias d1=&#39;docker container run -it --rm&#39;
&lt;/code&gt;&lt;/pre&gt;
</content
    >
  </entry> 
  <entry>
    <title>Useful Raspberry Pi WiFi commands</title>
    <link
      href="https://blog.r0b.io/post/useful-rpi-wifi-commands/"
      rel="alternate"
      type="text/html"
      title="Useful Raspberry Pi WiFi commands"
    />
    <updated>2020-03-21T00:00:00Z</updated>
    <id>https://blog.r0b.io/post/useful-rpi-wifi-commands/</id>
    <content
      type="html"
      >&lt;p&gt;I keep needing these commands when doing pi things,
so I thought I&#39;d put them here in one place:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# Edit the wifi config file&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;# e.g. to add a new router&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;nano&lt;/span&gt; /etc/wpa_supplicant/wpa_supplicant.conf

&lt;span class=&quot;token comment&quot;&gt;# Reconfigure the wifi on the pi, reloading the config&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; wpa_cli &lt;span class=&quot;token parameter variable&quot;&gt;-i&lt;/span&gt; wlan0 reconfigure

&lt;span class=&quot;token comment&quot;&gt;# Check the status of the wifi connection&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; wpa_cli &lt;span class=&quot;token parameter variable&quot;&gt;-i&lt;/span&gt; wlan0 status&lt;/code&gt;&lt;/pre&gt;
</content
    >
  </entry> 
  <entry>
    <title>Spoofing a Raspberry Pi&#39;s mac address</title>
    <link
      href="https://blog.r0b.io/post/spoofing-an-rpi-mac-address/"
      rel="alternate"
      type="text/html"
      title="Spoofing a Raspberry Pi&#39;s mac address"
    />
    <updated>2020-02-22T00:00:00Z</updated>
    <id>https://blog.r0b.io/post/spoofing-an-rpi-mac-address/</id>
    <content
      type="html"
      >&lt;p&gt;It&#39;s surprisingly simple to make a raspberry pi appear as a different mac address on a network.&lt;/p&gt;
&lt;p&gt;Why you want to spoof it isn&#39;t important, all you need to do is modify &lt;code&gt;/boot/cmdline.txt&lt;/code&gt;
by appending:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;smsc95xx.macaddr=aa:bb:cc:dd:ee:ff
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then reboot your pi (&lt;code&gt;sudo reboot&lt;/code&gt;) and it will force it to appear as that mac address.&lt;/p&gt;
</content
    >
  </entry> 
  <entry>
    <title>Tales from the bashrc: bashrc</title>
    <link
      href="https://blog.r0b.io/post/tales-from-the-bashrc-bashrc/"
      rel="alternate"
      type="text/html"
      title="Tales from the bashrc: bashrc"
    />
    <updated>2020-02-13T00:00:00Z</updated>
    <id>https://blog.r0b.io/post/tales-from-the-bashrc-bashrc/</id>
    <content
      type="html"
      >&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token builtin class-name&quot;&gt;alias&lt;/span&gt; &lt;span class=&quot;token assign-left variable&quot;&gt;bashrc&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;nano ~/.bashrc &amp;amp;&amp;amp; source ~/.bashrc&#39;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I&#39;m that lazy, but I&#39;m quite happy with this.
It simply edits my bashrc and automatically reloads the config into the current terminal.
I think I just found it faffy to edit the right file on mac,
only to try the thing I changed and realised I forgot to source the file too.&lt;/p&gt;
&lt;p&gt;It also works for zshrc too, although it does take a bit longer to re-source a zshrc
(maybe because I&#39;m using oh-my-zsh).&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token builtin class-name&quot;&gt;alias&lt;/span&gt; &lt;span class=&quot;token assign-left variable&quot;&gt;zshrc&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;nano ~/.zshrc &amp;amp;&amp;amp; source ~/.zshrc&#39;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Just be careful editing this alias iteself.
I got into a bad loop when trying this where it would constantly keep opening nano every time I closed it.&lt;/p&gt;
</content
    >
  </entry> 
  <entry>
    <title>Connecting a Raspberry pi to 802.1x WiFi</title>
    <link
      href="https://blog.r0b.io/post/connecting-an-rpi-to-802.1x/"
      rel="alternate"
      type="text/html"
      title="Connecting a Raspberry pi to 802.1x WiFi"
    />
    <updated>2020-02-03T00:00:00Z</updated>
    <id>https://blog.r0b.io/post/connecting-an-rpi-to-802.1x/</id>
    <content
      type="html"
      >&lt;p&gt;First you&#39;ll want to connect your pi either via ethernet,
an actual keyboard &amp;amp; monitor
or &lt;a href=&quot;https://desertbot.io/blog/ssh-into-pi-zero-over-usb&quot;&gt;ssh-over-usb&lt;/a&gt; if you&#39;re fancy.
All the wifi config on a pi is setup in &lt;code&gt;/etc/wpa_supplicant/wpa_supplicant.conf&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;First though, you need to generate a hash of your password,
so your password isn&#39;t stored in plaintext on the
&lt;a href=&quot;https://www.raspberrypi-spy.co.uk/2014/08/how-to-reset-a-forgotten-raspberry-pi-password/&quot;&gt;very-hackable&lt;/a&gt;
pi.&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# ssh you@yourpi.local&lt;/span&gt;
&lt;span class=&quot;token builtin class-name&quot;&gt;read&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-s&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-p&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Password: &quot;&lt;/span&gt; pass &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;token builtin class-name&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-n&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$pass&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;iconv&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-t&lt;/span&gt; utf16le &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; openssl md4 &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;sed&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;s/(stdin)= //&#39;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;# then type your password and hit enter and you&#39;ll get:&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;# Password: abcdef123abcdef123abcdef123&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;# From now on $PASSWORD will be this hash&lt;/span&gt;
&lt;span class=&quot;token assign-left variable&quot;&gt;PASSWORD&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;abcdef123abcdef123abcdef123

&lt;span class=&quot;token comment&quot;&gt;# clear the $pass variable if you&#39;re paranoid ;)&lt;/span&gt;
&lt;span class=&quot;token builtin class-name&quot;&gt;unset&lt;/span&gt; pass&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now you can start editing your &lt;code&gt;wpa_supplicant.conf&lt;/code&gt; file:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;nano&lt;/span&gt; /etc/wpa_supplicant/wpa_supplicant.conf&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;First make sure it contains these values at the top,
setting the country code to wherever the pi is.
In newer pis it needs this to be set for wifi to work&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev
update_config=1
country=GB
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then add a network block for the connection you want to conenct to.
Where &lt;code&gt;$SSID&lt;/code&gt; is the name of the router,
&lt;code&gt;$USERNAME&lt;/code&gt; is your username for the wifi
and &lt;code&gt;$PASSWORD&lt;/code&gt; is the hash from above.
Be careful with the quotes!&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;network = {
	ssid=&amp;quot;$SSID&amp;quot;
	proto=RSN
	key_mgmt=WPA-EAP
	pairwise=CCMP
	auth_alg=OPEN
	eap=PEAP
	phase2=&amp;quot;auth=MSCHAPV2&amp;quot;
	identity=&amp;quot;$USERNAME&amp;quot;
	password=hash:$PASSWORD
}
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;These work for the 802.1x wifi I&#39;m connecting to&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Now you can use the &lt;code&gt;wpa_cli&lt;/code&gt; to restart the pi&#39;s network interface.&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# Tell it to reconfigure itself with the changes from wpa_supplicant.conf&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; wpa_cli &lt;span class=&quot;token parameter variable&quot;&gt;-i&lt;/span&gt; wlan0 reconfigure

&lt;span class=&quot;token comment&quot;&gt;# You can check on what its doing with this:&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; wpa_cli &lt;span class=&quot;token parameter variable&quot;&gt;-i&lt;/span&gt; wlan0 status

&lt;span class=&quot;token comment&quot;&gt;# You can also check for an ip with&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;ifconfig&lt;/span&gt; wlan0

&lt;span class=&quot;token comment&quot;&gt;# And you can check its working with this&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;nslookup&lt;/span&gt; duck.com&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now your pi&#39;s on the internet! 🥳&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Credit to &lt;a href=&quot;https://openlab.ncl.ac.uk/people/dan-jackson/&quot;&gt;Dan Jackson&lt;/a&gt;
for the required arguments and password hash generator.&lt;/p&gt;
&lt;/blockquote&gt;
</content
    >
  </entry> 
  <entry>
    <title>Tales from the bashrc: npr</title>
    <link
      href="https://blog.r0b.io/post/tales-from-the-bashrc-npr/"
      rel="alternate"
      type="text/html"
      title="Tales from the bashrc: npr"
    />
    <updated>2020-01-27T00:00:00Z</updated>
    <id>https://blog.r0b.io/post/tales-from-the-bashrc-npr/</id>
    <content
      type="html"
      >&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token builtin class-name&quot;&gt;alias&lt;/span&gt; &lt;span class=&quot;token assign-left variable&quot;&gt;npr&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;npm run -s --&#39;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I added a new alias to my .&lt;code&gt;bashrc&lt;/code&gt; today.
I was fed up with npm&#39;s run command,
it does two things that were annoying me.&lt;/p&gt;
&lt;h2 id=&quot;needlessly-noisy&quot; tabindex=&quot;-1&quot;&gt;Needlessly noisy&lt;/h2&gt;
&lt;p&gt;The first thing is that it&#39;s noisy.
It spews out lots of information that I&#39;ve never found useful
and distracts from the real error(s).&lt;/p&gt;
&lt;p&gt;For instance, running &lt;code&gt;npm run lint&lt;/code&gt; in this repo spews out all this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;gt; @robb_j/r0b-blog@1.0.0 lint /Users/rob/dev/r0b/blog
&amp;gt; eslint

sh: eslint: command not found
npm ERR! code ELIFECYCLE
npm ERR! syscall spawn
npm ERR! file sh
npm ERR! errno ENOENT
npm ERR! @robb_j/r0b-blog@1.0.0 lint: `eslint`
npm ERR! spawn ENOENT
npm ERR!
npm ERR! Failed at the @robb_j/r0b-blog@1.0.0 lint script.
npm ERR! This is probably not a problem with npm. There is likely additional logging output above.

npm ERR! A complete log of this run can be found in:
npm ERR!     /Users/rob/.npm/_logs/2020-01-14T20_35_05_835Z-debug.log
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Whereas &lt;code&gt;npm run -s lint&lt;/code&gt; goes straight to the error:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sh: eslint: command not found
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;option-stealing&quot; tabindex=&quot;-1&quot;&gt;Option stealing&lt;/h2&gt;
&lt;p&gt;The second annoying thing is that it steals dash-dash options,
which gets infuriating when you&#39;re making a command-line tool.&lt;/p&gt;
&lt;p&gt;If you ran &lt;code&gt;npm run cli --help&lt;/code&gt;, the --help doesn&#39;t make it to your application,
it instead shows the help for &lt;code&gt;npm run&lt;/code&gt;.
This is fixed by adding &lt;code&gt;--&lt;/code&gt; after &lt;code&gt;run&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id=&quot;the-alias&quot; tabindex=&quot;-1&quot;&gt;The alias&lt;/h2&gt;
&lt;p&gt;So I created npr, which combines these two things into a new smaller command.
It&#39;s not going to change the world, but it makes my life a tiny bit easier.&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token builtin class-name&quot;&gt;alias&lt;/span&gt; &lt;span class=&quot;token assign-left variable&quot;&gt;npr&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;npm run -s --&#39;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
</content
    >
  </entry> 
  <entry>
    <title>A minimal kiosk mode for a Raspberry Pi</title>
    <link
      href="https://blog.r0b.io/post/minimal-rpi-kiosk/"
      rel="alternate"
      type="text/html"
      title="A minimal kiosk mode for a Raspberry Pi"
    />
    <updated>2020-01-20T00:00:00Z</updated>
    <id>https://blog.r0b.io/post/minimal-rpi-kiosk/</id>
    <content
      type="html"
      >&lt;p&gt;When I was making my &lt;a href=&quot;https://twitter.com/Robbb_J/status/834487521442668545?s=20&quot;&gt;Smart mirror&lt;/a&gt;
I made a webapp to run on the Raspberry Pi hidden behind the frame.
From all the tutorials I saw,
they reccomended using a full instillation of the Raspbian desktop,
then installing chrome, then hacking away at it.
I didn&#39;t like this.&lt;/p&gt;
&lt;p&gt;I felt this was a waste as all I wanted was to run chrome.
I didn&#39;t need a window manager,
or any of the countless pre-installed apps that come with raspbian.
I wanted a minimal footprint on the pi, with hopefully only chrome.
This means less to manage and a smaller security footprint.&lt;/p&gt;
&lt;p&gt;Then I found &lt;a href=&quot;https://www.sylvaindurand.org/launch-chromium-in-kiosk-mode/&quot;&gt;this blog post&lt;/a&gt;,
which promised to do all of that.&lt;/p&gt;
&lt;h2 id=&quot;steps&quot; tabindex=&quot;-1&quot;&gt;Steps&lt;/h2&gt;
&lt;p&gt;Start with &lt;a href=&quot;https://www.raspberrypi.org/downloads/raspbian/&quot;&gt;Raspbian Lite&lt;/a&gt;
(&lt;strong&gt;not&lt;/strong&gt; the desktop version).
Then install the packages required to run chromium
and set the pi to boot straight into to the console.&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;apt-get&lt;/span&gt; update &lt;span class=&quot;token parameter variable&quot;&gt;-qq&lt;/span&gt;

&lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;apt-get&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;install&lt;/span&gt; --no-install-recommends xserver-xorg-video-all &lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;
  xserver-xorg-input-all xserver-xorg-core xinit x11-xserver-utils &lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;
  chromium-browser unclutter

&lt;span class=&quot;token comment&quot;&gt;# Go to: Boot Options &gt; Console Autologin&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; raspi-config&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;19/10/25&lt;/strong&gt; — On Debian 13+ &lt;code&gt;chromium-browser&lt;/code&gt; seems to have been renamed to just &lt;code&gt;chromium&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Next edit &lt;code&gt;/home/pi/.bash_profile&lt;/code&gt; to automatically start the gui.
There&#39;s a check for the bash context first,
so you don&#39;t accidentally start chromium whenever you ssh in.&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-z&lt;/span&gt; &lt;span class=&quot;token environment constant&quot;&gt;$DISPLAY&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;tty&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;)&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; /dev/tty1 &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;then&lt;/span&gt;
  startx
&lt;span class=&quot;token keyword&quot;&gt;fi&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The last bit is to setup &lt;code&gt;/home/pi/.xinitrc&lt;/code&gt; to run chromium whenever you run startx.
Here&#39;s the full list of &lt;a href=&quot;https://peter.sh/experiments/chromium-command-line-switches/&quot;&gt;chromium arguments&lt;/a&gt;.&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token shebang important&quot;&gt;#!/usr/bin/env sh&lt;/span&gt;
xset &lt;span class=&quot;token parameter variable&quot;&gt;-dpms&lt;/span&gt;
xset s off
xset s noblank

unclutter &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt;
chromium-browser https://yourfancywebsite.com &lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;
  --window-size&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1920,1080&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;
  --window-position&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0,0&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;
  --start-fullscreen &lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;
  &lt;span class=&quot;token parameter variable&quot;&gt;--kiosk&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;
  &lt;span class=&quot;token parameter variable&quot;&gt;--incognito&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;
  &lt;span class=&quot;token parameter variable&quot;&gt;--noerrdialogs&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;
  --disable-translate &lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;
  --no-first-run &lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;
  &lt;span class=&quot;token parameter variable&quot;&gt;--fast&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;
  --fast-start &lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;
  --disable-infobars &lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;
  --disable-features&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;TranslateUI &lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;
  --disk-cache-dir&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;/dev/null &lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;
  --overscroll-history-navigation&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;
  --disable-pinch&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It disables the cursor and screensaver.
Then runs chromium with *all* of the flags.
Set &lt;code&gt;https://yourfancywebsite.com&lt;/code&gt; to the website which you want to display.
And set &lt;code&gt;--window-size&lt;/code&gt; to the size of your display (it&#39;s horizontal first and vertical after the comma).&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;You may also want to uncomment &lt;code&gt;disable_overscan=1&lt;/code&gt; in &lt;code&gt;/boot/config.txt&lt;/code&gt;
so that the pi boots up using the full display.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Now whenever the pi boots up it&#39;ll go into the console then on into chromium.
If you want to exit you can hit &lt;code&gt;Alt+F4&lt;/code&gt;, then enter &lt;code&gt;startx&lt;/code&gt; to start up the browser again.&lt;/p&gt;
</content
    >
  </entry> 
  <entry>
    <title>Using jsx WITHOUT React</title>
    <link
      href="https://blog.r0b.io/post/using-jsx-without-react/"
      rel="alternate"
      type="text/html"
      title="Using jsx WITHOUT React"
    />
    <updated>2020-01-12T00:00:00Z</updated>
    <id>https://blog.r0b.io/post/using-jsx-without-react/</id>
    <content
      type="html"
      >&lt;p&gt;From my experience, jsx has always been synonomous with React.
But jsx can in fact be used without it.
Jsx lets you write xml structures in javascript and apply your own meaning to them.
You can see Facebook&#39;s &lt;a href=&quot;https://facebook.github.io/jsx/&quot;&gt;specification here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Jsx is not meant to be implemented as part of the Ecmascript specification,
but rather used by preprocessors (like &lt;a href=&quot;https://babeljs.io/&quot;&gt;babel&lt;/a&gt;)
to transform jsx code to native javascript code.
Much like typescript is transpilled into javascript.&lt;/p&gt;
&lt;h2 id=&quot;what-jsx-does&quot; tabindex=&quot;-1&quot;&gt;What jsx does&lt;/h2&gt;
&lt;p&gt;Jsx is passed to babel and it converts your xml into javascript function calls.
You pass a &lt;code&gt;pragma&lt;/code&gt; option to babel which tells it how to process your xml.
For instance if you set pragma to &lt;code&gt;createElement&lt;/code&gt; and run this script through it:&lt;/p&gt;
&lt;pre class=&quot;language-jsx&quot;&gt;&lt;code class=&quot;language-jsx&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; myDocument &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;p&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;Hello, world&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;p&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Babel would convert your code into:&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; myDocument &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;createElement&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;p&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;name&#39;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;Hello, world&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Which has nicely transformed our code into something a browser (or node.js) will understand.
We still have to craft our &lt;code&gt;createElement&lt;/code&gt; though.
In React this is a method which creates components,
but we can make it do whatever we want.&lt;/p&gt;
&lt;h2 id=&quot;pragma-method-signature&quot; tabindex=&quot;-1&quot;&gt;Pragma method signature&lt;/h2&gt;
&lt;p&gt;A pragma method takes two parameters
and a &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/rest_parameters&quot;&gt;rest argument&lt;/a&gt;,
for example:&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token function&quot;&gt;createElement&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;tagNameOrComponent&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; attributes&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;children&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;tagNameOrComponent&lt;/code&gt; is either a string tagName of a native element (like a &lt;code&gt;&amp;lt;p&amp;gt;&lt;/code&gt; or &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt;)
or a variable to something in the scope of your jsx.
Maybe you want to pass a function and call it inside you pragma method.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;attributes&lt;/code&gt; is an object of key-value pairs that you passed to the xml element.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;...children&lt;/code&gt; is a rest argument of any xml children that are inside the parent tag.
They have already been processed by your pragma method.&lt;/p&gt;
&lt;h2 id=&quot;an-example&quot; tabindex=&quot;-1&quot;&gt;An example&lt;/h2&gt;
&lt;p&gt;So now to use this in a real (trivial) example.
First setup a node project and install some dependencies.&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# Create a project folder&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;mkdir&lt;/span&gt; jsx-without-react
&lt;span class=&quot;token builtin class-name&quot;&gt;cd&lt;/span&gt; jsx-without-react

&lt;span class=&quot;token comment&quot;&gt;# Create an npm project&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;npm&lt;/span&gt; init &lt;span class=&quot;token parameter variable&quot;&gt;-y&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;# Install babel&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;npm&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;install&lt;/span&gt; @babel/cli @babel/core @babel/plugin-transform-react-jsx&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Next we&#39;ll need a &lt;code&gt;.babelrc&lt;/code&gt; which will tell babel how to process our jsx.&lt;/p&gt;
&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;plugins&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;@babel/plugin-transform-react-jsx&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token property&quot;&gt;&quot;pragma&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;createElement&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;comments&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And then add an &lt;code&gt;index.jsx&lt;/code&gt; file&lt;/p&gt;
&lt;pre class=&quot;language-jsx&quot;&gt;&lt;code class=&quot;language-jsx&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;createElement&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;tag&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; attributes&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;children&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; tag&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; attributes&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; children &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; myDocument &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;p&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;Hello, world&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;p&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now you can run this command to generate your javascript.&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;npx babel index.jsx &lt;span class=&quot;token parameter variable&quot;&gt;-d&lt;/span&gt; dist&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Which will create a new &lt;code&gt;dist/index.js&lt;/code&gt; file with the transpilled code.&lt;/p&gt;
&lt;h2 id=&quot;an-example-with-dom-elements&quot; tabindex=&quot;-1&quot;&gt;An example with dom elements&lt;/h2&gt;
&lt;p&gt;For a real world example, lets use jsx to create dom elements and render them.
With the same setup as before, lets change our &lt;code&gt;index.jsx&lt;/code&gt; to:&lt;/p&gt;
&lt;pre class=&quot;language-jsx&quot;&gt;&lt;code class=&quot;language-jsx&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// A jsx pragma method to create html dom elements (more info below)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;createElement&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;tagName&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; attrs &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;children&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; elem &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Object&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;assign&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;createElement&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;tagName&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; attrs&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; child &lt;span class=&quot;token keyword&quot;&gt;of&lt;/span&gt; children&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;Array&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;isArray&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;child&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; elem&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;append&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;child&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; elem&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;append&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;child&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; elem
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// Setup some data&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; name &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;Geoff&#39;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; friends &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;Sarah&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;James&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;Hercule&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// Create some dom elements&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; app &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;div&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;className&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;app&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;h1&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;className&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;title&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt; Hello, world! &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;h1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;p&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt; Welcome back, &lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt; &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;p&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;p&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;strong&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;Your friends are:&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;strong&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;p&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;ul&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;friends&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;li&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;li&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;ul&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// Render our dom elements&lt;/span&gt;
window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getElementById&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;app&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;replaceWith&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;app&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;You can add &lt;code&gt;// eslint-disable-next-line no-unused-vars&lt;/code&gt; to ignore annoying eslint errors&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;And to test our code, add an &lt;code&gt;index.html&lt;/code&gt;&lt;/p&gt;
&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;token doctype&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;!&lt;/span&gt;&lt;span class=&quot;token doctype-tag&quot;&gt;DOCTYPE&lt;/span&gt; &lt;span class=&quot;token name&quot;&gt;html&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;html&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;lang&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;en&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;head&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;meta&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;charset&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;UTF-8&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;meta&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;viewport&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;content&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;width=device-width, initial-scale=1.0&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;meta&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;http-equiv&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;X-UA-Compatible&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;content&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;ie=edge&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;title&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;jsx WITHOUT react&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;title&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;link&lt;/span&gt;
      &lt;span class=&quot;token attr-name&quot;&gt;rel&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;stylesheet&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
      &lt;span class=&quot;token attr-name&quot;&gt;href&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;https://cdn.jsdelivr.net/gh/kognise/water.css@latest/dist/light.min.css&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;head&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;body&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;div&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;app&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;script&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;src&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;dist/index.js&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token script&quot;&gt;&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;script&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;body&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;html&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then run the transpiller again and open your &lt;code&gt;index.html&lt;/code&gt; in a browser.
I snuck in &lt;a href=&quot;https://github.com/kognise/water.css&quot;&gt;water.css&lt;/a&gt; to make raw html prettier.&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;npx babel index.jsx &lt;span class=&quot;token parameter variable&quot;&gt;-d&lt;/span&gt; dist&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;my-createelement-function&quot; tabindex=&quot;-1&quot;&gt;My createElement function&lt;/h2&gt;
&lt;p&gt;Here is my &lt;code&gt;createElement&lt;/code&gt; from above, it does a couple of things.&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;createElement&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;tagName&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; attrs &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;children&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; elem &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Object&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;assign&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;createElement&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;tagName&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; attrs&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; child &lt;span class=&quot;token keyword&quot;&gt;of&lt;/span&gt; children&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;Array&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;isArray&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;child&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; elem&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;append&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;child&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; elem&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;append&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;child&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; elem
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;First it creates a dom element with &lt;code&gt;document.createElement&lt;/code&gt;
and assigns the jsx attributes onto it with &lt;code&gt;Object.assign&lt;/code&gt;.
This lets you set most element properties like &lt;code&gt;id&lt;/code&gt; or
&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Element/className&quot;&gt;Element#className&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Next it loops through the child elements,
which have already been generated into dom elements with this function.
With each element it adds them as a child.
I chose &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/ParentNode/append&quot;&gt;Window#append&lt;/a&gt;
because will it create text nodes for you too.
It checks for arrays children too so it can add any nested children,
like the friend&#39;s mapping above:&lt;/p&gt;
&lt;pre class=&quot;language-jsx&quot;&gt;&lt;code class=&quot;language-jsx&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;ul&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;friends&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;li&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;li&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;ul&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;pragmafrag&quot; tabindex=&quot;-1&quot;&gt;pragmaFrag&lt;/h2&gt;
&lt;p&gt;There is another babel parameter which lets you handle
&lt;a href=&quot;https://reactjs.org/docs/fragments.html#short-syntax&quot;&gt;jsx short fragments&lt;/a&gt;.
You can pass &lt;code&gt;pragmaFrag&lt;/code&gt; in your &lt;code&gt;.babelrc&lt;/code&gt; to tells babel how to handle fragments.&lt;/p&gt;
&lt;p&gt;If you setup a &lt;code&gt;.babelrc&lt;/code&gt; like this:&lt;/p&gt;
&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;plugins&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
      &lt;span class=&quot;token string&quot;&gt;&quot;@babel/plugin-transform-react-jsx&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token property&quot;&gt;&quot;pragma&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;createElement&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token property&quot;&gt;&quot;pragmaFrag&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&#39;fragment&#39;&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then you can:&lt;/p&gt;
&lt;pre class=&quot;language-jsx&quot;&gt;&lt;code class=&quot;language-jsx&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;createElement&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;tagName&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; attrs &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;children&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;tagName &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;fragment&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; children
  &lt;span class=&quot;token comment&quot;&gt;// Same as above ...&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; elements &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;p&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;Hello,&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;p&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;p&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;world!&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;p&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It will now pass whatever you set &lt;code&gt;pragmaFrag&lt;/code&gt; to your pragma method,
which you can handle in any way you like.
In this case &lt;code&gt;&#39;fragment&#39;&lt;/code&gt; will get passed to &lt;code&gt;createElement&lt;/code&gt;
which the method handles by returning the child elements.
This can be useful if you don&#39;t want to create too many extra dom elements.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;That&#39;s what I know about using jsx without react.
I&#39;ve found it useful for tidying up small web apps which only need dom access.&lt;/p&gt;
&lt;p&gt;You can find the example at &lt;a href=&quot;https://github.com/robb-j/r0b-blog/tree/master/examples/jsx-without-react&quot;&gt;robb-j/r0b-blog&lt;/a&gt;&lt;/p&gt;
</content
    >
  </entry>
</feed>
