<?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: Simon MacDonald</title>
    <description>The latest articles on DEV Community by Simon MacDonald (@macdonst).</description>
    <link>https://dev.to/macdonst</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%2F7457%2F264ecf25-839f-473d-8042-c68021748fe8.JPG</url>
      <title>DEV Community: Simon MacDonald</title>
      <link>https://dev.to/macdonst</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/macdonst"/>
    <language>en</language>
    <item>
      <title>Island Architecture with Web Components</title>
      <dc:creator>Simon MacDonald</dc:creator>
      <pubDate>Thu, 18 Jul 2024 20:22:10 +0000</pubDate>
      <link>https://dev.to/begin/island-architecture-with-web-components-3hnp</link>
      <guid>https://dev.to/begin/island-architecture-with-web-components-3hnp</guid>
      <description>&lt;p&gt;Island Architecture is a concept first described by &lt;a href="https://front-end.social/@ksylor" rel="noopener noreferrer"&gt;Katie Sylor-Miller&lt;/a&gt; and later expanded upon by &lt;a href="https://mastodon.social/@developit" rel="noopener noreferrer"&gt;Jason Miller&lt;/a&gt; in his &lt;a href="https://jasonformat.com/islands-architecture/" rel="noopener noreferrer"&gt;post&lt;/a&gt;. Island Architecture, as described by Jason, involves server-rendering HTML and then injecting placeholders or slots around highly dynamic regions. These regions, or "islands," are initially rendered on the server and then upgraded to interactive components on the client side.&lt;/p&gt;

&lt;p&gt;🤔 Does that sound like any framework you know?&lt;/p&gt;

&lt;p&gt;By breaking down a web page into smaller, independently upgradeable units, Island Architecture achieves a balance between server-side rendering (SSR) and client-side interactivity. This method is akin to progressive enhancement but with a focus on maintaining a clear separation between static and dynamic content​.&lt;/p&gt;

&lt;p&gt;Let’s use the page you are currently reading as an example.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F10agoi76n62hv3adxc4q.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F10agoi76n62hv3adxc4q.png" alt="page structure" width="800" height="871"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The majority of the page is static content but we have two islands of interactivity, the header and our “made with” component. Both are server side rendered with default content on the initial page load, then they are upgraded to interactive components when their script tag loads.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; go ahead and turn off JavaScript in your browser. The page will continue to function.&lt;/p&gt;

&lt;h2&gt;
  
  
  Benefits of Using Web Components in an Island Architecture
&lt;/h2&gt;

&lt;p&gt;Web components are a perfect fit for Island Architecture due to their encapsulation, reusability, and ability to function independently. Here's how they enhance the architecture:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Encapsulation and Reusability:&lt;/strong&gt; Web components allow developers to create custom HTML elements with their own styles and behaviors. This encapsulation ensures that each component can be developed, tested, and maintained independently, aligning perfectly with the principles of Island Architecture.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Progressive Hydration:&lt;/strong&gt; Web components facilitate progressive hydration, where interactive elements are initialized over time based on their visibility and importance. This technique improves performance by ensuring that only the essential parts of the page are interactive immediately, reducing the initial load time.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SEO and Accessibility:&lt;/strong&gt; By serving meaningful HTML from the server, Island Architecture ensures that the essential content is accessible to search engines and assistive technologies. Web components enhance this by allowing developers to build interactive elements without compromising the underlying HTML structure.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Improved Performance:&lt;/strong&gt; Unlike SPAs that require a complete JavaScript bundle to be loaded and parsed before rendering the page, Island Architecture loads smaller JavaScript chunks corresponding to individual components. This leads to faster load times and a better user experience, especially on mobile devices​.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Modern JavaScript frameworks contain a lot of JavaScript. However, most web pages/applications fall somewhere between needing no JavaScript (for example: a docs site or blog) and sites that need a lot of JavaScript (like an interactive dashboard or social media site).&lt;/p&gt;

&lt;p&gt;Can you imagine sending a modern JS framework over the wire just to create this page when there are only two components that require JavaScript?  I checked one major framework blog page and it included 1.4 MB of JavaScript. Contrast that with the 7 kb of JavaScript used on this page.&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating an Enhance Island
&lt;/h2&gt;

&lt;p&gt;When building a web application with Enhance we default to using &lt;a href="https://enhance.dev/docs/conventions/elements" rel="noopener noreferrer"&gt;elements&lt;/a&gt; as the reusable building blocks. Our elements are server side renderable out of the box, offering incredible performance and a seamless path for progressive enhancement.&lt;/p&gt;

&lt;p&gt;However, when you are building islands of interactivity with Enhance, &lt;a href="https://enhance.dev/docs/conventions/components" rel="noopener noreferrer"&gt;components&lt;/a&gt; are the solution you are looking for. They offer all the same benefits of elements as they are server side renderable but they also include client side JavaScript so you can add interactivity.&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;CustomElement&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;@enhance/custom-element&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MadeWith&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;CustomElement&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="k"&gt;super&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;getRandomItem&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;getRandomItem&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;bind&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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;placeContainer&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="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;.js-made-with-place&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;emojiContainer&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="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;.js-made-with-emoji&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;swap&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;swap&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;bind&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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;selectedVariant&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&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;variants&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="na"&gt;place&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Longmont, CO&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="na"&gt;emojis&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;&amp;amp;#127957;&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// camping&lt;/span&gt;
       &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;&amp;amp;#129452;&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// bison&lt;/span&gt;
       &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;&amp;amp;#127784;&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// snow cloud&lt;/span&gt;
       &lt;span class="p"&gt;],&lt;/span&gt;
     &lt;span class="p"&gt;},&lt;/span&gt;
     &lt;span class="err"&gt;…&lt;/span&gt; &lt;span class="c1"&gt;// etc.&lt;/span&gt;
   &lt;span class="p"&gt;]&lt;/span&gt;
 &lt;span class="p"&gt;}&lt;/span&gt;

 &lt;span class="nf"&gt;render&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="p"&gt;{&lt;/span&gt;
   &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="s2"&gt;`
    Made with &amp;lt;span class="js-made-with-emoji"&amp;gt;&amp;amp;#128150;&amp;lt;/span&amp;gt;
    &amp;lt;span class="inline-block"&amp;gt;in
      &amp;lt;span class='js-made-with-place'&amp;gt;California&amp;lt;/span&amp;gt;
    &amp;lt;/span&amp;gt;
   `&lt;/span&gt;
 &lt;span class="p"&gt;}&lt;/span&gt;

 &lt;span class="nf"&gt;getRandomItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;array&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="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;array&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;floor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;random&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
 &lt;span class="p"&gt;}&lt;/span&gt;

 &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;getNewVariant&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;selection&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="nf"&gt;getRandomItem&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;variants&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

   &lt;span class="c1"&gt;// avoid selecting the same variant twice in a row&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;selection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;place&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;selectedVariant&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;place&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="nf"&gt;getNewVariant&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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;selectedVariant&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;selection&lt;/span&gt;
     &lt;span class="k"&gt;return&lt;/span&gt;
   &lt;span class="p"&gt;}&lt;/span&gt;
 &lt;span class="p"&gt;}&lt;/span&gt;

 &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;swap&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getNewVariant&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;placeContainer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;innerHTML&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;selectedVariant&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;place&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;emojiContainer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;innerHTML&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="nf"&gt;getRandomItem&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;selectedVariant&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;emojis&lt;/span&gt;
   &lt;span class="p"&gt;)&lt;/span&gt;
 &lt;span class="p"&gt;}&lt;/span&gt;

 &lt;span class="nf"&gt;connectedCallback&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;interval&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;setInterval&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;swap&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
 &lt;span class="p"&gt;}&lt;/span&gt;

 &lt;span class="nf"&gt;disconnectedCallback&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cancelIterval&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;interval&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;customElements&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;define&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;made-with&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;MadeWith&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Wrapping Up
&lt;/h2&gt;

&lt;p&gt;Using frameworks like Enhance can simplify the implementation of Island Architecture with web components. Enhance’s approach involves rendering the entire page as static HTML at request time and then selectively hydrating only the necessary components on the client side. This method ensures that the critical content is always available instantly, and interactive parts are progressively enhanced as needed​.&lt;/p&gt;

&lt;p&gt;In conclusion, combining web components with Island Architecture offers a powerful way to build modern web applications that are fast, scalable, and accessible. By focusing on rendering static content server-side and enhancing it with client-side interactivity, developers can achieve a seamless user experience without the drawbacks of traditional heavyweight JavaScript frameworks.&lt;/p&gt;

&lt;h2&gt;
  
  
  Further Reading
&lt;/h2&gt;

&lt;p&gt;If you are interested in learning how to communicate between different islands on your page, read our post on &lt;a href="https://dev.to/begin/component-communication-in-enhance-2de3"&gt;Component Communication in Enhance&lt;/a&gt;&lt;/p&gt;

</description>
      <category>enhance</category>
      <category>webdev</category>
      <category>webcomponents</category>
    </item>
    <item>
      <title>Component Communication in Enhance</title>
      <dc:creator>Simon MacDonald</dc:creator>
      <pubDate>Thu, 18 Jul 2024 20:15:29 +0000</pubDate>
      <link>https://dev.to/begin/component-communication-in-enhance-2de3</link>
      <guid>https://dev.to/begin/component-communication-in-enhance-2de3</guid>
      <description>&lt;p&gt;Enhance makes it easy to build applications composed of web components. Each component encapsulates its own UI and functionality. However, under some circumstances you may need to communicate between components to share data or react to changes elsewhere on the page. In this post, we are going to learn a few ways of achieving communication between components.&lt;/p&gt;

&lt;p&gt;In our first example we will build a chart component that is composed of three child components (blue border): &lt;code&gt;chart-options&lt;/code&gt;, &lt;code&gt;chart-type&lt;/code&gt; and &lt;code&gt;reactive-chart&lt;/code&gt; and their parent component (orange border) &lt;code&gt;chart-container&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe3yv5iefiysacw2bqloh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe3yv5iefiysacw2bqloh.png" alt="Chart example" width="712" height="286"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Child to Parent communication
&lt;/h2&gt;

&lt;p&gt;For this type of communication, we leverage the web platform by using Events for communication from child to parent.&lt;/p&gt;

&lt;p&gt;In our example app, when the user clicks on the "Show Labels" switch we want to toggle whether or not the labels are displayed on our chart. This means we will need to build a web component that listens to the change event from the switch, marshals up the important data and then fires a &lt;code&gt;CustomEvent&lt;/code&gt; that the parent component will listen to.&lt;/p&gt;

&lt;p&gt;Here’s the full code listing for you to look over then we’ll point out some of the salient features.&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;// app/components/chart-options.mjs&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;CustomElement&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;@enhance/custom-element&lt;/span&gt;&lt;span class="dl"&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="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ChartOptions&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;CustomElement&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       &lt;span class="k"&gt;super&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;optionChanged&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;optionChanged&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;bind&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="p"&gt;}&lt;/span&gt;

   &lt;span class="nf"&gt;connectedCallback&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="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;change&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;optionChanged&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
   &lt;span class="p"&gt;}&lt;/span&gt;

   &lt;span class="nf"&gt;disconnectedCallback&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="nf"&gt;removeEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;change&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;optionChanged&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
   &lt;span class="p"&gt;}&lt;/span&gt;

   &lt;span class="nf"&gt;optionChanged&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;evt&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="nf"&gt;dispatchEvent&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;CustomEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;chartoption&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;bubbles&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;detail&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
               &lt;span class="na"&gt;attribute&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;evt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&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;evt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;checked&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;render&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="p"&gt;{&lt;/span&gt;
       &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="s2"&gt;`
         &amp;lt;slot&amp;gt;&amp;lt;/slot&amp;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;customElements&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;define&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;chart-options&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ChartOptions&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the web component’s &lt;code&gt;constructor&lt;/code&gt; method on line 6 you might notice this 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;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;optionChanged&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;optionChanged&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;bind&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We do this so that when the &lt;code&gt;optionsChanged&lt;/code&gt; event listener is invoked that &lt;code&gt;this&lt;/code&gt; is bound to our web component instead of the default binding which would be undefined.&lt;/p&gt;

&lt;p&gt;Next we’ll register/unregister our event listener in our &lt;code&gt;connectedCallback&lt;/code&gt; and &lt;code&gt;disconnectedCallback&lt;/code&gt; methods.&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;connectedCallback&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="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;change&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;optionChanged&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;disconnectedCallback&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="nf"&gt;removeEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;change&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;optionChanged&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 my preference for handling event listeners. There is some discussion about whether or not this is a best practice. See &lt;a href="https://nolanlawson.com/2024/01/13/web-component-gotcha-constructor-vs-connectedcallback/" rel="noopener noreferrer"&gt;Nolan Lawson&lt;/a&gt;, &lt;a href="https://hawkticehurst.com/writing/you-are-probably-using-connectedcallback-wrong/" rel="noopener noreferrer"&gt;Hawk Ticehurst&lt;/a&gt;, and &lt;a href="https://gomakethings.com/youre-probably-using-connectedcallback-wrong-in-your-web-component/" rel="noopener noreferrer"&gt;Chris Ferdinandi&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Then in our &lt;code&gt;optionsChanged&lt;/code&gt; event handler we create a &lt;code&gt;CustomEvent&lt;/code&gt; called &lt;code&gt;chartoption&lt;/code&gt; which will be dispatched up the DOM to our parent 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="nf"&gt;optionChanged&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;evt&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="nf"&gt;dispatchEvent&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;CustomEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;chartoption&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;bubbles&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;detail&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;attribute&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;evt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&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;evt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;checked&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;By using a &lt;code&gt;CustomEvent&lt;/code&gt; we can differentiate our &lt;code&gt;change&lt;/code&gt; event from other &lt;code&gt;change&lt;/code&gt; events elsewhere on the page and customize our payload.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; You may be wondering why we fire a &lt;code&gt;CustomEvent&lt;/code&gt; instead of letting the change event bubble up to our parent component. There are two very good reasons for this. The first is that we want to encapsulate the business logic into our &lt;code&gt;chart-options&lt;/code&gt; component. Since the parent component is listening for a &lt;code&gt;chartoption&lt;/code&gt; event changing the underlying implementation of the &lt;code&gt;chart-options&lt;/code&gt; component can be done without disturbing the parent component. Secondly, we have another component called &lt;code&gt;chart-type&lt;/code&gt; which would also be firing a change event and we want to be able to differentiate between the two components easily.&lt;/p&gt;

&lt;p&gt;Finally, our &lt;code&gt;render&lt;/code&gt; method is quite short:&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;render&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="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="s2"&gt;`
        &amp;lt;slot&amp;gt;&amp;lt;/slot&amp;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 we are using the &lt;a href="https://adactio.com/journal/20618" rel="noopener noreferrer"&gt;HTML web components&lt;/a&gt; pattern. Our web component is just a wrapper around our checkbox input element. For example:&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;chart-options&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;e-input-group&lt;/span&gt; &lt;span class="na"&gt;field-class=&lt;/span&gt;&lt;span class="s"&gt;"flex justify-content-between align-items-center"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;fieldset&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;label&amp;gt;&lt;/span&gt;Multiple&lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;e-switch&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;is=&lt;/span&gt;&lt;span class="s"&gt;"switch"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"checkbox"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"multiple"&lt;/span&gt; &lt;span class="na"&gt;checked&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;/e-switch&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/fieldset&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/e-input-group&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/chart-options&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With all this in place we are now able to communicate state changes from child components to the parent using the native web platform but how does the parent component listen for this &lt;code&gt;chartoption&lt;/code&gt; event?&lt;/p&gt;

&lt;h2&gt;
  
  
  Parent to Child communication
&lt;/h2&gt;

&lt;p&gt;In the previous section our child component was firing a &lt;code&gt;chartoption&lt;/code&gt; event. Our parent component needs to listen for this event and communicate down to the child components by setting attributes on the child component.&lt;/p&gt;

&lt;p&gt;Once again here is the full code listing. We’ll call out the important bits next.&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;// app/components/chart-container.mjs&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;CustomElement&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;@enhance/custom-element&lt;/span&gt;&lt;span class="dl"&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="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ChartContainer&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;CustomElement&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       &lt;span class="k"&gt;super&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;updateOption&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;updateOption&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;bind&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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;updateType&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;updateType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;bind&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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;chart&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="s1"&gt;reactive-chart&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;connectedCallback&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="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;chartoption&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;updateOption&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="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;charttype&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;updateType&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
   &lt;span class="p"&gt;}&lt;/span&gt;

   &lt;span class="nf"&gt;disconnectedCallback&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="nf"&gt;removeEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;chartoption&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;updateOption&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="nf"&gt;removeEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;charttype&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;updateType&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
   &lt;span class="p"&gt;}&lt;/span&gt;

   &lt;span class="nf"&gt;updateOption&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;evt&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;attribute&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;evt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;detail&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;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;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;chart&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;attribute&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="k"&gt;else&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;chart&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;removeAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;attribute&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;updateType&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;evt&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="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;evt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;detail&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;chart&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;type&lt;/span&gt;&lt;span class="dl"&gt;'&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="p"&gt;}&lt;/span&gt;

   &lt;span class="nf"&gt;render&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="p"&gt;{&lt;/span&gt;
       &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="s2"&gt;`
   &amp;lt;e-box ord="primary"&amp;gt;
       &amp;lt;e-row&amp;gt;
           &amp;lt;e-col span="3"&amp;gt;
               &amp;lt;h4 class="mar-t-xs"&amp;gt;Chart Options&amp;lt;/h4&amp;gt;
               &amp;lt;chart-options&amp;gt;
                   &amp;lt;e-input-group field-class="flex justify-content-between align-items-center" &amp;gt;
                       &amp;lt;fieldset&amp;gt;
                           &amp;lt;label&amp;gt;Multiple&amp;lt;/label&amp;gt;
                           &amp;lt;e-switch&amp;gt;
                               &amp;lt;input is="switch" type="checkbox" name="multiple" checked&amp;gt;
                           &amp;lt;/e-switch&amp;gt;

                       &amp;lt;/fieldset&amp;gt;
                   &amp;lt;/e-input-group&amp;gt;
                   &amp;lt;e-input-group field-class="flex justify-content-between align-items-center" &amp;gt;
                       &amp;lt;fieldset&amp;gt;
                           &amp;lt;label&amp;gt;Show Labels&amp;lt;/label&amp;gt;
                           &amp;lt;e-switch&amp;gt;
                               &amp;lt;input is="switch" type="checkbox" checked name="show-labels" &amp;gt;
                           &amp;lt;/e-switch&amp;gt;
                       &amp;lt;/fieldset&amp;gt;
                   &amp;lt;/e-input-group&amp;gt;
               &amp;lt;/chart-options&amp;gt;
       &amp;lt;/e-col&amp;gt;
       &amp;lt;e-col&amp;gt;
           &amp;lt;div&amp;gt;
               &amp;lt;reactive-chart
                   data-key="medals"
                   type="bar"
                   heading="2016 Summer Olympics Medal Table"
                   value-key="Country"
                   value-names="Gold,Silver,Bronze"
                   multiple
                   show-labels&amp;gt;
               &amp;lt;/reactive-chart&amp;gt;
               &amp;lt;chart-type&amp;gt;
                   &amp;lt;e-input-group enhanced="✨"&amp;gt;
                       &amp;lt;legend&amp;gt;Chart Type&amp;lt;/legend&amp;gt;
                       &amp;lt;fieldset&amp;gt;
                           &amp;lt;input id="bar" type="radio" name="type" value="bar" checked&amp;gt;
                           &amp;lt;label for="bar"&amp;gt;Bar&amp;lt;/label&amp;gt;
                           &amp;lt;input id="column" type="radio" name="type" value="column"&amp;gt;
                           &amp;lt;label for="column"&amp;gt;Column&amp;lt;/label&amp;gt;
                           &amp;lt;input id="area" type="radio" name="type" value="area"&amp;gt;
                           &amp;lt;label for="area"&amp;gt;Area&amp;lt;/label&amp;gt;
                           &amp;lt;input id="line" type="radio" name="type" value="line"&amp;gt;
                           &amp;lt;label for="line"&amp;gt;Line&amp;lt;/label&amp;gt;
                       &amp;lt;/fieldset&amp;gt;
                   &amp;lt;/e-input-group&amp;gt;
               &amp;lt;/chart-type&amp;gt;
           &amp;lt;/div&amp;gt;
       &amp;lt;/e-col&amp;gt;
   &amp;lt;/e-row&amp;gt;
   &amp;lt;/e-box&amp;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;customElements&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;define&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;chart-container&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ChartContainer&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In our &lt;code&gt;connectedCallback&lt;/code&gt; method we are listening for two &lt;code&gt;CustomEvents&lt;/code&gt;. The first one for &lt;code&gt;chartoption&lt;/code&gt; is the only one we’ll be going in depth into for this 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="nf"&gt;connectedCallback&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="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;chartoption&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;updateOption&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="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;charttype&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;updateType&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;When our &lt;code&gt;chart-container&lt;/code&gt; component receives a &lt;code&gt;chartoption&lt;/code&gt; event it calls the &lt;code&gt;updateOption&lt;/code&gt; method:&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;updateOption&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;evt&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;attribute&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;evt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;detail&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;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;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;chart&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;attribute&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="k"&gt;else&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;chart&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;removeAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;attribute&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;From the event's details we can pull off the name of the attribute we need to set and whether the switch is on or off. If our &lt;code&gt;value&lt;/code&gt; is &lt;code&gt;true&lt;/code&gt; we set an attribute on the &lt;code&gt;reactive-chart&lt;/code&gt; component. If our &lt;code&gt;value&lt;/code&gt; is false, we remove the attribute.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; for more details on dealing with binary attributes see &lt;a href="https://developer.mozilla.org/en-US/docs/Glossary/Boolean/HTML" rel="noopener noreferrer"&gt;MDN&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;With this attribute being added or removed from the &lt;code&gt;reactive-chart&lt;/code&gt; component, it will be notified via its  &lt;code&gt;attributeChangedCallback&lt;/code&gt;. Then our chart can re-render itself based on the updated attributes.&lt;/p&gt;

&lt;p&gt;Check out the &lt;a href="https://horse-6n8.begin.app/chart" rel="noopener noreferrer"&gt;demo&lt;/a&gt; and &lt;a href="https://github.com/macdonst/e-components/tree/reactive" rel="noopener noreferrer"&gt;source code&lt;/a&gt; for this example.&lt;/p&gt;

&lt;h2&gt;
  
  
  Shared State or Store
&lt;/h2&gt;

&lt;p&gt;For larger applications, managing state between multiple components can become complex. In such cases, using a shared state management solution can be beneficial and that’s why &lt;code&gt;@enhance/store&lt;/code&gt; exists. Store based communication is not limited to a parent-child relation. You can share information between any components.&lt;/p&gt;

&lt;p&gt;In our second example we’ll simplify our chart component that is still composed of three child components (blue border): &lt;code&gt;chart-options&lt;/code&gt;, &lt;code&gt;chart-type&lt;/code&gt; and &lt;code&gt;reactive-chart&lt;/code&gt;. We will replace &lt;code&gt;CustomEvents&lt;/code&gt; with a shared state so that sibling elements can communicate with each other instead of requiring a parent element to receive events and pass down attribute changes.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2tcncetlh98rvnik0in6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2tcncetlh98rvnik0in6.png" alt="Another chart example" width="800" height="240"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;@enhance/store&lt;/code&gt; is a &lt;a href="https://en.wikipedia.org/wiki/Singleton_pattern" rel="noopener noreferrer"&gt;singleton&lt;/a&gt; reactive data store. The store provides an interface that allows components to subscribe to updates to the store or specific properties on the store.&lt;/p&gt;

&lt;p&gt;For our example we will create a new file called &lt;code&gt;app/browser/api.mjs&lt;/code&gt;. That is where we will initialize our store.&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;Store&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;@enhance/store&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;store&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Store&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="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;API&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="nf"&gt;initialize&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="nx"&gt;store&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
     &lt;span class="na"&gt;subscribe&lt;/span&gt;&lt;span class="p"&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;subscribe&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
     &lt;span class="na"&gt;unsubscribe&lt;/span&gt;&lt;span class="p"&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;unsubscribe&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 API interface will give components access to our store and a convenient way to subscribe/unsubscribe the changes to data in the store.&lt;/p&gt;

&lt;p&gt;For our "position" selection box we will use the &lt;code&gt;chart-options&lt;/code&gt; component from the previous example. Switching it from firing a &lt;code&gt;CustomEvent&lt;/code&gt; to updating the store. The &lt;code&gt;optionChanged&lt;/code&gt; method becomes:&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;optionChanged&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;evt&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;api&lt;/span&gt;&lt;span class="p"&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;position&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;evt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Since we are no longer sending &lt;code&gt;CustomEvents&lt;/code&gt; the &lt;code&gt;chart-container&lt;/code&gt; component becomes purely presentational. You can remove all of the business logic and just have a render method. For 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="c1"&gt;// app/components/chart-container.mjs&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;CustomElement&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;@enhance/custom-element&lt;/span&gt;&lt;span class="dl"&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="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ChartContainer&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;CustomElement&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="nf"&gt;render&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="p"&gt;{&lt;/span&gt;
       &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="s2"&gt;`
       &amp;lt;e-row&amp;gt;
       &amp;lt;e-col colspan="12"&amp;gt;
           &amp;lt;e-box ord="primary"&amp;gt;
               &amp;lt;e-row&amp;gt;
               &amp;lt;e-col&amp;gt;
               &amp;lt;chart-type&amp;gt;
                   &amp;lt;e-input-group&amp;gt;
                       &amp;lt;legend&amp;gt;Sort By&amp;lt;/legend&amp;gt;
                       &amp;lt;fieldset&amp;gt;
                           &amp;lt;input id="goals" type="radio" name="type" value="goals"&amp;gt;
                           &amp;lt;label for="goals"&amp;gt;Goals&amp;lt;/label&amp;gt;
                           &amp;lt;input id="assists" type="radio" name="type" value="assists"&amp;gt;
                           &amp;lt;label for="assists"&amp;gt;Assists&amp;lt;/label&amp;gt;
                           &amp;lt;input id="points" type="radio" name="type" value="points" checked&amp;gt;
                           &amp;lt;label for="points"&amp;gt;Points&amp;lt;/label&amp;gt;
                       &amp;lt;/fieldset&amp;gt;
                   &amp;lt;/e-input-group&amp;gt;
               &amp;lt;/chart-type&amp;gt;
               &amp;lt;/e-col&amp;gt;
               &amp;lt;e-col&amp;gt;
               &amp;lt;chart-options&amp;gt;
                   &amp;lt;e-input-group field-class="flex justify-content-between align-items-center" &amp;gt;
                       &amp;lt;fieldset&amp;gt;
                           &amp;lt;select&amp;gt;
                               &amp;lt;option value="all"&amp;gt;Position&amp;lt;/option&amp;gt;
                               &amp;lt;option value="C"&amp;gt;Centre&amp;lt;/option&amp;gt;
                               &amp;lt;option value="L"&amp;gt;Left Wing&amp;lt;/option&amp;gt;
                               &amp;lt;option value="R"&amp;gt;Right Wing&amp;lt;/option&amp;gt;
                               &amp;lt;option value="D"&amp;gt;Defence&amp;lt;/option&amp;gt;
                         &amp;lt;/select&amp;gt;
                       &amp;lt;/fieldset&amp;gt;
                   &amp;lt;/e-input-group&amp;gt;
               &amp;lt;/chart-options&amp;gt;
               &amp;lt;/e-col&amp;gt;
               &amp;lt;/e-row&amp;gt;
               &amp;lt;reactive-chart
                   data-key="scorers"
                   type="points"
                   heading="2023 NHL Scoring Leaders"
                   value-key="Points"
                   value-names="Goals,Assists,Points"
                   position="all"
                   multiple
                   show-labels&amp;gt;
               &amp;lt;/reactive-chart&amp;gt;
               &amp;lt;/e-box&amp;gt;
           &amp;lt;/e-col&amp;gt;
       &amp;lt;/e-row&amp;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;customElements&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;define&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;chart-container&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ChartContainer&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now in our &lt;code&gt;reactive-chart&lt;/code&gt; we’ll subscribe to changes in the store.&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;connectedCallback&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;api&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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;update&lt;/span&gt;&lt;span class="p"&gt;,[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;chartData&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;position&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;type&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;disconnectedCallback&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;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;unsubscribe&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;update&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 the &lt;code&gt;chart-options&lt;/code&gt; or &lt;code&gt;chart-type&lt;/code&gt; component updates the state the &lt;code&gt;update&lt;/code&gt; method of &lt;code&gt;reactive-chart&lt;/code&gt; will be invoked.&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;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;payload&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;payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;position&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="nf"&gt;setAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;position&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&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;position&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;payload&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="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="nf"&gt;setAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&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;type&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;payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;chartData&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// re-render chart with updated 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;Then the component takes advantage of the &lt;code&gt;attributeChangedCallback&lt;/code&gt; to re-render itself based on the &lt;code&gt;position&lt;/code&gt; or &lt;code&gt;type&lt;/code&gt; filter you set.&lt;/p&gt;

&lt;p&gt;Check out the &lt;a href="https://shiny-qmm.begin.app/chart" rel="noopener noreferrer"&gt;demo&lt;/a&gt; and &lt;a href="https://github.com/macdonst/e-components/tree/reactive-store" rel="noopener noreferrer"&gt;source code&lt;/a&gt; for this example.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;By leveraging what the web platform already provides we can communicate between parent and child components by passing attributes down to the child and events up to the parent. This is enough for most web applications and we caution you not to rush into more complicated solutions. When interactions get more complex, then consider adopting a shared state solution like &lt;code&gt;@enhance/store&lt;/code&gt;.&lt;/p&gt;

</description>
      <category>enhance</category>
      <category>webdev</category>
      <category>webcomponents</category>
    </item>
    <item>
      <title>A React Developers Guide to Writing Enhance Components</title>
      <dc:creator>Simon MacDonald</dc:creator>
      <pubDate>Fri, 08 Mar 2024 18:28:23 +0000</pubDate>
      <link>https://dev.to/begin/a-react-developers-guide-to-writing-enhance-components-onc</link>
      <guid>https://dev.to/begin/a-react-developers-guide-to-writing-enhance-components-onc</guid>
      <description>&lt;p&gt;Frequently, we are asked by React developers why patterns they have learned while writing components using JSX do not translate to writing web components. In this post, I'll try to capture some common gotchas that developers coming from React or other JavaScript view frameworks may run into when writing plain vanilla web components. Thanks to &lt;a href="https://enhance.dev/discord"&gt;Discord&lt;/a&gt; user &lt;code&gt;taphill&lt;/code&gt; for the inspiration on this post.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why is there so much confusion?
&lt;/h2&gt;

&lt;p&gt;In my opinion, it is because &lt;strong&gt;JSX is not HTML&lt;/strong&gt;. Some folks may see that as controversial, but it isn't. In fact, legacy React documentation &lt;a href="https://legacy.reactjs.org/docs/introducing-jsx.html#:~:text=This%20funny%20tag%20syntax%20is,the%20full%20power%20of%20JavaScript."&gt;explicitly states&lt;/a&gt; that JSX is not HTML.&lt;/p&gt;

&lt;h3&gt;
  
  
  JSX is not…
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;HTML&lt;/li&gt;
&lt;li&gt;A String&lt;/li&gt;
&lt;li&gt;A templating language&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  JSX is…
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;A syntax extension to JavaScript&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This explains why JSX must be compiled into vanilla JavaScript before it can run in the browser.&lt;/p&gt;

&lt;p&gt;In the immortal words of &lt;a href="https://www.tiktok.com/@tanaradoublechocolate"&gt;tanaradoublechocolate&lt;/a&gt;, “See how this looks like something you've seen before but isn't?"&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;element&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;h1&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;title&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;/h1&amp;gt;&lt;/span&gt;&lt;span class="err"&gt;;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Without JSX, you would write this as:&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;element&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`&amp;lt;h1&amp;gt;&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="s2"&gt;&amp;lt;/h1&amp;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 syntax changes are not significant, but they are enough to trip you up as you transition from writing React components to web components.&lt;/p&gt;

&lt;p&gt;Let's take a look at some of the other frequent gotchas that developers are likely to encounter.&lt;/p&gt;

&lt;h2&gt;
  
  
  Quoting Attributes
&lt;/h2&gt;

&lt;p&gt;In JSX, it is customary to provide the value of an attribute without quotes:&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;image&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;img&lt;/span&gt; &lt;span class="nx"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;href&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="nx"&gt;alt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;altText&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is because properties in React are passed by reference, not by value. This makes a lot of sense when you think about how React handles re-renders. The child component needs to know when the parent component has changed the value of the property.&lt;/p&gt;

&lt;p&gt;Writing the same tag using a string template in JavaScript would look 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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;image&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`&amp;lt;img src="&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;href&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;" alt="&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;altText&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;" /&amp;gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We are using a &lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals"&gt;string template literal&lt;/a&gt; to create the tag and the &lt;code&gt;${}&lt;/code&gt; syntax to provide string interpolation, that is, substituting the values of &lt;code&gt;href&lt;/code&gt; and &lt;code&gt;altText&lt;/code&gt; into our string.&lt;/p&gt;

&lt;p&gt;However, this doesn't address why we double quote our attributes. Even the HTML specification says these quotes are optional. Well, they are optional until they aren't. Let's look at an 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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;href&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://images.pexels.com/photos/45201/kitty-cat-kitten-pet-45201.jpeg&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;altText&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;white cat&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;image&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`&amp;lt;img src="&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;href&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;" alt="&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;altText&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;" /&amp;gt;`&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This produces the HTML&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;img&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://images.pexels.com/photos/45201/kitty-cat-kitten-pet-45201.jpeg"&lt;/span&gt; &lt;span class="na"&gt;alt=&lt;/span&gt;&lt;span class="s"&gt;"white cat"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt; \
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If we had omitted the double quoting of attributes, we get&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;img&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;https://images.pexels.com/photos/45201/kitty-cat-kitten-pet-45201.jpeg&lt;/span&gt; &lt;span class="na"&gt;alt=&lt;/span&gt;&lt;span class="s"&gt;white&lt;/span&gt; &lt;span class="na"&gt;cat&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt; \
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nothing would break, but your HTML would be subtly wrong. The image's alt text would only be &lt;code&gt;white&lt;/code&gt;, and there would be this dangling attribute on the image tag with the name of &lt;code&gt;cat&lt;/code&gt;. The browser will still display the image correctly, but your alt text will be wrong.&lt;/p&gt;

&lt;h2&gt;
  
  
  Naming Attributes
&lt;/h2&gt;

&lt;p&gt;It is well known that when you specify CSS class names in a HTML page you use the &lt;code&gt;class&lt;/code&gt; attribute. As well, it is commonly known that when you specify CSS class names in a JSX you use the &lt;code&gt;className&lt;/code&gt; attribute — but why is that? The answer refers back to the beginning of this post, JSX is not HTML, JSX is an extension of JavaScript and &lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/class"&gt;&lt;code&gt;class&lt;/code&gt; is a reserved word&lt;/a&gt; in JavaScript. This necessitates that when writing JSX you use &lt;code&gt;className&lt;/code&gt; instead, just as the JSX attribute &lt;code&gt;htmlFor&lt;/code&gt; is used instead of the  &lt;code&gt;for&lt;/code&gt; attribute.&lt;/p&gt;

&lt;p&gt;React &lt;a href="https://legacy.reactjs.org/docs/dom-elements.html#all-supported-html-attributes"&gt;recommends that you use camel cased attributes&lt;/a&gt;. Once again this is fine when you are writing JavaScript, but in HTML all &lt;a href="https://html.spec.whatwg.org/multipage/dom.html#embedding-custom-non-visible-data-with-the-data-*-attributes:~:text=All%20attribute%20names%20on%20HTML%20elements%20in%20HTML%20documents%20get%20ASCII%2Dlowercased%20automatically"&gt;attributes are lowercased&lt;/a&gt;. So when you are writing web components, it's best to default to lowercase attribute names — or use kebab or snake case if you must.&lt;/p&gt;

&lt;p&gt;Of particular concern here is that &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements#responding_to_attribute_changes"&gt;the attributeChangedCallback function&lt;/a&gt; will not fire for attributes with uppercase characters (because of the aforementioned parser behavior). Therefore, attributes named with multiple words should be delimited using kebab-casing or snake_casing (or not delimited at all).&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;case&lt;/th&gt;
&lt;th&gt;attribute&lt;/th&gt;
&lt;th&gt;JS access&lt;/th&gt;
&lt;th&gt;&lt;code&gt;attributeChangedCallback&lt;/code&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;lower&lt;/td&gt;
&lt;td&gt;&lt;code&gt;imagewidth&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;attrs.imagewidth&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;kebab&lt;/td&gt;
&lt;td&gt;&lt;code&gt;image-width&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;attrs['image-width']&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;snake&lt;/td&gt;
&lt;td&gt;&lt;code&gt;image_width&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;attrs.image_width&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;camel&lt;/td&gt;
&lt;td&gt;&lt;code&gt;imageWidth&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;attrs.imageWidth&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Complex Attributes
&lt;/h2&gt;

&lt;p&gt;React developers are used to being able to pass objects into their components via properties. When writing HTML you are limited to using attributes — and in HTML, all attributes are strings. For 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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;person&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;user0&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;firstName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Axol&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;lastName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Lotl&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;element&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;User&lt;/span&gt; &lt;span class="nx"&gt;person&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;person&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="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;User&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;person&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="err"&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 your User component you can access all the properties of the &lt;code&gt;person&lt;/code&gt; object.&lt;/p&gt;

&lt;p&gt;However, when writing web components if you wrote:&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;person&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;user0&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;firstName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Axol&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;lastName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Lotl&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;element&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`&amp;lt;user-card person="&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;person&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&amp;gt;&amp;lt;/user-card&amp;gt;`&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then inside your &lt;code&gt;user-card&lt;/code&gt; component the value of &lt;code&gt;person&lt;/code&gt; would be &lt;code&gt;[object Object]&lt;/code&gt;. This is to be expected since, once again, all attributes are strings. So, how do we go about passing objects into web components?&lt;/p&gt;

&lt;p&gt;If the object is relatively small, you can set multiple attributes on your 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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;element&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`&amp;lt;user-card id="&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;person&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;"  firstname="&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;person&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;firstName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"  lastname="&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;person&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lastName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&amp;gt;&amp;lt;/user-card&amp;gt;`&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;However, if your object is very complex, you will want to fetch your data from the component. We use the store to hold complex data when writing an Enhance web component. For 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="c1"&gt;// app/elements/user-card.mjs&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;UserCard&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;state&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;attrs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;store&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;state&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;id&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;attrs&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;users&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;store&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;users&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="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="s2"&gt;`… `&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// app/pages/card.mjs&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;element&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`&amp;lt;user-card id="&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;"&amp;gt;&amp;lt;/user-card&amp;gt;`&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;p&gt;Tip: For more information on the &lt;a href="https://enhance.dev/docs/elements/state/store"&gt;Store&lt;/a&gt; read the &lt;a href="https://enhance.dev/docs"&gt;Enhance Docs&lt;/a&gt;.&lt;/p&gt;



&lt;h2&gt;
  
  
  Rendering Markup from Arrays
&lt;/h2&gt;

&lt;p&gt;A common pattern in many apps is looping over some data to create our UI. Quite often in React apps you will see something 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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;todoList&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;ul&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;todos&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;todo&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="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;li&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;todo&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="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;todo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;text&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;/li&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;/ul&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;When using this pattern in an Enhance app, you might automatically write the web component version of this code 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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;todoList&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`&amp;lt;ul&amp;gt;
  &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;todos&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;todo&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="s2"&gt;`&amp;lt;li key="&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;todo&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;"&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;todo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;/li&amp;gt;`&lt;/span&gt;
  &lt;span class="p"&gt;))}&lt;/span&gt;&lt;span class="s2"&gt;
&amp;lt;/ul&amp;gt;`&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That works, sort of. When viewing your output in the browser, you will notice a comma between each &lt;code&gt;li&lt;/code&gt; tag. That is because the &lt;code&gt;map&lt;/code&gt; function returns an &lt;code&gt;array&lt;/code&gt;. When you convert an &lt;code&gt;array&lt;/code&gt; to a string in JavaScript, it adds that comma between each item.&lt;/p&gt;

&lt;p&gt;In order to prevent this default behavior, we use the &lt;code&gt;join&lt;/code&gt; method.&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;todoList&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`&amp;lt;ul&amp;gt;
  &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;todos&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;todo&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="s2"&gt;`&amp;lt;li key="&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;todo&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;"&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;todo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;/li&amp;gt;`&lt;/span&gt;
  &lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;join&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="s2"&gt;
&amp;lt;/ul&amp;gt;`&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We call the &lt;code&gt;join&lt;/code&gt; method in the above code with the empty string. This will produce one large string that will get inserted into the DOM. If you prefer to make your HTML source pretty, you can pass the newline character to the &lt;code&gt;join&lt;/code&gt; method. Don't forget to escape the newline character properly; it should look like &lt;code&gt;join('\\n')&lt;/code&gt;.&lt;/p&gt;

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

&lt;p&gt;Hopefully that clears up some of the confusion when coming from React projects to writing web components — whether or not you are using the Enhance framework or not. If you are interested in learning more about Enhance:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://fosstodon.org/@enhance_dev"&gt;Follow&lt;/a&gt; Axol, the Enhance Mascot on Mastodon…&lt;/li&gt;
&lt;li&gt;Join the &lt;a href="https://enhance.dev/discord"&gt;Enhance Discord&lt;/a&gt; and share what you've built, or ask for help.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>enhance</category>
      <category>react</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Removing React is just weakness leaving your codebase</title>
      <dc:creator>Simon MacDonald</dc:creator>
      <pubDate>Wed, 31 Jan 2024 19:44:25 +0000</pubDate>
      <link>https://dev.to/begin/removing-react-is-just-weakness-leaving-your-codebase-4pin</link>
      <guid>https://dev.to/begin/removing-react-is-just-weakness-leaving-your-codebase-4pin</guid>
      <description>&lt;p&gt;Apologies to &lt;a href="https://en.wikipedia.org/wiki/Chesty_Puller"&gt;Chesty Puller&lt;/a&gt; for appropriating his quote, &lt;em&gt;“Pain is weakness leaving the body.”&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;It’s 2024, and you are about to start a new project. Do you reach for &lt;a href="https://react.dev/"&gt;React&lt;/a&gt;, a framework you know and love or do you look at one of the other hot new frameworks like &lt;a href="https://astro.build/"&gt;Astro&lt;/a&gt;, &lt;a href="https://enhance.dev"&gt;Enhance&lt;/a&gt;, &lt;a href="https://www.11ty.dev/"&gt;11ty&lt;/a&gt;, &lt;a href="https://kit.svelte.dev/"&gt;SvelteKit&lt;/a&gt; or gasp, plain vanilla Web Components?&lt;/p&gt;

&lt;p&gt;In this post, I will enumerate why I no longer use React and haven’t for the past two years after 7 years of thrashing about with the library.&lt;/p&gt;

&lt;p&gt;Quick note: I started writing this post before taking off for the holidays. Well, there must have been something in the zeitgeist, as we’ve seen a spate of articles where developers are voicing their displeasure with React.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://macwright.com/2024/01/03/miffed-about-react"&gt;Increasingly miffed about the state of React &lt;/a&gt;releases by &lt;a href="https://mastodon.social/@tmcw"&gt;Tom MacWright&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://blog.cassidoo.co/post/annoyed-at-react/"&gt;Kind of annoyed at React&lt;/a&gt; by &lt;a href="https://notacult.social/@cassidoo"&gt;Cassidy Williams&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://dev.to/matfrana/react-where-are-you-going-5284"&gt;React, where are you going?&lt;/a&gt; By Matteo Frana&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://gomakethings.com/the-decline-of-react/"&gt;The Decline of React&lt;/a&gt; by &lt;a href="https://mastodon.social/@cferdinandi"&gt;Chris Ferdinandi&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://johan.hal.se/wrote/2024/01/24/concatenating-text/"&gt;Concatenating text&lt;/a&gt; by &lt;a href="https://ruby.social/@hejsna"&gt;Johan Halse&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It’s not that React has been immune to criticism, as &lt;a href="https://fediverse.zachleat.com/@zachleat"&gt;Zach Leatherman&lt;/a&gt; has detailed in his post &lt;a href="https://www.zachleat.com/web/react-criticism/"&gt;A Historical Reference of React Criticism&lt;/a&gt;, but it sure seems like we’ve reached a tipping point.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Shiny Object Syndrome
&lt;/h2&gt;

&lt;p&gt;React has used the &lt;a href="https://en.wikipedia.org/wiki/Shiny_object_syndrome"&gt;shiny object syndrome&lt;/a&gt; to its advantage.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Shiny object syndrome is the situation where people focus undue attention on an idea that is new and trendy, yet drop it in its entirety as soon as something new can take its place.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5md8mobzbdifdvnege8k.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5md8mobzbdifdvnege8k.jpg" alt="raven" width="800" height="781"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So, how is React like the shiny object syndrome? Obviously, there was a lot of hype around React when it was originally released by Meta nee Facebook, which had developers flocking to the library.&lt;/p&gt;

&lt;p&gt;Whether on purpose or not, React took advantage of this situation by continuously delivering or promising to deliver changes to the library, with a brand new API being released every 12 to 18 months. Those new APIs and the breaking changes they introduce are the new shiny objects you can’t help but chase. You spend multiple cycles learning the new API and upgrading your application. It sure feels like you are doing something, but in reality, you are only treading water.&lt;/p&gt;

&lt;p&gt;Just in the time that I had been coding React applications, the library went through some major changes.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;2013: JSX&lt;/li&gt;
&lt;li&gt;2014: React createElement API&lt;/li&gt;
&lt;li&gt;2015: Class Components&lt;/li&gt;
&lt;li&gt;2018: Functional Components&lt;/li&gt;
&lt;li&gt;2018: Hooks&lt;/li&gt;
&lt;li&gt;2023: React Server Components&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By my reckoning, if you’ve maintained a React codebase for the past decade, you’ve re-written your application at least three times and possibly four.&lt;/p&gt;

&lt;p&gt;Let’s take React out of the equation and imagine you had to justify to your boss that you needed to completely re-write your application every 2.5 years. You probably got approved to do the first rewrite, you might have got approval for the second rewrite but there is no way in hell you got approval for the third and fourth rewrites.&lt;/p&gt;

&lt;p&gt;By choosing React, we’ve signed up for a lot of unplanned work. Think of the value we could have produced for our users and company if we weren’t subject to the whims of whatever the cool kids were doing over in React.&lt;/p&gt;

&lt;p&gt;Stop signing up for breaking changes!&lt;/p&gt;

&lt;h2&gt;
  
  
  The Rule of Least Power
&lt;/h2&gt;

&lt;p&gt;When building web applications, we should remember the rule of least power.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;When designing computer systems, one is often faced with a choice between using a more or less powerful language for publishing information, for expressing constraints, or for solving some problem. This finding explores tradeoffs relating the choice of language to reusability of information. The "Rule of Least Power" suggests choosing the least powerful language suitable for a given purpose.&lt;/p&gt;

&lt;p&gt;W3C - &lt;a href="https://www.w3.org/2001/tag/doc/leastPower.html"&gt;https://www.w3.org/2001/tag/doc/leastPower.html&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3h5bubh2j160e7nout0j.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3h5bubh2j160e7nout0j.jpg" alt="rule of least power" width="800" height="782"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When building on the web, we could take this to mean that we should start with a base of HTML for our content, add CSS for presentation where necessary, and finally sprinkle in JavaScript to add interactivity and other capabilities that HTML and CSS cannot handle. React flips this on its head by starting with JavaScript to generate the HTML content, adding JavaScript to apply the CSS presentation too, and finally adding yet more JavaScript to apply the interactivity.&lt;/p&gt;

&lt;p&gt;This contravenes the rule of least power.&lt;/p&gt;

&lt;h3&gt;
  
  
  Presentation
&lt;/h3&gt;

&lt;p&gt;Here’s a simple React heading 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="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;MyHeading&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="k"&gt;return &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;h2&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;children&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;/h2&lt;/span&gt;&lt;span class="err"&gt;&amp;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;It looks like you are writing HTML, but you are not. You’re writing JSX (JavaScript syntax extension). Fundamentally, JSX just provides syntactic sugar for the &lt;code&gt;React.createElement(component, props, ...children) function&lt;/code&gt;. So the JSX &lt;code&gt;&amp;lt;MyHeading&amp;gt;My Heading&amp;lt;/MyHeading&amp;gt;&lt;/code&gt; code compiles to:&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;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;MyHeading&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;My Heading&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;While you may think you are writing HTML, you are actually adding a lot of unnecessary (and possibly unexpected) JavaScript to your application.&lt;/p&gt;

&lt;h3&gt;
  
  
  Styling
&lt;/h3&gt;

&lt;p&gt;CSS in JS libraries are very popular in React applications as they allow you to &lt;del&gt;avoid learning CSS&lt;/del&gt; take a component-based approach to styling. The &lt;a href="https://speakerdeck.com/vjeux/react-css-in-js"&gt;original CSS in JS library&lt;/a&gt; was developed by &lt;a href="https://blog.vjeux.com/"&gt;Christopher Chedeau&lt;/a&gt;, a front-end engineer who worked on the React team at Meta.&lt;/p&gt;

&lt;p&gt;Let’s use JSS for our 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="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;React&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;react&lt;/span&gt;&lt;span class="dl"&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;createUseStyles&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;react-jss&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;useStyles&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createUseStyles&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;myButton&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;green&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;top&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;right&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="na"&gt;bottom&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="na"&gt;left&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;1rem&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;&amp;amp; span&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;fontWeight&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;bold&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;myLabel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;fontStyle&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;italic&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;Button&lt;/span&gt; &lt;span class="o"&gt;=&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="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;classes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useStyles&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="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;button&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;classes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;myButton&lt;/span&gt;&lt;span class="p"&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;span&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;classes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;myLabel&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;children&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;/span&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;/button&lt;/span&gt;&lt;span class="err"&gt;&amp;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 great because we’ve been able to co-locate our styles with our components, but it comes at a cost: performance.&lt;/p&gt;

&lt;p&gt;A quick reminder of how browsers work: first, it downloads your HTML. Then, it downloads any CSS from the &lt;code&gt;head&lt;/code&gt; tag and applies it to the DOM. Finally, it downloads, parses and executes your JavaScript code. Because you have added your CSS declarations in all of your components, your JavaScript bundle is larger than it would be for a non-CSS in JS solution. This will increase the time it takes to parse your code just because of the larger bundle size, as well as the execution time due to the additional function calls to serialize the CSS.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F96ewpp30kxpec0vayenz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F96ewpp30kxpec0vayenz.png" alt="drowning in js" width="800" height="592"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We are drowning in JavaScript.&lt;/p&gt;

&lt;p&gt;Use less of it.&lt;/p&gt;

&lt;p&gt;This is &lt;a href="https://timkadlec.com/2014/09/js-parse-and-execution-time/"&gt;not new advice&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Who’s running React these days?
&lt;/h2&gt;

&lt;p&gt;No really. Who is running React? React’s last official release (as of this writing) is 18.2.0, released on June 14th, 2022. That was 19 months ago. To me, that signals that &lt;del&gt;Facebook&lt;/del&gt;, er Meta, is no longer interested in pushing the library forward and instead, IMHO has &lt;a href="https://react.dev/learn/start-a-new-react-project#production-grade-react-frameworks"&gt;ceded the leadership of React to the frameworks&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://x.com/acdlite/status/1617611126514266112?s=20"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foz49pxci70wrdxfe723q.png" alt="use a framework" width="593" height="219"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Is this a good thing? I’m not too sure. One problem with this arrangement is that Vercel is a venture-capitalist-backed startup, and they need to show a return on investment to their investors. As we’ve seen in the industry in the past year, &lt;a href="https://mas.to/@carnage4life/111755376956948779"&gt;VC funding is drying up&lt;/a&gt;. God forbid something happens to Vercel, but what happens to Next.js — and, to a greater extent, React — if Vercel disappears?&lt;/p&gt;

&lt;p&gt;Additionally, there are worries about Vercel’s current stewardship of React. For instance, Next.js uses Canary versions of React because those are &lt;em&gt;"considered stable for libraries."&lt;/em&gt; &lt;a href="https://react.dev/blog/2023/05/03/react-canaries"&gt;That seems quite odd to me&lt;/a&gt;. As well, Next.js overrides the global implementation of node fetch, which &lt;a href="https://twitter.com/webdevcody/status/1733091163337265531"&gt;leads to problems&lt;/a&gt; — a decision that the Next.js team seems to be &lt;a href="https://x.com/leeerob/status/1733154383410684148?s=20"&gt;rethinking&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I’m not the only one worried about this, as the Remix team has &lt;a href="https://github.com/remix-run/react"&gt;forked React&lt;/a&gt; into their organization. No commits yet, but it makes you wonder.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where do we go from here?
&lt;/h2&gt;

&lt;p&gt;This post may feel pretty negative to you, and from my point of view, it was born of the &lt;a href="https://begin.com/blog/posts/2023-01-24-i-just-wanted-to-buy-pants"&gt;frustration&lt;/a&gt; I’ve had dealing with React applications as a developer and an end user. Don’t even get me started on the GitHub rewrite, which doesn’t seem to be going &lt;a href="https://mastodon.social/@bendelarre/111585575314778379"&gt;too&lt;/a&gt; &lt;a href="https://mastodon.social/@hynek/111543867278988769"&gt;well&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Just so we are clear, you weren’t wrong to choose React for previous projects. It is/was a hugely popular library. Companies were hiring React developers, and you needed a job. No one here is saying you were wrong to use React to further your career.&lt;/p&gt;

&lt;p&gt;However, here is my unsolicited advice:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If you are starting a new project today, evaluate the other options like Enhance, Astro, 11ty, SvelteKit, and vanilla Web Components.&lt;/li&gt;
&lt;li&gt;If you are currently maintaining an existing React application, investigate how you can add web components to your project. Web components are library agnostic, so you can begin to future-proof your application. (&lt;a href="https://angular.io/guide/elements"&gt;Angular&lt;/a&gt;, &lt;a href="https://react.dev/reference/react-dom/components#custom-html-elements"&gt;React&lt;/a&gt;, &lt;a href="https://vuejs.org/guide/extras/web-components"&gt;Vue&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Inquire how you can use HTML and CSS to &lt;a href="https://www.youtube.com/watch?v=qziVRaZqnfE"&gt;replace&lt;/a&gt; some of the things you do in JavaScript currently.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;It may seem hyperbolic to say that React is a liability lurking in your codebase, but is it? You are staring down an eventual React 19.0 release, which will introduce a number of breaking changes, forcing you to rewrite your application yet again.&lt;/p&gt;

&lt;p&gt;Why?&lt;/p&gt;

&lt;p&gt;Is it React’s vaunted developer experience benefits? Or is it the better user experience? &lt;a href="https://infrequently.org/2023/02/the-market-for-lemons/"&gt;Nope and nope&lt;/a&gt;!&lt;/p&gt;

&lt;p&gt;My suggestion is to start investigating how you can remove this weakness from your codebase. Look at ways to de-risk what will be yet another rewrite.&lt;/p&gt;

&lt;p&gt;And then level up even further by learning web fundamentals.&lt;/p&gt;

&lt;p&gt;The web is backward and forward compatible. Anything you learn about HTML, CSS and browser API’s will serve you well for the next 25 years, which is not something you can say about the current fashion in JavaScript libraries. By ejecting from the thrash of React and other heavy-handed frameworks and doubling down on web fundamentals, you’ll be future-proofing both your career and your codebases.&lt;/p&gt;

</description>
      <category>react</category>
      <category>webdev</category>
      <category>webcomponents</category>
    </item>
    <item>
      <title>Shadow DOM: Not by Default</title>
      <dc:creator>Simon MacDonald</dc:creator>
      <pubDate>Fri, 18 Aug 2023 19:53:32 +0000</pubDate>
      <link>https://dev.to/begin/shadow-dom-not-by-default-8l3</link>
      <guid>https://dev.to/begin/shadow-dom-not-by-default-8l3</guid>
      <description>&lt;p&gt;Yesterday, I briefly interacted with &lt;a href="https://front-end.social/@matuzo"&gt;Manuel Matuzović&lt;/a&gt; after reading his Mastodon post on his growing doubts over &lt;em&gt;the shadow DOM in general&lt;/em&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;After almost a year working with web components I'm starting to doubt the usefulness of style encapsulation and shadow DOM in general.&lt;/p&gt;

&lt;p&gt;Styling and some accessibility stuff is so much easier without…&lt;/p&gt;

&lt;p&gt;&lt;a href="https://front-end.social/@matuzo"&gt;Manuel Matuzović&lt;/a&gt;, &lt;a href="https://front-end.social/@matuzo/110904820573072435"&gt;August 17th 2023&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;@matuzo That's how all of us enhance.dev folks feel. The shadow DOM is a tool that should be reached for only when needed. It shouldn't be the default when it comes to working with custom elements/web components.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://mastodon.online/@macdonst"&gt;Simon MacDonald&lt;/a&gt;, &lt;a href="https://mastodon.online/@macdonst/110905389455407088"&gt;August 17th 2023&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a class="mentioned-user" href="https://dev.to/macdonst"&gt;@macdonst&lt;/a&gt; interesting! That's the exact opposite of what e.g. the lit docs say. Do you have your or your team's thoughts on shadow don written down somewhere?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://front-end.social/@matuzo"&gt;Manuel Matuzović&lt;/a&gt;, &lt;a href="https://front-end.social/@matuzo/110906023414558748"&gt;August 17th 2023&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;@matuzo  let me find something or better yet this gives me the excuse to blog about thoughts that have been running through my head for a bit.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://mastodon.online/@macdonst"&gt;Simon MacDonald&lt;/a&gt;, &lt;a&gt;August 17th 2023&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This made me realize we haven’t done the best job of explaining why we don’t default to using the shadow DOM and how Enhance works. So let’s dig in!&lt;/p&gt;

&lt;h2&gt;
  
  
  Why not just use the shadow DOM from the start?
&lt;/h2&gt;

&lt;p&gt;In many cases, you ain’t gonna need it (YAGNI). The light DOM has served the web well for many years, and we can get quite far with it. To enumerate some of the reasons why we don’t immediately reach for the shadow DOM, they would be:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;HTML-first: To keep our page weight down, we defer adding JavaScript until it is absolutely required. For many of our custom elements, we can get by with only HTML and CSS.&lt;/li&gt;
&lt;li&gt;Server-side Rendering: While we are excited about Declarative Shadow DOM it has yet to land in all evergreen browsers (come on FireFox). Until such time it becomes ubiquitous, we’ll stick with our approach.&lt;/li&gt;
&lt;li&gt;Flash of Unstyled Custom Element (FOUCE): as described below, waiting for the &lt;code&gt;customElements.define()&lt;/code&gt; method to be called before your web component is displayed can negatively affect users’ impression of your application.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://kinsta.com/blog/web-components/#ignored-inputs"&gt;Form participation&lt;/a&gt;: by default, elements in the shadow DOM inside a form do not inherit the default behaviors of form elements. For example, a submit button in the shadow DOM will not automatically submit your form when the &lt;code&gt;Enter&lt;/code&gt; key is hit. There is a spec called Form Associated Custom Elements (FACE) that gives you the APIs to build web components that participate in forms. However, fixing a problem created by JavaScript by writing more JavaScript is like handing a drowning man a glass of water, IMHO.&lt;/li&gt;
&lt;li&gt;Styling: I confess that I am CSS challenged, but the shadow DOM introduces a new way of styling components for the sake of style encapsulation. Plus, we have other (easier) ways of ensuring style encapsulation.&lt;/li&gt;
&lt;li&gt;Accessibility: the shadow DOM introduces problems with accessibility. For more info, read this &lt;a href="https://nolanlawson.com/2022/11/28/shadow-dom-and-accessibility-the-trouble-with-aria/"&gt;thoughtful post&lt;/a&gt; from Nolan Lawson.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  What is Enhance?
&lt;/h2&gt;

&lt;p&gt;Enhance is an HTML-first full-stack web framework that gives you everything you need to build standards-based multi-page web apps that perform and scale.&lt;/p&gt;

&lt;h2&gt;
  
  
  Right, but what does that mean?
&lt;/h2&gt;

&lt;p&gt;It means that Enhance is a one-stop solution for building web applications. You write your application using web standards like HTML, CSS and JavaScript. Enhance allows you to server-side render (SSR) your custom elements while providing a path for them to be “enhanced” to full web components.&lt;/p&gt;

&lt;h2&gt;
  
  
  Okay, that sounds good, but how does it work?
&lt;/h2&gt;

&lt;p&gt;Let’s show instead of tell by building a simple message component from the ground up using Enhance. Let’s create our Enhance single file component “app/elements/my-message.mjs”.&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;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;MyMessage&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;state&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;attrs&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;state&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;message&lt;/span&gt; &lt;span class="o"&gt;=&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="nx"&gt;attrs&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="s2"&gt;`
    &amp;lt;h1&amp;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;&amp;lt;/h1&amp;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 very simple custom element that will take the string from the attribute &lt;code&gt;message&lt;/code&gt; and wrap it in a &lt;code&gt;h1&lt;/code&gt; tag. To use it in our HTML page, we’d just write:&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;my-message&lt;/span&gt; &lt;span class="na"&gt;message=&lt;/span&gt;&lt;span class="s"&gt;"Hello World"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/my-message&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Which produces:&lt;/p&gt;

&lt;h1&gt;Hello World&lt;/h1&gt;

&lt;p&gt;When viewed in the browser.&lt;/p&gt;

&lt;p&gt;Great, now we have the basis of our single file component by writing the HTML-first, but now I want to do some styling, so let’s add a &lt;code&gt;style&lt;/code&gt; tag to our 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="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;MyMessage&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;state&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;attrs&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;state&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;message&lt;/span&gt; &lt;span class="o"&gt;=&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="nx"&gt;attrs&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="s2"&gt;`
    &amp;lt;style&amp;gt;
      h1 { color: Crimson; }
    &amp;lt;/style&amp;gt;
    &amp;lt;h1&amp;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;&amp;lt;/h1&amp;gt;
  `&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Refreshing our browser, we now see hello world in crimson.&lt;/p&gt;

&lt;h1&gt;Hello World&lt;/h1&gt;

&lt;p&gt;But wait, wouldn’t that &lt;code&gt;style&lt;/code&gt; tag screw up the style of all the &lt;code&gt;h1&lt;/code&gt; tags on my page? Don’t we need to use the shadow DOM here to encapsulate our component styles away from the rest of the page?&lt;/p&gt;

&lt;p&gt;Well, you could do that, and you wouldn’t be wrong, but one of the philosophies behind Enhance is to delay using the shadow DOM until you absolutely need it instead of immediately reaching for it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Style Transforms
&lt;/h2&gt;

&lt;p&gt;The way Enhance prevents your component styles from interfering with other elements on your page is by running a style transform on the server before sending your HTML to the client. In our above example, it will take the &lt;code&gt;style&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="nt"&gt;&amp;lt;style&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;h1&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Crimson&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;/style&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And hoist it to the &lt;code&gt;head&lt;/code&gt; of your document, where it will look 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;style&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;my-message&lt;/span&gt; &lt;span class="nt"&gt;h1&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Crimson&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;/style&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;If you have more than one &lt;code&gt;my-message&lt;/code&gt; element on your page, the style transform will also deduplicate the CSS so the directives only appear once.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This provides the added benefit of avoiding the dreaded &lt;a href="https://www.abeautifulsite.net/posts/flash-of-undefined-custom-elements/"&gt;Flash of Unstyled Custom Element (FOUCE)&lt;/a&gt; when dealing with web components. This way, you can avoid using the:&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="nd"&gt;:not&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;:defined&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;visibility&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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;trick to hide web components until they are defined by a call to &lt;code&gt;customElements.define()&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  But this isn’t a web component?
&lt;/h2&gt;

&lt;p&gt;True. I’d say what we have built so far is a server-side rendered custom element, and it doesn’t become a real web component until we enhance it (see what I did there) by calling &lt;code&gt;customElements.define()&lt;/code&gt;. So let’s go ahead and round out our single file component by adding in some JavaScript.&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;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;MyMessage&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;state&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;attrs&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;state&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;message&lt;/span&gt; &lt;span class="o"&gt;=&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="nx"&gt;attrs&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="s2"&gt;`
  &amp;lt;style&amp;gt;
    h1 { color: Crimson; }
  &amp;lt;/style&amp;gt;
  &amp;lt;h1&amp;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;&amp;lt;/h1&amp;gt;
  &amp;lt;script type="module"&amp;gt;
    class MyMessage extends HTMLElement {
      constructor() {
        super()
        this.heading = this.querySelector('h1')
      }

      static get observedAttributes() {
        return [ 'message' ]
      }

      attributeChangedCallback(name, oldValue, newValue) {
        if (oldValue !== newValue) {
          if (name === 'message') {
            this.heading.textContent = newValue
          }
        }
      }
  }

  customElements.define('my-message', MyMessage)
  &amp;lt;/script&amp;gt;
`&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ah, now we have a &lt;em&gt;real&lt;/em&gt; web component. If you update the &lt;code&gt;message&lt;/code&gt; attribute of the &lt;code&gt;my-message&lt;/code&gt; tag, the component will re-render itself.&lt;/p&gt;

&lt;p&gt;In our example, we still aren’t using the shadow DOM, and I don’t see any reason why we would need to at this point, but if you really wanted to, you could change the script tag to use the shadow DOM approach.&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;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;MyMessage&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;state&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;attrs&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;state&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;message&lt;/span&gt; &lt;span class="o"&gt;=&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="nx"&gt;attrs&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="s2"&gt;`
    &amp;lt;h1&amp;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;&amp;lt;/h1&amp;gt;
    &amp;lt;script type="module"&amp;gt;
    const template = document.createElement('template')
    template.innerHTML = "&amp;lt;style&amp;gt;h1 { color: Crimson; }&amp;lt;/style&amp;gt;&amp;lt;h1&amp;gt;&amp;lt;/h1&amp;gt;"

    class MyMessage extends HTMLElement {
        constructor() {
          super();
          const shadow = this.attachShadow({ mode: 'open' });
          shadow.appendChild(template.content.cloneNode(true));
        }

        static get observedAttributes() {
          return [ 'message' ]
        }

        attributeChangedCallback(name, oldValue, newValue) {
          if (oldValue !== newValue) {
            if (name === 'message') {
              this.shadowRoot.querySelector('h1').innerText = newValue
            }
          }
        }
      }

      customElements.define('my-message', MyMessage)
    &amp;lt;/script&amp;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 doesn't mean you are required to write vanilla JavaScript web components either. If you are familiar with using &lt;a href="https://www.fast.design/"&gt;Fast&lt;/a&gt; or &lt;a href="https://lit.dev/"&gt;Lit&lt;/a&gt; to write web components you can include those libraries in you Enhance application. However, with the introduction of Enhance base classes for the &lt;a href="https://github.com/enhance-dev/enhance-custom-element"&gt;light&lt;/a&gt; and &lt;a href="https://github.com/enhance-dev/enhance-shadow-element"&gt;shadow&lt;/a&gt; DOM you can get the same DX improvements where you write less boilerplate web component code while enabling the sharing of a render method between the SSR and CSR rendering.&lt;/p&gt;

&lt;h2&gt;
  
  
  Next Steps
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;If you disagree with this article, maybe try out &lt;a href="https://enhance.dev/"&gt;Enhance&lt;/a&gt; in anger and let us know what you think.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://fosstodon.org/@enhance_dev"&gt;Follow&lt;/a&gt; Axol, the Enhance Mascot on Mastodon…&lt;/li&gt;
&lt;li&gt;Join the &lt;a href="https://enhance.dev/discord"&gt;Enhance Discord&lt;/a&gt; and share what you’ve built, or ask for help.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>webcomponents</category>
      <category>shadowdom</category>
    </item>
    <item>
      <title>Introducing Enhance Movies</title>
      <dc:creator>Simon MacDonald</dc:creator>
      <pubDate>Wed, 26 Jul 2023 23:43:28 +0000</pubDate>
      <link>https://dev.to/begin/introducing-enhance-movies-23d9</link>
      <guid>https://dev.to/begin/introducing-enhance-movies-23d9</guid>
      <description>&lt;p&gt;We are excited to present our non-trivial learning application, &lt;a href="https://enhance-movies.com/" rel="noopener noreferrer"&gt;Enhance Movies&lt;/a&gt;, designed to highlight the exceptional web development experience you get from &lt;a href="https://enhance.dev/" rel="noopener noreferrer"&gt;Enhance&lt;/a&gt;. Our movies app is built with Enhance and &lt;a href="https://www.themoviedb.org/" rel="noopener noreferrer"&gt;The Movie Database API&lt;/a&gt;. With a strong focus on simplicity, performance, progressive enhancement, and offline local development capabilities, this application is set to transform your understanding of what can be done by focusing on the web platform.&lt;/p&gt;

&lt;h2&gt;
  
  
  Easy-to-Understand HTML Markup
&lt;/h2&gt;

&lt;p&gt;Our sample application boasts easy-to-understand HTML markup, making it accessible to developers of all levels. Whether you're a seasoned pro or just starting your web development journey, our application ensures that you can quickly grasp and extend its functionality effortlessly.&lt;/p&gt;

&lt;p&gt;Check out the clean markup via the use of custom elements:&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;movie-symbols&amp;gt;&amp;lt;/movie-symbols&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;movie-header&amp;gt;&amp;lt;/movie-header&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;movie-layout&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;movie-feature&lt;/span&gt; &lt;span class="na"&gt;slot=&lt;/span&gt;&lt;span class="s"&gt;"header"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/movie-feature&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;movie-sidebar&lt;/span&gt; &lt;span class="na"&gt;slot=&lt;/span&gt;&lt;span class="s"&gt;"left-sidebar"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/movie-sidebar&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"p0"&lt;/span&gt; &lt;span class="na"&gt;slot=&lt;/span&gt;&lt;span class="s"&gt;"main"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;movies-index-collection&amp;gt;&amp;lt;/movies-index-collection&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;movie-footer&lt;/span&gt; &lt;span class="na"&gt;slot=&lt;/span&gt;&lt;span class="s"&gt;"footer"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/movie-footer&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/movie-layout&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;'/_public/browser/movie-trailer-modal.mjs'&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;Performance is the backbone of any successful web application. We understand the significance of speed and efficiency in delivering a seamless user experience. Our sample application has been meticulously crafted with performance in mind. Say goodbye to sluggish load times and lagging interactions – our application ensures snappy responses and lightning-fast performance across devices and networks.&lt;/p&gt;

&lt;p&gt;Don’t believe us, check out our &lt;a href="https://pagespeed.web.dev/analysis/https-enhance-movies-com/ukiqxxuoi5?form_factor=mobile" rel="noopener noreferrer"&gt;PageSpeed Insights results&lt;/a&gt;.&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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3zb4j4ohz4d1tifi1o5n.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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3zb4j4ohz4d1tifi1o5n.png" alt="Enhance Movies Page Speed Results"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Progressively Enhanced
&lt;/h2&gt;

&lt;p&gt;Building for the web means embracing the diversity of user devices and capabilities. Our application follows a progressive enhancement approach, guaranteeing a fantastic user experience regardless of the user's browser or device. Users with modern browsers will enjoy enhanced features, while those with older setups will still access the core functionality – ensuring inclusivity and wider reach for your projects.&lt;/p&gt;

&lt;p&gt;All the functionality for this application is accomplished with HTML, CSS and just 8.6 kb of JavaScript (3.9 kb compressed). What is even more impressive is you can turn JavaScript off completely and the application still works. Go ahead give it a try!&lt;/p&gt;

&lt;h2&gt;
  
  
  Local Development
&lt;/h2&gt;

&lt;p&gt;The world doesn't have consistent internet access everywhere, but that shouldn't limit you while developing. Our sample application is designed to work seamlessly even in offline scenarios. Developers can access cached content (I hope you like Transformers: Rise of the Beasts), perform tasks, and run searches, thanks to the power of offline functionality via API response mocking.&lt;/p&gt;

&lt;h2&gt;
  
  
  Experience it Yourself
&lt;/h2&gt;

&lt;p&gt;We believe in the power of experience, and thus, we encourage you to try our sample application firsthand. Discover the seamless blend of easy-to-understand HTML markup, performance, progressive enhancement, and local development  – all in one package.&lt;/p&gt;

&lt;p&gt;🔗 &lt;a href="https://enhance-movies.com/" rel="noopener noreferrer"&gt;Live Demo of Enhance Movies&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With this example, we hope to demonstrate the incredible potential of web development using modern web standards — and to help you embrace the future of web development today!&lt;/p&gt;

&lt;h2&gt;
  
  
  Next Steps
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Checkout the source code for the example app at &lt;a href="https://github.com/enhance-dev/enhance-movies" rel="noopener noreferrer"&gt;enhance-movies&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://fosstodon.org/@enhance_dev" rel="noopener noreferrer"&gt;Follow&lt;/a&gt; Axol, the Enhance Mascot on Mastodon..&lt;/li&gt;
&lt;li&gt;Join the &lt;a href="https://enhance.dev/discord" rel="noopener noreferrer"&gt;Enhance Discord&lt;/a&gt; and share what you’ve built, or ask for help.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>webdev</category>
      <category>enhance</category>
      <category>webcomponents</category>
    </item>
    <item>
      <title>Why you should roll your own auth</title>
      <dc:creator>Simon MacDonald</dc:creator>
      <pubDate>Wed, 10 May 2023 18:28:14 +0000</pubDate>
      <link>https://dev.to/begin/why-you-should-roll-your-own-auth-2jf5</link>
      <guid>https://dev.to/begin/why-you-should-roll-your-own-auth-2jf5</guid>
      <description>&lt;p&gt;Conventional wisdom states that rolling your own authentication system can be a significant undertaking that requires a lot of expertise in security and web development. In most cases, using a third-party authentication service is better than trying to build your own from scratch. However, there are some cases where rolling your own auth may be beneficial, such as:&lt;/p&gt;

&lt;h2&gt;
  
  
  Customization
&lt;/h2&gt;

&lt;p&gt;If your authentication needs are unique or require specific customizations, rolling your own auth may be the better option. Third-party authentication services may not be able to meet your specific requirements.&lt;/p&gt;

&lt;p&gt;Leaving your application to show a third-party dialog can be “jarring” for your users. Most third parties offer limited customization options or provide you with an API to make the authentication calls yourself. At this point the effort to build your own system may not be much different than customizing a third party solution.&lt;/p&gt;

&lt;h2&gt;
  
  
  Compliance
&lt;/h2&gt;

&lt;p&gt;If your business operates in a highly regulated industry, you may need to comply with specific security and privacy regulations that third-party authentication services may not meet. In this case, rolling your own auth may be necessary to ensure compliance. Additionally, your application may have region specific regulations as to where the data must be hosted.&lt;/p&gt;

&lt;h2&gt;
  
  
  Control
&lt;/h2&gt;

&lt;p&gt;By rolling your own authentication system, you have complete control over the entire authentication process, from user data storage to encryption. This can be beneficial for businesses that need complete control over their authentication systems.&lt;/p&gt;

&lt;p&gt;Two important points about control is that owning your own auth solution:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Allows you to avoid slow API calls to third party services to retrieve user information. Slow is a relative term, but a sub 10 ms query to your cloud hosted database will beat an API call every day of the week.&lt;/li&gt;
&lt;li&gt;Retains ownership of your user database.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Cost
&lt;/h2&gt;

&lt;p&gt;Depending on your authentication needs and the volume of users, rolling your own auth may be more cost-effective than using a third-party authentication service. Third-party authentication services can be expensive, especially for large businesses with many users.&lt;/p&gt;

&lt;p&gt;Overall, rolling your own authentication system is a viable option. Especially when you consider your own authentication requirements, compliance, control and cost issues. However, building your own auth requires specialized security and web development expertise, and the risks of building an insecure authentication system can be significant. Therefore, weighing the pros and cons carefully and seeking expert advice before deciding to roll your own auth is essential.&lt;/p&gt;

&lt;p&gt;In other words,&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--eOwANezE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0rpl9osholfgh39cwu0r.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--eOwANezE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0rpl9osholfgh39cwu0r.jpg" alt="old man giving link a sword in the legend of zelda" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Starting next week, we are embarking on a series of posts to teach you how to safely and securely implement your own authentication solution.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Username and password authentication&lt;/li&gt;
&lt;li&gt;Confirming email addresses and phone numbers&lt;/li&gt;
&lt;li&gt;Magic links and one-time passwords&lt;/li&gt;
&lt;li&gt;OAuth&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Next Steps
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Join &lt;a href="https://enhance.dev/discord"&gt;the Enhance Discord&lt;/a&gt; and share what you’ve built or ask for help.&lt;/li&gt;
&lt;li&gt;Let us know what features you would like to see covered in our Authentication series.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>webdev</category>
      <category>enhance</category>
      <category>auth</category>
    </item>
    <item>
      <title>Supporting Publish Own Site, Syndicate Elsewhere</title>
      <dc:creator>Simon MacDonald</dc:creator>
      <pubDate>Mon, 01 May 2023 13:40:14 +0000</pubDate>
      <link>https://dev.to/begin/supporting-publish-own-site-syndicate-elsewhere-40c4</link>
      <guid>https://dev.to/begin/supporting-publish-own-site-syndicate-elsewhere-40c4</guid>
      <description>&lt;p&gt;Our next step towards making it easier for everyone to participate in the open and indie web is the release of &lt;a href="https://github.com/enhance-dev/arc-plugin-posse"&gt;@enhance/arc-plugin-posse&lt;/a&gt;. This plugin checks your RSS feed for new content and syndicates it to whatever platforms you choose. The plugin is designed to work seamlessly with the &lt;a href="https://begin.com/blog/posts/2023-03-17-introducing-the-enhance-blog-template"&gt;Enhance Blog Template&lt;/a&gt;, but you can also deploy it as a standalone plugin for existing sites.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is "Publish Own Site, Syndicate Elsewhere?"
&lt;/h2&gt;

&lt;p&gt;Publishing on your own site and syndicating elsewhere (POSSE) is a content strategy involving creating content on your website first and then distributing it to other platforms. This approach allows you to take advantage of the traffic and visibility of different platforms while retaining control over your content and driving traffic back to your website.&lt;/p&gt;

&lt;h2&gt;
  
  
  What are the benefits of POSSE?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;Control Over Your Content&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;You have complete control over your website when you publish content on it. You can choose the tone, the format, and the length of your content. You can also include links to other content on your website, which can help to drive traffic. When you syndicate your content elsewhere, you can retain this control by creating a direct ownership chain that can be traced back to you without any intervening 3rd party’s Terms of Services getting in the way.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;Increased Visibility&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Syndicating your content on other platforms can increase your visibility and reach a larger audience. Publishing content on high-traffic websites relevant to your niche exposes it to a new audience that may not have found it otherwise. Syndication can increase website traffic and attract new customers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;Cost-Effective&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Publishing content on your own website is typically very cost-effective. All you need is a website and some basic content-creation tools. The hardest thing is creating and posting content on a regular basis. To start, consider using the &lt;a href="https://begin.com/blog/posts/2023-03-17-introducing-the-enhance-blog-template"&gt;Enhance Blog Template&lt;/a&gt; and deploying to Begin for free. Worried about the ole bait and switch on costs? Checkout our &lt;a href="https://begin.com/pricing"&gt;pricing guarantee&lt;/a&gt;. If you want a custom domain name, you can do that with &lt;a href="https://begin.com/blog/posts/2023-04-03-begin-domains"&gt;Begin Domains&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting Started
&lt;/h2&gt;

&lt;p&gt;To start syndicating your content to other sites, we need to add the plugin to an existing Enhance application. First, install the plugin from npm:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; @enhance/arc-plugin-posse
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then add the plugin to your application by editing your &lt;code&gt;.arc&lt;/code&gt; file and adding a line under the &lt;code&gt;@plugins&lt;/code&gt; pragma:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@plugins
enhance/arc-plugin-posse
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To configure your plugin you’ll add a few more lines to your &lt;code&gt;.arc&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@posse
feed "https://url.to/rss"
since “2023-04-27”
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;feed&lt;/code&gt; option tells the plugin what RSS feed to inspect for new content while the &lt;code&gt;since&lt;/code&gt; property lets the plugin know the date to start syndicating content on.&lt;/p&gt;

&lt;h2&gt;
  
  
  Syndication Targets
&lt;/h2&gt;

&lt;p&gt;The plugin supports syndicating content to &lt;a href="https://dev.to/"&gt;dev.to&lt;/a&gt;, &lt;a href="https://joinmastodon.org/"&gt;Mastodon&lt;/a&gt; and &lt;a href="https://twitter.com/"&gt;Twitter&lt;/a&gt; (while it lasts 😉). Additional syndication targets are planned, but please let us know what you’d like to see next by raising an &lt;a href="https://github.com/enhance-dev/arc-plugin-posse/issues"&gt;issue&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The plugin will only attempt to syndicate to a target if the necessary environment variables are set for each syndication target. For more information see the documentation on &lt;a href="https://github.com/enhance-dev/arc-plugin-posse#readme"&gt;@enhance/arc-plugin-posse&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Mastodon Example
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--PPlbM_bX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/z971tyt3new21qcq647f.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--PPlbM_bX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/z971tyt3new21qcq647f.png" alt="Mastodon Post" width="581" height="396"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Twitter Example
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--4LXErz9t--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/61ck1j14fe2oidx35jn3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--4LXErz9t--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/61ck1j14fe2oidx35jn3.png" alt="Twitter Post" width="598" height="219"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Further Reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Dig into the details of the &lt;a href="https://begin.com/blog/posts/2023-03-17-introducing-the-enhance-blog-template"&gt;Enhance Blog Template&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://begin.com/blog/posts/2023-04-06-customizing-the-enhance-blog-template"&gt;Customize&lt;/a&gt; the style of your blog.&lt;/li&gt;
&lt;li&gt;Grab a &lt;a href="https://begin.com/blog/posts/2023-04-03-begin-domains"&gt;domain name&lt;/a&gt;!&lt;/li&gt;
&lt;li&gt;Set up &lt;a href="https://begin.com/blog/posts/2023-04-19-webmention-support-in-enhance-blog-template"&gt;webmention&lt;/a&gt; support on your blog.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Next Steps
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Join the &lt;a href="https://enhance.dev/discord"&gt;Enhance Discord&lt;/a&gt; and share what you’ve built or ask for help.&lt;/li&gt;
&lt;li&gt;Let us know what features you would like to see next. What syndication targets are important to you?&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>webdev</category>
      <category>enhance</category>
      <category>posse</category>
      <category>indieweb</category>
    </item>
    <item>
      <title>Webmention Support in Enhance Blog Template</title>
      <dc:creator>Simon MacDonald</dc:creator>
      <pubDate>Wed, 19 Apr 2023 14:48:56 +0000</pubDate>
      <link>https://dev.to/begin/webmention-support-in-enhance-blog-template-2mpj</link>
      <guid>https://dev.to/begin/webmention-support-in-enhance-blog-template-2mpj</guid>
      <description>&lt;p&gt;We want to make it easier for folks to participate in the open and &lt;a href="https://indieweb.org/"&gt;indie web&lt;/a&gt;. Our first step towards this goal was the publication of our &lt;a href="https://begin.com/blog/posts/2023-03-17-introducing-the-enhance-blog-template"&gt;Enhance Blog Template&lt;/a&gt; which enables you to publish your site and own your content. Our next step is adding support for incoming and outgoing webmentions to the blog template.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is a Webmention?
&lt;/h2&gt;

&lt;p&gt;A &lt;a href="https://www.w3.org/TR/webmention/"&gt;webmention&lt;/a&gt; is a simple way to notify any URL when you mention it on your site. When you link to another site, you send it a webmention (if it accepts them). Conversely, when a site links to your content, you can provide them with an endpoint where they can send you a mention.&lt;/p&gt;

&lt;p&gt;Let’s walk through an example of two sites supporting webmentions talking to each other.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;I have a website where I write short reviews about books I read. I really do at &lt;a href="https://bookrecs.org/"&gt;bookrecs.org&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;My friend Cole has his website where he also writes a review about a book we’ve both read. In his review, he includes a permalink back to my original review.&lt;/li&gt;
&lt;li&gt;When Cole publishes his article, his site automatically notifies me that his post has linked to my original post.&lt;/li&gt;
&lt;li&gt;My site verifies that Cole’s post contains a link to my book review and stores information about Cole’s post so I can decide whether or not to include it as a mention in my review.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That’s two independent websites talking directly to one another without needing a centralized service.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--EYyBzivN--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lvc05wg14ul06l5kweah.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--EYyBzivN--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lvc05wg14ul06l5kweah.jpg" alt="it's magic" width="600" height="496"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Sometimes the web is just magic.&lt;/p&gt;

&lt;h2&gt;
  
  
  How it works
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Adds microformat support for &lt;a href="https://microformats.org/wiki/h-card"&gt;h-card&lt;/a&gt; and &lt;a href="https://microformats.org/wiki/h-entry"&gt;h-entry&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/webmention&lt;/code&gt; route to accept incoming mentions.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/admin&lt;/code&gt; routes to approve incoming mentions.&lt;/li&gt;
&lt;li&gt;Scheduled function to send outgoing mentions&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Microformats
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://microformats.org/wiki/Main_Page"&gt;microformats&lt;/a&gt; are HTML for marking up people, organizations, events, locations, blog posts, products, reviews, resumes, recipes etc. Sites use microformats to publish a standard API that is consumed and used by search engines, browsers, and other websites&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;When you send a webmention to a remote server, it will verify that your site links to the remote server. One way to make it easier for the mentioned site to display our webmention is to follow the h-card and h-entry microformats. The blog template will automatically wrap your blog post in an h-entry and include an h-card on every page.&lt;/p&gt;

&lt;h3&gt;
  
  
  h-card
&lt;/h3&gt;

&lt;p&gt;You’ll first need to provide the data for your h-card. Open up the file &lt;code&gt;app/api/h-card.json&lt;/code&gt;, then add whatever information you feel comfortable sharing online. At a minimum, we suggest adding your name, photo and URL.&lt;/p&gt;

&lt;p&gt;For example:&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;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"Sally Ride"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"photo"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://upload.wikimedia.org/wikipedia/commons/thumb/0/0c/Sally_Ride_%281984%29.jpg/1024px-Sally_Ride_%281984%29.jpg"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://en.wikipedia.org/wiki/Sally_Ride"&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;blockquote&gt;
&lt;p&gt;If you don’t have a photo hosted on the web we suggest you use your GitHub avatar which follows the format: &lt;code&gt;https://github.com/username.png&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;See this &lt;a href="https://github.com/enhance-dev/enhance-blog-template/blob/main/app/api/h-card.json.example"&gt;example&lt;/a&gt; of all the properties you can add to your h-card.json file.&lt;/p&gt;

&lt;h3&gt;
  
  
  h–entry
&lt;/h3&gt;

&lt;p&gt;When the blog template generates the HTML for your blog post, it applies the CSS classes required for it to be considered a valid h-entry.&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;article&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"h-entry"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;h1&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"p-name"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;h-card Test&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;p&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"dt-published"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;April 6, 2023&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;section&lt;/span&gt; &lt;span class="na"&gt;slot=&lt;/span&gt;&lt;span class="s"&gt;"e-content"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;Testing having a blog post that has a h-card and h-entry.&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/section&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;section&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"p-summary hidden"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Testing h-card and h-entry.&lt;span class="nt"&gt;&amp;lt;/section&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/article&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;A quick explainer on microformat class names:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;p-name&lt;/strong&gt; - entry name/title&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;e-content&lt;/strong&gt; - full content of the entry&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;dt-published&lt;/strong&gt; - when the entry was published&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;p-summary&lt;/strong&gt; - short entry summary&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For more information see &lt;a href="https://microformats.org/wiki/h-entry#Core_Properties"&gt;h-entry core properties&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  &lt;code&gt;/webmention&lt;/code&gt; Route
&lt;/h2&gt;

&lt;p&gt;The blog template will automatically add a &lt;code&gt;link&lt;/code&gt; tag with a &lt;code&gt;rel&lt;/code&gt; of &lt;code&gt;webmention&lt;/code&gt; in the &lt;code&gt;head&lt;/code&gt; of your application that points to your webmention route. This is one of the ways remote services can &lt;a href="https://www.w3.org/TR/webmention/#sender-discovers-receiver-webmention-endpoint"&gt;discover your webmention route&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This endpoint supports POSTing &lt;code&gt;application/x-www-form-urlencoded&lt;/code&gt; content type and verifies that the incoming data has a &lt;code&gt;source&lt;/code&gt; and &lt;code&gt;target&lt;/code&gt; properties. If it does, the incoming webmention is accepted and stored in our database for approval in our admin interface.&lt;/p&gt;

&lt;h2&gt;
  
  
  Admin Interface
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Admin password
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;/admin&lt;/code&gt; route is secured with a password. To configure the password on your blog, you will need to create an environment variable.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--bawW2FeA--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ts0b63tkkjhpwet5ez8r.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--bawW2FeA--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ts0b63tkkjhpwet5ez8r.png" alt="login to admin interface" width="411" height="377"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;begin &lt;span class="nb"&gt;env &lt;/span&gt;create &lt;span class="nt"&gt;--env&lt;/span&gt; staging &lt;span class="nt"&gt;--name&lt;/span&gt; SECRET_PASSWORD &lt;span class="nt"&gt;--value&lt;/span&gt; yoursecretpassword
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After you are logged in, the admin interface is where you can approve or reject webmentions.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--RnLTc2or--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/plomcdh30m6zzkjofi66.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--RnLTc2or--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/plomcdh30m6zzkjofi66.png" alt="list of webmentions" width="800" height="477"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once approved, a webmention will appear below the post it mentions.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--SGzBvP0K--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vusjisxtlz8a50zy0t7p.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--SGzBvP0K--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vusjisxtlz8a50zy0t7p.png" alt="sample webmention" width="771" height="269"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Outgoing Webmentions
&lt;/h2&gt;

&lt;p&gt;By default, the blog template is set up to check for new posts on your site once per day. If it does detect a new post, it will inspect it for external links. Any external links found are checked to see if they accept webmentions. If the mentioned site does, a webmention is sent.&lt;/p&gt;

&lt;p&gt;For high-traffic sites, you can increase this polling interval. In your app’s &lt;code&gt;.arc&lt;/code&gt; file find the &lt;code&gt;@scheduled&lt;/code&gt; section:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@scheduled
check-rss
  rate 1 day
  src jobs/scheduled/check-rss
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Modify the &lt;code&gt;rate&lt;/code&gt; property so the check is made more frequently. For example, this will check your site every hour:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@scheduled
check-rss
  rate 1 hour
  src jobs/scheduled/check-rss
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The function supports any valid &lt;a href="https://docs.aws.amazon.com/AmazonCloudWatch/latest/events/ScheduledEvents.html#RateExpressions"&gt;rate expression&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Further Reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Dig into the details of the &lt;a href="https://begin.com/blog/posts/2023-03-17-introducing-the-enhance-blog-template"&gt;Enhance Blog Template&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://begin.com/blog/posts/2023-04-06-customizing-the-enhance-blog-template"&gt;Customize&lt;/a&gt; the style of your blog.&lt;/li&gt;
&lt;li&gt;Grab a &lt;a href="https://begin.com/blog/posts/2023-04-03-begin-domains"&gt;domain name&lt;/a&gt;!&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Next Steps
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Join the &lt;a href="https://enhance.dev/discord"&gt;Enhance Discord&lt;/a&gt; and share what you’ve built or ask for help.&lt;/li&gt;
&lt;li&gt;Let us know what features you would like to see next. Maybe &lt;a href="https://indieweb.org/POSSE"&gt;POSSE&lt;/a&gt;?&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>webdev</category>
      <category>enhance</category>
      <category>webmentions</category>
      <category>indieweb</category>
    </item>
    <item>
      <title>Making the leap to AWS with Architect</title>
      <dc:creator>Simon MacDonald</dc:creator>
      <pubDate>Fri, 14 Apr 2023 15:01:40 +0000</pubDate>
      <link>https://dev.to/begin/making-the-leap-to-aws-with-architect-1pb3</link>
      <guid>https://dev.to/begin/making-the-leap-to-aws-with-architect-1pb3</guid>
      <description>&lt;p&gt;A guest post by &lt;a href="https://charnalia.dev/" rel="noopener noreferrer"&gt;Ameya Charnalia&lt;/a&gt; on his journey to migrate from Heroku to AWS via Architect.&lt;/p&gt;




&lt;p&gt;After Heroku &lt;a href="https://help.heroku.com/RSBRUH58/removal-of-heroku-free-product-plans-faq" rel="noopener noreferrer"&gt;axed its free-tier in 2022&lt;/a&gt;, I was approached by &lt;a href="https://mastodon.online/@macdonst" rel="noopener noreferrer"&gt;Simon MacDonald&lt;/a&gt;—head of developer experience at &lt;a href="https://begin.com/" rel="noopener noreferrer"&gt;Begin&lt;/a&gt; and my JavaScript mentor—to migrate my Twitter bot, &lt;a href="https://twitter.com/billsbeepboop" rel="noopener noreferrer"&gt;Billbot&lt;/a&gt;, to Amazon Web Services (AWS).&lt;/p&gt;

&lt;p&gt;While I was at it, he also recommended moving to Mastodon since there was talk Twitter would be restricting access to its free API service in the coming weeks.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://twitter.com/TwitterDev/status/1621026986784337922?s=20" rel="noopener noreferrer"&gt;&lt;img src="https://media.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%2Fa9xch4xtpo6stdduzxff.png" alt="twitter API announcement"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Access to the API remains &lt;a href="https://developer.twitter.com/en/products/twitter-api" rel="noopener noreferrer"&gt;free for write-only cases&lt;/a&gt; at the time of publishing.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This innocuous suggestion from Simon made me slightly nervous.&lt;/p&gt;

&lt;p&gt;See, Heroku made it very easy for frontend developers like me to deploy our backend projects. Create a procfile, push your code up, make sure your environment variables are set up properly and, before you know it, you’re off to the races.&lt;/p&gt;

&lt;p&gt;But I was only delaying the inevitable.&lt;/p&gt;

&lt;p&gt;As my projects became more mature and scaled up, I became more involved in writing server-side code. Being a primarily self-taught developer, I knew I had to become familiar with industry-standard cloud solutions such as AWS, but I was spooked by the deployment process. I was warned the process could be cumbersome and the learning curve steep.&lt;/p&gt;

&lt;p&gt;Did I say I was a tad nervous?&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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpucgtxsk61htvespwb5b.gif" 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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpucgtxsk61htvespwb5b.gif" alt="nervous"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;My bot, for example, runs every morning at 7 a.m. and tweets—or toots, depending on your social media platform—government bills scheduled for debate in the &lt;a href="https://www.parl.ca/" rel="noopener noreferrer"&gt;Canadian parliament&lt;/a&gt; for that day. How would I migrate my Node.js code from Heroku to AWS while learning about AWS cloud formations, lambdas and databases? I was worried my application would break and leave its users in the dark, scrambling to find out about the day’s bills. (OK, I admit I’m being a tad dramatic—the bot’s 200-odd followers could go to the House of Commons or Senate website to see the day’s projected business. But. I did want to ensure my application’s reliability)&lt;/p&gt;

&lt;p&gt;That’s where &lt;a href="https://arc.codes/docs/en/get-started/quickstart" rel="noopener noreferrer"&gt;Architect&lt;/a&gt; comes in.&lt;/p&gt;

&lt;p&gt;Made by Begin, Architect is a simple, open-source framework for building and delivering powerful &lt;a href="https://fwa.dev/" rel="noopener noreferrer"&gt;Functional Web Apps&lt;/a&gt; (FWAs) on AWS.&lt;/p&gt;

&lt;p&gt;Off the hop, we decided we’d be using AWS lambda functions triggered through a daily cron job. To achieve this, we made use of Architect’s events-based helper functions. The Architect team has two Node.js runtime helpers: those for general purpose operations such as events and those designed solely for delivering dynamic data via HTTP endpoints.&lt;/p&gt;

&lt;p&gt;We wrote two lambda events functions—one each for Mastodon and Twitter—and one scheduler function that dealt with most of the application’s business logic, which included making calls to parliament’s API, filtering the bills and returning all the necessary data to the handler function. When triggered by the schedule’s handler function, the Architect publish function checks which event subscriptions are associated with each event function and makes the calls to the Twitter and Mastodon APIs, respectively.&lt;/p&gt;

&lt;p&gt;All of this makes sure that Billbot’s tweets and &lt;a href="https://mstdn.ca/@billbot" rel="noopener noreferrer"&gt;toots&lt;/a&gt; are on time, every day.&lt;/p&gt;

&lt;p&gt;In the root of the application sits the &lt;code&gt;app.arc&lt;/code&gt; file. Here you can declare the application namespace, its lambda functions, and any AWS regions and plugins it may use. Once I set up my AWS credentials via the AWS command line interface, I was ready to deploy.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@app
billbot

@scheduled
checkbills cron(0 11 ? * * *)

@events
postbill
tweetbill

@aws
profile default
region us-east-2
architecture arm64
runtime nodejs18.x
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That was pretty much it.&lt;/p&gt;

&lt;p&gt;Architect’s documentation is well-crafted and guides you in deploying your application, regardless of your comfort level with AWS. The developer experience was intuitive, and Architect stripped away much of the AWS complexity, allowing me to get right to learning about, and deploying with, lambda functions.&lt;/p&gt;

&lt;p&gt;What’s more, is that it will to save me nearly $16 a month in Heroku bills.&lt;/p&gt;




&lt;p&gt;If you want to learn more about Architect check out our &lt;a href="https://arc.codes" rel="noopener noreferrer"&gt;documentation&lt;/a&gt; or join us on our &lt;a href="https://discord.gg/y5A2eTsCRX" rel="noopener noreferrer"&gt;Discord &lt;/a&gt; to ask a question.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>architect</category>
    </item>
    <item>
      <title>Adding Comments to the Enhance Blog Template</title>
      <dc:creator>Simon MacDonald</dc:creator>
      <pubDate>Thu, 30 Mar 2023 18:30:11 +0000</pubDate>
      <link>https://dev.to/begin/adding-comments-to-the-enhance-blog-template-fkf</link>
      <guid>https://dev.to/begin/adding-comments-to-the-enhance-blog-template-fkf</guid>
      <description>&lt;p&gt;Recently, we released the &lt;a href="https://begin.com/blog/posts/2023-03-17-introducing-the-enhance-blog-template" rel="noopener noreferrer"&gt;Enhance Blog Template&lt;/a&gt;, and one thing I felt was missing was the ability to have random people on the internet disagree with me. That’s right. We’re adding a comment section.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;Install the &lt;a href="https://begin.com/docs/" rel="noopener noreferrer"&gt;Begin CLI&lt;/a&gt; as we will be using it to generate some code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting Started
&lt;/h2&gt;

&lt;p&gt;In order to super-charge our development, I will use the Begin CLI to scaffold our comments API routes and database access layer. From your terminal run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;begin generate scaffold Comment name:string email:email &lt;span class="se"&gt;\&lt;/span&gt;
comment:string &lt;span class="nb"&gt;date&lt;/span&gt;:string slug:string
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;scaffold&lt;/code&gt; command creates the following database backed API routes for you:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;app/api/comments.mjs
- GET  /comments            # list all comments
- POST /comments            # create new comments
app/api/comments/$id.mjs
- GET  /comments/$id        # read one comment
- POST /comments/$id        # update a comment
app/api/comments/$id/delete.mjs
- POST /comments/$id/delete # delete a comment
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We won’t use all of those API routes in this example, but you should be aware of how easy it is to get started building a CRUD app with the &lt;code&gt;scaffold&lt;/code&gt; command.&lt;/p&gt;

&lt;h2&gt;
  
  
  New UI Components
&lt;/h2&gt;

&lt;p&gt;While the &lt;code&gt;scaffold&lt;/code&gt; command created a bunch of UI components for us they don’t match the design of the blog template so we are going to create some new components. The components created by &lt;code&gt;scaffold&lt;/code&gt; are intended to get you up and running quickly but we expect you will end up removing them from your project and/or replacing them with your own implementations. When you do please share your designs on the &lt;a href="https://enhance.dev/discord" rel="noopener noreferrer"&gt;Enhance Discord&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Create these four new files in your project.&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;// app/elements/field-set.mjs&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;FieldSet&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;state&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;attrs&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;state&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;legend&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;attrs&lt;/span&gt;

 &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="s2"&gt;`
   &amp;lt;style&amp;gt;
     fieldset {
       border: 3px solid hsl(0deg 0% 35% / 15%);
     }

     @media (prefers-color-scheme: dark) {
       fieldset {
         border-color: hsla(0deg 0% 85% / 30%);
       }
     }
   &amp;lt;/style&amp;gt;

   &amp;lt;fieldset class='mt0 mb0 p0 font-body'&amp;gt;
     &amp;lt;legend class='font-heading p-2 text3'&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;legend&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;/legend&amp;gt;
     &amp;lt;slot&amp;gt;&amp;lt;/slot&amp;gt;
   &amp;lt;/fieldset&amp;gt;
 `&lt;/span&gt;
&lt;span class="p"&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="c1"&gt;// app/elements/submit-button.mjs&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;SubmitButton&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="p"&gt;{&lt;/span&gt;
 &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="s2"&gt;`
   &amp;lt;style&amp;gt;
     button {
       background-color: var(--color-dark);
       color: var(--color-light);
       -webkit-font-smoothing: antialiased;
       -moz-osx-font-smoothing: grayscale;
     }

     @media (prefers-color-scheme: dark) {
       button {
         background-color: var(--color-light);
         color: var(--color-dark);
       -webkit-font-smoothing: unset;
       -moz-osx-font-smoothing: unset;
       }
     }
   &amp;lt;/style&amp;gt;
   &amp;lt;button type='submit' class='pt-3 pb-3 pl0 pr0 font-semibold'&amp;gt;
     Submit
   &amp;lt;/button&amp;gt;
 `&lt;/span&gt;
&lt;span class="p"&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="c1"&gt;// app/elements/text-area.mjs&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;TextArea&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;state&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;attrs&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;state&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;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;name&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="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;attrs&lt;/span&gt;

 &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="s2"&gt;`
   &amp;lt;style&amp;gt;
     textarea {
       background-color: transparent;
       box-shadow: 0 0 0 1px hsl(0deg 0% 30% / 50%);
       resize: vertical;
       min-height: 6rem;
     }

     textarea:focus {
       outline: none;
       box-shadow: 0 0 0 2px var(--color-dark);
     }

     @media (prefers-color-scheme: dark) {
       textarea {
         box-shadow: 0 0 0 1px hsla(0deg 0% 80% / 60%);
       }
       textarea:focus {
         box-shadow: 0 0 0 2px var(--color-light);
       }
     }
   &amp;lt;/style&amp;gt;
   &amp;lt;label&amp;gt;
     &amp;lt;span class='block text-1 mb-3 font-semibold'&amp;gt;
       &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;
     &amp;lt;/span&amp;gt;
     &amp;lt;textarea id='&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;' name='&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;' type='&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="s2"&gt;' class='mb0 p-2 w-full leading3'&amp;gt;&amp;lt;/textarea&amp;gt;
   &amp;lt;/label&amp;gt;
 `&lt;/span&gt;
&lt;span class="p"&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="c1"&gt;// app/elements/text-input.mjs&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;TextInput&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;state&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;attrs&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;state&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;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;name&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="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;attrs&lt;/span&gt;

 &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="s2"&gt;`
   &amp;lt;style&amp;gt;
     input {
       background-color: transparent;
       box-shadow: 0 0 0 1px hsl(0deg 0% 30% / 50%);
     }

     input:focus {
       outline: none;
       box-shadow: 0 0 0 2px var(--color-dark);
     }

     @media (prefers-color-scheme: dark) {
       input {
         box-shadow: 0 0 0 1px hsla(0deg 0% 80% / 60%);
       }
       input:focus {
         box-shadow: 0 0 0 2px var(--color-light);
       }
     }
   &amp;lt;/style&amp;gt;
   &amp;lt;label&amp;gt;
     &amp;lt;span class='block text-1 mb-3 font-semibold'&amp;gt;
       &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;
     &amp;lt;/span&amp;gt;
     &amp;lt;input id='&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;' name='&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;' type='&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="s2"&gt;' class='mb0 p-2 w-full' /&amp;gt;
   &amp;lt;/label&amp;gt;
 `&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Great, now we’ve got some components that look good in our blog template in light or dark mode.&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating a Comment
&lt;/h2&gt;

&lt;p&gt;Now that we have some API routes to store our comments in a database, let’s update our post page to create comments. Open up &lt;code&gt;app/pages/posts/$$.mjs&lt;/code&gt; as we are going to add a few lines.&lt;/p&gt;

&lt;p&gt;First, we’ll need to pull more data from the store in order to populate the comment section.&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="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;comment&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="nx"&gt;problems&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{},&lt;/span&gt; &lt;span class="nx"&gt;slug&lt;/span&gt; &lt;span class="o"&gt;=&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="nx"&gt;store&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Wait, hold up, why are we pulling the comment data from the store if this is a new comment? Well, it’s simple. If the user submits a new comment and there is a problem either on the server or with validation, we’ll need to repopulate the comment form to avoid them having to re-type everything. We’re not monsters.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Remember always validate your inputs on the client and on the server.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;As well we’ll add a section underneath our blog post that will provide a form for the user to comment on our post.&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;enhance-form&lt;/span&gt;
    &lt;span class="na"&gt;action=&lt;/span&gt;&lt;span class="s"&gt;"/comments/${comment.key}"&lt;/span&gt;
    &lt;span class="na"&gt;method=&lt;/span&gt;&lt;span class="s"&gt;"POST"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"${problems.form ? 'block' : 'hidden'}"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;Found some problems!&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;ul&amp;gt;&lt;/span&gt;${problems.form}&lt;span class="nt"&gt;&amp;lt;/ul&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;field-set&lt;/span&gt; &lt;span class="na"&gt;legend=&lt;/span&gt;&lt;span class="s"&gt;"Comment"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;text-input&lt;/span&gt; &lt;span class="na"&gt;label=&lt;/span&gt;&lt;span class="s"&gt;"Name"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"text"&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"name"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"name"&lt;/span&gt;
     &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"${comment?.name}"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/text-input&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;text-input&lt;/span&gt; &lt;span class="na"&gt;label=&lt;/span&gt;&lt;span class="s"&gt;"Email"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt;
     &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"${comment?.email}"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/text-input&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;text-area&lt;/span&gt; &lt;span class="na"&gt;label=&lt;/span&gt;&lt;span class="s"&gt;"Comment"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"text"&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"comment"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"comment"&lt;/span&gt;
     &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"${comment?.comment}"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/text-area&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;id=&lt;/span&gt;&lt;span class="s"&gt;"slug"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"slug"&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"${slug}"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;submit-button&lt;/span&gt; &lt;span class="na"&gt;style=&lt;/span&gt;&lt;span class="s"&gt;"float: right"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/submit-button&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/field-set&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/enhance-form&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Full source 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="c1"&gt;// app/pages/posts/$$.mjs&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;function &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;state&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;store&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;state&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;comment&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="nx"&gt;problems&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{},&lt;/span&gt; &lt;span class="nx"&gt;slug&lt;/span&gt; &lt;span class="o"&gt;=&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="nx"&gt;store&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;frontmatter&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;post&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;published&lt;/span&gt; &lt;span class="o"&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;title&lt;/span&gt; &lt;span class="o"&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;rating&lt;/span&gt; &lt;span class="o"&gt;=&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="nx"&gt;frontmatter&lt;/span&gt;

 &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="s2"&gt;`
     &amp;lt;site-layout&amp;gt;
       &amp;lt;article class="font-body leading4 m-auto p0 p5-lg"&amp;gt;
         &amp;lt;h1 class="font-heading font-bold mb0 mb4-lg text3 text5-lg tracking-1 leading1 text-center"&amp;gt;&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="s2"&gt;&amp;lt;/h1&amp;gt;
         &amp;lt;p class='text-center mb0 mb4-lg'&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;published&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;/p&amp;gt;
         &amp;lt;section slot="doc"&amp;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="nx"&gt;html&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;/section&amp;gt;
         &amp;lt;p class='mb0 mb4-lg'&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;rating&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; out of 5 stars&amp;lt;/p&amp;gt;
       &amp;lt;/article&amp;gt;
       &amp;lt;enhance-form
         action="/comments/&lt;/span&gt;&lt;span class="p"&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;key&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"
         method="POST"&amp;gt;
         &amp;lt;div class="&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;problems&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;form&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;block&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;hidden&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&amp;gt;
           &amp;lt;p&amp;gt;Found some problems!&amp;lt;/p&amp;gt;
           &amp;lt;ul&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;problems&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;form&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;/ul&amp;gt;
         &amp;lt;/div&amp;gt;
         &amp;lt;field-set legend="Comment"&amp;gt;
           &amp;lt;text-input label="Name" type="text" id="name" name="name" value="&lt;/span&gt;&lt;span class="p"&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;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;" errors="&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;problems&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&amp;gt;&amp;lt;/text-input&amp;gt;
           &amp;lt;text-input label="Email" type="email" id="email" name="email" value="&lt;/span&gt;&lt;span class="p"&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;email&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;" errors="&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;problems&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;errors&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&amp;gt;&amp;lt;/text-input&amp;gt;
           &amp;lt;text-area label="Comment" type="text" id="comment" name="comment" value="&lt;/span&gt;&lt;span class="p"&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;comment&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;" errors="&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;problems&lt;/span&gt;&lt;span class="p"&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;errors&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&amp;gt;&amp;lt;/text-area&amp;gt;
           &amp;lt;input type="hidden" id="slug" name="slug" value="&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="s2"&gt;" /&amp;gt;
           &amp;lt;submit-button style="float: right"&amp;gt;&amp;lt;/submit-button&amp;gt;
         &amp;lt;/field-set&amp;gt;
       &amp;lt;/enhance-form&amp;gt;
     &amp;lt;/site-layout&amp;gt;
 `&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What’s a slug?
&lt;/h2&gt;

&lt;p&gt;Keen readers may have noticed a hidden field in our comment form called &lt;code&gt;slug&lt;/code&gt;. In web parlance, a slug is the last part of the URL address that serves as a unique identifier of the page. We need to save the post URL as part of the comment for later when we want to display comments under our post.&lt;/p&gt;

&lt;p&gt;In order for &lt;code&gt;app/pages/posts/$$.mjs&lt;/code&gt; to have access to the &lt;code&gt;slug&lt;/code&gt; we will need to populate it in our store by adding it to the return statement in &lt;code&gt;app/api/posts/$$.mjs&lt;/code&gt;. Luckily, the request object passed to our &lt;code&gt;GET&lt;/code&gt; handler makes it easy to provide a &lt;code&gt;slug&lt;/code&gt; via &lt;code&gt;req.path&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;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="na"&gt;json&lt;/span&gt;&lt;span class="p"&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="na"&gt;slug&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;path&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;
  
  
  Redirecting &lt;code&gt;POST /comments&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;When scaffold runs it creates a &lt;code&gt;POST /comments&lt;/code&gt; API route for you in &lt;code&gt;app/api/comments.mjs&lt;/code&gt;. When the POST route runs to completion it will redirect the user back to the &lt;code&gt;/comments&lt;/code&gt; UI route but we want to go back to the blog post we were reading instead. This will require a few minor changes to the POST route.&lt;/p&gt;

&lt;p&gt;On the line under the post function declaration let’s grab the value of the &lt;code&gt;slug&lt;/code&gt; the user submitted.&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;post&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="p"&gt;{&lt;/span&gt;
 &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;slug&lt;/span&gt; &lt;span class="o"&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;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;slug&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then anywhere you see &lt;code&gt;location: ‘/comments’&lt;/code&gt; replace it with &lt;code&gt;location: slug&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Great, now our comment form’s hidden slug input value is properly populated and our API is re-directing you back to the page you were reading. Go ahead and submit a new comment on one of your blog posts, and it will be stored in the database.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reading Comments from the Database
&lt;/h2&gt;

&lt;p&gt;The next challenge is reading all of the comments from the database. Once again, &lt;code&gt;scaffold&lt;/code&gt; comes to the rescue: It has already written the database access layer for you. In &lt;code&gt;app/models/comments.mjs&lt;/code&gt; you will find all the methods you need in order to create, read, update, delete or list comments stored in the database. In our case, we’ll only need to list the comments.&lt;/p&gt;

&lt;p&gt;Open up &lt;code&gt;app/api/posts/$$.mjs&lt;/code&gt; and import the &lt;code&gt;getComments&lt;/code&gt; function to get a list of all the comments in the database.&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;getComments&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;../../models/comments.mjs&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then before the return in that file, we’ll query the database for all the comments and filter them based on the current page that is being viewed.&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;comments&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getComments&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;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;slug&lt;/span&gt; &lt;span class="o"&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;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And finally, update the return statement at the end of the file to include our comments array.&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;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;json&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="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;slug&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;path&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;Full source 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="c1"&gt;// app/api/posts/$$.mjs&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;readFileSync&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;fs&lt;/span&gt;&lt;span class="dl"&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;URL&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;url&lt;/span&gt;&lt;span class="dl"&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;Arcdown&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;arcdown&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;HljsLineWrapper&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;../../lib/hljs-line-wrapper.mjs&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;defaultClassMapping&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;../../lib/markdown-class-mappings.mjs&lt;/span&gt;&lt;span class="dl"&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;getComments&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;../../models/comments.mjs&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="cm"&gt;/** @type {import('@enhance/types').EnhanceApiFn} */&lt;/span&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="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="c1"&gt;// reinvoked each req so no weird regexp caching&lt;/span&gt;
 &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;arcdown&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;Arcdown&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
   &lt;span class="na"&gt;pluginOverrides&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
     &lt;span class="na"&gt;markdownItToc&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       &lt;span class="na"&gt;containerClass&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;toc mb2 ml-2&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="na"&gt;listType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ul&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;markdownItClass&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;defaultClassMapping&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="p"&gt;},&lt;/span&gt;
   &lt;span class="na"&gt;hljs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
     &lt;span class="na"&gt;sublanguages&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;javascript&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;xml&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;css&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;plugins&lt;/span&gt;&lt;span class="p"&gt;:&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;HljsLineWrapper&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;code-line&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="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;activePath&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;
 &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;docPath&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;activePath&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/^&lt;/span&gt;&lt;span class="se"&gt;\/?&lt;/span&gt;&lt;span class="sr"&gt;blog&lt;/span&gt;&lt;span class="se"&gt;\/&lt;/span&gt;&lt;span class="sr"&gt;/&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="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;index&lt;/span&gt;&lt;span class="dl"&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;docPath&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;endsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&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="nx"&gt;docPath&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;index&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="c1"&gt;// trailing slash == index.md file&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;docURL&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;URL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`../../blog/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;docPath&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.md`&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;meta&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="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;docMarkdown&lt;/span&gt;
 &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="nx"&gt;docMarkdown&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;readFileSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;docURL&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pathname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;utf-8&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;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="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;_err&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;404&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;post&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;arcdown&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;docMarkdown&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;comments&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getComments&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;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;slug&lt;/span&gt; &lt;span class="o"&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;path&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;json&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="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
     &lt;span class="na"&gt;slug&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;path&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;h2&gt;
  
  
  Pulling it all together
&lt;/h2&gt;

&lt;p&gt;Now that the store is populated with all the values we need to display comments we can update our &lt;code&gt;app/pages/posts/$$.mjs&lt;/code&gt; file one more time.&lt;/p&gt;

&lt;p&gt;First, we’ll need to add the comments to the data we pull from the store.&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="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;comments&lt;/span&gt; &lt;span class="o"&gt;=&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;=&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;problems&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{},&lt;/span&gt; &lt;span class="nx"&gt;slug&lt;/span&gt; &lt;span class="o"&gt;=&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="nx"&gt;store&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then under the &lt;code&gt;enhance-from&lt;/code&gt; tag we’ll add a new section which lists all the comments the blog post has received so far.&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;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;h2&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;font-heading mt4 pl0 pr0 pl5-lg pr5-lg text1 text3-lg tracking-1 leading1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nx"&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="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="nx"&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="nx"&gt;length&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;comment&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;comments&lt;/span&gt;&lt;span class="dl"&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;/h2&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="nx"&gt;dl&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;font-body leading4 m-auto p0 p5-lg&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nx"&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="nf"&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="s2"&gt;`&amp;lt;dt class="mb-6"&amp;gt;
        &amp;lt;span class='font-semibold'&amp;gt;&lt;/span&gt;&lt;span class="p"&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;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;/span&amp;gt;
        &amp;lt;span class='text-1'&amp;gt;on &lt;/span&gt;&lt;span class="p"&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;date&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;/span&amp;gt;
       &amp;lt;/dt&amp;gt;
    &amp;lt;dd class="mb4"&amp;gt;&lt;/span&gt;&lt;span class="p"&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;comment&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;/dd&amp;gt;`&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;join&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="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/dl&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;Full source 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="c1"&gt;// app/pages/posts/$$.mjs&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;function &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;state&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;store&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;state&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;comments&lt;/span&gt; &lt;span class="o"&gt;=&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;=&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;problems&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{},&lt;/span&gt; &lt;span class="nx"&gt;slug&lt;/span&gt; &lt;span class="o"&gt;=&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="nx"&gt;store&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;frontmatter&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;post&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;published&lt;/span&gt; &lt;span class="o"&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;title&lt;/span&gt; &lt;span class="o"&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;rating&lt;/span&gt; &lt;span class="o"&gt;=&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="nx"&gt;frontmatter&lt;/span&gt;

 &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="s2"&gt;`
     &amp;lt;site-layout&amp;gt;
       &amp;lt;article class="font-body leading4 m-auto p0 p5-lg"&amp;gt;
         &amp;lt;h1 class="font-heading font-bold mb0 mb4-lg text3 text5-lg tracking-1 leading1 text-center"&amp;gt;&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="s2"&gt;&amp;lt;/h1&amp;gt;
         &amp;lt;p class='text-center mb0 mb4-lg'&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;published&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;/p&amp;gt;
         &amp;lt;section slot="doc"&amp;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="nx"&gt;html&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;/section&amp;gt;
         &amp;lt;p class='mb0 mb4-lg'&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;rating&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; out of 5 stars&amp;lt;/p&amp;gt;
       &amp;lt;/article&amp;gt;
       &amp;lt;enhance-form
         action="/comments/&lt;/span&gt;&lt;span class="p"&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;key&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"
         method="POST"&amp;gt;
         &amp;lt;div class="&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;problems&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;form&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;block&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;hidden&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&amp;gt;
           &amp;lt;p&amp;gt;Found some problems!&amp;lt;/p&amp;gt;
           &amp;lt;ul&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;problems&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;form&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;/ul&amp;gt;
         &amp;lt;/div&amp;gt;
         &amp;lt;field-set legend="Comment"&amp;gt;
           &amp;lt;text-input label="Name" type="text" id="name" name="name" value="&lt;/span&gt;&lt;span class="p"&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;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;" errors="&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;problems&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&amp;gt;&amp;lt;/text-input&amp;gt;
           &amp;lt;text-input label="Email" type="email" id="email" name="email" value="&lt;/span&gt;&lt;span class="p"&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;email&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;" errors="&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;problems&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;errors&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&amp;gt;&amp;lt;/text-input&amp;gt;
           &amp;lt;text-area label="Comment" type="text" id="comment" name="comment" value="&lt;/span&gt;&lt;span class="p"&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;comment&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;" errors="&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;problems&lt;/span&gt;&lt;span class="p"&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;errors&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&amp;gt;&amp;lt;/text-area&amp;gt;
           &amp;lt;input type="hidden" id="slug" name="slug" value="&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="s2"&gt;" /&amp;gt;
           &amp;lt;input type="hidden" id="key" name="key" value="&lt;/span&gt;&lt;span class="p"&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;key&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;" /&amp;gt;
           &amp;lt;submit-button style="float: right"&amp;gt;&amp;lt;/submit-button&amp;gt;
         &amp;lt;/field-set&amp;gt;
       &amp;lt;/enhance-form&amp;gt;
       &amp;lt;h2 class="font-heading mt4 pl0 pr0 pl5-lg pr5-lg text1 text3-lg tracking-1 leading1"&amp;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="nx"&gt;length&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;comments&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;comment&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;comments&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:&amp;lt;/h2&amp;gt;
       &amp;lt;dl class="font-body leading4 m-auto p0 p5-lg"&amp;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="nf"&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="s2"&gt;`&amp;lt;dt class="mb-6"&amp;gt;&amp;lt;span class='font-semibold'&amp;gt;&lt;/span&gt;&lt;span class="p"&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;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;/span&amp;gt; &amp;lt;span class='text-1'&amp;gt;on &lt;/span&gt;&lt;span class="p"&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;date&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;/span&amp;gt;&amp;lt;/dt&amp;gt;
           &amp;lt;dd class="mb4"&amp;gt;&lt;/span&gt;&lt;span class="p"&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;comment&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;/dd&amp;gt;`&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;join&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="s2"&gt;
       &amp;lt;/dl&amp;gt;
     &amp;lt;/site-layout&amp;gt;
 `&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Demo
&lt;/h2&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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff0x1d134ann74om51z0q.gif" 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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff0x1d134ann74om51z0q.gif" alt="comments demo"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping Up
&lt;/h2&gt;

&lt;p&gt;Adding commenting functionality to the blog template is not too difficult. However, it does avoid topics like authenticating the submitter and approving comments but this post was getting pretty long so we’ll defer those topics to another post.&lt;/p&gt;

&lt;p&gt;If you want to learn more about the scaffold command, I suggest reading our &lt;a href="https://begin.com/blog/posts/2023-03-08-enhance-api-routes-and-openapi" rel="noopener noreferrer"&gt;earlier blog post&lt;/a&gt; on the topic. Have you tried out deploying a blog template yet? Let us know what you think on &lt;a href="https://enhance.dev/discord" rel="noopener noreferrer"&gt;Discord&lt;/a&gt; or &lt;a href="https://fosstodon.org/@enhance_dev" rel="noopener noreferrer"&gt;Mastodon&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>enhance</category>
      <category>webcomponents</category>
    </item>
    <item>
      <title>Introducing the Enhance Blog Template</title>
      <dc:creator>Simon MacDonald</dc:creator>
      <pubDate>Fri, 17 Mar 2023 21:57:30 +0000</pubDate>
      <link>https://dev.to/begin/introducing-the-enhance-blog-template-4bc2</link>
      <guid>https://dev.to/begin/introducing-the-enhance-blog-template-4bc2</guid>
      <description>&lt;p&gt;One thing we’ve heard from users is that they want more options for getting started using Enhance. Today we are excited to announce our first Enhance application template - &lt;a href="https://github.com/enhance-dev/enhance-blog-template"&gt;Blog&lt;/a&gt;. Give the new blog template a test drive then jump onto the &lt;a href="https://enhance.dev/discord"&gt;Enhance Discord&lt;/a&gt; to tell us how it went, and let us know what other templates you’d like to see in the future.&lt;/p&gt;

&lt;h2&gt;
  
  
  Features
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Author posts in markdown&lt;/li&gt;
&lt;li&gt;Syntax highlighting of code blocks&lt;/li&gt;
&lt;li&gt;Light and dark themes based on the operating system setting&lt;/li&gt;
&lt;li&gt;Automatic RSS feed generation&lt;/li&gt;
&lt;li&gt;CI/CD via GitHub Actions&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Project Structure
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;app
├── api ............... data routes
│   ├── posts
│   │   └── $$.mjs .... load data for individual blog post
│   └── index.mjs ..... list of blog posts
├── blog
│   └── posts ......... post files in markdown format
│       └── *.md
├── elements .......... single file components
│   └── *.mjs
├── lib
│   ├── hljs-line-wrapper.mjs
│   └── markdown-class-mappings.mjs
├── pages ............. file-based routing
│   ├── posts
│   │   └── $$.mjs .... individual blog post
│   └── index.mjs ..... list of blog posts
└── head.mjs .......... head tag, used on all pages
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;Install the &lt;a href="https://begin.com/docs/"&gt;Begin CLI &lt;/a&gt;to simplify the deployment of your new blog.&lt;/p&gt;

&lt;h2&gt;
  
  
  Local Development
&lt;/h2&gt;

&lt;p&gt;Clone the blog template repository.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone git@github.com:enhance-dev/enhance-blog-template.git
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Install dependencies&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;enhance-blog-template
npm &lt;span class="nb"&gt;install&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Start the development server.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm start
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open a browser tab to &lt;a href="http://localhost:3333"&gt;http://localhost:3333&lt;/a&gt; and start editing your blog. The local development server will hot-reload your site as you make changes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Blog Posts
&lt;/h2&gt;

&lt;p&gt;You’ll probably want to clear out all the files in &lt;code&gt;app/blog/posts&lt;/code&gt; since you didn’t write them. Any file with the extension .md will automatically be added to your blog. The filename should follow the format &lt;code&gt;YYYY-MM-DD-title.md&lt;/code&gt; (e.g. &lt;code&gt;2023-03-07-new-post.md&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;In order for the file to be correctly processed it needs to include some frontmatter. You are not limited to these three properties. Add additional properties and make use of them in your blog.&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="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;The title&lt;/span&gt;
&lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;A description of your post&lt;/span&gt;
&lt;span class="na"&gt;published&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;The date published in the format "Month Day, Year" (e.g. March 7, 2023)&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The rest of your post can be any valid markdown, including fenced code blocks. For more info on adding languages to the syntax highlighting, check out the documentation for &lt;a href="https://github.com/architect/arcdown"&gt;Arcdown&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Customization
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Styling
&lt;/h3&gt;

&lt;p&gt;Styles for this template are applied in the following places:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;styleguide.json&lt;/code&gt;: Dark and light colors are defined here, as are the font stacks to be used on headings and body text. System font stacks are used by default; you can find some great alternatives at &lt;a href="https://modernfontstacks.com/"&gt;Modern Font Stacks&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;public/css/global.css&lt;/code&gt;: This stylesheet applies some basic styles at the global level.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;public/css/a11y-dark.min.css&lt;/code&gt;: This stylesheet applies syntax highlighting to code blocks. Feel free to swap this out with another highlight.js theme of your choosing (and update the link to the stylesheet in &lt;code&gt;head.mjs&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;app/lib/markdown-class-mappings.mjs&lt;/code&gt;: This file exports an object of HTML element names matched to arrays of classes from Enhance’s utility class system. When your markdown files are converted to HTML, these classes will be attached to the respective HTML elements.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;&amp;lt;style&amp;gt;&lt;/code&gt; blocks in the Single File Components (SFCs), which live in the app/elements directory. Styles written in these style blocks will be scoped to the custom elements they’re defined in.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This template supports dark mode out of the box — try switching between light and dark mode in your operating system settings to see this in action!&lt;/p&gt;

&lt;h3&gt;
  
  
  Site Title
&lt;/h3&gt;

&lt;p&gt;There are a few places in the template where you will want to update your site’s title:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;In &lt;code&gt;app/head.mjs&lt;/code&gt; you can set the &lt;code&gt;&amp;lt;title/&amp;gt;&lt;/code&gt; tag.&lt;/li&gt;
&lt;li&gt;In &lt;code&gt;app/elements/site-header.mjs&lt;/code&gt; you can set the blog title and subtitle that shows up on every page.&lt;/li&gt;
&lt;li&gt;Finally, in &lt;code&gt;src/plugins/create-rss-feed.js&lt;/code&gt; you can update the title and description that is included in your RSS feed.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Deploying Your Blog
&lt;/h2&gt;

&lt;p&gt;Try out your blog by deploying it to the Begin free tier.&lt;/p&gt;

&lt;p&gt;Login to Begin&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;begin login
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create your application and staging environment by following the interactive prompts:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;begin create
This project doesn&lt;span class="s1"&gt;'t appear to be associated with a Begin app
? Would you like to create a Begin app based on this project? (Y/n) · true
? What would you like to name your app? · blog-template
? What would you like to name your first environment? · staging
Archiving and uploading project to Begin...
Project uploaded, you can now exit this process and check its status
with: begin deploy --status
Beginning deployment of '&lt;/span&gt;staging&lt;span class="s1"&gt;'
Packaging build for deployment
Publishing build to Begin
Build completed!
Deployed '&lt;/span&gt;staging&lt;span class="s1"&gt;' to: https://blog-template.begin.app
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Try our your new blog by visiting the URL &lt;code&gt;begin create&lt;/code&gt; provides for you.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Optional Step&lt;/strong&gt;: If you plan on using the default GitHub Action for CI/CD you should create a production environment.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;begin create &lt;span class="nt"&gt;--env&lt;/span&gt; production
App environment &lt;span class="s1"&gt;'production'&lt;/span&gt; created at https://blog-template-prod.begin.app
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Configuring CI/CD
&lt;/h2&gt;

&lt;p&gt;This repo comes with a GitHub action that will deploy your site to the &lt;code&gt;staging&lt;/code&gt; environment when there is a commit to the &lt;code&gt;main&lt;/code&gt; branch and &lt;code&gt;production&lt;/code&gt; when you tag a release.&lt;/p&gt;

&lt;p&gt;For this to work you must &lt;a href="https://docs.github.com/en/actions/security-guides/encrypted-secrets#creating-encrypted-secrets-for-a-repository"&gt;create a repo secret&lt;/a&gt; named &lt;code&gt;BEGIN_TOKEN&lt;/code&gt;. Once you successfully login to Begin using the CLI command &lt;code&gt;begin login&lt;/code&gt; you can retrieve the value for &lt;code&gt;BEGIN_TOKEN&lt;/code&gt; in the file &lt;code&gt;~/.begin/config.json&lt;/code&gt;. Use the value of &lt;code&gt;access_token&lt;/code&gt; in this file as the value for &lt;code&gt;BEGIN_TOKEN&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Next Steps
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Join the &lt;a href="https://enhance.dev/discord"&gt;Enhance Discord&lt;/a&gt; and share what you’ve built or ask for help.&lt;/li&gt;
&lt;li&gt;Let us know what templates you’d like to see next!&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>webdev</category>
      <category>enhance</category>
      <category>webcomponents</category>
    </item>
  </channel>
</rss>
