<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
    <channel>
        <title>Dylan Staley</title>
        <link>https://dstaley.com</link>
        <description>Dylan Staley, Web Developer and purveyor of cute puppy pics.</description>
        <generator>Zola</generator>
        <language>en</language>
        <atom:link href="https://dstaley.com/rss.xml" rel="self" type="application/rss+xml"/>
        <lastBuildDate>Sun, 23 Jun 2024 12:00:00 -0700</lastBuildDate>
            <item>
                <title>What&#x27;s On My Desk: 2024 Edition</title>
                <pubDate>Sun, 23 Jun 2024 12:00:00 -0700</pubDate>
                <link>https://dstaley.com/posts/whats-on-my-desk-2024-edition/</link>
                <guid>https://dstaley.com/posts/whats-on-my-desk-2024-edition/</guid>
                <description>The (frankly) absurd lengths I&#x27;ll go through not to push more than one button to switch between my work and personal computers.</description>
                <content:encoded>&lt;p&gt;Next month is my seven-year anniversary of working from home. Since then, I&#x27;ve learned a lot about having a workspace that is functional, minimizes annoyance, and allows me to do my best work. The biggest thing I&#x27;ve learned is that while I really enjoy having a separate work and personal computer, I don&#x27;t like the idea of having separate &lt;em&gt;anything else&lt;&#x2F;em&gt;. The idea of having to switch between a work keyboard and personal keyboard is something that I find deeply unacceptable. I&#x27;m sure it works for some people, but it&#x27;s not something I could manage myself. I want one set of peripherals, shared between two computers. I want to buy high quality hardware just once and get that benefit both personally and professionally with the push of a single button. While it&#x27;s taken a while to get the implementation details down, I think I&#x27;ve finally found something that gets me incredibly close to that ideal. I&#x27;ve been using most of this setup for the past two-and-a-half years and have recently made a few adjustments to eliminate some of the pain points I&#x27;ve run into in the past. This is my best attempt yet at a completely ludicrous work-from-home setup that is easily shared between a personal and a work computer at the push of a button.&lt;&#x2F;p&gt;
&lt;p&gt;The centerpiece of the whole setup is a Level1Techs KVM, specifically the &lt;a href=&quot;https:&#x2F;&#x2F;www.store.level1techs.com&#x2F;products&#x2F;p&#x2F;14-kvm-switch-single-monitor-2computer-64pfg-7l6da&quot;&gt;DisplayPort 1.4 Single Monitor – Two Computer&lt;&#x2F;a&gt; 10Gbps model. This wonderful little piece of hardware allows me to share my 4K 144Hz monitor and USB peripherals between both my work MacBook Pro and my personal desktop. Many other KVMs, especially the more affordable options, omit full DisplayPort 1.4 compatibility, which would limit me to a paltry 60Hz refresh rate. As anyone who has used a high-refresh rate display can attest, it&#x27;s a difficult comfort to give up.&lt;&#x2F;p&gt;


&lt;figure&gt;
&lt;a href=&quot;&#x2F;img&amp;#x2F;kvm.jpg&quot;&gt;
    &lt;img
        alt=&quot;To the left is the Drop BMR1 speaker, which is slender with a white mesh grille. To the right of the speaker is the KVM, sitting on top of a Thunderbolt 3 dock. On top of the KVM is a white marble pen holder, with various pens, one of which displays the Clerk logo.&quot;
        class=&quot;m-0&quot;
        loading=&quot;lazy&quot;
        width=&quot;1600&quot;
        height=&quot;1200&quot;
        src=&quot;&amp;#x2F;&amp;#x2F;dstaley.com&amp;#x2F;processed_images&amp;#x2F;kvm.ba35785fb2b1908c.jpg&quot;
        srcset=&quot;
            &amp;#x2F;&amp;#x2F;dstaley.com&amp;#x2F;processed_images&amp;#x2F;kvm.ba35785fb2b1908c.jpg,
            &amp;#x2F;&amp;#x2F;dstaley.com&amp;#x2F;processed_images&amp;#x2F;kvm.af6406b8a00f702c.jpg 2x
        &quot;
    &#x2F;&gt;
&lt;&#x2F;a&gt;

&lt;figcaption class=&quot;italic text-sm&quot;&gt;My Level1Techs KVM sitting on top of the Dell WD19TBS Thunderbolt 3 dock. On the left is the Drop BMR1 speaker. Also pictured here is an Ecobee room sensor, which helps keep my office at a tolerable temperature.&lt;&#x2F;figcaption&gt;
&lt;&#x2F;figure&gt;
&lt;p&gt;Switching between computers is as easy as pressing a single button, which moves my monitor, keyboard, mouse, microphone, teleprompter, and Stream Deck between computers. If you read &lt;a href=&quot;&#x2F;posts&#x2F;pushing-buttons-with-rust&#x2F;&quot;&gt;my post from a year ago&lt;&#x2F;a&gt;, you&#x27;ll notice an interesting omission: my webcam. While resetting the USB hub every time I switched computers worked, it wasn&#x27;t exactly an elegant solution. That&#x27;s where the recently released Elgato Game Capture Neo comes in.&lt;&#x2F;p&gt;
&lt;p&gt;The &lt;a href=&quot;https:&#x2F;&#x2F;www.elgato.com&#x2F;us&#x2F;en&#x2F;p&#x2F;game-capture-neo&quot;&gt;Game Capture Neo&lt;&#x2F;a&gt; works similarly to the &lt;a href=&quot;https:&#x2F;&#x2F;www.elgato.com&#x2F;us&#x2F;en&#x2F;p&#x2F;cam-link-4k&quot;&gt;Elgato CamLink&lt;&#x2F;a&gt;: it takes in an HDMI signal and presents that to the host computer as a UVC device (which most software understands as a webcam). The neat extra feature it has that the CamLink doesn&#x27;t is HDMI passthrough; since this is designed to capture gaming footage, having the ability to pass the HDMI signal along to a display is useful. For me though, this allows me to connect my HDMI webcam to two computers simultaneously, without having the awkward dance of resetting the USB hub to get the CamLink working when I switch computers. I&#x27;ve used this setup for a week at my new job and it seems to be working reliably. There is one small issue though: the Game Capture Neo advertises a variety of compatible resolutions, both in 4:3 and 16:9 aspect ratios even when the source aspect ratio is 16:9. This means that when software requests a 640x480 resolution, the Game Capture Neo happily squishes my 16:9 video into a 4:3 container. Thankfully I&#x27;ve only run into one application where this is the case, but I work around this using &lt;a href=&quot;https:&#x2F;&#x2F;reincubate.com&#x2F;camo&#x2F;&quot;&gt;Camo&lt;&#x2F;a&gt;. Think of it as a more user-friendly OBS. Camo allows me to request only the 720p output of the Game Capture Neo and is smart enough to correctly scale that when another piece of software requests a 640x480 video stream. I use Camo in applications that expect a 640x480 output from the webcam, and the Game Capture Neo in everything else.&lt;&#x2F;p&gt;
&lt;p&gt;Speaking of video calls, I&#x27;ve also added an &lt;a href=&quot;https:&#x2F;&#x2F;www.elgato.com&#x2F;us&#x2F;en&#x2F;p&#x2F;prompter&quot;&gt;Elgato Prompter&lt;&#x2F;a&gt; to the mix. I really like the idea of being able to make eye contact while on a video call (something that companies like Microsoft are going so far as to &lt;a href=&quot;https:&#x2F;&#x2F;blogs.windows.com&#x2F;devices&#x2F;2020&#x2F;08&#x2F;20&#x2F;make-a-more-personal-connection-with-eye-contact-now-generally-available&#x2F;&quot;&gt;use AI to fake&lt;&#x2F;a&gt;), and the Elgato Prompter allows that by putting a specially designed reflective mirror in front of your webcam. It has a small screen that acts as an external display that you can put your video call on, which allows you to look directly into the camera lens. It&#x27;s not without its faults though; I had to add a &lt;a href=&quot;https:&#x2F;&#x2F;www.amazon.com&#x2F;dp&#x2F;B00XNMXNV0&quot;&gt;circular polarizer&lt;&#x2F;a&gt; to my camera to eliminate a glow that came from the screen, which darkens the image a bit. This is easily rectified with the use of two &lt;a href=&quot;https:&#x2F;&#x2F;www.elgato.com&#x2F;us&#x2F;en&#x2F;p&#x2F;key-light&quot;&gt;Elgato Key Lights&lt;&#x2F;a&gt;, which also help combat the darkness of my home office. The Prompter uses a technology called &lt;a href=&quot;https:&#x2F;&#x2F;www.synaptics.com&#x2F;products&#x2F;displaylink-graphics&quot;&gt;DisplayLink&lt;&#x2F;a&gt;, which allows you to connect displays over non-display protocols like USB and TCP&#x2F;IP; this helps when you have a device that doesn&#x27;t have any available display out ports. I&#x27;ve found that this isn&#x27;t the most reliable thing in the world, but it&#x27;s a small price to pay for the benefit I feel that it brings.&lt;&#x2F;p&gt;


&lt;figure&gt;
&lt;a href=&quot;&#x2F;img&amp;#x2F;light-comparison.jpg&quot;&gt;
    &lt;img
        alt=&quot;Side by side comparison photo of me. The left is darker, with half of my face in shadow. The right is brighter and more colorful, especially in the background. I am wearing a black t-shirt with the Clerk wordmark.&quot;
        class=&quot;m-0&quot;
        loading=&quot;lazy&quot;
        width=&quot;1600&quot;
        height=&quot;900&quot;
        src=&quot;&amp;#x2F;&amp;#x2F;dstaley.com&amp;#x2F;processed_images&amp;#x2F;light-comparison.bc3859ff20ccd11e.jpg&quot;
        srcset=&quot;
            &amp;#x2F;&amp;#x2F;dstaley.com&amp;#x2F;processed_images&amp;#x2F;light-comparison.bc3859ff20ccd11e.jpg,
            &amp;#x2F;&amp;#x2F;dstaley.com&amp;#x2F;processed_images&amp;#x2F;light-comparison.bcb882e57e17d0e0.jpg 2x
        &quot;
    &#x2F;&gt;
&lt;&#x2F;a&gt;

&lt;figcaption class=&quot;italic text-sm&quot;&gt;Before and after turning on my Elgato Key Lights. Notice how the colors are significantly warmer and brighter.&lt;&#x2F;figcaption&gt;
&lt;&#x2F;figure&gt;
&lt;p&gt;For a webcam, I use a &lt;a href=&quot;https:&#x2F;&#x2F;electronics.sony.com&#x2F;imaging&#x2F;interchangeable-lens-cameras&#x2F;aps-c&#x2F;p&#x2F;ilce6000l-b&quot;&gt;Sony a6000&lt;&#x2F;a&gt; with a &lt;a href=&quot;https:&#x2F;&#x2F;www.sigmaphoto.com&#x2F;30mm-f1-4-dc-dn-c&quot;&gt;Sigma 30mm f&#x2F;1.4 DC DN Contemporary lens&lt;&#x2F;a&gt;. This was the camera I originally upgraded to when I became unsatisfied with the quality of typical webcams, but I switched to the Sony a5100 for its pop-up display; since the Elgato Prompter blocks the camera I decided to switch back. The a6000 also gives me the ability to control the camera via an &lt;a href=&quot;https:&#x2F;&#x2F;electronics.sony.com&#x2F;imaging&#x2F;imaging-accessories&#x2F;all-accessories&#x2F;p&#x2F;rmtdslr2&quot;&gt;IR remote&lt;&#x2F;a&gt;, which is useful for adjusting settings without having to unmount the camera from the prompter to be able to see the display. I also have the camera plugged into a &lt;a href=&quot;https:&#x2F;&#x2F;www.philips-hue.com&#x2F;en-us&#x2F;p&#x2F;hue-smart-plug&#x2F;046677552343&quot;&gt;Philips Hue Smart Plug&lt;&#x2F;a&gt;, which allows it to be turned on and off without physically moving the power dial on the camera.&lt;&#x2F;p&gt;
&lt;p&gt;I&#x27;ve also upgraded my audio setup, switching from the Mackie CR3 monitors to the &lt;a href=&quot;https:&#x2F;&#x2F;drop.com&#x2F;buy&#x2F;drop-bmr1-nearfield-monitors&quot;&gt;Drop BMR1 Nearfield monitors&lt;&#x2F;a&gt;. These diminutive speakers don&#x27;t have incredible reviews, but I mainly got them for their size, not their performance characteristics. This is mostly because I&#x27;ve recently outfitted my office with a stereo pair of the &lt;a href=&quot;https:&#x2F;&#x2F;www.sonos.com&#x2F;en-us&#x2F;shop&#x2F;era-100&quot;&gt;Sonos Era 100s&lt;&#x2F;a&gt;, which I use to listen to music (which is the main reason I&#x27;d want good speakers anyway). The BMR1s have been sufficient for the occasional YouTube video and work wonderfully for video calls. Plus, they take up significantly less space on my desk compared to the CR3s.&lt;&#x2F;p&gt;
&lt;p&gt;Previously, I was able to connect both my work and personal computers directly to the CR3s due to them having multiple inputs. The BMR1s only have one line-in, but this is trivially solved with the &lt;a href=&quot;https:&#x2F;&#x2F;rolls.com&#x2F;product&#x2F;MX42&quot;&gt;Rolls MX42 Stereo Mini Mixer&lt;&#x2F;a&gt;, which is a passive device that can take in up to four stereo signals and output them over one output. This allows me to have both computers outputting simultaneously to the same set of speakers. This is convenient since it allows me to get notifications from both computers at the same time.&lt;&#x2F;p&gt;


&lt;figure&gt;
&lt;a href=&quot;&#x2F;img&amp;#x2F;rolls-mx42-mixer.jpg&quot;&gt;
    &lt;img
        alt=&quot;The ROLLS MX42 Stereo Mini Mixer sitting on top of a black mesh grille. To the left is a burgandy backpack with the Clerk wordmark on it.&quot;
        class=&quot;m-0&quot;
        loading=&quot;lazy&quot;
        width=&quot;1600&quot;
        height=&quot;1200&quot;
        src=&quot;&amp;#x2F;&amp;#x2F;dstaley.com&amp;#x2F;processed_images&amp;#x2F;rolls-mx42-mixer.4b70b37177b1380c.jpg&quot;
        srcset=&quot;
            &amp;#x2F;&amp;#x2F;dstaley.com&amp;#x2F;processed_images&amp;#x2F;rolls-mx42-mixer.4b70b37177b1380c.jpg,
            &amp;#x2F;&amp;#x2F;dstaley.com&amp;#x2F;processed_images&amp;#x2F;rolls-mx42-mixer.ae8ac44611f9a851.jpg 2x
        &quot;
    &#x2F;&gt;
&lt;&#x2F;a&gt;

&lt;figcaption class=&quot;italic text-sm&quot;&gt;The ROLLS MX42 Stereo Mini Mixer. Shortly after taking this photo, I realized that I had forgotten to switch the left and right channels since the Drop BMR1s expect the active unit to be on the right, but I have it placed on my left.&lt;&#x2F;figcaption&gt;
&lt;&#x2F;figure&gt;
&lt;p&gt;For keyboard and mouse, I use the &lt;a href=&quot;https:&#x2F;&#x2F;www.logitech.com&#x2F;en-us&#x2F;products&#x2F;keyboards&#x2F;mx-mechanical-mini.html&quot;&gt;Logitech MX Mechanical Mini&lt;&#x2F;a&gt; and the &lt;a href=&quot;https:&#x2F;&#x2F;www.logitech.com&#x2F;en-us&#x2F;products&#x2F;mice&#x2F;mx-master-3s.910-006557.html&quot;&gt;MX Master 3S&lt;&#x2F;a&gt;. Both connect to Logitech&#x27;s wireless Bolt receiver, which allows them to switch between computers with the rest of the USB peripherals. This setup wouldn&#x27;t work with Bluetooth devices, since those are bound to a specific host. I also love that the MX keyboards are designed for both Windows and macOS, with appropriate labels on the keys. Lots of other keyboards offer a Mac&#x2F;PC switch, but this is usually a physical toggle on the device itself; the Logitech keyboards change their behavior based on what OS the receiver is connected to. This means that the keyboard switches between Mac and PC layouts automatically when the KVM switches between computers. No manual intervention required. This is important because the position of the Super key (Start on PC keyboards and Command on Mac keyboards) is immediately to the left and right of the spacebar on a Mac layout, whereas that key position is used for the Alt key on a PC layout. Once I get used to the layout switching, I&#x27;ll probably explore building my own keyboard that has similar behavior; while I think the MX Mechanical Mini is a great keyboard, I do miss the loud &quot;thock&quot; of my previous keyboard, the Drop ALT.&lt;&#x2F;p&gt;


&lt;figure&gt;
&lt;a href=&quot;&#x2F;img&amp;#x2F;mx-mechanical-keyboard.jpg&quot;&gt;
    &lt;img
        alt=&quot;Close up photo of the bottom row of keys on the Logitech MX Mechanical Mini keyboard. To the left of the keyboard is a pair of Clerk cookie socks.&quot;
        class=&quot;m-0&quot;
        loading=&quot;lazy&quot;
        width=&quot;1600&quot;
        height=&quot;1200&quot;
        src=&quot;&amp;#x2F;&amp;#x2F;dstaley.com&amp;#x2F;processed_images&amp;#x2F;mx-mechanical-keyboard.36fd6cf28749fc46.jpg&quot;
        srcset=&quot;
            &amp;#x2F;&amp;#x2F;dstaley.com&amp;#x2F;processed_images&amp;#x2F;mx-mechanical-keyboard.36fd6cf28749fc46.jpg,
            &amp;#x2F;&amp;#x2F;dstaley.com&amp;#x2F;processed_images&amp;#x2F;mx-mechanical-keyboard.71238a7880ea2aec.jpg 2x
        &quot;
    &#x2F;&gt;
&lt;&#x2F;a&gt;

&lt;figcaption class=&quot;italic text-sm&quot;&gt;The Logitech MX Mechanical Mini keyboard, which has labels for both the Mac and PC key layout.&lt;&#x2F;figcaption&gt;
&lt;&#x2F;figure&gt;
&lt;p&gt;Another new piece of desk gadgetry that I&#x27;ve acquired recently is the &lt;a href=&quot;https:&#x2F;&#x2F;www.elgato.com&#x2F;us&#x2F;en&#x2F;p&#x2F;stream-deck-plus-black&quot;&gt;Elgato Stream Deck +&lt;&#x2F;a&gt;. I&#x27;ve owned the Stream Deck Mini for several years, but I never really loved using it because it felt so chintzy; pressing the keys required more force than pushing it across my desk, meaning I had to hold it in place and press buttons. The Stream Deck + is heavier, which results in button presses requiring less force than moving it, which makes it feel more stable. The larger model also has some lovely tactile knobs that I use to control the brightness and temperature of my Key Lights, along with my system volume. I have actions setup to put my office into video call mode, which turns the webcam on, turns the Key Lights on, dims my overhead lights, and turns on a light in my hallway for some additional contrast. I&#x27;m still finding ways to integrate it into my setup; I&#x27;m pretty sure I can figure out a way to have it trigger the KVM, which would allow me to mount the KVM underneath my desk. The touchscreen to switch pages is also a nice feature, although I&#x27;d love the ability to switch pages with a knob instead.&lt;&#x2F;p&gt;


&lt;figure&gt;
&lt;a href=&quot;&#x2F;img&amp;#x2F;stream-deck.jpg&quot;&gt;
    &lt;img
        alt=&quot;Close up photo of the Elgato Stream Deck +. To the right is a black hat with a black Clerk logo.&quot;
        class=&quot;m-0&quot;
        loading=&quot;lazy&quot;
        width=&quot;1600&quot;
        height=&quot;1200&quot;
        src=&quot;&amp;#x2F;&amp;#x2F;dstaley.com&amp;#x2F;processed_images&amp;#x2F;stream-deck.f7095ebdc7f75a09.jpg&quot;
        srcset=&quot;
            &amp;#x2F;&amp;#x2F;dstaley.com&amp;#x2F;processed_images&amp;#x2F;stream-deck.f7095ebdc7f75a09.jpg,
            &amp;#x2F;&amp;#x2F;dstaley.com&amp;#x2F;processed_images&amp;#x2F;stream-deck.1d11341ff240a563.jpg 2x
        &quot;
    &#x2F;&gt;
&lt;&#x2F;a&gt;

&lt;figcaption class=&quot;italic text-sm&quot;&gt;The Elgato Stream Deck +. I&amp;#x27;m still finding good uses for the buttons, hence why some are empty.&lt;&#x2F;figcaption&gt;
&lt;&#x2F;figure&gt;
&lt;p&gt;At my previous job I used the &lt;a href=&quot;https:&#x2F;&#x2F;www.bhphotovideo.com&#x2F;c&#x2F;product&#x2F;1632006-REG&#x2F;dell_wd19tbs_modular_thunderbolt_dock_with.html&quot;&gt;Dell WD19TBS Thunderbolt 3 dock&lt;&#x2F;a&gt; with the Dell laptop they supplied. I was surprised to see that it works wonderfully with a MacBook Pro, including full 4K 144Hz DisplayPort output over the Thunderbolt 3 port. The dock doesn&#x27;t have audio out, but that&#x27;s fine since I usually try to use the &lt;a href=&quot;https:&#x2F;&#x2F;www.apple.com&#x2F;us-edu&#x2F;shop&#x2F;product&#x2F;MW2Q3AM&#x2F;A&#x2F;usb-c-to-35-mm-headphone-jack-adapter&quot;&gt;Apple USB-C to 3.5mm Headphone Jack adapter&lt;&#x2F;a&gt; anyway. Its digital-to-analog converter is excellent, but it seems to have issues being plugged into a USB-A to USB-C adapter, which results in the DAC going completely haywire and outputting full-volume static every few hours. I switched to the &lt;a href=&quot;https:&#x2F;&#x2F;us.creative.com&#x2F;p&#x2F;sound-blaster&#x2F;sound-blasterx-g1&quot;&gt;Creative Sound BlasterX G1&lt;&#x2F;a&gt;, which uses USB-A, and haven&#x27;t had any issues.&lt;&#x2F;p&gt;
&lt;p&gt;Overall, I&#x27;m happy with the setup I&#x27;ve managed to assemble given the quality it results in. I get to use a great 4K 144Hz monitor, a mirrorless DSLR webcam, teleprompter, wireless keyboard and mouse, and more between both my work and personal computers, all with the push of a single button.&lt;&#x2F;p&gt;


&lt;figure&gt;
&lt;a href=&quot;&#x2F;img&amp;#x2F;desk.jpg&quot;&gt;
    &lt;img
        alt=&quot;Overview shot of my entire desk, with all the hardware mentioned. On the bottom right hand side of the desk is a notepad with &amp;quot;In case you haven&amp;#x27;t figured it out yet...I&amp;#x27;ve joined the team at Clerk!&amp;quot; written on it.&quot;
        class=&quot;m-0&quot;
        loading=&quot;lazy&quot;
        width=&quot;1600&quot;
        height=&quot;1200&quot;
        src=&quot;&amp;#x2F;&amp;#x2F;dstaley.com&amp;#x2F;processed_images&amp;#x2F;desk.7c606e491f17155f.jpg&quot;
        srcset=&quot;
            &amp;#x2F;&amp;#x2F;dstaley.com&amp;#x2F;processed_images&amp;#x2F;desk.7c606e491f17155f.jpg,
            &amp;#x2F;&amp;#x2F;dstaley.com&amp;#x2F;processed_images&amp;#x2F;desk.bc2f49aa63132156.jpg 2x
        &quot;
    &#x2F;&gt;
&lt;&#x2F;a&gt;

&lt;figcaption class=&quot;italic text-sm&quot;&gt;All of the hardware in action. You&amp;#x27;ll notice that the underside of my desk is conspicuously missing; that&amp;#x27;s because, like all eldritch horrors, merely looking upon it is enough to drive anyone mad. The wallpaper is &amp;quot;Chatbots&amp;quot; by David Lanham.&lt;&#x2F;figcaption&gt;
&lt;&#x2F;figure&gt;
&lt;details&gt;
    &lt;summary&gt;&lt;h2 class=&quot;inline&quot; id=&quot;hardware-used&quot;&gt;Hardware Used&lt;&#x2F;h2&gt;&lt;&#x2F;summary&gt;
    &lt;ul&gt;
        &lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;www.store.level1techs.com&#x2F;products&#x2F;p&#x2F;14-kvm-switch-single-monitor-2computer-64pfg-7l6da&quot;&gt;Level1Techs DisplayPort 1.4 KVM&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
        &lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;www.anker.com&#x2F;products&#x2F;a7505&quot;&gt;Anker 7-Port USB 3.0 Hub&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
        &lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;electronics.sony.com&#x2F;tv-video&#x2F;gaming-monitors&#x2F;all-inzone-monitors&#x2F;p&#x2F;sdmu27m90&quot;&gt;Sony INZONE M9 4K HDR 144Hz Gaming Monitor&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
        &lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;www.ergotron.com&#x2F;en-us&#x2F;products&#x2F;product-details&#x2F;45-669&quot;&gt;Ergotron NX Monitor Arm&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
        &lt;li&gt;
            Camera
            &lt;ul&gt;
                &lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;electronics.sony.com&#x2F;imaging&#x2F;interchangeable-lens-cameras&#x2F;aps-c&#x2F;p&#x2F;ilce6000l-b&quot;&gt;Sony a6000&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
                &lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;www.sigmaphoto.com&#x2F;30mm-f1-4-dc-dn-c&quot;&gt;Sigma 30mm f&#x2F;1.4 DC DN Contemporary Lens&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
                &lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;www.amazon.com&#x2F;dp&#x2F;B00XNMXNV0&quot;&gt;Amazon Basics Circular Polarizer Lens Filter&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
                &lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;www.elgato.com&#x2F;us&#x2F;en&#x2F;p&#x2F;cam-link-4k&quot;&gt;Elgato CamLink 4K&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
                &lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;www.elgato.com&#x2F;us&#x2F;en&#x2F;p&#x2F;game-capture-neo&quot;&gt;Elgato Game Capture Neo&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
                &lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;www.elgato.com&#x2F;us&#x2F;en&#x2F;p&#x2F;prompter&quot;&gt;Elgato Prompter&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
                &lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;www.elgato.com&#x2F;us&#x2F;en&#x2F;p&#x2F;key-light&quot;&gt;Elgato Key Light&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
                &lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;www.philips-hue.com&#x2F;en-us&#x2F;p&#x2F;hue-smart-plug&#x2F;046677552343&quot;&gt;Philips Hue Smart Plug&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
                &lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;electronics.sony.com&#x2F;imaging&#x2F;imaging-accessories&#x2F;all-accessories&#x2F;p&#x2F;rmtdslr2&quot;&gt;Sony Wireless Remote&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
            &lt;&#x2F;ul&gt;
        &lt;&#x2F;li&gt;
        &lt;li&gt;
            Audio
            &lt;ul&gt;
                &lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;rolls.com&#x2F;product&#x2F;MX42&quot;&gt;Rolls MX42 Stereo Mini Mixer&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
                &lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;drop.com&#x2F;buy&#x2F;drop-bmr1-nearfield-monitors&quot;&gt;Drop BMR1 Nearfield Monitors&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
                &lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;www.sonos.com&#x2F;en-us&#x2F;shop&#x2F;era-100&quot;&gt;Sonos Era 100&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
                &lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;www.apple.com&#x2F;us-edu&#x2F;shop&#x2F;product&#x2F;MW2Q3AM&#x2F;A&#x2F;usb-c-to-35-mm-headphone-jack-adapter&quot;&gt;Apple USB-C to 3.5 mm Headphone Jack Adapter&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
                &lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;us.creative.com&#x2F;p&#x2F;sound-blaster&#x2F;sound-blasterx-g1&quot;&gt;Creative Sound BlasterX G1 USB DAC&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
                &lt;li&gt;Elgato Wave:1 (discontinued, replaced by the &lt;a href=&quot;https:&#x2F;&#x2F;www.elgato.com&#x2F;us&#x2F;en&#x2F;p&#x2F;wave-3-black&quot;&gt;Wave:3&lt;&#x2F;a&gt;)&lt;&#x2F;li&gt;
                &lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;rode.com&#x2F;en-us&#x2F;accessories&#x2F;stands-bars&#x2F;psa1&quot;&gt;RODE PSA1 Studio Arm&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
            &lt;&#x2F;ul&gt;
        &lt;&#x2F;li&gt;
        &lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;www.logitech.com&#x2F;en-us&#x2F;products&#x2F;keyboards&#x2F;mx-mechanical-mini.html&quot;&gt;Logitech MX Keys Mechanical&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
        &lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;www.logitech.com&#x2F;en-us&#x2F;products&#x2F;mice&#x2F;mx-master-3s.910-006557.html&quot;&gt;Logitech MX Master 3S&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
        &lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;www.elgato.com&#x2F;us&#x2F;en&#x2F;p&#x2F;stream-deck-plus-black&quot;&gt;Elgato Stream Deck +&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
        &lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;www.bhphotovideo.com&#x2F;c&#x2F;product&#x2F;1632006-REG&#x2F;dell_wd19tbs_modular_thunderbolt_dock_with.html&quot;&gt;Dell WD19TBS Thunderbolt Dock&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
    &lt;&#x2F;ul&gt;
&lt;&#x2F;details&gt;</content:encoded>
            </item>
            <item>
                <title>Dynamic Open Graph Images with @vercel&#x2F;og and Zola</title>
                <pubDate>Thu, 20 Jun 2024 12:00:00 -0700</pubDate>
                <link>https://dstaley.com/posts/dynamic-open-graph-images-with-vercel-og-and-zola/</link>
                <guid>https://dstaley.com/posts/dynamic-open-graph-images-with-vercel-og-and-zola/</guid>
                <description>Static sites can have a little dynamic, as a treat.</description>
                <content:encoded>&lt;p&gt;The website you&#x27;re currently reading is a static site, built with the static site generator &lt;a href=&quot;https:&#x2F;&#x2F;www.getzola.org&#x2F;&quot;&gt;Zola&lt;&#x2F;a&gt;. I&#x27;ve always been a fan of static sites. There&#x27;s something lovely about the idea that it&#x27;s all just a collection of HTML and CSS files that you can toss on basically any hosting service and have a website going. One of the neat things about hosting on Vercel, though, is that I can opt-in to some of their more dynamic features, even when using a static site. So, inspired by &lt;a href=&quot;https:&#x2F;&#x2F;www.zachleat.com&#x2F;web&#x2F;automatic-opengraph&#x2F;&quot;&gt;Zach Leatherman&lt;&#x2F;a&gt;, I wanted to see if I could add support for dynamic Open Graph image generation using Vercel&#x27;s &lt;a href=&quot;https:&#x2F;&#x2F;vercel.com&#x2F;docs&#x2F;functions&#x2F;og-image-generation&#x2F;og-image-api&quot;&gt;&lt;code&gt;@vercel&#x2F;og&lt;&#x2F;code&gt;&lt;&#x2F;a&gt; library. Spoiler alert: you can!&lt;&#x2F;p&gt;
&lt;p&gt;Whether you&#x27;re using a static site generator like Zola or &lt;a href=&quot;https:&#x2F;&#x2F;gohugo.io&#x2F;&quot;&gt;Hugo&lt;&#x2F;a&gt;, or a JavaScript framework like &lt;a href=&quot;https:&#x2F;&#x2F;nextjs.org&#x2F;&quot;&gt;Next.js&lt;&#x2F;a&gt; or &lt;a href=&quot;https:&#x2F;&#x2F;astro.build&#x2F;&quot;&gt;Astro&lt;&#x2F;a&gt;, Vercel gives you access to &lt;a href=&quot;https:&#x2F;&#x2F;vercel.com&#x2F;docs&#x2F;functions&quot;&gt;API routes&lt;&#x2F;a&gt;, which are basically functions that can run in response to an incoming request. Frameworks usually have much nicer implementations (including a fancy file-based router), but static sites still can have API routes by simply dropping some JavaScript into a folder named &lt;code&gt;api&lt;&#x2F;code&gt; in the root of your project. When deployed to Vercel, their build system will bundle those files into separate functions and deploy them, making them available at &lt;code&gt;&#x2F;api&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;However, what about local development? &lt;code&gt;zola serve&lt;&#x2F;code&gt; isn&#x27;t going to magically understand how to invoke HTTP endpoints written in JavaScript. That&#x27;s where the &lt;a href=&quot;https:&#x2F;&#x2F;vercel.com&#x2F;docs&#x2F;cli&quot;&gt;Vercel CLI&lt;&#x2F;a&gt; comes into play. If you&#x27;re using a known framework, running &lt;code&gt;vercel dev&lt;&#x2F;code&gt; inside your project will start two servers, one being your framework on a random port, and the other being a proxy server that knows how to route requests to either your API endpoints or to your framework&#x27;s server. For instance, when I run &lt;code&gt;vercel dev&lt;&#x2F;code&gt; in the folder for this website, &lt;code&gt;vercel&lt;&#x2F;code&gt; runs the proxy server on port 3000, and starts the &lt;code&gt;zola&lt;&#x2F;code&gt; binary on port 51579. This allows me to run both Zola and Vercel Functions locally for development.&lt;&#x2F;p&gt;
&lt;p&gt;The next question that you probably have is how can you install JavaScript dependencies in a static site project that doesn&#x27;t use a JavaScript package manager? Well, the answer is pretty unexciting. It&#x27;s simply &quot;use a JavaScript package manager&quot;. Even if your project is configured using a static site framework preset, Vercel is smart enough to install dependencies when it sees a &lt;code&gt;package.json&lt;&#x2F;code&gt; file. This is admittedly a bit weird, especially considering that the &quot;Install Command&quot; listed in my project&#x27;s &quot;Build &amp;amp; Development Settings&quot; configuration is &quot;None&quot;, but I&#x27;m thankful for the behavior as it allows me to install &lt;code&gt;@vercel&#x2F;og&lt;&#x2F;code&gt;, which is the magical piece that allows me to support dynamic Open Graph images like this one:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;api&#x2F;og?slug=posts&#x2F;2024-06-20-dynamic-open-graph-images-with-vercel-og-and-zola&#x2F;index.md&quot; alt=&quot;&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;So now that we know how to create API routes and install JavaScript dependencies, let&#x27;s generate some Open Graph images. Dropping the following example from Vercel into &lt;code&gt;&#x2F;api&#x2F;og.jsx&lt;&#x2F;code&gt; should make the &lt;code&gt;&#x2F;api&#x2F;og&lt;&#x2F;code&gt; URL return a static &quot;Hello, world!&quot; image.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;tsx&quot; style=&quot;background-color:#2b303b;color:#6c7079;&quot; class=&quot;language-tsx &quot;&gt;&lt;code class=&quot;language-tsx&quot; data-lang=&quot;tsx&quot;&gt;&lt;span style=&quot;color:#cd74e8;&quot;&gt;import &lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;{ &lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;ImageResponse &lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;} &lt;&#x2F;span&gt;&lt;span style=&quot;color:#cd74e8;&quot;&gt;from &lt;&#x2F;span&gt;&lt;span style=&quot;color:#9acc76;&quot;&gt;&amp;#39;@vercel&#x2F;og&amp;#39;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#cd74e8;&quot;&gt;export function &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5cb3fa;&quot;&gt;GET&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;() {
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;color:#cd74e8;&quot;&gt;return &lt;&#x2F;span&gt;&lt;span style=&quot;color:#adb7c9;&quot;&gt;new &lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;ImageResponse(
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;    (
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;      &amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;div
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#db9d63;&quot;&gt;style&lt;&#x2F;span&gt;&lt;span style=&quot;color:#adb7c9;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color:#c94e42;&quot;&gt;{&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;{
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;          display: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#9acc76;&quot;&gt;&amp;#39;flex&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;,
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;          fontSize: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#db9d63;&quot;&gt;128&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;,
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;          background: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#9acc76;&quot;&gt;&amp;#39;white&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;,
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;          width: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#9acc76;&quot;&gt;&amp;#39;100%&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;,
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;          height: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#9acc76;&quot;&gt;&amp;#39;100%&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;,
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;        }&lt;&#x2F;span&gt;&lt;span style=&quot;color:#c94e42;&quot;&gt;}
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;      &amp;gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;        Hello, World!
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;      &amp;lt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;div&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;&amp;gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;    )
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;  )
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;However, if you run this locally and try to access &lt;code&gt;&#x2F;api&#x2F;og&lt;&#x2F;code&gt;, you&#x27;ll get a &lt;code&gt;404 NOT_FOUND&lt;&#x2F;code&gt; error. This is because the Vercel CLI by itself doesn&#x27;t generate API routes from files ending in &lt;code&gt;.jsx&lt;&#x2F;code&gt;. If you try to simply rename the file to &lt;code&gt;og.js&lt;&#x2F;code&gt;, you&#x27;ll get &lt;code&gt;Error: Unexpected token &#x27;&amp;lt;&#x27;&lt;&#x2F;code&gt;. This makes sense given that we have JSX in a &lt;code&gt;.js&lt;&#x2F;code&gt; file. Ultimately, I wasn&#x27;t able to figure out how to get the Vercel CLI to work well with JSX. However, at the end of the day JSX is transformed to plain JavaScript anyway, so we can just perform the transform manually. &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;vercel&#x2F;satori&quot;&gt;Satori&lt;&#x2F;a&gt;, the underlying engine used by &lt;code&gt;@vercel&#x2F;og&lt;&#x2F;code&gt; that renders markup to images, expects objects with a &lt;code&gt;type&lt;&#x2F;code&gt; property that contains a string value and a &lt;code&gt;props&lt;&#x2F;code&gt; property containing, well, props. This is also where the &lt;code&gt;children&lt;&#x2F;code&gt; prop goes. Without JSX, our example from above looks like this:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;js&quot; style=&quot;background-color:#2b303b;color:#6c7079;&quot; class=&quot;language-js &quot;&gt;&lt;code class=&quot;language-js&quot; data-lang=&quot;js&quot;&gt;&lt;span style=&quot;color:#cd74e8;&quot;&gt;import &lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;{ &lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;ImageResponse &lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;} &lt;&#x2F;span&gt;&lt;span style=&quot;color:#cd74e8;&quot;&gt;from &lt;&#x2F;span&gt;&lt;span style=&quot;color:#9acc76;&quot;&gt;&amp;#39;@vercel&#x2F;og&amp;#39;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#cd74e8;&quot;&gt;export function &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5cb3fa;&quot;&gt;GET&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;() {
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;color:#cd74e8;&quot;&gt;return &lt;&#x2F;span&gt;&lt;span style=&quot;color:#adb7c9;&quot;&gt;new &lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;ImageResponse(
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;    {
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;      type: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#9acc76;&quot;&gt;&amp;#39;div&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;,
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;      props: {
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;        style: {
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;          display: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#9acc76;&quot;&gt;&amp;#39;flex&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;,
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;          fontSize: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#db9d63;&quot;&gt;128&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;,
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;          background: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#9acc76;&quot;&gt;&amp;#39;white&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;,
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;          width: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#9acc76;&quot;&gt;&amp;#39;100%&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;,
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;          height: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#9acc76;&quot;&gt;&amp;#39;100%&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;,
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;        },
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;        children: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#9acc76;&quot;&gt;&amp;#39;Hello, World!&amp;#39;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;      }
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;    }
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;  )
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;With this code we&#x27;re now able to dynamically generate Open Graph images even though we&#x27;re using a static site! There&#x27;s a few other nice things I&#x27;ve added to my own Open Graph images. The first is a simple helper function named &lt;code&gt;h()&lt;&#x2F;code&gt; (real ones know) that allows me to build the image using a slightly nicer syntax than plain objects.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;js&quot; style=&quot;background-color:#2b303b;color:#6c7079;&quot; class=&quot;language-js &quot;&gt;&lt;code class=&quot;language-js&quot; data-lang=&quot;js&quot;&gt;&lt;span style=&quot;color:#cd74e8;&quot;&gt;function &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5cb3fa;&quot;&gt;h&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;tag&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;props&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;) {
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;color:#cd74e8;&quot;&gt;return &lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;{ type: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;tag&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;props &lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;};
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;With &lt;code&gt;h()&lt;&#x2F;code&gt;, the above example looks like this:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;js&quot; style=&quot;background-color:#2b303b;color:#6c7079;&quot; class=&quot;language-js &quot;&gt;&lt;code class=&quot;language-js&quot; data-lang=&quot;js&quot;&gt;&lt;span style=&quot;color:#cd74e8;&quot;&gt;export function &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5cb3fa;&quot;&gt;GET&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;() {
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;color:#cd74e8;&quot;&gt;return &lt;&#x2F;span&gt;&lt;span style=&quot;color:#adb7c9;&quot;&gt;new &lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;ImageResponse(
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5cb3fa;&quot;&gt;h&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#9acc76;&quot;&gt;&amp;#39;div&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;, {
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;      style: {
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;        display: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#9acc76;&quot;&gt;&amp;#39;flex&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;,
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;        fontSize: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#db9d63;&quot;&gt;128&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;,
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;        background: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#9acc76;&quot;&gt;&amp;#39;white&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;,
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;        width: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#9acc76;&quot;&gt;&amp;#39;100%&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;,
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;        height: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#9acc76;&quot;&gt;&amp;#39;100%&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;,
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;      },
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;      children: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#9acc76;&quot;&gt;&amp;#39;Hello, World!&amp;#39;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;    })
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;  )
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Satori also supports custom fonts if you provide a font file, but I didn&#x27;t want to go through the trouble of storing a font file within my repo. Thankfully, Google Fonts will happily give you a compatible font file if you ask nicely (read: using the older &lt;code&gt;&#x2F;css&lt;&#x2F;code&gt; endpoint with a non-browser &lt;code&gt;User-Agent&lt;&#x2F;code&gt;). This means that with a small helper function, I&#x27;m able to load the TTF data for any font hosted by Google Fonts:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;js&quot; style=&quot;background-color:#2b303b;color:#6c7079;&quot; class=&quot;language-js &quot;&gt;&lt;code class=&quot;language-js&quot; data-lang=&quot;js&quot;&gt;&lt;span style=&quot;color:#cd74e8;&quot;&gt;function &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5cb3fa;&quot;&gt;extractTTFURL&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;css&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;) {
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;color:#cd74e8;&quot;&gt;const &lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;urlRegex &lt;&#x2F;span&gt;&lt;span style=&quot;color:#adb7c9;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5ebfcc;&quot;&gt;&#x2F;src:&lt;&#x2F;span&gt;&lt;span style=&quot;color:#db9d63;&quot;&gt;\s&lt;&#x2F;span&gt;&lt;span style=&quot;color:#adb7c9;&quot;&gt;*&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5ebfcc;&quot;&gt;url\((&lt;&#x2F;span&gt;&lt;span style=&quot;color:#db9d63;&quot;&gt;[&lt;&#x2F;span&gt;&lt;span style=&quot;color:#adb7c9;&quot;&gt;^&lt;&#x2F;span&gt;&lt;span style=&quot;color:#db9d63;&quot;&gt;)]&lt;&#x2F;span&gt;&lt;span style=&quot;color:#adb7c9;&quot;&gt;+&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5ebfcc;&quot;&gt;)\)&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;color:#cd74e8;&quot;&gt;const &lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;match &lt;&#x2F;span&gt;&lt;span style=&quot;color:#adb7c9;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;css&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5ebfcc;&quot;&gt;match&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;urlRegex&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;);
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;color:#cd74e8;&quot;&gt;if &lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;match &lt;&#x2F;span&gt;&lt;span style=&quot;color:#adb7c9;&quot;&gt;&amp;amp;&amp;amp; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;match&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;[&lt;&#x2F;span&gt;&lt;span style=&quot;color:#db9d63;&quot;&gt;1&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;]) {
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#cd74e8;&quot;&gt;return &lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;match&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;[&lt;&#x2F;span&gt;&lt;span style=&quot;color:#db9d63;&quot;&gt;1&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;];
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;  }
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;color:#cd74e8;&quot;&gt;return &lt;&#x2F;span&gt;&lt;span style=&quot;color:#db9d63;&quot;&gt;null&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;}
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#cd74e8;&quot;&gt;async function &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5cb3fa;&quot;&gt;loadGoogleFont&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;name&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;) {
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;color:#cd74e8;&quot;&gt;const &lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;url &lt;&#x2F;span&gt;&lt;span style=&quot;color:#adb7c9;&quot;&gt;= new &lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;URL(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#9acc76;&quot;&gt;&amp;quot;https:&#x2F;&#x2F;fonts.googleapis.com&#x2F;css&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;);
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;url&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;searchParams&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5ebfcc;&quot;&gt;append&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#9acc76;&quot;&gt;&amp;quot;family&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;name&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;);
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;color:#cd74e8;&quot;&gt;const &lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;css &lt;&#x2F;span&gt;&lt;span style=&quot;color:#adb7c9;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#cd74e8;&quot;&gt;await &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5cb3fa;&quot;&gt;fetch&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;url&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;).&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5ebfcc;&quot;&gt;then&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;((&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;res&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;) &lt;&#x2F;span&gt;&lt;span style=&quot;color:#cd74e8;&quot;&gt;=&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;res&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5cb3fa;&quot;&gt;text&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;());
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;color:#cd74e8;&quot;&gt;const &lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;ttfUrl &lt;&#x2F;span&gt;&lt;span style=&quot;color:#adb7c9;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5cb3fa;&quot;&gt;extractTTFURL&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;css&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;);
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;color:#cd74e8;&quot;&gt;if &lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#adb7c9;&quot;&gt;!&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;ttfUrl&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;) {
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#cd74e8;&quot;&gt;throw &lt;&#x2F;span&gt;&lt;span style=&quot;color:#adb7c9;&quot;&gt;new &lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;Error(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#9acc76;&quot;&gt;`unable to determine TTF URL from ${&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;css&lt;&#x2F;span&gt;&lt;span style=&quot;color:#9acc76;&quot;&gt;}`&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;);
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;  }
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;color:#cd74e8;&quot;&gt;const &lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;data &lt;&#x2F;span&gt;&lt;span style=&quot;color:#adb7c9;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#cd74e8;&quot;&gt;await &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5cb3fa;&quot;&gt;fetch&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;ttfUrl&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;).&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5ebfcc;&quot;&gt;then&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;((&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;res&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;) &lt;&#x2F;span&gt;&lt;span style=&quot;color:#cd74e8;&quot;&gt;=&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;res&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5cb3fa;&quot;&gt;arrayBuffer&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;());
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;color:#cd74e8;&quot;&gt;return &lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;{ &lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;name&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;data &lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;};
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Finally, I didn&#x27;t want to pass in the title via a URL parameter, mainly because I didn&#x27;t want people to be able to generate arbitrary Open Graph images with my face on them (especially since generating images isn&#x27;t free!). So, with a small tweak to &lt;code&gt;vercel.json&lt;&#x2F;code&gt; to bundle the Markdown files for posts with the function&#x27;s code, I&#x27;m now able to read the frontmatter during function invocation. This means I can trivially configure how the function works via frontmatter, instead of creating an ever-growing list of URL parameters.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;json&quot; style=&quot;background-color:#2b303b;color:#6c7079;&quot; class=&quot;language-json &quot;&gt;&lt;code class=&quot;language-json&quot; data-lang=&quot;json&quot;&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;{
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;color:#9acc76;&quot;&gt;&amp;quot;$schema&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#9acc76;&quot;&gt;&amp;quot;https:&#x2F;&#x2F;openapi.vercel.sh&#x2F;vercel.json&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;,
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;color:#9acc76;&quot;&gt;&amp;quot;functions&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;: {
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#9acc76;&quot;&gt;&amp;quot;api&#x2F;og&#x2F;index.js&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;: {
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;      &lt;&#x2F;span&gt;&lt;span style=&quot;color:#9acc76;&quot;&gt;&amp;quot;includeFiles&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#9acc76;&quot;&gt;&amp;quot;content&#x2F;**&#x2F;*.md&amp;quot;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;    }
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;  }
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;As someone who spends a lot of time working with sites deployed on Vercel using frameworks like Next.js, it&#x27;s nice to see that there&#x27;s still some benefit to hosting static sites on Vercel. While I wish the documentation was better, I&#x27;m glad that Vercel makes functionality like Vercel Functions available to static sites.&lt;&#x2F;p&gt;
&lt;p&gt;If you&#x27;re curious to see the full implementation, it&#x27;s all &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;dstaley&#x2F;dstaley.com&#x2F;blob&#x2F;master&#x2F;api&#x2F;og&#x2F;index.js&quot;&gt;open source&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
</content:encoded>
            </item>
            <item>
                <title>Do It With Kindness; or, Why a Skateboard Can (Sometimes) Be Faster Than a Ferrari</title>
                <pubDate>Mon, 10 Jun 2024 12:00:00 -0700</pubDate>
                <link>https://dstaley.com/posts/do-it-with-kindness/</link>
                <guid>https://dstaley.com/posts/do-it-with-kindness/</guid>
                <description>The advice I left for my team on my last day as an engineer at HashiCorp</description>
                <content:encoded>&lt;p&gt;&lt;em&gt;This post was originally shared with my team at HashiCorp on my last day. It has been edited to account for a broader audience.&lt;&#x2F;em&gt;&lt;&#x2F;p&gt;
&lt;hr &#x2F;&gt;
&lt;p&gt;I spent a lot of time at HashiCorp thinking about how I could help others level up their engineering skills. With that in mind, I wanted to make one final attempt to share some of the things that I think engineers can do to distinguish themselves apart from the rest. This, of course, isn&#x27;t an exhaustive list, as any attempt to do so could probably fill several libraries, but I do think it captures some of the qualities that I&#x27;ve seen in the engineers I look up to and admire.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;read-the-error-message-code-documentation&quot;&gt;Read the error message&#x2F;code&#x2F;documentation&lt;&#x2F;h2&gt;
&lt;p&gt;One of the really awesome things about software engineering is that if you don&#x27;t know how something works, you can simply go read the code (well, when you have access to the code that is!). Get really, really good at that. In high school, my computer science teacher frequently told us that &quot;computers don&#x27;t do anything you haven&#x27;t told them to do&quot;, which was a really nice way of telling us to stop complaining and just read the code that we had written. In large codebases it&#x27;s unreasonable to expect engineers to understand off the top of their heads how everything works, so being able to open an unfamiliar piece of code and read it top-to-bottom and understand what it&#x27;s doing is a skill you&#x27;ll benefit from.&lt;&#x2F;p&gt;
&lt;p&gt;Similarly, get good at reading error messages and documentation. I can&#x27;t count the number of times when an error message contained the specific piece of knowledge that I needed to go solve the issue. Even when an error message is terrible, it often comes with a stack trace, so you can see all the lines at play that resulted in a specific error. Jump into the code and walk backwards, and you&#x27;ll likely figure out what the issue was, even if the error message was unhelpful.&lt;&#x2F;p&gt;
&lt;p&gt;Documentation is another thing that you should be good at reading. Frameworks like Next.js have tons of documentation detailing their features and functionality. If you haven&#x27;t read through them at least once I highly suggest doing so, if only to drop breadcrumbs of knowledge that you can pick up when you run into issues.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;get-good-at-searching&quot;&gt;Get good at searching&lt;&#x2F;h2&gt;
&lt;p&gt;Learning how to search for things, whether it be on Google, Slack, GitHub, Datadog, or some other service, is such a superpower. In just a few seconds you can search across an unfathomably large amount of information, and being able to do so effectively can really boost your productivity. I find this most useful when searching after encountering an unfamiliar error message. One thing I really find useful is searching for the portion of the error message that is unlikely to appear in anything &lt;em&gt;other&lt;&#x2F;em&gt; than the error message itself but is also applicable enough that it will match against all instances of the error. In these situations, performing exact searches (usually by using quotations) is also a great strategy. Most search features have additional filters to narrow down the results, so make sure you&#x27;re learning about and using those. For example, I frequently used the &lt;code&gt;org:&lt;&#x2F;code&gt;, &lt;code&gt;language:&lt;&#x2F;code&gt;, and &lt;code&gt;path:&lt;&#x2F;code&gt; filters in GitHub Search.&lt;&#x2F;p&gt;
&lt;p&gt;Getting good at search is often just a matter of being able to intuit the pieces that are likely contributing to the error. As an example, a documentation author at HashiCorp was having issues with Docker on their MacBook where one of my team&#x27;s Node applications would simply output &quot;Segmentation fault&quot; and exit. However, another user running the exact same branch on their MacBook did not encounter the same error. The first user was using an Intel Mac, whereas the second user was using Apple Silicon. With that information I Googled &quot;docker node segmentation fault intel mac&quot; and the first result was &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;docker&#x2F;for-mac&#x2F;issues&#x2F;6824&quot;&gt;this issue from over a year ago&lt;&#x2F;a&gt;, which had some helpful configuration changes that we were able to make to get the Intel user back up and running.&lt;&#x2F;p&gt;
&lt;p&gt;Being good at search isn&#x27;t just for error messages though. In organizations that use tools like Slack and Google Workspace, you can dig up some incredible things by using search. I frequently used Slack to look up previous conversations to get background on why certain decisions were made, especially when the people who made them were no longer at HashiCorp. Tools like Google Cloud Search (which allows you to search for text in anything hosted in Gmail or Google Drive) were similarly useful in finding old documents and slide decks, something I found myself referencing a lot, especially when it came to decisions made outside my team.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;make-minimal-reproductions&quot;&gt;Make minimal reproductions&lt;&#x2F;h2&gt;
&lt;p&gt;When it comes to debugging, being able to make a minimal reproduction of the issue is basically the holy grail. Many open source projects won&#x27;t even &lt;em&gt;look&lt;&#x2F;em&gt; at a bug report without one. The reason they&#x27;re so good is that they boil the issue down to the pieces that are necessary to make it happen, and nothing else. This allows you to narrow your focus to just the pieces at play, and easily confirm if your changes resolve the issue. During my time at HashiCorp, I made dozens of minimal reproductions that ultimately resulted in pinpointing the source of an issue. These reproductions also helped the Next.js and Vercel teams fix issues on their end, ultimately benefiting all of their customers. Whether you start from scratch or start pulling pieces out one by one, being able to end up with the problem distilled to its most minimal form is a powerful skill to have.&lt;&#x2F;p&gt;
&lt;p&gt;To give an example, about a year ago one of our team members ran into an issue with dynamic routes after updating Next.js where pages would render with data from the incorrect page, but &lt;em&gt;only&lt;&#x2F;em&gt; when our application was deployed to Vercel. They opened a support case and worked with Vercel to try and track the issue down, but after a month of back and forth, were unable to narrow down the issue enough to come to a resolution. When it began to block our ability to update to Next.js v14, I sat down with my coworker&#x27;s notes and took a few hours to build a reproduction that was just a handful of files and reliably demonstrated the issue. When I shared that with Vercel, they were able to deploy a fix in just 10 days.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;try-to-figure-it-out-yourself-first&quot;&gt;Try to figure it out yourself first&lt;&#x2F;h2&gt;
&lt;p&gt;When you run into a problem, make a decent attempt at figuring it out on your own before you call in reinforcements. Don&#x27;t spend too much time, especially if you&#x27;re truly stuck, but you should at least try to solve the problem yourself. This allows you to build up experience with unfamiliar systems, and helps you develop your own understanding. Pairing with other people is a productivity boost, but it can also easily become a crutch holding you back from developing your own skills, especially if your pairing partner isn&#x27;t experienced enough to help guide you to the solution, and instead simply tells you what the answer is.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;align-how-long-it-takes-to-make-a-decision-with-how-long-it-takes-to-unmake-it&quot;&gt;Align how long it takes to make a decision with how long it takes to unmake it&lt;&#x2F;h2&gt;
&lt;p&gt;Allen Pike has a great article titled &lt;a href=&quot;https:&#x2F;&#x2F;allenpike.com&#x2F;2023&#x2F;do-something-so-we-can-change-it&quot;&gt;&quot;Do Something, So We Can Change It&quot;&lt;&#x2F;a&gt; which I highly recommend reading. I often like to think about technical decisions in terms of how long it takes to undo a change. If it takes half an hour, there&#x27;s no reason to spend two weeks trying to come up with a perfect solution. If it will take six months to undo, maybe make sure you&#x27;re really confident that your approach is the correct one. Don&#x27;t spend tons of time worrying over small, easily reversible decisions. Save that time for the big, complicated, thorny problems.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;it-s-okay-to-be-wrong-but-don-t-become-known-for-it&quot;&gt;It&#x27;s okay to be wrong, but don&#x27;t become known for it&lt;&#x2F;h2&gt;
&lt;p&gt;The best engineers I&#x27;ve ever worked with were the ones who weren&#x27;t ashamed to admit they didn&#x27;t know something. Instead of making something up or guessing, they were honest that they had no idea. As we become more experienced, there&#x27;s this desire to maintain our prestige in the eyes of others, which often leads us to feeling discomfort when admitting we don&#x27;t know something. That&#x27;s totally okay! I can&#x27;t recall a single instance where an engineer admitted they didn&#x27;t know something and I thought less of them. However, there &lt;em&gt;have&lt;&#x2F;em&gt; been times in which an engineer confidently answered a question with incorrect information, without any qualification that their understanding wasn&#x27;t complete. This is always an awkward, uncomfortable situation because correcting misinformation in a kind way takes a large amount of skill. It&#x27;s always easier to just admit you don&#x27;t know something, because you don&#x27;t want to build a reputation for giving unreliable information.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;be-a-little-lazy&quot;&gt;Be (a little) lazy&lt;&#x2F;h2&gt;
&lt;p&gt;Look, I understand the allure of building wonderful, beautiful, complicated systems, but sometimes the best thing is the laziest approach. Instead of trying to build something that covers every possible edge case, build the thing that covers the majority of the &lt;em&gt;non&lt;&#x2F;em&gt;-edge cases, and iterate from there. At HashiCorp, we talked a lot about &quot;building a skateboard&quot;, which originates from a &lt;a href=&quot;https:&#x2F;&#x2F;blog.crisp.se&#x2F;2016&#x2F;01&#x2F;25&#x2F;henrikkniberg&#x2F;making-sense-of-mvp&quot;&gt;talk by Henrik Kniberg&lt;&#x2F;a&gt;, instead of jumping straight to building a car. We also started using the term &quot;bus ticket&quot; for something that currently exists and can be used without building something new. This way of thinking allows you to avoid spending a lot of time doing something that isn&#x27;t quite the right thing.&lt;&#x2F;p&gt;
&lt;figure&gt;
    &lt;a href=&quot;https:&#x2F;&#x2F;xkcd.com&#x2F;974&#x2F;&quot; target=&quot;_blank&quot;&gt;&lt;img class=&quot;m-0&quot; src=&quot;https:&#x2F;&#x2F;imgs.xkcd.com&#x2F;comics&#x2F;the_general_problem.png&quot; &#x2F;&gt;&lt;&#x2F;a&gt;
    &lt;figcaption class=&quot;italic text-sm&quot;&gt;&quot;General Problem&quot;. XKCD.&lt;&#x2F;figcaption&gt;
&lt;&#x2F;figure&gt;
&lt;p&gt;Knowing when to go with the easy approach and when to spend the time building the &quot;right&quot; approach is a skill that only comes with time and experience, but you can start by asking yourself &quot;What is the easiest way to solve &lt;em&gt;this&lt;&#x2F;em&gt; problem?&quot; Start from that solution, and expand outwards, instead of jumping straight to the solution to solve all instances of an entire class of problems.&lt;&#x2F;p&gt;
&lt;p&gt;So, what does the lazy solution look like? Sometimes it&#x27;s just copying and pasting code instead of trying to fit two use cases into the same API. Sometimes it&#x27;s copying in a small dependency and modifying it slightly instead of making a pull request and waiting for a change to be merged. Sometimes it&#x27;s accepting that you&#x27;re only going to be able to handle a specific set of circumstances automatically and being okay with handling the other cases manually.&lt;&#x2F;p&gt;
&lt;p&gt;All this being said, don&#x27;t take this as advice to be bad at your job, or to take shortcuts that result in worse outcomes. Being lazy has nothing to do with not doing the work that &lt;em&gt;needs&lt;&#x2F;em&gt; to be done. It just means avoiding work that &lt;em&gt;doesn&#x27;t&lt;&#x2F;em&gt; really need to be done.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;be-okay-saying-no&quot;&gt;Be okay saying &quot;No&quot;&lt;&#x2F;h2&gt;
&lt;p&gt;You don&#x27;t need to immediately say yes to every idea presented to you. &lt;strong&gt;Your best work will be highly opinionated.&lt;&#x2F;strong&gt; Believe it or not, the customer isn&#x27;t always right. (An alternative version I&#x27;ve heard is &quot;the customer is always right &lt;em&gt;in matters of taste&lt;&#x2F;em&gt;&quot;, which feels more like something I could get behind.) Often their ideas don&#x27;t account for the full context at play, and likely aren&#x27;t the best possible solution to the underlying problem. If someone approaches you with a specific solution, take the time to dig into what the &lt;em&gt;actual&lt;&#x2F;em&gt; problem is, and then try to solve &lt;em&gt;that&lt;&#x2F;em&gt; instead of simply saying &quot;Yes&quot; to whatever they&#x27;ve proposed. This is hard sometimes, and impossible in others, but is necessary if you want to have good results.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;do-everything-with-kindness&quot;&gt;Do everything with kindness&lt;&#x2F;h2&gt;
&lt;p&gt;Finally, in everything you do, &lt;em&gt;do it with kindness&lt;&#x2F;em&gt;. I can think of no worse outcome than becoming known as the person who doesn&#x27;t. At the end of the day, our jobs are not so important that it&#x27;s worth being unkind to someone else. Always assume that others are acting with positive intent, and ensure that your words and actions build others up instead of tearing them down. Focus on critiquing &lt;em&gt;ideas&lt;&#x2F;em&gt;, not people, and make sure your critique is constructive and actionable; there&#x27;s no value in simply saying an idea is &quot;bad&quot;. When others present ideas, even if you disagree, offer to work together to build towards something that you &lt;em&gt;can&lt;&#x2F;em&gt; agree with. Do this with eagerness and excitement. Working with others is a joy if you let it be, and even more so if you&#x27;re a joy to work with yourself.&lt;&#x2F;p&gt;
</content:encoded>
            </item>
            <item>
                <title>Pushing Buttons with Rust; or, How I Spent Four Hours Automating a One Second Task</title>
                <pubDate>Mon, 23 Jan 2023 12:00:00 -0800</pubDate>
                <link>https://dstaley.com/posts/pushing-buttons-with-rust/</link>
                <guid>https://dstaley.com/posts/pushing-buttons-with-rust/</guid>
                <description>Have you tried turning it off and on again?</description>
                <content:encoded>&lt;p&gt;I&#x27;ve worked hard to set up my desk with the devices I enjoy using. This includes a nice monitor, a clicky mechanical keyboard with nice-looking keycaps, a webcam made out of a mirrorless camera, and a high-quality microphone. These are mostly used for my job but given how much money and effort I&#x27;ve spent on them, it&#x27;d be a waste not to use them for my personal use as well. My home office isn&#x27;t spacious enough to support a work and personal desk, so I went with the next best thing: &lt;a href=&quot;https:&#x2F;&#x2F;store.level1techs.com&#x2F;products&#x2F;14-kvm-switch-dual-monitor-2computer&quot;&gt;a KVM switch from Level 1 Techs&lt;&#x2F;a&gt; which allows me to easily switch all my peripherals from my personal computer to my work computer. I simply press a button (or a keyboard shortcut) and the KVM disconnects the peripherals from one computer and connects them to the other.&lt;&#x2F;p&gt;
&lt;p&gt;There&#x27;s just one problem. The KVM does this in a way that prevents the reset of most of the peripherals. While that&#x27;s sufficient most of the time, it causes issues with devices that need a full power-cycle to reinitialize. In my case, it&#x27;s my &lt;a href=&quot;https:&#x2F;&#x2F;www.elgato.com&#x2F;en&#x2F;cam-link-4k&quot;&gt;Elgato CamLink&lt;&#x2F;a&gt;, which I use to connect my Sony a5000 as a webcam. Without a power-cycle, the CamLink fails to present itself to the host computer.&lt;&#x2F;p&gt;
&lt;p&gt;Once I figured out what the issue was, I came up with a hacky solution: put the powered USB hub on a smart outlet, and then use a button on my Elgato Stream Deck to power-cycle the hub, which in turn would power-cycle the CamLink. This has worked wonderfully for the past year, but there&#x27;s still a problem.&lt;&#x2F;p&gt;
&lt;p&gt;I keep forgetting to press the dang button.&lt;&#x2F;p&gt;
&lt;p&gt;But what if I didn&#x27;t need to remember? Why can&#x27;t my computer just reset the hub for me when I switch from one computer to another?&lt;&#x2F;p&gt;
&lt;p&gt;At first, I had some cursed ideas, like connecting an ESP32 to the Human Interface Device port on my KVM and using that to trigger both the device switching and the hub reset. But then I realized that if my computer knew what devices were connected to it at a given moment, surely there&#x27;s a way to also know when a device gets removed!&lt;&#x2F;p&gt;
&lt;p&gt;And sure enough, there is: enter the &lt;a href=&quot;https:&#x2F;&#x2F;learn.microsoft.com&#x2F;en-us&#x2F;uwp&#x2F;api&#x2F;windows.devices.enumeration.devicewatcher?view=winrt-22621&quot;&gt;DeviceWatcher&lt;&#x2F;a&gt; API, which allows just that: receive notifications when a device is added or removed.&lt;&#x2F;p&gt;
&lt;p&gt;To use the API, I turned to the &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;microsoft&#x2F;windows-rs&quot;&gt;&lt;code&gt;windows&lt;&#x2F;code&gt;&lt;&#x2F;a&gt; Rust crate, which is a projection of the Windows API for Rust. This allows me to access all of the Windows APIs using idiomatic Rust code, and felt like a perfect small project to do on a Sunday morning.&lt;&#x2F;p&gt;
&lt;p&gt;So, without further ado, I&#x27;d like to introduce you to &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;dstaley&#x2F;monitor-monitor&quot;&gt;&lt;code&gt;monitor-monitor&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;, a small Windows utility to send an off&#x2F;on command to a TP-Link Kasa smart plug when a monitor is added and removed! I decided that the most important device to &lt;em&gt;monitor&lt;&#x2F;em&gt; was my monitor, since it&#x27;s the cleanest indicator that the KVM has switched to my other computer. Using the keyboard wouldn&#x27;t have worked since I regularly unplug it for cleaning, and my microphone requires that it be unplugged during software updates. I probably could have used the mouse or the CamLink, but the monitor seemed the best choice.&lt;&#x2F;p&gt;
&lt;p&gt;Now, when I press the button to switch computers, &lt;code&gt;monitor-monitor&lt;&#x2F;code&gt; receives an event when the monitor is removed and sends the off&#x2F;on command to the smart plug. By the time the monitor has reacquired the display signal, all my peripherals are already reset and ready to go.&lt;&#x2F;p&gt;
&lt;p&gt;Now I&#x27;m free to replace that muscle memory with this gif of two very good dogs:&lt;&#x2F;p&gt;
&lt;video autoplay loop muted playsinline title=&quot;Clip from the Pokemon anime where the legendary dog Suicune inquisitively looks at a Yamper.&quot;&gt;
    &lt;source src=&quot;&#x2F;img&#x2F;good-dogs.mp4&quot; type=&quot;video&#x2F;mp4&quot;&gt;
&lt;&#x2F;video&gt;
</content:encoded>
            </item>
            <item>
                <title>Running Zola on WebAssembly</title>
                <pubDate>Mon, 09 Jan 2023 12:00:00 -0800</pubDate>
                <link>https://dstaley.com/posts/running-zola-on-wasm/</link>
                <guid>https://dstaley.com/posts/running-zola-on-wasm/</guid>
                <description>Just because you&#x27;re compiling software written in Rust doesn&#x27;t mean you can&#x27;t also fail to compile software written in C.</description>
                <content:encoded>&lt;p&gt;The site you&#x27;re reading this on is built using &lt;a href=&quot;https:&#x2F;&#x2F;www.getzola.org&#x2F;&quot;&gt;Zola&lt;&#x2F;a&gt; (unless of course you&#x27;re reading this from some future date where I&#x27;ve decided to rebuild the site using something &lt;em&gt;other&lt;&#x2F;em&gt; than Zola, in which case how&#x27;s the future?) and hosted on &lt;a href=&quot;https:&#x2F;&#x2F;vercel.com&#x2F;&quot;&gt;Vercel&lt;&#x2F;a&gt; (again, unless you&#x27;re reading this after that&#x27;s no longer the case). One of the neat features of Vercel is first-party support for various static-site generators, including the ability to control which version is used to render your site. When I was moving this site from Netlify to Vercel, I set the &lt;code&gt;ZOLA_VERSION&lt;&#x2F;code&gt; environment variable to the latest available version, &lt;code&gt;0.16.1&lt;&#x2F;code&gt;, and was greeted with the following build log:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#6c7079;&quot;&gt;&lt;code&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;[16:16:06.599] Cloning github.com&#x2F;dstaley&#x2F;dstaley.com (Branch: master, Commit: fcf0f09)
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;[16:16:07.092] Cloning completed: 493.016ms
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;[16:16:07.480] Looking up build cache...
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;[16:16:07.773] Build Cache not found
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;[16:16:07.807] Running &amp;quot;vercel build&amp;quot;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;[16:16:08.310] Vercel CLI 28.2.5
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;[16:16:08.522] Installing Zola version 0.16.1
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;[16:16:08.953] zola: &#x2F;lib64&#x2F;libm.so.6: version `GLIBC_2.27&amp;#39; not found (required by zola)
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;[16:16:08.953] zola: &#x2F;lib64&#x2F;libm.so.6: version `GLIBC_2.29&amp;#39; not found (required by zola)
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;[16:16:08.953] zola: &#x2F;lib64&#x2F;libstdc++.so.6: version `GLIBCXX_3.4.26&amp;#39; not found (required by zola)
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;[16:16:08.954] zola: &#x2F;lib64&#x2F;libc.so.6: version `GLIBC_2.28&amp;#39; not found (required by zola)
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;[16:16:08.954] zola: &#x2F;lib64&#x2F;libc.so.6: version `GLIBC_2.27&amp;#39; not found (required by zola)
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;[16:16:08.954] zola: &#x2F;lib64&#x2F;libc.so.6: version `GLIBC_2.29&amp;#39; not found (required by zola)
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;[16:16:08.954] Error: Command &amp;quot;zola build&amp;quot; exited with 1
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Even though Zola is written in Rust, it still relies on &lt;a href=&quot;https:&#x2F;&#x2F;www.gnu.org&#x2F;software&#x2F;libc&#x2F;&quot;&gt;glibc&lt;&#x2F;a&gt;, the GNU C Library. The update to v15 changed how the Zola binary for Linux was built, causing it to rely on newer versions of glibc. After a few emails with Vercel&#x27;s support team, I confirmed that the build environment used by Vercel only had access to glibc 2.26, hence the errors when attempting to use the latest version of Zola.&lt;&#x2F;p&gt;
&lt;p&gt;Now, at this point, I had a few options if I wanted to use the latest version of Zola to build my site, but the easiest was probably setting up my Vercel project to download a custom-built version of Zola that was built against a lower version of glibc. While it certainly would have worked, and wouldn&#x27;t have been too much effort, it also wasn&#x27;t a fun or interesting solution.&lt;&#x2F;p&gt;
&lt;video autoplay loop muted playsinline title=&quot;Scene from the 2010 film Inception in which Arthur, played by Joseph Gordon-Levitt, is seen holding a rifle before Eames, played by Tom Hardy, says &#x27;You musn&#x27;t be afraid to dream a little bigger, darling&#x27; before pulling out a grenade launcher.&quot;&gt;
    &lt;source src=&quot;&#x2F;img&#x2F;dream-a-little-bigger-darling.mp4&quot; type=&quot;video&#x2F;mp4&quot;&gt;
&lt;&#x2F;video&gt;
&lt;p&gt;Instead, I decided to see if I could compile Zola to WASM targeting the WebAssembly System Interface (WASI) and run it as a standard npm package.&lt;&#x2F;p&gt;
&lt;p&gt;Spoiler: I could!&lt;&#x2F;p&gt;
&lt;p&gt;With most Rust projects, compiling for WASI is relatively simple. You can run &lt;code&gt;cargo build --target wasm32-wasi&lt;&#x2F;code&gt; and get a neat &lt;code&gt;.wasm&lt;&#x2F;code&gt; file that will then run using WASI runtimes like &lt;a href=&quot;https:&#x2F;&#x2F;nodejs.org&#x2F;api&#x2F;wasi.html&quot;&gt;node&lt;&#x2F;a&gt;, &lt;a href=&quot;https:&#x2F;&#x2F;wasmtime.dev&#x2F;&quot;&gt;Wasmtime&lt;&#x2F;a&gt;, &lt;a href=&quot;https:&#x2F;&#x2F;wasmedge.org&#x2F;&quot;&gt;WasmEdge&lt;&#x2F;a&gt;, and more. That is, unless the Rust project you&#x27;re compiling uses features that aren&#x27;t available in Rust&#x27;s WASI implementation (such as networking, which has &lt;em&gt;some&lt;&#x2F;em&gt; support, but not enough for large libraries like &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;hyperium&#x2F;hyper&quot;&gt;hyper&lt;&#x2F;a&gt;). Zola, being a static site generator, heavily relies on networking support to provide the &lt;code&gt;zola serve&lt;&#x2F;code&gt; command, which allows you to preview your static site using a local web server. If I wanted to build a WASM version of Zola that could be used to build my site, I was going to need to remove all of the networking code.&lt;&#x2F;p&gt;
&lt;p&gt;One really neat aspect of Rust is its support for &lt;a href=&quot;https:&#x2F;&#x2F;doc.rust-lang.org&#x2F;reference&#x2F;conditional-compilation.html&quot;&gt;conditional compilation&lt;&#x2F;a&gt;, which allows you to exclude code from being compiled based on a number of different conditions. One of those conditions is called &quot;features&quot;, which are basically what it says on the tin: optional features of your application. This meant that I could mark complete sections of code as relying on the &lt;code&gt;serve&lt;&#x2F;code&gt; feature using the &lt;code&gt;#[cfg(feature = &quot;serve&quot;)]&lt;&#x2F;code&gt; attribute. By making the &lt;code&gt;serve&lt;&#x2F;code&gt; feature a default feature, and compiling with the &lt;code&gt;--no-default-features&lt;&#x2F;code&gt; flag, I could make sure that any code that relied on networking was completely disabled.&lt;&#x2F;p&gt;
&lt;p&gt;However, networking isn&#x27;t the only feature of Rust that isn&#x27;t available in WASI. WebAssembly is single-threaded (although support for threads &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;WebAssembly&#x2F;wasi-threads&quot;&gt;has been proposed&lt;&#x2F;a&gt;), so code that relied on spawning threads was also not going to work in my WASM port. The main instance of this lack of support came from &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;rayon-rs&#x2F;rayon&quot;&gt;&lt;code&gt;rayon&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;, which is a data parallelism library that provides parallel loops that function the same way as sequential loops in the standard library. That parity is important, as I was able to basically &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;dstaley&#x2F;zola&#x2F;blob&#x2F;wasm&#x2F;components&#x2F;libs&#x2F;src&#x2F;no_rayon.rs&quot;&gt;provide an alternate implementation of &lt;code&gt;rayon&lt;&#x2F;code&gt;&lt;&#x2F;a&gt; that simply used the sequential version of the method provided by &lt;code&gt;rayon&lt;&#x2F;code&gt;. This meant that even though the code was calling a &lt;code&gt;.par_iter_mut()&lt;&#x2F;code&gt; method, it was actually invoking the built-in &lt;code&gt;.iter_mut()&lt;&#x2F;code&gt; method.&lt;&#x2F;p&gt;
&lt;p&gt;Most features that don&#x27;t work on WASI will trigger compile-time errors, making it simple to make the necessary changes to get things compiling. However, there&#x27;s unfortunately some issues that are only exposed at runtime. One such issue came from a fairly innocuous looking piece of code:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#6c7079;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#cd74e8;&quot;&gt;pub fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5cb3fa;&quot;&gt;is_path_in_directory&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;parent&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#adb7c9;&quot;&gt;&amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;Path, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;path&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#adb7c9;&quot;&gt;&amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;Path) -&amp;gt; Result&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#cd74e8;&quot;&gt;bool&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;&amp;gt; {
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#cd74e8;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt; canonical_path &lt;&#x2F;span&gt;&lt;span style=&quot;color:#adb7c9;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt; path
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;        .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5ebfcc;&quot;&gt;canonicalize&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;()
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;        .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5ebfcc;&quot;&gt;with_context&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;(|| format!(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#9acc76;&quot;&gt;&amp;quot;Failed to canonicalize &lt;&#x2F;span&gt;&lt;span style=&quot;color:#db9d63;&quot;&gt;{}&lt;&#x2F;span&gt;&lt;span style=&quot;color:#9acc76;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;, path.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5ebfcc;&quot;&gt;display&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;()))&lt;&#x2F;span&gt;&lt;span style=&quot;color:#adb7c9;&quot;&gt;?&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#cd74e8;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt; canonical_parent &lt;&#x2F;span&gt;&lt;span style=&quot;color:#adb7c9;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt; parent
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;        .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5ebfcc;&quot;&gt;canonicalize&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;()
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;        .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5ebfcc;&quot;&gt;with_context&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;(|| format!(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#9acc76;&quot;&gt;&amp;quot;Failed to canonicalize &lt;&#x2F;span&gt;&lt;span style=&quot;color:#db9d63;&quot;&gt;{}&lt;&#x2F;span&gt;&lt;span style=&quot;color:#9acc76;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;, parent.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5ebfcc;&quot;&gt;display&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;()))&lt;&#x2F;span&gt;&lt;span style=&quot;color:#adb7c9;&quot;&gt;?&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;    Ok(canonical_path.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5ebfcc;&quot;&gt;starts_with&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;(canonical_parent))
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This piece of code checks to see if the provided &lt;code&gt;path&lt;&#x2F;code&gt; is contained by the provided &lt;code&gt;parent&lt;&#x2F;code&gt;. This is achieved by canonicalizing each path and comparing the prefixes. While the &lt;code&gt;.canonicalize()&lt;&#x2F;code&gt; method is provided when compiling to WASI, it will always error out since WASI &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;rust-lang&#x2F;rust&#x2F;issues&#x2F;82339&quot;&gt;doesn&#x27;t really have the concept of paths&lt;&#x2F;a&gt; (at least not in the same way as most other operating systems think of them). Thankfully, the solution was to simply leave the path as is when running on WASI.&lt;&#x2F;p&gt;
&lt;p&gt;So far, most of the issues I ran into were relatively easy to fix; it simply took a moment to figure out what was causing the issue, and then tweaking the code to act a bit differently when it was running on WASI.&lt;&#x2F;p&gt;
&lt;p&gt;Unfortunately, there was a big roadblock ahead.&lt;&#x2F;p&gt;
&lt;p&gt;Zola used &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;sass&#x2F;libsass&quot;&gt;&lt;code&gt;libsass&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;For those of you who are unaware, I envy you. LibSass, like &lt;a href=&quot;https:&#x2F;&#x2F;nokogiri.org&#x2F;&quot;&gt;Nokogiri&lt;&#x2F;a&gt;, is one of those dependencies that elicits long sighs from developers, primarily due to the fact that it&#x27;s a C&#x2F;C++ library that honestly has no business being integrated into non-C&#x2F;C++ projects. It&#x27;s even caused &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;getzola&#x2F;zola&#x2F;issues&#x2F;1535&quot;&gt;headaches for the Zola maintainers&lt;&#x2F;a&gt;, outside the scope of my WASM port. I did make a solid effort to get it working; I played around with virtually every aspect of &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;WebAssembly&#x2F;wasi-sdk&quot;&gt;WASI SDK&lt;&#x2F;a&gt; in an attempt to get it to compile. The main issue I was running into was that the Rust crate &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;compass-rs&#x2F;sass-rs&quot;&gt;sass-rs&lt;&#x2F;a&gt; needed to link to the C++ standard library (sound familiar?). On Linux, this is usually provided by &lt;code&gt;libstdc++&lt;&#x2F;code&gt;. However, in WASI SDK, this is provided by &lt;code&gt;libc++&lt;&#x2F;code&gt;. I had to manually patch the &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;compass-rs&#x2F;sass-rs&#x2F;blob&#x2F;master&#x2F;sass-sys&#x2F;build.rs&quot;&gt;&lt;code&gt;build.rs&lt;&#x2F;code&gt;&lt;&#x2F;a&gt; file to always return &lt;code&gt;cargo:rustc-link-lib=dylib=c++&lt;&#x2F;code&gt; in an attempt to ensure it was linked correctly. Even though I was able to get things linking correctly on the Rust side, it would fail when compiling to WASM. As I&#x27;ve mentioned on this blog before I have limited patience when it comes to C code, so I eventually gave up and switched Zola&#x27;s SASS implementation to &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;connorskees&#x2F;grass&quot;&gt;&lt;code&gt;grass&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;, a Sass compiler written purely in Rust. It worked wonderfully, and only required the &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;getzola&#x2F;zola&#x2F;compare&#x2F;master...dstaley:zola:wasm#diff-488d6d4ecac2c5b0fe08ab8b601a946dce8a2cc4283229ef195bd24582a9ebfa&quot;&gt;smallest of changes&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;Once I had a WASM version of Zola, I then needed to wrap the module in a bit of setup code so that the node runtime could execute the module. This was (thankfully!) trivially easy. Here&#x27;s the complete implementation:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;js&quot; style=&quot;background-color:#2b303b;color:#6c7079;&quot; class=&quot;language-js &quot;&gt;&lt;code class=&quot;language-js&quot; data-lang=&quot;js&quot;&gt;&lt;span style=&quot;color:#9acc76;&quot;&gt;&amp;quot;use strict&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#cd74e8;&quot;&gt;const &lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;{ &lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;readFile &lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;} &lt;&#x2F;span&gt;&lt;span style=&quot;color:#adb7c9;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5ebfcc;&quot;&gt;require&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#9acc76;&quot;&gt;&amp;quot;node:fs&#x2F;promises&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;);
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#cd74e8;&quot;&gt;const &lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;{ &lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;WASI &lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;} &lt;&#x2F;span&gt;&lt;span style=&quot;color:#adb7c9;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5ebfcc;&quot;&gt;require&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#9acc76;&quot;&gt;&amp;quot;wasi&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;);
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#cd74e8;&quot;&gt;const &lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;{ &lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;env &lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;} &lt;&#x2F;span&gt;&lt;span style=&quot;color:#adb7c9;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5ebfcc;&quot;&gt;require&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#9acc76;&quot;&gt;&amp;quot;node:process&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;);
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#cd74e8;&quot;&gt;const &lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;{ &lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;join &lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;} &lt;&#x2F;span&gt;&lt;span style=&quot;color:#adb7c9;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5ebfcc;&quot;&gt;require&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#9acc76;&quot;&gt;&amp;quot;node:path&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;);
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;font-style:italic;color:#5f697a;&quot;&gt;&#x2F;**
&lt;&#x2F;span&gt;&lt;span style=&quot;font-style:italic;color:#5f697a;&quot;&gt; *
&lt;&#x2F;span&gt;&lt;span style=&quot;font-style:italic;color:#5f697a;&quot;&gt; * &lt;&#x2F;span&gt;&lt;span style=&quot;font-style:italic;color:#cd74e8;&quot;&gt;@param &lt;&#x2F;span&gt;&lt;span style=&quot;font-style:italic;color:#5f697a;&quot;&gt;{string} &lt;&#x2F;span&gt;&lt;span style=&quot;font-style:italic;color:#eb6772;&quot;&gt;siteDir&lt;&#x2F;span&gt;&lt;span style=&quot;font-style:italic;color:#5f697a;&quot;&gt; Path to Zola site, relative to the current working directory
&lt;&#x2F;span&gt;&lt;span style=&quot;font-style:italic;color:#5f697a;&quot;&gt; * &lt;&#x2F;span&gt;&lt;span style=&quot;font-style:italic;color:#cd74e8;&quot;&gt;@param &lt;&#x2F;span&gt;&lt;span style=&quot;font-style:italic;color:#5f697a;&quot;&gt;{string} &lt;&#x2F;span&gt;&lt;span style=&quot;font-style:italic;color:#eb6772;&quot;&gt;[baseUrl]
&lt;&#x2F;span&gt;&lt;span style=&quot;font-style:italic;color:#5f697a;&quot;&gt; *&#x2F;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;module.exports &lt;&#x2F;span&gt;&lt;span style=&quot;color:#adb7c9;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#cd74e8;&quot;&gt;async function &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5cb3fa;&quot;&gt;build&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;siteDir &lt;&#x2F;span&gt;&lt;span style=&quot;color:#adb7c9;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#9acc76;&quot;&gt;&amp;quot;.&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;baseUrl&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;) {
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;color:#cd74e8;&quot;&gt;let &lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;args &lt;&#x2F;span&gt;&lt;span style=&quot;color:#adb7c9;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;[&lt;&#x2F;span&gt;&lt;span style=&quot;color:#9acc76;&quot;&gt;&amp;quot;zola&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#9acc76;&quot;&gt;&amp;quot;--root&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#9acc76;&quot;&gt;&amp;quot;&#x2F;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#9acc76;&quot;&gt;&amp;quot;build&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;];
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;color:#cd74e8;&quot;&gt;if &lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;baseUrl&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;) {
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;args &lt;&#x2F;span&gt;&lt;span style=&quot;color:#adb7c9;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;[&lt;&#x2F;span&gt;&lt;span style=&quot;color:#adb7c9;&quot;&gt;...&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;args&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#9acc76;&quot;&gt;&amp;quot;--base-url&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;baseUrl&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;];
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;  }
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;color:#cd74e8;&quot;&gt;const &lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;wasi &lt;&#x2F;span&gt;&lt;span style=&quot;color:#adb7c9;&quot;&gt;= new &lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;WASI({
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;args&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;,
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;env&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;,
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;    preopens: {
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;      &lt;&#x2F;span&gt;&lt;span style=&quot;color:#9acc76;&quot;&gt;&amp;quot;&#x2F;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5cb3fa;&quot;&gt;join&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;(process.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5ebfcc;&quot;&gt;cwd&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;(), &lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;siteDir&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;),
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;    },
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;  });
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;color:#cd74e8;&quot;&gt;const &lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;importObject &lt;&#x2F;span&gt;&lt;span style=&quot;color:#adb7c9;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;{ wasi_snapshot_preview1: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;wasi&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;wasiImport &lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;};
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;color:#cd74e8;&quot;&gt;const &lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;wasm &lt;&#x2F;span&gt;&lt;span style=&quot;color:#adb7c9;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#cd74e8;&quot;&gt;await &lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;WebAssembly&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5ebfcc;&quot;&gt;compile&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;(
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#cd74e8;&quot;&gt;await &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5cb3fa;&quot;&gt;readFile&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5cb3fa;&quot;&gt;join&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;(__dirname, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#9acc76;&quot;&gt;&amp;quot;zola.wasm&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;))
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;  );
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;color:#cd74e8;&quot;&gt;const &lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;instance &lt;&#x2F;span&gt;&lt;span style=&quot;color:#adb7c9;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#cd74e8;&quot;&gt;await &lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;WebAssembly&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5cb3fa;&quot;&gt;instantiate&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;wasm&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;importObject&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;);
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;wasi&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5cb3fa;&quot;&gt;start&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;instance&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;);
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;};
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Once this was published to npm, all I needed to do to run this on Vercel was to point a &lt;code&gt;build&lt;&#x2F;code&gt; script in my &lt;code&gt;package.json&lt;&#x2F;code&gt; to the following file:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;js&quot; style=&quot;background-color:#2b303b;color:#6c7079;&quot; class=&quot;language-js &quot;&gt;&lt;code class=&quot;language-js&quot; data-lang=&quot;js&quot;&gt;&lt;span style=&quot;color:#cd74e8;&quot;&gt;import &lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;build &lt;&#x2F;span&gt;&lt;span style=&quot;color:#cd74e8;&quot;&gt;from &lt;&#x2F;span&gt;&lt;span style=&quot;color:#9acc76;&quot;&gt;&amp;quot;@dstaley&#x2F;zola-wasm&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#cd74e8;&quot;&gt;const &lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;baseUrl &lt;&#x2F;span&gt;&lt;span style=&quot;color:#adb7c9;&quot;&gt;=
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;  process.env.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;VERCEL_ENV &lt;&#x2F;span&gt;&lt;span style=&quot;color:#adb7c9;&quot;&gt;=== &lt;&#x2F;span&gt;&lt;span style=&quot;color:#9acc76;&quot;&gt;&amp;quot;production&amp;quot;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#adb7c9;&quot;&gt;? &lt;&#x2F;span&gt;&lt;span style=&quot;color:#9acc76;&quot;&gt;&amp;quot;https:&#x2F;&#x2F;dstaley.com&amp;quot;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#adb7c9;&quot;&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#9acc76;&quot;&gt;`https:&#x2F;&#x2F;${process.env.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;VERCEL_URL&lt;&#x2F;span&gt;&lt;span style=&quot;color:#9acc76;&quot;&gt;}`&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#cd74e8;&quot;&gt;await &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5cb3fa;&quot;&gt;build&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#9acc76;&quot;&gt;&amp;quot;.&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;baseUrl&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;);
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;I dropped that into the repo for this site, created a pull request, and was greeted with the following output in the build log on Vercel:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#6c7079;&quot;&gt;&lt;code&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;Cloning github.com&#x2F;dstaley&#x2F;dstaley.com (Branch: master, Commit: 78e53bf)
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;Cloning completed: 569.844ms
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;Restored build cache
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;Running &amp;quot;vercel build&amp;quot;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;Vercel CLI 28.10.0
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;Installing dependencies...
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;up to date in 172ms
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;&amp;gt; dstaley.com@0.0.0 build
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;&amp;gt; node --experimental-wasi-unstable-preview1 build.mjs
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;(node:328) ExperimentalWarning: WASI is an experimental feature. This feature could change at any time
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;(Use `node --trace-warnings ...` to show where the warning was created)
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;Building site...
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;Checking all internal links with anchors.
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;&amp;gt; Successfully checked 0 internal link(s) with anchors.
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;-&amp;gt; Creating 6 pages (0 orphan) and 1 sections
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;Done in 6.2s.
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;Build Completed in &#x2F;vercel&#x2F;output [14s]
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;Generated build outputs:
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt; - Static files: 40
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt; - Serverless Functions: 0
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt; - Edge Functions: 0
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;Deployed outputs in 1s
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;Build completed. Populating build cache...
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;Uploading build cache [4.35 MB]...
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;Build cache uploaded: 887.541ms
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;Done with &amp;quot;.&amp;quot;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;And with that I was able to build my site using the latest version of Zola on Vercel, despite the fact that Vercel didn&#x27;t have the latest version of glibc. The code for this is, of course, &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;dstaley&#x2F;zola&#x2F;tree&#x2F;wasm&quot;&gt;available on GitHub&lt;&#x2F;a&gt; and &lt;a href=&quot;https:&#x2F;&#x2F;www.npmjs.com&#x2F;package&#x2F;@dstaley&#x2F;zola-wasm&quot;&gt;on npm&lt;&#x2F;a&gt;. Was this a good idea? No, probably not.&lt;&#x2F;p&gt;
&lt;p&gt;But it was a hell of a lot of fun.&lt;&#x2F;p&gt;
</content:encoded>
            </item>
            <item>
                <title>Hello iPod, Welcome to 2023</title>
                <pubDate>Tue, 03 Jan 2023 12:00:00 -0800</pubDate>
                <link>https://dstaley.com/posts/hello-ipod-welcome-to-2023/</link>
                <guid>https://dstaley.com/posts/hello-ipod-welcome-to-2023/</guid>
                <description>Or, how spending four years refusing to use software written in C can actually pay off.</description>
                <content:encoded>&lt;p&gt;My first iPod was a silver iPod mini, given to me by my mom when I was around twelve years old. It wasn&#x27;t my first portable audio player (that title going to a cheap flash-based MP3 player that, to this day, I&#x27;ve been unable to find out the model number), but it was undoubtedly the device that sparked an infatuation that remains to this day, over seventeen years later. I&#x27;ve owned a few different iPods since then, ending in a PRODUCT(RED) third generation iPod nano I purchased in my freshman year of high school, which would be the last iPod I owned before the smartphone became a thing.&lt;&#x2F;p&gt;
&lt;p&gt;That is, until the summer of 2017.&lt;&#x2F;p&gt;
&lt;p&gt;At the time, I was driving a car that, despite being made in 2014, still came with a USB port that you could plug an iPod into. I had grown tired of the instability of Bluetooth, and desperately missed having album art show up on my car&#x27;s display. Armed with an annoyance and a modicum of programming knowledge (a terrible combination I&#x27;m afraid), I bought a fifth generation iPod and set out to see if I could figure out how to synchronize my meticulously curated Plex music library.&lt;&#x2F;p&gt;
&lt;p&gt;When you consider the fact that the iPod has sold over 450 million devices since its introduction in 2001, you would think that there would be an abundance of ways to programmatically add music to an iPod. Unfortunately, that&#x27;s not the case. Given that each subsequent generation of iPods modified the internal database structure, few projects managed to keep up with the changes enough to support later models of iPods. Even if you managed to keep up, &lt;a href=&quot;https:&#x2F;&#x2F;arstechnica.com&#x2F;gadgets&#x2F;2008&#x2F;11&#x2F;apple-lawyers-hand-ipod-hash-cracking-site-a-dmca-notice&#x2F;&quot;&gt;Apple apparently sued developers&lt;&#x2F;a&gt; for figuring out how to correctly sign the iPod&#x27;s database file. That being said, there are a few options when it comes to open-source software to interface with an iPod.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;libgpod&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;p&gt;libgpod is a C library used in software such as &lt;a href=&quot;https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;Gtkpod&quot;&gt;gtkpod&lt;&#x2F;a&gt;, &lt;a href=&quot;https:&#x2F;&#x2F;wiki.gnome.org&#x2F;Apps&#x2F;Rhythmbox&quot;&gt;Rhythmbox&lt;&#x2F;a&gt;, and &lt;a href=&quot;https:&#x2F;&#x2F;amarok.kde.org&#x2F;&quot;&gt;Amarok&lt;&#x2F;a&gt;. Like most software written in C, this was an absolute nightmare to get working (for some definition of working that is). At the time I was using a macOS device, which wasn&#x27;t &lt;em&gt;technically&lt;&#x2F;em&gt; supported by libgod, so I guess I was setting myself up for failure there. I tried various Python bindings, and even wrote some C code at one point. Desperate, I even tried getting it to run in Docker on my MacBook, but ultimately was unsuccessful in getting everything working; if I recall correctly, the main issue I was facing was a lack of album art, which was one of the primary motivators for even undertaking this adventure in the first place.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;ipod-sharp&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;p&gt;&lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;mono&#x2F;ipod-sharp&quot;&gt;ipod-sharp&lt;&#x2F;a&gt; is a C# library originally used for &lt;a href=&quot;http:&#x2F;&#x2F;www.banshee-project.org&#x2F;&quot;&gt;Banshee&lt;&#x2F;a&gt; (before it switched to libgpod). Being written in C# and targeting Mono, the original cross-platform implementation of .NET, it was my hope that it&#x27;d be relatively easy to get running on macOS. Alas, the odds were not in my favor, and I was never able to get it running in a project, largely due to its use of Linux&#x27;s &lt;a href=&quot;https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;HAL_(software)&quot;&gt;HAL&lt;&#x2F;a&gt;, which has been deprecated.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;AppleScript&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Desperately grasping at straws, I eventually turned to AppleScript, the built-in scripting language that&#x27;s a part of macOS. AppleScript allows for automating a wide variety of applications, including iTunes. While I was ultimately successful at copying a song to my iPod, I was never able to get album art to transfer. Plus, AppleScript was a macOS-only solution, and I really wanted something that would work on Windows, which I was planning to switch to at the time.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;gnupod&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Finally, I came across &lt;a href=&quot;https:&#x2F;&#x2F;www.gnu.org&#x2F;software&#x2F;gnupod&#x2F;&quot;&gt;gnupod&lt;&#x2F;a&gt;, a tool written in Perl. Getting this running on macOS was a chore, so I eventually decided to take the easy route and use Docker to run the scripts provided by gnupod. With a smidge of Python, I was finally able to download my music from my Plex server, convert it to an iPod-compatible format using ffmpeg, and transfer it to the iPod via gnupod. Album art, one of the main reasons I wanted to use an iPod in the first place, was finally something I could handle!&lt;&#x2F;p&gt;
&lt;p&gt;&lt;code&gt;gnupod&lt;&#x2F;code&gt; worked wonderfully for over a year. I ended up using it right until I got a new car, which sadly didn&#x27;t support iPod connections anymore.&lt;&#x2F;p&gt;
&lt;p&gt;During my desperate search of the internet, I came across a mention on StackOverflow of an application called &lt;a href=&quot;https:&#x2F;&#x2F;stackoverflow.com&#x2F;questions&#x2F;901319&#x2F;apple-ipod-and-c-sharp&quot;&gt;SharePod&lt;&#x2F;a&gt;. This Windows application allowed users to manage their iPods without using iTunes, and the developer had kindly provided the core management functions in an open-source library called &lt;code&gt;sharepod-lib&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;The only problem was that, contrary to the popular saying, it seemed the internet &lt;em&gt;did&lt;&#x2F;em&gt; forget about sharepod-lib.&lt;&#x2F;p&gt;
&lt;p&gt;Originally uploaded to &lt;a href=&quot;https:&#x2F;&#x2F;web.archive.org&#x2F;web&#x2F;20111028035037&#x2F;code.google.com&#x2F;p&#x2F;sharepod-lib&quot;&gt;Google Code in 2010&lt;&#x2F;a&gt;, &lt;code&gt;sharepod-lib&lt;&#x2F;code&gt; never found its way into any of the archives created of Google Code when it shut down in 2016. I combed through every possible location that might have retained a copy, even going so far as to run a paid &lt;a href=&quot;https:&#x2F;&#x2F;twitter.com&#x2F;dstaley&#x2F;status&#x2F;1095488284178796545&quot;&gt;Twitter campaign&lt;&#x2F;a&gt; in the hopes of tracking down someone who happened to have the repo.&lt;&#x2F;p&gt;
&lt;p&gt;Every couple of months, I&#x27;d get interested in trying to recover a copy of sharepod-lib but would never make any progress. That would change on March 22, 2022.&lt;&#x2F;p&gt;
&lt;p&gt;I can&#x27;t remember what specifically made me change my search tactics, but I somehow came across the website for &lt;a href=&quot;https:&#x2F;&#x2F;supersync.com&#x2F;&quot;&gt;SuperSync&lt;&#x2F;a&gt;, an application that allowed you to synchronize multiple iTunes libraries. At one point in time, SuperSync relied on &lt;code&gt;sharepod-lib&lt;&#x2F;code&gt; to power its iPod functionality, and given that this was a third-party application, there was a small chance that the application used the source code of &lt;code&gt;sharepod-lib&lt;&#x2F;code&gt; rather than the compiled DLL. So, with my fingers crossed, I emailed the creator, Brad:&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;Hi there!&lt;&#x2F;p&gt;
&lt;p&gt;I noticed on your license page that you mention the usage of SharePodLib, a library that required a paid license for use in commercial products. I&#x27;m curious if when you acquired a license to use SharePodLib in a commercial product, you also got access to the source code for SharePodLib? While it was open sourced at one point, I&#x27;m afraid it&#x27;s been lost to time, so I&#x27;ve been reaching out to people known to have used it in the past in the hopes that someone has a copy of the source.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;The next day, I received the following response from Brad:&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;Hi Dylan,&lt;&#x2F;p&gt;
&lt;p&gt;Here is a link to the two copies of the source code I have. One marked &quot;newer&quot; whatever that means. I think for a while I might have had a windows command line tool to connect to ipods. But gave up when Apple locked things down.&lt;&#x2F;p&gt;
&lt;p&gt;https:&#x2F;&#x2F;drive.google.com&#x2F;file&#x2F;d&#x2F;...&lt;&#x2F;p&gt;
&lt;p&gt;I hope that helps!&lt;&#x2F;p&gt;
&lt;p&gt;- Brad&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;I can still remember my heart racing reading this email. I quickly downloaded the linked file and was greeted with the full directory structure for &lt;code&gt;sharepod-lib&lt;&#x2F;code&gt;, a directory structure I must have looked at hundreds of times using Wayback Machine&#x27;s capture of Google Code.&lt;&#x2F;p&gt;
&lt;p&gt;I immediately made several backups, just in case.&lt;&#x2F;p&gt;
&lt;p&gt;I spent the following few weeks updating &lt;code&gt;sharepod-lib&lt;&#x2F;code&gt; to run on the modern, cross-platform version of .NET. Honestly, I was surprised at just how little I had to change to get things up and running. The primary changes were replacing old platform-specific libraries like SQLite and System.Drawing with modern, cross-platform versions. To ensure things worked across a variety of iPods, I amassed a &lt;a href=&quot;https:&#x2F;&#x2F;mastodon.social&#x2F;@dstaley&#x2F;108251051303316277&quot;&gt;small collection of nearly every model&lt;&#x2F;a&gt;. Knowing that there was finally going to be a modern, easily accessible way to manage iPods was something that really drove me over those few weeks. As a test run for the library, I built a &lt;a href=&quot;https:&#x2F;&#x2F;twitter.com&#x2F;dstaley&#x2F;status&#x2F;1527770384723898369&quot;&gt;prototype battery benchmark&lt;&#x2F;a&gt; that would copy a specific copyleft album to your connected iPod, create a playlist, and allow you to repeat the playlist until the battery died. The benchmark would then calculate the runtime based on the number of plays for each song, giving you an estimate of the battery life for your iPod. Once I finished updating the library, I had planned on recreating my original Plex sync application, allowing anyone to easily sync their iPod with their Plex music library, as I had done back in 2017.&lt;&#x2F;p&gt;
&lt;p&gt;Sometimes, however, motivation is a fickle thing. As life got in the way, I found myself less and less enthused about spending hours writing unit tests and modernizing parts of the library. There was also the issue of accessing the iPod&#x27;s extended SysInfo, a collection of data attributes necessary for album artwork support and creating valid database hashes (without which the iPod wouldn&#x27;t parse the database). On Windows, it was relatively easy to issue the appropriate SCSI INQUIRY commands using an Administrator account (the same being true for Linux), but on modern versions of macOS that capability was reserved for kernel drivers. I spent many nights fighting with libusb, a userspace USB API in an attempt to recreate enough of the USB Mass Storage protocol to obtain the information necessary but was never able to get anything working reliably despite trying to write the application in C, Go, and Rust. This roadblock considerably dampened my motivations, and eventually I stopped working on the project completely. Looking at the files on my computer, I haven&#x27;t updated anything in nearly six months.&lt;&#x2F;p&gt;
&lt;p&gt;So, as they say, if you love something, set it free.&lt;&#x2F;p&gt;
&lt;p&gt;That&#x27;s why I&#x27;m super excited to announce that I&#x27;ve made &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;dstaley&#x2F;sharepod-lib&quot;&gt;&lt;code&gt;sharepod-lib&lt;&#x2F;code&gt;&lt;&#x2F;a&gt; and its modern successor &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;dstaley&#x2F;clickwheel&quot;&gt;&lt;code&gt;Clickwheel&lt;&#x2F;code&gt;&lt;&#x2F;a&gt; available on GitHub under the Lesser GNU Public License. It&#x27;s my hope that someone finds one (or both!) of them useful and as inspiring to them as it has been to me over the last several years.&lt;&#x2F;p&gt;
&lt;p&gt;I do have to give a note of warning though: it&#x27;s in fairly rough shape. While there are some unit tests, most of my tests were written using disk images of my iPods, complete with copyrighted music. Because I have no desire whatsoever to have the repo removed because of a Digital Millenium Copyright Act violation, I&#x27;ve removed the tests that relied on copyrighted music. There are still some tests left, but I know they&#x27;re not exhaustive. Clickwheel is in no way a production library, and it might never be, but I figured it&#x27;ll do more good out there in the public than it would do sitting on my hard drive.&lt;&#x2F;p&gt;
&lt;p&gt;It&#x27;s my hope that by releasing this to the public, there might one day be an easy-to-use library to programmatically manage your iPod.&lt;&#x2F;p&gt;
&lt;p&gt;And it won&#x27;t have anything to do with C.&lt;&#x2F;p&gt;
</content:encoded>
            </item>
            <item>
                <title>Tailscale on Kobo Sage</title>
                <pubDate>Mon, 22 Nov 2021 12:00:00 -0800</pubDate>
                <link>https://dstaley.com/posts/tailscale-on-kobo-sage/</link>
                <guid>https://dstaley.com/posts/tailscale-on-kobo-sage/</guid>
                <description>It&#x27;s basically a rite of passage for Tailscale users to put Tailscale on the weirdest device they own.</description>
                <content:encoded>&lt;p&gt;Last month, Rakuten finally released a reasonably-sized Kobo with USB C, the Kobo Sage. One of the most fascinating things about the Kobo eReaders (compared to the Kindle eReaders) is that they run a user-modifiable version of Linux, meaning you&#x27;re free to do all sorts of weird and wild things with them.&lt;&#x2F;p&gt;
&lt;p&gt;So I decided to put &lt;a href=&quot;https:&#x2F;&#x2F;tailscale.com&quot;&gt;Tailscale&lt;&#x2F;a&gt; on my Kobo Sage.&lt;&#x2F;p&gt;
&lt;p&gt;Tailscale is an easy-to-use VPN(ish) service that allows you to connect multiple devices over the internet to a secure, shared network. Since it&#x27;s is distributed as statically-compiled binaries (thanks Go!), getting Tailscale downloaded and running on Kobo Sage was as easy as downloading the binaries, copying them to the onboard storage (since the root filesystem is only about 300 MB), symlinking them into a &lt;code&gt;$PATH&lt;&#x2F;code&gt; friendly folder, and running &lt;code&gt;tailscaled&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;sh&quot; style=&quot;background-color:#2b303b;color:#6c7079;&quot; class=&quot;language-sh &quot;&gt;&lt;code class=&quot;language-sh&quot; data-lang=&quot;sh&quot;&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;wget&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt; https:&#x2F;&#x2F;pkgs.tailscale.com&#x2F;stable&#x2F;tailscale_1.18.0_arm.tgz
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;tar -xzf&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt; tailscale_1.18.0_arm.tgz
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;mv&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt; tailscale_1.18.0_arm&#x2F;tailscaled &#x2F;mnt&#x2F;onboard
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;mv&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt; tailscale_1.18.0_arm&#x2F;tailscale &#x2F;mnt&#x2F;onboard
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;ln -s&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt; &#x2F;mnt&#x2F;onboard&#x2F;tailscale &#x2F;usr&#x2F;bin&#x2F;tailscale
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;ln -s&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt; &#x2F;mnt&#x2F;onboard&#x2F;tailscaled &#x2F;usr&#x2F;bin&#x2F;tailscaled
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;rm -rf&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt; tailscale_1.18.0_arm
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;rm&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt; tailscale_1.18.0_arm.tgz
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;tailscaled
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;However, if you were to do this, you&#x27;ll be greeted with this:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#6c7079;&quot;&gt;&lt;code&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;wgengine.NewUserspaceEngine(tun &amp;quot;tailscale0&amp;quot;) error: exec: &amp;quot;iptables&amp;quot;: executable file not found in $PATH
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;wgengine.New: exec: &amp;quot;iptables&amp;quot;: executable file not found in $PATH
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;While the Linux 4.9 kernel used by Kobo Sage supports &lt;code&gt;iptables&lt;&#x2F;code&gt;, the actual binary is nowhere to be found on the device. Thankfully, acquiring a suitable binary isn&#x27;t too terribly difficult.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;getting-iptables&quot;&gt;Getting &lt;code&gt;iptables&lt;&#x2F;code&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;Unfortunately, there&#x27;s no officially distributed pre-compiled binaries of &lt;code&gt;iptables&lt;&#x2F;code&gt;. So, we can either compile from source (either &lt;a href=&quot;https:&#x2F;&#x2F;gist.github.com&#x2F;dstaley&#x2F;49477b0f12ff79156bb02318ecdce028&quot;&gt;on a 32-bit ARM device&lt;&#x2F;a&gt; or via cross-compilation), or we can yank the binary out of an image of a 32-bit ARM Linux distro. We need a distro that&#x27;s old enough to be using &lt;code&gt;glibc&lt;&#x2F;code&gt; version less than 2.19, so the best choice I came up with was the July 5, 2017 release of Raspbian, the last to be based on Debian 8 (codename &lt;code&gt;jessie&lt;&#x2F;code&gt;).&lt;&#x2F;p&gt;
&lt;p&gt;Using 7zip for Windows, I extracted the following files from the disk image:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;&#x2F;sbin&#x2F;xtables-multi&lt;&#x2F;code&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;&#x2F;lib&#x2F;libip4tc.so.0.1.0&lt;&#x2F;code&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;&#x2F;lib&#x2F;libip6tc.so.0.1.0&lt;&#x2F;code&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;&#x2F;lib&#x2F;libxtables.so.10.0.0&lt;&#x2F;code&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;&lt;em&gt;Note: You can also extract these files on Linux, but it&#x27;s &lt;a href=&quot;https:&#x2F;&#x2F;stackoverflow.com&#x2F;questions&#x2F;40356259&#x2F;mount-linux-image-in-docker-container&quot;&gt;a bit more complicated&lt;&#x2F;a&gt;.&lt;&#x2F;em&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Now that we had a binary, it was time to put it on the device.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;installing-iptables&quot;&gt;Installing &lt;code&gt;iptables&lt;&#x2F;code&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;Out of the box, Kobo Sage doesn&#x27;t support &lt;code&gt;ssh&lt;&#x2F;code&gt;, but it does support &lt;code&gt;telnet&lt;&#x2F;code&gt; after &lt;a href=&quot;https:&#x2F;&#x2F;goodereader.com&#x2F;blog&#x2F;kobo-ereader-news&#x2F;how-to-access-the-secret-kobo-developer-options&quot;&gt;enabliing dev mode&lt;&#x2F;a&gt;. Since I couldn&#x27;t use &lt;code&gt;scp&lt;&#x2F;code&gt; to copy files to the device, I ran a web server on my computer, and downloaded the files with &lt;code&gt;wget&lt;&#x2F;code&gt;:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;sh&quot; style=&quot;background-color:#2b303b;color:#6c7079;&quot; class=&quot;language-sh &quot;&gt;&lt;code class=&quot;language-sh&quot; data-lang=&quot;sh&quot;&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;wget&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt; http:&#x2F;&#x2F;callisto.localdomain:8000&#x2F;xtables-multi
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;wget&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt; http:&#x2F;&#x2F;callisto.localdomain:8000&#x2F;libip4tc.so.0.1.0
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;wget&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt; http:&#x2F;&#x2F;callisto.localdomain:8000&#x2F;libip6tc.so.0.1.0
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;wget&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt; http:&#x2F;&#x2F;callisto.localdomain:8000&#x2F;libxtables.so.10.0.0
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Once I had the files, I was able to move them into the appropriate locations:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;sh&quot; style=&quot;background-color:#2b303b;color:#6c7079;&quot; class=&quot;language-sh &quot;&gt;&lt;code class=&quot;language-sh&quot; data-lang=&quot;sh&quot;&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;mv&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt; xtables-multi &#x2F;sbin
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;mv&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt; libip4tc.so.0.1.0 &#x2F;lib
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;mv&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt; libip6tc.so.0.1.0 &#x2F;lib
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;mv&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt; libxtables.so.10.0.0 &#x2F;lib
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Since we need &lt;code&gt;iptables&lt;&#x2F;code&gt; in &lt;code&gt;$PATH&lt;&#x2F;code&gt;, I created a symlink in &lt;code&gt;&#x2F;sbin&lt;&#x2F;code&gt;:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;sh&quot; style=&quot;background-color:#2b303b;color:#6c7079;&quot; class=&quot;language-sh &quot;&gt;&lt;code class=&quot;language-sh&quot; data-lang=&quot;sh&quot;&gt;&lt;span style=&quot;color:#5ebfcc;&quot;&gt;cd&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt; &#x2F;sbin
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;ln -s&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt; xtables-multi iptables
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;We also need symlinks for the libraries, so I ran the following in &lt;code&gt;&#x2F;lib&lt;&#x2F;code&gt;:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;sh&quot; style=&quot;background-color:#2b303b;color:#6c7079;&quot; class=&quot;language-sh &quot;&gt;&lt;code class=&quot;language-sh&quot; data-lang=&quot;sh&quot;&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;ln -s&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt; libxtables.so.10.0.0 libxtables.so.10
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;ln -s&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt; libip4tc.so.0.1.0 libip4tc.so.0
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;ln -s&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt; libip6tc.so.0.1.0 libip6tc.so.0
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;chmod&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt; 755 libxtables.so.10.0.0
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;chmod&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt; 755 libip4tc.so.0
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;chmod&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt; 755 libip6tc.so.0
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Once I had the binary and libraries in the right spots, I was able to confirm that &lt;code&gt;iptables&lt;&#x2F;code&gt; worked:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;sh&quot; style=&quot;background-color:#2b303b;color:#6c7079;&quot; class=&quot;language-sh &quot;&gt;&lt;code class=&quot;language-sh&quot; data-lang=&quot;sh&quot;&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;iptables --version
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;h2 id=&quot;testing-tailscale&quot;&gt;Testing Tailscale&lt;&#x2F;h2&gt;
&lt;p&gt;With the &lt;code&gt;iptables&lt;&#x2F;code&gt; binary working, confirming Tailscale works was the next step. By default &lt;code&gt;tailscaled&lt;&#x2F;code&gt; stores some state in &lt;code&gt;&#x2F;var&#x2F;lib&lt;&#x2F;code&gt;, which the Kobo doesn&#x27;t like. So, for the purposes of testing, I just manually told &lt;code&gt;tailscaled&lt;&#x2F;code&gt; to store its state in a file at the root directory.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;sh&quot; style=&quot;background-color:#2b303b;color:#6c7079;&quot; class=&quot;language-sh &quot;&gt;&lt;code class=&quot;language-sh&quot; data-lang=&quot;sh&quot;&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;tailscaled --state&lt;&#x2F;span&gt;&lt;span style=&quot;color:#adb7c9;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;tailscaled.state
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;In another telnet session, I was able to bring Tailscale up with&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;sh&quot; style=&quot;background-color:#2b303b;color:#6c7079;&quot; class=&quot;language-sh &quot;&gt;&lt;code class=&quot;language-sh&quot; data-lang=&quot;sh&quot;&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;tailscale&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt; up
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;After authenticating, the final step was to open a Tailscale-only URL in the Kobo&#x27;s experimental web browser to confirm that I was, indeed, connected to my Tailscale network!&lt;&#x2F;p&gt;
&lt;a href=&quot;&#x2F;img&#x2F;tailscale-on-kobo.jpg&quot;&gt;
    &lt;img
        alt=&quot;Kobo Sage displaying a webpage with the heading You&#x27;re connected over Tailscale&quot;
        loading=&quot;lazy&quot;
        srcset=&quot;
            
&amp;#x2F;&amp;#x2F;dstaley.com&amp;#x2F;processed_images&amp;#x2F;tailscale-on-kobo.000210c8c898f3c2.jpg,
            
&amp;#x2F;&amp;#x2F;dstaley.com&amp;#x2F;processed_images&amp;#x2F;tailscale-on-kobo.caa95e82563bf0a5.jpg 2x
        &quot;
        src=&quot;
&amp;#x2F;&amp;#x2F;dstaley.com&amp;#x2F;processed_images&amp;#x2F;tailscale-on-kobo.000210c8c898f3c2.jpg&quot;
        width=&quot;800&quot;
        height=&quot;1066&quot;
    &#x2F;&gt;
&lt;&#x2F;a&gt;
&lt;h2 id=&quot;staying-connected-to-tailscale&quot;&gt;Staying Connected to Tailscale&lt;&#x2F;h2&gt;
&lt;p&gt;Getting Tailscale up and running wasn&#x27;t as smooth as on a typical Linux device, but &lt;em&gt;staying connected&lt;&#x2F;em&gt; to Tailscale is an even trickier thing to do. Kobo Sage doesn&#x27;t have &lt;code&gt;systemd&lt;&#x2F;code&gt;, so we can&#x27;t use the included service definition provided by Tailscale. To further complicate matters, if we try to hook into the lower levels of the OS and make a mistake, we can end up with a bricked device that&#x27;s unable to be recovered. Thankfully, there&#x27;s been a ton of exploration in this area by other developers (shoutout to the &lt;a href=&quot;https:&#x2F;&#x2F;www.mobileread.com&#x2F;forums&#x2F;forumdisplay.php?f=247&quot;&gt;Kobo Developer&#x27;s Corner on the MobileRead Forums&lt;&#x2F;a&gt;), and the generally agreed upon way of running things at boot is by &lt;a href=&quot;https:&#x2F;&#x2F;www.mobileread.com&#x2F;forums&#x2F;showpost.php?p=4166247&amp;amp;postcount=2&quot;&gt;using the udev system&lt;&#x2F;a&gt;. This allows us not only to hook into a startup event, but also into events that are triggered when Kobo Sage connects to and disconnects from WiFi. This allows us to run &lt;code&gt;tailscale up&lt;&#x2F;code&gt; when we have an internet connection, and &lt;code&gt;tailscale down&lt;&#x2F;code&gt; when we lose the connection.&lt;&#x2F;p&gt;
&lt;p&gt;To start, I defined the udev rules in a file named &lt;code&gt;98-tailscale.rules&lt;&#x2F;code&gt; in &lt;code&gt;&#x2F;etc&#x2F;udev&#x2F;rules.d&lt;&#x2F;code&gt;&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#6c7079;&quot;&gt;&lt;code&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;KERNEL==&amp;quot;loop0&amp;quot;, RUN+=&amp;quot;&#x2F;usr&#x2F;local&#x2F;dstaley&#x2F;boot.sh&amp;quot;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;KERNEL==&amp;quot;wlan*&amp;quot;, ACTION==&amp;quot;add&amp;quot;, RUN+=&amp;quot;&#x2F;usr&#x2F;local&#x2F;dstaley&#x2F;on-wlan-up.sh&amp;quot;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;KERNEL==&amp;quot;wlan*&amp;quot;, ACTION==&amp;quot;remove&amp;quot;, RUN+=&amp;quot;&#x2F;usr&#x2F;local&#x2F;dstaley&#x2F;on-wlan-down.sh&amp;quot;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The &lt;code&gt;KERNEL==&quot;loop0&quot;&lt;&#x2F;code&gt; rule runs roughly at boot, so that&#x27;s where I start &lt;code&gt;tailscaled&lt;&#x2F;code&gt;. The next two rules are for when the WiFi is brought up and torn down respectively.&lt;&#x2F;p&gt;
&lt;p&gt;The script for starting &lt;code&gt;tailscaled&lt;&#x2F;code&gt; is straightforward. The catch, though (and with Kobo there&#x27;s &lt;em&gt;always&lt;&#x2F;em&gt; a catch), is that the OS will normally kill long-running tasks spawned by udev. To circumvent that, I used a combination of &lt;code&gt;renice&lt;&#x2F;code&gt; and &lt;code&gt;setsid&lt;&#x2F;code&gt; to ensure that &lt;code&gt;tailscaled&lt;&#x2F;code&gt; isn&#x27;t killed off.&lt;&#x2F;p&gt;
&lt;p&gt;The &lt;code&gt;boot.sh&lt;&#x2F;code&gt; script contains the following:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;sh&quot; style=&quot;background-color:#2b303b;color:#6c7079;&quot; class=&quot;language-sh &quot;&gt;&lt;code class=&quot;language-sh&quot; data-lang=&quot;sh&quot;&gt;&lt;span style=&quot;font-style:italic;color:#5f697a;&quot;&gt;#!&#x2F;bin&#x2F;sh
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;font-style:italic;color:#5f697a;&quot;&gt;# Start by renicing ourselves to a neutral value, to avoid any mishap...
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;renice&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt; 0&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt; -p &lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;$&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;$
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;font-style:italic;color:#5f697a;&quot;&gt;# Launch in the background, with a clean env, after a setsid call to make very very sure udev won&amp;#39;t kill us ;).
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;env -i&lt;&#x2F;span&gt;&lt;span style=&quot;color:#adb7c9;&quot;&gt; --&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt; setsid &#x2F;usr&#x2F;local&#x2F;dstaley&#x2F;on-boot.sh &lt;&#x2F;span&gt;&lt;span style=&quot;color:#adb7c9;&quot;&gt;&amp;amp;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;font-style:italic;color:#5f697a;&quot;&gt;# Done :)
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5ebfcc;&quot;&gt;exit&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt; 0
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This script runs &lt;code&gt;on-boot.sh&lt;&#x2F;code&gt; in the background. That script contains:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;sh&quot; style=&quot;background-color:#2b303b;color:#6c7079;&quot; class=&quot;language-sh &quot;&gt;&lt;code class=&quot;language-sh&quot; data-lang=&quot;sh&quot;&gt;&lt;span style=&quot;font-style:italic;color:#5f697a;&quot;&gt;#!&#x2F;bin&#x2F;sh
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;font-style:italic;color:#5f697a;&quot;&gt;# make absolutely sure that iptables is in the PATH
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#cd74e8;&quot;&gt;export &lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;PATH&lt;&#x2F;span&gt;&lt;span style=&quot;color:#adb7c9;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color:#9acc76;&quot;&gt;&#x2F;usr&#x2F;sbin:&#x2F;usr&#x2F;bin:$&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;PATH
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;font-style:italic;color:#5f697a;&quot;&gt;# make sure &#x2F;mnt&#x2F;onboard is mounted
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;timeout&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt; 5 sh&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt; -c &lt;&#x2F;span&gt;&lt;span style=&quot;color:#9acc76;&quot;&gt;&amp;quot;while ! grep &#x2F;mnt&#x2F;onboard &#x2F;proc&#x2F;mounts; do sleep 0.1; done&amp;quot;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#cd74e8;&quot;&gt;if &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5ebfcc;&quot;&gt;[[ &lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;$&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;? -eq&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt; 143 &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5ebfcc;&quot;&gt;]]&lt;&#x2F;span&gt;&lt;span style=&quot;color:#adb7c9;&quot;&gt;; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#cd74e8;&quot;&gt;then
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5ebfcc;&quot;&gt;exit&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt; 1
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#cd74e8;&quot;&gt;fi
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#cd74e8;&quot;&gt;case &lt;&#x2F;span&gt;&lt;span style=&quot;color:#9acc76;&quot;&gt;&amp;quot;$(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;pidof&lt;&#x2F;span&gt;&lt;span style=&quot;color:#9acc76;&quot;&gt; tailscaled &lt;&#x2F;span&gt;&lt;span style=&quot;color:#adb7c9;&quot;&gt;| &lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;wc -w&lt;&#x2F;span&gt;&lt;span style=&quot;color:#9acc76;&quot;&gt;)&amp;quot; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#cd74e8;&quot;&gt;in
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;0&lt;&#x2F;span&gt;&lt;span style=&quot;color:#cd74e8;&quot;&gt;) &lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;tailscaled --state&lt;&#x2F;span&gt;&lt;span style=&quot;color:#adb7c9;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;&#x2F;tailscaled.state &lt;&#x2F;span&gt;&lt;span style=&quot;color:#adb7c9;&quot;&gt;&amp;amp;&amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt; &#x2F;tailscaled.log &lt;&#x2F;span&gt;&lt;span style=&quot;color:#adb7c9;&quot;&gt;&amp;amp;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;   ;;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#cd74e8;&quot;&gt;esac
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5ebfcc;&quot;&gt;exit&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt; 0
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Since we&#x27;re not starting a long running process on connecting and disconnecting to WiFi, the scripts &lt;code&gt;on-wlan-up.sh&lt;&#x2F;code&gt; and &lt;code&gt;on-wlan-down.sh&lt;&#x2F;code&gt; only contain a single command: &lt;code&gt;tailscale up&lt;&#x2F;code&gt; and &lt;code&gt;tailscale down&lt;&#x2F;code&gt; respectively.&lt;&#x2F;p&gt;
&lt;p&gt;After placing these files in their new homes and rebooting, I can now access my Kobo Sage via my Tailscale network! This also means that I can access other devices on my network, which will come in handy in the future when I want to do things like backup my notebooks to my NAS at home.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;other-kobo-devices&quot;&gt;Other Kobo Devices&lt;&#x2F;h2&gt;
&lt;p&gt;Since Rakuten reuses a lot of the same software for all of their Kobo devices, there&#x27;s a decent chance that these steps will work on any Kobo. If you do decide to go that route, you&#x27;ll need to verify that your Kobo&#x27;s kernel was compiled with &lt;code&gt;iptables&lt;&#x2F;code&gt; support, and that the included glibc version is at least equal to the glibc version used to compile the &lt;code&gt;iptables&lt;&#x2F;code&gt; binary.&lt;&#x2F;p&gt;
</content:encoded>
            </item>
            <item>
                <title>Christmas with Dart</title>
                <pubDate>Tue, 08 Jan 2019 12:00:00 -0800</pubDate>
                <link>https://dstaley.com/posts/christmas-with-dart/</link>
                <guid>https://dstaley.com/posts/christmas-with-dart/</guid>
                <description>The exciting tale of how I started to build a Flutter app but ended up with three Dart libraries instead</description>
                <content:encoded>&lt;p&gt;Over the Christmas holiday, I sat down to work on a project I&#x27;ve been attempting to get going for several months now. I&#x27;m a light &lt;a href=&quot;https:&#x2F;&#x2F;pinboard.in&quot;&gt;Pinboard&lt;&#x2F;a&gt; user, and I&#x27;d love a minimal Pinboard client for Android that makes it easy to bookmark sites I find for safe keeping. Recently, Google&#x27;s &lt;a href=&quot;https:&#x2F;&#x2F;flutter.io&#x2F;&quot;&gt;Flutter&lt;&#x2F;a&gt; released their first 1.0 release, so I decided to take a look in lieu of reaching for my typical choice of React Native.&lt;&#x2F;p&gt;
&lt;p&gt;First off, I didn&#x27;t really understand what made Flutter impressive until I learned more about what Flutter actually is. Essentially, Flutter is a natively-compiled version of &lt;a href=&quot;https:&#x2F;&#x2F;skia.org&#x2F;&quot;&gt;Skia&lt;&#x2F;a&gt;, a 2D graphics library used in Chrome, Firefox, Android, and more. Paired with Skia is an ahead-of-time compiled version of a Dart application that communicates with Skia to draw your applications UI. Flutter also handles integrating things like native platform widgets that would be cumbersome to recreate in Skia (like Google Maps embeds or a WebView). When you stop and think about it, Flutter is almost like a web browser in that it executes arbitrary UI code and renders it, while also connecting to the underlying platform.&lt;&#x2F;p&gt;
&lt;p&gt;It&#x27;s basically a native version of Electron. Basically. If you squint enough.&lt;&#x2F;p&gt;
&lt;p&gt;What makes me most excited about Flutter is the &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;google&#x2F;flutter-desktop-embedding&quot;&gt;Flutter Desktop Embedding Engine&lt;&#x2F;a&gt;, which provides a way to run Skia and Flutter on virtually any platform. I don&#x27;t think it&#x27;s quite ready for people to start building with it yet, but pretty soon you&#x27;ll be able to run Flutter apps on Android, iOS, Windows, macOS, and Linux using 100% native code. While I don&#x27;t think this will completely displace Electron, I can easily see it becoming a better choice for smaller, desktop-focused applications.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;but-what-about-dart&quot;&gt;But what about Dart?&lt;&#x2F;h2&gt;
&lt;p&gt;Flutter uses &lt;a href=&quot;https:&#x2F;&#x2F;www.dartlang.org&#x2F;&quot;&gt;Dart&lt;&#x2F;a&gt;, a programming language with a, well, complicated past. In its most recent incarnation, however, there&#x27;s quite a bit to like about Dart! Coming from a web development background, Dart is probably the most JavaScript-like typed language outside of TypeScript. I felt productive right away, and didn&#x27;t feel the need to read a bunch of tutorials or watch a bunch of videos before building things I was proud of.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;things-i-liked&quot;&gt;Things I liked&lt;&#x2F;h3&gt;
&lt;h4 id=&quot;it-s-like-javascript-but-with-types&quot;&gt;It&#x27;s like JavaScript, but with types!&lt;&#x2F;h4&gt;
&lt;p&gt;First and foremost, Dart is a typed language. But like many newer typed languages, it &lt;em&gt;feels&lt;&#x2F;em&gt; much more like a dynamic language. For example, the following is totally valid Dart code:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;dart&quot; style=&quot;background-color:#2b303b;color:#6c7079;&quot; class=&quot;language-dart &quot;&gt;&lt;code class=&quot;language-dart&quot; data-lang=&quot;dart&quot;&gt;&lt;span style=&quot;color:#cd74e8;&quot;&gt;var&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt; users &lt;&#x2F;span&gt;&lt;span style=&quot;color:#adb7c9;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt; [
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;    {&lt;&#x2F;span&gt;&lt;span style=&quot;color:#9acc76;&quot;&gt;&amp;#39;id&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#adb7c9;&quot;&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#db9d63;&quot;&gt;1&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#9acc76;&quot;&gt;&amp;#39;name&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#adb7c9;&quot;&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#9acc76;&quot;&gt;&amp;#39;Dylan&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;},
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;    {&lt;&#x2F;span&gt;&lt;span style=&quot;color:#9acc76;&quot;&gt;&amp;#39;id&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#adb7c9;&quot;&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#db9d63;&quot;&gt;2&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#9acc76;&quot;&gt;&amp;#39;name&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#adb7c9;&quot;&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#9acc76;&quot;&gt;&amp;#39;Josh&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;},
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;];
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;&lt;code&gt;users&lt;&#x2F;code&gt; is inferred to be &lt;code&gt;List&amp;lt;Map&amp;lt;String, Object&amp;gt;&amp;gt;&lt;&#x2F;code&gt;, without having to declare that beforehand.&lt;&#x2F;p&gt;
&lt;p&gt;One interesting aspect of this is Dart&#x27;s &lt;code&gt;dynamic&lt;&#x2F;code&gt; type, which is essentially TypeScript&#x27;s &lt;code&gt;any&lt;&#x2F;code&gt;. It allows you to pass any type you want around, and is basically an escape hatch from the type system. However, it&#x27;s important to minimize the use of &lt;code&gt;dynamic&lt;&#x2F;code&gt; in your code since you really want to benefit from Dart&#x27;s type system.&lt;&#x2F;p&gt;
&lt;h4 id=&quot;an-incredible-out-of-the-box-experience&quot;&gt;An incredible out-of-the-box experience&lt;&#x2F;h4&gt;
&lt;p&gt;Another aspect of Dart that really blew me away was the development experience that the community has rallied around. Dart has a neat package called &lt;a href=&quot;https:&#x2F;&#x2F;pub.dartlang.org&#x2F;packages&#x2F;stagehand&quot;&gt;&lt;code&gt;stagehand&lt;&#x2F;code&gt;&lt;&#x2F;a&gt; that handles project scaffolding, which provides nice things like package management with &lt;code&gt;pub&lt;&#x2F;code&gt;, testing with Dart&#x27;s &lt;code&gt;test&lt;&#x2F;code&gt; package, and a package structure that makes it easy to create quality package pages for &lt;a href=&quot;https:&#x2F;&#x2F;pub.dartlang.org&#x2F;&quot;&gt;Pub&lt;&#x2F;a&gt;, Dart&#x27;s package repository. Compared to JavaScript, creating and publishing a well-tested Dart package was a breeze.&lt;&#x2F;p&gt;
&lt;h4 id=&quot;code-generation&quot;&gt;Code Generation&lt;&#x2F;h4&gt;
&lt;p&gt;Dart also has some excellent packages for code generation, including the really nifty &lt;a href=&quot;https:&#x2F;&#x2F;pub.dartlang.org&#x2F;packages&#x2F;code_builder&quot;&gt;&lt;code&gt;code_builder&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;, which provides a typed API for building Dart code.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;dart&quot; style=&quot;background-color:#2b303b;color:#6c7079;&quot; class=&quot;language-dart &quot;&gt;&lt;code class=&quot;language-dart&quot; data-lang=&quot;dart&quot;&gt;&lt;span style=&quot;font-style:italic;color:#5f697a;&quot;&gt;&#x2F;&#x2F;&#x2F; Outputs:
&lt;&#x2F;span&gt;&lt;span style=&quot;font-style:italic;color:#5f697a;&quot;&gt;&#x2F;&#x2F;&#x2F;
&lt;&#x2F;span&gt;&lt;span style=&quot;font-style:italic;color:#5f697a;&quot;&gt;&#x2F;&#x2F;&#x2F; ```dart
&lt;&#x2F;span&gt;&lt;span style=&quot;font-style:italic;color:#5f697a;&quot;&gt;&#x2F;&#x2F;&#x2F; class Animal extends Organism {
&lt;&#x2F;span&gt;&lt;span style=&quot;font-style:italic;color:#5f697a;&quot;&gt;&#x2F;&#x2F;&#x2F;   void eat() =&amp;gt; print(&amp;#39;Yum!&amp;#39;);
&lt;&#x2F;span&gt;&lt;span style=&quot;font-style:italic;color:#5f697a;&quot;&gt;&#x2F;&#x2F;&#x2F; }
&lt;&#x2F;span&gt;&lt;span style=&quot;font-style:italic;color:#5f697a;&quot;&gt;&#x2F;&#x2F;&#x2F; ```
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#f0c678;&quot;&gt;String &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5cb3fa;&quot;&gt;animalClass&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;() {
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;color:#cd74e8;&quot;&gt;final&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt; animal &lt;&#x2F;span&gt;&lt;span style=&quot;color:#adb7c9;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#f0c678;&quot;&gt;Class&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;((b) &lt;&#x2F;span&gt;&lt;span style=&quot;color:#adb7c9;&quot;&gt;=&amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt; b
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;    ..name &lt;&#x2F;span&gt;&lt;span style=&quot;color:#adb7c9;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#9acc76;&quot;&gt;&amp;#39;Animal&amp;#39;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;    ..extend &lt;&#x2F;span&gt;&lt;span style=&quot;color:#adb7c9;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5cb3fa;&quot;&gt;refer&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#9acc76;&quot;&gt;&amp;#39;Organism&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;)
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;    ..methods.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5cb3fa;&quot;&gt;add&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#f0c678;&quot;&gt;Method&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5cb3fa;&quot;&gt;returnsVoid&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;((b) &lt;&#x2F;span&gt;&lt;span style=&quot;color:#adb7c9;&quot;&gt;=&amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt; b
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;      ..name &lt;&#x2F;span&gt;&lt;span style=&quot;color:#adb7c9;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#9acc76;&quot;&gt;&amp;#39;eat&amp;#39;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;      ..body &lt;&#x2F;span&gt;&lt;span style=&quot;color:#adb7c9;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5cb3fa;&quot;&gt;refer&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#9acc76;&quot;&gt;&amp;#39;print&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;).&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5cb3fa;&quot;&gt;call&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;([&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5cb3fa;&quot;&gt;literalString&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#9acc76;&quot;&gt;&amp;#39;Yum!&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;)]).code)));
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;color:#cd74e8;&quot;&gt;return&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt; _dartfmt.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5cb3fa;&quot;&gt;format&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#9acc76;&quot;&gt;&amp;#39;${&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;animal.accept(DartEmitter())&lt;&#x2F;span&gt;&lt;span style=&quot;color:#9acc76;&quot;&gt;}&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;);
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;h4 id=&quot;dart-compiles-to-javascript&quot;&gt;Dart compiles to JavaScript&lt;&#x2F;h4&gt;
&lt;p&gt;I will admit that Dart&#x27;s ability to compile to JavaScript is really cool, but in the current environment, I don&#x27;t really think it&#x27;s that compelling of a feature. TypeScript provides all the same type guarantees, and manages to have a much more robust and expressive type system as well. However, this will definitely come in handy when &lt;a href=&quot;https:&#x2F;&#x2F;medium.com&#x2F;flutter-io&#x2F;hummingbird-building-flutter-for-the-web-e687c2a023a8&quot;&gt;Hummingbird&lt;&#x2F;a&gt;, a port of Flutter to the web, launches.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;things-i-wish-were-better&quot;&gt;Things I wish were better&lt;&#x2F;h3&gt;
&lt;h4 id=&quot;json-support&quot;&gt;JSON support&lt;&#x2F;h4&gt;
&lt;p&gt;One of the first snags I ran into with Dart was its JSON support. On Dart&#x27;s &lt;a href=&quot;https:&#x2F;&#x2F;www.dartlang.org&#x2F;guides&#x2F;libraries&#x2F;library-tour#decoding-and-encoding-json&quot;&gt;Language Tour&lt;&#x2F;a&gt;, the following sample is provided:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;dart&quot; style=&quot;background-color:#2b303b;color:#6c7079;&quot; class=&quot;language-dart &quot;&gt;&lt;code class=&quot;language-dart&quot; data-lang=&quot;dart&quot;&gt;&lt;span style=&quot;color:#cd74e8;&quot;&gt;var&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt; jsonString &lt;&#x2F;span&gt;&lt;span style=&quot;color:#adb7c9;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#9acc76;&quot;&gt;&amp;#39;&amp;#39;&amp;#39;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#9acc76;&quot;&gt;  [
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#9acc76;&quot;&gt;    {&amp;quot;score&amp;quot;: 40},
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#9acc76;&quot;&gt;    {&amp;quot;score&amp;quot;: 80}
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#9acc76;&quot;&gt;  ]
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#9acc76;&quot;&gt;&amp;#39;&amp;#39;&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#cd74e8;&quot;&gt;var&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt; scores &lt;&#x2F;span&gt;&lt;span style=&quot;color:#adb7c9;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5cb3fa;&quot;&gt;jsonDecode&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;(jsonString);
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Looks easy enough, right? Well, the type of &lt;code&gt;scores&lt;&#x2F;code&gt; is actually &lt;code&gt;dynamic&lt;&#x2F;code&gt; as opposed to the expected &lt;code&gt;List&amp;lt;Map&amp;lt;String, int&amp;gt;&amp;gt;&lt;&#x2F;code&gt;, and needs to be casted to &lt;code&gt;List&lt;&#x2F;code&gt;. Even after casting, however, the type is still &lt;code&gt;List&amp;lt;dynamic&amp;gt;&lt;&#x2F;code&gt;, which means that calling &lt;code&gt;int&lt;&#x2F;code&gt; methods requires an additional cast:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;dart&quot; style=&quot;background-color:#2b303b;color:#6c7079;&quot; class=&quot;language-dart &quot;&gt;&lt;code class=&quot;language-dart&quot; data-lang=&quot;dart&quot;&gt;&lt;span style=&quot;color:#cd74e8;&quot;&gt;var&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt; jsonString &lt;&#x2F;span&gt;&lt;span style=&quot;color:#adb7c9;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#9acc76;&quot;&gt;&amp;#39;&amp;#39;&amp;#39;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#9acc76;&quot;&gt;  [
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#9acc76;&quot;&gt;    {&amp;quot;score&amp;quot;: 40},
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#9acc76;&quot;&gt;    {&amp;quot;score&amp;quot;: 80}
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#9acc76;&quot;&gt;  ]
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#9acc76;&quot;&gt;&amp;#39;&amp;#39;&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#cd74e8;&quot;&gt;var&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt; scores &lt;&#x2F;span&gt;&lt;span style=&quot;color:#adb7c9;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5cb3fa;&quot;&gt;jsonDecode&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;(jsonString) &lt;&#x2F;span&gt;&lt;span style=&quot;color:#cd74e8;&quot;&gt;as &lt;&#x2F;span&gt;&lt;span style=&quot;color:#f0c678;&quot;&gt;List&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;scores.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5cb3fa;&quot;&gt;forEach&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;((s) {
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5cb3fa;&quot;&gt;print&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;((s[&lt;&#x2F;span&gt;&lt;span style=&quot;color:#9acc76;&quot;&gt;&amp;#39;score&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;] &lt;&#x2F;span&gt;&lt;span style=&quot;color:#cd74e8;&quot;&gt;as &lt;&#x2F;span&gt;&lt;span style=&quot;color:#f0c678;&quot;&gt;int&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;).&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5cb3fa;&quot;&gt;abs&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;());
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;});
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Well, what if instead of casting to List, I cast to &lt;code&gt;List&amp;lt;Map&amp;lt;String, int&amp;gt;&amp;gt;&lt;&#x2F;code&gt;?&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;dart&quot; style=&quot;background-color:#2b303b;color:#6c7079;&quot; class=&quot;language-dart &quot;&gt;&lt;code class=&quot;language-dart&quot; data-lang=&quot;dart&quot;&gt;&lt;span style=&quot;color:#cd74e8;&quot;&gt;var&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt; jsonString &lt;&#x2F;span&gt;&lt;span style=&quot;color:#adb7c9;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#9acc76;&quot;&gt;&amp;#39;&amp;#39;&amp;#39;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#9acc76;&quot;&gt;  [
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#9acc76;&quot;&gt;    {&amp;quot;score&amp;quot;: 40},
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#9acc76;&quot;&gt;    {&amp;quot;score&amp;quot;: 80}
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#9acc76;&quot;&gt;  ]
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#9acc76;&quot;&gt;&amp;#39;&amp;#39;&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#cd74e8;&quot;&gt;var&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt; scores &lt;&#x2F;span&gt;&lt;span style=&quot;color:#adb7c9;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5cb3fa;&quot;&gt;jsonDecode&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;(jsonString) &lt;&#x2F;span&gt;&lt;span style=&quot;color:#cd74e8;&quot;&gt;as &lt;&#x2F;span&gt;&lt;span style=&quot;color:#f0c678;&quot;&gt;List&lt;&#x2F;span&gt;&lt;span style=&quot;color:#adb7c9;&quot;&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#f0c678;&quot;&gt;Map&lt;&#x2F;span&gt;&lt;span style=&quot;color:#adb7c9;&quot;&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#f0c678;&quot;&gt;String&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#f0c678;&quot;&gt;int&lt;&#x2F;span&gt;&lt;span style=&quot;color:#adb7c9;&quot;&gt;&amp;gt;&amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;scores.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5cb3fa;&quot;&gt;forEach&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;((s) {
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5cb3fa;&quot;&gt;print&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;(s[&lt;&#x2F;span&gt;&lt;span style=&quot;color:#9acc76;&quot;&gt;&amp;#39;score&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;].&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5cb3fa;&quot;&gt;abs&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;());
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;});
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The above code compiles, but throws a runtime error:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#6c7079;&quot;&gt;&lt;code&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;type &amp;#39;List&amp;lt;dynamic&amp;gt;&amp;#39; is not a subtype of type &amp;#39;List&amp;lt;Map&amp;lt;String, int&amp;gt;&amp;gt;&amp;#39; in type cast
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;For comparison, here&#x27;s how Go handles a similar situation:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;go&quot; style=&quot;background-color:#2b303b;color:#6c7079;&quot; class=&quot;language-go &quot;&gt;&lt;code class=&quot;language-go&quot; data-lang=&quot;go&quot;&gt;&lt;span style=&quot;color:#cd74e8;&quot;&gt;var &lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;jsonString &lt;&#x2F;span&gt;&lt;span style=&quot;color:#adb7c9;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;[]&lt;&#x2F;span&gt;&lt;span style=&quot;color:#cd74e8;&quot;&gt;byte&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#9acc76;&quot;&gt;`[
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#9acc76;&quot;&gt;    {&amp;quot;score&amp;quot;: 40},
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#9acc76;&quot;&gt;    {&amp;quot;score&amp;quot;: 80}
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#9acc76;&quot;&gt;]`&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;)
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#cd74e8;&quot;&gt;var &lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;scores &lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;[]&lt;&#x2F;span&gt;&lt;span style=&quot;color:#cd74e8;&quot;&gt;map&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;[&lt;&#x2F;span&gt;&lt;span style=&quot;color:#cd74e8;&quot;&gt;string&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;]&lt;&#x2F;span&gt;&lt;span style=&quot;color:#cd74e8;&quot;&gt;int
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;_ &lt;&#x2F;span&gt;&lt;span style=&quot;color:#adb7c9;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;json&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;Unmarshal&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;jsonString&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#adb7c9;&quot;&gt;&amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;scores&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;)
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#cd74e8;&quot;&gt;for &lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;_&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;s &lt;&#x2F;span&gt;&lt;span style=&quot;color:#adb7c9;&quot;&gt;:= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#cd74e8;&quot;&gt;range &lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;scores &lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;{
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;fmt&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;Println&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;s&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;[&lt;&#x2F;span&gt;&lt;span style=&quot;color:#9acc76;&quot;&gt;&amp;quot;score&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;])
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Go&#x27;s excellent JSON library allows decoding JSON into arbitrarily complex objects. Its superiority is even more obvious when you consider how much ceremony is required from Dart in order to decode more complex objects. Here&#x27;s how Go handles decoding a suggested tag response from Pinboard:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;go&quot; style=&quot;background-color:#2b303b;color:#6c7079;&quot; class=&quot;language-go &quot;&gt;&lt;code class=&quot;language-go&quot; data-lang=&quot;go&quot;&gt;&lt;span style=&quot;color:#cd74e8;&quot;&gt;var &lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;jsonString &lt;&#x2F;span&gt;&lt;span style=&quot;color:#adb7c9;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;[]&lt;&#x2F;span&gt;&lt;span style=&quot;color:#cd74e8;&quot;&gt;byte&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#9acc76;&quot;&gt;`
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#9acc76;&quot;&gt;  [
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#9acc76;&quot;&gt;    {&amp;quot;popular&amp;quot;: [&amp;quot;tech&amp;quot;, &amp;quot;programming&amp;quot;]},
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#9acc76;&quot;&gt;    {&amp;quot;recommended&amp;quot;: [&amp;quot;compsci&amp;quot;]}
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#9acc76;&quot;&gt;  ]
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#9acc76;&quot;&gt;`&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;)
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#cd74e8;&quot;&gt;var &lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;response &lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;[]&lt;&#x2F;span&gt;&lt;span style=&quot;color:#cd74e8;&quot;&gt;map&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;[&lt;&#x2F;span&gt;&lt;span style=&quot;color:#cd74e8;&quot;&gt;string&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;][]&lt;&#x2F;span&gt;&lt;span style=&quot;color:#cd74e8;&quot;&gt;string
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;_ &lt;&#x2F;span&gt;&lt;span style=&quot;color:#adb7c9;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;json&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;Unmarshal&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;jsonString&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#adb7c9;&quot;&gt;&amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;response&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;)
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#cd74e8;&quot;&gt;for &lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;_&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;s &lt;&#x2F;span&gt;&lt;span style=&quot;color:#adb7c9;&quot;&gt;:= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#cd74e8;&quot;&gt;range &lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;response &lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;{
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;fmt&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;Println&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;s&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;)
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;And here&#x27;s the Dart version:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;dart&quot; style=&quot;background-color:#2b303b;color:#6c7079;&quot; class=&quot;language-dart &quot;&gt;&lt;code class=&quot;language-dart&quot; data-lang=&quot;dart&quot;&gt;&lt;span style=&quot;color:#cd74e8;&quot;&gt;var&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt; jsonString &lt;&#x2F;span&gt;&lt;span style=&quot;color:#adb7c9;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#9acc76;&quot;&gt;&amp;#39;&amp;#39;&amp;#39;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#9acc76;&quot;&gt;  [
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#9acc76;&quot;&gt;    {&amp;quot;popular&amp;quot;: [&amp;quot;tech&amp;quot;, &amp;quot;programming&amp;quot;]},
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#9acc76;&quot;&gt;    {&amp;quot;recommended&amp;quot;: [&amp;quot;compsci&amp;quot;]}
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#9acc76;&quot;&gt;  ]
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#9acc76;&quot;&gt;&amp;#39;&amp;#39;&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#cd74e8;&quot;&gt;var&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt; response &lt;&#x2F;span&gt;&lt;span style=&quot;color:#adb7c9;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5cb3fa;&quot;&gt;jsonDecode&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;(jsonString) &lt;&#x2F;span&gt;&lt;span style=&quot;color:#cd74e8;&quot;&gt;as &lt;&#x2F;span&gt;&lt;span style=&quot;color:#f0c678;&quot;&gt;List&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#cd74e8;&quot;&gt;var&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt; result &lt;&#x2F;span&gt;&lt;span style=&quot;color:#adb7c9;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt; response
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;    .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5cb3fa;&quot;&gt;map&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;((i) &lt;&#x2F;span&gt;&lt;span style=&quot;color:#adb7c9;&quot;&gt;=&amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt; (i &lt;&#x2F;span&gt;&lt;span style=&quot;color:#cd74e8;&quot;&gt;as &lt;&#x2F;span&gt;&lt;span style=&quot;color:#f0c678;&quot;&gt;Map&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;).&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5cb3fa;&quot;&gt;map&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;((&lt;&#x2F;span&gt;&lt;span style=&quot;color:#f0c678;&quot;&gt;dynamic&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt; key, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#f0c678;&quot;&gt;dynamic&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt; value) &lt;&#x2F;span&gt;&lt;span style=&quot;color:#adb7c9;&quot;&gt;=&amp;gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#f0c678;&quot;&gt;MapEntry&lt;&#x2F;span&gt;&lt;span style=&quot;color:#adb7c9;&quot;&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#f0c678;&quot;&gt;String&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#f0c678;&quot;&gt;List&lt;&#x2F;span&gt;&lt;span style=&quot;color:#adb7c9;&quot;&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#f0c678;&quot;&gt;String&lt;&#x2F;span&gt;&lt;span style=&quot;color:#adb7c9;&quot;&gt;&amp;gt;&amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;(key, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#f0c678;&quot;&gt;List&lt;&#x2F;span&gt;&lt;span style=&quot;color:#adb7c9;&quot;&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#f0c678;&quot;&gt;String&lt;&#x2F;span&gt;&lt;span style=&quot;color:#adb7c9;&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5cb3fa;&quot;&gt;from&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;(value))))
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;    .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5cb3fa;&quot;&gt;toList&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;();
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5cb3fa;&quot;&gt;print&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;(result);
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;And finally, here&#x27;s a complete Go program to decode a Pinboard Post:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;go&quot; style=&quot;background-color:#2b303b;color:#6c7079;&quot; class=&quot;language-go &quot;&gt;&lt;code class=&quot;language-go&quot; data-lang=&quot;go&quot;&gt;&lt;span style=&quot;color:#cd74e8;&quot;&gt;package &lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;main
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#cd74e8;&quot;&gt;import &lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;(
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;color:#9acc76;&quot;&gt;&amp;quot;encoding&#x2F;json&amp;quot;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;color:#9acc76;&quot;&gt;&amp;quot;fmt&amp;quot;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;color:#9acc76;&quot;&gt;&amp;quot;log&amp;quot;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;)
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#cd74e8;&quot;&gt;type &lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;Post &lt;&#x2F;span&gt;&lt;span style=&quot;color:#cd74e8;&quot;&gt;struct &lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;{
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;Href&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;Description&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;Extended&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;Meta&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;Hash&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;Time&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;Shared&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;Toread&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;Tags &lt;&#x2F;span&gt;&lt;span style=&quot;color:#cd74e8;&quot;&gt;string
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;}
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#cd74e8;&quot;&gt;func &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5cb3fa;&quot;&gt;main&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;() {
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;color:#cd74e8;&quot;&gt;var &lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;jsonString &lt;&#x2F;span&gt;&lt;span style=&quot;color:#adb7c9;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;[]&lt;&#x2F;span&gt;&lt;span style=&quot;color:#cd74e8;&quot;&gt;byte&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#9acc76;&quot;&gt;`
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#9acc76;&quot;&gt;    {
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#9acc76;&quot;&gt;        &amp;quot;href&amp;quot;: &amp;quot;https:&#x2F;&#x2F;date-fns.org&#x2F;&amp;quot;,
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#9acc76;&quot;&gt;        &amp;quot;description&amp;quot;: &amp;quot;date-fns - modern JavaScript date utility library&amp;quot;,
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#9acc76;&quot;&gt;        &amp;quot;extended&amp;quot;: &amp;quot;&amp;quot;,
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#9acc76;&quot;&gt;        &amp;quot;meta&amp;quot;: &amp;quot;0c4f66fb2dd90d6feeab250a9640d8f4&amp;quot;,
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#9acc76;&quot;&gt;        &amp;quot;hash&amp;quot;: &amp;quot;680e1c195c9be62896b1bc5875f89453&amp;quot;,
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#9acc76;&quot;&gt;        &amp;quot;time&amp;quot;: &amp;quot;2018-10-19T03:36:58Z&amp;quot;,
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#9acc76;&quot;&gt;        &amp;quot;shared&amp;quot;: &amp;quot;yes&amp;quot;,
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#9acc76;&quot;&gt;        &amp;quot;toread&amp;quot;: &amp;quot;no&amp;quot;,
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#9acc76;&quot;&gt;        &amp;quot;tags&amp;quot;: &amp;quot;javascript&amp;quot;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#9acc76;&quot;&gt;      }
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#9acc76;&quot;&gt;  `&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;)
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;color:#cd74e8;&quot;&gt;var &lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;post &lt;&#x2F;span&gt;&lt;span style=&quot;color:#cd74e8;&quot;&gt;Post
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;err &lt;&#x2F;span&gt;&lt;span style=&quot;color:#adb7c9;&quot;&gt;:= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;json&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;Unmarshal&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;jsonString&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#adb7c9;&quot;&gt;&amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;post&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;)
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;color:#cd74e8;&quot;&gt;if &lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;err &lt;&#x2F;span&gt;&lt;span style=&quot;color:#adb7c9;&quot;&gt;!= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#db9d63;&quot;&gt;nil &lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;{
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;log&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;Fatal&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;err&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;)
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;  }
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;fmt&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;Printf&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#9acc76;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#db9d63;&quot;&gt;%+v&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5ebfcc;&quot;&gt;\n&lt;&#x2F;span&gt;&lt;span style=&quot;color:#9acc76;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;post&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;)
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;And here&#x27;s the Dart version:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;dart&quot; style=&quot;background-color:#2b303b;color:#6c7079;&quot; class=&quot;language-dart &quot;&gt;&lt;code class=&quot;language-dart&quot; data-lang=&quot;dart&quot;&gt;&lt;span style=&quot;color:#cd74e8;&quot;&gt;import &lt;&#x2F;span&gt;&lt;span style=&quot;color:#9acc76;&quot;&gt;&amp;#39;dart:convert&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#cd74e8;&quot;&gt;class &lt;&#x2F;span&gt;&lt;span style=&quot;color:#f0c678;&quot;&gt;Post&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt; {
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;color:#f0c678;&quot;&gt;String&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt; href, description, extended, meta, hash, time, shared, toread, tags;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;color:#f0c678;&quot;&gt;Post&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;({
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;this&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;.href,
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;this&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;.description,
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;this&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;.extended,
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;this&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;.meta,
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;this&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;.hash,
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;this&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;.time,
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;this&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;.shared,
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;this&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;.toread,
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;this&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;.tags,
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;  });
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;color:#cd74e8;&quot;&gt;factory &lt;&#x2F;span&gt;&lt;span style=&quot;color:#f0c678;&quot;&gt;Post&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5cb3fa;&quot;&gt;fromJson&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#f0c678;&quot;&gt;Map&lt;&#x2F;span&gt;&lt;span style=&quot;color:#adb7c9;&quot;&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#f0c678;&quot;&gt;String&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#f0c678;&quot;&gt;Object&lt;&#x2F;span&gt;&lt;span style=&quot;color:#adb7c9;&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt; json) {
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#cd74e8;&quot;&gt;return &lt;&#x2F;span&gt;&lt;span style=&quot;color:#f0c678;&quot;&gt;Post&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;(
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;      href&lt;&#x2F;span&gt;&lt;span style=&quot;color:#adb7c9;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt; json[&lt;&#x2F;span&gt;&lt;span style=&quot;color:#9acc76;&quot;&gt;&amp;#39;href&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;],
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;      description&lt;&#x2F;span&gt;&lt;span style=&quot;color:#adb7c9;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt; json[&lt;&#x2F;span&gt;&lt;span style=&quot;color:#9acc76;&quot;&gt;&amp;#39;description&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;],
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;      extended&lt;&#x2F;span&gt;&lt;span style=&quot;color:#adb7c9;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt; json[&lt;&#x2F;span&gt;&lt;span style=&quot;color:#9acc76;&quot;&gt;&amp;#39;extended&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;],
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;      meta&lt;&#x2F;span&gt;&lt;span style=&quot;color:#adb7c9;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt; json[&lt;&#x2F;span&gt;&lt;span style=&quot;color:#9acc76;&quot;&gt;&amp;#39;meta&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;],
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;      hash&lt;&#x2F;span&gt;&lt;span style=&quot;color:#adb7c9;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt; json[&lt;&#x2F;span&gt;&lt;span style=&quot;color:#9acc76;&quot;&gt;&amp;#39;hash&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;],
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;      time&lt;&#x2F;span&gt;&lt;span style=&quot;color:#adb7c9;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt; json[&lt;&#x2F;span&gt;&lt;span style=&quot;color:#9acc76;&quot;&gt;&amp;#39;time&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;],
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;      shared&lt;&#x2F;span&gt;&lt;span style=&quot;color:#adb7c9;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt; json[&lt;&#x2F;span&gt;&lt;span style=&quot;color:#9acc76;&quot;&gt;&amp;#39;shared&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;],
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;      toread&lt;&#x2F;span&gt;&lt;span style=&quot;color:#adb7c9;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt; json[&lt;&#x2F;span&gt;&lt;span style=&quot;color:#9acc76;&quot;&gt;&amp;#39;toread&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;],
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;      tags&lt;&#x2F;span&gt;&lt;span style=&quot;color:#adb7c9;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt; json[&lt;&#x2F;span&gt;&lt;span style=&quot;color:#9acc76;&quot;&gt;&amp;#39;tags&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;],
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;    );
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;  }
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;}
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#cd74e8;&quot;&gt;void &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5cb3fa;&quot;&gt;main&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;() {
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;color:#cd74e8;&quot;&gt;var&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt; jsonString &lt;&#x2F;span&gt;&lt;span style=&quot;color:#adb7c9;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#9acc76;&quot;&gt;&amp;#39;&amp;#39;&amp;#39;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#9acc76;&quot;&gt;  {
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#9acc76;&quot;&gt;    &amp;quot;href&amp;quot;: &amp;quot;https:&#x2F;&#x2F;date-fns.org&#x2F;&amp;quot;,
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#9acc76;&quot;&gt;    &amp;quot;description&amp;quot;: &amp;quot;date-fns - modern JavaScript date utility library&amp;quot;,
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#9acc76;&quot;&gt;    &amp;quot;extended&amp;quot;: &amp;quot;&amp;quot;,
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#9acc76;&quot;&gt;    &amp;quot;meta&amp;quot;: &amp;quot;0c4f66fb2dd90d6feeab250a9640d8f4&amp;quot;,
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#9acc76;&quot;&gt;    &amp;quot;hash&amp;quot;: &amp;quot;680e1c195c9be62896b1bc5875f89453&amp;quot;,
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#9acc76;&quot;&gt;    &amp;quot;time&amp;quot;: &amp;quot;2018-10-19T03:36:58Z&amp;quot;,
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#9acc76;&quot;&gt;    &amp;quot;shared&amp;quot;: &amp;quot;yes&amp;quot;,
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#9acc76;&quot;&gt;    &amp;quot;toread&amp;quot;: &amp;quot;no&amp;quot;,
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#9acc76;&quot;&gt;    &amp;quot;tags&amp;quot;: &amp;quot;javascript&amp;quot;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#9acc76;&quot;&gt;  }
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#9acc76;&quot;&gt;&amp;#39;&amp;#39;&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;color:#cd74e8;&quot;&gt;var&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt; decodedJson &lt;&#x2F;span&gt;&lt;span style=&quot;color:#adb7c9;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5cb3fa;&quot;&gt;jsonDecode&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;(jsonString);
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;color:#cd74e8;&quot;&gt;var&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt; post &lt;&#x2F;span&gt;&lt;span style=&quot;color:#adb7c9;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#f0c678;&quot;&gt;Post&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5cb3fa;&quot;&gt;fromJson&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;(decodedJson);
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5cb3fa;&quot;&gt;print&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;(post);
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;While the Dart version seems not too bad aside from the verbosity, it really gets out of hand once you&#x27;re decoding complex, deeply nested structures. It&#x27;d be great if converting JSON to a class in Dart was as simple as it is in Go.&lt;&#x2F;p&gt;
&lt;h4 id=&quot;non-nullable-types&quot;&gt;Non-nullable types&lt;&#x2F;h4&gt;
&lt;p&gt;Believe it or not, the following Dart program compiles just fine:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;dart&quot; style=&quot;background-color:#2b303b;color:#6c7079;&quot; class=&quot;language-dart &quot;&gt;&lt;code class=&quot;language-dart&quot; data-lang=&quot;dart&quot;&gt;&lt;span style=&quot;color:#f0c678;&quot;&gt;String &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5cb3fa;&quot;&gt;mockingCase&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#f0c678;&quot;&gt;String&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt; message) {
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;color:#cd74e8;&quot;&gt;return&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt; message
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;      .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5cb3fa;&quot;&gt;split&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#9acc76;&quot;&gt;&amp;#39;&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;)
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;      .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5cb3fa;&quot;&gt;asMap&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;()
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;      .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5cb3fa;&quot;&gt;map&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;((i, letter) {
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#cd74e8;&quot;&gt;return &lt;&#x2F;span&gt;&lt;span style=&quot;color:#f0c678;&quot;&gt;MapEntry&lt;&#x2F;span&gt;&lt;span style=&quot;color:#adb7c9;&quot;&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#f0c678;&quot;&gt;int&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#f0c678;&quot;&gt;String&lt;&#x2F;span&gt;&lt;span style=&quot;color:#adb7c9;&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;(
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;          i,
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;          i &lt;&#x2F;span&gt;&lt;span style=&quot;color:#adb7c9;&quot;&gt;% &lt;&#x2F;span&gt;&lt;span style=&quot;color:#db9d63;&quot;&gt;2 &lt;&#x2F;span&gt;&lt;span style=&quot;color:#adb7c9;&quot;&gt;== &lt;&#x2F;span&gt;&lt;span style=&quot;color:#db9d63;&quot;&gt;0 &lt;&#x2F;span&gt;&lt;span style=&quot;color:#adb7c9;&quot;&gt;?&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt; letter &lt;&#x2F;span&gt;&lt;span style=&quot;color:#adb7c9;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt; letter.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5cb3fa;&quot;&gt;toUpperCase&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;(),
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;        );
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;      })
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;      .values
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;      .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5cb3fa;&quot;&gt;join&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#9acc76;&quot;&gt;&amp;#39;&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;);
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;}
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#cd74e8;&quot;&gt;void &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5cb3fa;&quot;&gt;main&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;() {
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5cb3fa;&quot;&gt;print&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5cb3fa;&quot;&gt;mockingCase&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#db9d63;&quot;&gt;null&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;));
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This is because the &lt;code&gt;String&lt;&#x2F;code&gt; type is nullable, meaning it can be &lt;code&gt;String&lt;&#x2F;code&gt; or &lt;code&gt;null&lt;&#x2F;code&gt;. With Dart, the compiler doesn&#x27;t warn you when you pass &lt;code&gt;null&lt;&#x2F;code&gt; into a function that requires a valid type, nor does it require you to check that your input isn&#x27;t null. Thankfully there&#x27;s &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;dart-lang&#x2F;language&#x2F;blob&#x2F;master&#x2F;working&#x2F;0110-incremental-sound-nnbd&#x2F;roadmap.md&quot;&gt;a proposal&lt;&#x2F;a&gt; in the works to add non-nullable types to Dart.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;building-with-dart&quot;&gt;Building with Dart&lt;&#x2F;h2&gt;
&lt;p&gt;Remember a few minutes ago when I mentioned building a Pinboard app with Flutter? Well, it started with building a Pinboard API client in Dart. When I got done building the client and writing the tests, I wanted to check if my tests appropriately covered all the code I had written. Unfortunately, there isn&#x27;t a great library like &lt;a href=&quot;https:&#x2F;&#x2F;www.npmjs.com&#x2F;package&#x2F;nyc&quot;&gt;&lt;code&gt;nyc&lt;&#x2F;code&gt;&lt;&#x2F;a&gt; in JavaScript to generate coverage reports.&lt;&#x2F;p&gt;
&lt;p&gt;So, I wrote one myself.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;a href=&quot;https:&#x2F;&#x2F;pub.dartlang.org&#x2F;packages&#x2F;duvet&quot;&gt;&lt;code&gt;duvet&lt;&#x2F;code&gt;&lt;&#x2F;a&gt; is a test coverage report library that uses the &lt;code&gt;test&lt;&#x2F;code&gt; package to run your tests, collect coverage with &lt;code&gt;coverage&lt;&#x2F;code&gt;, and generate a nice-looking coverage report.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;img&#x2F;duvet.png&quot; alt=&quot;Screenshot of a duvet coverage report&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;However, while building &lt;code&gt;duvet&lt;&#x2F;code&gt;, I realized I needed something to help me generate HTML documents. There didn&#x27;t seem to be anything out there that I really liked, so I again took inspiration from the JavaScript ecosystem and built &lt;a href=&quot;https:&#x2F;&#x2F;pub.dartlang.org&#x2F;packages&#x2F;hyper&quot;&gt;&lt;code&gt;hyper&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;, a simple way to build HTML documents with Dart. Here&#x27;s what it looks like:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;dart&quot; style=&quot;background-color:#2b303b;color:#6c7079;&quot; class=&quot;language-dart &quot;&gt;&lt;code class=&quot;language-dart&quot; data-lang=&quot;dart&quot;&gt;&lt;span style=&quot;color:#cd74e8;&quot;&gt;var&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt; fullHtmlDoc &lt;&#x2F;span&gt;&lt;span style=&quot;color:#adb7c9;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5cb3fa;&quot;&gt;hyper&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;(
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#9acc76;&quot;&gt;&amp;#39;html&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;, children&lt;&#x2F;span&gt;&lt;span style=&quot;color:#adb7c9;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt; [
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;      &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5cb3fa;&quot;&gt;hyper&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#9acc76;&quot;&gt;&amp;#39;head&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;, children&lt;&#x2F;span&gt;&lt;span style=&quot;color:#adb7c9;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt; [
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5cb3fa;&quot;&gt;hyper&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#9acc76;&quot;&gt;&amp;#39;title&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;, children&lt;&#x2F;span&gt;&lt;span style=&quot;color:#adb7c9;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt; [
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;          &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5cb3fa;&quot;&gt;t&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#9acc76;&quot;&gt;&amp;#39;Hello, world!&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;),
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;        ])
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;      ]),
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;      &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5cb3fa;&quot;&gt;hyper&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#9acc76;&quot;&gt;&amp;#39;body&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;, children&lt;&#x2F;span&gt;&lt;span style=&quot;color:#adb7c9;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt; [
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5cb3fa;&quot;&gt;hyper&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;(
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;          &lt;&#x2F;span&gt;&lt;span style=&quot;color:#9acc76;&quot;&gt;&amp;#39;h1&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;,
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;          attrs&lt;&#x2F;span&gt;&lt;span style=&quot;color:#adb7c9;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt; {&lt;&#x2F;span&gt;&lt;span style=&quot;color:#9acc76;&quot;&gt;&amp;#39;class&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#adb7c9;&quot;&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#9acc76;&quot;&gt;&amp;quot;greeting&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;},
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;          children&lt;&#x2F;span&gt;&lt;span style=&quot;color:#adb7c9;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt; [
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5cb3fa;&quot;&gt;t&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#9acc76;&quot;&gt;&amp;#39;Hello from Hyper!&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;),
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;          ],
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;        )
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;      ]),
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;    ],
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;);
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Using the previously mentioned &lt;a href=&quot;https:&#x2F;&#x2F;pub.dartlang.org&#x2F;packages&#x2F;code_builder&quot;&gt;&lt;code&gt;code_builder&lt;&#x2F;code&gt;&lt;&#x2F;a&gt; package, I was also able to generate an API that matches the HTML specification to give a little more safety to building HTML:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;dart&quot; style=&quot;background-color:#2b303b;color:#6c7079;&quot; class=&quot;language-dart &quot;&gt;&lt;code class=&quot;language-dart&quot; data-lang=&quot;dart&quot;&gt;&lt;span style=&quot;color:#cd74e8;&quot;&gt;var&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt; a &lt;&#x2F;span&gt;&lt;span style=&quot;color:#adb7c9;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt; h.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5cb3fa;&quot;&gt;a&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;(
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;    href&lt;&#x2F;span&gt;&lt;span style=&quot;color:#adb7c9;&quot;&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#9acc76;&quot;&gt;&amp;#39;https:&#x2F;&#x2F;google.com&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;,
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;    children&lt;&#x2F;span&gt;&lt;span style=&quot;color:#adb7c9;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt; [
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5cb3fa;&quot;&gt;t&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#9acc76;&quot;&gt;&amp;#39;Click me!&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;),
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;    ],
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;);
&lt;&#x2F;span&gt;&lt;span style=&quot;font-style:italic;color:#5f697a;&quot;&gt;&#x2F;&#x2F; &amp;lt;a href=&amp;quot;https:&#x2F;&#x2F;google.com&amp;quot;&amp;gt;Click me!&amp;lt;&#x2F;a&amp;gt;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;&lt;a href=&quot;https:&#x2F;&#x2F;pub.dartlang.org&#x2F;packages&#x2F;hyper&quot;&gt;&lt;code&gt;hyper&lt;&#x2F;code&gt;&lt;&#x2F;a&gt; was heavily inspired by the excellent &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;hyperhype&#x2F;hyperscript&quot;&gt;&lt;code&gt;HyperScript&lt;&#x2F;code&gt;&lt;&#x2F;a&gt; library for JavaScript.&lt;&#x2F;p&gt;
&lt;p&gt;With &lt;code&gt;duvet&lt;&#x2F;code&gt; and &lt;code&gt;hyper&lt;&#x2F;code&gt;, I was then able to calculate coverage for my &lt;a href=&quot;https:&#x2F;&#x2F;pub.dartlang.org&#x2F;packages&#x2F;pinboard&quot;&gt;Pinboard API client&lt;&#x2F;a&gt; (the third and final library in this grand adventure into Dart). It was nice to see so many green bars!&lt;&#x2F;p&gt;
&lt;h2 id=&quot;so-what-s-next&quot;&gt;So, what&#x27;s next?&lt;&#x2F;h2&gt;
&lt;p&gt;After my experience with Dart, I came away impressed and excited for what it means for the future: an expressive, accessible, ahead-of-time compiled, type-safe language that runs on virtually every platform. Combined with Flutter&#x27;s web-like approach to cross-platform support, I honestly think that both Dart and Flutter have an exciting amount of potential going forward.&lt;&#x2F;p&gt;
&lt;p&gt;Now maybe I can finally get around to writing that Pinboard client.&lt;&#x2F;p&gt;
</content:encoded>
            </item>
            <item>
                <title>Gigabit in Baton Rouge</title>
                <pubDate>Sun, 30 Apr 2017 12:00:00 -0800</pubDate>
                <link>https://dstaley.com/posts/gigabit-in-baton-rouge/</link>
                <guid>https://dstaley.com/posts/gigabit-in-baton-rouge/</guid>
                <description>My ISP wouldn&#x27;t give me a list of where they were providing gigabit internet. So I made my own.</description>
                <content:encoded>&lt;p&gt;A few days ago, I asked my ISP what I thought was a simple question:&lt;&#x2F;p&gt;
&lt;blockquote class=&quot;twitter-tweet&quot; data-lang=&quot;en&quot;&gt;
  &lt;p lang=&quot;en&quot; dir=&quot;ltr&quot;&gt;&lt;a href=&quot;https:&#x2F;&#x2F;twitter.com&#x2F;CoxHelp&quot;&gt;@CoxHelp&lt;&#x2F;a&gt; I’m trying to find out where in my city Cox is offering Gigablast internet. Who can I reach out to that would be able to help me?&lt;&#x2F;p&gt;
  &amp;mdash; Dylan Staley (@dstaley) &lt;a href=&quot;https:&#x2F;&#x2F;twitter.com&#x2F;dstaley&#x2F;status&#x2F;856967438621081600&quot;&gt;April 25, 2017&lt;&#x2F;a&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;The response I received pointed me towards a page on Cox&#x27;s website that allows you to enter an address to see if it had access to Gigablast, Cox&#x27;s gigabit internet service. Since I was more curious about the areas in which gigabit was offered (as opposed to a single address), I tried several more times to get in touch with someone at Cox who might be able to provide a list of neighborhoods.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;img&#x2F;ugh-seriously-cox.png&quot; alt=&quot;Cox&amp;#39;s entirely unhelpful response when asked for a list of addresses that have gigabit internet.&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;According to the five separate Cox representatives I spoke with, the only way to figure out where Cox is offering gigabit internet is to put addresses into their website, one by one.&lt;&#x2F;p&gt;
&lt;p&gt;So, I did. I checked whether Cox was offering gigabit internet for all 168,000 addresses listed in the city&#x27;s &lt;a href=&quot;https:&#x2F;&#x2F;data.brla.gov&#x2F;Housing-and-Development&#x2F;Street-Address-Listing&#x2F;6fyg-p3r9&quot;&gt;Open Data portal&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;After checking every address in the parish, I was incredibly surprised by how difficult it apparently was for Cox to give me a list of neighborhoods considering that such a list would have only been &lt;em&gt;two neighborhoods long&lt;&#x2F;em&gt;. In the almost two years since Cox &lt;a href=&quot;http:&#x2F;&#x2F;www.prnewswire.com&#x2F;news-releases&#x2F;cox-communications-launches-gigabit-internet-service-in-louisiana-300118022.html&quot;&gt;announced the availability of their gigabit service&lt;&#x2F;a&gt; at a ceremony in a neighborhood where the cheapest home &lt;a href=&quot;http:&#x2F;&#x2F;www.americanazachary.com&#x2F;available-homes-home-sites&#x2F;floor-plans&#x2F;&quot;&gt;starts at $288,000&lt;&#x2F;a&gt;, they&#x27;ve managed to roll the service out to a single additional neighborhood. The cheapest home in that neighborhood starts at $390,000.&lt;&#x2F;p&gt;
&lt;p&gt;In November of last year, &lt;a href=&quot;http:&#x2F;&#x2F;www.prnewswire.com&#x2F;news-releases&#x2F;100-fiber-network-powered-by-att-fiber-now-available-in-baton-rouge-area-300364490.html&quot;&gt;AT&amp;amp;T announced&lt;&#x2F;a&gt; that their fiber network was available in Baton Rouge, so I used the same method to check where they were offering service. Whereas only 200 addresses have access to Cox&#x27;s gigabit service, &lt;em&gt;almost 10,000&lt;&#x2F;em&gt; have access to AT&amp;amp;T&#x27;s. That means that while Cox&#x27;s gigabit network is available to only 0.12% of addresses in the parish, AT&amp;amp;T&#x27;s gigabit service is available to 6%, fifty times more. What&#x27;s even more surprising is that AT&amp;amp;T&#x27;s network isn&#x27;t limited to recently built homes. I was pleasantly surprised to find several homes built in the 70s that have access to gigabit from AT&amp;amp;T.&lt;&#x2F;p&gt;
&lt;p&gt;However, what I didn&#x27;t find were addresses in North Baton Rouge, despite the fact that the area was explicitly called out as having fiber in AT&amp;amp;T&#x27;s November press release. In fact, if you map out the areas where AT&amp;amp;T is offering gigabit and compare it to median household income, a (sadly unsurprising) trend emerges.&lt;&#x2F;p&gt;
&lt;figure&gt;
  &lt;iframe src=&quot;https:&#x2F;&#x2F;baton-rouge-gigabit-map.glitch.me&#x2F;&quot; class=&quot;w-full h-[500px]&quot;&gt;&lt;&#x2F;iframe&gt;
  &lt;figcaption class=&quot;italic text-sm&quot;&gt;Income data from the U.S. Census Bureau’s 2015 American Community Survey. &lt;a target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;baton-rouge-gigabit-map.glitch.me&#x2F;&quot;&gt;View map in new tab&lt;&#x2F;a&gt;&lt;&#x2F;figcaption&gt;
&lt;&#x2F;figure&gt;
&lt;p&gt;A few weeks ago, Ars Technica published an article with the headline &lt;a href=&quot;https:&#x2F;&#x2F;arstechnica.com&#x2F;information-technology&#x2F;2017&#x2F;03&#x2F;att-allegedly-discriminated-against-poor-people-in-broadband-upgrades&#x2F;&quot;&gt;&quot;AT&amp;amp;T allegedly &quot;discriminated&quot; against poor people in broadband upgrades&quot;&lt;&#x2F;a&gt;, which highlighted a report that demonstrated there was virtually no overlap in areas that had fiber-to-the-home and areas that had a poverty rate of 35% or more. It&#x27;s unsurprising that lower-income areas in Baton Rouge are being left out as well.&lt;&#x2F;p&gt;
</content:encoded>
            </item>
            <item>
                <title>Stencil and Webpack 2</title>
                <pubDate>Sun, 12 Feb 2017 12:00:00 -0800</pubDate>
                <link>https://dstaley.com/posts/stencil-and-webpack-2/</link>
                <guid>https://dstaley.com/posts/stencil-and-webpack-2/</guid>
                <description>With the recent release of Webpack 2, I wanted to see how easy it would be to migrate Stencil to it, and if I could take advantage of automatic code splitting.</description>
                <content:encoded>&lt;p&gt;Over the past few weeks I&#x27;ve read a lot about code splitting, or the idea of delivering just the JavaScript needed to run the current page&#x2F;route. Code splitting is an attempt to balance the benefits and drawbacks to large client-side applications. Instead of downloading and parsing all of the JavaScript for your entire application, you can deliver just a portion. As users navigate to new routes within your application, the code necessary for them is downloaded on the fly. Initial page load times are quicker thanks to less JavaScript being downloaded, but subsequent page loads of new routes have to download a small amount of JavaScript. It&#x27;s all about finding a balance that&#x27;s right for your application and users.&lt;&#x2F;p&gt;
&lt;p&gt;Since code splitting works by mapping modules to routes, I wanted to see if I could apply the strategy to Stencil, which already has a good one-module-per-route setup. The first step though was to upgrade to Webpack 2, which I knew had quite a few breaking changes.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;upgrading-to-webpack-2&quot;&gt;Upgrading to Webpack 2&lt;&#x2F;h2&gt;
&lt;p&gt;&lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;dstaley&#x2F;stencil-webpack-2&#x2F;commit&#x2F;470b55e6557bf4f852e43cf7d7032e2249f10270&quot;&gt;Annotated Commit&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Since some of the default Stencil dependencies rely on Webpack 1, I had to upgrade them along with Webpack.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;code&gt;npm install babel-loader babel-core babel-preset-es2015 webpack karma-webpack --save-dev&lt;&#x2F;code&gt;&lt;&#x2F;p&gt;
&lt;p&gt;After updating dependencies, I modified the Webpack config to accommodate for breaking changes. Thankfully, the only configuration change needed was a tweak to the configuration for &lt;code&gt;babel-loader&lt;&#x2F;code&gt;. In Webpack 1, you could simply define the loader as &lt;code&gt;babel&lt;&#x2F;code&gt;, and Webpack would automatically add the &lt;code&gt;-loader&lt;&#x2F;code&gt; prefix. In Webpack 2, that&#x27;s no longer the case, so you need to specify it manually. With that change made, I ran &lt;code&gt;stencil start&lt;&#x2F;code&gt; and was greeted with a fairly cryptic stack trace:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#6c7079;&quot;&gt;&lt;code&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;&#x2F;Users&#x2F;dstaley&#x2F;.nvm&#x2F;versions&#x2F;node&#x2F;v4.3.2&#x2F;lib&#x2F;node_modules&#x2F;@bigcommerce&#x2F;stencil-cli&#x2F;node_modules&#x2F;jspm&#x2F;node_modules&#x2F;systemjs&#x2F;node_modules&#x2F;es6-module-loader&#x2F;dist&#x2F;es6-module-loader.src.js:2712
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;            throw new TypeError(&amp;#39;Illegal module name &amp;quot;&amp;#39; + name + &amp;#39;&amp;quot;&amp;#39;);
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;            ^
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;TypeError: Illegal module name &amp;quot;&#x2F;Users&#x2F;dstaley&#x2F;Desktop&#x2F;stencil-error&#x2F;node_modules&#x2F;babel-loader&#x2F;lib&#x2F;index.js&amp;quot;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;    at Loader.$__Object$defineProperty.value (&#x2F;Users&#x2F;dstaley&#x2F;.nvm&#x2F;versions&#x2F;node&#x2F;v4.3.2&#x2F;lib&#x2F;node_modules&#x2F;@bigcommerce&#x2F;stencil-cli&#x2F;node_modules&#x2F;jspm&#x2F;node_modules&#x2F;systemjs&#x2F;node_modules&#x2F;es6-module-loader&#x2F;dist&#x2F;es6-module-loader.src.js:2712:19)
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;    at Loader.loader.normalize (&#x2F;Users&#x2F;dstaley&#x2F;.nvm&#x2F;versions&#x2F;node&#x2F;v4.3.2&#x2F;lib&#x2F;node_modules&#x2F;@bigcommerce&#x2F;stencil-cli&#x2F;node_modules&#x2F;jspm&#x2F;node_modules&#x2F;systemjs&#x2F;dist&#x2F;system.src.js:1672:44)
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;    at Loader.loader.normalize (&#x2F;Users&#x2F;dstaley&#x2F;.nvm&#x2F;versions&#x2F;node&#x2F;v4.3.2&#x2F;lib&#x2F;node_modules&#x2F;@bigcommerce&#x2F;stencil-cli&#x2F;node_modules&#x2F;jspm&#x2F;node_modules&#x2F;systemjs&#x2F;dist&#x2F;system.src.js:1713:44)
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;    at Loader.loader.normalize (&#x2F;Users&#x2F;dstaley&#x2F;.nvm&#x2F;versions&#x2F;node&#x2F;v4.3.2&#x2F;lib&#x2F;node_modules&#x2F;@bigcommerce&#x2F;stencil-cli&#x2F;node_modules&#x2F;jspm&#x2F;node_modules&#x2F;systemjs&#x2F;dist&#x2F;system.src.js:2182:44)
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;    at Loader.import (&#x2F;Users&#x2F;dstaley&#x2F;.nvm&#x2F;versions&#x2F;node&#x2F;v4.3.2&#x2F;lib&#x2F;node_modules&#x2F;@bigcommerce&#x2F;stencil-cli&#x2F;node_modules&#x2F;jspm&#x2F;node_modules&#x2F;systemjs&#x2F;node_modules&#x2F;es6-module-loader&#x2F;dist&#x2F;es6-module-loader.src.js:2280:40)
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;    at Loader.loader.import (&#x2F;Users&#x2F;dstaley&#x2F;.nvm&#x2F;versions&#x2F;node&#x2F;v4.3.2&#x2F;lib&#x2F;node_modules&#x2F;@bigcommerce&#x2F;stencil-cli&#x2F;node_modules&#x2F;jspm&#x2F;node_modules&#x2F;systemjs&#x2F;dist&#x2F;system.src.js:103:25)
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;    at loadLoader (&#x2F;Users&#x2F;dstaley&#x2F;Desktop&#x2F;stencil-error&#x2F;node_modules&#x2F;webpack&#x2F;node_modules&#x2F;loader-runner&#x2F;lib&#x2F;loadLoader.js:3:16)
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;    at iteratePitchingLoaders (&#x2F;Users&#x2F;dstaley&#x2F;Desktop&#x2F;stencil-error&#x2F;node_modules&#x2F;webpack&#x2F;node_modules&#x2F;loader-runner&#x2F;lib&#x2F;LoaderRunner.js:169:2)
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;    at runLoaders (&#x2F;Users&#x2F;dstaley&#x2F;Desktop&#x2F;stencil-error&#x2F;node_modules&#x2F;webpack&#x2F;node_modules&#x2F;loader-runner&#x2F;lib&#x2F;LoaderRunner.js:362:2)
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;    at NormalModule.doBuild (&#x2F;Users&#x2F;dstaley&#x2F;Desktop&#x2F;stencil-error&#x2F;node_modules&#x2F;webpack&#x2F;lib&#x2F;NormalModule.js:129:2)
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The thing that made this error really strange is that manually running &lt;code&gt;webpack&lt;&#x2F;code&gt; wouldn&#x27;t trigger it; only when running &lt;code&gt;stencil start&lt;&#x2F;code&gt; would I encounter the error. Knowing this, I set out to figure out what Stencil was doing that would interfere with Webpack&#x27;s ability to load &lt;code&gt;babel-loader&lt;&#x2F;code&gt;. Long story short, Stencil is using JSPM, which polyfills &lt;code&gt;System.import&lt;&#x2F;code&gt;. Webpack&#x27;s &lt;code&gt;loader-runner&lt;&#x2F;code&gt; defaults to using &lt;code&gt;System.import&lt;&#x2F;code&gt; if it&#x27;s defined, otherwise it will load things itself. The issue arose from the fact that JSPM&#x27;s polyfill wasn&#x27;t compatible with Webpack. Since transitioning Stencil away from JSPM wasn&#x27;t what I wanted to spend my Sunday on, I decided to use a workaround that I&#x27;m honestly not very proud of: I straight up removed the &lt;code&gt;System&lt;&#x2F;code&gt; object from the global scope. The good news though is that it worked, and it didn&#x27;t seem to break anything obvious!&lt;&#x2F;p&gt;
&lt;p&gt;After disabling the polyfill, &lt;code&gt;stencil start&lt;&#x2F;code&gt; was able to properly call Webpack and build the bundle! So now it was time to move on to the difficult part.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;code-splitting&quot;&gt;Code Splitting&lt;&#x2F;h2&gt;
&lt;p&gt;&lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;dstaley&#x2F;stencil-webpack-2&#x2F;commit&#x2F;4776bf964a35c9b2b87b54db066ad8558b9727e2&quot;&gt;Annotated Commit&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Since Stencil already ships with a very nicely modularized structure, adding code splitting was surprisingly easy. Every page on a Stencil-based site has page type, and each page type has a module containing the functionality for that page type. The first step was to remove all the imports, and redefine them as calls to &lt;code&gt;System.import&lt;&#x2F;code&gt;. So this:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;js&quot; style=&quot;background-color:#2b303b;color:#6c7079;&quot; class=&quot;language-js &quot;&gt;&lt;code class=&quot;language-js&quot; data-lang=&quot;js&quot;&gt;&lt;span style=&quot;color:#9acc76;&quot;&gt;&amp;#39;pages&#x2F;account&#x2F;orders&#x2F;all&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;account&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;,
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;became&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;js&quot; style=&quot;background-color:#2b303b;color:#6c7079;&quot; class=&quot;language-js &quot;&gt;&lt;code class=&quot;language-js&quot; data-lang=&quot;js&quot;&gt;&lt;span style=&quot;color:#9acc76;&quot;&gt;&amp;#39;pages&#x2F;account&#x2F;orders&#x2F;all&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;: () &lt;&#x2F;span&gt;&lt;span style=&quot;color:#cd74e8;&quot;&gt;=&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;System&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5cb3fa;&quot;&gt;import&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#9acc76;&quot;&gt;&amp;#39;.&#x2F;theme&#x2F;account&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;),
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Since &lt;code&gt;System.import&lt;&#x2F;code&gt; uses a network request to load the module, it returns a Promise. I had to make some slight adjustments to how Stencil invokes the module to accommodate for this:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;js&quot; style=&quot;background-color:#2b303b;color:#6c7079;&quot; class=&quot;language-js &quot;&gt;&lt;code class=&quot;language-js&quot; data-lang=&quot;js&quot;&gt;&lt;span style=&quot;font-style:italic;color:#5f697a;&quot;&gt;&#x2F;&#x2F; Comment
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#cd74e8;&quot;&gt;const &lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;pageTypePromise &lt;&#x2F;span&gt;&lt;span style=&quot;color:#adb7c9;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;pages&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5ebfcc;&quot;&gt;get&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;templateFile&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;);
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#cd74e8;&quot;&gt;if &lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;pageTypePromise &lt;&#x2F;span&gt;&lt;span style=&quot;color:#adb7c9;&quot;&gt;!== &lt;&#x2F;span&gt;&lt;span style=&quot;color:#db9d63;&quot;&gt;false&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;) {
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5cb3fa;&quot;&gt;pageTypePromise&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;().&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5ebfcc;&quot;&gt;then&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;((&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;PageTypeFn&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;) &lt;&#x2F;span&gt;&lt;span style=&quot;color:#cd74e8;&quot;&gt;=&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;{
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#cd74e8;&quot;&gt;const &lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;pageType &lt;&#x2F;span&gt;&lt;span style=&quot;color:#adb7c9;&quot;&gt;= new &lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;PageTypeFn.default(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;context&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;);
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;pageType&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;context &lt;&#x2F;span&gt;&lt;span style=&quot;color:#adb7c9;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;context&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#cd74e8;&quot;&gt;return &lt;&#x2F;span&gt;&lt;span style=&quot;color:#5cb3fa;&quot;&gt;loader&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;pageType&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;pages&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;);
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;  });
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;After setting up all the routes and attempting to run Stencil, I was incredibly excited to see my &lt;code&gt;main.js&lt;&#x2F;code&gt; bundle loaded, and then a network call for the homepage&#x27;s module. Unfortunately, it was requesting the module from the root of the web server, instead of relative to the location of the entry point. After some quick Googling, I discovered that &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;webpack&#x2F;webpack&#x2F;issues&#x2F;3265&quot;&gt;you can manually specify the directory that contains your bundles&lt;&#x2F;a&gt;, and Webpack will use that when requesting them. However, since Stencil uses dynamic paths for each build of your theme, I knew that manually choosing the directory wouldn&#x27;t work. Thankfully, there&#x27;s &lt;a href=&quot;https:&#x2F;&#x2F;developer.mozilla.org&#x2F;en-US&#x2F;docs&#x2F;Web&#x2F;API&#x2F;Document&#x2F;currentScript&quot;&gt;a little known browser API&lt;&#x2F;a&gt; that will report the script element that the current script is invoking from. Like most good things in this world, &lt;a href=&quot;http:&#x2F;&#x2F;caniuse.com&#x2F;#feat=document-currentscript&quot;&gt;it&#x27;s not available on Internet Explorer&lt;&#x2F;a&gt;, but is available in all evergreen browsers.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;js&quot; style=&quot;background-color:#2b303b;color:#6c7079;&quot; class=&quot;language-js &quot;&gt;&lt;code class=&quot;language-js&quot; data-lang=&quot;js&quot;&gt;&lt;span style=&quot;color:#cd74e8;&quot;&gt;const &lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;scriptURL &lt;&#x2F;span&gt;&lt;span style=&quot;color:#adb7c9;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;document.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;currentScript&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;src&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;__webpack_public_path__ &lt;&#x2F;span&gt;&lt;span style=&quot;color:#adb7c9;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;scriptURL&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5ebfcc;&quot;&gt;slice&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#db9d63;&quot;&gt;0&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;scriptURL&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5ebfcc;&quot;&gt;lastIndexOf&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#9acc76;&quot;&gt;&amp;quot;&#x2F;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;) &lt;&#x2F;span&gt;&lt;span style=&quot;color:#adb7c9;&quot;&gt;+ &lt;&#x2F;span&gt;&lt;span style=&quot;color:#db9d63;&quot;&gt;1&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;);
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;After figuring that out, I was able to successfully load modules dynamically! To confirm that code splitting was working, I used the excellent &lt;a href=&quot;https:&#x2F;&#x2F;chrisbateman.github.io&#x2F;webpack-visualizer&#x2F;&quot;&gt;Webpack Visualizer&lt;&#x2F;a&gt; by &lt;a href=&quot;https:&#x2F;&#x2F;twitter.com&#x2F;batemanchris&quot;&gt;Chris Bateman&lt;&#x2F;a&gt;. When I loaded my &lt;code&gt;main&lt;&#x2F;code&gt; bundle, I was a bit taken aback by the fact that Lodash represented a whopping 32% of the bundle size!&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;img&#x2F;lodash-bundle-size.jpg&quot; alt=&quot;Lodash represented 32% of the main bundle&amp;#39;s size&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;One of the really neat things about Lodash is that it&#x27;s incredibly modular, and also happens to have a very nice implementation of ES modules through &lt;a href=&quot;https:&#x2F;&#x2F;www.npmjs.com&#x2F;package&#x2F;lodash-es&quot;&gt;&lt;code&gt;lodash-es&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;, where each function is exported as an ES module. There&#x27;s currently &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;webpack&#x2F;webpack&#x2F;issues&#x2F;1750&quot;&gt;an issue with tree shaking for lodash-es&lt;&#x2F;a&gt;, but I worked around it by importing modifying my imports to point directly to the specific file, instead of using module resolution. You can see the changes I made to several of Stencil&#x27;s JavaScript files in this &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;dstaley&#x2F;stencil-webpack-2&#x2F;commit&#x2F;856a5d8da20c335fcee9d5a9bde4a147d7119879&quot;&gt;annotated commit&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;results&quot;&gt;Results&lt;&#x2F;h2&gt;
&lt;p&gt;Going into this, I wasn&#x27;t really expecting massive performance improvements from code splitting. I&#x27;m mainly drawn to the idea of not loading code I&#x27;m not going to use, so I didn&#x27;t perform any benchmarks. (Also the Stencil development server is quite slow because of all the extra things it does behind the scene, so I wouldn&#x27;t have gotten accurate numbers anyhow.) What I did examine, however, was JavaScript bundle size. I was quite impressed with the difference! Without code splitting, my bundle was 1,941KB (397KB gzip). With code splitting, the two files necessary for the homepage totaled 851KB (202KB gzip). That&#x27;s an over 49% reduction in bytes transferred, and a 56% reduction in parsed size!&lt;&#x2F;p&gt;
&lt;p&gt;The best part of code splitting is that as pages grow in functionality, you need not burden every route with the added cost of downloading and parsing a large amount of JavaScript that will never be executed. Combined with BigCommerce&#x27;s aggressive fingerprinting and caching, the additional network request on page load isn&#x27;t that big of a deal.&lt;&#x2F;p&gt;
&lt;p&gt;If you&#x27;re curious about how to implement any of this, you can checkout the &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;dstaley&#x2F;stencil-webpack-2&quot;&gt;code on GitHub&lt;&#x2F;a&gt;, where I&#x27;ve also annotated my commits. It&#x27;s really important to note that this is highly experimental, and I&#x27;m not currently using it in production. In fact, I haven&#x27;t even attempted to upload a theme built with code splitting, so I&#x27;m not entirely sure it&#x27;d even work. It&#x27;s more of a thought experiment in how Stencil can be optimized, rather than a specific recommendation.&lt;&#x2F;p&gt;
&lt;p&gt;If you have any questions or comments about Webpack, code splitting, or Stencil, please feel free to &lt;a href=&quot;https:&#x2F;&#x2F;twitter.com&#x2F;dstaley&quot;&gt;tweet me&lt;&#x2F;a&gt;!&lt;&#x2F;p&gt;
</content:encoded>
            </item>
            <item>
                <title>Migrating to Stencil</title>
                <pubDate>Mon, 31 Oct 2016 12:00:00 -0800</pubDate>
                <link>https://dstaley.com/posts/migrating-to-stencil/</link>
                <guid>https://dstaley.com/posts/migrating-to-stencil/</guid>
                <description>About a year ago, I helped build a new website for Marucci Sports. Almost exactly a year later, we rebuilt the site from the ground up using Stencil, a new theme framework from Bigcommerce.</description>
                <content:encoded>&lt;p&gt;On June 4, 2015, I managed to convince my current employer &lt;a href=&quot;https:&#x2F;&#x2F;maruccisports.com&quot;&gt;Marucci Sports&lt;&#x2F;a&gt; to take on the audacious task of redesigning our website. Exactly four weeks later, I interviewed the amazing designer who would eventually become our Lead Web Designer, Diana Perkins. Over the next 20 weeks Diana and our marketing team created a design that would go on to win our ecommerce platform&#x27;s &lt;a href=&quot;https:&#x2F;&#x2F;www.bigcommerce.com&#x2F;press&#x2F;releases&#x2F;bigcommerce-announces-2016-design-award-winners&#x2F;&quot;&gt;first ever Design Awards&lt;&#x2F;a&gt;. We launched late November of that year, just five days before Black Friday.&lt;&#x2F;p&gt;
&lt;p&gt;And this year, we raised the stakes. We rebuilt and redesigned our site in just six weeks.&lt;&#x2F;p&gt;
&lt;p&gt;Right when we began work on our site in 2015, &lt;a href=&quot;https:&#x2F;&#x2F;bigcommerce.com&quot;&gt;Bigcommerce&lt;&#x2F;a&gt; (the ecommerce platform maruccisports.com is powered by) launched their new theme development framework &lt;a href=&quot;https:&#x2F;&#x2F;stencil.bigcommerce.com&#x2F;&quot;&gt;Stencil&lt;&#x2F;a&gt;. However, at launch Stencil was missing some of the functionality we had used to create our existing site, namely custom templates for products, categories, and pages. Essentially, if we wanted to use Stencil from the beginning, we would have had to redesign everything to use the same template. This wasn&#x27;t something we were okay with, so we held out on transitioning to Stencil until custom template support was added.&lt;&#x2F;p&gt;
&lt;p&gt;Thankfully, on August 31 of this year, custom template support was added to Stencil. What follows is how we took our Blueprint site and rebuilt it using Stencil.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;getting-started&quot;&gt;Getting Started&lt;&#x2F;h2&gt;
&lt;p&gt;Before we could even begin with Stencil, we realized that there was one restriction we&#x27;d quickly run into. Our theme relies heavily on high-resolution imagery, stored in our codebase as massive files that are then downscaled by our image CDN &lt;a href=&quot;http:&#x2F;&#x2F;imgix.com&#x2F;&quot;&gt;imgix&lt;&#x2F;a&gt;. Unfortunately, Stencil limits your theme to 50MB total, and requires you to host any files that put you above that threshold somewhere else. It would have been an absolute nightmare to keep our image files outside of our Stencil theme; it would have essentially negated all benefits we would have received from being able to run our site locally. This is where one of my favorite aspects of Stencil comes into play: it&#x27;s completely open source. The code powering the local development environment and the server-side template rendering &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;bigcommerce&#x2F;stencil-cli&quot;&gt;is available on GitHub&lt;&#x2F;a&gt;, and they even left pull requests enabled.&lt;&#x2F;p&gt;
&lt;p&gt;So, with just a few short lines of code &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;bigcommerce&#x2F;paper&#x2F;pull&#x2F;97&quot;&gt;I sent in a pull request&lt;&#x2F;a&gt; that enables theme developers to keep large files locally in their project, but reference them in a way that when in production, a remote resource is served. For instance&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;handlebars&quot; style=&quot;background-color:#2b303b;color:#6c7079;&quot; class=&quot;language-handlebars &quot;&gt;&lt;code class=&quot;language-handlebars&quot; data-lang=&quot;handlebars&quot;&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;{% raw %}{{&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;cdn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#9acc76;&quot;&gt;&amp;quot;imgix:really-large-image.png&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;}}{% endraw %}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;would become&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#6c7079;&quot;&gt;&lt;code&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;&#x2F;assets&#x2F;cdn&#x2F;imgix&#x2F;really-large-image.png
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;when developing locally, and&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#6c7079;&quot;&gt;&lt;code&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;https:&#x2F;&#x2F;source-name.imgix.net&#x2F;really-large-image.png
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;when the theme is deployed to a store. This does mean we need to remember to deploy the &lt;code&gt;cdn&#x2F;imgix&lt;&#x2F;code&gt; folder when we deploy our site, but it&#x27;s still better than needing to deploy images just to test locally!&lt;&#x2F;p&gt;
&lt;p&gt;The other small problem we had to overcome was that when we bundled our theme with &lt;code&gt;stencil bundle&lt;&#x2F;code&gt;, our CDN folder would get included. We &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;bigcommerce&#x2F;stencil-cli&#x2F;pull&#x2F;240&quot;&gt;submitted a PR to fix that as well&lt;&#x2F;a&gt;, but as of writing it hasn&#x27;t been merged. Thankfully there&#x27;s no harm in running a fork of &lt;code&gt;stencil-cli&lt;&#x2F;code&gt;, so that&#x27;s how we&#x27;re working around that for the time being.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;getting-started-for-real-this-time&quot;&gt;Getting Started (For Real This Time)&lt;&#x2F;h2&gt;
&lt;p&gt;Once we figured out our image hosting strategy, we started working on migrating our theme from Blueprint to Stencil. We began by forking &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;bigcommerce&#x2F;stencil&quot;&gt;Cornerstore&lt;&#x2F;a&gt;, Bigcommerce&#x27;s showcase Stencil theme. From there, we slowly moved over each custom layout we&#x27;d designed, focusing first on pages that didn&#x27;t have dynamic content. Moving the pages over was relatively simple, complicated only by the fact we were also moving from a Bootstrap-based theme to a Foundation-based one. Our previous theme used Bootstrap&#x27;s grid classes, so rewriting templates and stylesheets to be more semantic was probably the most complex change we had to make.&lt;&#x2F;p&gt;
&lt;p&gt;After our static content pages were added, we focused on sprucing up dynamically generated pages like our category and product pages. However, there&#x27;s one page in particular that I&#x27;m incredibly proud of, and that&#x27;s the layout for our custom wood bats. Our designer, Diana, really knocked it out of the park with this layout.&lt;&#x2F;p&gt;
&lt;picture&gt;
  &lt;source media=&quot;(min-width: 1024px)&quot; srcset=&quot;&#x2F;img&#x2F;custom-bat-template-desktop.png&quot; &#x2F;&gt;
  &lt;img alt=&quot;Custom Pro customization experience&quot; src=&quot;&#x2F;img&#x2F;custom-bat-template.png&quot; &#x2F;&gt;
&lt;&#x2F;picture&gt;
&lt;p&gt;In the process of rewriting the custom bat layout, we also took the opportunity to upgrade our bat preview to take advantage of Stencil&#x27;s new event system. In our Blueprint theme, we were listening for clicks on the individual swatches that represented the colors available.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;js&quot; style=&quot;background-color:#2b303b;color:#6c7079;&quot; class=&quot;language-js &quot;&gt;&lt;code class=&quot;language-js&quot; data-lang=&quot;js&quot;&gt;&lt;span style=&quot;color:#5cb3fa;&quot;&gt;$&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#9acc76;&quot;&gt;&amp;quot;.productAttributeList&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;).&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5cb3fa;&quot;&gt;on&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#9acc76;&quot;&gt;&amp;quot;click&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#9acc76;&quot;&gt;&amp;quot;li.swatch&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#cd74e8;&quot;&gt;function &lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;e&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;) {
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;color:#cd74e8;&quot;&gt;if &lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;e&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;.target.tagName &lt;&#x2F;span&gt;&lt;span style=&quot;color:#adb7c9;&quot;&gt;=== &lt;&#x2F;span&gt;&lt;span style=&quot;color:#9acc76;&quot;&gt;&amp;quot;SPAN&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;) {
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#cd74e8;&quot;&gt;var &lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;children &lt;&#x2F;span&gt;&lt;span style=&quot;color:#adb7c9;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;e&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;currentTarget&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;children&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;[&lt;&#x2F;span&gt;&lt;span style=&quot;color:#db9d63;&quot;&gt;0&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;].childNodes;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#cd74e8;&quot;&gt;var &lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;piece &lt;&#x2F;span&gt;&lt;span style=&quot;color:#adb7c9;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;children&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;[&lt;&#x2F;span&gt;&lt;span style=&quot;color:#db9d63;&quot;&gt;3&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;].&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;dataset&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;option&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#cd74e8;&quot;&gt;var &lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;color &lt;&#x2F;span&gt;&lt;span style=&quot;color:#adb7c9;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;children&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;[&lt;&#x2F;span&gt;&lt;span style=&quot;color:#db9d63;&quot;&gt;5&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;].&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;textContent&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5ebfcc;&quot;&gt;replace&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5ebfcc;&quot;&gt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#db9d63;&quot;&gt;\s&lt;&#x2F;span&gt;&lt;span style=&quot;color:#adb7c9;&quot;&gt;+&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5ebfcc;&quot;&gt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#cd74e8;&quot;&gt;g&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#9acc76;&quot;&gt;&amp;quot;_&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;).&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5ebfcc;&quot;&gt;toLowerCase&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;();
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#cd74e8;&quot;&gt;var &lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;state &lt;&#x2F;span&gt;&lt;span style=&quot;color:#adb7c9;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;{};
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;state&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;[&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;piece&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;] &lt;&#x2F;span&gt;&lt;span style=&quot;color:#adb7c9;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;color&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;el&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5cb3fa;&quot;&gt;setState&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;state&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;);
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;  }
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;});
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This…worked. However if you look closely you can see we&#x27;re relying on a very specific order of &lt;code&gt;childNodes&lt;&#x2F;code&gt;. Thankfully I don&#x27;t think we ever touched them, but it was still less than ideal.&lt;&#x2F;p&gt;
&lt;p&gt;In Stencil, this is a bit more elegant:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;js&quot; style=&quot;background-color:#2b303b;color:#6c7079;&quot; class=&quot;language-js &quot;&gt;&lt;code class=&quot;language-js&quot; data-lang=&quot;js&quot;&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;utils&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;hooks&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5cb3fa;&quot;&gt;on&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#9acc76;&quot;&gt;&amp;quot;product-option-change&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;, (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;event&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;changedOption&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;) &lt;&#x2F;span&gt;&lt;span style=&quot;color:#cd74e8;&quot;&gt;=&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;{
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;font-style:italic;color:#5f697a;&quot;&gt;&#x2F;&#x2F; this.options is a mapping of unfriendly Bigcommerce IDs to friendly values.
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;color:#cd74e8;&quot;&gt;if &lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;this&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;.options.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5cb3fa;&quot;&gt;hasOwnProperty&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;changedOption&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;.name)) {
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#cd74e8;&quot;&gt;const &lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;option &lt;&#x2F;span&gt;&lt;span style=&quot;color:#adb7c9;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;this&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;.options[&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;changedOption&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;.name];
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#cd74e8;&quot;&gt;const &lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;piece &lt;&#x2F;span&gt;&lt;span style=&quot;color:#adb7c9;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;option&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;.name;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#cd74e8;&quot;&gt;const &lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;color &lt;&#x2F;span&gt;&lt;span style=&quot;color:#adb7c9;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;option&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;values&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;[&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;changedOption&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;.value];
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;canvas&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#5ebfcc;&quot;&gt;set&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;piece&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#eb6772;&quot;&gt;color&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;);
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;  }
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#abb2bf;&quot;&gt;});
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;While the event system is a great addition, the most powerful addition to Stencil is definitely the ability to inject context data (such as the current product, category, customer, etc.) into JavaScript. That&#x27;s what &lt;code&gt;this.options&lt;&#x2F;code&gt; is in the previous example; it&#x27;s a collection of the current product&#x27;s options. By giving developers access to the raw product data, you can build great experiences like our bat preview entirely on the Bigcommerce platform, no add-on needed.&lt;&#x2F;p&gt;
&lt;p&gt;One caveat to this, though, is that you don&#x27;t get a lot of flexibility in what data you receive. For instance, we wanted to add previews of available colors for our products to our category pages. On a product page, you have access to the product&#x27;s options, but you don&#x27;t have access to this information from a category page. Because of this limitation (most likely made to reduce backend processing time), we had to build a simple off-platform API integration to get color values for a given product, and render them on a category page. This is actually the only feature on our entire site that requires something other than what you get out of the box with Bigcommerce!&lt;&#x2F;p&gt;
&lt;picture&gt;
  &lt;source media=&quot;(min-width: 1024px)&quot; srcset=&quot;&#x2F;img&#x2F;category-template-desktop.png&quot; &#x2F;&gt;
  &lt;img alt=&quot;Category page product listing with color swatches&quot; src=&quot;&#x2F;img&#x2F;category-template.png&quot; &#x2F;&gt;
&lt;&#x2F;picture&gt;
&lt;p&gt;Speaking of things you get out of the box, one of the big things we tried to focus on with our transition to Stencil was using native functionality instead of trying to go at it on our own. This enables us to rely more heavily on the control panel, and enable more people in the company to make minor changes like editing the order of products on our category pages. One of the largest downsides of using static category layouts was that it forced us to make and deploy even small changes like updating a price. Looking back, we should have sucked it up and figured out how to properly use Blueprint to render a list of products. If any theme developer is reading this and thinking of doing what we did and treating their category pages like a static site: don&#x27;t. In Blueprint we felt we would have been restricted in how we presented our products, but we&#x27;re incredibly thankful not to feel that way with Stencil. By working with the theme framework instead of against it, we were able to add things like product filters and sorting to our category pages, which in turn provides a much more engaging shopping experience to our users.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;things-i-like-about-stencil&quot;&gt;Things I like about Stencil&lt;&#x2F;h2&gt;
&lt;p&gt;There&#x27;s a ton to love about Stencil, from both the store owner perspective and that of a theme developer. I think I&#x27;m a bit lucky in that I can speak from both points of view.&lt;&#x2F;p&gt;
&lt;p&gt;As a developer, the single biggest benefit that Stencil brings is a local development environment. As far as I know, Bigcommerce is &lt;em&gt;the only SaaS eCommerce platform&lt;&#x2F;em&gt; that is able to provide an environment like this to their developers, and it&#x27;s a Really Big Deal. (Although all rendering happens locally, Stencil still requires an internet connection to retrieve live-store information. Thankfully it wouldn&#x27;t be too terribly difficult to create a mock server that returns static data.)&lt;&#x2F;p&gt;
&lt;p&gt;One of the other big things that I really love about Stencil is that it feels like a tool designed for how the web is built in 2016. Stylesheets are written in SCSS; JavaScript is transpiled with Babel (and you can even add your own plugins and transforms!) and bundled with webpack (although you&#x27;re free to use whatever you&#x27;d like), and templates are written in Handlebars. With our Blueprint theme, we basically designed our own build system based on Gulp, SCSS, Babel, and Nunjucks, so we felt right at home with Stencil.&lt;&#x2F;p&gt;
&lt;p&gt;The last feature of Stencil I want to mention is it&#x27;s new optimized single-page checkout. On our previous site, our checkout process was part of our theme, meaning we were the only ones able to make changes to it. On Stencil, the entire checkout experience is controlled by Bigcommerce. While we lose out on the ability to control the overall design, we basically gain an entire team of product designers and developers who are dedicated to making our checkout experience the best it can be. The optimized single page checkout experience is still in beta, but once it rolls out to all our customers we&#x27;re anticipating a nice decrease in our abandonment rate. One other neat thing about letting Bigcommerce control our checkout is that we were able to being accepting Apple Pay with a few button clicks!&lt;&#x2F;p&gt;
&lt;h2 id=&quot;things-i-d-like-to-see-improved&quot;&gt;Things I&#x27;d like to see improved&lt;&#x2F;h2&gt;
&lt;p&gt;As of writing, Stencil has only been tested on Node 4 and npm 2. I&#x27;d love to see official support for the latest LTS version of Node (v6.9.1) and npm 3 (and yarn). I tried running Stencil on Node 6 way back when it was first released, so I&#x27;m not sure if support has been improved since then.&lt;&#x2F;p&gt;
&lt;p&gt;For a locally running site, Stencil&#x27;s performance doesn&#x27;t seem all that great. I have a MacBook Pro with a 3 GHz Core i7 and 16GB of RAM, and my page load times are still pretty bad. The biggest bottleneck is that Stencil has to make multiple API requests to get the information it needs to render the page. There&#x27;s a caching mechanism in place, but it doesn&#x27;t seem to help all that much. Towards the end of our Stencil migration, we setup a Heroku app running Stencil in a Docker container. Sadly the free dyno wasn&#x27;t powerful enough to run the app without timing out, so we had to move to a &lt;code&gt;standard-2x&lt;&#x2F;code&gt; dyno to keep response times reasonable. Even then, I had to give our team warning that the live site wouldn&#x27;t be as slow.&lt;&#x2F;p&gt;
&lt;p&gt;Another aspect I think Stencil could greatly benefit from is a more robust JavaScript API client. For instance, the included &lt;code&gt;stencil-utils&lt;&#x2F;code&gt; library has a function &lt;code&gt;api.product.getById&lt;&#x2F;code&gt;. At first glance, you might think this allows you to retrieve a product as a JSON object, but by default it will return the rendered HTML of the product page. You actually need to &lt;a href=&quot;http:&#x2F;&#x2F;stackoverflow.com&#x2F;a&#x2F;37976026&#x2F;1492393&quot;&gt;manually add a Handlebars component to your theme&lt;&#x2F;a&gt; that emits the JSON representation of the page&#x27;s context, and then pass that template to the function as a parameter. I&#x27;m glad there&#x27;s at least a workaround, but it feels silly that it was necessary in the first place. Other quibbles with the JavaScript API include the inability to request product categories by ID (you have to use their URLs like &lt;code&gt;&#x2F;apparel&#x2F;adult&lt;&#x2F;code&gt;) and the opaqueness of available configuration options (like including related products in a product response).&lt;&#x2F;p&gt;
&lt;p&gt;One of the things we spent a lot of time building for our previous theme was an automated build-and-deploy system. We could run &lt;code&gt;gulp deploy --environment production&lt;&#x2F;code&gt; and have our latest changes live on the website in about two minutes. With Stencil, however, there&#x27;s no developer-friendly deployment method. Deploying a Stencil site consists of running &lt;code&gt;stencil bundle&lt;&#x2F;code&gt; to create a ZIP archive containing your build artifacts, logging into your site, navigating to your store&#x27;s design settings, uploading the ZIP file, waiting for it to process, selecting it, clicking apply, and then confirming your change. It&#x27;s tedious to say the least, but at least there&#x27;s now a way to atomically deploy a theme. Previously, we were syncing a WebDAV directory with our local repo, which resulted in about two minutes where anyone who visited the site could potentially receive a non-functional page. We mitigated this as much as possible with asset fingerprinting, but it couldn&#x27;t be entirely prevented. That being said, I did spend some time reverse-engineering the Stencil theme upload process, and so far I&#x27;m able to bundle a theme and submit it for processing all from the command line. Pretty soon I think I&#x27;ll be able to run &lt;code&gt;stencil bundle &amp;amp;&amp;amp; gulp deploy&lt;&#x2F;code&gt; to deploy the site, which is an important first step to building a continuous delivery pipeline for the site.&lt;&#x2F;p&gt;
&lt;p&gt;My final suggestion is more of a &quot;here&#x27;s something that would be awesome&quot; as opposed to &quot;here&#x27;s something that needs to be improved&quot;. I&#x27;d love to see a Stencil theme that relies heavily on client-side rendering. The beginnings are definitely there, since the &lt;code&gt;stencil-utils&lt;&#x2F;code&gt; package can return rendered HTML, but being able to run your entire store as an Ember or React app would be a really awesome feature.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;closing-thoughts&quot;&gt;Closing Thoughts&lt;&#x2F;h2&gt;
&lt;p&gt;Over the last several weeks, there have been a ton of moments where I really felt the benefits Stencil has brought to Marucci. I will admit, however, that a lot of our previous pain was self-inflicted because of our insistence on using static category pages. Recently I was in a meeting where we wanted to make an older product available for sale again. With a few clicks we were able to edit the product&#x27;s name and description, change the category it was in, and make it available for sale. For anyone who runs a store, I&#x27;m sure that sounds like the most basic thing, but previously that change would have required opening our local repo, copy&#x2F;pasting the product&#x27;s nunjucks object from one category file to another, changing the information, and then deploying the site (which took a solid two minutes by the way. Two minutes every time I wanted to preview a change or debug some JavaScript.). Then, we&#x27;d still have to go into the admin panel and change the product&#x27;s name so that it was the same as in the template file. To be honest, writing that all out really drives home how poor of a decision it was on my part, but it does make me that much more excited to be using Stencil!&lt;&#x2F;p&gt;
&lt;p&gt;For theme developers who are thinking about moving from Blueprint to Stencil, I say definitely get started. I wouldn&#x27;t try to rush and get it done before the holiday shopping season, but it should definitely be a priority for you. The improvements it brings on just a style level alone are worth it, but the developer ergonomics really seal the deal.&lt;&#x2F;p&gt;
&lt;p&gt;If you&#x27;re a merchant on Bigcommerce, especially one who is using a stock theme, I&#x27;d highly recommend taking a look at the gorgeous Stencil themes available on the &lt;a href=&quot;https:&#x2F;&#x2F;www.bigcommerce.com&#x2F;theme-store&#x2F;&quot;&gt;Bigcommerce Theme Store&lt;&#x2F;a&gt;. I spoke about a lot of the development benefits to Stencil, but I strongly feel that a good development experience leads to better themes with more features for merchants!&lt;&#x2F;p&gt;
&lt;p&gt;If you have any questions about our experiences with Bigcommerce or with Stencil, please feel free to &lt;a href=&quot;https:&#x2F;&#x2F;twitter.com&#x2F;dstaley&quot;&gt;tweet me&lt;&#x2F;a&gt;!&lt;&#x2F;p&gt;
</content:encoded>
            </item>
    </channel>
</rss>