<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Lindsay Wardell</title>
    <description>The latest articles on Forem by Lindsay Wardell (@lindsaykwardell).</description>
    <link>https://forem.com/lindsaykwardell</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F291844%2F328d539f-6cb6-4758-95c0-cd3e92c8bb6b.jpg</url>
      <title>Forem: Lindsay Wardell</title>
      <link>https://forem.com/lindsaykwardell</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/lindsaykwardell"/>
    <language>en</language>
    <item>
      <title>Integrating Mastodon with Astro</title>
      <dc:creator>Lindsay Wardell</dc:creator>
      <pubDate>Tue, 22 Nov 2022 19:47:18 +0000</pubDate>
      <link>https://forem.com/lindsaykwardell/integrating-mastodon-with-astro-4na6</link>
      <guid>https://forem.com/lindsaykwardell/integrating-mastodon-with-astro-4na6</guid>
      <description>&lt;p&gt;In case you have missed it, Twitter is going through a series of upheavals. This has led to a large number of folks leaving the platform for alternatives. I've been interested in &lt;a href="https://joinmastodon.org/" rel="noopener noreferrer"&gt;Mastodon&lt;/a&gt; as a potential alternative to Twitter for some time, especially since it's open source, but I was never able to make the jump. Now that it feels everyone is trying out alternative platforms, the number of folks I know on Mastodon has grown significantly, and I've been able to replicate a lot of what I was doing on Twitter.&lt;/p&gt;

&lt;p&gt;Before I dive in too far, let's talk about microblogging and federation as Mastodon describes it. For microblogging, the &lt;a href="https://docs.joinmastodon.org/" rel="noopener noreferrer"&gt;Mastodon docs&lt;/a&gt; say:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Similar to how blogging is the act of publishing updates to a website, microblogging is the act of publishing small updates to a stream of updates on your profile. You can publish text posts and optionally attach media such as pictures, audio, video, or polls. Mastodon lets you follow friends and discover new ones.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;For me, a microblog is a way to share what's going on or what I'm interested in with people in a less formal, more conversational way. I can talk about life, current events, things my kid says, or just share things that I found that interest me. Twitter has been my microblogging platform for the past few years, while I write my more longform blog posts here on my site.&lt;/p&gt;

&lt;p&gt;How does Mastodon define federation?&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Federation is a form of decentralization. Instead of a single central service that all people use, there are multiple services, that any number of people can use.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Twitter is very centralized; there's only one "instance", and you can't communicate from Twitter to other platforms. Federated platforms, on the other hand, allow for multiple providers to communicate together in a standardized way. The easiest comparison is email, which is another federated system. Different email providers establish rules and filter content as they want, and allow you to interact with other email providers beyond what they host. Mastodon is similar, in that each instance has its own moderators and rules, and allows for interoperation between other Mastodon instances.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Note before we keep going: This post is not intended as a pitch for switching to Mastodon. I know it's not for everyone. But I'm really liking it.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;One of the really neat things about Mastodon is that you can host your own instance, and have it federate with the rest of Mastodon. This allows you to control your own data, and host on your own site. This sounds really cool! It'd be neat to host my own social media account under a URL that I own, it seems like an easy way to verify myself. However, I switched to using a static site (specifically using Astro) specifically because I didn't want to pay monthly for a server. It didn't make sense to start paying just to have an account on social media, so I set the idea aside.&lt;/p&gt;

&lt;h2&gt;
  
  
  Integrating to the Fediverse
&lt;/h2&gt;

&lt;p&gt;Later, I read an article by &lt;a href="https://mastodon.online/@maartenballiauw" rel="noopener noreferrer"&gt;Maarten Balliauw&lt;/a&gt; titled "&lt;a href="https://blog.maartenballiauw.be/post/2022/11/05/mastodon-own-donain-without-hosting-server.html" rel="noopener noreferrer"&gt;Mastodon on your own domain without a server&lt;/a&gt;", in which he described using the &lt;a href="https://webfinger.net/" rel="noopener noreferrer"&gt;WebFinger&lt;/a&gt; spec to inform Mastodon of your account. I highly recommend reading the whole article, but in short, Mastodon is looking for a resource at &lt;code&gt;/.well-known/webfinger&lt;/code&gt; that returns JSON in a specific structure. Mastodon is passing a query parameter to specify the account, but if you're hosting your own site, you can ignore the parameter and just return a JSON structure for your account. This sounded interesting, and I wanted to give it a shot!&lt;/p&gt;

&lt;p&gt;My site is built on Astro, and is hosted with Netlify. To meet the requirement for the endpoint, I created an endpoint at &lt;code&gt;/.well-known/webfinger.json&lt;/code&gt;, which just includes the following code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;acct:lindsaykwardell@mastodon.social&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;aliases&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://mastodon.social/@lindsaykwardell&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://mastodon.social/users/lindsaykwardell&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="na"&gt;links&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;rel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;http://webfinger.net/rel/profile-page&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;text/html&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://mastodon.social/@lindsaykwardell&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;rel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;self&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/activity+json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://mastodon.social/users/lindsaykwardell&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;rel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;http://ostatus.org/schema/1.0/subscribe&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://mastodon.social/authorize_interaction?uri={uri}&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, in my Netlify config (&lt;code&gt;netlify.toml&lt;/code&gt;), I added a redirect:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="nn"&gt;[[redirects]]&lt;/span&gt;
  &lt;span class="py"&gt;from&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"/.well-known/webfinger"&lt;/span&gt;
  &lt;span class="py"&gt;to&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"/.well-known/webfinger.json"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With that, searching for &lt;code&gt;@lindsay@lindsaykwardell.com&lt;/code&gt; properly returns my account on mastodon.social!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkatf683j9n2inub5wb2r.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkatf683j9n2inub5wb2r.png" alt="Screenshot of Mastodon search with my custom domain as the query and my account on mastodon.social as the result" width="584" height="444"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Awesome! Now people can look me up by my own URL, and I don't have to host a Mastodon instance to do it! However, this communication is only one way; I can tell the Fediverse who I am, but I don't have a page on my site with any content. How can we fix that?&lt;/p&gt;

&lt;h2&gt;
  
  
  Bringing the Fediverse Home
&lt;/h2&gt;

&lt;p&gt;Because Mastodon is built to interoperate with other systems, it has a powerful, public API that can be used to fetch data about users and their posts. In addition, it offers multiple ways to subscribe to any user's posts, including an RSS feed. You can subscribe to anyone on Mastodon via RSS by appending &lt;code&gt;.rss&lt;/code&gt; to their username (feel free to follow me at &lt;a href="https://mastodon.social/@lindsaykwardell.rss" rel="noopener noreferrer"&gt;https://mastodon.social/@lindsaykwardell.rss&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;I have a custom username that can be found on Mastodon, but what I was missing was a page to actually see my content on my site. On Mastodon, the username &lt;code&gt;@lindsaykwardell@mastodon.social&lt;/code&gt; can be found at mastodon.social/&lt;a class="mentioned-user" href="https://dev.to/lindsaykwardell"&gt;@lindsaykwardell&lt;/a&gt;. However, lindsaykwardell.com/&lt;a class="mentioned-user" href="https://dev.to/lindsay"&gt;@lindsay&lt;/a&gt; didn't exist, so I decided to fix that!&lt;/p&gt;

&lt;p&gt;Again, using Astro, I created a page (&lt;code&gt;/@lindsay.astro&lt;/code&gt;) that would fetch the content of my RSS feed and then render it. I created a separate template for my site that would also fetch my Mastodon profile and banner images, description, follower counts, and other details, and then rendered it out using static HTML.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;---
import MicroblogPost from "../layouts/MicroblogPost.astro";
import MicroPost from "../components/MicroPost.astro";
import Parser from 'rss-parser';

const feedUrl = "https://mastodon.social/@lindsaykwardell.rss"
const parser = new Parser({
  customFields: {
    item: [ ['media:content', 'attachments', { keepArray: true} ]]
  }
})

const feed = await parser.parseURL(feedUrl);

const content = {
  title: feed.title,
  snippet: "Public posts from @lindsay@lindsaykwardell.com",
  slug: "/@lindsay"
}
---
&amp;lt;MicroblogPost content={content}&amp;gt;
  {feed.items.map(item =&amp;gt; (
    &amp;lt;MicroPost 
      avatar={feed.image.url}
      displayName={feed.title}
      content={item.content}
      createdAt={item.pubDate}
      url={item.link}
      attachments={item.attachments?.map(attachment =&amp;gt; attachment['$'])}
    /&amp;gt;
  ))}
&amp;lt;/MicroblogPost&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this file, I'm importing the layout (&lt;code&gt;MicroblogPost&lt;/code&gt;) and a component (&lt;code&gt;MicroPost&lt;/code&gt;), as well as &lt;a href="https://www.npmjs.com/package/rss-parser" rel="noopener noreferrer"&gt;rss-parser&lt;/a&gt;. I then created a parser, and added the custom field for media content (such as images). I then fetch the feed content, parse it, and pass it into the site template and components. With that, I can now see my Mastodon content within my own site!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe9116zxy3dk79bxo88tn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe9116zxy3dk79bxo88tn.png" alt="The landing page for my Mastodon microblog content on my own website" width="800" height="677"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I then took it a step further, and created an individual page for each post. On sites like Twitter and Mastodon, you can link directly to a post (such as &lt;a href="https://twitter.com/lindsaykwardell/status/1583481321330200577" rel="noopener noreferrer"&gt;https://twitter.com/lindsaykwardell/status/1583481321330200577&lt;/a&gt; or &lt;a href="https://mastodon.social/@lindsaykwardell/109248202243508025" rel="noopener noreferrer"&gt;https://mastodon.social/@lindsaykwardell/109248202243508025&lt;/a&gt;). It made sense that if I was trying to recreate the interface of a microblog on my own site, I would want to do the same.&lt;/p&gt;

&lt;p&gt;This required using &lt;a href="https://docs.astro.build/en/core-concepts/routing/#server-ssr-mode" rel="noopener noreferrer"&gt;Astro's dynamic routing&lt;/a&gt; and enabling &lt;a href="https://docs.astro.build/en/guides/server-side-rendering/" rel="noopener noreferrer"&gt;SSR mode&lt;/a&gt;, but the result is that any of my Mastodon posts can be viwed on my personal site! Here's the code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;---
import MicroblogPost from "../../layouts/MicroblogPost.astro";
import MicroPost from "../../components/MicroPost.astro";

const { id } = Astro.params;

const data = await fetch(`https://mastodon.social/api/v1/statuses/${id}`).then(res =&amp;gt; res.json())

const name = data.account.display_name
const post = data.content.replace(/&amp;lt;[^&amp;gt;]*&amp;gt;?/gm, '');
let snippet = post.substring(0,30);

if (snippet.length === 30) {
  snippet += "..."
}

const card = data.card;

const content = {
  title: name + ": " + snippet,
  snippet: post,
  image: data.media_attachments[0]?.url,
  slug: `/@lindsay/${id}`,
}
---
&amp;lt;MicroblogPost content={content}&amp;gt;
  &amp;lt;MicroPost
    avatar={data.account.avatar}
    url={data.url}
    username={data.account.username}
    userPage={data.account.url}
    createdAt={data.created_at}
    displayName={data.account.display_name}
    content={data.content}
    attachments={data.media_attachments}
    card={card}
    postStats={{
      repliesCount: data.replies_count,
      reblogsCount: data.reblogs_count,
      favouritesCount: data.favourites_count,
    }}
  /&amp;gt;
&amp;lt;/MicroblogPost&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Rather than fetching from the RSS feed, I use Mastodon's public API to fetch via ID. I then pass the data for the post into the &lt;code&gt;MicroPost&lt;/code&gt; component, including a bit more metadata than the RSS feed gives us. With that, we can now load individual Mastodon posts directly into the site! Go to &lt;a href="https://dev.to/@lindsay/109248202243508025"&gt;this post about my ViteConf talk&lt;/a&gt; to see!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjxzth10kdrbtg515014a.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjxzth10kdrbtg515014a.png" alt="A post about my ViteConf talk on using Elm in Vite, displayed on my website" width="800" height="874"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;One thing to note about using the API in this way, because we are just fetching data by ID we could load a post that I didn't write. I'm not too worried about it, as the correct user's name and profile image will be displayed in the post contents, but it is a consideration to keep in mind.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;I'm sure there is more that can be done to integrate my site with the Fediverse, but this already feels really nice. Future changes could lead to better integrations with Mastodon, such as following or favoriting things from my site rather than having to go to Mastodon, but it's not a huge issue at the moment.&lt;/p&gt;

&lt;p&gt;While Twitter may be going through some major struggles, this feels like a good way to keep up interaction with the dev community and integrate it more with my own site in a way that Twitter never allowed. And that, at least, is worth it.&lt;/p&gt;

&lt;p&gt;If you're interested in how this turned out, check out &lt;a href="https://lindsaykwardell.com/@lindsay" rel="noopener noreferrer"&gt;lindsaykwardell.com/@lindsay&lt;/a&gt;!&lt;/p&gt;

</description>
      <category>astro</category>
      <category>javascript</category>
      <category>mastodon</category>
      <category>ssr</category>
    </item>
    <item>
      <title>Utilizing Native Dialog in Elm</title>
      <dc:creator>Lindsay Wardell</dc:creator>
      <pubDate>Mon, 09 May 2022 23:30:54 +0000</pubDate>
      <link>https://forem.com/lindsaykwardell/utilizing-native-dialog-in-elm-96n</link>
      <guid>https://forem.com/lindsaykwardell/utilizing-native-dialog-in-elm-96n</guid>
      <description>&lt;p&gt;A common design pattern when building client-side applications is the need to display a modal or dialog on top of the main page content. It may be a login form, a "What's New" style notification, a feedback input, or any number of other possible designs. Modals are incredibly tricky to program by hand in a way that provides a friendly and accessible experience. The W3C provides &lt;a href="https://www.w3.org/TR/wai-aria-practices/#dialog_modal" rel="noopener noreferrer"&gt;a list of reccomendations and guidelines for creating dialog interfaces&lt;/a&gt;, which includes a series of recommendations and guidelines on how to implement a dialog.&lt;/p&gt;

&lt;p&gt;From the above documentation, here's the definition of a dialog:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A dialog is a window overlaid on either the primary window or another dialog window. Windows under a modal dialog are inert. That is, users cannot interact with content outside an active dialog window. Inert content outside an active dialog is typically visually obscured or dimmed so it is difficult to discern, and in some implementations, attempts to interact with the inert content cause the dialog to close.&lt;/p&gt;

&lt;p&gt;Like non-modal dialogs, modal dialogs contain their tab sequence. That is, Tab and Shift + Tab do not move focus outside the dialog. However, unlike most non-modal dialogs, modal dialogs do not provide means for moving keyboard focus outside the dialog window without closing the dialog.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It's fairly simple to create the basic outline of a dialog interface: render a box or card in the center of the screen, provide a fade-out overlay for the background content (ideally one that can be clicked to close the box), and prevent global scrolling. However, in order to meet the requirements as outlined by the W3C, we also need to prevent tabbing in the background application, set focus on a button that can close the dialog, and restore focus to the element that triggered the dialog after it closes. Suddenly things feel a lot more complicated!&lt;/p&gt;

&lt;p&gt;Thankfully, there is a browser native way to implement dialogs that covers (most of) the cases above, and provides straightforward ways to handle the rest: &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/dialog" rel="noopener noreferrer"&gt;the &lt;code&gt;&amp;lt;dialog&amp;gt;&lt;/code&gt; element!&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What is &lt;code&gt;&amp;lt;dialog&amp;gt;&lt;/code&gt;?
&lt;/h2&gt;

&lt;p&gt;From the MDN docs:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The &lt;code&gt;&amp;lt;dialog&amp;gt;&lt;/code&gt; HTML element represents a dialog box or other interactive component, such as a dismissible alert, inspector, or subwindow.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;code&gt;&amp;lt;dialog&amp;gt;&lt;/code&gt; is a browser-based way to create dialog interfaces. It's a newer HTML element, which only recently got support from all major browsers (Safari 15.4 was the last one, released in March 2022). According to caniuse, it's now supported across the board (except in IE11, which is expected):&lt;/p&gt;

&lt;p&gt;&lt;a href="https://caniuse.com/dialog" rel="noopener noreferrer"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--cvbaHevj--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://d33wubrfki0l68.cloudfront.net/4018d4a838cc598cb253924992e36632b8ed4da4/84113/blog/dialog-caniuse.png" alt="Screenshot of caniuse information on the dialog element." width="800" height="422"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In short, a dialog element is just like any other HTML element, and can be written like this (try it out!):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;html&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;title&amp;gt;&lt;/span&gt;Dialog example&lt;span class="nt"&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;Dialogs are awesome!&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"open-dialog"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Open dialog&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;dialog&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;h2&amp;gt;&lt;/span&gt;My dialog&lt;span class="nt"&gt;&amp;lt;/h2&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;button&amp;gt;&lt;/span&gt;Close&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/dialog&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"script.js"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;open-dialog&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;click&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dialog&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;showModal&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dialog &amp;gt; button&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;click&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dialog&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Some cool things to note here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You get a basic style for the dialog box and the background (which are all targetable by CSS and can be tweaked as desired).&lt;/li&gt;
&lt;li&gt;Opening the dialog sets focus to the close button.&lt;/li&gt;
&lt;li&gt;Closing the dialog sets focus back to the open button.&lt;/li&gt;
&lt;li&gt;Tabbing is diabled outside of the dialog when it's open.&lt;/li&gt;
&lt;li&gt;You can also press the escape key to close the dialog.&lt;/li&gt;
&lt;li&gt;If you're using a screen reader, such as VoiceOver, it should announce that focus has shifted between the open and close buttons.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That's a lot of free benefits for using the built-in element! Of course, some of the functionality here is reliant on Javascript. In this case, we set two event listeners, one for clicking the open button and another for clicking the close button. In order to properly toggle the dialog, we need to call a method on the dialog element itself. To open the dialog, we call &lt;code&gt;showModal&lt;/code&gt;, and to close it we call &lt;code&gt;close&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  The &lt;code&gt;open&lt;/code&gt; Attribute
&lt;/h3&gt;

&lt;p&gt;But wait! The MDN docs list a different way to display the dialog as open: &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/dialog#attr-open" rel="noopener noreferrer"&gt;the &lt;code&gt;open&lt;/code&gt; attribute&lt;/a&gt;!&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Indicates that the dialog is active and can be interacted with. When the open attribute is not set, the dialog shouldn't be shown to the user. It is recommended to use the .show() or .showModal() methods to render dialogs, rather than the open attribute.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;As Elm developers, it might be tempting to go down this path and set the attribute. However, doing this does &lt;em&gt;not&lt;/em&gt; provide the additional benefits of updating focus, blocking the background from input, etc. To get the full benefit of the dialog element, we need to call &lt;code&gt;showModal&lt;/code&gt;. Note that &lt;code&gt;show&lt;/code&gt; is also an option, but it does basically the same thing as &lt;code&gt;open&lt;/code&gt;, and is not recommended.&lt;/p&gt;

&lt;p&gt;We can, however, check whether the dialog is visible at the moment by looking at &lt;code&gt;open&lt;/code&gt;. When we call &lt;code&gt;showModal&lt;/code&gt;, &lt;code&gt;open&lt;/code&gt; is set to &lt;code&gt;true&lt;/code&gt;, which means that rather than have distinct calls to open and the close the dialog, we could choose to have a simple toggle function, which internally checks whether the dialog is open.&lt;/p&gt;

&lt;h2&gt;
  
  
  Implementing &lt;code&gt;&amp;lt;dialog&amp;gt;&lt;/code&gt; in Elm
&lt;/h2&gt;

&lt;p&gt;Now that we understand what the dialog element provides us, and what is required to make it useful, let's start working with it in Elm. Elm provides a number of HTML elements in its &lt;code&gt;Html&lt;/code&gt; module, but since the dialog element is newer, we first need to define it ourselves.&lt;/p&gt;

&lt;h3&gt;
  
  
  Rendering the dialog
&lt;/h3&gt;

&lt;p&gt;In order to define a custom element, we need to import &lt;code&gt;Html&lt;/code&gt;, then we can use &lt;code&gt;Html.node&lt;/code&gt; to generate a custom element. As with any HTML element in Elm, it takes two arguments (a list of attributes and a list of HTML), but it also takes a string to use as the custom element's tag. Here's a basic example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elm"&gt;&lt;code&gt;&lt;span class="n"&gt;dialog&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;List&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Html&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="kt"&gt;Attribute&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;List&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Html&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;Html&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt;
&lt;span class="n"&gt;dialog&lt;/span&gt; &lt;span class="n"&gt;attr&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
    &lt;span class="kt"&gt;Html&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt; &lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;dialog"&lt;/span&gt; &lt;span class="n"&gt;attr&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This provides a basic interface for a dialog element. However, we're going to need to trigger it's &lt;code&gt;showModal&lt;/code&gt; and &lt;code&gt;close&lt;/code&gt; methods, so it's probably helpful to require an ID. Let's just add the ID as part of the function signature:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elm"&gt;&lt;code&gt;&lt;span class="n"&gt;dialog&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;List&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Html&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="kt"&gt;Attribute&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;List&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Html&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;Html&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt;
&lt;span class="n"&gt;dialog&lt;/span&gt; &lt;span class="n"&gt;elementId&lt;/span&gt; &lt;span class="n"&gt;attr&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
    &lt;span class="kt"&gt;Html&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt; &lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;dialog"&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="n"&gt;elementId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;::&lt;/span&gt; &lt;span class="n"&gt;attr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Great! Now the ID will be set by the first argument, and the rest of the HTML element signature is the same that other elements use. At this point, you can start rendering the dialog on the page, but it won't appear (unless you set the &lt;code&gt;open&lt;/code&gt; attribute). We need a way to trigger the methods provided in Javascript.&lt;/p&gt;

&lt;h3&gt;
  
  
  Triggering the dialog
&lt;/h3&gt;

&lt;p&gt;Elm provides &lt;a href="https://guide.elm-lang.org/interop/ports.html" rel="noopener noreferrer"&gt;ports&lt;/a&gt; as the go-to solution for interacting between Javascript and Elm. We need to utilize a port to call out to Javascript in order to trigger the dialog to appear. Let's define our port below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elm"&gt;&lt;code&gt;&lt;span class="k"&gt;port&lt;/span&gt; &lt;span class="n"&gt;toggleDialog&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;Cmd&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When our Elm application is compiled, &lt;code&gt;toggleDialog&lt;/code&gt; will be available to subscribe to from Javascript. In this case, it provides a string (the dialog's ID) that we can use to select our element and perform the required action on it. Here's an example of our Javascript code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ports&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;toggleDialog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;dialog&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`#&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dialog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;open&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;dialog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;close&lt;/span&gt;&lt;span class="p"&gt;.();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;dialog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;showModal&lt;/span&gt;&lt;span class="p"&gt;.();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a pretty simple snippet of Javascript. We take the provided ID, find the element, and check whether it's open. If it is, then we close it, otherwise we open it. If you want, you could add an additional port for Javascript to inform Elm of the current status of the dialog, but it's not required.&lt;/p&gt;

&lt;p&gt;Here's an &lt;a href="https://github.com/lindsaykwardell/elm-dialog-example" rel="noopener noreferrer"&gt;example repository&lt;/a&gt; showcasing the dialog element, and here's a &lt;a href="https://elm-dialog-example.netlify.app/" rel="noopener noreferrer"&gt;live demo&lt;/a&gt; of that repository. Check it out!&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;The dialog element provides a lot of benefits for developers needing to utilize this UI pattern. However, it's important to note that just because it provides a large number of benefits and accessibility support built-in, it still requires planning and testing to properly implement a dialog that is usable by all the visitors to your site. Make sure that you and your team are performing regular a11y testing, so you aren't leaving any of your users out of the loop with what your site is showing them!&lt;/p&gt;

&lt;p&gt;Also, keep in mind that it is a fairly new element, and you may have users that are not supported yet. If you still have a large number of users that don't have access to the dialog element, consider using an existing alternative or providing an alternative userflow.&lt;/p&gt;

&lt;p&gt;That said, it's very exciting to see what the future of HTML looks like, and I'm looking forward to more elements like this that are built-in and provide a lot of value to both users and developers. Try it out, and see if it works for you today!&lt;/p&gt;

</description>
      <category>html</category>
      <category>elm</category>
      <category>dialog</category>
      <category>a11y</category>
    </item>
    <item>
      <title>Shipping Side Projects</title>
      <dc:creator>Lindsay Wardell</dc:creator>
      <pubDate>Fri, 25 Mar 2022 04:09:27 +0000</pubDate>
      <link>https://forem.com/lindsaykwardell/shipping-side-projects-3ek1</link>
      <guid>https://forem.com/lindsaykwardell/shipping-side-projects-3ek1</guid>
      <description>&lt;p&gt;It's late at night. Everyone else has gone to bed, but you're up just a bit later. Your fingers are flying across the keys, your brain fighting against exhaustion but flowing with great ideas. In front of you, your laptop's fans hum gently as your side project's local environment runs in the terminal. Constantly switching between your IDE and the browser, you watch as your ideas come to life before you. It's truly a magical moment. Just one more feature, and it'll be ready to ship...&lt;/p&gt;

&lt;p&gt;A week or two later, your app is still half finished, but the drive you felt that night has faded. New ideas have started to form in your mind, unrelated to what you were working on previously. You spun up a new Git repo, and you're almost ready to buy a new domain name. It'll fit nicely next to dozen or so others you've already purchased. One day, you'll ship all of them. At least, that's the plan.&lt;/p&gt;

&lt;p&gt;Side projects get a lot of attention from developers. It's common to visit someone's website or Github page and find multiple projects on display, all in various stages of completion or function. On Twitter, I regularly see developers talking about their side projects, often lamenting the fact that they have so many and feeling guilting that they aren't being released or finished. It's especially common to joke about the number of domains that we all have purchased but never used. At some point, I think we as developers get depressed and frustrated because of all the things we started with the best of intentions, but haven't completed.&lt;/p&gt;

&lt;p&gt;Rather than stress ourselves out about the amount of work waiting for us when we get off work, I think it's useful to put side projects into context: what they are, what they are for, and who benefits from them. &lt;/p&gt;

&lt;h2&gt;
  
  
  The Purpose of Side Projects
&lt;/h2&gt;

&lt;p&gt;There are lots of reasons for starting a new side project. A new framework could have been released that has some new ideas, or you want to try a new language or library. You could be trying out some new techniques, or just want to explore an idea or two that you created. Before you start a side project, it's important to understand why you're doing it. What do you want to get out of doing this project?&lt;/p&gt;

&lt;p&gt;When I was starting to learn React, I needed a project that I felt was interesting. Rather than build a todo list or something else, I came up with a basic turn-based strategy game. It was a lot of fun, and let me explore a lot of advanced concepts in React. After many weeks of exploring this idea, the game was in a working state, but had a long way to go to be "complete".&lt;/p&gt;

&lt;p&gt;Rather than continuing to work on it, however, I set it aside. Why? Because my goal (learing React) was complete, and I was ready to move on to learning somethign else. While it would have been cool to add all the features I had thought up, continuing to work on one project wasn't worth it. I've since come back to this concept multiple times in different frameworks and languages, the latest version being in Elm. You can &lt;a href="https://juralen.lindsaykwardell.com" rel="noopener noreferrer"&gt;play it here&lt;/a&gt;!&lt;/p&gt;

&lt;h2&gt;
  
  
  You Are the Stakeholder
&lt;/h2&gt;

&lt;p&gt;When working on a professional project, either freelancing or as an employee, there are a number of stakeholders that are invested in the work you're doing and the end result of that work. This can lead to working on things that you aren't interested in, or are outright opposed to building.&lt;/p&gt;

&lt;p&gt;For side projects, you are the stakeholder! And often, you're also the only stakeholder, which means that you get to decide what to build, how to build it, and, most importantly, when to work on it. Too often, we push ourselves to work on projects just because we started them, or out of feelings of guilt that we can't complete something. It is definitely useful to have completed, polished examples of your work for a portfolio, but not all side projects belong in that category.&lt;/p&gt;

&lt;p&gt;At work, stakeholders help establish which projects have priority. As the stakeholder for your side projects, don't feel guilty about moving onto something else, or deprioritizing certain projects. There's no need to feel bad about side projects, they're fun!&lt;/p&gt;

&lt;p&gt;When I feel like I'm not interested in a side project any more, rather than say I've abandoned it, I ship it. It may not be "feature-complete" to everything that I planned, but neither is any production application.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Friendly reminder, nobody is forcing you to work on side projects. If you don't want to work on it any more, then congrats! It just reached MVP. Go do something you enjoy.&lt;/p&gt;— Lindsay Wardell 🏳️‍⚧️ (&lt;a class="mentioned-user" href="https://dev.to/lindsaykwardell"&gt;@lindsaykwardell&lt;/a&gt;) &lt;a href="https://twitter.com/lindsaykwardell/status/1494702474896105474?ref_src=twsrc%5Etfw" rel="noopener noreferrer"&gt;February 18, 2022&lt;/a&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Scoping Side Projects
&lt;/h2&gt;

&lt;p&gt;One of the big draws to technology is this thought that we can build a startup as a side project, and turn it into our main job. While there are a number of successes with this approach, this doesn't apply to every side project! Not every todo app needs to become Todoist. Not every budgeting app needs to turn into You Need A Budget. Just like at work, it's important to scope our side projects, so that we understand exactly what a given project should and should not become.&lt;/p&gt;

&lt;p&gt;When I'm working on a side project, I try to put them into one of three categories - learning, maintained, and "slow cooker".&lt;/p&gt;

&lt;h3&gt;
  
  
  Learning Projects
&lt;/h3&gt;

&lt;p&gt;Learning projects are experiments, with a goal of exploring an idea or learning a new language/framework/library/etc. These may be created to follow along with a tutorial, or just to see if something works in the way that I expect. One of the hallmarks of these projects is that I don't expect it to last longer than a week or two, and will then be abandoned.&lt;/p&gt;

&lt;p&gt;Besides learning and exploration, these projects allow you to plant seeds of knowledge. Sometimes I will experiment with something, abandon it, then later realize that I need to do the same thing I was experimenting with. Rather than start everything from scratch, I can often copy/paste from one of these learning projects, allowing me to move faster and build on my previous learning.&lt;/p&gt;

&lt;p&gt;Examples of learning projects include todo apps, using a public API such as the Pokemon API or the Star Wars GraphQL API, or spinning up and toying with a new framework.&lt;/p&gt;

&lt;h3&gt;
  
  
  Maintained Projects
&lt;/h3&gt;

&lt;p&gt;Maintained projects are created with the goal of becoming a long-running project. Ideally these projects are clean and maintained, and typically small (but not always). When I want to do something to improve or change them, it doesn't take much to spin up a local environment, even if it's been awhile. I may point at these projects as examples of things I have done, such as in a portfolio.&lt;/p&gt;

&lt;p&gt;The key identifier for a maintained project is that I expect to be coming back to it repeatedly over a long period of time, but is small enough to be finished in a reasonable amount of time.&lt;/p&gt;

&lt;p&gt;Examples of maintained projects include my blog, small open source projects, and finished sites/applications that are used by folks on a day-to-day basis.&lt;/p&gt;

&lt;h3&gt;
  
  
  Slow Cooker Projects
&lt;/h3&gt;

&lt;p&gt;Slow cooker projects are similar to maintained projects, except they may not have a polished, final product in the near future. These projects are the playgrounds of ambition, where the drive to build something new and make a difference somehow. This is where startup-level projects live. It takes time to build something like this, and it's important to recognize that. Just like using a slow cooker takes hours to make a meal, building a large application, framework, or whatever your idea is will take time. Don't beat yourself up when something like this doesn't result in a full-on startup after a few months, or even longer.&lt;/p&gt;

&lt;p&gt;For me, slow cooker projects are incredibly exciting, but also incredibly draining. There's so much going on in life already, and there's only so much room for large-scale projects like this. When I'm working on a project like this, I don't limit my imagination or scope, but I allow myself to come and go from the project as inspiration strikes. If I'm not excited about working on a side project, then I set it aside, and wait for inspiration to come back. My goal with slow cooker projects is to enjoy the process, and hopefully have a finished product that I can be proud of.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Side projects are the first drafts of writing, the tuning session of music, and the experimentation phase of science. They provide us a fast feedback loop, and give us the opportunity to try out new ideas and concepts. They serve a crucial role in becoming a developer, and building up or maintaining our skills. &lt;/p&gt;

&lt;p&gt;The value of side projects is in how much value they give to us, the developers. Next time you're feeling burdened with all the side projects you've never completed, remind yourself that they all served their purpose. The only person you owe to work on side projects is yourself. Don't feel like working on it? You just reached version 1.0. Congratulations!&lt;/p&gt;

&lt;p&gt;Enjoy your next side project!&lt;/p&gt;

</description>
      <category>productivity</category>
      <category>webdev</category>
      <category>career</category>
      <category>sideprojects</category>
    </item>
    <item>
      <title>Utilizing Elm in a Web Worker</title>
      <dc:creator>Lindsay Wardell</dc:creator>
      <pubDate>Fri, 18 Feb 2022 01:17:59 +0000</pubDate>
      <link>https://forem.com/lindsaykwardell/utilizing-elm-in-a-web-worker-3jeg</link>
      <guid>https://forem.com/lindsaykwardell/utilizing-elm-in-a-web-worker-3jeg</guid>
      <description>&lt;p&gt;The Elm programming language is a great way to model and write a modern web application. By utilizing functional programming and a strong type system, Elm encourages developers to build applications that are more reliable and more easily maintained. But as a compile-to-Javascript language, there is only so much that Elm can offer by default. Any tasks that require large computations in Javascript will, unfortunately, require the same computations in Elm. Such large tasks can block the main thread in browsers, causing visual issues and a non-responsive UI. Obviously this is not what we want for our users, so what can we do?&lt;/p&gt;

&lt;p&gt;Enter &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API" rel="noopener noreferrer"&gt;Web Workers&lt;/a&gt;. From MDN: &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Web Workers&lt;/strong&gt; makes it possible to run a script operation in a background thread separate from the main execution thread of a web application. The advantage of this is that laborious processing can be performed in a separate thread, allowing the main (usually the UI) thread to run without being blocked/slowed down.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Web Workers are a way that browser-based applications can move certain tasks out of the main thread, into their own environment. Web Workers have a number of restrictions to them, such as not being able to access the DOM, but they do have the ability to make HTTP requests via &lt;code&gt;fetch&lt;/code&gt; as well as run standard Javascript code. Since Elm is a compile-to-JS language, that means that we can mount an Elm app within the Web Worker as well!&lt;/p&gt;

&lt;p&gt;Let's explore what it would look like to use Elm inside of a Web Worker. We'll look at two ways to do it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Using vanilla JS, no bundlers or frameworks beyond what Elm provides.&lt;/li&gt;
&lt;li&gt;Incorporating these techniques into Vite, which provides a helpful wrapper around the Web Worker API.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Writing our Elm Modules
&lt;/h2&gt;

&lt;p&gt;To start, let's set up a basic setup to work with. In a new folder, run &lt;code&gt;elm init&lt;/code&gt;, which generates our base elm.json and a &lt;code&gt;src&lt;/code&gt; folder. Within &lt;code&gt;src&lt;/code&gt;, create two files: &lt;code&gt;Main.elm&lt;/code&gt; and &lt;code&gt;Worker.elm&lt;/code&gt;. We'll fill these in shortly. Let's also create an &lt;code&gt;index.html&lt;/code&gt; at the root of our working direction (we'll come back to it later).&lt;/p&gt;

&lt;p&gt;First, let's set up a very basic &lt;code&gt;Main.elm&lt;/code&gt; file. While Web Workers are primarily useful for large tasks, for this example we're going to keep things simple for our examples. In our main file, we'll implement a basic counter example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elm"&gt;&lt;code&gt;&lt;span class="k"&gt;port&lt;/span&gt; &lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="kt"&gt;Main&lt;/span&gt; &lt;span class="k"&gt;exposing&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="kt"&gt;Browser&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="kt"&gt;Html&lt;/span&gt; &lt;span class="k"&gt;exposing&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Html&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;button&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;div&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="kt"&gt;Html&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="kt"&gt;Events&lt;/span&gt; &lt;span class="k"&gt;exposing&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="n"&gt;init&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Int&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;Cmd&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;init&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;Cmd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;none&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="kt"&gt;Msg&lt;/span&gt;
    &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;Increment&lt;/span&gt;
    &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kt"&gt;Decrement&lt;/span&gt;
    &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kt"&gt;Set&lt;/span&gt; &lt;span class="kt"&gt;Int&lt;/span&gt;


&lt;span class="n"&gt;update&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Msg&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;Int&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="kt"&gt;Int&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;Cmd&lt;/span&gt; &lt;span class="kt"&gt;Msg&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;update&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt;
        &lt;span class="kt"&gt;Increment&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;increment&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="kt"&gt;Decrement&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;decrement&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="kt"&gt;Set&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;Cmd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;none&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="n"&gt;view&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Int&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;Html&lt;/span&gt; &lt;span class="kt"&gt;Msg&lt;/span&gt;
&lt;span class="n"&gt;view&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
    &lt;span class="n"&gt;div&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="n"&gt;button&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="n"&gt;onClick&lt;/span&gt; &lt;span class="kt"&gt;Decrement&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;-"&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;div&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fromInt&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;button&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="n"&gt;onClick&lt;/span&gt; &lt;span class="kt"&gt;Increment&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;+"&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="p"&gt;]&lt;/span&gt;


&lt;span class="n"&gt;subscriptions&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Int&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;Sub&lt;/span&gt; &lt;span class="kt"&gt;Msg&lt;/span&gt;
&lt;span class="n"&gt;subscriptions&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
    &lt;span class="n"&gt;receiveCount&lt;/span&gt; &lt;span class="kt"&gt;Set&lt;/span&gt;


&lt;span class="n"&gt;main&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Program&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="kt"&gt;Int&lt;/span&gt; &lt;span class="kt"&gt;Msg&lt;/span&gt;
&lt;span class="n"&gt;main&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
    &lt;span class="kt"&gt;Browser&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;element&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;init&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;\&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;init&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;update&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;view&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;view&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;subscriptions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;subscriptions&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;


&lt;span class="k"&gt;port&lt;/span&gt; &lt;span class="n"&gt;increment&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Int&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;Cmd&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt;


&lt;span class="k"&gt;port&lt;/span&gt; &lt;span class="n"&gt;decrement&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Int&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;Cmd&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt;


&lt;span class="k"&gt;port&lt;/span&gt; &lt;span class="n"&gt;receiveCount&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Int&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;Sub&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a fairly straightforward Elm app, but with one key difference: rather than update the state here, we are returning a command to relay the current state to a port. We also have a port to receive a number, which then updates our local state.&lt;/p&gt;

&lt;p&gt;Since we are going to handle this &lt;em&gt;very&lt;/em&gt; complex computation in a Web Worker, let's now write a basic Elm module to run from within the Worker.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elm"&gt;&lt;code&gt;&lt;span class="k"&gt;port&lt;/span&gt; &lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="kt"&gt;Worker&lt;/span&gt; &lt;span class="k"&gt;exposing&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="kt"&gt;Platform&lt;/span&gt;


&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="kt"&gt;Msg&lt;/span&gt;
    &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;Increment&lt;/span&gt; &lt;span class="kt"&gt;Int&lt;/span&gt;
    &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kt"&gt;Decrement&lt;/span&gt; &lt;span class="kt"&gt;Int&lt;/span&gt;


&lt;span class="n"&gt;init&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;Cmd&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;init&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;Cmd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;none&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="n"&gt;update&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Msg&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;Cmd&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;update&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt;
        &lt;span class="kt"&gt;Increment&lt;/span&gt; &lt;span class="n"&gt;int&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sendCount&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;int&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="kt"&gt;Decrement&lt;/span&gt; &lt;span class="n"&gt;int&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sendCount&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;int&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="n"&gt;subscriptions&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;Sub&lt;/span&gt; &lt;span class="kt"&gt;Msg&lt;/span&gt;
&lt;span class="n"&gt;subscriptions&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
    &lt;span class="kt"&gt;Sub&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;batch&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="n"&gt;increment&lt;/span&gt; &lt;span class="kt"&gt;Increment&lt;/span&gt;
        &lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;decrement&lt;/span&gt; &lt;span class="kt"&gt;Decrement&lt;/span&gt;
        &lt;span class="p"&gt;]&lt;/span&gt;


&lt;span class="n"&gt;main&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Program&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="kt"&gt;Msg&lt;/span&gt;
&lt;span class="n"&gt;main&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
    &lt;span class="kt"&gt;Platform&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;worker&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;init&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;init&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;update&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;subscriptions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;subscriptions&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;


&lt;span class="k"&gt;port&lt;/span&gt; &lt;span class="n"&gt;increment&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Int&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;Sub&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt;


&lt;span class="k"&gt;port&lt;/span&gt; &lt;span class="n"&gt;decrement&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Int&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;Sub&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt;


&lt;span class="k"&gt;port&lt;/span&gt; &lt;span class="n"&gt;sendCount&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Int&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;Cmd&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What's going on here? First, we import &lt;code&gt;Platform&lt;/code&gt;, which provides us with the function &lt;a href="https://package.elm-lang.org/packages/elm/core/latest/Platform#worker" rel="noopener noreferrer"&gt;&lt;code&gt;Platform.worker&lt;/code&gt;&lt;/a&gt;. Most of the time, when writing an Elm app, we're leaning on &lt;a href="https://package.elm-lang.org/packages/elm/browser/latest/" rel="noopener noreferrer"&gt;elm/Browser&lt;/a&gt; to create apps that bind to the DOM. But in this case, we don't have a DOM to bind to, so we utilize Platform to create a basic app that doesn't do that. &lt;code&gt;worker&lt;/code&gt; takes three inputs: &lt;code&gt;init&lt;/code&gt;, &lt;code&gt;update&lt;/code&gt;, and &lt;code&gt;subscriptions&lt;/code&gt; (it's basically the same as &lt;code&gt;Browser.element&lt;/code&gt;, from our Main.elm example).&lt;/p&gt;

&lt;p&gt;We also create two ports for incrementing and decrementing the input (an incredibly taxing computation for even modern Javascript), and connect those to equivalent &lt;code&gt;Msg&lt;/code&gt; values. Within the update function, we then send the results to &lt;code&gt;sendCount&lt;/code&gt;, which outputs from Elm into the wild west of Javascript for us.&lt;/p&gt;

&lt;p&gt;Conceptually, it looks like this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Main receives a message (&lt;code&gt;Increment&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;In Main's update function, we send the current count to a matching port (&lt;code&gt;increment 0&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;This value is sent (via Javascript) from Main to Worker, and connected to the matching port (also &lt;code&gt;increment 0&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;The Worker sends out the result of its intense calculation (&lt;code&gt;sendCount 1&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Main receives the updated value, and updates its model accordingly (&lt;code&gt;receiveCount 1&lt;/code&gt;)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you're familiar with The Elm Architecture, this is basically the same thing but with more steps. It's also important to note that because we are relying on ports to communicate between the Main and Worker apps, this calculation is inherently asynchronous. This is really only ideal for certain workloads, and should probably not be used 100% of the time (especially for small tasks like addition/subtraction).&lt;/p&gt;

&lt;h2&gt;
  
  
  Scaffold index.html
&lt;/h2&gt;

&lt;p&gt;Now that we've had a look at the Elm code, let's look at Javascript. Since we are using vanilla JS and not a bundler, we first need to bundle our Elm code. Run the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;elm make src/Main.elm &lt;span class="nt"&gt;--output&lt;/span&gt; main.js
elm make src/Worker.elm &lt;span class="nt"&gt;--output&lt;/span&gt; elm-worker.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will output our &lt;code&gt;main.js&lt;/code&gt; and &lt;code&gt;worker.js&lt;/code&gt; files, which we can import into our HTML. Speaking of which let's do that! Here's a basic HTML file to start with. All it does is mount our Main app, we'll get to the Worker in a moment.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;html&lt;/span&gt; &lt;span class="na"&gt;lang=&lt;/span&gt;&lt;span class="s"&gt;"en"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;title&amp;gt;&lt;/span&gt;Elm Web Workers&lt;span class="nt"&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"app"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;div&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"main.js"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Elm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Main&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;node&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;app&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you open the HTML file in a browser right now, it should properly render the Main app, but the buttons don't appear to do anything. That's because rather than updating our model, they are instead sending it to ports. Currently, we aren't doing anything with our ports, but before we hook them up, let's add our Web Worker.&lt;/p&gt;

&lt;h2&gt;
  
  
  Adding the Web Worker
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;For this section, I will be referring to &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers" rel="noopener noreferrer"&gt;MDN's excellent guide to using Web Workers&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;In order to create a web worker, we need to have an external JS file that can be imported and executed as a web worker. The most basic implementation of a worker can be a simple &lt;code&gt;console.log&lt;/code&gt;. Let's do that first.&lt;/p&gt;

&lt;p&gt;Create a &lt;code&gt;worker.js&lt;/code&gt; file and put in &lt;code&gt;console.log("Hello, worker!")&lt;/code&gt;. Then, in our HTML file, add this code to the top of your script block:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;worker&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Worker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;worker.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Elm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Main&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;node&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;app&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This instructs the browser to create a worker using the Javascript file that is found at the named location (in our case, &lt;code&gt;worker.js&lt;/code&gt;). If you open your devtools, you should see "Hello, worker!" appear there, generated from &lt;code&gt;worker.js:1&lt;/code&gt;. Great!&lt;/p&gt;

&lt;p&gt;Now let's add some communication between the worker and main JS files. &lt;/p&gt;

&lt;h3&gt;
  
  
  Sending a message
&lt;/h3&gt;

&lt;p&gt;In your HTML file, let's add another line of code that will enable sending a message to the worker. To send a message from main to the worker, we use &lt;code&gt;worker.postMessage()&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;worker&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Worker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;worker.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Elm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Main&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;node&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;app&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;worker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;postMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To receive a message in the worker, we set &lt;code&gt;onmessage&lt;/code&gt; (not a variable) to be a function that receives a function. Delete the contents of your &lt;code&gt;worker.js&lt;/code&gt; file and add the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;onmessage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As with all Javascript events, there are a number of other values sent to the onmessage function. For the sake of this blog post, we only care about the data key. If you run this script, you should see a &lt;code&gt;1&lt;/code&gt; logged out into the console. Congratulations, we are now able to pass data to the worker! But what about passing it into Elm?&lt;/p&gt;

&lt;p&gt;Web Workers provide a special API for importing scripts into them:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Worker threads have access to a global function, importScripts(), which lets them import scripts. It accepts zero or more URIs as parameters to resources to import.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;By using &lt;code&gt;importScripts()&lt;/code&gt;, we can import our Elm worker module, initialize it, and begin to use its ports. Let's update our &lt;code&gt;worker.js&lt;/code&gt; as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;importScripts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;elm-worker.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Elm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Worker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="nx"&gt;onmessage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ports&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;increment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ports&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sendCount&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;int&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For those less familiar with Elm, we are initializing our Elm worker without a DOM node (because there are no DOM nodes in the worker). Then, using its ports, when we receive a message from the main thread, we send it to the &lt;code&gt;increment&lt;/code&gt; port. Elm then does its incredibly complicated calculations, and returns (via the &lt;code&gt;sendCount&lt;/code&gt; port) the updated integer (which we log for now). Excellent!&lt;/p&gt;

&lt;p&gt;Before we go too much further, let's update the main and worker to properly target either the increment or decrement ports. In &lt;code&gt;index.html&lt;/code&gt;, update your script block to the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;worker&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Worker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;worker.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Elm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Main&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;node&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;app&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ports&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;increment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;int&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;worker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;postMessage&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;increment&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;int&lt;/span&gt;
&lt;span class="p"&gt;}))&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ports&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;decrement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;int&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;worker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;postMessage&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;decrement&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;int&lt;/span&gt;
&lt;span class="p"&gt;}))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, in our worker, update to the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;importScripts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;elm-worker.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Elm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Worker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="nx"&gt;onmessage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;increment&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ports&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;increment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;decrement&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ports&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;decrement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ports&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sendCount&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;int&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you refresh the page, you can now start clicking the buttons and seeing the results log in the console. Of course, it's only going to show 1 or -1, so let's pass data back to the main thread.&lt;/p&gt;

&lt;p&gt;Web Workers have a global &lt;code&gt;postMessage&lt;/code&gt; function that allows us to pass back data. Let's wrap up this code and send the calculated result to the main thread (and our Main Elm app):&lt;/p&gt;

&lt;p&gt;In worker.js, do the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;importScripts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;elm-worker.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Elm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Worker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="nx"&gt;onmessage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;increment&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ports&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;increment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;decrement&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ports&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;decrement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ports&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sendCount&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;int&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nf"&gt;postMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;int&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In index.html, update the script block:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;worker&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Worker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;worker.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Elm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Main&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;node&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;app&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ports&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;increment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;int&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;worker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;postMessage&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;increment&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;int&lt;/span&gt;
&lt;span class="p"&gt;}))&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ports&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;decrement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;int&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;worker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;postMessage&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;decrement&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;int&lt;/span&gt;
&lt;span class="p"&gt;}))&lt;/span&gt;

&lt;span class="nx"&gt;worker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onmessage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ports&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;receiveCount&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And with that, we are now passing data! Congratulations! If you need to pass any complex data between the main and worker threads, you will probably need to turn to JSON encoding/decoding. You can also pass an object with a custom message if needed, rather than using multiple ports and relying on Javascript to act as the controller.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/lindsaykwardell/elm-vanilla-js-web-worker" rel="noopener noreferrer"&gt;Here's a repository with the code we've been looking at.&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Web Workers in Vite
&lt;/h2&gt;

&lt;p&gt;Using vanilla HTML and JS is nice, but most of the time at work or in larger projects we're using some sort of build tooling to have a more streamlined experience. I'm personally a big fan of &lt;a href="https://vitejs.dev/" rel="noopener noreferrer"&gt;Vite&lt;/a&gt;, the frontend tooling solution by the creator of Vue. I maintain &lt;a href="https://github.com/lindsaykwardell/vite-elm-template" rel="noopener noreferrer"&gt;a Vite template for building Elm applications&lt;/a&gt;, which utilized the excellent Elm plugin for Vite to achieve hot module reload and directly importing our &lt;code&gt;.elm&lt;/code&gt; files into our Javascript.&lt;/p&gt;

&lt;p&gt;As an added benefit for our use case, &lt;a href="https://vitejs.dev/guide/features.html#web-workers" rel="noopener noreferrer"&gt;Vite provides some abstraction over the Web Worker API&lt;/a&gt; that we explored above. In Vite, when we import a script that we want to use as a web worker, we can append a query parameter that signals to Vite what it is, and then Vite will wrap it in a function that generates the correct worker command.&lt;/p&gt;

&lt;p&gt;Let's migrate our above code into Vite and see how this works. I'll be using my template to scaffold a basic app. To do that yourself, run the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx degit lindsaykwardell/vite-elm-template vite-elm-web-worker
&lt;span class="nb"&gt;cd &lt;/span&gt;vite-elm-web-worker
npm &lt;span class="nb"&gt;install&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That will clone the template locally (with no Git history) into the &lt;code&gt;vite-elm-web-worker&lt;/code&gt; folder, enter it, and install the required dependencies. Feel free to rename it to whatever you prefer. Then, delete the contents of the &lt;code&gt;src&lt;/code&gt; folder and replace them with our &lt;code&gt;Main.elm&lt;/code&gt; and &lt;code&gt;Worker.elm&lt;/code&gt; files. At this point, you should have a setup that looks like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--m9BAKu8o--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://d33wubrfki0l68.cloudfront.net/51905a42395da9e713f8c0b82ad2bbc28b8fc1a1/fca26/blog/elm-web-worker-vite-1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--m9BAKu8o--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://d33wubrfki0l68.cloudfront.net/51905a42395da9e713f8c0b82ad2bbc28b8fc1a1/fca26/blog/elm-web-worker-vite-1.png" alt="File tree in VS Code, showing the src folder has two files: Main.elm, and Worker.elm" width="648" height="990"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Next, let's bring over our worker.js and other Javascript. Let's start by creating a &lt;code&gt;worker.js&lt;/code&gt; file (we'll come back to it in a moment), and then update our &lt;code&gt;main.js&lt;/code&gt; file to include our worker and port logic:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./style.css&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Elm&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./src/Main.elm&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;ElmWorker&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./worker?worker&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;root&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#app div&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;worker&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ElmWorker&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Elm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Main&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;node&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;root&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ports&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;increment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
  &lt;span class="nx"&gt;worker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;postMessage&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;increment&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ports&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;decrement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
  &lt;span class="nx"&gt;worker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;postMessage&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;decrement&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;worker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onmessage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ports&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;receiveCount&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This should look very familiar to what we were doing, but with some additional import syntax at the top. This is because we're using Vite, and Vite supports ES Modules by default during development. Rather than including multiple script tags (which is still an option), we can import a single ES module (main.js), and import our other files within it.&lt;/p&gt;

&lt;p&gt;For the worker, most of the code we wrote previously will work, but Vite provides some additional sugar on top of the API here:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The worker script can also use import statements instead of importScripts() - note during dev this relies on browser native support and currently only works in Chrome, but for the production build it is compiled away.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So rather than using &lt;code&gt;importScripts()&lt;/code&gt;, Vite requires that we use the standard ES Module import syntax. However, there's an issue here: Elm doesn't compile by default into a format that works well with ES Modules. In addition, the Vite plugin for Elm assumes that you are building a browser-based app (a reasonable assumption), and injects some DOM-powered troubleshooting helpers, which do not work in the worker because the worker doesn't have access to the DOM.&lt;/p&gt;

&lt;p&gt;For example, let's assume we update our worker to use ES import syntax, like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Elm&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./src/Worker.elm&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Elm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Worker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="nx"&gt;onmessage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;increment&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ports&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;increment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;decrement&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ports&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;decrement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ports&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sendCount&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;int&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nf"&gt;postMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;int&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you start your development environment now (using &lt;code&gt;npm run dev&lt;/code&gt;), you will immediately see an error in the browser console:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Uncaught ReferenceError: HTMLElement is not defined
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This error is being thrown by &lt;code&gt;overlay.ts&lt;/code&gt;. This file adds an error overlay when Elm isn't able to properly compile. So if you're working in the Main.elm file, and make a change that doesn't compile, you'll see something like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--D-lJ0OIi--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://d33wubrfki0l68.cloudfront.net/e3de55a056ca7d8e0b5c02c45780fd57c172fa22/abd7c/blog/elm-web-worker-vite-2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--D-lJ0OIi--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://d33wubrfki0l68.cloudfront.net/e3de55a056ca7d8e0b5c02c45780fd57c172fa22/abd7c/blog/elm-web-worker-vite-2.png" alt="In-browser error alerting that a type of " width="800" height="595"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Pretty helpful during app development, but very frustrating when trying to load Elm in a web worker. There is a setting that can be set in the Vite config (&lt;code&gt;server.hmr.overlay: false&lt;/code&gt;) to disable the overlay, but unfortuantely it doesn't actually prevent HTMLElement from being referenced within the Worker.&lt;/p&gt;

&lt;p&gt;A second approach could be to precompile our Worker.elm file, and import it directly into the &lt;code&gt;worker.js&lt;/code&gt; file (as we did in our vanilla JS example). This, however, throws a silent error; the app will load without any obvious failures, but the worker isn't actually initialized. Go ahead and try it! Run &lt;code&gt;elm make src/Worker.elm --output elm-worker.js&lt;/code&gt;, then update the &lt;code&gt;worker.js&lt;/code&gt; to the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Elm&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./elm-worker.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;I'm here!&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Elm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Worker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="nx"&gt;onmessage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;increment&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ports&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;increment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;decrement&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ports&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;decrement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ports&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sendCount&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;int&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nf"&gt;postMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;int&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you spin up the app again, you'll notice that our &lt;code&gt;console.log&lt;/code&gt; doesn't even run. That's because the web worker was never initialized, which is very unhelpful for our complex computations.&lt;/p&gt;

&lt;p&gt;So what's the solution? At the moment, the best solution I've found is to create a separate entrypoint for Vite, import &lt;code&gt;Worker.elm&lt;/code&gt; there, and compile it with Vite. That will perform the transformation we need on Elm to allow an import into the worker.&lt;/p&gt;

&lt;p&gt;Within our &lt;code&gt;src&lt;/code&gt; folder, create an &lt;code&gt;elm-worker.js&lt;/code&gt; file, and put the following into it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Elm&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./Worker.elm&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Elm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Worker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a very basic file, all it does is import our Worker.elm file, initialize the app, and export it. Now we need to compile this file with Vite. At the root level of our app, create a file called &lt;code&gt;worker.config.js&lt;/code&gt;. This will be a special Vite configuration file that we will only use to compile &lt;code&gt;elm-worker.js&lt;/code&gt;. Here's a good configuration to start with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;defineConfig&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;vite&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;elmPlugin&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;vite-plugin-elm&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;path&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;defineConfig&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;publicDir&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;plugins&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;elmPlugin&lt;/span&gt;&lt;span class="p"&gt;()],&lt;/span&gt;
  &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;outDir&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./elm-worker&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;sourcemap&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;lib&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;__dirname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./src/elm-worker.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;elm-worker&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;fileName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;format&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;`elm-worker.&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;format&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.js`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This configuration specifies that we only care about &lt;code&gt;elm-worker.js&lt;/code&gt;, not importing any other files (such as the &lt;code&gt;public&lt;/code&gt; folder), and to build those files in an &lt;code&gt;elm-worker&lt;/code&gt; folder. By default, Vite compiles both ESM and UMD formats; this is probably not useful for our case, but it's not a big issue.&lt;/p&gt;

&lt;p&gt;With our config in place, run the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx vite build &lt;span class="nt"&gt;--config&lt;/span&gt; worker.config.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This instructs Vite to run its build command, using our new config file instead of the default one. Once it finishes, you should see a new &lt;code&gt;elm-worker&lt;/code&gt; folder, with two files inside: &lt;code&gt;elm-worker.es.js&lt;/code&gt; and &lt;code&gt;elm-worker.umd.js&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;With our newly compiled ES-compatible file in hand, we can now, at last, import our Elm worker into our web worker file, and everything will work as expected. Update our &lt;code&gt;worker.js&lt;/code&gt; file (at the root of our app) to the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./elm-worker/elm-worker.es.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="nx"&gt;onmessage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;increment&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ports&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;increment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;decrement&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ports&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;decrement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ports&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sendCount&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;int&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nf"&gt;postMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;int&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you run &lt;code&gt;npm run dev&lt;/code&gt; now, and start clicking on the plus and minus buttons, you should see the value displayed on the screen changing. Congratulations! We now have a web worker running Elm within Vite!&lt;/p&gt;

&lt;p&gt;This is by no means not a straightforward solution, but it does at least work, and it allows us to utilize the other benefits of using a frontend development tool like Vite. To make things easier going forward, you can add a custom script to &lt;code&gt;package.json&lt;/code&gt; (something like &lt;code&gt;build:worker&lt;/code&gt;) to run our worker build command, and you can even add it to our &lt;code&gt;dev&lt;/code&gt; script to ensure it runs every time, keeping our web worker closer in sync with the rest of our app.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/lindsaykwardell/vite-elm-web-worker" rel="noopener noreferrer"&gt;Here's a repo with our working Vite code.&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Obviously basic addition and subtraction isn't worth the extra overhead of using web workers. Tasks that require large computations (either complex calculations, or just parsing a lot of data) are ideal for this situation. One side project where I've used a web worker required potentially processing more than 2 megabytes of data, which, when done in the main thread, caused the entire app to freeze. Moving the same calculation to a web worker didn't speed up the calculation, but it did allow the UI (and the CSS) to continue running at full speed. &lt;a href="https://github.com/lindsaykwardell/juralen-elm/tree/master/src/Game/Analyzer" rel="noopener noreferrer"&gt;Here's the web worker&lt;/a&gt; from the side project if you're interested!&lt;/p&gt;

&lt;p&gt;Also, in case you're concerned, &lt;a href="https://caniuse.com/webworkers" rel="noopener noreferrer"&gt;Web Workers have been supported&lt;/a&gt; in all modern browsers since IE10, so feel free to use them in your new projects!&lt;/p&gt;

&lt;p&gt;I look forward to seeing what you make with Web Components!&lt;/p&gt;

</description>
      <category>elm</category>
      <category>web</category>
      <category>vite</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Coming Into Vue: What's Next in Vue 3</title>
      <dc:creator>Lindsay Wardell</dc:creator>
      <pubDate>Tue, 08 Feb 2022 19:51:08 +0000</pubDate>
      <link>https://forem.com/lindsaykwardell/coming-into-vue-whats-next-in-vue-3-17fj</link>
      <guid>https://forem.com/lindsaykwardell/coming-into-vue-whats-next-in-vue-3-17fj</guid>
      <description>&lt;p&gt;It was a moment of celebration across the Vueniverse. At last, after more than a year of Vue 3 being available on the &lt;code&gt;next&lt;/code&gt; branch of all the core repositories (and many of the related frameworks and libraries), Vue 3 has been officially released to the world as the recommended way to write Vue applications. The moment was noted on the &lt;a href="https://blog.vuejs.org/posts/vue-3-as-the-new-default.html" rel="noopener noreferrer"&gt;official Vue blog&lt;/a&gt; as well as on Twitter.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;🎉 It's done! Vue 3 is now the default version and the brand new &lt;a href="https://t.co/0N2uGPCtsh" rel="noopener noreferrer"&gt;https://t.co/0N2uGPCtsh&lt;/a&gt; is live!&lt;br&gt;&lt;br&gt;More details in the blog post in case you missed it: &lt;a href="https://t.co/ub8L4KhPsJ" rel="noopener noreferrer"&gt;https://t.co/ub8L4KhPsJ&lt;/a&gt;&lt;/p&gt;— Vue.js (@vuejs) &lt;a href="https://twitter.com/vuejs/status/1490592213184573441?ref_src=twsrc%5Etfw" rel="noopener noreferrer"&gt;February 7, 2022&lt;/a&gt;
&lt;/blockquote&gt;

&lt;p&gt;Considering that the initial release of Vue 3 (named "One Piece") was &lt;a href="https://github.com/vuejs/core/releases?q=3.0.0&amp;amp;expanded=true" rel="noopener noreferrer"&gt;originally released on September 18, 2020&lt;/a&gt;, the fact that we're only now reaching the official recommendation has shaped the Vue ecosystem. For applications, most of the actual migration between Vue 2 to Vue 3 is straightforward, with Vue &lt;a href="https://v3.vuejs.org/guide/migration/introduction.html#breaking-changes" rel="noopener noreferrer"&gt;minimizing breaking changes&lt;/a&gt; while adding new features such as the &lt;a href="https://v3.vuejs.org/guide/composition-api-introduction.html#why-composition-api" rel="noopener noreferrer"&gt;Composition API&lt;/a&gt; as well as the latest new feature to land in Vue, &lt;code&gt;&amp;lt;script setup&amp;gt;&lt;/code&gt;, which provides a way to build Vue apps without so much boilerplate code (&lt;a href="https://www.youtube.com/watch?v=adkxGYeW97c" rel="noopener noreferrer"&gt;I presented a demo of using Composition API and &lt;code&gt;&amp;lt;script setup&amp;gt;&lt;/code&gt;&lt;/a&gt; alongside Ben Hong at VueJS Athens, check it out!).&lt;/p&gt;

&lt;p&gt;However, the Vue ecosystem has seen its share of churn in the meantime. Core APIs that frameworks like Vuetify rely on were altered, to the point that even the Migration Build doesn't cover the differences between Vue 2 and 3. Frameworks such as Nuxt and Quasar have been working to support Vue 3 (with Quasar releasing it's stable support back in 2021). Meanwhile, work has been going into Vite, which is now &lt;a href="https://www.npmjs.com/package/create-vue" rel="noopener noreferrer"&gt;the official recommendation for building Vue apps&lt;/a&gt; as well (replacing the Vue CLI).&lt;/p&gt;

&lt;p&gt;Now that we've reached an official recommendation to use Vue 3, what does the future look like for the Vue ecosystem? I asked Twitter what they were looking forward to in the future of Vue, and these are some of the answers that I got.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reactivity Transform
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;The Reactivity Transform Unification is the final piece in Vue 3 reactivity primitives and &amp;lt;script setup&amp;gt; based RFCs story. Once &lt;a href="https://twitter.com/vuejs?ref_src=twsrc%5Etfw" rel="noopener noreferrer"&gt;@vuejs&lt;/a&gt; finalizes this RFC, there will be a massive DX improvement both for advanced users and for newcomers to the framework&lt;a href="https://t.co/8DzIz96ovD" rel="noopener noreferrer"&gt;https://t.co/8DzIz96ovD&lt;/a&gt;&lt;/p&gt;— patak (@patak_dev) &lt;a href="https://twitter.com/patak_dev/status/1491019920070438914?ref_src=twsrc%5Etfw" rel="noopener noreferrer"&gt;February 8, 2022&lt;/a&gt;
&lt;/blockquote&gt;

&lt;p&gt;The next new feature coming to Vue 3 is being referred to as the &lt;a href="https://github.com/vuejs/rfcs/discussions/413" rel="noopener noreferrer"&gt;"Reactivity Transform Unification"&lt;/a&gt;. The main issue being resolved here is that the introduction of &lt;code&gt;Ref&lt;/code&gt; can be complex, especially moving from Vue 2 to 3. For context, in Vue 3 today, you can create and access a ref value like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;setup&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ref&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;vue&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;        &lt;span class="c1"&gt;// Set the value with a `ref` call&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;    &lt;span class="c1"&gt;// Access the value with `.value` in JS&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;template&amp;gt;&lt;/span&gt;
  &lt;span class="c"&gt;&amp;lt;!-- Access the value with just the ref name --&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="na"&gt;click=&lt;/span&gt;&lt;span class="s"&gt;"count++"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    {{count}}
  &lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/template&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The change would be to provide a utility function, &lt;code&gt;$ref&lt;/code&gt;, that would unwrap the ref into a reactive variable, and then let you work with it directly, rather than accessing the &lt;code&gt;.value&lt;/code&gt; key.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;setup&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;$ref&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="c1"&gt;// Set the value with a `$ref` call&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;      &lt;span class="c1"&gt;// Access the value directly!&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;template&amp;gt;&lt;/span&gt;
  &lt;span class="c"&gt;&amp;lt;!-- No changes, still access the value directly --&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="na"&gt;click=&lt;/span&gt;&lt;span class="s"&gt;"count++"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    {{count}}
  &lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/template&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This feels a lot more comfortable to work with, especially for non-Vue developers! There have been a number of proposals trying to resolve the cumbersome nature of refs, and I think this is a comfortable middle ground. There are a number of other functions proposed in the RFC, go check it out and provide your feedback!&lt;/p&gt;

&lt;h2&gt;
  
  
  Nuxt 3
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;Can't wait for &lt;a href="https://twitter.com/nuxt_js?ref_src=twsrc%5Etfw" rel="noopener noreferrer"&gt;@nuxt_js&lt;/a&gt; 3 to be stable now!&lt;/p&gt;— CapitaineToinon (@CapitaineToinon) &lt;a href="https://twitter.com/CapitaineToinon/status/1490952449007710210?ref_src=twsrc%5Etfw" rel="noopener noreferrer"&gt;February 8, 2022&lt;/a&gt;
&lt;/blockquote&gt;

&lt;p&gt;The next big call-out I saw from the ecosystem is Nuxt 3 reaching a stable build. Nuxt 3 is an exciting new major version for Vue's primary SSR framework, with new features such as its Nitro backend (that allows it to be deployed to a number of environments beyond a standard Node server) and Vite integration. On the Nuxt site, there is a chart comparing the different versions of Nuxt as they exist today, and their recommendations. I'll copy it below to keep a snapshot of the status as I write this post, but the &lt;a href="https://v3.nuxtjs.org/getting-started/introduction#comparison" rel="noopener noreferrer"&gt;latest comparison can be found on the Nuxt 3 site&lt;/a&gt;.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature / Version&lt;/th&gt;
&lt;th&gt;Nuxt 2&lt;/th&gt;
&lt;th&gt;Nuxt Bridge&lt;/th&gt;
&lt;th&gt;Nuxt 3&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Vue&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Stability&lt;/td&gt;
&lt;td&gt;😊 Stable&lt;/td&gt;
&lt;td&gt;😌 Semi-stable&lt;/td&gt;
&lt;td&gt;😬 Unstable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Performance&lt;/td&gt;
&lt;td&gt;🏎 Fast&lt;/td&gt;
&lt;td&gt;✈️ Faster&lt;/td&gt;
&lt;td&gt;🚀 Fastest&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Nitro Engine&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ESM support&lt;/td&gt;
&lt;td&gt;🌙 Partial&lt;/td&gt;
&lt;td&gt;👍 Better&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TypeScript&lt;/td&gt;
&lt;td&gt;☑️ Opt-in&lt;/td&gt;
&lt;td&gt;🚧 Partial&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Composition API&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;🚧 Partial&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Options API&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Components Auto Import&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;&amp;lt;script setup&amp;gt;&lt;/code&gt; syntax&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;🚧 Partial&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Auto Imports&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Webpack&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Vite&lt;/td&gt;
&lt;td&gt;⚠️ Partial&lt;/td&gt;
&lt;td&gt;🚧 Partial&lt;/td&gt;
&lt;td&gt;🚧 Experimental&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Nuxi CLI&lt;/td&gt;
&lt;td&gt;❌ Old&lt;/td&gt;
&lt;td&gt;✅ nuxi&lt;/td&gt;
&lt;td&gt;✅ nuxi&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Static sites&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;🚧&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;There are a couple key features that are still being worked out before Nuxt 3 is stable, based on this chart. Since Nuxt 2 was based on Webpack, Vite integration is still experimental and under development. I've done some playing around with it, and found it to work fairly well, but I'm not working on production code with Nuxt and Vite so there's probably some edge cases still to work out. If you want to try Nuxt 3 and Vite, just add this to your Nuxt config:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// nuxt.config.ts&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;defineNuxtConfig&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;nuxt3&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// https://v3.nuxtjs.org/docs/directory-structure/nuxt.config&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;defineNuxtConfig&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="c1"&gt;// Add the `vite` key to your config, and you'll opt into Vite mode&lt;/span&gt;
  &lt;span class="na"&gt;vite&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The other missing feature is static site generation. While Nuxt is primarily a server-side generator, it can be used to build static sites as well. If you're wanting to use Nuxt 3 features, but really need static sites, you'll need to use the &lt;a href="https://v3.nuxtjs.org/getting-started/bridge/" rel="noopener noreferrer"&gt;Nuxt Bridge&lt;/a&gt;. From the docs:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Bridge is a forward-compatibility layer that allows you to experience many of the new Nuxt 3 features by simply installing and enabling a Nuxt module.&lt;/p&gt;

&lt;p&gt;Using Nuxt Bridge, you can make sure your project is (almost) ready for Nuxt 3 and have the best developer experience without needing a major rewrite or risk breaking changes.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If you're looking to try out Nuxt 3 with an existing app, I'd suggest checking out the Bridge build. Otherwise, spin up a new app with Nuxt 3, and be aware that static generation is currently under development and does not work. Also, remember that because it's under development, it's best to consider the Nuxt APIs as unstable and prone to change. While it feels solid, Nuxt 3 is not yet ready for production use.&lt;/p&gt;

&lt;h2&gt;
  
  
  Vuetify
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;Vuetify compatible with vue3&lt;/p&gt;— Viliam Mihálik (@ViliamMih) &lt;a href="https://twitter.com/ViliamMih/status/1491017715527860225?ref_src=twsrc%5Etfw" rel="noopener noreferrer"&gt;February 8, 2022&lt;/a&gt;
&lt;/blockquote&gt;

&lt;p&gt;Vuetify, one of the most well-known material frameworks for building Vue apps, is still working on support for Vue 3. Part of this is due to an overhaul under the hood that will lead to improved performance and a better experience. According to the &lt;a href="https://vuetifyjs.com/en/introduction/roadmap/" rel="noopener noreferrer"&gt;Vuetify official roadmap&lt;/a&gt;, Vuetify 3 is planned for release in May 2022, with a public beta in February.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Target Release: May 2022&lt;/li&gt;
&lt;li&gt;Alpha: Live&lt;/li&gt;
&lt;li&gt;Beta: February 2022&lt;/li&gt;
&lt;li&gt;Overview:

&lt;ul&gt;
&lt;li&gt;Rebuilt for Vue 3 using the new composition api&lt;/li&gt;
&lt;li&gt;Global properties that allow you to make large overarching changes to your app&lt;/li&gt;
&lt;li&gt;Improved SASS variable customization and extensibility with Built-In Modules&lt;/li&gt;
&lt;li&gt;New Vue CLI presets for generating pre-built starting projects&lt;/li&gt;
&lt;li&gt;First party Vite support for lightning fast development&lt;/li&gt;
&lt;li&gt;Greatly improved TypeScript support&lt;/li&gt;
&lt;li&gt;Better framework coverage with E2E testing using Cypress&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;Check out the links on the Vuetify page to access their Github page and Discord channel.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ecosystem Support and Stability
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;It'll be great when the ecosystem catches up inc. all the plugins and Nuxt 3&lt;/p&gt;— Anthony Gore (&lt;a class="mentioned-user" href="https://dev.to/anthonygore"&gt;@anthonygore&lt;/a&gt;) &lt;a href="https://twitter.com/anthonygore/status/1490876690813571073?ref_src=twsrc%5Etfw" rel="noopener noreferrer"&gt;February 8, 2022&lt;/a&gt;
&lt;/blockquote&gt;

&lt;p&gt;There are a number of other prominent Vue libraries that are still working on their stable Vue 3 support. Vue Apollo, Vuelidate, and Bootstrap Vue have some work done, but are in different stages of either "not available" or "public alpha". Some other projects, such as &lt;a href="https://github.com/rigor789/nativescript-vue-next" rel="noopener noreferrer"&gt;NativeScript Vue&lt;/a&gt;, are having to undergo more substantial rewrites in order to be compatible. All of this takes time, and in the meantime the projects that are reliant on these libraries will have to remain on Vue 2, or switch to alternatives that already support Vue 3.&lt;/p&gt;

&lt;p&gt;In that sense, libraries/frameworks that already support Vue 3 have a clear advantage at the moment, and should definitely be considered if you're using a Vue 2-only option. UI frameworks like &lt;a href="https://quasar.dev/" rel="noopener noreferrer"&gt;Quasar&lt;/a&gt; are a good alternative to Vuetify or Bootstrap if you're able to make the switch, for example.&lt;/p&gt;

&lt;p&gt;There are also some libraries that have already been updated, such as &lt;a href="https://github.com/SortableJS/vue.draggable.next" rel="noopener noreferrer"&gt;Vue Draggable&lt;/a&gt;, as well as others that will not be getting Vue 3 support, such as &lt;a href="https://vueformulate.com/" rel="noopener noreferrer"&gt;Vue Formulate&lt;/a&gt; (if you're using it, check out the public beta for its replacement, &lt;a href="https://formkit.com/" rel="noopener noreferrer"&gt;FormKit&lt;/a&gt;).&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://twitter.com/useFormKit?ref_src=twsrc%5Etfw" rel="noopener noreferrer"&gt;@useFormKit&lt;/a&gt; 🤗&lt;/p&gt;— Justin Schroeder (@jpschroeder) &lt;a href="https://twitter.com/jpschroeder/status/1490759805702754313?ref_src=twsrc%5Etfw" rel="noopener noreferrer"&gt;February 7, 2022&lt;/a&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;As a wrap-up to this topic, what I think is most wanted out of the Vue ecosystem in the near future is some peace and quiet.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Ecosystem stability. Vue3 is great, but as a new developer to the Vue space, everything seems so fragile. :/&lt;/p&gt;— Nikki Strømsnes (@TheNix) &lt;a href="https://twitter.com/TheNix/status/1490955186147184640?ref_src=twsrc%5Etfw" rel="noopener noreferrer"&gt;February 8, 2022&lt;/a&gt;
&lt;/blockquote&gt;

&lt;p&gt;Upheavals in how things are done are never easy. The Vue team saw that changes were required in order for Vue applications to continue scaling and solving the problems developers were facing, and made some difficult choices. That, on top of a pandemic interrupting the normal flow of life, has led to a very delicate place for the Vue ecosystem. I feel like in general, we're heading in the right direction, and the more libraries that are able to support Vue 3 moving forward, the faster we'll get there.&lt;/p&gt;

&lt;p&gt;Vue 3 is a fantastic JS framework to pick up and use today. If you are new to Vue, I highly encourage checking out &lt;a href="https://vuejs.org/" rel="noopener noreferrer"&gt;the new official documentation for Vue 3&lt;/a&gt;, it's a fantastic resource that will get you up to speed on the essentials and current recommended practices. If you're into podcasts, I'd also recommend checking out both &lt;a href="https://viewsonvue.com/" rel="noopener noreferrer"&gt;Views on Vue&lt;/a&gt; and &lt;a href="https://enjoythevue.io/" rel="noopener noreferrer"&gt;Enjoy the Vue&lt;/a&gt;. And most important, make sure to enjoy the journey along the way.&lt;/p&gt;

</description>
      <category>vue</category>
      <category>vite</category>
      <category>nuxt</category>
      <category>vuetify</category>
    </item>
    <item>
      <title>Setting up an Elm project in 2022</title>
      <dc:creator>Lindsay Wardell</dc:creator>
      <pubDate>Mon, 20 Dec 2021 00:49:47 +0000</pubDate>
      <link>https://forem.com/lindsaykwardell/setting-up-an-elm-project-in-2022-lj4</link>
      <guid>https://forem.com/lindsaykwardell/setting-up-an-elm-project-in-2022-lj4</guid>
      <description>&lt;p&gt;If you haven't used Elm before, it can be a bit intimidating to get started. A pure functional programming language for building web applications, Elm provides a number of tools for writing Elm apps, including &lt;code&gt;elm reactor&lt;/code&gt; for fast recompiling of specific modules and &lt;code&gt;elm make&lt;/code&gt; for building JS assets. But this workflow can feel a bit lacking if you're coming from a Javascript ecosystem, where hot module reload (HMR), automatic bundling, and integration with CSS and other frontend technologies is common.&lt;/p&gt;

&lt;p&gt;Luckily, there are a number of options for setting up a successful Elm application today. Let's explore what's available for building an Elm application in 2022, including ways to get a better development environment, as well as the tooling surrounding Elm that makes working with the language as delightful as possible.&lt;/p&gt;

&lt;h2&gt;
  
  
  Elm (the language)
&lt;/h2&gt;

&lt;p&gt;You can install the Elm language (and its bundled tooling) &lt;a href="https://www.npmjs.com/package/elm" rel="noopener noreferrer"&gt;from npm&lt;/a&gt;. Installing Elm globally gives you access to a handful of useful tools:&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;elm repl&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;This is the command line repl for Elm. You can use it to execute commands in Elm and see what the result would be.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;elm init&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;This is the command to initialize Elm in a folder. Because Elm has its own ecoystem of tooling and libraries separate from Javascript, it also does not use npm for its package management, and as such does not rely on &lt;code&gt;package.json&lt;/code&gt;. If you want to add Elm to a project (or start a new project), you'll need to run this command. When you do, you'll see a message like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ elm init
Hello! Elm projects always start with an elm.json file. I can create them!

Now you may be wondering, what will be in this file? How do I add Elm files to
my project? How do I see it in the browser? How will my code grow? Do I need
more directories? What about tests? Etc.

Check out &amp;lt;https://elm-lang.org/0.19.1/init&amp;gt; for all the answers!

Knowing all that, would you like me to create an elm.json file now? [Y/n]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is an example of how Elm provides helpful information at every opportunity, as well! The link it provides takes you to documentation on how to structure and scaffold an Elm application.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;elm reactor&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Running &lt;code&gt;elm reactor&lt;/code&gt; opens an interactive page for navigating your Elm code and previewing it in the browser. Note that is does not render external Javascript, or any values set in your HTML files; it is only displaying what the output is for a given Elm file. Also, it does not support auto refresh, so you will have to manually refresh your browser.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.lindsaykwardell.com%2Fblog%2Felm-reactor.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.lindsaykwardell.com%2Fblog%2Felm-reactor.png" alt="Elm reactor, showing current files in the src folder, source directories, and dependencies"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;elm install&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;As you may expect, this command installs an Elm dependency in your application. Sometimes you will need to install a dependency that is already a sub dependency, in which case Elm will inform you that it's going to move the dependency up in the chain so that you can import its modules directly.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;elm make&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;This command lets you build the output Javascript from your Elm code, so that you can import and initialize it in your application. If you are utilizing a tool like Webpack, Parcel, Rollup, or Vite, this is typically not something you'll need to do (more on bundlers later).&lt;/p&gt;

&lt;p&gt;There are other commands available as well. Feel free to run &lt;code&gt;elm --help&lt;/code&gt; to see all the available commands and options.&lt;/p&gt;

&lt;h2&gt;
  
  
  Editor
&lt;/h2&gt;

&lt;p&gt;There are a handful of plugins and extension for different editors, the majority of which can be found on an &lt;a href="https://github.com/elm/editor-plugins" rel="noopener noreferrer"&gt;officially updated list&lt;/a&gt; within the Elm organization on Github. My editor of choice is VS Code, and the &lt;a href="https://marketplace.visualstudio.com/items?itemName=Elmtooling.elm-ls-vscode" rel="noopener noreferrer"&gt;VS Code extension for Elm&lt;/a&gt; is an excellent tool for writing in Elm. It provides details on errors when saving, references to where a particular function or value is utilized, and access to function documentation on hover&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.lindsaykwardell.com%2Fblog%2Felm-vs-code.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.lindsaykwardell.com%2Fblog%2Felm-vs-code.png" alt="The Elm tooling extension in VS Code"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Code Formatting
&lt;/h2&gt;

&lt;p&gt;Unlike Javascript, Elm has an &lt;a href="https://elm-lang.org/docs/style-guide" rel="noopener noreferrer"&gt;official Style Guide&lt;/a&gt; for how Elm code should be structured. In addition, some formatting is built into the language itself, such as requiring indents of four spaces. This removes a major point of contention within teams. In addition, the community has put together a wonderful tool called &lt;a href="https://github.com/avh4/elm-format" rel="noopener noreferrer"&gt;&lt;code&gt;elm-format&lt;/code&gt;&lt;/a&gt;. Similar to Prettier, this utility can be used to ensure that all Elm code matches the official style guide. Unlike Prettier, there is no custom configuration, meaning once again that your team can focus on writing code instead of what kind of quotes to use.&lt;/p&gt;

&lt;h2&gt;
  
  
  Linting
&lt;/h2&gt;

&lt;p&gt;The Elm community has an unofficial linter (called &lt;a href="https://github.com/jfmengels/elm-review/tree/2.6.1" rel="noopener noreferrer"&gt;&lt;code&gt;elm-review&lt;/code&gt;&lt;/a&gt;), which can be used to check your code for potential bugs or mistakes, or highlight a better way to write Elm. Unlike &lt;code&gt;elm-format&lt;/code&gt; (and more similar to tools like ESLint), &lt;code&gt;elm-review&lt;/code&gt; does not come with any default rules to follow:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;All the rules describing problematic code are written in Elm, and elm-review does not come with any built-in rules; instead users are encouraged to write rules themselves and publish them as Elm packages, for everyone to benefit. Search the package registry to find what's out there!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Testing
&lt;/h2&gt;

&lt;p&gt;The de-facto standard for testing an Elm application is &lt;a href="https://github.com/elm-explorations/test" rel="noopener noreferrer"&gt;&lt;code&gt;elm-test&lt;/code&gt;&lt;/a&gt;. However, as noted in the README:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;When people say “elm-test” they usually refer to either:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;This Elm package for writing tests.&lt;/li&gt;
&lt;li&gt;rtfeldman/node-test-runner – a CLI tool (called elm-test) for running tests defined using this package in the terminal.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;p&gt;The Elm package (available on the &lt;a href="https://package.elm-lang.org/packages/elm-explorations/test/latest" rel="noopener noreferrer"&gt;Elm package repository&lt;/a&gt;) contains the functions required to write tests, query HTML, and perform assertions. The syntax should be familiar if you've written unit or fuzz tests in other languages, with &lt;code&gt;describe&lt;/code&gt; blocks and individual &lt;code&gt;test&lt;/code&gt; functions being called.&lt;/p&gt;

&lt;p&gt;To actually run the tests, however, there are currently two options. The first, as noted above, is &lt;a href="https://github.com/rtfeldman/node-test-runner" rel="noopener noreferrer"&gt;&lt;code&gt;node-test-runner&lt;/code&gt;&lt;/a&gt;, which is available from npm at &lt;code&gt;elm-test&lt;/code&gt;. This utility will run the tests as defined in your Elm code, and return the results. There is a second option, &lt;a href="https://github.com/mpizenberg/elm-test-rs" rel="noopener noreferrer"&gt;&lt;code&gt;elm-test-rs&lt;/code&gt;&lt;/a&gt;, which is written in Rust instead of Node. It has a handful of features that &lt;code&gt;node-test-runner&lt;/code&gt; does not have, as well as some downsides (see the Github README for details), but in general both tools work very well for testing Elm code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Development Environment
&lt;/h2&gt;

&lt;p&gt;Now's the fun part - writing our app! Using only the above tools, you would be set to build an Elm application that is properly formatted, tested, and compiled to JS. However, that doesn't help you get it onto the page; you'l still have to create an HTML file, import your Elm code along with any other dependencies you may have, and test it in your "live" environment. Found an error? Time to recompile!&lt;/p&gt;

&lt;p&gt;Needless to say, this can get pretty tedious, not to mention that most Javascript developers today are used to more advanced tooling (such as auto refresh or hot module reload). What can we do in order to set up a better development environment for ourselves?&lt;/p&gt;

&lt;h3&gt;
  
  
  elm-live
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2Fwking-io%2Felm-live%2Fraw%2Fmaster%2Felm-live-logo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2Fwking-io%2Felm-live%2Fraw%2Fmaster%2Felm-live-logo.png" alt="elm-live, a flexible dev server for Elm."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;First on our list is &lt;a href="https://github.com/wking-io/elm-live" rel="noopener noreferrer"&gt;elm-live&lt;/a&gt;. From their README, elm-live provides:&lt;/p&gt;

&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;Hot reloading&lt;/li&gt;
&lt;li&gt;Local SSL&lt;/li&gt;
&lt;li&gt;No reload mode&lt;/li&gt;
&lt;li&gt;No server mode&lt;/li&gt;
&lt;li&gt;and more!&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;p&gt;It's a pretty great tool for working with an Elm application, removing the tedious step of constantly rebuilding our application. However, there's still a couple drawbacks with using elm-live. First, it exists as a development environment for Elm, which means that changing our HTML, CSS, or Javascript assets will not trigger anything. Second, if you need any dependencies from npm, elm-live will not compile these for you. You'll still need some sort of bundler for the rest of your assets. elm-live is excellent for projects written primarily in Elm, or if you don't mind running two development environments side by side.&lt;/p&gt;

&lt;h3&gt;
  
  
  Webpack/Rollup/etc
&lt;/h3&gt;

&lt;p&gt;If you're a Javascript developer, you may have gotten accustomed with importing arbitrary files into your Javascript code. Want to bring in a &lt;code&gt;.vue&lt;/code&gt; file? Go for it! &lt;code&gt;.svelte&lt;/code&gt;? Sure, why not! Our favorite frameworks typically provide some sort of integration with bundlers so that we can import them directly, without having to compile them first to standard Javascript. Can we do that with Elm?&lt;/p&gt;

&lt;p&gt;It turns out, we can! Elm has pretty good support across compilers/bundlers, so that you can import an Elm file directly into your core JS application - no compile step required! Keep in mind that you will still need to initialize the Elm application in order for that code to do anything, but otherwise you are able to treat it like any other compile-to-JS asset.&lt;/p&gt;

&lt;p&gt;Here's a list of common compiler options:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Webpack: &lt;a href="https://github.com/elm-community/elm-webpack-loader" rel="noopener noreferrer"&gt;&lt;code&gt;elm-webpack-loader&lt;/code&gt;&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Rollup: &lt;a href="https://github.com/ulisses-alves/rollup-plugin-elm#readme" rel="noopener noreferrer"&gt;&lt;code&gt;rollup-plugin-elm&lt;/code&gt;&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Vite: &lt;a href="https://github.com/hmsk/vite-plugin-elm" rel="noopener noreferrer"&gt;&lt;code&gt;vite-plugin-elm&lt;/code&gt;&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you like using Parcel, then you're in luck! Parcel provides &lt;a href="https://parceljs.org/languages/elm" rel="noopener noreferrer"&gt;built-in support for Elm&lt;/a&gt; by default, no plugins required. There had been some issues in Parcel 2 and Elm, but at this point they appear to be worked out, making Parcel an amazing choice if you're expecting interop with Javascript and want a great experience.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;elm-tooling&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Great! Now we're able to import our Elm code directly into our existing applications, making the integration that much easier. However, there's still one last thing we can do to improve our environment. One problem we may run into is that the tooling described above needs to be installed locally in order to utilize it, which can lead to more work onboarding developers or getting started with a project.&lt;/p&gt;

&lt;p&gt;There is a tool called &lt;a href="https://elm-tooling.github.io/elm-tooling-cli/" rel="noopener noreferrer"&gt;&lt;code&gt;elm-tooling&lt;/code&gt;&lt;/a&gt; which does all the hard work for us. Rather than having to install the Elm language, &lt;code&gt;elm-format&lt;/code&gt;, and &lt;code&gt;elm-test&lt;/code&gt; (or &lt;code&gt;elm-test-rs&lt;/code&gt;) individually, we can run &lt;code&gt;npx elm-tooling install&lt;/code&gt;, and all of the core tooling can be installed at once, significantly faster than if they were installed individually. This can even be added as a postinstall command in your &lt;code&gt;package.json&lt;/code&gt;, ensuring that Elm and its related tools are available with a single install command. This is extremely useful when setting up your local environment, or during CI build steps.&lt;/p&gt;

&lt;h2&gt;
  
  
  Frameworks and Templates
&lt;/h2&gt;

&lt;p&gt;All this tooling is great, and an excellent way to get writing Elm code and integrating it into your application. But sometimes it's nice to start from a higher level abstraction, much like React developers often start with Create React App, or Vue developers use the Vue CLI. While there are no official templates or frameworks beyond what we've already talked about, there are a few interesting projects that are worth taking a look at.&lt;/p&gt;

&lt;h3&gt;
  
  
  elm-spa
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://www.elm-spa.dev/" rel="noopener noreferrer"&gt;elm-spa&lt;/a&gt; is a great way to start working on a single-page Elm application. Browser navigation and handling multiple pages is something that is a bit more difficult in Elm than in pure Javascript, and as such requires more boilerplate code in order to make it work. elm-spa handles all of the complexities of building an SPA for you, while providing a number of great features for building a modern web application.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.lindsaykwardell.com%2Fblog%2Felm-spa-image.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.lindsaykwardell.com%2Fblog%2Felm-spa-image.png" alt="Build reliable applications with Elm. With elm-spa, you can create production-ready application with one command: npx elm-spa new. No need to configure webpack, gulp, or any other NPM dev tools. This zero-configuration CLI comes with a live-reloading dev server, production-ready build commands, and even a few scaffolding commands for new and existing applications."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  elm-pages
&lt;/h3&gt;

&lt;p&gt;Where elm-spa is targeting single-page applications, &lt;a href="https://elm-pages.com/" rel="noopener noreferrer"&gt;elm-pages&lt;/a&gt; is built for generating static sites. Utilizing Elm's type system, elm-pages's goal is to make handling external data for a static site easy. Conceptually, it's similar to what Gatsby or Gridsome do (provide a typesafe interface to access multiple types of data), but utilizing Elm's strengths rather than adding an additional type system such as GraphQL.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.lindsaykwardell.com%2Fblog%2Felm-pages-image.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.lindsaykwardell.com%2Fblog%2Felm-pages-image.png" alt="Pull in typed Elm data to your pages. Whether your data is coming from markdown files, APIs, a CMS, or all of the above, elm-pages lets you pull in just the data you need for a page. No loading spinners, no Msg or update logic, just define your data and use it in your view."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  ElmBook
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://elm-book-in-elm-book.netlify.app/overview" rel="noopener noreferrer"&gt;ElmBook&lt;/a&gt;'s goal is to provide a solid documentation site for library or package code. It includes a search function, as well as the ability to break down your docs into chapters and books. It also allows rendering Elm code within your docs, via a shared model, so that you can demonstrate functionality in the UI. It's built on top of elm-live, so working with ElmBook should feel familiar if you've used it before.&lt;/p&gt;

&lt;p&gt;If you're coming from Vue, the focus of ElmBook is similar to Vuepress, but rather than focusing on markdown files, ElmBook is written within Elm itself. Markdown is supported for writing the documentation itself, but all of your work will be handled within the Elm code, making your documentation perfectly typesafe as well as useful.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Every application, tool or team has their own history worth telling.&lt;/p&gt;

&lt;p&gt;ElmBook tries to help them by making it easy to create rich documents that showcase their libraries documentations, UI components, design tokens, or anything else their creativity comes up with.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;vite-elm-template&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;This was a template I created to scratch my own itch. As a Vue developer, I love working with Vite, and really wanted a base template for Vite that I could use to build Elm applications. &lt;a href="https://github.com/lindsaykwardell/vite-elm-template" rel="noopener noreferrer"&gt;&lt;code&gt;vite-elm-template&lt;/code&gt;&lt;/a&gt; is a basic Vite template intended to get you started writing in Elm without having to spend time configuring everything yourself. Unlike elm-pages or elm-spa, it's not a framework of any sort. If you want to bring in single-page application features, or other functionalities, you will have to build those in yourself. That said, it's perfect for getting started with a basic environment.&lt;/p&gt;

&lt;p&gt;As of this writing it includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Hot Module Reload of all code in the app (including Elm)&lt;/li&gt;
&lt;li&gt;Tooling installation via &lt;code&gt;elm-tooling&lt;/code&gt;

&lt;ul&gt;
&lt;li&gt;Includes Elm, &lt;code&gt;elm-format&lt;/code&gt;, &lt;code&gt;elm-json&lt;/code&gt;, and &lt;code&gt;elm-test-rs&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Basic unit test examples&lt;/li&gt;

&lt;li&gt;Github Actions CI for running tests&lt;/li&gt;

&lt;li&gt;Recommends the Elm VS Code extension&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;If Vite is something you enjoy working with, I hope you'll check it out! There is also a link in the README to set up a workspace using Gitpod if you want to try things out in a sandbox environment first.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;I hope that helps you get started building an Elm application! Elm is an amazing language to learn and work with, and the tooling that exists to support it today makes working with it as delightful an experience as the language itself provides. Above all, I hope you enjoy learning and working with Elm in 2022!&lt;/p&gt;

&lt;p&gt;If you're new to Elm, and would like to learn more about it, check out the &lt;a href="https://guide.elm-lang.org/" rel="noopener noreferrer"&gt;official Elm Guide&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>elm</category>
      <category>spa</category>
      <category>vite</category>
    </item>
    <item>
      <title>Astro 0.21 - Upgrade Experience</title>
      <dc:creator>Lindsay Wardell</dc:creator>
      <pubDate>Sat, 20 Nov 2021 23:39:45 +0000</pubDate>
      <link>https://forem.com/lindsaykwardell/astro-021-upgrade-experience-39j</link>
      <guid>https://forem.com/lindsaykwardell/astro-021-upgrade-experience-39j</guid>
      <description>&lt;p&gt;The day has finally arrived! &lt;a href="https://astro.build/blog/astro-021-release/" rel="noopener noreferrer"&gt;Astro has released 0.21 publicly&lt;/a&gt;, and it's a big change. Most important of all, they've migrated the development environment from Snowpack to &lt;a href="https://vitejs.dev/" rel="noopener noreferrer"&gt;Vite&lt;/a&gt;, which opens up a whole slew of new features and plugins to the Astro ecosystem. There are also a number of other changes, which are listed in the annoucement &lt;a href="https://docs.astro.build/migration/0.21.0/" rel="noopener noreferrer"&gt;and migration guide&lt;/a&gt;. If you're looking to upgrade your site, I highly encourage you to check out those resources first!&lt;/p&gt;

&lt;p&gt;Rather than guide you through the upgrade process (their migration doc is pretty good for that), I want to document my experience. I ran into a number of interesting bumps along the way, and I think it would be beneficial to share my experience with anyone else interested in performing their upgrade.&lt;/p&gt;

&lt;p&gt;For context on my site, I am mostly using Astro componets (with the &lt;code&gt;.astro&lt;/code&gt; file extension), with a couple small Vue components. Most of the issues I ran into were related to Astro components, and the changed API. I did not have to do anything related to file imports, React, or TSX/JSX.&lt;/p&gt;

&lt;p&gt;Also, if you're interested, &lt;a href="https://github.com/lindsaykwardell/lindsaykwardell/pull/25" rel="noopener noreferrer"&gt;here's the PR&lt;/a&gt; I used to track my changes as I performed the upgrade.&lt;/p&gt;

&lt;p&gt;With that out of the way, let's upggrade to Astro 0.21!&lt;/p&gt;

&lt;h2&gt;
  
  
  Update Dependencies
&lt;/h2&gt;

&lt;p&gt;The first thing I did was update my &lt;code&gt;package.json&lt;/code&gt; to use the new dependencies. This was a straightforward change using &lt;code&gt;npm outdated&lt;/code&gt; and manually updating/removing unused packages. Besides updating Astro and the Vue renderer to the latest version, I removed &lt;code&gt;@snowpack/plugin-dotenv&lt;/code&gt; because Astro now supports &lt;code&gt;.env&lt;/code&gt; files by default!&lt;/p&gt;

&lt;p&gt;Also, as Astro is no longer using Snowpack for its build tool, I had to update my environment variables as well, removing &lt;code&gt;SNOWPACK&lt;/code&gt; from the variable name.&lt;/p&gt;

&lt;p&gt;However, when I ran &lt;code&gt;npm run dev&lt;/code&gt;, I was presented with an error!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;Error: Build failed with 12 errors:
node_modules/fetch-blob/from.js:1:59: error: Could not resolve &lt;span class="s2"&gt;"node:fs"&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;mark it as external to exclude it from the bundle&lt;span class="o"&gt;)&lt;/span&gt;
node_modules/fetch-blob/from.js:2:25: error: Could not resolve &lt;span class="s2"&gt;"node:path"&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;mark it as external to exclude it from the bundle&lt;span class="o"&gt;)&lt;/span&gt;
node_modules/fetch-blob/from.js:3:31: error: Could not resolve &lt;span class="s2"&gt;"node:worker_threads"&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;mark it as external to exclude it from the bundle&lt;span class="o"&gt;)&lt;/span&gt;
node_modules/node-fetch/src/body.js:8:34: error: Could not resolve &lt;span class="s2"&gt;"node:stream"&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;mark it as external to exclude it from the bundle&lt;span class="o"&gt;)&lt;/span&gt;
node_modules/node-fetch/src/body.js:9:31: error: Could not resolve &lt;span class="s2"&gt;"node:util"&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;mark it as external to exclude it from the bundle&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This error was thrown because I was importing &lt;code&gt;node-fetch&lt;/code&gt; in a couple JS files. In Astro 0.21, &lt;a href="https://docs.astro.build/guides/data-fetching/" rel="noopener noreferrer"&gt;&lt;code&gt;fetch&lt;/code&gt; is now globally available&lt;/a&gt;, both inside and outside of &lt;code&gt;.astro&lt;/code&gt; components. Removing the import of &lt;code&gt;node-fetch&lt;/code&gt; solved the problem.&lt;/p&gt;

&lt;p&gt;However, I was presented with a different error!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;panic: Export statements must be placed at the top of .astro files!
panic: Export statements must be placed at the top of .astro files!
2:14:52 PM &lt;span class="o"&gt;[&lt;/span&gt;vite] Error when evaluating SSR module /src/layouts/BlogPost.astro:
    at /src/layouts/BaseLayout.astro
2:14:52 PM &lt;span class="o"&gt;[&lt;/span&gt;vite] Error when evaluating SSR module /src/pages/blog/a-song-unsung.md:
    at /src/layouts/BaseLayout.astro
2:14:52 PM &lt;span class="o"&gt;[&lt;/span&gt;vite] Error when evaluating SSR module /src/pages/index.astro:
    at /src/layouts/BaseLayout.astro
02:14 PM &lt;span class="o"&gt;[&lt;/span&gt;astro] 500 /                                        1753ms
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Astro now requires all exports (such as the Props interface) to be at the top of the file. This was a simple change, but not one documented in the migration guide. By either removing or moving the exported Props interface, the dev environment was able to load. However, something was missing...&lt;/p&gt;

&lt;p&gt;The content!&lt;/p&gt;

&lt;h2&gt;
  
  
  Missing Body
&lt;/h2&gt;

&lt;p&gt;When &lt;code&gt;localhost:3000&lt;/code&gt; came up, I was presented with a perfectly blank screen. I noticed that the head tag was filling in properly, but not the body. In Chrome devtools, nothing appeared between the &lt;code&gt;&amp;lt;body&amp;gt;&lt;/code&gt; tag.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;html&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
    &lt;span class="c"&gt;&amp;lt;!-- meta tags, script, etc --&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;body&amp;gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What's going on here? I'm honestly still not sure. If I updated my &lt;code&gt;index.astro&lt;/code&gt; file to have some extra text before calling &lt;code&gt;&amp;lt;BaseLayout&amp;gt;&lt;/code&gt;, the text would load, and the meta tags would be replicated inside the &lt;code&gt;&amp;lt;body&amp;gt;&lt;/code&gt; tag. The solution was to eliminate the &lt;code&gt;BaseHead&lt;/code&gt; component (which was only utilized by &lt;code&gt;BaseLayout&lt;/code&gt;), which placed the actual HTML tags in the layout component.&lt;/p&gt;

&lt;p&gt;However, I took a bit of a detour before coming to this solution, and realized that my Tailwind configuration was no longer working. This was thankfully a straightforward fix, although the documentation seemed to be spread across a couple pages.&lt;/p&gt;

&lt;p&gt;First, I had to manually install PostCSS and autoprefixer. Then, I updated my &lt;code&gt;postcss.config.cjs&lt;/code&gt; file to include postcss-nested, Tailwind, and autoprefixer (previously I had only needed to import postcss-nested). This reenabled Tailwind for my site, which I was able to validate after I fixed the missing body problem.&lt;/p&gt;

&lt;p&gt;However, something didn't look quite right&lt;/p&gt;

&lt;h2&gt;
  
  
  Missing Styles
&lt;/h2&gt;

&lt;p&gt;While the site loaded (and Tailwind properly initialized), a number of custom styles were missing.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--e7h8Wv9z--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://lindsaykwardell.com/blog/missing-styles.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--e7h8Wv9z--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://lindsaykwardell.com/blog/missing-styles.png" alt="lindsaykwardell.com. Dark mode is not working, and other styles are slightly off" width="800" height="438"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Crucially (for me), dark mode was completely nonfunctional. Some elements would switch style, but a majority (including the root background and any text) were not changing. Consulting the Chrome devtools revealed that my styles (using PostCSS) were not being properly processed before passing into the DOM. For example, this is what Astro was putting into the site (formatting added by me):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.blog-item.astro-EDZCIZOF&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="err"&gt;@apply&lt;/span&gt; &lt;span class="err"&gt;shadow-md&lt;/span&gt; &lt;span class="err"&gt;rounded-lg&lt;/span&gt; &lt;span class="err"&gt;relative&lt;/span&gt; &lt;span class="err"&gt;overflow-hidden&lt;/span&gt; &lt;span class="err"&gt;flex&lt;/span&gt; &lt;span class="err"&gt;justify-center;&lt;/span&gt;&lt;span class="nl"&gt;animation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="m"&gt;0.3s&lt;/span&gt; &lt;span class="n"&gt;ease-out&lt;/span&gt; &lt;span class="m"&gt;0s&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt; &lt;span class="n"&gt;shift&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="nc"&gt;.blog-image-wrapper.astro-EDZCIZOF&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="m"&gt;300px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="nl"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="m"&gt;100%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="nl"&gt;overflow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nb"&gt;hidden&lt;/span&gt;&lt;span class="p"&gt;;}&lt;/span&gt;&lt;span class="k"&gt;@keyframes&lt;/span&gt; &lt;span class="n"&gt;shift&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="err"&gt;0&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;translateY&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;30%&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="nl"&gt;opacity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;}&lt;/span&gt;&lt;span class="err"&gt;100&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;translateY&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="nl"&gt;opacity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;}}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Clearly, Astro was not properly applying PostCSS here. Tailwind's &lt;code&gt;@apply&lt;/code&gt; rule was being ignored and sent straight to the compiled CSS. The solution here was to export these global styles into the &lt;code&gt;global.css&lt;/code&gt; file, instead of inline on my &lt;code&gt;BaseLayout.astro&lt;/code&gt; component. That's fine, it's probably better this way anyway.&lt;/p&gt;

&lt;p&gt;Navigating around the site, I kept noticing that other styles were also failing:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--1a9uoBTS--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://www.lindsaykwardell.com/blog/missing-styles-2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--1a9uoBTS--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://www.lindsaykwardell.com/blog/missing-styles-2.png" alt="Blog post preview card, the hero image is too short compared to what it should be" width="800" height="714"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--zfQ55bx9--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://www.lindsaykwardell.com/blog/missing-styles-3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--zfQ55bx9--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://www.lindsaykwardell.com/blog/missing-styles-3.png" alt="PrismJS styling is not being applied" width="800" height="715"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The solution, as before, was to pull out the inline CSS into external files. I wasn't a huge fan of this solution, but it worked. Styles looked as they were supposed to, and life was good.&lt;/p&gt;

&lt;p&gt;Except the site still wouldn't build.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bad CSS!
&lt;/h2&gt;

&lt;p&gt;Everything looks as it should, so I tried to deploy to Netlify. However, an error was still popping up:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--562eCMXb--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://lindsaykwardell.com/blog/css-syntax-error.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--562eCMXb--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://lindsaykwardell.com/blog/css-syntax-error.png" alt="Build error showing that a CSS value was invalid, due to an extra dot after a semicolon." width="800" height="98"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Unknown word? That doesn't make se... oh. OH. There's an extra period after a semicolon. That's weird, I didn't write anything like that. And besides, that's coming from &lt;code&gt;about.css&lt;/code&gt;, which isn't a file I created. What's going on?&lt;/p&gt;

&lt;p&gt;Doing a bit of searching, I found another style block that I had missed in my previous cleanup, but this one was scoped to a component. I'd much rather not extract it from the Astro component, considering that's the whole point of scoped CSS in a component (as a Vue fan, I'm a proponent of this approach).&lt;/p&gt;

&lt;p&gt;I reached out on the Astro Discord channel (which I highly recommend if you are using Astro), and reported the issue.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--YUt8S2-3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://www.lindsaykwardell.com/blog/discord-postcss-discussion.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--YUt8S2-3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://www.lindsaykwardell.com/blog/discord-postcss-discussion.png" alt="Discord chat discussion between Lindasy and Nate regarding PostCSS" width="800" height="498"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We quickly discovered that the cause of the problem was that I had set the language for the style block to &lt;code&gt;postcss&lt;/code&gt;, which was causing issues with the Astro compiler. Switching to &lt;code&gt;pcss&lt;/code&gt; solved the problem, and I was able to find and update the remaining scoped CSS blocks to correctly use Postcss. (If you're interested, &lt;a href="https://discord.com/channels/830184174198718474/911628265528119299/911638741951590440" rel="noopener noreferrer"&gt;here's the link&lt;/a&gt; to the Discord thread). I also moved back a number of styles to their components, leaving just the actual global styles in &lt;code&gt;global.css&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Success?
&lt;/h2&gt;

&lt;p&gt;With that, I was able to deploy an updated version of the site, now powered by Astro 0.21. My tests were passing, visuals looked correct, and the build ran locally without errors. Let's send it up to Netlify for deployment!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;7:58:22 AM: &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; astro build
7:58:23 AM: 03:58 PM &lt;span class="o"&gt;[&lt;/span&gt;config] Set &lt;span class="s2"&gt;"buildOptions.site"&lt;/span&gt; to generate correct canonical URLs and sitemap
7:58:26 AM: warn - You have enabled the JIT engine which is currently &lt;span class="k"&gt;in &lt;/span&gt;preview.
7:58:26 AM: warn - Preview features are not covered by semver, may introduce breaking changes, and can change at any time.
7:58:31 AM: &lt;span class="o"&gt;[&lt;/span&gt;object Object]
7:58:31 AM: Error: &lt;span class="o"&gt;[&lt;/span&gt;object Object]
7:58:31 AM: npm ERR! code ELIFECYCLE
7:58:31 AM: npm ERR! errno 1
7:58:31 AM: npm ERR! lindsaykwardell@4.0.0 generate: &lt;span class="sb"&gt;`&lt;/span&gt;astro build&lt;span class="sb"&gt;`&lt;/span&gt;
7:58:31 AM: npm ERR! Exit status 1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What a lovely error! Thankfully, this was my fault, not Astro or Netlify. I had forgotten to update my environment variables on Netlify, causing the build to fail (that object is probably a JS fetch error). Easy fix, and it was good to go. The build went up, deployment was successful, and... my JS wasn't loading.&lt;/p&gt;


&lt;blockquote&gt;
&lt;p&gt;Having some trouble with &lt;a href="https://twitter.com/Netlify?ref_src=twsrc%5Etfw" rel="noopener noreferrer"&gt;@Netlify&lt;/a&gt; and &lt;a href="https://twitter.com/astrodotbuild?ref_src=twsrc%5Etfw" rel="noopener noreferrer"&gt;@astrodotbuild&lt;/a&gt; 0.21. For some reason, I'm unable to fetch my JS files for partial hydration because they're blocked by CORS. It looks like files are being stored on Cloudfront instead of my domain.&lt;br&gt;&lt;br&gt;Anyone run into something like this before?&lt;/p&gt;— Lindsay Wardell 🏳️‍⚧️ (&lt;a class="mentioned-user" href="https://dev.to/lindsaykwardell"&gt;@lindsaykwardell&lt;/a&gt;) &lt;a href="https://twitter.com/lindsaykwardell/status/1462173643249651713?ref_src=twsrc%5Etfw" rel="noopener noreferrer"&gt;November 20, 2021&lt;/a&gt;
&lt;/blockquote&gt; 

&lt;p&gt;This was slightly more complex, but had an easy solution. The build was working as expected locally, I could see the JS files were present (and clearly being cached by Netlify on Cloudfront). I didn't have any build plugins enabled that modified the JS, and (most confusing) everything worked as expected on Astro 0.20.&lt;/p&gt;

&lt;p&gt;After doing some googling, I found the problem in a &lt;a href="https://answers.netlify.com/t/no-access-control-allow-origin-header/14631" rel="noopener noreferrer"&gt;Netlify support thread&lt;/a&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Yup - that is saying that cloudfront doesn’t have your custom header which is true! I think you’re using asset optimization on the site, and that will clash with custom headers - we can’t set them on those resources. So, you could turn off that feature in your build and deploy settings and that would remove the problem.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I'm not certain what custom header is being set by Astro/Netlify/wherever, but between 0.20 and 0.21 a custom header was being applied, which broke the JS fetch when going across domains. I went into my settings, and disabled the JS minification and bundling offered by Netlify. At last, everything actually, truly worked.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;As upgrades go, this was honestly pretty straightforward. I started working on it late Friday night, and was able to finish Saturday morning. No major rewrites were required, just some updated syntax and moving some CSS around. And best of all, Astro is now powered by Vite, so I can start trying out the plugins I'm more familiar with on my site (rather than looking deeper into the Snowpack ecosystem).&lt;/p&gt;

&lt;p&gt;If you're looking to migrate your own site, I strongly encourage checking out the &lt;a href="https://docs.astro.build/migration/0.21.0/" rel="noopener noreferrer"&gt;Migration Guide&lt;/a&gt;, and &lt;a href="https://docs.astro.build/migration/0.21.0/" rel="noopener noreferrer"&gt;the blog post&lt;/a&gt; explaining the Astro team's reasoning for making the shift from Snowpack to Astro.&lt;/p&gt;

&lt;p&gt;If you haven't tried Astro out yet, &lt;a href="https://lindsaykwardell.com/blog/rebuilding-site-with-astro" rel="noopener noreferrer"&gt;I wrote about my experience&lt;/a&gt; migrating from Nuxt static site to Astro, and the performance benefits I immediately saw by migrating. I definitely recommend checking out Astro if you're interested in static site generation.&lt;/p&gt;

&lt;p&gt;Until next time!&lt;/p&gt;

</description>
      <category>astro</category>
      <category>javascript</category>
      <category>ssg</category>
    </item>
    <item>
      <title>From Nuxt to Astro - Rebuilding with Astro</title>
      <dc:creator>Lindsay Wardell</dc:creator>
      <pubDate>Sun, 10 Oct 2021 03:45:21 +0000</pubDate>
      <link>https://forem.com/lindsaykwardell/from-nuxt-to-astro-rebuilding-with-astro-5ann</link>
      <guid>https://forem.com/lindsaykwardell/from-nuxt-to-astro-rebuilding-with-astro-5ann</guid>
      <description>&lt;p&gt;I don't remember exactly when I started hearing about &lt;a href="https://astro.build" rel="noopener noreferrer"&gt;Astro&lt;/a&gt;, one of the latest static site generators to help tackle the problem of building sites with less Javascript. The problem is one we're all familiar with - how can I build a static site (in my case, my personal site) using the languages and tools I know best, while performing at its best? After migrating from Wordpress, I first tried &lt;a href="https://www.gatsbyjs.com/" rel="noopener noreferrer"&gt;Gatsby&lt;/a&gt;, then &lt;a href="https://gridsome.org/" rel="noopener noreferrer"&gt;Gridsome&lt;/a&gt;, and most recently &lt;a href="https://nuxtjs.org/" rel="noopener noreferrer"&gt;Nuxt&lt;/a&gt;. All of these are excellent tools, and I highly recommend them. But one thing that is the same across all of them is that they are tied to their specific framework (React or Vue).&lt;/p&gt;

&lt;p&gt;Astro does away with that, and it's one of the things that really drew me to the framework. From their site:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;It’s time to accept that the framework wars won’t have a winner — that’s why Astro lets you use any framework you want (or none at all). And if most sites only have islands of interactivity, shouldn’t our tools optimize for that? We’re not the first to ask the question, but we might be the first with an answer for every framework.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This caught my interest. The idea of "framework wars" having a winner never made sense to me. None of these tools - React, Vue, Svelte, Angular - need to be the overall winner to make developers productive. Having a winner at all would mean that innovation is stalled, at best. The fact that Astro allows you to utilize whichever framework is most comfortable means that it can adjust to whatever change comes in the future, and focus more on what it does best: building static assets.&lt;/p&gt;

&lt;p&gt;And so, as one does, I decided to rewrite my personal site from Nuxt to Astro.&lt;/p&gt;

&lt;h2&gt;
  
  
  Performance Woes
&lt;/h2&gt;

&lt;p&gt;I should say, before going too much further, that I love Nuxt as a framework. I think it's an amazing tool, and I realize that, as I write this, we are days away from the release of Nuxt 3's public beta.&lt;/p&gt;

&lt;p&gt;That said, I've been running a number of sites with Nuxt in static site mode, and each of them has some odd quirks that I've never been able to fully work out. One site, a single page in the truest sense with only a bit of reactivity, was constantly reporting Typescript errors in VS Code. This was because the VS Code plugins (either Vetur or Volar) did not recognize that Nuxt's &lt;code&gt;asyncData&lt;/code&gt; method returned state to the Vue object. This is not Nuxt's fault, but it made things annoying.&lt;/p&gt;

&lt;p&gt;A second site (which is purely static assets, almost no JS interaction in the browser) had an issue that when code was updated, any content fetched with &lt;a href="https://content.nuxtjs.org/" rel="noopener noreferrer"&gt;Nuxt's Content module&lt;/a&gt; would be missing after the hot module reload finished. I found a workaround, and it's not a huge deal, but it's annoying.&lt;/p&gt;

&lt;p&gt;My personal site uses data from multiple sources, including Github and a few podcasts RSS feeds. Using Nuxt, I was doing more data fetching on render than I wanted. This hadn't been an issue with either Gatsby or Gridsome, and I expect that if I had explored &lt;code&gt;buildModules&lt;/code&gt; more closely I could have found a solution. As it was, some pages had to fetch content on the client, and when that content is split between multiple endpoints, it made things slow.&lt;/p&gt;

&lt;p&gt;All of these sites, from the smallest to the largest, had one unifying issue: Lighthouse performance scores were never great. Below are my Lighthouse scores for this site before migrating from Nuxt:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Flindsaykwardell.com%2Fblog%2Fnuxt-lighthouse.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Flindsaykwardell.com%2Fblog%2Fnuxt-lighthouse.png" alt="Nuxt-based site Lighthouse scores. Performance: 57, Accessibility: 79, Best Practices: 93, SEO: 100"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This was done on my home page, on a fresh instance of Chrome with no plugins installed, in order to get the closest to a clean reading. The home page is loading a handful of images (language icons, my profile image), my latest blog post, and a few SVGs for social icons courtesy of Font Awesome. Data was also being fetched from Github's GraphQL API to get my profile's description, pinned repositories, and a few other details.&lt;/p&gt;

&lt;p&gt;Here's the breakdown of the performance score:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Flindsaykwardell.com%2Fblog%2Fnuxt-performance.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Flindsaykwardell.com%2Fblog%2Fnuxt-performance.png" alt="Performance metrics. First contentful paint: 2.0s, Time to Interactive: 6.3s, Speed Index: 2.3s, Total Blocking Time: 150ms, Largest Contentful Paint: 7.4s, Cumulative Layout Shift: 0.47"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Of these scores, the Largest Contentful Paint and Time to Interactive stood out to me the most. This is a mostly static page, with a number of links and one button (to toggle dark mode). Why was Nuxt taking so long to be interactive?&lt;/p&gt;

&lt;p&gt;Looking at my Network requests, it looks to me like Nuxt is mostly fetching Javascript, and then spending its time executing it. I made a few notes to see what I was looking at. On a typical page load, I had:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;37 unique requests&lt;/li&gt;
&lt;li&gt;6.7MB of resources loaded (including images)&lt;/li&gt;
&lt;li&gt;Load time: 2.5s&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What could I do to cut down on all of this data fetching and Javascript execution?&lt;/p&gt;

&lt;h2&gt;
  
  
  Time for Less Javascript
&lt;/h2&gt;

&lt;p&gt;This is where Astro caught my attention. On their home page, they say:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;For a technology built on top of three different languages, the modern web seems to focus an awful lot on JavaScript. We don’t think it has to—and that’s certainly not a revolutionary concept.&lt;/p&gt;

&lt;p&gt;We’ll eagerly jump at the chance to sing JavaScript’s praises, but HTML and CSS are pretty great too. There aren’t enough modern tools which reflect that, which is why we're building Astro.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Astro is a framework that is focused primarily on fetching your data from whichever source or sources you use, injecting it into an HTML template, and building static assets from it. While Astro is built on Javascript, it doesn't focus on sending Javascript to the client. Any functionality you want can still be brought in, whether that's vanilla JS, React, Vue, or something else.&lt;/p&gt;

&lt;p&gt;This way of building a static site feels very comfortable and familiar to me. I started web development in HTML, CSS, and PHP, and avoided Javascript at all costs for many years (both before and after jQuery came onto the scene). Rendering HTML and CSS to the client is what I did, with some logic involved to perform simple tasks like displaying a list of elements or fetching data from a database. Using Astro, it's basically the same thing, just using Javascript instead of PHP.&lt;/p&gt;

&lt;p&gt;Here's an example of my main blog page, which renders a list of blog posts. Astro uses a unique syntax that combines the look and feel of Markdown, JSX, and standard HTML. All build time Javascript is handled in a 'frontmatter'-like block at the top of the file, and the static template is built below that.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="o"&gt;---&lt;/span&gt;
&lt;span class="c1"&gt;// Import components&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;BaseLayout&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;../layouts/BaseLayout.astro&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;BlogPostPreview&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;../components/BlogPostPreview.astro&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Fetch posts&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;allPosts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Astro&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetchContent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./blog/*.md&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;post&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; 
        &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;date&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sort&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; 
        &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;date&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;valueOf&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;date&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;valueOf&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Render to HTML&lt;/span&gt;
&lt;span class="o"&gt;---&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;BaseLayout&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;flex flex-col lg:flex-row flex-wrap&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;allPosts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;post&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;w-full lg:w-1/2 xl:w-1/3 p-4&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;BlogPostPreview&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;    &lt;span class="p"&gt;))}&lt;/span&gt;
  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/BaseLayout&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This may look familiar to someone who has used React before, with just a few oddities (no key on the mapped JSX? Extra dashes between the head and the return?), but it's important to remember that the result of this is pure HTML. No Javascript will ever be parsed on the client from this snippet. These components are all written with Astro's unique syntax, but the same is true when using React, Vue, or anything else: only static HTML and CSS would result from rendering this page.&lt;/p&gt;

&lt;p&gt;But what if you want to load some Javascript? What if you need some client side interaction?&lt;/p&gt;

&lt;h2&gt;
  
  
  Partial Hydration
&lt;/h2&gt;

&lt;p&gt;Astro promotes the concept of &lt;a href="https://docs.astro.build/core-concepts/component-hydration" rel="noopener noreferrer"&gt;Partial Hydration&lt;/a&gt;. From Astro's documentation:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Astro generates every website with zero client-side JavaScript, by default. Use any frontend UI component that you’d like (React, Svelte, Vue, etc.) and Astro will automatically render it to HTML at build-time and strip away all JavaScript. This keeps every site fast by default.&lt;/p&gt;

&lt;p&gt;But sometimes, client-side JavaScript is required. This guide shows how interactive components work in Astro using a technique called partial hydration.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Most sites do not need to be fully controlled by Javascript. This concept of partial hydration leans into that. Using my personal site as an example, the only dynamic portion of the site is toggling dark mode. In the Nuxt version of the site, I was reliant on the Nuxt runtime to toggle light and dark mode. To be frank, that's overkill for a static site. I shouldn't have to render an entire SPA just to toggle dark mode, right?&lt;/p&gt;

&lt;p&gt;On their page about partial hydration, the Astro docs reference &lt;a href="https://jasonformat.com/islands-architecture/" rel="noopener noreferrer"&gt;Jason Miller's blog post on the idea of an Islands Architecture&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;In an “islands” model, server rendering is not a bolt-on optimization aimed at improving SEO or UX. Instead, it is a fundamental part of how pages are delivered to the browser. The HTML returned in response to navigation contains a meaningful and immediately renderable representation of the content the user requested.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Rather than load an entire SPA to handle a small portion of functionality, Vue can target a much smaller section of the DOM, and render only the portion of the application that I need (in this case, a button and some JS to toggle dark mode). Vue supports this usage by default, but in the world of frameworks we tend to forget this. A number of recent episodes of Views on Vue have explored this concept, including &lt;a href="https://viewsonvue.com/using-vue-with-an-spa-with-ariel-dorol-vue-159" rel="noopener noreferrer"&gt;using Vue without an SPA&lt;/a&gt; and &lt;a href="https://viewsonvue.com/building-micro-frontends-with-lawrence-almeida-vue-160" rel="noopener noreferrer"&gt;building micro frontends&lt;/a&gt;. &lt;a href="https://lists.wikimedia.org/hyperkitty/list/wikitech-l@lists.wikimedia.org/thread/SOZREBYR36PUNFZXMIUBVAIOQI4N7PDU/" rel="noopener noreferrer"&gt;The Wikimedia Foundation also uses Vue this way&lt;/a&gt;, rendering client-side functionality on top of an existing PHP monolith (&lt;a href="https://viewsonvue.com/adoping-vue-at-wikimedia-with-eric-gardner-vue-165" rel="noopener noreferrer"&gt;listen to my discussion with Eric Gardner&lt;/a&gt; to learn more).&lt;/p&gt;

&lt;p&gt;When viewed in this way, performance is almost a byproduct of following best practices with Astro. For my personal site, I only needed a simple button to toggle dark mode. While I know this could be handled just as easily with vanilla JS, I wanted to try using Vue to build an island of functionality. Here's my Vue component:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;template&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt;
    &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"dark-mode-button"&lt;/span&gt;
    &lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="na"&gt;click=&lt;/span&gt;&lt;span class="s"&gt;"toggleDarkMode"&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    {{ isDark ? "🌙" : "☀️" }}
  &lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/template&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;lang=&lt;/span&gt;&lt;span class="s"&gt;"ts"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;data&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;isDark&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;localStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;darkMode&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;true&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;methods&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;toggleDarkMode&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isDark&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isDark&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;watch&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;isDark&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;localStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;darkMode&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isDark&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;html&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;html&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isDark&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;classList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;dark&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;classList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;dark&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And here's an example of how I'm using the component:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="o"&gt;---&lt;/span&gt;
&lt;span class="c1"&gt;// Import the Vue component into an Astro component&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;DarkModeButton&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;../components/DarkModeButton.vue&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="o"&gt;---&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;html&lt;/span&gt; &lt;span class="nx"&gt;lang&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;en&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt; &lt;span class="c"&gt;&amp;lt;!--&lt;/span&gt; &lt;span class="nx"&gt;the&lt;/span&gt; &lt;span class="nx"&gt;rest&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;my&lt;/span&gt; &lt;span class="nx"&gt;template&lt;/span&gt; &lt;span class="o"&gt;--&amp;gt;&lt;/span&gt;
    &lt;span class="c"&gt;&amp;lt;!--&lt;/span&gt; &lt;span class="nx"&gt;Display&lt;/span&gt; &lt;span class="nx"&gt;the&lt;/span&gt; &lt;span class="nx"&gt;Vue&lt;/span&gt; &lt;span class="nx"&gt;component&lt;/span&gt; &lt;span class="o"&gt;--&amp;gt;&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;DarkModeButton&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nx"&gt;only&lt;/span&gt; &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/body&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/html&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, I'm using Astro's &lt;code&gt;client:only&lt;/code&gt; directive. This tells Astro that it should be hydrated on the client, so that the Javascript will be executed. In this case, because the component is accessing the &lt;code&gt;window&lt;/code&gt; element, I want to make sure it doesn't get executed during buildtime. The best part is that, within the Astro component, it just asks like a normal component that can accept props.&lt;/p&gt;

&lt;p&gt;Astro has a number of renderers, and at the recent &lt;a href="https://youtu.be/gpTbH469Qog?t=5756" rel="noopener noreferrer"&gt;Vue Contributor Days&lt;/a&gt;, Fred Schott said that first-class Vue support is very important to the Astro team, and that it comes out of the box when working with Astro. You do need to add the renderer to your Astro configuration, but that's all that is required to enable Vue components.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Results
&lt;/h2&gt;

&lt;p&gt;Rewriting my personal site took about a week. Most of my templating was migrated from Vue to Astro components (although, as noted above, this wasn't a requirement to make the switch), with a couple Vue components for interactivity. The migration itself went very smoothly, especially since Astro support PostCSS (and therefore Tailwind) via a plugin for Snowpack. The benefits of prefetching the data and generating static HTML were obvious very early on, and the ability to mix basic HTML and CSS with Vue components was very straightforward.&lt;/p&gt;

&lt;p&gt;After I finished and deployed, I ran Lighthouse on the finished migration. Here are the results:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Flindsaykwardell.com%2Fblog%2Fastro-lighthouse.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Flindsaykwardell.com%2Fblog%2Fastro-lighthouse.png" alt="Astro-based site Lighthouse scores: Performance: 100, Accessibility: 95, Best Practices: 100, SEO: 100"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And here's the performance results:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Flindsaykwardell.com%2Fblog%2Fastro-performance.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Flindsaykwardell.com%2Fblog%2Fastro-performance.png" alt="Performance metrics. First contentful paint: 1.6s, Time to Interactive: 1.6s, Speed Index: 1.6s, Total Blocking Time: 0ms, Largest Contentful Paint: 1.6s, Cumulative Layout Shift: 0"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Much better! Because everything is being loaded as HTML and CSS, rather than utilizing a JavaScript framework to render the page, everything is much faster.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Astro is a relatively new tool for building static sites, but it's already gaining a lot of traction. Astro recently won the &lt;a href="https://www.netlify.com/blog/2021/10/06/jammies-award-winners-2021/" rel="noopener noreferrer"&gt;Ecosystem Innovation Award&lt;/a&gt; as part of Jamstack Conf 2021. From the linked page:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This year’s ecosystem innovation award goes to Astro, an innovative Jamstack platform that lets you build faster websites with less client-side JavaScript. They make it possible for developers to build fully functional sites with any framework of their choice or none at all. Astro offers the best of both worlds when it comes to lightweight static sites generators like 11ty and bundle-heavy alternatives like Next and Svelte Kit.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I'm really excited to see where Astro goes in the future. One item on their roadmap is to include server-side rendering, which I'm very excited for personally. I look forward to seeing what else comes out of this very interesting project.&lt;/p&gt;

&lt;p&gt;Feel free to look at &lt;a href="https://github.com/lindsaykwardell/lindsaykwardell" rel="noopener noreferrer"&gt;the repository for this site&lt;/a&gt; to view the code, and compare it against the Nuxt equivalent (in the Git history). If you want to learn more about Astro, check out their site at &lt;a href="https://astro.build" rel="noopener noreferrer"&gt;astro.build&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>vue</category>
      <category>ssg</category>
      <category>astro</category>
    </item>
    <item>
      <title>Build a Static Comment System</title>
      <dc:creator>Lindsay Wardell</dc:creator>
      <pubDate>Sun, 15 Nov 2020 14:50:12 +0000</pubDate>
      <link>https://forem.com/lindsaykwardell/build-a-static-comment-system-kij</link>
      <guid>https://forem.com/lindsaykwardell/build-a-static-comment-system-kij</guid>
      <description>&lt;h2&gt;
  
  
  From Wordpress to Jamstack
&lt;/h2&gt;

&lt;p&gt;Back when I first started my own blog, I did what many still do today and deployed a Wordpress site. Honestly, Wordpress is great. If you're looking into setting up your own site, it's a fine option! The main problem I had with it, however, was relying on another service to host my posts, my images, everything. What if my hosting provider were to shut down? How could I migrate from their MySQL database to another easily? What would I do with all of my content?&lt;/p&gt;

&lt;p&gt;This actually happened to me, when I needed to migrate from one provider to another. The solution - abandon everything, and start from scratch. A migration wasn't possible to my new host, so I copied everything into a text file and started over on the site. &lt;/p&gt;

&lt;p&gt;Then I learned about Gatsby, and that I could have a static site where my blog posts are all stored in text files. That sounds like a win! I could control my posts, my site, my content, and host it anywhere. This sounded exactly like what I wanted to do. I looked at headless Wordpress, but decided I wanted full control of the site. I built out a first version of the site with Gatsby, deployed it to Netlify, and life was good.&lt;/p&gt;

&lt;p&gt;Except...&lt;/p&gt;

&lt;p&gt;What about comments?&lt;/p&gt;

&lt;h2&gt;
  
  
  Static Comments??
&lt;/h2&gt;

&lt;p&gt;I've never had a super popular blog, but having a comment system felt important to build a complete blog. The options that are out there are... okay, but most of them didn't actually match what I was going for. I settled on Disqus, but the fact that I couldn't host it, plus the tie-in to another service meant that it felt antithetical to hosting a static site.&lt;/p&gt;

&lt;p&gt;After doing some research, I found &lt;a href="https://staticman.net/" rel="noopener noreferrer"&gt;Staticman&lt;/a&gt;. Quoting from their homepage, "Staticman handles user-generated content for you and transforms it into data files that sit in your GitHub repository, along with the rest of your content." This concept spoke to me. I did some research into using this approach, but at the time, it looked like the service had grown too fast, and comments were processing too slowly, if at all. Hopefully they've fixed it by now, but again, it's another service to rely on.&lt;/p&gt;

&lt;p&gt;All of this research, however, led me to a decision. I'm a developer; I can build this myself!&lt;/p&gt;

&lt;h2&gt;
  
  
  Jamstack to the Rescue!
&lt;/h2&gt;

&lt;p&gt;My goals for this project:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Accept input from a user&lt;/li&gt;
&lt;li&gt;Process that into a text file&lt;/li&gt;
&lt;li&gt;Commit that text file into a Github repository.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I'm already hosted on Netlify, so accepting user input is straightforward. Netlify offers form submission (&lt;a href="https://www.netlify.com/products/forms/" rel="noopener noreferrer"&gt;read more here&lt;/a&gt;). In short, by adding some basic attributes to a form, you can enable a POST request to your site that Netlify will capture and process. I'm using Vue, so I turned to &lt;a href="https://vueformulate.com/" rel="noopener noreferrer"&gt;Vue Formulate&lt;/a&gt; to build the form, and &lt;a href="https://vuetensils.stegosource.com/" rel="noopener noreferrer"&gt;Vuetensils&lt;/a&gt; for an alert on success/failure. Unfortunately this doesn't work nicely with Netlify, so I had to add the form in a standard way in order for Netlify to pick it up and build the POST endpoint. A simple compromise.&lt;/p&gt;

&lt;p&gt;Below is the code for Netlify to pick up the form. Feel free to just use a basic form element if you want, I decided to go with Vue Formulate for the added validation and submission features.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;form&lt;/span&gt;
  &lt;span class="na"&gt;data-netlify=&lt;/span&gt;&lt;span class="s"&gt;"true"&lt;/span&gt;
  &lt;span class="na"&gt;data-netlify-honeypot=&lt;/span&gt;&lt;span class="s"&gt;"bot-field"&lt;/span&gt;
  &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"new-comment"&lt;/span&gt;
  &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"hidden"&lt;/span&gt;
&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"hidden"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"form-name"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"hidden"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"postTitle"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"hidden"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"postPath"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"hidden"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"author"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"hidden"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"hidden"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"message"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/form&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Great, I've got my form, and it's submitting to Netlify. But how can I access that data to submit to Github?&lt;/p&gt;

&lt;p&gt;Luckily, Netlify has another great feature: &lt;a href="https://www.netlify.com/products/functions/" rel="noopener noreferrer"&gt;Serverless Functions&lt;/a&gt;! In short, they allow you to create AWS Lambda functions that they will host, and you don't need to create an AWS account to do anything.&lt;/p&gt;

&lt;p&gt;Here's a basic example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;exports&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;handler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt; &lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; 
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; 
    &lt;span class="na"&gt;statusCode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
    &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Success!&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; 
  &lt;span class="p"&gt;};&lt;/span&gt; 
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In addition to writing arbitrary serverless functions, Netlify provides a number of hooks to catch events that would go to their APIs, such as Identity or Forms. &lt;a href="https://docs.netlify.com/functions/trigger-on-events/" rel="noopener noreferrer"&gt;You can read more about them here&lt;/a&gt;. In this case, we want to create a function called &lt;code&gt;submission-created.js&lt;/code&gt;, which will receive an object called &lt;code&gt;payload&lt;/code&gt; in the event body. This payload will contain all of our form information. We can then use that to generate a markdown file for the comment.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;axios&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;axios&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;uuid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;uuid&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;v4&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;dayjs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dayjs&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;crypto&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;crypto&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;utc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dayjs/plugin/utc&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nx"&gt;dayjs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;extend&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;utc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nx"&gt;exports&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;handler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;payload&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;postTitle&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;postPath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;author&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;filePath&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`content/comments/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nf"&gt;uuid&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;&lt;span class="s2"&gt;.md`&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`---
postPath: "&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;postPath&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"
date: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nf"&gt;dayjs&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;utc&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;YYYY-MM-DD HH:mm:ss&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;
author: "&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;author&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"
authorId: "&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;crypto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createHash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;md5&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;digest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;hex&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;"
---
&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As a quick aside - you can always just use a generic serverless function for this step. I went with Netlify Forms and handling the event because Netlify by default applies spam filtering to the form input. You can also add a bot field (see the above HTML snippet where it says &lt;code&gt;data-netlify-honeypot&lt;/code&gt;) to get additional checks on form submission. Rather than build in a call to something like Akismet, or import my own spam filter, I felt this was the simplest way forward. It felt a bit like a compromise on my 'I own everything' take, but if I have to move platforms I can rebuild it fairly easily.&lt;/p&gt;

&lt;p&gt;All right, we now have our form hooked up and a serverless function to capture the data. Where do we save this? Well, anywhere we want, really! In my case, I wanted to store this data in Github. For this use case, &lt;a href="https://docs.github.com/en/free-pro-team@latest/rest" rel="noopener noreferrer"&gt;Github offers a RESTful API&lt;/a&gt; where a developer can interact with a given repository. In this case, it allows me to commit a new file into a branch of my blog.&lt;/p&gt;

&lt;p&gt;For this example, I will use Axios, but feel free to use &lt;code&gt;isomorphic-fetch&lt;/code&gt; or your preferred fetch library.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://api.github.com/repos/lindsaykwardell/lindsaykwardell/contents/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
    &lt;span class="nx"&gt;filePath&lt;/span&gt;

  &lt;span class="nx"&gt;axios&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`New comment on &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;postTitle&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;branch&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;new-comments&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;author&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Lindsay Wardell&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;COMMIT_EMAIL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="na"&gt;committer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Lindsay Wardell&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;COMMIT_EMAIL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;base64&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;Authorization&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`token &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;GITHUB_API_TOKEN&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
      &lt;span class="nf"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;statusCode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Your comment has been submitted!&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
      &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
      &lt;span class="nf"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;statusCode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;An error occurred!&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
      &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, any form submission from our site will go to Netlify, pass to this function, and get committed to our Github repository. For my case, I created a separate branch for new comments, just in case any spam filtering still needs to be done.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Congratulations! You now have complete control over your comments on a static site. This should work with any static site generator. My goal was to have complete control over the contents of my site, so I can take it with me wherever I want. While I do feel a bit tied into Netlify, I feel that it's a worthy compromise, considering all of the data is mine at the end of the day.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/lindsaykwardell/lindsaykwardell" rel="noopener noreferrer"&gt;Here's a link&lt;/a&gt; to my site's Github repository in case you want to look at the full source code.&lt;/p&gt;

&lt;p&gt;Stay safe!&lt;/p&gt;

</description>
      <category>jamstack</category>
      <category>netlify</category>
      <category>github</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Introduction to http_wrapper for Deno</title>
      <dc:creator>Lindsay Wardell</dc:creator>
      <pubDate>Tue, 26 May 2020 00:00:00 +0000</pubDate>
      <link>https://forem.com/lindsaykwardell/introduction-to-httpwrapper-for-deno-2da2</link>
      <guid>https://forem.com/lindsaykwardell/introduction-to-httpwrapper-for-deno-2da2</guid>
      <description>&lt;p&gt;I'm a big fan of &lt;a href="https://deno.land" rel="noopener noreferrer"&gt;Deno&lt;/a&gt;, the new Javascript runtime by the creator of Node. I love that it is built with Typescript in mind, reducing the amount of configuration required to get work done. I like that packages (or modules, as they are referred to) can be imported via URL, and don't require a package manager. I also love how &lt;em&gt;fast&lt;/em&gt; it is to start a Deno project for development.&lt;/p&gt;

&lt;p&gt;For the uninitiated:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Deno is a simple, modern and secure runtime for JavaScript and TypeScript that uses V8 and is built in Rust.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Secure by default. No file, network, or environment access, unless explicitly enabled.&lt;/li&gt;
&lt;li&gt;Supports TypeScript out of the box.&lt;/li&gt;
&lt;li&gt;Ships only a single executable file.&lt;/li&gt;
&lt;li&gt;Has built-in utilities like a dependency inspector (deno info) and a code formatter (deno fmt).&lt;/li&gt;
&lt;li&gt;Has a set of reviewed (audited) standard modules that are guaranteed to work with Deno: deno.land/std&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;p&gt;As a web developer, my first question was, "Okay, how do I start an HTTP server?"&lt;/p&gt;

&lt;p&gt;It turns out, the built-in methods to handle this aren't too difficult to figure out:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;serve&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://deno.land/std@0.53.0/http/server.ts&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;s&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;serve&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;8000&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="k"&gt;await &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;respond&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Hello World&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Want to respond differently for different endpoints? No big deal!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;listenAndServe&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://deno.land/std@0.53.0/http/server.ts&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;s&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;listenAndServe&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;8000&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ServerRequest&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/example&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;respond&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;You found the example endpoint!&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;respond&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;404&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pretty straightforward. However, this method of writing an API could get pretty verbose: multiple if-statements, no simple solution to break out different endpoints into their own scope or module, and no clear way to scale your application as your routes grow. A large application could become unwieldy pretty fast using this format to write your API.&lt;/p&gt;

&lt;p&gt;There are already a lot of libraries out there for managing routes (Oak and Drash come to mind). From what I can tell, though, these libraries wrap the default &lt;code&gt;ServerRequest&lt;/code&gt; object in their own formats, making them incompatible with other built-in methods (like web sockets). I really love how simple it is to get started, there must be a way to just wrap the default &lt;code&gt;listenAndServe&lt;/code&gt; and &lt;code&gt;ServerRequest&lt;/code&gt; objects in a router-based solution.&lt;/p&gt;

&lt;p&gt;And so I wrote &lt;code&gt;http_wrapper&lt;/code&gt;, a simple wrapper around the above code. Let's implement the above example using &lt;code&gt;http_wrapper&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Server&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Router&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://deno.land/x/http_wrapper@v0.3.0/mod.ts&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Server&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;router&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Router&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/example&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ServerRequest&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;respond&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;You found the example endpoint!&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;routes&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;start&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;8000&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Save the above in a file (say index.ts), and run it as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;deno run --allow-net --allow-read index.ts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Note: Deno has a secure runtime, so whenever you want to reach out of its sandbox you need to explicitly allow it to.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;http_wrapper&lt;/code&gt; adds two new classes:&lt;/p&gt;

&lt;h2&gt;
  
  
  Server
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;Server&lt;/code&gt; class handles calling &lt;code&gt;listenAndServe()&lt;/code&gt;, and correctly determining which endpoint was called. When the server is started (with &lt;code&gt;app.start()&lt;/code&gt;), internally the class calls &lt;code&gt;listenAndServe()&lt;/code&gt; and begins checking each request against its list of routes and endpoints. The config object that is passed into &lt;code&gt;app.start()&lt;/code&gt; is &lt;code&gt;Deno.ListenOptions&lt;/code&gt;, the same as for &lt;code&gt;listenAndServe()&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Router
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;Router&lt;/code&gt; is where the fun begins. This class is meant to handle all of your API needs. It takes a constructor of a string, which is the base route for that router:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Creating the /example route&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;router&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Router&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/example&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Endpoints can then be added to routes for different HTTP methods:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;GET&lt;/li&gt;
&lt;li&gt;POST&lt;/li&gt;
&lt;li&gt;PUT&lt;/li&gt;
&lt;li&gt;DELETE&lt;/li&gt;
&lt;li&gt;OPTIONS&lt;/li&gt;
&lt;li&gt;PATCH&lt;/li&gt;
&lt;li&gt;HEAD
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;router&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Router&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/example&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Add a GET request&lt;/span&gt;
&lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;response&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;You found me!&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="c1"&gt;// Add a POST request&lt;/span&gt;
&lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;response&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;204&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Headers&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;content-type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
    &lt;span class="p"&gt;}),&lt;/span&gt;
    &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Saved!&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Note: Currently, there is no support for path parameters&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Putting it together
&lt;/h2&gt;

&lt;p&gt;Once you have defined your routes, you can bring them into the &lt;code&gt;Server&lt;/code&gt; by using &lt;code&gt;app.use()&lt;/code&gt;. Also, if you have static files you would like to deploy with your app, you can define the static file folder and endpoint with &lt;code&gt;app.static()&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;route1&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./Router1.ts&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Server&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;route1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;routes&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Router.routes is a getter, so no function calls are required&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;static&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;static&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;start&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;8000&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Congratulations, you are now hosting an app with two endpoints:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="http://localhost:8000/example" rel="noopener noreferrer"&gt;http://localhost:8000/example&lt;/a&gt; (various string/JSON responses)&lt;/li&gt;
&lt;li&gt;
&lt;a href="http://localhost:8000/" rel="noopener noreferrer"&gt;http://localhost:8000/&lt;/a&gt; (static files)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The best part? For development, it takes about a second to start up!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;deno run --allow-net --allow-read index.ts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can check out &lt;code&gt;http_wrapper&lt;/code&gt; here: &lt;a href="https://deno.land/x/http_wrapper" rel="noopener noreferrer"&gt;Deno.land&lt;/a&gt;. The project is MIT licensed, so feel free to use it and report any issues you find. &lt;/p&gt;

&lt;p&gt;Next time, I'll talk about how to incorporate &lt;code&gt;http_wrapper&lt;/code&gt; with a Vue 3 SPA to create a chat room, while keeping the ~1 second startup for development.&lt;/p&gt;

&lt;p&gt;Take care!&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>deno</category>
      <category>typescript</category>
    </item>
    <item>
      <title>When I Grow Up...</title>
      <dc:creator>Lindsay Wardell</dc:creator>
      <pubDate>Mon, 18 May 2020 00:00:00 +0000</pubDate>
      <link>https://forem.com/lindsaykwardell/when-i-grow-up-1e06</link>
      <guid>https://forem.com/lindsaykwardell/when-i-grow-up-1e06</guid>
      <description>&lt;p&gt;&lt;strong&gt;"What do you want to be when you grow up?"&lt;/strong&gt; &lt;/p&gt;

&lt;p&gt;This age-old question, asked of children before they can know what 'grow up' even means exactly, often results in answers both cute and curious. Some of the answers I remember giving include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Astronaut&lt;/li&gt;
&lt;li&gt;Mathematician &lt;em&gt;(due to a TV show we watched in 1st grade)&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;Teacher&lt;/li&gt;
&lt;li&gt;Office Max employee &lt;em&gt;(well, I got to do that one!)&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;Author&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One theme popped out from an early age, though: whatever I was going to do, it was going to revolve around computers.&lt;/p&gt;

&lt;p&gt;Software development has always been something I was interested in. I remember being awed by an early version of Excel at a science fair, and how you could program it to do whatever you wanted. A friend and I explored making fan games with GUI game designers like The Games Factory or Multimedia Fusion. We spent countless hours trying to put together fan games for Sonic the Hedgehog and other characters, or trying to make our own. I went to Cybercamps, a computer camp which offered weeklong courses on game development, programming, graphic design (Photoshop), and web design.&lt;/p&gt;

&lt;p&gt;In 2002, we realized that we could look at the source code for a website, change it, save it, and open it in our browsers. It wasn't long after that that I got some books on HTML4 and CSS2, and started writing my own code. We made websites hosted on Freewebs, and I was able to use the HTML sections they offered to do some things myself.&lt;/p&gt;

&lt;p&gt;From there, I started setting up websites for gaming clans I was a part of. I would download an HTML template, put it on the server my family rented, and &lt;em&gt;bam&lt;/em&gt;! Website for the clan. It felt great. At the time, I wrote for a school assignment, "What I want for a career is to be a website designer."&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--BYyxGuvl--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://www.lindsaykwardell.com/assets/static/when-i-grow-up.15a4cd5.5d673aa776bad4dd032d4492c735c870.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--BYyxGuvl--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://www.lindsaykwardell.com/assets/static/when-i-grow-up.15a4cd5.5d673aa776bad4dd032d4492c735c870.jpg" alt="School paper stating I want to be a website designer" width="800" height="785"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In 2005, my friend started a new fan game based on Shadow the Hedgehog. As a "company" making the game, he came up with the title, "Blackarms Studios" (a reference to the race of aliens in the Shadow the Hedgehog game, the Black Arms). And so, I made a website. We hosted it on Yagaboosh.com, which was derived from a random word/sound that I made as a child one day. We set up a community forum using ProBoards, and others from the fan game community made their way in. Later, my friend shifted to focus on making videos for YouTube. At one point, our channel was regularly reaching YouTube's top 100. Bear in mind, this was back in the day when YouTube videos couldn't be longer than 10 minutes.&lt;/p&gt;

&lt;p&gt;Other members of the community wanted websites, so I started working with them to create and host something based on the template I had made for BAS. I then billed myself as running "The Yagaboosh Network" (everything was a URL based off of my original website, Yagaboosh.com) It was a blast. I dove straight into using CSS for styling the website, while ensuring the structure functioned without a stylesheet. I used CSS hover effects to generate dropdown menus. I avoided Javascript at the time, because it seemed intimidating and there were general concerns about compatibility across browsers.&lt;/p&gt;

&lt;p&gt;Sadly, I don't have the original sites any more, but the Wayback Machine managed to archive some of my efforts. Looks like a large portion of the styling was misssed, though.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--y160affc--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://www.lindsaykwardell.com/assets/static/blackarms-studios.7d0ab09.f5d66bf9b045feea81dddba0513be5f0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--y160affc--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://www.lindsaykwardell.com/assets/static/blackarms-studios.7d0ab09.f5d66bf9b045feea81dddba0513be5f0.png" alt="Screenshot from the Wayback Machine of Blackarms Studios" width="800" height="535"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The community was amazing. It was probably my favorite part of the site. But I was never happy was using ProBoards, because it didn't integrate with the main site and I couldn't style it nearly as well. So in 2007, we migrated from ProBoards to a self-hosted solution with PHPBB. Over the following months, I tweaked the PHPBB theme to treat the global announcements as a blog, and finally my dream of merging the two was complete. This was my first real experience with "back-end web development" - I was writing my own PHP code, as well as the special PHPBB syntax, in order to make my own product.&lt;/p&gt;

&lt;p&gt;The community slowly faded away, as things do. Our peak was in 2008, according to the stat counter I placed on the site (still accessible, thanks again to the Wayback Machine). Considering we were a small community site, I'm fairly proud of these numbers.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--D0cWfvo6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://www.lindsaykwardell.com/assets/static/blackarms-studios-stats.a450ee0.295fc87da1592f5d746f1411a0a0c13a.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--D0cWfvo6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://www.lindsaykwardell.com/assets/static/blackarms-studios-stats.a450ee0.295fc87da1592f5d746f1411a0a0c13a.png" alt="Stat counter statistic for Blackarms Studios" width="800" height="334"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Activity on the site dropped off after 2009. The YouTube channel had been repeatedly banned for violating DMCA regulations (this was before video game companies realized that fan content was a good thing). Part of this decline was because of my focus on the web technologies, rather than supporting the community, but also because we had been around for 4-5 years. The old members moved on, while no new members were joining.&lt;/p&gt;

&lt;p&gt;We kept talking about what to do for the website until 2011, when I left for Brazil for two years as a missionary. During the mission, I was appointed financial secretary, which meant I worked from 10 to 5 in the mission office. Within my first month, my web development came up in conversation, and I gave an example of how easy it was to get started making a website. I kept playing with it when I had a spare moment, and was able to arrange a public release of the website.&lt;/p&gt;

&lt;p&gt;The site was written as a custom CMS, because I didn't have access to tool like Wordpress or PHPBB. It was my first attempt at a PHP application. The banner at the top was randomly selected from between 5 options. A blog was included to display the mission's monthly message. Other features included fading between images using Javascript (I believe I used jQuery) and a scroll on the left with "Scriptures of Power". The scroll in the screenshot below is sadly broken, but it involved some complicated PHP code that allowed the scroll to animate downward to display all of the text. This was complicated because to achieve the animation, I was counting the number of characters in the quote, and then generating the CSS within PHP.&lt;/p&gt;

&lt;p&gt;Thanks again to the Wayback Machine for the archived version of the site.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--vcE7Vl5d--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://www.lindsaykwardell.com/assets/static/missao-brasil-goiania.d9c744c.1dce7c61e71467ca32f0099467f59034.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--vcE7Vl5d--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://www.lindsaykwardell.com/assets/static/missao-brasil-goiania.d9c744c.1dce7c61e71467ca32f0099467f59034.png" alt="Goiania Brazil Mission website" width="800" height="779"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It was my first custom project, and I was very excited about it. But as with many side projects, I was unsatisfied with some of the initial decisions I had made. These constraints made it difficult to expand on the site, and I felt there had to be a better way. So I started over, updating the site as needed while I worked in the office, but truly focusing on the new project - Yagaboosh.&lt;/p&gt;

&lt;p&gt;Yagaboosh was going to be a completely flexible CMS, built around a plugin system. Everything was going to be plugins, including the blog functionality, so that features could be updated separately or ignored if not wanted. This concept had formed in my head from the years of working on Wordpress and PHPBB - wouldn't it be great if I could just pick and choose which tools I used, and put them together in a cohesive way?&lt;/p&gt;

&lt;p&gt;I feel like the project had a lot of potential, but eventually I was transferred away from the office. I took the source code with me, but when I got home in October 2013 I didn't feel like working on it all that much. I did some minor adjustments in 2014, but then I got married, and life priorities had to be shifted around. I later did some further updates, mostly to the theme (I switched to using Bootstrap instead of a custom theme), and pushed it to deployment.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--EmcvhZeM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://www.lindsaykwardell.com/assets/static/yagaboosh.2665e34.0c52d06b575388f046c3d148182e1485.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--EmcvhZeM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://www.lindsaykwardell.com/assets/static/yagaboosh.2665e34.0c52d06b575388f046c3d148182e1485.png" alt="New Yagaboosh install page" width="800" height="429"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Between 2014 and 2017, I didn't do much in the way of programming. I worked as a tech specialist at Office Depot while studying for my Associate's Degree without really knowing what I wanted to do. In 2015, our daughter was born, I started working on my Bachelor's degree, and I switched jobs to be an IT Manager at Lindsey Forwarders.&lt;/p&gt;

&lt;p&gt;Most of my time there was working there was spent on day-to-day operations, and for a time I filled the role of Operations Manager rather than focusing on IT. But I did get to deploy a PHP-based time tracking system, and did some work on an existing internal application made using Magic from around 1995. I also got to make an Excel spreadsheet for tracking rates.&lt;/p&gt;

&lt;p&gt;In 2017, the business was sold to a son-in-law, and I began to prepare to leave the company. A friend recommended attending the Tech Academy, a programming boot camp in Portland, OR. I made an arrangement to work part time while attending the boot camp, and in return I would start developing a new application to replace the existing one from 1995. I attended the Tech Academy from November 2017 to February 2018, learning the more professional side of development I had never had due to my self-taught method up until then.&lt;/p&gt;

&lt;p&gt;At the Tech Academy, I learned about Git, Javascript/jQuery, and how to build server-side applications that weren't just based on PHP scripts - specifically, ASP.NET and C# MVC. I also got to experiment with Knockout.js, and saw the power of a single-page application. These methods and technologies would prove essential to my career.&lt;/p&gt;

&lt;p&gt;For the rest of 2018, I switched between helping the new owner adjust to the company he had bought and working on new development projects. The two main projects were a new Wordpress site for the company, and the Online Portal that was intended to replace the old desktop app. The Wordpress site used an existing theme, but I was able to use my skills from the Tech Academy and past experience to style it how I wanted. I also got to make a new logo for the company, using Photoshop.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--O5HJqkuo--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://www.lindsaykwardell.com/assets/static/lindsey-forwarders.ea27f7a.bca8a27d32b4690f34bdbaf8c7724cc1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--O5HJqkuo--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://www.lindsaykwardell.com/assets/static/lindsey-forwarders.ea27f7a.bca8a27d32b4690f34bdbaf8c7724cc1.png" alt="Lindsey Forwarders home page" width="800" height="716"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The Online Portal was much more exciting. I started a prototype in 2017 while still attending the Tech Academy. The prototype was built using PHP and jQuery, and applied as close to MVC functionality as I could manage. There were dynamic Javascript imports depending on the page, REST endpoints for performing actions, and dynamic page rerenders as tasks were done. But as with the mission blog, it wasn't scalable. On top of that, there was a severe performance hit when querying the database via PHP that I couldn't figure out. Looking back, I think I was using an older version of PHP which caused it, but I'm still not certain.&lt;/p&gt;

&lt;p&gt;After I completed the Tech Academy, I started trying to learn React, but was quickly overwhelmed by the number of new concepts I was trying to wrap my head around. Instead of continuing with React, I switched to Vue, and found a Javascript framework that was much more comfortable to get started with. I also started learning Node.js, and I realized how powerful it was to use one language for the entire application. The shift between PHP and Javascript during the prototyping phase had been annoying, especially since both are dynamic languages.&lt;/p&gt;

&lt;p&gt;In May/June 2018, I built a new prototype using Vue and Node.js. I just replicated one page, the one where the database query was too slow. The result - a 4x performance increase, at minimum; often it was even more than that. I made the case to my boss to allow the shift from PHP/jQuery to Node/Vue, and he agreed. By this point, we had been in a closed pilot with a few customers, so we made the decision to support the old version while working on the new one. It was only a short time later that we were ready to switch. I was very impressed with the speed of development on the Vue/Node application compared to PHP/jQuery.&lt;/p&gt;

&lt;p&gt;I continued working on the new application through the year, and into the start of 2019. However, in February 2019, I was contacted by a recruiter to see if I was interested in a position with Daimler Trucks North America, building a new project using Vue and Node. The interview was great, and they offered me the job. I immediately accepted. I started at DTNA in March, 2019.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--WaiGLS47--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://www.lindsaykwardell.com/assets/static/mp-elevator.42db587.276ca1d4870b91cea10f0e197169e51a.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--WaiGLS47--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://www.lindsaykwardell.com/assets/static/mp-elevator.42db587.276ca1d4870b91cea10f0e197169e51a.jpg" alt="Me in the elevator at my new job" width="800" height="1067"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Working in a large corporation as a developer was a major shift from anything I had done before. I worked under my project manager (the same with whom I interviewed), along with a BSA assigned to my project. My contact information was intentionally kept from the app owner, so that all communication was routed through the BSA. We had regular meeting with architecture and CI/CD teams. I had to coordinate between other teams to call APIs needed to make our application work. All things I had either done myself, or had documentation to follow. &lt;/p&gt;

&lt;p&gt;I worked on that first project from March to June, 2019. It was an internal tool, so it wouldn't have a large user base. Also, once I had the position, I learned that I would need to write the application using Java, not Node. I had never written a single line of Java. I asked for the time to learn sufficient to write the application, which I received, and was able to complete the project a month ahead of time.&lt;/p&gt;

&lt;p&gt;This turned out to be a good thing, because I was transferred to a new project where a developer was being transferred. This project was no small internal app - I'm actually not going to talk about it much because I'm not sure what I can say officially. I will say that, in many ways, it is the culmination of everything I have learned up until this point - database design, application development, working with teams, using version control and branches, and more. When the application goes live, and is used by its intended audience, it will be my code, along with that of my fellow developers, that is making magic happen.&lt;/p&gt;

&lt;p&gt;There is more to the story - side projects, frustration, growing pains, and more. The path I have taken to get here was never straightforward. But in the end, I am doing what I love.&lt;/p&gt;

&lt;p&gt;In 2002, I dreamed of being a website designer. Today, I know that I made that happen.&lt;/p&gt;

&lt;p&gt;So now, I'll ask you:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"What do you want to be when you grow up?"&lt;/strong&gt; &lt;/p&gt;

</description>
      <category>career</category>
    </item>
  </channel>
</rss>
