<?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>DEV Community: Sam Beckham</title>
    <description>The latest articles on DEV Community by Sam Beckham (@samdbeckham).</description>
    <link>https://dev.to/samdbeckham</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%2F59272%2F8a2b15d9-497c-47af-a561-5810f7537293.jpeg</url>
      <title>DEV Community: Sam Beckham</title>
      <link>https://dev.to/samdbeckham</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/samdbeckham"/>
    <language>en</language>
    <item>
      <title>Experimenting with AI</title>
      <dc:creator>Sam Beckham</dc:creator>
      <pubDate>Fri, 02 Jun 2023 00:00:00 +0000</pubDate>
      <link>https://dev.to/samdbeckham/experimenting-with-ai-1e20</link>
      <guid>https://dev.to/samdbeckham/experimenting-with-ai-1e20</guid>
      <description>&lt;p&gt;The world has gone crazy for AI. AI discussions are dominating keynotes, product releases, and Twitter threads. But what does building with AI actually look like? I wanted to learn, so I &lt;a href="https://sam.beckham.io/wrote/starting-small/"&gt;started small&lt;/a&gt; and spent a day experimenting.&lt;/p&gt;

&lt;h2&gt;
  
  
  A simple AI experiment
&lt;/h2&gt;

&lt;p&gt;I needed an experiment that was small enough to be shippable in a day, but large enough to challenge me. I decided to update the recommendation feature on this blog. The previous algorithm suggested the three latest articles. It did the job, but wasn't too smart. It was the perfect target for my AI experiments. It was simple to understand, but would a deeper understanding of AI to get it done.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting familiar with AI
&lt;/h2&gt;

&lt;p&gt;I wanted to create a some POCs (proofs of concept) to help me learn. The first step was to get the OpenAI API running in my project. I kept it simple and asked it to, "tell me a joke". After following the getting started guide (and some configuration) I got it a response.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Q: What did the fish say when it hit the wall?&lt;/p&gt;

&lt;p&gt;A: Dam!"&lt;/p&gt;

&lt;p&gt;&lt;em&gt;–'text-davinci-003'&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;By doing this, I learned that requests are made of tokens. Each token is a snippet of text, usually the length of a word. There's a limit on the number of tokens you can send and receive in each API call. The longer the prompt, the less room for the response. If you set this limit too low, or make the prompt too big, you get incomplete responses.&lt;/p&gt;

&lt;h2&gt;
  
  
  Proving the Concept
&lt;/h2&gt;

&lt;p&gt;By updating the prompt from, "Tell me a joke", I could turn it into something useful. I passed a list of article titles and asked for the three most related ones. After some finessing, I landed on the following prompt:&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;prompt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;` Given the title of an article is "&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;article_title&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;". Which three articles from the following array of article titles are the most related?

&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="nx"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;article_titles&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;.

Return your answer in the following JSON format: { "recommendations": ["one", "two", "three"] }`&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I learned that writing prompts like &lt;a href="https://cucumber.io/docs/bdd/"&gt;BDD tests&lt;/a&gt; is quite effective. The AI was more likely to understand the question if I laid it out clearly.&lt;/p&gt;

&lt;p&gt;To make the result parsable, it was crucial to provide a format. Asking it to, &lt;em&gt;"return the answer in JSON"&lt;/em&gt; was not enough. I needed to provide an example response to get a more reliable result. More reliable, but not completely reliable.&lt;/p&gt;

&lt;p&gt;Here's what I got when asking for related articles for "&lt;a href="https://sam.beckham.io/wrote/increasing-your-focus-with-pomodoro/"&gt;Increase You Focus With Pomodoro&lt;/a&gt;":&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"recommendations"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"increase your focus with pomodoro"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"automate your meeting agendas"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"find what makes you productive"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Though it was obvious in hindsight, the most related article was the article itself. This was easy to fix by filtering it out of the list before passing it up. The POC worked well, but there were a few problems I wanted to address:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. It wasn't reliable
&lt;/h3&gt;

&lt;p&gt;Since this was chat-based, the outcome wasn't reliable. It worked 80% of the time, but often gave a poor response. It had a habit of adding extra text around the JSON, making it harder to parse. Switching from the &lt;code&gt;text-davinci-003&lt;/code&gt; model to the more chat-focused &lt;code&gt;gpt-3.5-turbo&lt;/code&gt; model helped, but didn't completely fix it. I would need to write a more robust response parser if I were to move this from POC to production-ready code.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. The recommendations weren't great
&lt;/h3&gt;

&lt;p&gt;The recommendations were okay, but they were a bit naive. Since I was only sending the titles of the articles, we weren't giving the AI much data to work with. For example: "&lt;a href="https://sam.beckham.io/wrote/running-a-conference/"&gt;Running a Conference&lt;/a&gt;" would recommend, "&lt;a href="https://sam.beckham.io/wrote/running-the-jamstack-on-gitlab/"&gt;Running the JamStack on GitLab&lt;/a&gt;", instead of the more obvious, "&lt;a href="https://sam.beckham.io/wrote/starting-a-meetup/"&gt;Starting a Meetup&lt;/a&gt;". To get better results, I'd need to give the AI more data.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. There were too many API calls
&lt;/h3&gt;

&lt;p&gt;The OpenAI API isn't free. Up to this point I had only spent a few cents, but at scale this could get expensive. Running this for every article—and passing more data per article—would increase costs. I needed to reduce and cache these calls wherever possible.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using OpenAI Embeddings
&lt;/h2&gt;

&lt;p&gt;After a bit more research into the issues above, I discovered &lt;a href="https://platform.openai.com/docs/guides/embeddings"&gt;embeddings&lt;/a&gt;. Instead of writing a prompt for every article, I could send them all to the embedding endpoint. This would return a multi-dimensional graph with all the articles plotted on it. The closer the points are on the graph, the more related they are.&lt;/p&gt;

&lt;p&gt;To understand how embeddings work, we need to simplify what they're doing.&lt;/p&gt;

&lt;p&gt;Imagine all the articles plotted on a standard 2D chart. On each axis is a different topic. There's "leadership" on one and "engineering" on another. We can plot all the articles on this chart based on how much the content within them relates to each topic. When we look at this chart, we can see the ones that are closer together are more related to each other. The ones that are further apart, will have less in common.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Cf6I4wv6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://sam.beckham.io/src/assets/images/articles/experimenting-with-ai/2d-embedding-chart.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Cf6I4wv6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://sam.beckham.io/src/assets/images/articles/experimenting-with-ai/2d-embedding-chart.png" alt="A scatter chart showing the relationships between articles when plotted against Leadership and Engineering topics" width="" height=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Comparing two topics doesn't give us much. To get more accurate information, we can add a third axis. This one measures the relation to "productivity". Since the axis needs to be orthogonal to the other two, we need to jump into the third dimension to visualize it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--M1Jt5dRk--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://sam.beckham.io/src/assets/images/articles/experimenting-with-ai/3d-embedding-chart.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--M1Jt5dRk--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://sam.beckham.io/src/assets/images/articles/experimenting-with-ai/3d-embedding-chart.gif" alt="A 3D scatter chart showing the relationships between articles when plotted against Leadership, Engineering, and Productivity topics" width="" height=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We can still get the distance between points, but now we're comparing across three dimensions. OpenAI uses thousands of dimensions to create embeddings, but the concept is the same. The closer two nodes are, the more related they are.&lt;/p&gt;

&lt;p&gt;This approach solves all our issues. We get more accurate, reliable, recommendations with one API call. Because of this, we can afford to send more data to make the recommendations better. Since this is no-longer chat based, the response is a structured data format. The embedding data may be different if we ran it again, but the data structure would be the same.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sending the right data to the AI
&lt;/h2&gt;

&lt;p&gt;We can send more data to the API, but which data do we send? We could send the whole article, but we would soon hit the token limit. Luckily, ChatGPT is great at summarizing and categorizing text. We can use this to our advantage and ask it to generate tags for all our articles. By sending tags instead of the full article, we send less data but still get the point of the article across. Because I was only doing this once, I used the ChatGPT app. This came with the added benefit of using the more powerful &lt;code&gt;GPT4&lt;/code&gt; engine that isn't available to us on the API.&lt;/p&gt;

&lt;p&gt;Here's the prompt I wrote:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Given the following markdown article, please suggest five tags formatted as yaml:

{ARTICLE_CONTENT}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice I'm using the BDD test style again. I didn't provide an example response, so the results weren't reliable. Since this was using the ChatGPT app though, it didn't matter too much. It had the context of the previous chat messages so I could refine the result. When it returned the results in the wrong format, I would ask it to, &lt;em&gt;"reformat the response as yaml"&lt;/em&gt; and it fixed it up. I could also refine the suggestions by saying, &lt;em&gt;"swap {TAG_NAME} for another suggestion"&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Here were the suggestions for this article:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;AI experimentation&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;recommendation feature&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;OpenAI API&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;embeddings&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;article tagging&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It took a while to generate all the tags, but it was a one-time operation. Once I had all the tags, I wrote a script that fetched all the articles and extracted their tags. I then sent an array of these to the embedding endpoint and got all the data back to run comparisons against later.&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="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;input&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="s2"&gt;Experimenting with AI: AI experimentation, recommendation feature, OpenAI API, embeddings, article tagging&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;Increase your focus with pomodoro: Productivity, Time Management, Focus Technique, Pomodoro Technique, Distraction Management&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;Running a Conference: conference, event organization, sponsorship, diversity fund, speaker payment&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="c1"&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;h2&gt;
  
  
  Cosine similarity formula
&lt;/h2&gt;

&lt;p&gt;Our brains can't handle graphs in dimensions higher than the three we live in, but computers have no problem with it. Using the &lt;a href="https://en.wikipedia.org/wiki/Cosine_similarity"&gt;cosine similarity formula&lt;/a&gt;, we can get the distance between two points in any number of dimensions. Letting the &lt;a href="https://www.npmjs.com/package/compute-cosine-similarity"&gt;compute-cosine-similarity&lt;/a&gt; npm package do the heavy-lifting, made computing similarity a breeze. To get the recommended articles, I loop through all the articles and calculate their similarity to the current article. Then I sort the array by similarity, remove the first result, and return the next three. This is all done against the dataset we generated previously, so it's really fast.&lt;/p&gt;

&lt;h2&gt;
  
  
  Takeaways
&lt;/h2&gt;

&lt;p&gt;This approach solved all the issues with the first POC. It's much more reliable since we're dealing with deterministic data. There's only one API call that's made once every few weeks. Most importantly, the recommendations are much better. They're not perfect, but they're good enough.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;For&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Running a Conference"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"recommendations"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Starting a Meetup"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"path"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/wrote/starting-a-meetup/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"similarity"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.8543&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"All Meetings are Optional"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"path"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/wrote/all-meetings-are-optional/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"similarity"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.8363&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Automate Your Meeting Agendas"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"path"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/wrote/automate-your-meeting-agendas/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"similarity"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.7954&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Recommendations are a nice feature, but the point of this was to learn more about AI. I'm an engineering manager so I don't need to be a guru, but I still need to know what I'm talking about. By going through a practical application of AI, I'm better prepared for conversations with my engineers as we dive in to &lt;a href="https://www.youtube.com/watch?v=ejWeMdVz8Nk"&gt;AI at GitLab&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If I've made any incorrect assumptions, or could have done this better, reach out on &lt;a href="https://mastodon.social/@samdbeckham"&gt;Mastodon&lt;/a&gt;, or &lt;a href="https://twitter.com/samdbeckham"&gt;Twitter&lt;/a&gt; and tell me about it. I'm always happy to learn.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Owning my content again</title>
      <dc:creator>Sam Beckham</dc:creator>
      <pubDate>Mon, 07 Oct 2019 20:08:27 +0000</pubDate>
      <link>https://dev.to/samdbeckham/owning-my-content-again-167o</link>
      <guid>https://dev.to/samdbeckham/owning-my-content-again-167o</guid>
      <description>&lt;p&gt;Medium, we had fun, but you've changed. A beautiful reading experience tarnished by paywalls, advertisements, and popovers. I want my content back on my own platform.&lt;/p&gt;

&lt;p&gt;Owning my own platform gives me freedom. Freedom to tweak the code and eek out those extra milliseconds of performance. Freedom to add whatever features I want, and remove those that I don't. But most of all, the freedom of knowing it won't ever change or disappear, unless I want it to.&lt;/p&gt;

&lt;p&gt;It does come with a few downsides. The biggest two being discoverability and collaboration. I can write all the posts I want, but no one will see them unless they actively look for them.&lt;/p&gt;

&lt;p&gt;With a couple of extra additions to this site, I may have solved these problems. I just had to use something old, something new, something borrowed, and something blue.&lt;/p&gt;

&lt;p&gt;If you've got to this point and are wondering, &lt;em&gt;"I thought you wanted your own platform, this is Dev.to"&lt;/em&gt;. You're right. The original article is on &lt;a href="https://sam.beckham.io/wrote/owning-my-content-again/"&gt;my blog&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Something old
&lt;/h2&gt;

&lt;p&gt;Back in the good-old-days we all had our own blogs. People were still able to discover our posts by using &lt;a href="https://www.copyblogger.com/what-the-heck-is-rss/"&gt;RSS feeds&lt;/a&gt;. You could subscribe to the RSS feeds of you favourite blogs and your RSS reader would collate them all. It's a bit like Podcasts. You subscribe to the stations you want to listen to. Then your podcast player displays all the new podcasts from your favourite stations. In fact, it's exactly like podcasts. Because RSS is what they use to manage this.&lt;/p&gt;

&lt;p&gt;Unfortunately, using RSS for blogging died off a little in recent years. But as more and more people want to own their own data again, they're regaining popularity.&lt;/p&gt;

&lt;p&gt;So, in the sprit of boosting RSS back to the top, I created a feed for this blog. This site is running on Gridsome which made it easy to add an RSS feed. All I needed to do was hook up &lt;a href="https://github.com/darthmeme/gridsome-plugin-rss"&gt;&lt;code&gt;gridsome-plugin-rss&lt;/code&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&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="na"&gt;use&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;gridsome-plugin-rss&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;options&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;contentTypeName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SanityPost&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;latest&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;feedOptions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SITE_NAME&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;feed_url&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;GRIDSOME_BASE_PATH&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/rss.xml`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;site_url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;GRIDSOME_BASE_PATH&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="na"&gt;feedItemOptions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;node&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;description&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;url&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;GRIDSOME_BASE_PATH&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/wrote/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;slug&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&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;date&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;publishedAt&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;I dropped in the default config, tweaked it to match my own site, ran &lt;code&gt;yarn build&lt;/code&gt; to give it a test, and success! Sort of. The good news was, the feed was there and all my posts were in it. The bad news was, they were in a completely random order. After a little digging I realised they were all alphabetised on their UUIDs. A lovely order, but an entirely useless one. This was because &lt;code&gt;gridsome-plugin-css&lt;/code&gt; sorted posts by &lt;code&gt;node.date&lt;/code&gt; and my Sanity implementation doesn't have a &lt;code&gt;date&lt;/code&gt; field. It's a little more granular and has &lt;code&gt;_createdAt&lt;/code&gt;, &lt;code&gt;_updatedAt&lt;/code&gt;, and &lt;code&gt;publishedAt&lt;/code&gt;. The one I wanted was &lt;code&gt;publishedAt&lt;/code&gt;, but there was no way to tell the plugin this information. Yet.&lt;/p&gt;

&lt;p&gt;The best thing about open source is that everyone can contribute. I found a problem with the plugin and took it upon myself to &lt;a href="https://github.com/darthmeme/gridsome-plugin-rss/pull/11"&gt;fix it&lt;/a&gt;. A couple of changes to the codebase and I could pass in &lt;code&gt;dateField: "publishedAt"&lt;/code&gt; which orders our posts by the correct date field. Joy!&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://sam.beckham.io/rss.xml"&gt;RSS feed&lt;/a&gt; is up-and-running. It's nothing fancy, but it gets the job done.&lt;/p&gt;

&lt;h2&gt;
  
  
  Something New
&lt;/h2&gt;

&lt;p&gt;The best posts are the ones that spark a conversation. But how do we allow those discussions on a static site? I could add Disquss, or use FaaS (Functions as a Service) to let people post comments. But it's not quite what I want. Usually, I prefer using &lt;a href="https://about.gitlab.com/handbook/values/#boring-solutions"&gt;the boring solution&lt;/a&gt;. But this time I went for the shiny option. It's my platform, I'll do what I want.&lt;/p&gt;

&lt;p&gt;The shiny solution here is a new standard called &lt;a href="https://indieweb.org/Webmention"&gt;Webmentions&lt;/a&gt;. In essence, they're a list of all the places that "mentioned" your post. If you Tweet out your post on Twitter, that's a mention. If someone responds to that Tweet, that's a mention. If someone likes, or retweets that Tweet, that's a mention too. Then all you do is take that list, filter it, and render it to your site as "comments". This isn't just Twitter either. It works for other sites too, but for simplicity, I'll stick with Twitter for now.&lt;/p&gt;

&lt;p&gt;Getting this list wasn't too hard. I followed the instructions on &lt;a href="https://mxb.dev/blog/using-webmentions-on-static-sites/"&gt;Max Böck's post&lt;/a&gt; to the letter. If you want them on your site too, I'd start there.&lt;/p&gt;

&lt;p&gt;Once they were set up, I had to integrate them into Gridsome. Unfortunately, Max uses eleventy, so I could only use his post as guidance. There are also no Gridsome plugins for Webmentions yet, so I had to go alone on this one.&lt;/p&gt;

&lt;p&gt;If you want to see exactly how I did it, I'd recommend looking at the &lt;a href="https://gitlab.com/samdbeckham/sam.beckham.io/merge_requests/4/diffs"&gt;Merge Request&lt;/a&gt; (that's what we call Pull Requests at GitLab). This whole blog is public, so you can dig in and have a proper look at the code. If you want the cliff notes, the whole thing was done in three steps:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Add the Webmentions feed into the Gridsome API
&lt;/h3&gt;

&lt;p&gt;This is probably the most complex part. I needed to take the API provided by webmention.io and expose it on the GraphQL endpoint in Gridsome. This was done by adding the following code to &lt;code&gt;gridsome.server.js&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="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="nx"&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;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;API_ORIGIN&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://webmention.io/api/mentions.jf2&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="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;GRIDSOME_WEBMENTIONS_TOKEN&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&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;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&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="nx"&gt;api&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;loadSource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nx"&gt;store&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="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="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;axios&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;API_ORIGIN&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;params&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;GRIDSOME_WEBMENTIONS_TOKEN&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="nx"&gt;mentions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addCollection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;mentions&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;for&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;item&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;children&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;mentions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addNode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;item&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;h3&gt;
  
  
  2. Pull the mentions into a &lt;code&gt;PostComments.vue&lt;/code&gt; component
&lt;/h3&gt;

&lt;p&gt;Once the Webmentions data was available on the GraphQL endpoint, I could pull it into the project. Webmentions collates, likes, retweets, mentions, and replies. But I'm only interested in replies for now so I filtered everything else out using &lt;code&gt;wm_property&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight graphql"&gt;&lt;code&gt;&lt;span class="k"&gt;query&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;comments&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;allMentions&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sortBy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"published"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; 
    &lt;/span&gt;&lt;span class="n"&gt;wm_property&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;eq&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"in-reply-to"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;edges&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="n"&gt;author&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="n"&gt;photo&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="n"&gt;published&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"MMMM Do, YYYY"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="n"&gt;wm_target&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Unfortunately, Gridsome isn't able to pass javascript variables into GraphQL queries just yet. Which means I couldn't filter the mentions by post. This query returns all the mentions for all the posts. To get around this, I requested &lt;code&gt;wm_target&lt;/code&gt; which gave me the url to the post linked to that specific mention. All I needed to do was filter out the ones I didn't need after I loaded the mentions. Then I had a nice, clean array of "comments" that I could loop over and render as I please.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;computed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;comments&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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;$static&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;comments&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&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;$static&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;comments&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;edges&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;comment&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;comment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;wm_target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;match&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;postSlug&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;comment&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;comment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;node&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;h3&gt;
  
  
  3. Re-build the site when new comments are posted
&lt;/h3&gt;

&lt;p&gt;Because the comments get baked in at build-time, new mentions won't be rendered until I re-build the site. All I had to do there was set up a new pipeline trigger and pass the webhook URL to webmention.io. Whenever I get a new mention, webmention.io calls the webhook, GitLab triggers a build, and my site is redeployed. There's a potential 10-30 minute delay, but it's a small price to pay. Besides, the conversation is happening on Twitter, I'm just re-rendering it on this site.&lt;/p&gt;

&lt;h2&gt;
  
  
  Something Borrowed
&lt;/h2&gt;

&lt;p&gt;The RSS feed is a great first step towards discoverability, but I want to get my posts in front of a bigger audience. This is something Medium gave me for free and one of the main reasons I switched to using it. But there is another way.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://dev.to"&gt;Dev.to&lt;/a&gt; is a similar platform to Medium, but with a lot of the junk removed. If I didn't want to own my own data, this would be the platform I'd use. But just because it's not my primary platform, doesn't mean I can't borrow it when I want to. Dev.to can automatically cross-post your items to their platform. All you have to do is pass it your RSS feed. It's a good job we've just added one of those!&lt;/p&gt;

&lt;p&gt;If you're reading this post on &lt;a href="https://dev.to/samdbeckham"&gt;Dev.to&lt;/a&gt;, then this process worked!&lt;/p&gt;

&lt;h2&gt;
  
  
  Something Blue
&lt;/h2&gt;

&lt;p&gt;I'll be honest, this is where the analogy falls down. How about a lovely, blue footer?&lt;/p&gt;

&lt;p&gt;I'm just getting back into writing again. I hope to continue to add little improvements to this blog and write about them as I go. If you end out using any of these techniques on your own sites, let me know!&lt;/p&gt;

</description>
      <category>jamstack</category>
      <category>blog</category>
      <category>webmentions</category>
      <category>rss</category>
    </item>
    <item>
      <title>Running the JAMstack on GitLab</title>
      <dc:creator>Sam Beckham</dc:creator>
      <pubDate>Thu, 03 Oct 2019 10:37:08 +0000</pubDate>
      <link>https://dev.to/samdbeckham/running-the-jamstack-on-gitlab-gok</link>
      <guid>https://dev.to/samdbeckham/running-the-jamstack-on-gitlab-gok</guid>
      <description>&lt;p&gt;The JAMstack has piqued my interest. Not because it's fast, secure, or totally hot right now. But because it got me thinking, "We could run that on GitLab".&lt;/p&gt;

&lt;p&gt;These thoughts cross my mind a lot. GitLab is such a versatile tool that it has endless applications. I use GitLab to run a conference, keep track of my wedding plans, and even help create GitLab. It's almost definitely a case of &lt;a href="https://en.wikipedia.org/wiki/Law_of_the_instrument"&gt;Maslow's Hammer&lt;/a&gt; where, &lt;em&gt;"…everything looks like a nail"&lt;/em&gt;. But who cares, I'm eyeing up a JAMstack shaped nail right now, with my GitLab branded hammer in hand.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is the JAMstack?
&lt;/h2&gt;

&lt;p&gt;Before we dig into what I did, let's explain what the JAMstack is. The acronym itself stands for &lt;strong&gt;J&lt;/strong&gt;avascript, &lt;strong&gt;A&lt;/strong&gt;PIs, and &lt;strong&gt;M&lt;/strong&gt;arkup. Which on its own, could describe almost any website. Here's a quote from the top of &lt;a href="https://jamstack.org/"&gt;https://jamstack.org/&lt;/a&gt; that explains it better:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A modern architecture —&lt;br&gt;&lt;br&gt;
Create fast and secure sites and dynamic apps with &lt;strong&gt;J&lt;/strong&gt;avaScript, &lt;strong&gt;A&lt;/strong&gt;PIs, and prerendered &lt;strong&gt;M&lt;/strong&gt;arkup, served without web servers.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The key part here is &lt;strong&gt;"served without web servers"&lt;/strong&gt;. JAMstack sites are static. They do all the compiling at build time, not at runtime. It's often confused with isomorphic websites built with &lt;a href="https://nextjs.org/"&gt;Next&lt;/a&gt;, &lt;a href="https://nuxtjs.org/"&gt;Nuxt&lt;/a&gt;, or similar. But the concept is entirely different. We're not rendering pages on the server, then hydrating them once they get to the frontend. We're building the pages once (at build time) then serving up those static pages at runtime. This makes for a blazing fast website with little to no server costs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Let's do this
&lt;/h2&gt;

&lt;p&gt;If you hadn't already guessed, the project I created on the JAMstack is the blog you're reading right now. It looks and behaves in much the same way that it did previously, it's just a lot faster, and easier to update.&lt;/p&gt;

&lt;p&gt;The first thing I needed to do was pick a static site generator. I had a look at &lt;a href="https://www.gatsbyjs.org/"&gt;Gatsby&lt;/a&gt; and &lt;a href="https://www.11ty.io/"&gt;Eleventy&lt;/a&gt;, but ultimately settled on &lt;a href="https://gridsome.org/"&gt;Gridsome&lt;/a&gt;. It's based on &lt;a href="https://vuejs.org/"&gt;Vue.js&lt;/a&gt; and integrates with &lt;a href="https://graphql.org/"&gt;GraphQL&lt;/a&gt;, which is something I've been wanting to look into for a while. This seemed like the perfect excuse.&lt;/p&gt;

&lt;p&gt;With the site generator chosen, I needed to work out where to store my data. When this site was running on Jekyll, I used markdown files stored in the repo. Whilst this worked well enough, I wanted a little more flexibility. Something that required a CMS. I chose &lt;a href="https://www.sanity.io/"&gt;Sanity&lt;/a&gt; based on a recommendation from &lt;a href="https://twitter.com/jamiebradley234"&gt;Jamie Bradley&lt;/a&gt;. Sanity is a headless CMS with a GraphQL output layer, a perfect fit for my needs.&lt;/p&gt;

&lt;p&gt;With those two chosen, I had everything I needed to get started developing locally. I moved over all my old posts to Sanity, which was a lot harder than I expected. I wish there was an easy way to convert markdown to Sanity blocks (Sanity's rich text editor) but I had to do it all manually. I guess the one upside to my low output on this blog is that there weren't too many posts to migrate.&lt;/p&gt;

&lt;p&gt;I already had a lot of the components of my blog written in Vue from when I tried to move it over to &lt;a href="https://vuepress.vuejs.org/"&gt;VuePress&lt;/a&gt;. A Vue based static site generator that ended out being unfit for purpose. I copied these components over, tweaked them to fit, and added my GraphQL layer. Then I was good to go.&lt;/p&gt;

&lt;p&gt;Once I was ready to deploy, I copied the example &lt;a href="https://gridsome.org/docs/deploy-to-gitlab/"&gt;&lt;code&gt;.gitlab-ci&lt;/code&gt;&lt;/a&gt; file from Gridsome's documentation and pushed the site to GitLab. This triggered the build process on GitLab's CI pipeline and built out the static site to GitLab pages. After two painless DNS additions, I had my domain hooked up to GitLab pages with &lt;a href="https://about.gitlab.com/2019/07/22/gitlab-12-1-released/#automatic-https-for-pages-with-lets-encrypt"&gt;automatic SSL certs&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;With very little set up—which consisted more of copy/pasting than any real hard work—I had my blog up-and-running. When I pushed a change to the &lt;code&gt;master&lt;/code&gt; branch, GitLab's CI pipeline would trigger a build, regenerate all the static files, and re-deploy my website. This was the boring solution. The smallest possible change I could make to get my blog migrated to the JAMstack with GitLab.&lt;/p&gt;

&lt;h2&gt;
  
  
  Iterating on the Boring Solution
&lt;/h2&gt;

&lt;p&gt;The boring solution was great, but are were a few small tweaks I could do make to make the process a little nicer.&lt;/p&gt;

&lt;p&gt;Because my content was in Sanity, GitLab wasn't aware of any changes I made to it. The content publised on the website would be the content from Sanity at the time of the most recent build. I could manually trigger the pipeline after I published a change to pull in the latest data, but I'm far too lazy for that. I needed to automate this. Thankfully, GitLab allows you to create &lt;a href="https://docs.gitlab.com/ee/ci/triggers/"&gt;pipeline triggers&lt;/a&gt; that can listen to a webhook and start a pipeline every time it's used. On the Sanity side of things, you can &lt;a href="https://www.sanity.io/docs/data-store/webhooks#adding-webhook"&gt;call that webhook&lt;/a&gt; every time you publish a post. Now, whenever I publish a post, it triggers the pipeline then the website is re-built and deployed. It's not as instant as a usual CMS set up but it only takes a minute or so, and it only has to happen once.&lt;/p&gt;

&lt;p&gt;I'm also a bit of a stickler for performance. The boring solution was fast, but still fell down on a few points. I've fixed them all now and it's blazing fast. I'll save the story of how I did that for another day. To summarise, I minify, lazy-load, pre-fetch, and cache all of the things. I also added in a service worker for good measure and that sweet &lt;a href="https://sam.beckham.io/wrote/greased-lightning"&gt;offline first&lt;/a&gt; experience.&lt;/p&gt;

&lt;h2&gt;
  
  
  Serverless functions
&lt;/h2&gt;

&lt;p&gt;Whilst this site has no need for serverless functions (Functions as a Service, FaaS), they are a large part of the JAMstack. Static websites are great for sites with little user interaction beyond reading information. But what if your site needed to be more complex? What if you needed to allow users to log in, make purchases, or post content? This is where serverless functions come in. They're API endpoints that (when called) spin up a VM, run some code, then disappear again until they're needed next time. There are a lot of different platforms that offer this service. AWS lambda is the biggest right now, but there's also Netlify, Firebase, and a load of other providers that offer functions as a service. Including—you guessed it—&lt;a href="https://about.gitlab.com/2018/12/11/introducing-gitlab-serverless/"&gt;GitLab&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you want a more in-depth look at how FaaS can be used with the JAMstack, take a look at this section of, "&lt;a href="https://youtu.be/OUbkm4AH9g4?t=1498"&gt;Are you being servered&lt;/a&gt;" by Phil Hawksworth. Then go back to the beginning and watch the entire talk. It's a great example of what you can do with the JAMstack.&lt;/p&gt;

&lt;h2&gt;
  
  
  Have a go yourself
&lt;/h2&gt;

&lt;p&gt;If you want to have a go at building your own JAMstack website on GitLab, I've made &lt;a href="https://gitlab.com/samdbeckham/sam.beckham.io"&gt;this project&lt;/a&gt; completely public. You can look at the &lt;a href="https://gitlab.com/samdbeckham/sam.beckham.io/tree/master"&gt;code&lt;/a&gt;, clone the project yourself, and even watch my &lt;a href="https://gitlab.com/samdbeckham/sam.beckham.io/pipelines"&gt;pipelines&lt;/a&gt;. If GitLab's not your thing, head on over to &lt;a href="https://www.staticgen.com/"&gt;staticgen.com&lt;/a&gt;, pick a generator, and get your site up and running in seconds on &lt;a href="https://www.netlify.com/"&gt;Netlify&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>jamstack</category>
      <category>gitlab</category>
      <category>blog</category>
      <category>story</category>
    </item>
    <item>
      <title>Deep Copy and the Immutability Issue</title>
      <dc:creator>Sam Beckham</dc:creator>
      <pubDate>Sun, 25 Feb 2018 18:08:09 +0000</pubDate>
      <link>https://dev.to/samdbeckham/deep-copy-and-the-immutability-issue--cd6</link>
      <guid>https://dev.to/samdbeckham/deep-copy-and-the-immutability-issue--cd6</guid>
      <description>&lt;p&gt;In the latest episode of, "&lt;a href="https://www.youtube.com/watch?v=v6FQrXHv2h8&amp;amp;index=7&amp;amp;list=PLQnVLZV0MsRKVTJvKDTFsyWYRLn499JP6"&gt;I Have No Idea What I'm Doing&lt;/a&gt;" I learned that everything I thought I knew about immutability in Javascript was a lie.&lt;/p&gt;

&lt;p&gt;Okay, so I'm being dramatic. Not everything was a lie. But a fundamental part of my understanding was incorrect. After speaking to a few people about the issue, it seems this was a shared misconception.&lt;/p&gt;

&lt;p&gt;This all stems from a subtle, yet fundamental difference in how we copy objects in javascript. Deep copying and shallow copying.&lt;/p&gt;

&lt;p&gt;Deep copying is what we want for true immutable data. It is a copy of all the values of an object, and all the values of all the objects within it. Shallow copying—on the other hand—is a copy of all the values of an object, with references to all the objects within it. This is what tripped me up.&lt;/p&gt;

&lt;p&gt;To understand the problem, we need to look at &lt;strong&gt;three ways of copying objects&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Referencing
&lt;/h2&gt;

&lt;p&gt;Okay, so let's strip this all the way back. Let's create a mutable reference to an object.&lt;br&gt;
&lt;/p&gt;

&lt;div class="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;initialObject&lt;/span&gt; &lt;span class="o"&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;Sam&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;twitter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@samdbeckham&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;newObject&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;initialObject&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 bad for immutability because any changes to &lt;code&gt;newObject&lt;/code&gt; reflect in &lt;code&gt;initialObject&lt;/code&gt; like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;newObject&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;twitter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@frontendne&lt;/span&gt;&lt;span class="dl"&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="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;initialObject&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;twitter&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// @frontendne&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;In this example, &lt;code&gt;newObject&lt;/code&gt; is a reference to &lt;code&gt;initialObject&lt;/code&gt;. So whenever we get or set data on either of these objects it is also applied to the other object. This is useful in a lot of different ways, but not great for immutability.&lt;/p&gt;

&lt;h2&gt;
  
  
  Shallow copying
&lt;/h2&gt;

&lt;p&gt;This is the most common form of copying data in an immutable manner. We utilise the spread operator to create a copy of &lt;code&gt;initialObject&lt;/code&gt;. If you've used redux before, you'll have seen this inside your reducers.&lt;br&gt;
&lt;/p&gt;

&lt;div class="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;initialObject&lt;/span&gt; &lt;span class="o"&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;Sam&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;twitter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@samdbeckham&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;newObject&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="nx"&gt;initialObject&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;It's a subtle change, but the &lt;code&gt;...&lt;/code&gt; makes all the difference. &lt;code&gt;newObject&lt;/code&gt; is no-longer linked to &lt;code&gt;initialObject&lt;/code&gt;. It's now a copy of the data and an entirely new object. So if we make the same change we did earlier, we get the following result:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;newObject&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;twitter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@frontendne&lt;/span&gt;&lt;span class="dl"&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="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;initialObject&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;twitter&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// @samdbeckham&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;newObject&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;twitter&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// @frontendne&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Modifying the data on &lt;code&gt;newObject&lt;/code&gt; doesn't affect &lt;code&gt;initialObject&lt;/code&gt; anymore. We can go about our day, modifying &lt;code&gt;newObject&lt;/code&gt; and &lt;code&gt;initialObject&lt;/code&gt; remains clean. &lt;/p&gt;

&lt;p&gt;But this is a shallow copy, and the immutability is only one level deep. To show this, we need an object inside our &lt;code&gt;initialObject&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="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;initialObject&lt;/span&gt; &lt;span class="o"&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;Sam&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;social&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;twitter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@samdbeckham&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;youtube&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;frontendne&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;newObject&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="nx"&gt;initialObject&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;At first glance, this &lt;code&gt;newObject&lt;/code&gt; looks like an immutable copy of &lt;code&gt;initialObject&lt;/code&gt; but look what happens when we do this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;newObject&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;social&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;twitter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@frontendne&lt;/span&gt;&lt;span class="dl"&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="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;initialObject&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;social&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;twitter&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// @frontendne&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Sadly, the immutability is only skin deep. As soon as we go down another level, we're back to referencing values. If we were to open up &lt;code&gt;newObject&lt;/code&gt;, it would look a bit like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="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;newObject&lt;/span&gt; &lt;span class="o"&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;Sam&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;social&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;initialObject&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;social&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;We can get around this issue by shallow copying one level deeper and defining &lt;code&gt;newObject&lt;/code&gt; like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="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;newObject&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="nx"&gt;initialObject&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;social&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;initialObject&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;social&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 how it's usually dealt with in redux, but it only adds one more level of immutability. If there are any other nested objects they will still be stored as references. You can see how (with certain data structures) this could get messy.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;&lt;strong&gt;Note:&lt;/strong&gt;  &lt;code&gt;Object.assign()&lt;/code&gt; and &lt;code&gt;Object.freeze()&lt;/code&gt; have the same shallow copying issues as spread.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Deep Copying
&lt;/h2&gt;

&lt;p&gt;Finally, we get to deep copying. Deep copying offers us true object immutability. We can change &lt;strong&gt;any&lt;/strong&gt; value in an object—no matter how deeply nested it is—and it won't mutate the data we copied it from.&lt;br&gt;
&lt;/p&gt;

&lt;div class="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;initialObject&lt;/span&gt; &lt;span class="o"&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;Sam&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;social&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;twitter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@samdbeckham&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;youtube&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;frontendne&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;newObject&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;deepCopy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;initialObject&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;newObject&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;social&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;twitter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@frontendne&lt;/span&gt;&lt;span class="dl"&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="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;initialObject&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;social&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;twitter&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// @samdbeckham&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;newObject&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;social&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;twitter&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// @frontendne&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Hooray! We're immutable!&lt;/p&gt;

&lt;p&gt;Unfortunately, Javascript doesn't have a function called &lt;code&gt;deepCopy()&lt;/code&gt; so we've had to make our own; and it isn't pretty. There's no "nice" way to handle deep copying in Javascript. Das Surma wrote a &lt;a href="https://dassur.ma/things/deep-copy/"&gt;article on deep copy&lt;/a&gt; which has a few good examples, here are some of the simpler ones.&lt;/p&gt;

&lt;h3&gt;
  
  
  JSON
&lt;/h3&gt;

&lt;p&gt;This is the most concise and easy to understand method, and it looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="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;deepCopy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;object&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;parse&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="nx"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;object&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;First we turn the object into a JSON string with &lt;code&gt;JSON.stringify()&lt;/code&gt; then we convert that string back into an object with &lt;code&gt;JSON.parse()&lt;/code&gt;. Stringifying the data throws out all references, making the returned object completely immutable. But, if there are any references we need to keep inside this object, they're gone. If we have any maps, regex, dates, or other special types; they're gone. If we have any cyclic objects inside the object (which we shouldn't) the whole thing breaks and throws &lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors/Cyclic_object_value"&gt;an error&lt;/a&gt;. So it's not very robust.&lt;/p&gt;

&lt;h3&gt;
  
  
  Data Laundering
&lt;/h3&gt;

&lt;p&gt;If you don't want want to deal with the issues the JSON parser brings, there are a few—albeit hacky—methods you can can use. These all revolve around pasing our data to a service, then querying that service to pull our cleaned data back out. It's like money laundering, only with data, and nowhere near as cool.&lt;/p&gt;

&lt;p&gt;For example, we can utilise the notification API:&lt;br&gt;
&lt;/p&gt;

&lt;div class="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;deepCopy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;object&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="nx"&gt;Notification&lt;/span&gt;&lt;span class="p"&gt;(&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;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;object&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;silent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&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;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;This triggers a notification, silences it, then returns the data from that notification. Unfortunately, the user needs to be able to receive notifications for this to work.&lt;/p&gt;

&lt;p&gt;We can also utilise the history API and the &lt;code&gt;messageChannel&lt;/code&gt; in similar ways. But they all have their downsides.&lt;/p&gt;

&lt;h2&gt;
  
  
  What do now?
&lt;/h2&gt;

&lt;p&gt;Deep copying is a bit of a heavy-handed approach to immutability. Being aware of the gotchas of shallow copying should be enough to see you through most problems. You can use the nested spread method outlined above to fix any problem areas.&lt;br&gt;
If this approach is starting to get unwieldy, you should aim to improve your data structure first.&lt;/p&gt;

&lt;p&gt;If you absolutely &lt;strong&gt;need&lt;/strong&gt; deep copying, then fear not. There's an &lt;a href="https://github.com/whatwg/html/issues/793"&gt;issue on the HTML spec&lt;/a&gt; that hopes to address this, with the introduction of &lt;code&gt;structuredClone()&lt;/code&gt;. The more visibility this gets, the more likely it is to be implemented. Until then, I'd suggest using a library like &lt;a href="https://facebook.github.io/immutable-js/"&gt;Immutable.js&lt;/a&gt; to handle your immutability. Or grab the &lt;code&gt;cloneDeep()&lt;/code&gt; helper from the &lt;a href="https://lodash.com/docs/4.17.5#cloneDeep"&gt;underscore&lt;/a&gt; library for a quick-fix.&lt;/p&gt;

&lt;p&gt;If you're up for a challenge, try coming up with your own solution to deepCopy. My friend Niall had a lot of fun &lt;a href="https://twitter.com/JAMXCORE/status/965716621896667137"&gt;playing with some ideas on Twitter&lt;/a&gt;. I'd be interested to see what you all come up with.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;iframe width="710" height="399" src="https://www.youtube.com/embed/v6FQrXHv2h8"&gt;
&lt;/iframe&gt;

&lt;/h2&gt;

&lt;p&gt;This post was originally published on &lt;a href="https://sam.beckham.io/wrote/deep-copying-and-the-immutability-issue.html"&gt;my website&lt;/a&gt;&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>immutability</category>
      <category>deepcopy</category>
      <category>functionaljs</category>
    </item>
  </channel>
</rss>
