<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[joel.net - JavaScript, ReactJS, Node, Programming and More!]]></title><description><![CDATA[joel.net - JavaScript, ReactJS, Node, Programming and More!]]></description><link>https://joel.net</link><generator>RSS for Node</generator><lastBuildDate>Fri, 10 Apr 2026 11:27:34 GMT</lastBuildDate><atom:link href="https://joel.net/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[JavaScript and Type Narrowing Magic]]></title><description><![CDATA[Type Narrowing allows the IDE (development environment) to narrow down the object type, from a union type, to a more specific type.
Bullsh*t Example
In this bullsh*t example, value is the union type string | number, meaning it can either be a string ...]]></description><link>https://joel.net/javascript-and-type-narrowing-magic</link><guid isPermaLink="true">https://joel.net/javascript-and-type-narrowing-magic</guid><category><![CDATA[JavaScript]]></category><category><![CDATA[jsdoc]]></category><category><![CDATA[TypeSafety]]></category><dc:creator><![CDATA[Joel Thoms]]></dc:creator><pubDate>Wed, 16 Apr 2025 18:13:06 GMT</pubDate><content:encoded><![CDATA[<p>Type Narrowing allows the IDE (development environment) to narrow down the object type, from a union type, to a more specific type.</p>
<h2 id="heading-bullsht-example">Bullsh*t Example</h2>
<p>In this bullsh*t example, <code>value</code> is the union type <code>string | number</code>, meaning it can either be a <code>string</code> or <code>number</code> (ya, please don’t do that).</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1744822536520/86c1ec6e-71ce-485f-bf14-8499b5240ea0.png" alt="const value: string | number" class="image--center mx-auto" /></p>
<p>The type can be narrowed down by checking the type with an <code>if</code>. Now that the IDE knows the type, it also knows <code>.toUpperCase()</code> only exists on <code>string</code> and <code>.toFixed()</code> only exists on <code>number</code>.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1744822271328/8c8b4baa-b177-4e34-8d87-072cbac946d2.png" alt class="image--center mx-auto" /></p>
<h2 id="heading-type-narrowing-an-api-response">Type Narrowing an API Response</h2>
<p>In this example, <code>fetchData</code> can respond with either a <code>Result</code> or <code>ErrorMessage</code> type. We can use an <code>if</code> again to narrow the type down. The IDE now knows inside the <code>if</code>, <code>data</code> is always of type <code>Result</code> and inside the <code>else</code>, it must be of type <code>ErrorMessage</code>.  </p>
<p>The IDE now gives us proper <code>Property does not exist</code> error messages when used on the wrong type.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1744823031014/9f025338-f3dd-46ac-8b78-6395af8adf80.png" alt class="image--center mx-auto" /></p>
<h2 id="heading-strict-null-checks">Strict Null Checks</h2>
<p>When Strict Null Checks are enabled, we can narrow down <code>data.message</code> type from a <code>Nullable string</code> to <code>string</code>. The error seen on line <code>17</code> doesn’t appear on line <code>20</code> because the <code>if</code> narrows down the type.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1744823727291/666d8cad-1642-4590-86f4-6abb1bf58339.png" alt class="image--center mx-auto" /></p>
<h2 id="heading-type-narrowing-magic-in-custom-classes">Type Narrowing Magic in Custom Classes</h2>
<p>Type Narrowing can also be controlled in custom classes. Inside the <code>if</code>, the IDE knows <code>res</code> is of type <code>Success</code> and in the <code>else</code> it is type <code>Failure</code>.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1744824216522/3ec66afc-5cd1-4f9d-997e-6bbff57eaea7.png" alt class="image--center mx-auto" /></p>
<p>The type Narrowing is controlled through the JSDocs on the <code>isOk()</code> function like this:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">export</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Success</span> </span>{
  <span class="hljs-comment">/** <span class="hljs-doctag">@returns <span class="hljs-type">{this is Success&lt;T&gt;}</span> </span>*/</span>
  isOk() {
    <span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>;
  }
}

<span class="hljs-keyword">export</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Failure</span> </span>{
  <span class="hljs-comment">/** <span class="hljs-doctag">@returns <span class="hljs-type">{this is Success&lt;any&gt;}</span> </span>*/</span>
  isOk() {
    <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>;
  }
}
</code></pre>
<p>Here’s the full source to the above example:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// @ts-check</span>

<span class="hljs-comment">/**
 * <span class="hljs-doctag">@template <span class="hljs-variable">T</span></span>
 * Represents a successful result.
 */</span>
<span class="hljs-keyword">export</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Success</span> </span>{
  <span class="hljs-comment">/** <span class="hljs-doctag">@param <span class="hljs-type">{T}</span> </span>value */</span>
  <span class="hljs-keyword">constructor</span>(value) {
    <span class="hljs-comment">/** <span class="hljs-doctag">@private </span>*/</span>
    <span class="hljs-built_in">this</span>._value = value;
  }

  <span class="hljs-comment">/** <span class="hljs-doctag">@returns <span class="hljs-type">{this is Success&lt;T&gt;}</span> </span>*/</span>
  isOk() {
    <span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>;
  }

  <span class="hljs-comment">/** <span class="hljs-doctag">@returns <span class="hljs-type">{T}</span> </span>*/</span>
  value() {
    <span class="hljs-keyword">return</span> <span class="hljs-built_in">this</span>._value;
  }
}

<span class="hljs-comment">/**
 * <span class="hljs-doctag">@template <span class="hljs-variable">E</span></span>
 * Represents a failed result.
 */</span>
<span class="hljs-keyword">export</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Failure</span> </span>{
  <span class="hljs-comment">/** <span class="hljs-doctag">@param <span class="hljs-type">{E}</span> </span>error */</span>
  <span class="hljs-keyword">constructor</span>(error) {
    <span class="hljs-comment">/** <span class="hljs-doctag">@private </span>*/</span>
    <span class="hljs-built_in">this</span>._error = error;
  }

  <span class="hljs-comment">/** <span class="hljs-doctag">@returns <span class="hljs-type">{this is Success&lt;any&gt;}</span> </span>*/</span>
  isOk() {
    <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>;
  }

  <span class="hljs-comment">/** <span class="hljs-doctag">@returns <span class="hljs-type">{E}</span> </span>*/</span>
  error() {
    <span class="hljs-keyword">return</span> <span class="hljs-built_in">this</span>._error;
  }
}

<span class="hljs-comment">/**
 * Result class with static helpers for Success and Failure.
 */</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Result</span> </span>{
  <span class="hljs-comment">/**
   * <span class="hljs-doctag">@template <span class="hljs-variable">T</span></span>
   * <span class="hljs-doctag">@param <span class="hljs-type">{T}</span> <span class="hljs-variable">value</span></span>
   * <span class="hljs-doctag">@returns <span class="hljs-type">{Success&lt;T&gt;}</span></span>
   */</span>
  <span class="hljs-keyword">static</span> success(value) {
    <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> Success(value);
  }

  <span class="hljs-comment">/**
   * <span class="hljs-doctag">@template <span class="hljs-variable">E</span></span>
   * <span class="hljs-doctag">@param <span class="hljs-type">{E}</span> <span class="hljs-variable">error</span></span>
   * <span class="hljs-doctag">@returns <span class="hljs-type">{Failure&lt;E&gt;}</span></span>
   */</span>
  <span class="hljs-keyword">static</span> failure(error) {
    <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> Failure(error);
  }
}

<span class="hljs-keyword">const</span> res = <span class="hljs-built_in">Math</span>.random() &gt; <span class="hljs-number">0.5</span> ? Result.success(<span class="hljs-number">42</span>) : Result.failure(<span class="hljs-string">"error"</span>);

<span class="hljs-keyword">if</span> (res.isOk()) {
  <span class="hljs-built_in">console</span>.log(res.value());
  <span class="hljs-built_in">console</span>.log(res.error());
} <span class="hljs-keyword">else</span> {
  <span class="hljs-built_in">console</span>.log(res.value());
  <span class="hljs-built_in">console</span>.log(res.error());
}
</code></pre>
<h2 id="heading-summary">Summary</h2>
<p>Type Narrowing can be used to narrow down a type from a union type to a specific type. This allows the IDE to know which type you are working with and give errors or warnings about incorrect usage allowing you to write safer code.</p>
<p><a target="_blank" href="https://x.com/joelnet">Follow me on X</a></p>
<p>Cheers 🍻</p>
]]></content:encoded></item><item><title><![CDATA[First Beta of My Indie Game Dungeon Sweeper: A Knights Adventure]]></title><description><![CDATA[There is still a lot of work to be done, but I finally have something "playable" 🔥
I would describe it as a mashup of Mine Sweeper and a rogue-like dungeon crawler RPG.

That's right! The first playable demo is up! The game is still in active develo...]]></description><link>https://joel.net/first-beta-of-my-indie-game-dungeon-sweeper-a-knights-adventure</link><guid isPermaLink="true">https://joel.net/first-beta-of-my-indie-game-dungeon-sweeper-a-knights-adventure</guid><category><![CDATA[indiedev]]></category><category><![CDATA[Game Development]]></category><category><![CDATA[Games]]></category><dc:creator><![CDATA[Joel Thoms]]></dc:creator><pubDate>Tue, 25 Apr 2023 21:43:30 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1682528832565/92ce37d9-1246-4230-ac32-95cbf15b284b.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>There is still a lot of work to be done, but I finally have something "playable" 🔥</p>
<p>I would describe it as a mashup of Mine Sweeper and a rogue-like dungeon crawler RPG.</p>
<p><img src="https://img.itch.zone/aW1hZ2UvMjAzNDU1MC8xMTk3MjcxNS5naWY=/347x500/w%2F8Fgz.gif" alt /></p>
<p>That's right! The first playable demo is up! The game is still in active development, so expect bugs and features to change as I complete the project.</p>
<h2 id="heading-im-seeking-feedback">I'm Seeking Feedback</h2>
<p>I need your help! Here's the deal, I love the game, I truly do, I think it's a blast. But I need to know if <em>you</em> like the game. Do <em>you</em> find it fun? What works for you, and what doesn't?</p>
<h2 id="heading-i-need-help">I Need Help!</h2>
<p>Do you find the game fun? Share it with a friend. Share it on social. Leave a comment, favorite, etc on the <a target="_blank" href="https://joelnet.itch.io/dungeon-sweeper-a-knights-adventure-beta">itch</a> page. All those things help.</p>
<p>Do you know any YouTubers/TikTokers that review small indies? Send them my way!</p>
<h2 id="heading-play-the-demo-here">Play the Demo Here</h2>
<p>You can play the early access demo on <a target="_blank" href="https://joelnet.itch.io/dungeon-sweeper-a-knights-adventure-beta">itch</a> here:</p>
<p><a target="_blank" href="https://joelnet.itch.io/dungeon-sweeper-a-knights-adventure-beta">https://joelnet.itch.io/dungeon-sweeper-a-knights-adventure-beta</a></p>
<p>Keep in mind, it's early. There will be bugs and not all the features are there today but still so excited!!! 😆</p>
<p>Drop your email in the Newsletter box below or subscribe on the <a target="_blank" href="https://joelnet.itch.io/dungeon-sweeper-a-knights-adventure-beta">itch</a> page for updates! LFG!!! 🚀</p>
]]></content:encoded></item><item><title><![CDATA[Creating a TypeWriter Effect in Phaser.js v3]]></title><description><![CDATA[I recently added some dialog options for my indie game, Dungeon Sweeper: A Knights Adventure and wanted to create a TypeWriter effect for the dialog text.

This technique works well for monospaced fonts and may not work for variable-width fonts.
Fail...]]></description><link>https://joel.net/creating-a-typewriter-effect-in-phaserjs-v3</link><guid isPermaLink="true">https://joel.net/creating-a-typewriter-effect-in-phaserjs-v3</guid><category><![CDATA[Game Development]]></category><category><![CDATA[JavaScript]]></category><category><![CDATA[phaser]]></category><dc:creator><![CDATA[Joel Thoms]]></dc:creator><pubDate>Mon, 24 Apr 2023 18:19:23 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/-m88z7ily-w/upload/5c9add5635dd6bf8e15ae711b0ef323e.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I recently added some dialog options for my indie game, <strong>Dungeon Sweeper: A Knights Adventure</strong> and wanted to create a TypeWriter effect for the dialog text.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1682357116151/6362b1e8-ffc3-4837-9179-1facec666d17.gif" alt class="image--center mx-auto" /></p>
<p>This technique works well for monospaced fonts and may not work for variable-width fonts.</p>
<h2 id="heading-failed-attempts">Failed Attempts</h2>
<p>My first attempt was to write out text one letter at a time. But this didn't work with text that wasn't aligned to the left or text that wrapped.</p>
<p>My second attempt was to use a non-space white space to replace all characters. This would help with the non-left text and word-wrapped text, but then I learned not all white space is created equal.</p>
<h2 id="heading-the-figure-space">The Figure Space</h2>
<p>I found <a target="_blank" href="https://qwerty.dev/whitespace/">an amazing webpage</a> that helped me find the correct white space to use. The space I need is called a Figure Space and is the same width as a character.</p>
<h2 id="heading-the-code">The Code</h2>
<p>I wanted to be able to create a function that accepts a <code>Phaser.GameObjecte.Text</code> and automatically applies the animation.</p>
<p>The function would maintain the state needed to run the animation.</p>
<p>The function would need to be awaitable, so the caller knows when the animation is complete.</p>
<p>The function works by first resetting the <code>text</code> to empty. Then every interval, it writes out the next letter. To maintain the same letter positions, the <code>text</code> is kept the same length. The unseen characters are replaced with invisible characters.</p>
<pre><code class="lang-javascript"><span class="hljs-comment">/**
 * Create typewriter animation for text
 * <span class="hljs-doctag">@param <span class="hljs-type">{Phaser.GameObjects.Text}</span> <span class="hljs-variable">target</span></span>
 * <span class="hljs-doctag">@param <span class="hljs-type">{number}</span> </span>[speedInMs=25]
 * <span class="hljs-doctag">@returns <span class="hljs-type">{Promise&lt;void&gt;}</span></span>
 */</span>
<span class="hljs-keyword">export</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">animateText</span>(<span class="hljs-params">target, speedInMs = <span class="hljs-number">25</span></span>) </span>{
  <span class="hljs-comment">// store original text</span>
  <span class="hljs-keyword">const</span> message = target.text;
  <span class="hljs-keyword">const</span> invisibleMessage = message.replace(<span class="hljs-regexp">/[^ ]/g</span>, <span class="hljs-string">" "</span>);

  <span class="hljs-comment">// clear text on screen</span>
  target.text = <span class="hljs-string">""</span>;

  <span class="hljs-comment">// mutable state for visible text</span>
  <span class="hljs-keyword">let</span> visibleText = <span class="hljs-string">""</span>;

  <span class="hljs-comment">// use a Promise to wait for the animation to complete</span>
  <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Promise</span>(<span class="hljs-function">(<span class="hljs-params">resolve</span>) =&gt;</span> {
    <span class="hljs-keyword">const</span> timer = target.scene.time.addEvent({
      <span class="hljs-attr">delay</span>: speedInMs,
      <span class="hljs-attr">loop</span>: <span class="hljs-literal">true</span>,
      callback() {
        <span class="hljs-comment">// if all characters are visible, stop the timer</span>
        <span class="hljs-keyword">if</span> (target.text === message) {
          timer.destroy();
          <span class="hljs-keyword">return</span> resolve();
        }

        <span class="hljs-comment">// add next character to visible text</span>
        visibleText += message[visibleText.length];

        <span class="hljs-comment">// right pad with invisibleText</span>
        <span class="hljs-keyword">const</span> invisibleText = invisibleMessage.substring(visibleText.length);

        <span class="hljs-comment">// update text on screen</span>
        target.text = visibleText + invisibleText;
      },
    });
  });
}
</code></pre>
<h2 id="heading-summary">Summary</h2>
<p>I'm pretty happy with how the animation turned out and I am now calling this from within my dialog function.</p>
<p>If you are interested in following the game's development, toss your email into the newsletter box below.</p>
<p>Cheers 🍻</p>
]]></content:encoded></item><item><title><![CDATA[Dungeon Sweeper: A Knight's Adventure -- devlog 004]]></title><description><![CDATA[TL;DR: I discuss adding intuitive features, dealing with Phaser quirks such as a tween memory leak and scaling issues, managing screen size, and creating an executable for a demo. I express my struggle with the less enjoyable parts of game developmen...]]></description><link>https://joel.net/dungeon-sweeper-a-knights-adventure-devlog-004</link><guid isPermaLink="true">https://joel.net/dungeon-sweeper-a-knights-adventure-devlog-004</guid><category><![CDATA[GameDev]]></category><category><![CDATA[indiedev]]></category><category><![CDATA[Games]]></category><dc:creator><![CDATA[Joel Thoms]]></dc:creator><pubDate>Thu, 20 Apr 2023 14:00:39 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1681936698687/2bc1dd6b-2ad8-4c18-b9bb-572609a81843.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>TL;DR: I discuss adding intuitive features, dealing with Phaser quirks such as a tween memory leak and scaling issues, managing screen size, and creating an executable for a demo. I express my struggle with the less enjoyable parts of game development but remain motivated to push out a demo.</p>
<h2 id="heading-name-change">Name Change</h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1681933838478/a24d2d08-ca28-4420-9181-ee0d2279a49b.png" alt class="image--center mx-auto" /></p>
<p>I think I like the name <strong>Dungeon Sweeper: A Knight's Adventure</strong> more than <strong>Dungeon Sweep: Knight</strong>. Sure, it's a bit longer, but it's also more descriptive of what it is. The game is based on Minesweeper, So I think keeping Sweeper (as opposed to Sweep) in the name makes sense. Let me know what you think in the comments!</p>
<h2 id="heading-giving-the-user-what-they-want">Giving The User What They Want</h2>
<p>I watched a handful of people play my game and I kept giving the same instructions, "You can't drag to scroll, you have to click a tile to move to that spot."</p>
<p>I would repeat those instructions multiple times to the same players over and over again. They couldn't help but try to scroll the screen.</p>
<p>I decided to stop fighting it. If their intuition was to scroll, then I needed to add that feature.</p>
<p>A game that is intuitive to the user is better than forcing my way. It turns out I love the mechanic and it was worth the added time to get it into the game.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1681937892815/fb709ce5-4bcd-4c49-8948-206ca56669b9.gif" alt class="image--center mx-auto" /></p>
<h2 id="heading-im-struggling-with-phaser">I'm Struggling with Phaser</h2>
<p>In the beginning, Phaser was a breeze, but I'm running into a bunch of little quirks that are slowing me down.</p>
<h3 id="heading-tween-memory-leak">Tween Memory Leak</h3>
<p>I have a tween set to <code>repeat: -1</code> (loop infinitely) and the target Sprite is destroyed, the tween remains. This is effectively a memory leak. I assumed Phaser was smart enough to kill the tween when the target is destroyed (Phaser does this for non-infinite Tweens). To work around this I have this block of code:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// tween must be manuall removed when sprite is destroyed</span>
sprite.once(<span class="hljs-string">"destroy"</span>, <span class="hljs-function">() =&gt;</span> {
  scene.tweens.remove(tween);
});
</code></pre>
<h3 id="heading-my-zoom-scale-mistakes">My Zoom / Scale Mistakes</h3>
<p>The pixel art I am using is 16x16 and I am scaling this up 2.5x to 40x40. In Phaser, it's really easy to set the camera zoom.</p>
<pre><code class="lang-javascript">camera.setZoom(scale);
</code></pre>
<p>I was really happy with this early on since the camera did all the work for me. I'm paying for this mistake now.</p>
<p>All positioning now needs to be calculated with this scale in mind. Dragging was complex because the pixels you drag vs the pixels that must move are affected by the scale.</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// Update X position of score text</span>
score.setX(scene.cameras.main.width / scale);

<span class="hljs-comment">// Actual distance ondrag needs to be scaled down to match finger</span>
<span class="hljs-keyword">let</span> diffX = <span class="hljs-built_in">Math</span>.round(pointer.x - pointer.downX) / scale;
<span class="hljs-keyword">let</span> diffY = <span class="hljs-built_in">Math</span>.round(pointer.y - pointer.downY) / scale;
</code></pre>
<p>I could have eliminated this scale complexity by manually upscaling all my assets and using a zoom of 1:1.</p>
<h3 id="heading-scroll-on-drag-sucks">Scroll on Drag Sucks</h3>
<p>Something so simple and so complicated. The scale did not help with this either.</p>
<p>Because Phaser's camera is following a Sprite, I decided to modify the <code>camera.scale.followOffset</code> to move the screen. I'm not sure if this is the best way.</p>
<p>But found I had to keep saving the followOffset in my global state to run the math.</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// 😥 Math is getting complicated for a simple scroll on drag</span>

<span class="hljs-keyword">const</span> diffX = <span class="hljs-built_in">Math</span>.round(pointer.x - pointer.downX) / scale;
<span class="hljs-keyword">const</span> diffY = <span class="hljs-built_in">Math</span>.round(pointer.y - pointer.downY) / scale;

<span class="hljs-keyword">const</span> offsetX = states.drag.followOffsetX + diffX;
<span class="hljs-keyword">const</span> offsetY = states.drag.followOffsetY + diffY;

camera.setFollowOffset(offsetX, offsetY);
</code></pre>
<p>Overscrolling? So if you drag too far and hit the bounds I have set, the camera stops scrolling, which is what I want to happen. But the numbers keep going up in the <code>followOffset</code>. This means the user has to drag back to the visible area before the screen starts to animate again. It's kinda like a dead zone.</p>
<p>After I figured that out, I had to do even more math to limit the <code>followOffset</code> to the bounds set on the camera.</p>
<pre><code class="lang-javascript">scene.input.addListener(<span class="hljs-string">"dragend"</span>, <span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">const</span> knight = gameState.sprites[<span class="hljs-string">"knight"</span>];
    <span class="hljs-keyword">const</span> distX = knight.x - camera.worldView.centerX;
    <span class="hljs-keyword">const</span> distY = knight.y - camera.worldView.centerY;

    <span class="hljs-comment">// this is needed to prevent overshooting the offset</span>
    camera.setFollowOffset(distX, distY);
});
</code></pre>
<h3 id="heading-fit-to-screen">Fit to Screen</h3>
<p>I ended up having problems using Phaser's fit-to-screen feature. It worked on Chrome and Android but had issues on iPhone. So now I am managing the screen size myself. I am not sure if this is the best way. Let me know in the comments.</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// Phaser's fit-to-screen didn't work for me. So let's manually do it.</span>
<span class="hljs-built_in">window</span>.addEventListener(<span class="hljs-string">"resize"</span>, <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">const</span> { game } = gameState;
  game.scale.resize(
    <span class="hljs-built_in">document</span>.body.clientWidth,
    <span class="hljs-built_in">document</span>.body.clientHeight
  );
});
</code></pre>
<h2 id="heading-im-not-having-fun">I'm Not Having Fun</h2>
<p>So I'm into the part of the Game Dev that is simply not fun. I am trying to push out a demo so people can try it and provide feedback. But there are a few steps to take that I'm not enjoying one bit.</p>
<h3 id="heading-creating-an-executable">Creating an Executable</h3>
<p>I need to create a "build". It's a simple HTML / JavaScript game so I guess I'll package this up with Electron. Electron seems popular, so sure let's use that. Well, Electron has turned my 1MB JS bundle into a 100MB exe. But hey, at least it's an executable.</p>
<p>Is there an alternative to Electron I can use? Help!</p>
<h3 id="heading-code-signing-nightmares">Code Signing Nightmares</h3>
<p>Electron suggests Code Signing the executables. I looked into this and it's too expensive for what I am making. Everything requires an annual fee. Rough estimates on the low end of this are around $200/year.</p>
<p>I got excited when I found <a target="_blank" href="https://github.com/sigstore/fulcio">Fulcio</a> a free Code Signing option but after wasting half a day on this, I found this line in the GitHub, <em>"Fulcio only issues short-lived certificates that are valid for 10 minutes."</em></p>
<p>It's that <strong>10 minutes</strong> part that makes this option unusable for me. Honestly, I am not sure what I could use this for with that short life.</p>
<p>I may just end up doing Android / iPhone (and no Windows / Mac / Linux), which does have some costs too, but I have already accepted this cost mentally before starting the project.</p>
<p>Or maybe I'll provide executables with no Code Signature. I don't know what the impact of that will be.</p>
<p>What are others doing? Is Code Signing that important? I don't know.</p>
<h3 id="heading-marketing">Marketing</h3>
<p>Let me start by saying I probably couldn't be doing any worse than I am right now in this department. But what can I do?</p>
<p>I need a page to send people to. I have nothing right now. So I'm putting all my focus into getting a Demo live. I'm planning on getting this on itch.io. The problem with itch.io is they recommend having it as "close to ready" as possible and I need to set a price to charge. That's complicated since it's still in development.</p>
<p>But I have found a good compromise. I can set a price, and also provide a demo for free. So that way people can get the demo.</p>
<p>I thought of having two itch pages, one for the demo and then when ready, another for the paid game. I don't know the itch etiquette or how others handle this.</p>
<p>If you know, let me know!</p>
<h2 id="heading-demo-soon">Demo Soon!</h2>
<p>Well, the demo is coming very soon! If you don't want to miss it, toss your name into that Newsletter box at the bottom. See you soon! Cheers 🍻</p>
]]></content:encoded></item><item><title><![CDATA[Dungeon Sweep: Knight -- devlog 004]]></title><description><![CDATA[It's starting to feel like a real game!
Ok. It's still in the "prototype" phase. The gameplay basics are there, but it's still not ready for a demo. Maybe next week? If you are looking for a Demo, hit me up on my Twitter!
UI Updates
I am liking the l...]]></description><link>https://joel.net/dungeon-sweep-knight-devlog-004</link><guid isPermaLink="true">https://joel.net/dungeon-sweep-knight-devlog-004</guid><category><![CDATA[Game Development]]></category><dc:creator><![CDATA[Joel Thoms]]></dc:creator><pubDate>Mon, 06 Mar 2023 00:07:03 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1676921458576/59f17bc9-1272-4eee-a270-075e33df38d8.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>It's starting to feel like a real game!</p>
<p>Ok. It's still in the "prototype" phase. The gameplay basics are there, but it's still not ready for a demo. Maybe next week? If you are looking for a Demo, hit me up on <a target="_blank" href="https://twitter.com/joelnet">my Twitter</a>!</p>
<h2 id="heading-ui-updates">UI Updates</h2>
<p>I am liking the latest UI improvements. I added a hearts and points system to the UI. This helps indicate when a user makes a correct move as well as an incorrect one. The screen shake and buzz sound weren't enough according to the feedback I received.</p>
<p>There is also little floater points +10 or -50 above enemies when you make a move. This change with the instant feedback is a big improvement!</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1678040152478/f9f020d9-b153-40cf-b6b5-d314d6ca17fe.jpeg" alt class="image--center mx-auto" /></p>
<h2 id="heading-intermission">Intermission</h2>
<p>I decided an intermission was needed before each level. Something that let you know your status (what level you are entering) as well as what enemies you would encounter. You can't see it in the screenshots, but those little guys are animated and they look great!</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1678007157367/f73837bb-7e5e-4d9b-8483-7262918a18e4.jpeg" alt class="image--center mx-auto" /></p>
<h2 id="heading-end-game-report">End-Game Report</h2>
<p>After playing through a few hundred rounds of this game I was left at the end with no indication of how well I did. The game would simply be over and I would start again. I added an end-game report to show the player how well they did. I'll probably keep some sort of high scores for the player and then compare these scores to previous runs.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1678007168197/e045593e-d337-454c-b9f0-ce3117e92f03.jpeg" alt class="image--center mx-auto" /></p>
<h2 id="heading-state-management">State Management</h2>
<p>Well, I wasn't going to do it because I thought this game is just too simple to need anything complex, but I'm slowly merging in some state management and I can see immediate improvements.</p>
<p>Because I'm still in the keep-it-simple mindset, I went with what I felt was a simple solution. I'm using <a target="_blank" href="https://github.com/sindresorhus/on-change">on-change</a> to watch a game state object. on-change has a subscribe event that fires and pushes events into an EventEmitter. Now elements like hearts or player score can subscribe to the EventEmitter and update themselves. 💪</p>
<p>This pub/sub model eliminated some redundant code and makes debugging easier. When I update a value in the debug console, the game picks up the change and immediately reacts.</p>
<h2 id="heading-whats-next">What's Next?</h2>
<p>Following the 80/20 rule I think I'll have to spend the remainder 80% of my time to complete that last 20% 😂</p>
<p>The minesweeper rules are in place and working pretty well but the game is still missing potions, treasure chests, and boss fights.</p>
<p>I have a broad idea on how those RPG mechanics will work. My plan is to put some mechanics into the game and see how they feel. Get some feedback, adjust, repeat.</p>
<p>But I might work on some boring stuff like the main screen, settings, easy, normal, hard mode. Make things easier on myself and the beta testers.</p>
<p>Hit me up and throw your email in the box below to keep updated on the progress and when be notified when a demo arrives 🔥</p>
]]></content:encoded></item><item><title><![CDATA[Dungeon Sweep: Knight -- devlog 003]]></title><description><![CDATA[tl;dr I posted Gameplay Video on Twitter. Added some graphical flair to the dungeon. Found some free sound effects. Huge Gameplay pivot to rogue-like. Save states added. Struggles with ChatGPT and Phaser. How do I promote this thing?
Gameplay Video
I...]]></description><link>https://joel.net/dungeon-sweep-knight-devlog-003</link><guid isPermaLink="true">https://joel.net/dungeon-sweep-knight-devlog-003</guid><category><![CDATA[Game Development]]></category><category><![CDATA[Developer]]></category><dc:creator><![CDATA[Joel Thoms]]></dc:creator><pubDate>Wed, 01 Mar 2023 18:35:00 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1677623420771/573dae7b-c147-48a6-befb-68a882d6e8ea.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><strong>tl;dr</strong> I posted Gameplay Video on Twitter. Added some graphical flair to the dungeon. Found some free sound effects. Huge Gameplay pivot to rogue-like. Save states added. Struggles with ChatGPT and Phaser. How do I promote this thing?</p>
<h2 id="heading-gameplay-video">Gameplay Video</h2>
<p>I posted a Gameplay video of the prototype on <a target="_blank" href="https://twitter.com/joelnet">my Twitter</a>.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://twitter.com/joelnet/status/1629511542952857601">https://twitter.com/joelnet/status/1629511542952857601</a></div>
<p> </p>
<h2 id="heading-graphics">Graphics</h2>
<p>I added some flair to the dungeon. Some of the walls have holes in them. Some holes ooze a green slime. Random floor tiles will have cracks. Skulls scattered throughout the dungeon. I am loving how these small improvements made the dungeon feel less homogenous and more like an actual dungeon!</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1677619587616/04e60fc2-820a-4c49-9bac-726a76d879c0.png" alt class="image--center mx-auto" /></p>
<h2 id="heading-audio">Audio</h2>
<p>I added some free sounds that I found on <a target="_blank" href="https://pixabay.com/">Pixabay</a>. It's nice to hear the game coming together. I also added some variation to the sounds, so swinging the sword can produce multiple sounds. This minor variation added a lot more to the game than I thought it would have.</p>
<h2 id="heading-gameplay-pivot">Gameplay Pivot</h2>
<p>My initial vision was to have some type of Candy Crush-style level progression, but as I am playing, I'm not 100% sold on that design. If I made a single mistake, I'd abort the level and restart so I could get 3 stars. That's not how it's meant to be played.</p>
<p>From the feedback, people were also confused about their goals, since you wouldn't know how well you did until the end when they received a few or a lot of stars. In Minesweeper this is clear because if you mistake a mine, the game is immediately over. In this game, you are allowed a few mistakes.</p>
<p>So I am thinking of going rogue-like. The plan now is to have a health bar and points system. You keep progressing through the dungeon gaining or losing health and points until you finally meet your fate. The health bar will give the player immediate feedback when they do something wrong.</p>
<p>The rogue-like mechanic also allows for easier integration with items such as health magic potions, and maybe vendors.</p>
<p>I will also include a simple version where the player can just play a single level.</p>
<h2 id="heading-saving-state">Saving State</h2>
<p>Because I plan to use Apache Cordova to launch on mobile platforms, I need to use Cordova-friendly save states. After reviewing the options in Cordova documentation, I am going with IndexDB. It's simple enough to use and compatible across web/android/iphone.</p>
<p>I was able to implement IndexDB save states in a few minutes, so that also makes me happy.</p>
<h2 id="heading-struggles-with-chatgpt">Struggles with ChatGPT</h2>
<p>I thought ChatGPT was going to make this game dev 10x faster but in reality, it hasn't. ChatGPT has been giving me wrong information or bad code. ChatGPT also isn't that familiar with Phaser3. There is also a lot more code, like game play stuff, that ChatGPT just can't help with.</p>
<p>ChatGPT has excelled at classic algorithms. For example ChatGPT helped me create a recursive function to traverse a Tree structure to find the entrance.</p>
<pre><code class="lang-javascript"><span class="hljs-comment">/**
 * <span class="hljs-doctag">@param <span class="hljs-type">{TreeNode&lt;Container&gt;}</span> <span class="hljs-variable">dungeonTree</span></span>
 * <span class="hljs-doctag">@returns <span class="hljs-type">{Room}</span></span>
 */</span>
<span class="hljs-keyword">export</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">findDungeonEntranceRoom</span>(<span class="hljs-params">dungeonTree</span>) </span>{
  <span class="hljs-keyword">if</span> (!dungeonTree) <span class="hljs-keyword">return</span> <span class="hljs-literal">null</span>;

  <span class="hljs-keyword">if</span> (dungeonTree.leaf.room?.id.startsWith(<span class="hljs-string">"entrance_"</span>)) {
    <span class="hljs-keyword">return</span> dungeonTree.leaf.room;
  }

  <span class="hljs-keyword">const</span> leftLeaf = findDungeonEntranceRoom(dungeonTree.left);
  <span class="hljs-keyword">if</span> (leftLeaf) <span class="hljs-keyword">return</span> leftLeaf;

  <span class="hljs-keyword">return</span> findDungeonEntranceRoom(dungeonTree.right);
}
</code></pre>
<p>But a lot of the time ChatGPT would give me bad code and bad advice.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://twitter.com/joelnet/status/1630598703164604416">https://twitter.com/joelnet/status/1630598703164604416</a></div>
<p> </p>
<p>ChatGPT has been hit or miss with more missing than I'd like.</p>
<p>Even with these issues, if I had to do it all again from the beginning, I would still use ChatGPT.</p>
<h2 id="heading-struggles-with-phaser">Struggles with Phaser</h2>
<p>The Phaser documentation leaves something to be desired. The code examples are good, but I often find the perfect example for Phaser2 and then struggle to search the docs to find the same example for Phaser3. The differences between these two have given me and ChatGPT many headaches.</p>
<p>I have also run into small quirks that have eaten hours of my time. Here's one example that stole a few hours of my life. If that <code>await sleep(0)</code> statement is removed, <code>camera.worldView</code> won't be set and therefore <code>heartBar.setPosition</code> will be set incorrectly.</p>
<p>So in we go, the classic <code>setTimeout</code> fix. 🤦</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">drawUi</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">const</span> { camera } = gameState;
  <span class="hljs-keyword">const</span> heartBar = heartbar.getSprite().setScrollFactor(<span class="hljs-number">0</span>);

  <span class="hljs-keyword">await</span> sleep(<span class="hljs-number">0</span>); <span class="hljs-comment">// IMPORTANT: sleep allows camera.worldView to be set.</span>

  heartBar.setPosition(
    camera.worldView.x - camera.scrollX,
    camera.worldView.y - camera.scrollY
  );

  gameState.layers.ui.add(heartBar);
}
</code></pre>
<p>Given my issues and struggles with Phaser, I would not only use it again but also recommend it. The good in Phaser far outweighs the bad. Phaser is an amazing framework.</p>
<h2 id="heading-promotion">Promotion</h2>
<p>😭 Ugg. I don't know how to promote this thing and get it in front of eyeballs.</p>
<p>I'm planning on setting up an <a target="_blank" href="https://itch.io/">itch.io</a> page, but I don't know if I should have two pages. I want to give the beta for free, but charge for the final game so how I go about that is unclear. One page for the beta and a separate page for the full game? I'm not sure. The itch documentation says not to make a page until the game is ready. But at the same time, I need to tell people about it and get some sort of waitlist long before the game is ready. So I don't know what to do here and if you do, comment below or hit me up <a target="_blank" href="https://twitter.com/joelnet">@joelnet</a> because I seriously need the help.</p>
<h2 id="heading-whats-next">What's Next</h2>
<p>I need to finish the health system and then create the rogue-like play. The basic mechanics of the game are complete so I think I'm over the difficult part.</p>
<p>I need to finish the health system, add treasure chests, coins, points, vendors, boss fights. You know, the fun stuff! 😁</p>
<p><a target="_blank" href="https://joel.net/newsletter">Subscribe to my Newsletter to keep updated with Dungeon Sweep: Knight!</a></p>
]]></content:encoded></item><item><title><![CDATA[Dungeon Sweep: Knight -- devlog 002]]></title><description><![CDATA[Welcome back to another devlog for Dungeon Sweep: Knight! I'm excited to share the progress I've made on the game so far.
Just in case you didn't read the first devlog, Dungeon Sweep: Knight is a game that will have the mechanics of Minesweeper and c...]]></description><link>https://joel.net/dungeon-sweep-knight-devlog-002</link><guid isPermaLink="true">https://joel.net/dungeon-sweep-knight-devlog-002</guid><category><![CDATA[Game Development]]></category><category><![CDATA[JavaScript]]></category><dc:creator><![CDATA[Joel Thoms]]></dc:creator><pubDate>Mon, 20 Feb 2023 15:20:39 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1676862535299/a1e377e1-1a8a-4c31-a4bb-8d279c60fe3e.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Welcome back to another devlog for Dungeon Sweep: Knight! I'm excited to share the progress I've made on the game so far.</p>
<p>Just in case you didn't read the first devlog, Dungeon Sweep: Knight is a game that will have the mechanics of Minesweeper and combine them with RPG elements.</p>
<h2 id="heading-fog-of-war">Fog of War</h2>
<p>The fog of war has been created and I think it mostly works! You can click on the fog to reveal either nothing or an enemy. The tile numbers will also display how many enemies are surrounding them. If you are unfamiliar with this type of game mechanic, I'd recommend playing some Minesweeper.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1676864494550/3795866a-363f-48f3-9d68-ed9990bade02.png" alt class="image--center mx-auto" /></p>
<p>Double-click a tile to attack!</p>
<h2 id="heading-animations">Animations</h2>
<p>I'm still learning Phaser, but animations are easy enough to create. I have both an idle and run animation for the Knight. As well as a sword animation when he attacks. The zombies are also animated, though you can't see them long since they die so quickly. I'll probably make some adjustments to that.</p>
<h2 id="heading-code-improvements">Code Improvements</h2>
<p>I added eslint + prettier to help maintain a sane codebase. It's still such a pain getting eslint and prettier installed and working together. Really wish that process was improved a little.</p>
<p>I had been scaling up each of 16x16 tiles to 32x32 manually. I just found a global camera zoom in Phaser3! That saved me a bunch of code. I'm still learning Phaser, so I'll probably make a lot of these kinds of mistakes along the way.</p>
<h2 id="heading-end-game-screen">End Game Screen</h2>
<p>Create a basic End Game Screen. Still need to figure out how to make buttons. A lot of graphical improvements are needed here. But it's a start.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1676865435082/63773d63-b498-430c-bd32-657f8a9fe9eb.png" alt class="image--center mx-auto" /></p>
<h2 id="heading-whats-next">What's Next</h2>
<p>Need to adjust some gameplay mechanics. Wire up the end game screen and let the player retry the level or move to the next level. I'll need a start screen as well as a screen that shows all the levels and the current level you are on.</p>
<p>I need to add some more fun things. Different enemies, health bars, coins, treasure chests, potions, maybe some boss fights? Oh, ya and audio. Gonna need some game sounds soon too.</p>
]]></content:encoded></item><item><title><![CDATA[Dungeon Sweep: Knight -- devlog 001]]></title><description><![CDATA[I recently decided I wanted to make an indie game. I have had this idea for a while and I think it's about time.
The idea is a variation of Minesweeper with RPG elements. I'm thinking about calling it Dungeon Sweep: Knight.
Art
I can't art, so I deci...]]></description><link>https://joel.net/dungeon-sweep-knight-devlog-001</link><guid isPermaLink="true">https://joel.net/dungeon-sweep-knight-devlog-001</guid><category><![CDATA[Game Development]]></category><category><![CDATA[JavaScript]]></category><dc:creator><![CDATA[Joel Thoms]]></dc:creator><pubDate>Thu, 16 Feb 2023 15:20:39 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1676514267625/72695554-31c8-4b6a-bdbc-7bcc6364b9e2.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I recently decided I wanted to make an indie game. I have had this idea for a while and I think it's about time.</p>
<p>The idea is a variation of Minesweeper with RPG elements. I'm thinking about calling it <strong>Dungeon Sweep: Knight</strong>.</p>
<h2 id="heading-art">Art</h2>
<p>I can't art, so I decided to go hunting for a ready-to-use tileset. A quick google search brought me to a <a target="_blank" href="https://0x72.itch.io/16x16-dungeon-tileset">16x16-dungeon-tileset</a> with a style that I can only describe as perfect.</p>
<p><img src="https://img.itch.zone/aW1hZ2UvMzA5MDI4LzE1MTg1NTgucG5n/original/cX%2BRV3.png" alt /></p>
<h2 id="heading-game-engine">Game Engine</h2>
<p>After barely doing any research, I landed on <a target="_blank" href="https://phaser.io/">PhaserJS</a>. My decision primarily was it still being in active development and not abandoned yet like all the others. So let's give it a try!</p>
<p>I know I can use <a target="_blank" href="https://cordova.apache.org/">Apache Cordova</a> to turn this into native Android and iOS. I don't know how well that process works, but that's a problem for Future Joel, not Today Joel.</p>
<h2 id="heading-generating-a-dungeon">Generating a Dungeon</h2>
<p>Now that I have the perfect tileset in hand, it's time to create a dungeon. This ended up being a lot harder than I thought it would be. My initial plan was to hand-create dungeons using plain text files. Run that through a function and bam, dungeon!</p>
<p>Example early dungeon map (that didn't work):</p>
<pre><code class="lang-plaintext">#########################
#     #    x        P   #
#     #        x        #
#     ############      #
#   x       x       x   #
#########################
</code></pre>
<p>I had a lot of difficulties with this. The first is I am using <code>#</code> to draw a top wall, left wall, right wall, and bottom wall. The graphics were different for each wall. This was going to be difficult.</p>
<p>So I decided to run these problems through ChatGPT. Let ChatGPT solve this so I can get on with the fun stuff. Unfortunately, ChatGPT was TERRIBLE at this. Even worse than me. So that wasn't gonna work.</p>
<p>After realizing how difficult a problem I gave myself, I stepped back and did a search on NPM for dungeon generators. There were MANY on there, but one stuck out.</p>
<p><a target="_blank" href="https://www.npmjs.com/package/@halftheopposite/dungeon"><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1676513225513/33606c0c-246b-440c-8b44-1b89751f488e.png" alt class="image--center mx-auto" /></a></p>
<p><a target="_blank" href="https://www.npmjs.com/package/@halftheopposite/dungeon">@halftheopposite/dungeon</a> was exactly what I was looking for. It was configurable and I was confident I could make it work.</p>
<p>I ended up abandoning my custom dungeon generator for this one.</p>
<p>The dungeon generator used a different tileset, so I tweaked it until my tileset would work with it. This ended up being a little more difficult than I had anticipated since each dungeon tile returned was a number. So I had to map each number to a graphic. This was tedious but once it was done I fired up the game and it generated this beautiful map!</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1676512467671/de8574b7-6c4e-46b3-9bd0-8cd92053bf3a.png" alt class="image--center mx-auto" /></p>
<p>I'm trying to keep the game as a 9:16 portrait ratio so mobile view works well.</p>
<h2 id="heading-whats-next">What's Next</h2>
<p>I have a solid base for my game, engine, graphics, random dungeons that look great. Next I'll be adding the gameplay mechanics. I'll have to drop down some enemies, lay a fog of war on top.</p>
<p>I think I'll have the Knight run around chopping at everything, I think that could also be fun.</p>
<p>Cheers 🍻</p>
]]></content:encoded></item><item><title><![CDATA[I asked an AI Chatbot to write an Episode of Rick and Morty and I was blown away]]></title><description><![CDATA[Since OpenGPT launched I have been playing with this AI Chatbot. My experience has been mixed. I had thrown a handful of complex programming tasks at it and it failed miserably.
Other more simple programming requests I made did seem to yield good res...]]></description><link>https://joel.net/i-asked-an-ai-chatbot-to-write-an-episode-of-rick-and-morty-and-i-was-blown-away</link><guid isPermaLink="true">https://joel.net/i-asked-an-ai-chatbot-to-write-an-episode-of-rick-and-morty-and-i-was-blown-away</guid><category><![CDATA[OpenApi]]></category><category><![CDATA[chatbot]]></category><dc:creator><![CDATA[Joel Thoms]]></dc:creator><pubDate>Thu, 08 Dec 2022 15:30:42 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1670455340572/e1TSciUuB.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Since OpenGPT launched I have been playing with this AI Chatbot. My experience has been mixed. I had thrown a handful of complex programming tasks at it and it failed miserably.</p>
<p>Other more simple programming requests I made did seem to yield good results.</p>
<p>Could I use it to help me code? Can I rely on it? Where would this bot fit into my life?</p>
<h2 id="heading-creative-writing">Creative Writing</h2>
<p>I decided to throw some creative writing tasks at it and had my mind blown 🤯.</p>
<p>I asked it to write an episode of Rick &amp; Morty. This is a fairly complex task as the bot has to be trained on Rick &amp; Morty content. The process of story writing isn't simple and the bot's ability to inject humor I thought was perfect.</p>
<p>There were a few surprises I wasn't expecting in the story and the result was too good not to share. So here it goes.</p>
<p>The bot even surprised me by giving the episode a name.</p>
<h2 id="heading-rick-and-morty-the-fart-cure">Rick and Morty: The Fart Cure</h2>
<p>Disclaimer: The bot seems to have a character limit, so some lines were cut mid-sentence and I had to prompt it to continue telling the story.</p>
<p><strong>The Conversation:</strong></p>
<p><em>Write a Rick and Morty episode where Morty gets fatally poisoned by a lethal fart and they have to hunt for the anti-fart cure, which is of course another fart. They do not know which fart is the anti-fart cure and therefore Morty has to receive multiple farts, which he does not enjoy. In the end, the anti-fart cure ends up being Rick's fart, which is administered just in time at the final hour before Morty's passing. Morty's final line is "You son of a bitch."</em></p>
<blockquote>
<p>Rick and Morty: The Fart Cure</p>
<p>As Morty walked into the garage, he caught a whiff of a strange odor. "What's that smell, Rick?" he asked, wrinkling his nose.</p>
<p>"Just a little experiment I'm working on," Rick replied, grinning maniacally.</p>
<p>Morty peered into the lab and saw a gas tank connected to a hose. "What is it?" he asked.</p>
<p>"It's a fart, Morty," Rick said, his grin widening. "But not just any fart. This is a lethal fart. One sniff and you'll be dead in minutes."</p>
<p>Morty's eyes widened in horror. "You can't be serious, Rick!" he exclaimed.</p>
<p>"Of course I'm serious, Morty," Rick said, his grin turning into a sly smile. "Now let's see how long it takes for the poison to take effect."</p>
<p>Morty's eyes rolled back in his head and he collapsed to the floor. Rick looked down at him with a satisfied grin. "Well, that was faster than I expected," he said.</p>
<p>But as he looked closer, he saw that Morty was still breathing, though shallowly. "Oh, I forgot to factor in your immunity to farts," Rick muttered, his grin turning into a frown.</p>
<p>He quickly grabbed a syringe and injected Morty with the antidote. "That should do the trick," he said, watching as Morty's breathing gradually returned to normal.</p>
<p>"What the hell, Rick?" Morty said, his voice weak and hoarse. "You could have killed me!"</p>
<p>"But I didn't, Morty," Rick said, his grin returning. "And now we have a new problem to solve. We need to find the anti-fart cure."</p>
<p>"The what?" Morty asked, his eyes still closed.</p>
<p>"The anti-fart cure, Morty," Rick said, his voice filled with</p>
</blockquote>
<p><em>Please continue the story</em></p>
<blockquote>
<p>Rick and Morty: The Fart Cure</p>
<p>Rick and Morty set out on a mission to find the anti-fart cure. They traveled to different planets and dimensions, searching for a fart that could counteract the lethal fart that Rick had created.</p>
<p>But as they searched, they encountered a problem. "How are we supposed to know which fart is the anti-fart cure?" Morty asked, his face contorted in disgust as they encountered yet another fart.</p>
<p>"We'll have to try them all, Morty," Rick said, his eyes gleaming with excitement. "And I'll need your help to do it."</p>
<p>Morty groaned as Rick handed him a gas mask and started administering farts to him. "This is torture, Rick," he said, his voice muffled by the mask.</p>
<p>But Rick didn't seem to care. He was too busy laughing and enjoying himself as he watched Morty suffer.</p>
<p>Finally, after what felt like an eternity, Rick found the anti-fart cure. "This is it, Morty," he said, grinning from ear to ear as he held up a fart in a jar.</p>
<p>Morty's eyes widened in shock. "That's your fart, Rick," he said, his voice filled with disbelief.</p>
<p>"That's right, Morty," Rick said, his grin turning into a smug smile. "Only my fart</p>
</blockquote>
<p><em>Please continue the story</em></p>
<blockquote>
<p>Rick and Morty: The Fart Cure</p>
<p>Rick administered the anti-fart cure to Morty just in time, saving him from a gruesome death. As Morty recovered, he couldn't help but feel angry and betrayed by Rick's actions.</p>
<p>"You son of a bitch, Rick," he said, his voice filled with disgust. "How could you do this to me?"</p>
<p>But Rick just laughed. "It was all in good fun, Morty," he said, still grinning from ear to ear. "Besides, think of all the knowledge we gained from this adventure. We discovered a new fart, we found the anti-fart cure, and we had a blast doing it. What's not to love?"</p>
<p>Morty shook his head, unable to believe Rick's words. But deep down, he knew that Rick was right. Despite the pain and suffering, he had to admit that it was an adventure he would never forget.</p>
<p>The end.</p>
</blockquote>
<p><em>You forgot the final line of the story should be Morty saying "You son of a bitch." Please rewrite the last bit and include the final line.</em></p>
<blockquote>
<p>Rick and Morty: The Fart Cure</p>
<p>As Morty recovered from the lethal fart, he couldn't help but feel angry and betrayed by Rick's actions. "You son of a bitch, Rick," he said, his voice filled with disgust. "How could you do this to me?"</p>
<p>But Rick just laughed. "It was all in good fun, Morty," he said, still grinning from ear to ear. "Besides, think of all the knowledge we gained from this adventure. We discovered a new fart, we found the anti-fart cure, and we had a blast doing it. What's not to love?"</p>
<p>Morty shook his head, unable to believe Rick's words. But deep down, he knew that Rick was right. Despite the pain and suffering, he had to admit that it was an adventure he would never forget.</p>
<p>"You son of a bitch," Morty said, his voice filled with grudging respect.</p>
<p>The end.</p>
</blockquote>
<h2 id="heading-afterthoughts">Afterthoughts</h2>
<p>I enjoyed this dialog:</p>
<blockquote>
<p>"How are we supposed to know which fart is the anti-fart cure?" Morty asked, his face contorted in disgust as they encountered yet another fart.</p>
<p>"We'll have to try them all, Morty," Rick said, his eyes gleaming with excitement.</p>
</blockquote>
<p>As well as this:</p>
<blockquote>
<p>But Rick didn't seem to care. He was too busy laughing and enjoying himself as he watched Morty suffer.</p>
</blockquote>
<p>Rick's enjoyment of Morty's suffering wasn't part of my instructions, so that was a pleasant surprise and one I think was on par for a Rick &amp; Morty episode.</p>
<p>I'm looking forward to seeing the future advancements of this tech, I can't wait to see what else it can create. Also, Netflix, I'm available. Call me.</p>
]]></content:encoded></item><item><title><![CDATA[Const vs Immutable (Plus Shallow vs Deep)]]></title><description><![CDATA[While const and immutability are related, they are two separate concepts. These concepts often get confused, so I thought it made sense to cover them together.
It is my goal to not simply give you definitions, which are often forgotten. Instead, give...]]></description><link>https://joel.net/const-vs-immutable-plus-shallow-vs-deep</link><guid isPermaLink="true">https://joel.net/const-vs-immutable-plus-shallow-vs-deep</guid><category><![CDATA[JavaScript]]></category><category><![CDATA[immutable]]></category><dc:creator><![CDATA[Joel Thoms]]></dc:creator><pubDate>Mon, 22 Feb 2021 17:24:15 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1613859135089/nDj8LYtVs.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>While const and immutability are related, they are two separate concepts. These concepts often get confused, so I thought it made sense to cover them together.</p>
<p>It is my goal to not simply give you definitions, which are often forgotten. Instead, give you the intuition to understand the differences and why they work the way they do.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://www.youtube.com/watch?v=sJCL3LFOf2E">https://www.youtube.com/watch?v=sJCL3LFOf2E</a></div>
<p>First, let's start by visualizing code the way I do.</p>
<h2 id="how-i-visualize-code">🧠 How I Visualize Code</h2>
<p>When I see code, I visualize the shape and connections of that code in my head. For example, this line of code...</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">var</span> url = <span class="hljs-string">"https://joel.net"</span>
</code></pre>
<p>I visualize my <strong>Application</strong> which contains a variable named <code>url</code>. <code>url</code> points to an object of type <code>String</code> that contains the value. The nature of a <code>String</code> is immutable, represented by the 🔒.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1612568846499/V-ZABU1vR.png" alt="image.png" /></p>
<p>Because the <code>url</code> does not have a lock, the value can be re-assigned later.</p>
<h2 id="const-prevents-re-assignment-of-variables">Const Prevents Re-assignment of Variables</h2>
<p>Declaring a variable with <code>const</code>, prevents the re-assignment of that value. The value can be only be assigned during declaration.</p>
<p>Let's look at some code.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> cat = {
  <span class="hljs-attr">name</span>: <span class="hljs-string">"Mojo"</span>,
  <span class="hljs-attr">age</span>: <span class="hljs-number">11</span>
}
</code></pre>
<p>This code can be visualized as:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1612825906025/SLluA7c4e.png" alt="image.png" /></p>
<p>The <code>Application</code> contains a variable named <code>cat</code>. <code>cat</code> is <code>const</code> and cannot be re-assigned later. Notice the 🔒 on <code>cat</code>.</p>
<p><code>cat</code> points to an <code>Object</code> that contain the properties <code>name</code> (<code>String</code>) and <code>age</code> (<code>Number</code>). These values can be changed because JavaScript objects are mutable by default.</p>
<p>The nature of a <code>String</code> and <code>Number</code> are immutable so they're also flagged with a 🔒.</p>
<h2 id="making-object-immutable">Making Object Immutable</h2>
<p>Objects in JavaScript are mutable, meaning they are open to modification. To make a JavaScript object immutable (read-only) use the <code>Object.freeze()</code> method. There is also <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/seal"><code>Object.seal()</code></a>, which we won't be covering here.  But please do research on your own.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> cat = {
  <span class="hljs-attr">name</span>: <span class="hljs-string">"Mojo"</span>,
  <span class="hljs-attr">toys</span>: [<span class="hljs-string">"Ball"</span>, <span class="hljs-string">"Feather"</span>]
}

<span class="hljs-built_in">Object</span>.freeze(cat)
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1612920101222/izJ18hMYj.png" alt="image.png" /></p>
<p><code>Object.freeze()</code> adds a 🔒 flag to the <code>Object</code> that <code>cat</code> points to. Notice here how
<code>toys</code> points to an <code>Array</code>, which is mutable.</p>
<p>This means that while <code>name</code> and <code>toys</code> cannot be re-assigned, <code>toys.push("New Toy")</code> is valid because the <code>Array</code> is not immutable.</p>
<p>This is because <code>Object.freeze()</code> is a shallow operation, applying the freeze to only the <code>Object</code>.</p>
<h2 id="deep-freezing-objects">Deep Freezing Objects</h2>
<p>Because <code>Object.freeze()</code> is a shallow operation, applying only to the <code>Object</code> and not the children, a recursive function must be created to also apply <code>Object.freeze()</code> to those children.</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// from: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">deepFreeze</span>(<span class="hljs-params">object</span>) </span>{
  <span class="hljs-comment">// Retrieve the property names defined on object</span>
  <span class="hljs-keyword">const</span> propNames = <span class="hljs-built_in">Object</span>.getOwnPropertyNames(object);

  <span class="hljs-comment">// Freeze properties before freezing self</span>
  <span class="hljs-keyword">for</span> (<span class="hljs-keyword">const</span> name <span class="hljs-keyword">of</span> propNames) {
    <span class="hljs-keyword">const</span> value = object[name];

    <span class="hljs-keyword">if</span> (value &amp;&amp; <span class="hljs-keyword">typeof</span> value === <span class="hljs-string">"object"</span>) {
      deepFreeze(value);
    }
  }

  <span class="hljs-keyword">return</span> <span class="hljs-built_in">Object</span>.freeze(object);
}
</code></pre>
<p>With <code>deepFreeze</code>, we can now deep freeze JavaScript objects.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> cat = {
  <span class="hljs-attr">name</span>: <span class="hljs-string">"Mojo"</span>,
  <span class="hljs-attr">toys</span>: [<span class="hljs-string">"Ball"</span>, <span class="hljs-string">"Feather"</span>]
}

deepFreeze(cat)
</code></pre>
<p>With <code>deepFreeze</code> applied to <code>cat</code>, the 🔒 flag is now applied to <code>Array</code> indicating it is now immutable.</p>
<p><code>cat</code> is now constant and deeply immutable 🔥</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1612921051136/T7ry_4JAe.png" alt="image.png" /></p>
<h2 id="summary">Summary</h2>
<ul>
<li><code>const</code> prevents the re-assignment of variables.</li>
<li><code>Object.freeze</code> will shallow freeze an object.</li>
<li>A custom function must be created to deep freeze objects.</li>
</ul>
<p>Subscribe to my <a target="_blank" href="https://joel.net/newsletter">Newsletter</a> to continue learning about Cloudflare Workers!</p>
<p>Cheers 🍻</p>
<ul>
<li>Join my 📰 <a target="_blank" href="https://joel.net/newsletter">Newsletter</a></li>
<li>Subscribe to my 📺 YouTube, <a target="_blank" href="https://www.youtube.com/c/JoelCodes?sub_confirmation=1">JoelCodes</a></li>
<li>Say hi to me on Twitter <a target="_blank" href="https://twitter.com/joelnet">@joelnet</a></li>
</ul>
]]></content:encoded></item><item><title><![CDATA[⛅ Smart Routing with Cloudflare Workers and Webpack (bonus content)]]></title><description><![CDATA[Automatic Smart Routing for Cloudflare Workers simplifies routing and removes boilerplate. Currently each route is setup manually. This has been tedious and needs to change.
https://www.youtube.com/watch?v=q17r-ZLFMJA
Cloudflare Workers Course Outlin...]]></description><link>https://joel.net/smart-routing-with-cloudflare-workers-and-webpack-bonus-content</link><guid isPermaLink="true">https://joel.net/smart-routing-with-cloudflare-workers-and-webpack-bonus-content</guid><category><![CDATA[cloudflare]]></category><category><![CDATA[serverless]]></category><dc:creator><![CDATA[Joel Thoms]]></dc:creator><pubDate>Tue, 26 Jan 2021 16:53:37 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1611679959832/Z6HEgt3fI.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Automatic Smart Routing for Cloudflare Workers simplifies routing and removes boilerplate. Currently each route is setup manually. This has been tedious and needs to change.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://www.youtube.com/watch?v=q17r-ZLFMJA">https://www.youtube.com/watch?v=q17r-ZLFMJA</a></div>
<h2 id="cloudflare-workers-course-outline">Cloudflare Workers Course Outline</h2>
<ol>
<li><a target="_blank" href="https://joel.net/getting-started-with-serverless-cloudflare-workers-lesson-1">Getting Started with Serverless Cloudflare Workers</a></li>
<li><a target="_blank" href="https://joel.net/cloudflare-workers-as-a-web-server-with-webpack-lesson-2">Cloudflare Workers as a Web Server (with Webpack)</a></li>
<li><a target="_blank" href="https://joel.net/making-api-calls-from-a-cloudflare-worker-lesson-3">Making API Calls From a Cloudflare Worker</a></li>
<li><a target="_blank" href="https://joel.net/key-value-storage-with-cloudflare-workers-kv-lesson-4">Key-Value Storage With Cloudflare Workers KV</a></li>
<li><strong>[Bonus] Smart Routing with Cloudflare Workers</strong></li>
</ol>
<h2 id="before-smart-routing">Before Smart Routing</h2>
<p>This is what my routes look like before smart routing. Multiple imports as well as routes to manage inside of <code>src/index.js</code>.</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// ❌ Every page is imported at the top</span>
<span class="hljs-keyword">import</span> allPeople <span class="hljs-keyword">from</span> <span class="hljs-string">'./pages/allPeople'</span>
<span class="hljs-keyword">import</span> index <span class="hljs-keyword">from</span> <span class="hljs-string">'./pages/index'</span>
<span class="hljs-keyword">import</span> person <span class="hljs-keyword">from</span> <span class="hljs-string">'./pages/person'</span>
<span class="hljs-keyword">import</span> files <span class="hljs-keyword">from</span> <span class="hljs-string">'./pages/files'</span>
<span class="hljs-keyword">import</span> filesPost <span class="hljs-keyword">from</span> <span class="hljs-string">'./pages/files.post'</span>
<span class="hljs-keyword">import</span> filesList <span class="hljs-keyword">from</span> <span class="hljs-string">'./pages/files.list'</span>

<span class="hljs-comment">// ❌ Every route is manually added</span>
router.get(<span class="hljs-string">'/'</span>, <span class="hljs-function">() =&gt;</span> index(request))
router.get(<span class="hljs-string">'/people/?'</span>, <span class="hljs-function">() =&gt;</span> allPeople(request))
router.get(<span class="hljs-string">'/people/.+'</span>, <span class="hljs-function">() =&gt;</span> person(request))
router.get(<span class="hljs-string">'/files/.+'</span>, <span class="hljs-function">() =&gt;</span> files(request))
router.get(<span class="hljs-string">'/files/?'</span>, <span class="hljs-function">() =&gt;</span> filesList(request))
router.post(<span class="hljs-string">'/files/?'</span>, <span class="hljs-function">() =&gt;</span> filesPost(request))
</code></pre>
<p>I don't like this, there's far too much boilerplate. Fortunately with some features of webpack, this can be improved.</p>
<p>Also, In the previous lessons, I used a very crude method to pull parameters off the routes. A route like <code>/people/123</code> would be parsed and <code>123</code> would become <code>id</code>.</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// ❌ Gross</span>
<span class="hljs-keyword">const</span> person = <span class="hljs-keyword">async</span> request =&gt; {
  <span class="hljs-keyword">const</span> url = <span class="hljs-keyword">new</span> URL(request.url)
  <span class="hljs-keyword">const</span> id = url.pathname.substring(<span class="hljs-number">8</span>)
  <span class="hljs-keyword">const</span> person = <span class="hljs-keyword">await</span> fetchPerson(id)
  <span class="hljs-comment">/* code */</span>
}
</code></pre>
<h2 id="what-i-want-instead">What I Want Instead</h2>
<p>It would be nice to add files to the project and not have to worry about routing. I want the pages and routes to just be picked up automatically and modify less files.</p>
<p>Something like this (below), where the route is included in the page and not in <code>index.js</code>.</p>
<pre><code class="lang-javascript"><span class="hljs-comment">/**
  * ./src/pages/status.js
  */</span> 
<span class="hljs-keyword">import</span> { textResponse } <span class="hljs-keyword">from</span> <span class="hljs-string">'../lib/responses'</span>

<span class="hljs-comment">// ✅ routing is automatic</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> route = <span class="hljs-string">'/status/?'</span>

<span class="hljs-keyword">const</span> status = <span class="hljs-function">() =&gt;</span> textResponse(<span class="hljs-string">'OK'</span>)

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> status
</code></pre>
<p>It would also be nice if the parameterization of routes be be more idiomatic. Something like this:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// ✅ Route Parameters</span>
<span class="hljs-keyword">const</span> person = <span class="hljs-keyword">async</span> ({ params }) =&gt; {
  <span class="hljs-keyword">const</span> person = <span class="hljs-keyword">await</span> fetchPerson(params.id)
  <span class="hljs-comment">/* code */</span>
}
</code></pre>
<h2 id="webpacks-requirecache">Webpack's require.cache</h2>
<p>Webpack keeps a cache of all files added to the project inside of <code>require.cache</code>. This can be exploited to build the routes automatically.</p>
<p>Because webpack's <code>require.cache</code> contains all files and not just the pages, the non-pages need to be filtered out. So the test that seems to work for this is a page must be a <code>Module</code> and also export a <code>route</code>.</p>
<p>This is what those tests look like:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> isModule = <span class="hljs-function"><span class="hljs-params">module</span> =&gt;</span> {
  <span class="hljs-keyword">if</span> (!<span class="hljs-built_in">module</span> || !<span class="hljs-built_in">module</span>.exports) <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>
  <span class="hljs-keyword">return</span> <span class="hljs-built_in">Object</span>.prototype.toString.call(<span class="hljs-built_in">module</span>.exports) === <span class="hljs-string">'[object Module]'</span>
}

<span class="hljs-keyword">const</span> hasRoute = <span class="hljs-function"><span class="hljs-params">module</span> =&gt;</span> {
  <span class="hljs-keyword">if</span> (!<span class="hljs-built_in">module</span> || !<span class="hljs-built_in">module</span>.exports) <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>
  <span class="hljs-keyword">return</span> !!<span class="hljs-built_in">module</span>.exports.route
}
</code></pre>
<p>Next, export <code>getRoutes</code>. This function filters for the route pages and returns an Array of routes that include a <code>route</code>, <code>method</code> (default to <code>get</code>), and the <code>module</code>.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> getRoutes = <span class="hljs-function">() =&gt;</span>
  <span class="hljs-built_in">Object</span>.values(<span class="hljs-built_in">require</span>.cache)
    .filter(<span class="hljs-function"><span class="hljs-params">module</span> =&gt;</span> isModule(<span class="hljs-built_in">module</span>) &amp;&amp; isRoute(<span class="hljs-built_in">module</span>))
    .map(<span class="hljs-function"><span class="hljs-params">module</span> =&gt;</span> ({
      <span class="hljs-attr">route</span>: <span class="hljs-built_in">module</span>.exports.route,
      <span class="hljs-attr">method</span>: <span class="hljs-built_in">module</span>.exports.method || <span class="hljs-string">'get'</span>,
      <span class="hljs-attr">module</span>: <span class="hljs-built_in">module</span>.exports.default,
    }))
</code></pre>
<h2 id="auto-include-files-with-webpack">Auto-include Files With Webpack</h2>
<p>Once the imports for the pages are removed from <code>index.js</code>, Webpack won't include them. So they have to be forced into the bundle.</p>
<p>This can be done by modifying the <code>webpack.config.js</code> and adding the files there.</p>
<p>The <a target="_blank" href="https://www.npmjs.com/package/glob">glob</a> function returns a list of all the JavaScript files in the <code>./src/pages/</code> directory.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> glob = <span class="hljs-built_in">require</span>(<span class="hljs-string">'glob'</span>)

<span class="hljs-keyword">const</span> files = glob.sync(<span class="hljs-string">'./src/pages/**/*.js'</span>)
</code></pre>
<p>Include the files in the webpack's entry.</p>
<pre><code class="lang-javascript"><span class="hljs-built_in">module</span>.exports = {
  <span class="hljs-attr">entry</span>: files.concat([<span class="hljs-string">'./src/index.js'</span>]),
}
</code></pre>
<p>If there's a better way, let me know in the comments.</p>
<h2 id="add-the-auto-routing-to-srcindexjs">Add The Auto-Routing to <code>src/index.js</code></h2>
<p>Add the auto-routing before any catch-all routes. In this case, the <code>notFound</code> route should be included last.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> Router <span class="hljs-keyword">from</span> <span class="hljs-string">'./lib/router'</span>
<span class="hljs-keyword">import</span> notFound <span class="hljs-keyword">from</span> <span class="hljs-string">'./pages/404'</span>
<span class="hljs-keyword">import</span> { getRoutes } <span class="hljs-keyword">from</span> <span class="hljs-string">'./lib/auto-routes'</span>

<span class="hljs-keyword">const</span> routes = getRoutes()
<span class="hljs-keyword">const</span> router = <span class="hljs-keyword">new</span> Router()

routes.forEach(<span class="hljs-function">(<span class="hljs-params">{ route, method, <span class="hljs-built_in">module</span> }</span>) =&gt;</span> router[method](route, <span class="hljs-built_in">module</span>))
router.all(notFound)
</code></pre>
<h2 id="creating-route-parameters">Creating Route Parameters</h2>
<p>In the previous lessons, I used a very crude method to pull parameters off the routes. A route like <code>/people/123</code> would be parsed and <code>123</code> would become <code>id</code>.</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// ❌ Gross</span>
<span class="hljs-keyword">const</span> person = <span class="hljs-keyword">async</span> request =&gt; {
  <span class="hljs-keyword">const</span> url = <span class="hljs-keyword">new</span> URL(request.url)
  <span class="hljs-keyword">const</span> id = url.pathname.substring(<span class="hljs-number">8</span>)
  <span class="hljs-keyword">const</span> person = <span class="hljs-keyword">await</span> fetchPerson(id)
  <span class="hljs-comment">/* code */</span>
}
</code></pre>
<p>It would be nice if I could do something like this instead:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// ✅ Route Parameters</span>
<span class="hljs-keyword">const</span> person = <span class="hljs-keyword">async</span> ({ params }) =&gt; {
  <span class="hljs-keyword">const</span> person = <span class="hljs-keyword">await</span> fetchPerson(params.id)
  <span class="hljs-comment">/* code */</span>
}
</code></pre>
<p>This feature can be added by modifying the <a target="_blank" href="https://github.com/joelnet/cloudflare-worker-website/blob/1fcd52001bd8e3ca9a4810b138979af6a50f5a2a/src/lib/router.js#L22"><code>src/lib/router.js</code></a> file to assign a <code>params</code> object onto the <code>req</code>. Find <code>Path</code> and add the one-liner like below.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> Path = <span class="hljs-function"><span class="hljs-params">regExp</span> =&gt;</span> <span class="hljs-function"><span class="hljs-params">req</span> =&gt;</span> {
  <span class="hljs-keyword">const</span> url = <span class="hljs-keyword">new</span> URL(req.url)
  <span class="hljs-keyword">const</span> path = url.pathname
  <span class="hljs-keyword">const</span> match = path.match(regExp) || []
  <span class="hljs-comment">// 👇 Insert this line to create the `params` on `req`.</span>
  req.params = match &amp;&amp; match.groups ? match.groups : {}
  <span class="hljs-keyword">return</span> match[<span class="hljs-number">0</span>] === path
}
</code></pre>
<p>Since routes are already RegEx, we can use the Named Group feature for this.</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// ❌ No Named Group</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> route = <span class="hljs-string">'/people/.+'</span>

<span class="hljs-comment">// ✅ Group Named "id"</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> route = <span class="hljs-string">'/people/(?&lt;id&gt;.+)'</span>
</code></pre>
<p>Now code like this is possible!</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// 🔥 Route Parameters</span>
<span class="hljs-keyword">const</span> person = <span class="hljs-keyword">async</span> ({ params }) =&gt; {
  <span class="hljs-keyword">const</span> person = <span class="hljs-keyword">await</span> fetchPerson(params.id)
  <span class="hljs-comment">/* code */</span>
}
</code></pre>
<h2 id="summary">Summary</h2>
<p>These improvements will simplify the the way new routes are created. It also extracts parameters from the routes.</p>
<p>Browse <a target="_blank" href="https://github.com/joelnet/cloudflare-worker-website/tree/1fcd52001bd8e3ca9a4810b138979af6a50f5a2a">the repository at this point in history</a>.</p>
<p>Subscribe to my <a target="_blank" href="https://joel.net/newsletter">Newsletter</a> to continue learning about Cloudflare Workers!</p>
<p>Cheers 🍻</p>
<ul>
<li>Join my 📰 <a target="_blank" href="https://joel.net/newsletter">Newsletter</a></li>
<li>Subscribe to my 📺 YouTube, <a target="_blank" href="https://www.youtube.com/c/JoelCodes?sub_confirmation=1">JoelCodes</a></li>
<li>Say hi to me on Twitter <a target="_blank" href="https://twitter.com/joelnet">@joelnet</a></li>
</ul>
]]></content:encoded></item><item><title><![CDATA[⛅ Key-Value Storage With Cloudflare Workers KV (lesson 4)]]></title><description><![CDATA[Serverless key-value storage or KV provides an easy way to store / retrieve data for your Cloudflare Workers. Best of all, Cloudflare has recently introduced a FREE tier for KV!
https://www.youtube.com/watch?v=5dkzFrJ044o
Cloudflare Workers Course Ou...]]></description><link>https://joel.net/key-value-storage-with-cloudflare-workers-kv-lesson-4</link><guid isPermaLink="true">https://joel.net/key-value-storage-with-cloudflare-workers-kv-lesson-4</guid><category><![CDATA[cloudflare]]></category><category><![CDATA[serverless]]></category><dc:creator><![CDATA[Joel Thoms]]></dc:creator><pubDate>Fri, 01 Jan 2021 15:01:16 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1608329914322/yt0VrbUl2.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Serverless key-value storage or KV provides an easy way to store / retrieve data for your Cloudflare Workers. Best of all, Cloudflare has recently introduced a FREE tier for KV!</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://www.youtube.com/watch?v=5dkzFrJ044o">https://www.youtube.com/watch?v=5dkzFrJ044o</a></div>
<h2 id="cloudflare-workers-course-outline">Cloudflare Workers Course Outline</h2>
<ol>
<li><a target="_blank" href="https://joel.net/getting-started-with-serverless-cloudflare-workers-lesson-1">Getting Started with Serverless Cloudflare Workers</a></li>
<li><a target="_blank" href="https://joel.net/cloudflare-workers-as-a-web-server-with-webpack-lesson-2">Cloudflare Workers as a Web Server (with Webpack)</a></li>
<li><a target="_blank" href="https://joel.net/making-api-calls-from-a-cloudflare-worker-lesson-3">Making API Calls From a Cloudflare Worker</a></li>
<li><strong>Key-Value Storage With Cloudflare Workers KV</strong></li>
<li><a target="_blank" href="https://joel.net/smart-routing-with-cloudflare-workers-and-webpack-bonus-content">[Bonus] Smart Routing with Cloudflare Workers</a></li>
</ol>
<h2 id="about-workers-kv">About Workers KV</h2>
<p>Workers KV reads respond incredibly fast. According to Cloudflare, as quickly as a cached static file would.</p>
<p>Workers KV is an eventually-consistent database. Changes may take up to 60 seconds to propagate (though in my experience it's much less). This means KV cannot be used in applications that require atomic transactions.</p>
<p>Workers KV is a key-value storage. Typical of KV storage solutions, there is no search. But there are list operations.</p>
<p><a target="_blank" href="https://developers.cloudflare.com/workers/learning/how-kv-works">More on How KV Works</a></p>
<h2 id="what-well-be-making">What We'll Be Making</h2>
<p>For this lesson, we will be using Cloudflare KV to <code>put</code> a key, <code>get</code> a key, and <code>list</code> keys.</p>
<h2 id="starting-where-we-left-off">Starting Where We Left Off</h2>
<p>In case you haven't been following along with the previous lessons (you don't have to), you can start here:</p>
<pre><code class="lang-bash"><span class="hljs-comment"># continue where we left off</span>
$ git <span class="hljs-built_in">clone</span> https://github.com/joelnet/cloudflare-worker-website.git
$ <span class="hljs-built_in">cd</span> cloudflare-worker-website
$ git checkout f6a224b37010d76d694320d4748ac07cb75f7369
$ npm ci
</code></pre>
<h2 id="create-kv-namespace">Create KV Namespace</h2>
<p>Namespaces are a way to segregate KV stores. This is useful if you run many applications under Cloudflare or separation within an application. For example, you might want to create LOGINS and PROFILES Namespaces. You could also do this with a key prefix like <code>logins:&lt;login&gt;</code> or <code>profiles:&lt;profile&gt;</code>.</p>
<p>A Namespace can be created within the Cloudflare website or with the <code>wrangler</code> CLI.</p>
<p>I'm going to create a new Namespace called <code>FILES</code>.</p>
<pre><code class="lang-bash">$ wrangler kv:namespace create <span class="hljs-string">"FILES"</span>
</code></pre>
<p>The output should look something like this. Be sure to paste that bottom piece into your <code>wrangler.toml</code>.</p>
<pre><code class="lang-text">🌀  Creating namespace with title "my-website-FILES"
✨  Success!
Add the following to your configuration file:
kv_namespaces = [ 
         { binding = "FILES", id = "529b4c9be8b344f59adfe89cc9765879" }
]
</code></pre>
<h2 id="eslint">ESLint</h2>
<p>Add an ESLint Global by opening <code>.eslintrc.yml</code>. This will prevent ESLint from complaining about the <code>FILES</code> global.</p>
<pre><code class="lang-yaml"><span class="hljs-attr">globals:</span>
  <span class="hljs-attr">FILES:</span> <span class="hljs-string">readonly</span>
</code></pre>
<h2 id="the-setup">The Setup</h2>
<p>The plan is to create 3 files. One each to read, write, and list the keys. Then use KV to store markdown files and then display them as HTML.</p>
<p>First, setup the routing to the new files.</p>
<p>Open <a target="_blank" href="https://github.com/joelnet/cloudflare-worker-website/blob/f6a224b37010d76d694320d4748ac07cb75f7369/src/index.js">src/index.js</a> and add the imports.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> filesPost <span class="hljs-keyword">from</span> <span class="hljs-string">'./pages/files.post'</span>
<span class="hljs-keyword">import</span> files <span class="hljs-keyword">from</span> <span class="hljs-string">'./pages/files'</span>
<span class="hljs-keyword">import</span> filesList <span class="hljs-keyword">from</span> <span class="hljs-string">'./pages/files.list'</span>
</code></pre>
<p>Then add a new routes. <code>/?</code> is RegEx for optional <code>/</code>. <code>.+</code> is RegEx for one or more character.</p>
<pre><code>router.post(<span class="hljs-string">'/files/?'</span>, <span class="hljs-function"><span class="hljs-params">()</span> =&gt;</span> filesPost(request))
router.get(<span class="hljs-string">'/files/.+'</span>, <span class="hljs-function"><span class="hljs-params">()</span> =&gt;</span> files(request))
router.get(<span class="hljs-string">'/files/?'</span>, <span class="hljs-function"><span class="hljs-params">()</span> =&gt;</span> filesList(request))
</code></pre><h2 id="adding-a-key">Adding a Key</h2>
<p>Create <code>src/pages/files.post.js</code>. To create a key use the format: <code>await NAMESPACE.put(key, value)</code>.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> { htmlResponse } <span class="hljs-keyword">from</span> <span class="hljs-string">'../lib/responses'</span>

<span class="hljs-keyword">const</span> filePost = <span class="hljs-keyword">async</span> request =&gt; {
    <span class="hljs-keyword">const</span> { filename, contents } = <span class="hljs-keyword">await</span> request.json()
    <span class="hljs-keyword">await</span> FILES.put(filename, contents)
    <span class="hljs-keyword">return</span> htmlResponse(<span class="hljs-string">'SUCCESS'</span>)
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> filePost
</code></pre>
<p>Keys can auto-expire by setting the <code>expiration</code>. <a target="_blank" href="https://developers.cloudflare.com/workers/runtime-apis/kv#writing-key-value-pairs">Expiring Keys</a></p>
<h2 id="reading-a-key">Reading a Key</h2>
<p>Create <code>src/pages/files.js</code>. Reading a key uses the format: <code>await NAMESPACE.get(key)</code>.</p>
<p>Note: There are more sophisticated ways to pull the filename off the URL, I'm using <code>substring</code> for the simplicity of this demo.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> marked <span class="hljs-keyword">from</span> <span class="hljs-string">'marked'</span>
<span class="hljs-keyword">import</span> { htmlResponse } <span class="hljs-keyword">from</span> <span class="hljs-string">'../lib/responses'</span>

<span class="hljs-keyword">import</span> marked <span class="hljs-keyword">from</span> <span class="hljs-string">'marked'</span>
<span class="hljs-keyword">import</span> { htmlResponse } <span class="hljs-keyword">from</span> <span class="hljs-string">'../lib/responses'</span>

<span class="hljs-keyword">const</span> files = <span class="hljs-keyword">async</span> request =&gt; {
    <span class="hljs-keyword">const</span> url = <span class="hljs-keyword">new</span> URL(request.url)
    <span class="hljs-keyword">const</span> id = url.pathname.substring(<span class="hljs-number">7</span>)
    <span class="hljs-keyword">const</span> file = <span class="hljs-keyword">await</span> FILES.get(id)
    <span class="hljs-keyword">const</span> html = marked(file)
    <span class="hljs-keyword">return</span> htmlResponse(html)
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> files
</code></pre>
<h2 id="listing-keys">Listing Keys</h2>
<p>Create <code>src/pages/files.list.js</code>. The list uses the format: <code>await NAMESPACE.list()</code>.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> { htmlResponse } <span class="hljs-keyword">from</span> <span class="hljs-string">'../lib/responses'</span>

<span class="hljs-keyword">const</span> fileToLi = <span class="hljs-function"><span class="hljs-params">file</span> =&gt;</span> <span class="hljs-string">`
&lt;li&gt;
  &lt;a href="/files/<span class="hljs-subst">${file.name}</span>"&gt;<span class="hljs-subst">${file.name}</span>&lt;/a&gt;
&lt;/li&gt;
`</span>

<span class="hljs-keyword">const</span> fileList = <span class="hljs-keyword">async</span> () =&gt; {
    <span class="hljs-keyword">const</span> files = <span class="hljs-keyword">await</span> FILES.list()
    <span class="hljs-keyword">const</span> lis = files.keys.map(fileToLi).join(<span class="hljs-string">''</span>)
    <span class="hljs-keyword">return</span> htmlResponse(<span class="hljs-string">`&lt;ul&gt;<span class="hljs-subst">${lis}</span>&lt;/ul&gt;`</span>)
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> fileList
</code></pre>
<p>Lists also support paging and listing by prefix. <a target="_blank" href="https://developers.cloudflare.com/workers/runtime-apis/kv">Check the docs for more on lists</a>.</p>
<h2 id="extra-credit">Extra Credit</h2>
<ul>
<li><p>Using the same pattern, implement a delete with the method <code>await NAMESPACE.delete(key)</code>.</p>
</li>
<li><p>Implement pagination on the list page to support &gt; 1000 entries.</p>
</li>
<li><p>Experiment with Expiring Keys.</p>
</li>
</ul>
<p><a target="_blank" href="https://developers.cloudflare.com/workers/runtime-apis/kv">Refer to the API</a> for reference.</p>
<h2 id="summary">Summary</h2>
<p>We used Workers KV to create a file upload to upload <code>markdown</code> files as well as display those <code>markdown</code> files as <code>HTML</code>. As a bonus there is a page that lists the <code>FILES</code> uploaded.</p>
<p>Browse <a target="_blank" href="https://github.com/joelnet/cloudflare-worker-website/tree/b3dd8bb888f709fa30bf37bf63ac18b9d7ae30a4">the repository at this point in history</a>.</p>
<p>Subscribe to my <a target="_blank" href="https://joel.net/newsletter">Newsletter</a> to continue learning about Cloudflare Workers!</p>
<p>Cheers 🍻</p>
<ul>
<li>Join my 📰 <a target="_blank" href="https://joel.net/newsletter">Newsletter</a></li>
<li>Subscribe to my 📺 YouTube, <a target="_blank" href="https://www.youtube.com/c/JoelCodes?sub_confirmation=1">JoelCodes</a></li>
<li>Say hi to me on Twitter <a target="_blank" href="https://twitter.com/joelnet">@joelnet</a></li>
</ul>
]]></content:encoded></item><item><title><![CDATA[⛅ Making API Calls From a Cloudflare Worker (lesson 3)]]></title><description><![CDATA[Being able to retrieve data from an API or Database is a common task for a Cloudflare Worker. Without some type of API call to a database or service, your site would be the same as a static site. It's the API call that will make your site dynamic.
Cl...]]></description><link>https://joel.net/making-api-calls-from-a-cloudflare-worker-lesson-3</link><guid isPermaLink="true">https://joel.net/making-api-calls-from-a-cloudflare-worker-lesson-3</guid><category><![CDATA[JavaScript]]></category><category><![CDATA[cloudflare]]></category><dc:creator><![CDATA[Joel Thoms]]></dc:creator><pubDate>Tue, 08 Dec 2020 19:41:09 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1607414965570/umftp5_tt.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Being able to retrieve data from an API or Database is a common task for a Cloudflare Worker. Without some type of API call to a database or service, your site would be the same as a static site. It's the API call that will make your site dynamic.</p>
<p>Cloudflare Workers run on the Edge (server side), so the resulting output will be server rendered. making your page SEO (Search Engine Optimization) friendly.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://www.youtube.com/watch?v=QcXmGQqtUn0">https://www.youtube.com/watch?v=QcXmGQqtUn0</a></div>
<h2 id="cloudflare-workers-course-outline">Cloudflare Workers Course Outline</h2>
<ol>
<li><a target="_blank" href="https://joel.net/getting-started-with-serverless-cloudflare-workers-lesson-1">Getting Started with Serverless Cloudflare Workers</a></li>
<li><a target="_blank" href="https://joel.net/cloudflare-workers-as-a-web-server-with-webpack-lesson-2">Cloudflare Workers as a Web Server (with Webpack)</a></li>
<li><strong>Making API Calls From a Cloudflare Worker</strong></li>
<li><a target="_blank" href="https://joel.net/key-value-storage-with-cloudflare-workers-kv-lesson-4">Key-Value Storage With Cloudflare Workers KV</a></li>
<li><a target="_blank" href="https://joel.net/smart-routing-with-cloudflare-workers-and-webpack-bonus-content">[Bonus] Smart Routing with Cloudflare Workers</a></li>
</ol>
<h2 id="starting-where-we-left-off">Starting Where We Left Off</h2>
<p>In case you haven't been following along with the previous lessons (you don't have to), you can start here:</p>
<pre><code class="lang-bash"><span class="hljs-comment"># continue where we left off</span>
$ git <span class="hljs-built_in">clone</span> https://github.com/joelnet/cloudflare-worker-website.git
$ <span class="hljs-built_in">cd</span> cloudflare-worker-website
$ git checkout ae0c7dcf533099832bc313099425d270e326148e
$ npm ci
</code></pre>
<h2 id="recommended-use-native-fetch">Recommended: Use Native Fetch</h2>
<p>I would recommend against using a library like <code>axios</code> and instead use the native <code>fetch</code>. The reason is your Worker has 1 MB limit. Adding any new libraries should be carefully considered due to this hard limit.</p>
<p>Cloudflare Worker code runs server side. Thus you don't have to worry about browser incompatibility with libraries.</p>
<p>Test other clients you may be considering. Since Workers do not run Node.js, some npm modules may not work. For example, any library that requires <code>node-gyp</code> will not run in a Worker.</p>
<p>If a library will run in the browser, it should also run in a Cloudflare worker.</p>
<h2 id="create-srcapijs">Create <code>src/api.js</code></h2>
<p>I like to create an <code>src/api.js</code> file to hold all my API calls. This way if an underlying change needs to happen, it can happen in one file. This file will also simplifies the source for files using the API.</p>
<p>For this demo, I will be making two API calls, <code>fetchAllPeople</code> and <code>fetchPerson</code>.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> getJson = <span class="hljs-keyword">async</span> url =&gt; {
  <span class="hljs-keyword">const</span> response = <span class="hljs-keyword">await</span> fetch(url)
  <span class="hljs-keyword">if</span> (!response.ok) {
    <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">'Unexpected HTTP Response'</span>)
  }
  <span class="hljs-keyword">return</span> <span class="hljs-keyword">await</span> response.json()
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> fetchAllPeople = <span class="hljs-function">() =&gt;</span>
  getJson(<span class="hljs-string">'https://swapi.dev/api/people'</span>)

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> fetchPerson = <span class="hljs-function"><span class="hljs-params">id</span> =&gt;</span>
  getJson(<span class="hljs-string">`https://swapi.dev/api/people/<span class="hljs-subst">${id}</span>`</span>)
</code></pre>
<p>If you are curious about why I create the <code>getJson</code> abstraction this way and want to know more, I have created a video on this topic.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://www.youtube.com/watch?v=aIboXjxo-w8">https://www.youtube.com/watch?v=aIboXjxo-w8</a></div>
<h2 id="create-srcpagesallpeoplejs">Create <code>src/pages/allPeople.js</code></h2>
<p>This page will display a list of all available people. The SWAPI API features pagination, which we'll ignore to keep this demo short. Feel free to implement the pagination on your own 👍</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> { fetchAllPeople } <span class="hljs-keyword">from</span> <span class="hljs-string">'../api'</span>
<span class="hljs-keyword">import</span> { htmlResponse } <span class="hljs-keyword">from</span> <span class="hljs-string">'../lib/responses'</span>

<span class="hljs-keyword">const</span> personToLi = <span class="hljs-function">(<span class="hljs-params">person, index</span>) =&gt;</span>
  <span class="hljs-string">`&lt;li&gt;&lt;a href="/people/<span class="hljs-subst">${index + <span class="hljs-number">1</span>}</span>"&gt;<span class="hljs-subst">${person.name}</span>&lt;/a&gt;&lt;/li&gt;`</span>

<span class="hljs-keyword">const</span> allPeople = <span class="hljs-keyword">async</span> () =&gt; {
  <span class="hljs-keyword">const</span> { results } = <span class="hljs-keyword">await</span> fetchAllPeople()
  <span class="hljs-keyword">const</span> persons = results.map(personToLi)
  <span class="hljs-keyword">const</span> html = <span class="hljs-string">`&lt;ul&gt;<span class="hljs-subst">${persons.join(<span class="hljs-string">''</span>)}</span>&lt;/ul&gt;`</span>
  <span class="hljs-keyword">return</span> htmlResponse(html)
}
<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> allPeople
</code></pre>
<h2 id="create-srcpagespersonjs">Create <code>src/pages/person.js</code></h2>
<p>This page will display the individual person. There are more sophisticated ways of pulling the <code>id</code> from the URL, but I'm just going to use <code>substring</code> for simplicity of this demo.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> { fetchPerson } <span class="hljs-keyword">from</span> <span class="hljs-string">'../api'</span>
<span class="hljs-keyword">import</span> { objectToTable } <span class="hljs-keyword">from</span> <span class="hljs-string">'../lib/objectToTable'</span>
<span class="hljs-keyword">import</span> { htmlResponse } <span class="hljs-keyword">from</span> <span class="hljs-string">'../lib/responses'</span>

<span class="hljs-keyword">const</span> person = <span class="hljs-keyword">async</span> request =&gt; {
  <span class="hljs-keyword">const</span> url = <span class="hljs-keyword">new</span> URL(request.url)
  <span class="hljs-keyword">const</span> id = url.pathname.substring(<span class="hljs-number">8</span>)

  <span class="hljs-keyword">const</span> response = <span class="hljs-keyword">await</span> fetchPerson(id)
  <span class="hljs-keyword">const</span> table = objectToTable(response)
  <span class="hljs-keyword">const</span> html = <span class="hljs-string">`<span class="hljs-subst">${table}</span>
    &lt;a href="/people"&gt;&amp;lt;&amp;lt; back&lt;/a&gt;
  `</span>

  <span class="hljs-keyword">return</span> htmlResponse(html)
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> person
</code></pre>
<h2 id="support-files">Support files</h2>
<p>Create <code>src/lib/objectToTable.js</code>. This will convert an Object to an HTML table for this demo.</p>
<p>I don't care for looking at raw JSON, even if it's just a demo. So I'm gonna pretty it up a bit.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> objectToTable = <span class="hljs-function"><span class="hljs-params">obj</span> =&gt;</span> {
  <span class="hljs-keyword">const</span> rows = <span class="hljs-built_in">Object</span>.entries(obj)
    .map(<span class="hljs-function">(<span class="hljs-params">[key, value]</span>) =&gt;</span> <span class="hljs-string">`&lt;tr&gt;&lt;th&gt;<span class="hljs-subst">${key}</span>&lt;/th&gt;&lt;td&gt;<span class="hljs-subst">${value}</span>&lt;/td&gt;&lt;/tr&gt;`</span>)
    .join(<span class="hljs-string">''</span>)
  <span class="hljs-keyword">return</span> <span class="hljs-string">`&lt;table&gt;<span class="hljs-subst">${rows}</span>&lt;/table&gt;`</span>
}
</code></pre>
<h2 id="add-pages-to-the-router">Add Pages To The Router</h2>
<p>Now I need to add the routes to the <code>src/index.js</code> Router.</p>
<p>Import the new pages at the top of <code>src/index.js</code>:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> allPeople <span class="hljs-keyword">from</span> <span class="hljs-string">"./pages/allPeople"</span>;
<span class="hljs-keyword">import</span> person <span class="hljs-keyword">from</span> <span class="hljs-string">"./pages/person"</span>;
</code></pre>
<p>Then add the router code for those pages:</p>
<pre><code class="lang-javascript">router.get(<span class="hljs-string">'/people/?'</span>, <span class="hljs-function">() =&gt;</span> allPeople(request))
router.get(<span class="hljs-string">'/people/.+'</span>, <span class="hljs-function">() =&gt;</span> person(request))
</code></pre>
<h2 id="run-the-cloudflare-worker-locally">Run The Cloudflare Worker Locally</h2>
<pre><code class="lang-bash">$ npm run preview
</code></pre>
<p>After the browser opens, navigate to <code>/people</code>.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1607385406398/27UxoWEeV.png" alt="image.png" /></p>
<p>Click on any person to view the <code>/people/{id}</code> route.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1607385540303/ayQzJAYgQ.png" alt="image.png" /></p>
<p>View the source to confirm the application is rendered on the server.</p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">ul</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">li</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"/people/1"</span>&gt;</span>Luke Skywalker<span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">li</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"/people/2"</span>&gt;</span>C-3PO<span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">li</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"/people/3"</span>&gt;</span>R2-D2<span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">li</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"/people/4"</span>&gt;</span>Darth Vader<span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">li</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"/people/5"</span>&gt;</span>Leia Organa<span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">li</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"/people/6"</span>&gt;</span>Owen Lars<span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">li</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"/people/7"</span>&gt;</span>Beru Whitesun lars<span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">li</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"/people/8"</span>&gt;</span>R5-D4<span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">li</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"/people/9"</span>&gt;</span>Biggs Darklighter<span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">li</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"/people/10"</span>&gt;</span>Obi-Wan Kenobi<span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">ul</span>&gt;</span>
</code></pre>
<h2 id="summary">Summary</h2>
<p>Pulling data from an API and rendering the content from the server side is key for application that are require SEO. Cloudflare Workers let's us do this with very little code.</p>
<p>Browse <a target="_blank" href="https://github.com/joelnet/cloudflare-worker-website/tree/f6a224b37010d76d694320d4748ac07cb75f7369">the repository at this point in history</a>.</p>
<p>Subscribe to my <a target="_blank" href="https://joel.net/newsletter">Newsletter</a> to continue learning about Cloudflare Workers!</p>
<p>Cheers 🍻</p>
<ul>
<li>Join my 📰 <a target="_blank" href="https://joel.net/newsletter">Newsletter</a></li>
<li>Subscribe to my 📺 YouTube, <a target="_blank" href="https://www.youtube.com/c/JoelCodes?sub_confirmation=1">JoelCodes</a></li>
<li>Say hi to me on Twitter <a target="_blank" href="https://twitter.com/joelnet">@joelnet</a></li>
</ul>
]]></content:encoded></item><item><title><![CDATA[⛅ Cloudflare Workers as a Web Server with Webpack (Lesson 2)]]></title><description><![CDATA[In this article, I will be configuring a Worker as a Web Server with Webpack.
This is the second article in a series I am doing on Cloudflare Workers. I am excited about the Cloudflare Workers platform and if you are too, subscribe to my Newsletter a...]]></description><link>https://joel.net/cloudflare-workers-as-a-web-server-with-webpack-lesson-2</link><guid isPermaLink="true">https://joel.net/cloudflare-workers-as-a-web-server-with-webpack-lesson-2</guid><category><![CDATA[cloudflare]]></category><category><![CDATA[Web Hosting]]></category><category><![CDATA[JavaScript]]></category><dc:creator><![CDATA[Joel Thoms]]></dc:creator><pubDate>Mon, 16 Nov 2020 19:03:06 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1605553374526/Z5Awtec7G.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In this article, I will be configuring a Worker as a Web Server with Webpack.</p>
<p>This is the second article in a series I am doing on Cloudflare Workers. I am excited about the Cloudflare Workers platform and if you are too, subscribe to my Newsletter and get a notification for the next article!</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://www.youtube.com/watch?v=KMEgJRqYzzg">https://www.youtube.com/watch?v=KMEgJRqYzzg</a></div>
<h2 id="cloudflare-workers-course-outline">Cloudflare Workers Course Outline</h2>
<ol>
<li><a target="_blank" href="https://joel.net/getting-started-with-serverless-cloudflare-workers-lesson-1">Getting Started with Serverless Cloudflare Workers</a></li>
<li><strong>Cloudflare Workers as a Web Server (with Webpack)</strong></li>
<li><a target="_blank" href="https://joel.net/making-api-calls-from-a-cloudflare-worker-lesson-3">Making API Calls From a Cloudflare Worker</a></li>
<li><a target="_blank" href="https://joel.net/key-value-storage-with-cloudflare-workers-kv-lesson-4">Key-Value Storage With Cloudflare Workers KV</a></li>
<li><a target="_blank" href="https://joel.net/smart-routing-with-cloudflare-workers-and-webpack-bonus-content">[Bonus] Smart Routing with Cloudflare Workers</a></li>
</ol>
<h2 id="generate-a-cloudflare-worker-website">Generate a Cloudflare Worker Website</h2>
<p>If you already have <code>wrangler</code> installed, you can skip the <code>npm install</code> step.</p>
<pre><code class="lang-bash">$ npm install -g @cloudflare/wrangler
$ wrangler generate my-website
$ <span class="hljs-built_in">cd</span> my-website
</code></pre>
<h2 id="configure-webpack">Configure Webpack</h2>
<p>Webpack supports the <code>webworker</code> target output with this <code>webpack.config.js</code>.</p>
<p>I also move <code>index.js</code> to <code>src/index.js</code>. I like to keep all my source files in an <code>src</code> directory so they aren't hidden by all the configs in the root.</p>
<pre><code class="lang-javascript"><span class="hljs-built_in">module</span>.exports = {
    <span class="hljs-attr">target</span>: <span class="hljs-string">'webworker'</span>,
    <span class="hljs-attr">context</span>: __dirname,
    <span class="hljs-attr">entry</span>: <span class="hljs-string">'./src/index.js'</span>,
    <span class="hljs-attr">mode</span>: <span class="hljs-string">'development'</span>,
    <span class="hljs-attr">devtool</span>: <span class="hljs-string">'cheap-module-source-map'</span>,
    <span class="hljs-attr">module</span>: {
        <span class="hljs-attr">rules</span>: [
            {
                <span class="hljs-attr">test</span>: <span class="hljs-regexp">/\.html$/i</span>,
                loader: <span class="hljs-string">'html-loader'</span>,
            },
        ],
    },
}
</code></pre>
<p>I am also installing the <code>html-loader</code> to include html files in the bundle.</p>
<pre><code class="lang-bash">$ npm install --save-dev html-loader
</code></pre>
<h2 id="change-the-worker-type-to-webpack">Change The Worker Type to Webpack</h2>
<p>Change the <code>type</code> to <code>webpack</code> and add <code>webpack_config</code> with the value <code>webpack.config.js</code>.</p>
<p>Don't forget to set the<code>account_id</code>.</p>
<pre><code class="lang-toml"><span class="hljs-attr">name</span> = <span class="hljs-string">"my-website"</span>
<span class="hljs-attr">type</span> = <span class="hljs-string">"webpack"</span>
<span class="hljs-attr">account_id</span> = <span class="hljs-string">"1234567890"</span> <span class="hljs-comment"># set the account_id here!</span>
<span class="hljs-attr">workers_dev</span> = <span class="hljs-literal">true</span>
<span class="hljs-attr">route</span> = <span class="hljs-string">""</span>
<span class="hljs-attr">zone_id</span> = <span class="hljs-string">""</span>
<span class="hljs-attr">webpack_config</span> = <span class="hljs-string">"webpack.config.js"</span>
</code></pre>
<h2 id="add-html-files">Add HTML Files</h2>
<p>Create an <code>html</code> directory and add an <code>index.html</code> and <code>404.html</code>.</p>
<p><strong>index.html</strong></p>
<pre><code class="lang-html"><span class="hljs-meta">&lt;!DOCTYPE <span class="hljs-meta-keyword">html</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">html</span> <span class="hljs-attr">lang</span>=<span class="hljs-string">"en"</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">head</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">charset</span>=<span class="hljs-string">"UTF-8"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"viewport"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">"width=device-width, initial-scale=1.0"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">title</span>&gt;</span>Document<span class="hljs-tag">&lt;/<span class="hljs-name">title</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">head</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">body</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">h1</span>&gt;</span>Hello Worker!<span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">body</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">html</span>&gt;</span>
</code></pre>
<p><strong>404.html</strong></p>
<pre><code class="lang-html"><span class="hljs-meta">&lt;!DOCTYPE <span class="hljs-meta-keyword">html</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">html</span> <span class="hljs-attr">lang</span>=<span class="hljs-string">"en"</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">head</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">charset</span>=<span class="hljs-string">"UTF-8"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"viewport"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">"width=device-width, initial-scale=1.0"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">title</span>&gt;</span>Document<span class="hljs-tag">&lt;/<span class="hljs-name">title</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">head</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">body</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">h1</span>&gt;</span>404 Not Found<span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">body</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">html</span>&gt;</span>
</code></pre>
<h2 id="loading-routes">Loading Routes</h2>
<p>Modify the <code>src/index.js</code> file to import the HTML files and then in <code>handleRequest</code>, return either the <code>index.html</code> or the <code>404.html</code>. </p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> index <span class="hljs-keyword">from</span> <span class="hljs-string">'../html/index.html'</span>
<span class="hljs-keyword">import</span> notFound <span class="hljs-keyword">from</span> <span class="hljs-string">'../html/404.html'</span>

addEventListener(<span class="hljs-string">'fetch'</span>, <span class="hljs-function"><span class="hljs-params">event</span> =&gt;</span> {
  event.respondWith(handleRequest(event.request))
})

<span class="hljs-comment">/**
 * Respond with hello worker text
 * <span class="hljs-doctag">@param <span class="hljs-type">{Request}</span> <span class="hljs-variable">request</span></span>
 */</span>
<span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">handleRequest</span>(<span class="hljs-params">request</span>) </span>{
  <span class="hljs-keyword">const</span> { pathname } = <span class="hljs-keyword">new</span> URL(request.url)

  <span class="hljs-keyword">if</span> (pathname === <span class="hljs-string">'/'</span>) {
    <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> Response(index, {
      <span class="hljs-attr">headers</span>: { <span class="hljs-string">'content-type'</span>: <span class="hljs-string">'text/html'</span> },
    })
  }

  <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> Response(notFound, {
    <span class="hljs-attr">headers</span>: { <span class="hljs-string">'content-type'</span>: <span class="hljs-string">'text/html'</span> },
    <span class="hljs-attr">status</span>: <span class="hljs-number">404</span>,
  })
}
</code></pre>
<p>At this point let's preview and make sure it's all working.</p>
<pre><code class="lang-bash">$ wrangler preview --watch
</code></pre>
<p>If all goes well, then a window should popup like this one showing our <code>index.html</code> page.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1605073076808/rTipf6-vw.png" alt="image.png" /></p>
<h2 id="smarter-routing">Smarter Routing</h2>
<p>Routing with <code>if</code> statements is pretty primitive. I'm gonna improve this.</p>
<p>Copy the <code>router.js</code> from <a target="_blank" href="https://github.com/cloudflare/worker-template-router">cloudflare / worker-template-router</a> or <a target="_blank" href="https://github.com/joelnet/cloudflare-worker-website/blob/ae0c7dcf533099832bc313099425d270e326148e/src/lib/router.js">(2)</a> and paste it into <code>src/lib/router.js</code>.</p>
<p>Switch back to <code>src/index.js</code> and import the Router at the top of the file.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> Router <span class="hljs-keyword">from</span> <span class="hljs-string">'./lib/router'</span>
</code></pre>
<p>Now I can change the routes inside <code>handleRequest</code>.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">handleRequest</span>(<span class="hljs-params">request</span>) </span>{
    <span class="hljs-keyword">const</span> router = <span class="hljs-keyword">new</span> Router()

    router.get(
        <span class="hljs-string">'/'</span>,
        <span class="hljs-function">() =&gt;</span>
            <span class="hljs-keyword">new</span> Response(index, {
                <span class="hljs-attr">headers</span>: { <span class="hljs-string">'content-type'</span>: <span class="hljs-string">'text/html'</span> },
            }),
    )
    router.all(
        <span class="hljs-function">() =&gt;</span>
            <span class="hljs-keyword">new</span> Response(notFound, {
                <span class="hljs-attr">headers</span>: { <span class="hljs-string">'content-type'</span>: <span class="hljs-string">'text/html'</span> },
                <span class="hljs-attr">status</span>: <span class="hljs-number">404</span>,
            }),
    )

    <span class="hljs-keyword">const</span> response = <span class="hljs-keyword">await</span> router.route(request)
    <span class="hljs-keyword">return</span> response
}
</code></pre>
<p>It might not seem like a big change, but the route is now checking the <code>path</code> for <code>/</code> and the <code>method</code> for <code>GET</code>. This will give me great flexibility in my planned future.</p>
<h2 id="refactoring">Refactoring</h2>
<p>At this stage the Website is working pretty well, but I always like to do a little bit of refactoring in the end. It just makes me happy.</p>
<h3 id="responses">Responses</h3>
<p>First, I'm going to move the responses into a new file.</p>
<pre><code class="lang-javascript"><span class="hljs-comment">/**
 * src/lib/responses.js
 */</span>
<span class="hljs-keyword">import</span> notFoundHtml <span class="hljs-keyword">from</span> <span class="hljs-string">'../../html/404.html'</span>

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> htmlResponse = <span class="hljs-function"><span class="hljs-params">html</span> =&gt;</span>
    <span class="hljs-keyword">new</span> Response(html, {
        <span class="hljs-attr">headers</span>: { <span class="hljs-string">'content-type'</span>: <span class="hljs-string">'text/html'</span> },
    })

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> notFoundResponse = <span class="hljs-function">() =&gt;</span>
    <span class="hljs-keyword">new</span> Response(notFoundHtml, {
        <span class="hljs-attr">headers</span>: { <span class="hljs-string">'content-type'</span>: <span class="hljs-string">'text/html'</span> },
        <span class="hljs-attr">status</span>: <span class="hljs-number">404</span>,
    })
</code></pre>
<p>Now I can import these from <code>src/index.js</code></p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> { htmlResponse, notFoundResponse } <span class="hljs-keyword">from</span> <span class="hljs-string">'./lib/responses'</span>
</code></pre>
<p>Then my <code>handleRequest</code> function cleans up like this.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">handleRequest</span>(<span class="hljs-params">request</span>) </span>{
    <span class="hljs-keyword">const</span> router = <span class="hljs-keyword">new</span> Router()

    router.get(<span class="hljs-string">'/'</span>, <span class="hljs-function">() =&gt;</span> htmlResponse(index))
    router.all(<span class="hljs-function">() =&gt;</span> notFoundResponse(notFound))

    <span class="hljs-keyword">const</span> response = <span class="hljs-keyword">await</span> router.route(request)
    <span class="hljs-keyword">return</span> response
}
</code></pre>
<h3 id="pages">Pages</h3>
<p>I like having the concept of pages, similar to how Next.js works. All my logic can't be written inside of <code>src/index.js</code> and I like to break this out early.</p>
<p>So I'm going to create a <code>pages</code> directory and create my two routes.</p>
<pre><code class="lang-javascript"><span class="hljs-comment">/**
 * src/pages/index.js
 */</span>
<span class="hljs-keyword">import</span> { htmlResponse } <span class="hljs-keyword">from</span> <span class="hljs-string">'../lib/responses'</span>
<span class="hljs-keyword">import</span> index <span class="hljs-keyword">from</span> <span class="hljs-string">'../../html/index.html'</span>

<span class="hljs-keyword">const</span> home = <span class="hljs-function">() =&gt;</span> htmlResponse(index)

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> home
</code></pre>
<pre><code class="lang-javascript"><span class="hljs-comment">/**
 * src/pages/404.js
 */</span>
<span class="hljs-keyword">import</span> { notFoundResponse } <span class="hljs-keyword">from</span> <span class="hljs-string">'../lib/responses'</span>

<span class="hljs-keyword">const</span> notFound = <span class="hljs-function">() =&gt;</span> notFoundResponse()

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> notFound
</code></pre>
<p>In <code>src/index.js</code> I import the new routes and remove some old imports.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> index <span class="hljs-keyword">from</span> <span class="hljs-string">'./pages/index'</span>
<span class="hljs-keyword">import</span> notFound <span class="hljs-keyword">from</span> <span class="hljs-string">'./pages/404'</span>
<span class="hljs-comment">// import index from '../html/index.html'</span>
<span class="hljs-comment">// import notFound from '../html/404.html'</span>
<span class="hljs-comment">// import { htmlResponse, notFoundResponse } from './lib/responses'</span>
</code></pre>
<p>Then my <code>handleRequest</code> turns into this. Notice how I am passing the <code>request</code> into each page. It's unused now, but I will eventually have a route that will need it.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">handleRequest</span>(<span class="hljs-params">request</span>) </span>{
    <span class="hljs-keyword">const</span> router = <span class="hljs-keyword">new</span> Router()

    router.get(<span class="hljs-string">'/'</span>, <span class="hljs-function">() =&gt;</span> index(request))
    router.all(<span class="hljs-function">() =&gt;</span> notFound(request))

    <span class="hljs-keyword">const</span> response = <span class="hljs-keyword">await</span> router.route(request)
    <span class="hljs-keyword">return</span> response
}
</code></pre>
<h2 id="source-code">Source Code</h2>
<p>Check out the project over on my Github repo.</p>
<p><a target="_blank" href="https://github.com/joelnet/cloudflare-worker-website/tree/ae0c7dcf533099832bc313099425d270e326148e">https://github.com/joelnet/cloudflare-worker-website</a></p>
<h2 id="summary">Summary</h2>
<p>Configuring Cloudflare Workers to work as a web server is pretty simple using the <code>webpack</code> project type.</p>
<p>I was able to import HTML using the <code>html-loader</code> Webpack plugin and serve pages based on custom routing.</p>
<p>Subscribe to my <a target="_blank" href="https://joel.net/newsletter">Newsletter</a> to continue learning about Cloudflare Workers!</p>
<p>Cheers 🍻</p>
<ul>
<li>Join my 📰 <a target="_blank" href="https://joel.net/newsletter">Newsletter</a></li>
<li>Subscribe to my 📺 YouTube, <a target="_blank" href="https://www.youtube.com/c/JoelCodes?sub_confirmation=1">JoelCodes</a></li>
<li>Say hi to me on Twitter <a target="_blank" href="https://twitter.com/joelnet">@joelnet</a></li>
</ul>
]]></content:encoded></item><item><title><![CDATA[⛅ Getting Started with Serverless Cloudflare Workers (Lesson 1)]]></title><description><![CDATA[Cloudflare Workers are a similar to other serverless platform offerings, but at the Edge. Edge meaning your code is deployed and runs globally across many regions in the same way a CDN works for static content.
https://www.youtube.com/watch?v=WFlDSL7...]]></description><link>https://joel.net/getting-started-with-serverless-cloudflare-workers-lesson-1</link><guid isPermaLink="true">https://joel.net/getting-started-with-serverless-cloudflare-workers-lesson-1</guid><category><![CDATA[cloudflare]]></category><category><![CDATA[serverless]]></category><dc:creator><![CDATA[Joel Thoms]]></dc:creator><pubDate>Tue, 10 Nov 2020 14:31:12 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1604987375269/mzZo9x60r.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Cloudflare Workers are a similar to other serverless platform offerings, but at the Edge. Edge meaning your code is deployed and runs globally across many regions in the same way a CDN works for static content.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://www.youtube.com/watch?v=WFlDSL7Nrzw">https://www.youtube.com/watch?v=WFlDSL7Nrzw</a></div>
<h2 id="cloudflare-workers-course-outline">Cloudflare Workers Course Outline</h2>
<ol>
<li><strong>Getting Started with Serverless Cloudflare Workers</strong></li>
<li><a target="_blank" href="https://joel.net/cloudflare-workers-as-a-web-server-with-webpack-lesson-2">Cloudflare Workers as a Web Server (with Webpack)</a></li>
<li><a target="_blank" href="https://joel.net/making-api-calls-from-a-cloudflare-worker-lesson-3">Making API Calls From a Cloudflare Worker</a></li>
<li><a target="_blank" href="https://joel.net/key-value-storage-with-cloudflare-workers-kv-lesson-4">Key-Value Storage With Cloudflare Workers KV</a></li>
<li><a target="_blank" href="https://joel.net/smart-routing-with-cloudflare-workers-and-webpack-bonus-content">[Bonus] Smart Routing with Cloudflare Workers</a></li>
</ol>
<h2 id="advantages">Advantages</h2>
<p>Server Side Rendering - Better SEO compared to a static site host.</p>
<p>Automatic Scaling - Scaling is not an easy problem to solve and can be a PITA.</p>
<p>Fast - Cloudflare Workers are geographically distributed, so they're fast wherever your users happen to be.</p>
<p>0ms  Cold Starts - Unlike AWS Lambdas, Azure Functions, and Google Cloud Functions, Cloudflare Workers have no startup time.</p>
<p>Price - The price is amazing and the FREE plan might even be enough for your needs.</p>
<h2 id="disadvantages">Disadvantages</h2>
<p>CPU Runtime - 10ms (free) and 50ms (paid). You won't be running any CPU intensive tasks using Cloudflare Workers. So those will have to be offloaded to another server or service. Idle time isn't counted against this, so wait time for long API calls won't count against this.</p>
<p>Simultaneous Outgoing Requests - 6. If you make a lot of API calls from your workers, you could easily hit this limit. So you'll have to be smart about caching.</p>
<p>It's not Node.js - Cloudflare Workers do not run in a Node.js environment. There may be some Node API's or NPM modules that don't work in the Cloudlfare environment. But I have found that if it run inside a web browser, it's likely that it will also run in a Worker.</p>
<h2 id="what-does-it-all-mean">What Does It All Mean?</h2>
<p>Cloudflare Workers are not a replacement for Lambdas, or Cloud Functions. You likely won't be able to port your code from one to a worker. You will also need to design your application around the strengths and weaknesses of Workers.</p>
<p>With that being said, I am incredibly excited about the Cloudflare Worker platform. I have been working on some PoC projects on Cloudflare Workers and have been pretty impressed with the experience so far.</p>
<h2 id="lets-get-started">Let's Get Started</h2>
<p>Install the <code>wrangler</code> CLI.</p>
<pre><code class="lang-bash">$ npm install -g @cloudflare/wrangler
</code></pre>
<p>Use <code>wrangler</code> to generate a new site.</p>
<pre><code class="lang-bash">$ wrangler generate my-worker
$ <span class="hljs-built_in">cd</span> my-worker
</code></pre>
<p>Make sure it works</p>
<pre><code class="lang-bash">$ wrangler preview --watch
</code></pre>
<p>If all goes well, a window should popup looking a bit like this:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1604881228414/IYPcjKgbV.png" alt="Cloudflare Workers open in the Browser" /></p>
<p>Congratulations, your Cloudflare Worker is running locally!</p>
<h2 id="create-an-api-token">Create an API Token</h2>
<p>Sign into Cloudflare and go to the <a target="_blank" href="https://dash.cloudflare.com/profile/api-tokens">api-tokens</a> page and click <strong>Create Token</strong> then select <strong>Use Cloudflare Workers</strong> template.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1604882505769/7sC_3-2gg.png" alt="image.png" /></p>
<p>My permissions looks like this:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1604895672631/5mmR9J-Ab.png" alt="image.png" /></p>
<p>Copy your Token and keep it somewhere safe, like a password manager. It will not be shown again.</p>
<p>Now launch <code>wrangler config</code> and enter in your API Key.</p>
<pre><code class="lang-bash">$ wrangler config
</code></pre>
<p>There are other ways to authenticate, <a target="_blank" href="https://developers.cloudflare.com/workers/learning/getting-started">read the documentation for more details</a>, but an API Token is my preference.</p>
<h2 id="publish-your-project">Publish Your Project</h2>
<p>Open your <code>wrangler.toml</code> file and populate your <code>account_id</code>. Then use the <code>publish</code> subcommand.</p>
<pre><code class="lang-bash">$ wrangler publish
</code></pre>
<p>After publishing, I get a message with the URL of my new Worker.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1604895023484/ESjnSPYTL.png" alt="image.png" /></p>
<h2 id="more">More</h2>
<p>I will be creating many more articles and videos talking about Cloudflare Workers. Please subscribe to my 📰 <a target="_blank" href="https://joel.net/newsletter">Newsletter</a> to be notified when new articles come out.</p>
<p>Read further: <a target="_blank" href="https://developers.cloudflare.com/workers/learning/getting-started">Cloudflare Docs - Getting Started</a></p>
<p>Cheers 🍻</p>
<ul>
<li>Join my 📰 <a target="_blank" href="https://joel.net/newsletter">Newsletter</a></li>
<li>Subscribe to my 📺 YouTube, <a target="_blank" href="https://www.youtube.com/c/JoelCodes?sub_confirmation=1">JoelCodes</a></li>
<li>Say hi to me on Twitter <a target="_blank" href="https://twitter.com/joelnet">@joelnet</a></li>
</ul>
<p>Photo by <a target="_blank" href="https://www.pexels.com/@rakicevic-nenad-233369">Rakicevic Nenad</a> from <a target="_blank" href="https://www.pexels.com/photo/man-holding-ice-cream-cone-under-cloud-1262302">Pexels</a></p>
]]></content:encoded></item><item><title><![CDATA[🧠 I Code Differently Depending on These 5 Project Types]]></title><description><![CDATA[There are a lot of articles out there talking about best practices, patterns, principles, etc. When I say a lot, I really mean an overwhelming soul crushing amount of articles. But few of them talk about when these practices should be applied.
Even w...]]></description><link>https://joel.net/i-code-differently-depending-on-these-5-project-types</link><guid isPermaLink="true">https://joel.net/i-code-differently-depending-on-these-5-project-types</guid><category><![CDATA[design patterns]]></category><category><![CDATA[design and architecture]]></category><category><![CDATA[projects]]></category><category><![CDATA[programmer]]></category><category><![CDATA[Software Engineering]]></category><dc:creator><![CDATA[Joel Thoms]]></dc:creator><pubDate>Tue, 03 Nov 2020 14:31:11 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1604358863886/_rnDCOEeN.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>There are a lot of articles out there talking about best practices, patterns, principles, etc. When I say a lot, I really mean an overwhelming soul crushing amount of articles. But few of them talk about <em>when</em> these practices should be applied.</p>
<p>Even worse are the debates on social media about best practices and anti-patterns that are also without context.</p>
<p>It's the context that is missing and is so important in making these decisions. </p>
<p><strong>Context is King!</strong> 👑</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://www.youtube.com/watch?v=olULbdwJ9rE">https://www.youtube.com/watch?v=olULbdwJ9rE</a></div>
<h2 id="do-i-really-write-code-differently">Do I Really Write Code Differently?</h2>
<p>Absolutely. There's a quote somewhere that goes something like this:</p>
<blockquote>
<p>"A Junior Developer knows not of design patterns. A Senior Developer knows when to apply design patterns. A Master Developer knows when not to apply those design patterns." -- Unknown Author</p>
</blockquote>
<p>There isn't a single design pattern that has a rule that says it should be used in 100% of scenarios. But sometimes we treat them like they should be.</p>
<p>With all codes, patterns, practices, use your best judgment.</p>
<h2 id="project-types">Project Types</h2>
<p>There are a few ways I classify the projects that I work on. The project type usually determines how I design the software.</p>
<p>This is by no means an exhaustive list and may include overlap. Your list may be different.</p>
<h4 id="proof-of-concept">Proof of Concept</h4>
<p>A Proof of Concept (PoC) project's priority is <strong>development time</strong>. A PoC project should be created as fast as possible.</p>
<p>You may be tempted to write the code in a way that could be re-used if the project moves out of the PoC phase, but you would be sacrificing the main priority, which is speed of development.</p>
<p>Do you struggle with flex grid when you could bang out a <code>&lt;table&gt;</code> in 3 seconds? Okay, use a <code>&lt;table&gt;</code>.</p>
<p>Would setting up React take too long when you could do the same in jQuery in a minute? Okay, then use jQuery!</p>
<p>Redux vs a Global? Okay... you get it now.</p>
<p>Testing, Logging, Metrics, Monitoring, Performance, Scaling? Not in the PoC. Just make it work.</p>
<p>Whether it is successful or not, this code should be mostly discarded.</p>
<h4 id="personal-pet-projects">Personal (Pet Projects)</h4>
<p>This is my favorite type of project because <strong>there are no rules</strong>. The project's priority is me! Do I want to go with a functional (FP) design? I'm gonna go for it. Do I want to learn a new technology? Hell ya!</p>
<p>This is usually where I explore. I find some new technology and I explore it as much as I can in my own personal pet projects.</p>
<p>I typically apply more care for readability (compared to a PoC) in my Personal projects because I tend to pick them up again after years and still want to be able to understand them.</p>
<p>I don't care too much about testing or design patterns. I'm usually just exploring here.</p>
<p>I also like to include a <code>README.md</code> to remind me how to start/debug/deploy the project. This has saved my ass more than once.</p>
<h4 id="open-source">Open Source</h4>
<p>The priority of an Open Source project should be <strong>readability</strong>. The audience of this project is people who will use the code as well as people who will read the code.</p>
<p>You will be judged by the quality of your source, so keep it tight, clean, and well documented.</p>
<p>The higher the code coverage the better on an Open Source project. Give contributors the confidence of seeing green tests to know they didn't break anything. Also give yourself confidence to merge requests.</p>
<p>It's good to have some design patterns and practices laid out in a <code>CONTRIBUTORS.md</code>. This will clear up how contributors should write code and give you something to point to when a merge request deviates from the standard.</p>
<p>Code should be crafted with consideration, care, and impact.</p>
<h4 id="libraries">Libraries</h4>
<p>Library code should <strong>focus on the developer experience</strong>.</p>
<p>The focus should be on creating documentation and example code. The projects I love all have great documentation.</p>
<p>I also spend a little extra time to focus on input validation and verbose Error messages. You don't know what values a developer is going to pass into your code. So a little extra attention at this step can go a long way.</p>
<p>The focus on input validation can be the difference between good errors and bad errors.</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// ❌ Error: Cannot read property 'toString' of undefined</span>
<span class="hljs-comment">// ✅ Error: Options must include a 'url' property. more info: https://&lt;url-to-docs&gt;</span>
</code></pre>
<p>I try to optimize the code since I do not know how or where the code will be used and don't want to be the bottleneck in someone's application.</p>
<p>I also consider supporting TypeScript types. It's nice when you type dot and the editor will IntelliSense complete your code.</p>
<h4 id="enterprise-applications">Enterprise Applications</h4>
<p>The main focus of an enterprise application is <strong>uptime and performance</strong>.</p>
<p>Extra time needs to be spent to catch bugs before reaching production. This means unit tests, integration tests, cicd, etc.</p>
<p>Logging, Metrics, Monitoring, and Alerting all become critical to identify and debug issues.</p>
<p>Care must be taken when handling API keys and other secrets which could be leaked from source code.</p>
<p>Deployments tend to be slower due to the coordination and QA involved. So it's nice to be able to change configuration values without redeploying the application.</p>
<p>There is a lot of care that goes into an enterprise application to make sure it's always up.</p>
<h2 id="summary">Summary</h2>
<p>It can be overwhelming reading and navigating tech articles today. Take them all with a grain of salt and context. There are times to use them, there are also times to not.</p>
<p>There is no one size fits all. It all depends on the context.</p>
<p>Cheers 🍻</p>
<ul>
<li>Join my 📰 <a target="_blank" href="https://joel.net/newsletter">Newsletter</a></li>
<li>Subscribe to my 📺 YouTube, <a target="_blank" href="https://www.youtube.com/c/JoelCodes?sub_confirmation=1">JoelCodes</a></li>
<li>Say hi to me on Twitter <a target="_blank" href="https://twitter.com/joelnet">@joelnet</a></li>
</ul>
<p><span>Photo by <a href="https://unsplash.com/@markusspiske?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Markus Spiske</a> on <a href="https://unsplash.com/s/photos/different?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Unsplash</a></span></p>
]]></content:encoded></item><item><title><![CDATA[FREE JamStack / Static Site Hosting with Keybase.io]]></title><description><![CDATA[Keybase offers secure messaging and file sharing. Included in the file sharing offering is 250GB of storage which can also be used to host public JamStack / Static Websites.
https://www.youtube.com/watch?v=GzhhG5ItwY8
Install Keybase
Keybase Sites ar...]]></description><link>https://joel.net/free-jamstack-static-site-hosting-with-keybaseio</link><guid isPermaLink="true">https://joel.net/free-jamstack-static-site-hosting-with-keybaseio</guid><category><![CDATA[JAMstack]]></category><category><![CDATA[Web Hosting]]></category><dc:creator><![CDATA[Joel Thoms]]></dc:creator><pubDate>Mon, 26 Oct 2020 14:31:22 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1603416208504/ED4gUtlbH.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><a target="_blank" href="https://keybase.io">Keybase</a> offers secure messaging and file sharing. Included in the file sharing offering is <strong>250GB of storage which can also be used to host public JamStack / Static Websites</strong>.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://www.youtube.com/watch?v=GzhhG5ItwY8">https://www.youtube.com/watch?v=GzhhG5ItwY8</a></div>
<h2 id="install-keybase">Install Keybase</h2>
<p><strong>Keybase Sites</strong> are accessed through their software so you'll have to head over to <a target="_blank" href="https://keybase.io">Keybase.io</a> and follow the directions for your operating system to install.</p>
<p>After installing the <a target="_blank" href="https://keybase.io">Keybase</a> client, navigate to the <strong>Files</strong> tab. You should see a screen similar to this.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1603410494221/EPird1Mn4.png" alt="keybase.png" /></p>
<p>Follow the directions to <strong>Enable Keybase</strong> for the file system for your operating system. This will give you native file system access to your <a target="_blank" href="https://keybase.io">Keybase</a> secure file share.</p>
<p>Once the <strong>Keybase File System</strong> has been installed you should be able to access your files at <code>/keybase</code> for Mac/Linux and <code>K:\</code> on Windows.</p>
<p>If everything is setup correctly, under <code>/keybase</code> you should see three directories.</p>
<pre><code class="lang-text">/keybase
    /private
    /public
    /team
</code></pre>
<h2 id="the-keybase-subdomain">The Keybase Subdomain</h2>
<p>All <a target="_blank" href="https://keybase.io">Keybase</a> accounts are given a subdomain with the format <code>&lt;username&gt;.keybase.pub</code>. For example, my username is <code>joelnet</code>, so my subdomain is <a target="_blank" href="https://joelnet.keybase.pub">joelnet.keybase.pub</a>. This is where my static files will live.</p>
<h2 id="copying-files-to-keybase">Copying Files to Keybase</h2>
<p>Files can be added to the <strong>Keybase Subdomain</strong> by copying them into <code>/keybase/public/joelnet</code> (<code>K:\public\joelnet</code> on Windows). All files copied here are <strong>PUBLIC</strong> and everyone has <strong>read</strong> access to them.</p>
<p>I have prepared a Gatsby site that I am going to copy to <code>/keybase/public/joelnet</code>  (<code>K:\public\joelnet</code> on Windows).
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1603412507383/ugCFyuqtG.gif" alt="copy-files-to-keybase.gif" /></p>
<p>Your website has been deployed! Open up a browser and visit your new site. My site is now up and visible on <a target="_blank" href="https://joelnet.keybase.pub">joelnet.keybase.pub</a> 🔥</p>
<h2 id="custom-domain-names">Custom Domain Names</h2>
<p><a target="_blank" href="https://keybase.io">Keybase</a> also supports custom domain names, but the process is a little different.</p>
<p>The site needs to be deployed under <code>/keybase/private/&lt;username&gt;,kbpbot/&lt;site&gt;</code> (<code>K:\private\&lt;username&gt;,kbpbot/&lt;site&gt;</code> on Windows). <code>&lt;site&gt;</code> can be any name. This gives you the flexibility to host multiple sites on <a target="_blank" href="https://keybase.io">Keybase</a>.</p>
<p>The next step is setting up the DNS.</p>
<p>Create a <code>CNAME</code> to point to <code>kbp.keybaseapi.com</code>.</p>
<p>Next create a <code>TXT</code> record. If your site is at <code>my-site.example.com</code>, then prefix the record with <code>_keybase_pages.</code>.</p>
<p>This is what my DNS records look like:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1603413846461/qpclb_3J8.png" alt="dns.png" /></p>
<p>After copying your files, your website should be accessible on your custom domain!</p>
<h2 id="end">End</h2>
<p>More information about <a target="_blank" href="https://book.keybase.io/sites">Keybase Sites</a> can be found <a target="_blank" href="https://book.keybase.io/sites">here</a>.</p>
<ul>
<li>Join my 📰 <a target="_blank" href="https://joel.net/newsletter">Newsletter</a></li>
<li>Subscribe to my 📺 YouTube, <a target="_blank" href="https://www.youtube.com/c/JoelCodes?sub_confirmation=1">JoelCodes</a></li>
<li>Say hi to me on Twitter <a target="_blank" href="https://twitter.com/joelnet">@joelnet</a></li>
</ul>
<p>Cheers 🍻</p>
<p>Photo by <a target="_blank" href="https://www.pexels.com/@killian-eon-1185568">Killian Eon</a> from <a target="_blank" href="https://www.pexels.com/photo/blue-metal-tool-2635595">Pexels</a></p>
]]></content:encoded></item><item><title><![CDATA[Custom Array Sort Algorithms in JavaScript]]></title><description><![CDATA[https://www.youtube.com/watch?v=-S8b_j5XBV0
JavaScript's Default Array Sort
JavaScript's Array.sort defaults to a String sort. This catches many people off guard when attempting to sort an Array of type Number.
// ❌ Default search is a String search
...]]></description><link>https://joel.net/custom-array-sort-algorithms-in-javascript</link><guid isPermaLink="true">https://joel.net/custom-array-sort-algorithms-in-javascript</guid><category><![CDATA[JavaScript]]></category><category><![CDATA[sorting]]></category><dc:creator><![CDATA[Joel Thoms]]></dc:creator><pubDate>Thu, 22 Oct 2020 14:31:11 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1603328312733/QEK_WIJNl.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://www.youtube.com/watch?v=-S8b_j5XBV0">https://www.youtube.com/watch?v=-S8b_j5XBV0</a></div>
<h2 id="javascripts-default-array-sort">JavaScript's Default Array Sort</h2>
<p>JavaScript's <code>Array.sort</code> defaults to a <code>String</code> sort. This catches many people off guard when attempting to sort an <code>Array</code> of type <code>Number</code>.</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// ❌ Default search is a String search</span>
<span class="hljs-keyword">const</span> numbers = [<span class="hljs-number">10</span>, <span class="hljs-number">1</span>, <span class="hljs-number">3</span>, <span class="hljs-number">15</span>]
numbers.sort() <span class="hljs-comment">// [ 1, 10, 15, 3 ]</span>
</code></pre>
<p>In the above example, each <code>Number</code> is converted to a <code>String</code> and then sorted using a <code>String</code> sort.</p>
<p>At first, this can seem like a WTF JavaScript moment, but this happens because an <code>Array</code> can contain mixed elements and JavaScript doesn't know how it should sort. So <code>sort</code> defaults to a <code>String</code> sort.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> array = [<span class="hljs-number">1</span>, <span class="hljs-number">2</span>, <span class="hljs-number">3</span>, <span class="hljs-string">'Joel'</span>, <span class="hljs-number">4</span>, { <span class="hljs-attr">userId</span>: <span class="hljs-number">123</span> }]
</code></pre>
<p>When we want something other than a <code>String</code> sort, we have to be explicit.</p>
<h2 id="custom-sort-compare-function">Custom Sort Compare Function</h2>
<p>Creating a custom sort compare function is pretty easy. The function takes two elements, then we return <code>-1</code> if the first is lower and <code>1</code> if it's higher. <code>0</code> for the same.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> compareFunction = <span class="hljs-function">(<span class="hljs-params">a, b</span>) =&gt;</span> {
    <span class="hljs-comment">// Pseudo Code</span>
    <span class="hljs-keyword">if</span> (a is less than b) <span class="hljs-keyword">return</span> <span class="hljs-number">-1</span>
    <span class="hljs-keyword">if</span> (a is more than b) <span class="hljs-keyword">return</span> <span class="hljs-number">1</span>
    <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>
}
</code></pre>
<p>Then pass that function to the <code>sort</code> method.</p>
<pre><code class="lang-javascript">myArray.sort(compareFunction)
</code></pre>
<p>This flexibility will allow us to be creative with our sorting algorithms.</p>
<h2 id="number-sort">Number Sort</h2>
<p>To sort a <code>Number</code> <code>Array</code> we could create a custom <code>compareNumbers</code> function and pass that into <code>Array.sort</code>.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> compareNumbers = <span class="hljs-function">(<span class="hljs-params">a, b</span>) =&gt;</span> a - b

<span class="hljs-keyword">const</span> numbers = [<span class="hljs-number">10</span>, <span class="hljs-number">1</span>, <span class="hljs-number">3</span>, <span class="hljs-number">15</span>]
numbers.sort(compareNumbers) <span class="hljs-comment">// [ 1, 3, 10, 15 ]</span>
</code></pre>
<h2 id="custom-object-sort">Custom Object Sort</h2>
<p>Let's say we had some data that looks like this:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> customers = [
    { <span class="hljs-attr">id</span>: <span class="hljs-number">1</span>, <span class="hljs-attr">orders</span>: [<span class="hljs-string">'a-1000'</span>, <span class="hljs-string">'x-2000'</span>, <span class="hljs-string">'c-8000'</span>] },
    { <span class="hljs-attr">id</span>: <span class="hljs-number">2</span>, <span class="hljs-attr">orders</span>: [<span class="hljs-string">'a-1010'</span>] },
    { <span class="hljs-attr">id</span>: <span class="hljs-number">3</span>, <span class="hljs-attr">orders</span>: [<span class="hljs-string">'a-1040'</span>, <span class="hljs-string">'c-8050'</span>] },
]
</code></pre>
<p>Our requirement is to sort by the number (<code>length</code>) of <code>orders</code>. So the order should be <code>2</code>, <code>3</code>, <code>1</code>.</p>
<p>We can do that with a custom <code>compareOrderLength</code> function that will sort by <code>customer.orders.length</code>.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> compareOrderLength = <span class="hljs-function">(<span class="hljs-params">a, b</span>) =&gt;</span> a.orders.length - b.orders.length

customers.sort(compareOrderLength)
<span class="hljs-comment">/**
 * [
 *   { id: 2, orders: [ 'a-1010' ] }, 
 *   { id: 3, orders: [ 'a-1040', 'c-8050' ] }, 
 *   { id: 1, orders: [ 'a-1000', 'x-2000', 'c-8000' ] }
 * ]
 */</span>
</code></pre>
<h2 id="complex-custom-sorting">Complex Custom Sorting</h2>
<p>I recently had a use case where an API was returning data that looked like this. </p>
<pre><code class="lang-javascript"><span class="hljs-comment">// API Response</span>
[<span class="hljs-string">"1"</span>, <span class="hljs-string">"10"</span>, <span class="hljs-string">"2"</span>, <span class="hljs-string">"BLA"</span>, <span class="hljs-string">"BLA2"</span>, <span class="hljs-string">"3"</span>]
</code></pre>
<p>The <code>Array</code> contained all <code>String</code> items, but the business wanted the items to display like "1, 2, 3, 10, BLA, BLA2".</p>
<p>That meant, I had to detect when the <code>String</code> was a <code>Number</code> and Sort the "numbers" first and the text after.</p>
<p>As complex as that sounds, the sort algorithm wasn't too bad.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> isNumeric = <span class="hljs-function">(<span class="hljs-params">num</span>) =&gt;</span> !<span class="hljs-built_in">isNaN</span>(num)

<span class="hljs-keyword">const</span> customCompare = <span class="hljs-function">(<span class="hljs-params">a, b</span>) =&gt;</span> {
    <span class="hljs-keyword">if</span> (isNumeric(a) &amp;&amp; !isNumeric(b)) <span class="hljs-keyword">return</span> <span class="hljs-number">-1</span>
    <span class="hljs-keyword">if</span> (!isNumeric(a) &amp;&amp; isNumeric(b)) <span class="hljs-keyword">return</span> <span class="hljs-number">1</span>
    <span class="hljs-keyword">if</span> (isNumeric(a) &amp;&amp; isNumeric(b)) <span class="hljs-keyword">return</span> a - b
    <span class="hljs-keyword">return</span> a &lt; b ? <span class="hljs-number">-1</span> : <span class="hljs-number">1</span>
}

<span class="hljs-comment">// [ '1', '2', '3', '10', 'BLA', 'BLA2' ]</span>
</code></pre>
<h2 id="end">End</h2>
<p>So just remember the default <code>Array</code> sort is a <code>String</code> sort. To sort by anything else, you must create a compare function and pass that into sort.</p>
<ul>
<li>Check out my 📰 <a target="_blank" href="https://joel.net/newsletter">Newsletter</a></li>
<li>Subscribe to my 📺 YouTube, <a target="_blank" href="https://www.youtube.com/c/JoelCodes?sub_confirmation=1">JoelCodes</a></li>
<li>Say hi to me on Twitter <a target="_blank" href="https://twitter.com/joelnet">@joelnet</a></li>
</ul>
<p>Cheers 🍻</p>
<p><span><a target="_blank" href="https://unsplash.com/photos/-nz-GTuvyBw">Photo</a> by <a href="https://unsplash.com/@kellysikkema?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Kelly Sikkema</a> on <a href="https://unsplash.com/s/photos/organize?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Unsplash</a></span></p>
]]></content:encoded></item><item><title><![CDATA[This Week in JavaScript by Joel 2020-10-16]]></title><description><![CDATA[https://www.youtube.com/watch?v=ZtyHrzOjJZE
Why Tailwind CSS

Why Tailwind CSS is an interesting piece by swyx making the case for Tailwind.
What makes this article interesting is that he started as a non-believer even making such remarks against it:...]]></description><link>https://joel.net/this-week-in-javascript-by-joel-2020-10-16</link><guid isPermaLink="true">https://joel.net/this-week-in-javascript-by-joel-2020-10-16</guid><category><![CDATA[JavaScript]]></category><dc:creator><![CDATA[Joel Thoms]]></dc:creator><pubDate>Fri, 16 Oct 2020 14:31:09 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1602833838821/pzna7suqf.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://www.youtube.com/watch?v=ZtyHrzOjJZE">https://www.youtube.com/watch?v=ZtyHrzOjJZE</a></div>
<h2 id="why-tailwind-css">Why Tailwind CSS</h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1602810831640/jE5JcsvHm.png" alt="TailwindCSS Logo" /></p>
<p><a target="_blank" href="https://dev.to/swyx/why-tailwind-css-2o8f">Why Tailwind CSS</a> is an interesting piece by <a target="_blank" href="http://twitter.com/swyx">swyx</a> making the case for Tailwind.</p>
<p>What makes this article interesting is that he started as a non-believer even making such remarks against it:</p>
<blockquote>
<p>Tailwind caused ugly unreadable classname soup and said zero-runtime CSS-in-JS could do more with a lower learning curve. -- <a target="_blank" href="https://dev.to/swyx/why-tailwind-css-2o8f">swyx</a></p>
</blockquote>
<p>After shipping a few projects with Tailwind, he's now done a full 180.</p>
<blockquote>
<p>I was wrong on 2 counts: Tailwind is easier to learn than I thought, and CSSinJS's flexibility can be a negative. -- <a target="_blank" href="https://dev.to/swyx/why-tailwind-css-2o8f">swyx</a></p>
</blockquote>
<p>From naysayer to believer, it's an interesting read if you are on the fence with Tailwind.</p>
<p>🔗 <a target="_blank" href="https://dev.to/swyx/why-tailwind-css-2o8f">Why Tailwind CSS</a></p>
<h2 id="testing-non-exported-functions-in-javascript">Testing Non-Exported Functions in JavaScript</h2>
<p><a target="_blank" href="https://dev.to/samanthaming/testing-non-exported-functions-in-javascript-29le"><img src="https://res.cloudinary.com/practicaldev/image/fetch/s--tTe9QDUV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/1e0c3ph55sg3plrc6pz9.jpg" alt="Testing Non-Exported Functions" /></a></p>
<p><a target="_blank" href="https://dev.to/samanthaming/testing-non-exported-functions-in-javascript-29le">Testing Non-Exported Functions in JavaScript</a> by <a target="_blank" href="https://twitter.com/samantha_ming">samantha_ming</a> solves the problem of testing non-exported functions.</p>
<p>Typically these function either go untested or become exported just for testing.</p>
<p>Now you can leave those functions un-exported and still run your test suite against them.</p>
<p>🔗 <a target="_blank" href="https://dev.to/samanthaming/testing-non-exported-functions-in-javascript-29le">Testing Non-Exported Functions in JavaScript</a></p>
<h2 id="rslint">RSLint</h2>
<p><a target="_blank" href="http://rslint.org">RSLint</a> is an extremely fast JavaScript linter written in Rust focusing on ease of use, customizability, and speed.</p>
<p>RSLint is still a Work in Process and <a target="_blank" href="https://eslint.org/">ESLint</a> is still the recommended linter for JavaScript, but this is still an interesting project that I'm tracking.</p>
<p>🔗 <a target="_blank" href="http://rslint.org">RSLint</a></p>
<h2 id="amplibox">Amplibox</h2>
<p>Amplibox is a Self Hosted File Storage App with AWS Amplify also by <a target="_blank" href="http://twitter.com/swyx">swyx</a>.</p>
<p><a target="_blank" href="(https://dev.to/swyx/amplibox-a-self-hosted-file-storage-app-with-aws-amplify-1f6g"><img src="https://res.cloudinary.com/practicaldev/image/fetch/s--u_7orSSp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/94xwh0o7wv8f2ovesmgs.gif" alt /></a></p>
<p>The demo looks incredible and it's pretty easy to setup.</p>
<p>🔗 <a target="_blank" href="https://dev.to/swyx/amplibox-a-self-hosted-file-storage-app-with-aws-amplify-1f6g">Amplibox</a></p>
<h2 id="remix">Remix</h2>
<p>Remix is at it again! This time with a coding tutorial from <a target="_blank" href="https://twitter.com/ryanflorence">Ryan Florence</a> on caching.</p>
<p>If you were ever curious about what an <code>etag</code> is, <code>cache-control</code> headers and how the browser reacts to these. Then I I would highly recommend watching this:</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://www.youtube.com/watch?v=3XkU_DXcgl0">https://www.youtube.com/watch?v=3XkU_DXcgl0</a></div>
<p>🔗 <a target="_blank" href="https://remix.run">Remix</a></p>
<h2 id="community-shout-out">Community Shout Out</h2>
<p><a target="_blank" href="https://devslike.us">Devs Like Us</a> is a brand spanking new YouTube channel with only 80 subscribers so far!</p>
<p><a target="_blank" href="https://www.youtube.com/channel/UCOQaCm4q93rCUtTMI-YsaLg"><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1602810071372/g8QV1e05i.png" alt="Screenshot of Devs Like Us YouTube Channel" /></a></p>
<p>They have got a bunch of Podcast style content and discuss topics like <a target="_blank" href="https://www.youtube.com/watch?v=czdf84eD0OY">Work Life Balance</a>, <a target="_blank" href="https://www.youtube.com/watch?v=Bm77Hdzdd7k">Self-Taught vs Bootcamps vs College</a>, etc.</p>
<p>Their content is fantastic and <strong>they deserve way more than 80 subs</strong>. So head over to their YouTube and hit that <a target="_blank" href="https://www.youtube.com/channel/UCOQaCm4q93rCUtTMI-YsaLg?sub_confirmation=1">SUBSCRIBE</a>!</p>
<p>📺 <a target="_blank" href="https://www.youtube.com/channel/UCOQaCm4q93rCUtTMI-YsaLg">Devs Like Us YouTube</a></p>
<p>🌐 <a target="_blank" href="https://devslike.us">https://devslike.us</a></p>
<h2 id="end">End</h2>
<ul>
<li>Check out my 📰 <a target="_blank" href="https://joel.net/newsletter">Newsletter</a></li>
<li>Subscribe to my 📺 YouTube, <a target="_blank" href="https://www.youtube.com/c/JoelCodes?sub_confirmation=1">JoelCodes</a></li>
<li>Say hi to me on Twitter <a target="_blank" href="https://twitter.com/joelnet">@joelnet</a></li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Subscribe to My Newsletter!]]></title><description><![CDATA[It's about time I finally setup a Newsletter! 🎉
So here it is, stay up to date with my 📰 latest articles or 📺 YouTube videos!

Be Sure To:

Check out my 📰 Newsletter
Subscribe to my 📺 YouTube, JoelCodes
Say hi to me on Twitter @joelnet

Photo by...]]></description><link>https://joel.net/newsletter</link><guid isPermaLink="true">https://joel.net/newsletter</guid><dc:creator><![CDATA[Joel Thoms]]></dc:creator><pubDate>Thu, 15 Oct 2020 06:01:14 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1602741931826/1GYq88DbT.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>It's about time I finally setup a Newsletter! 🎉</p>
<p>So here it is, stay up to date with my <a target="_blank" href="https://joel.net">📰 latest articles</a> or <a target="_blank" href="https://www.youtube.com/JoelCodes">📺 YouTube videos</a>!</p>
<p><a target="_blank" href="https://www.youtube.com/JoelCodes"><img src="https://cdn.joel.net/joel.net/images/joel-codes-youtube.png" alt="Screenshot of my YouTube Channel" /></a></p>
<h2 id="be-sure-to">Be Sure To:</h2>
<ul>
<li>Check out my 📰 <a target="_blank" href="https://joel.net/newsletter">Newsletter</a></li>
<li>Subscribe to my 📺 YouTube, <a target="_blank" href="https://www.youtube.com/c/JoelCodes?sub_confirmation=1">JoelCodes</a></li>
<li>Say hi to me on Twitter <a target="_blank" href="https://twitter.com/joelnet">@joelnet</a></li>
</ul>
<p>Photo by <a target="_blank" href="https://www.pexels.com/@jeremy-bishop-1260133?utm_content=attributionCopyText&amp;utm_medium=referral&amp;utm_source=pexels">Jeremy Bishop</a> from <a target="_blank" href="https://www.pexels.com/photo/burning-newspaper-3464799/?utm_content=attributionCopyText&amp;utm_medium=referral&amp;utm_source=pexels">Pexels</a></p>
]]></content:encoded></item></channel></rss>