<?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: Stephen Belovarich</title>
    <description>The latest articles on DEV Community by Stephen Belovarich (@steveblue).</description>
    <link>https://dev.to/steveblue</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%2F69169%2Fbb0f2c9b-29dc-4faf-8470-cd85b4b3a713.jpeg</url>
      <title>DEV Community: Stephen Belovarich</title>
      <link>https://dev.to/steveblue</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/steveblue"/>
    <language>en</language>
    <item>
      <title>Server Side Rendering a Blog with Web Components</title>
      <dc:creator>Stephen Belovarich</dc:creator>
      <pubDate>Wed, 19 Apr 2023 04:31:22 +0000</pubDate>
      <link>https://dev.to/steveblue/server-side-rendering-a-blog-with-web-components-3ije</link>
      <guid>https://dev.to/steveblue/server-side-rendering-a-blog-with-web-components-3ije</guid>
      <description>&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/VEohjUkyFGU"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;This blog post supports a Youtube Livestream scheduled for Wednesday 4/19 at 12pm EST / 9am PST. You can watch the livestream &lt;a href="https://youtube.com/live/VEohjUkyFGU?feature=share" rel="noopener noreferrer"&gt;here on Youtube&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;It has never been easier to server side render a website. Years ago it took server side technologies like PHP and Java, requiring web developers to learn another language besides JavaScript. With the introduction of Node.js, web developers got a JavaScript runtime on the server and tools such as Express that could handle HTTP requests and return HTML to the client. Meta-frameworks for server side rendering sprang up that supported popular libraries and frameworks like React, Angular, Vue, or Svelte.&lt;/p&gt;

&lt;p&gt;Meta-frameworks had to consider React a first-class citizen because of how React utilizes Virtual DOM, an abstraction around DOM that enables the library to "diff" changes and update the view. React and Virtual DOM promised efficiency, but what we got was a toolchain that made the learning curve for web development more difficult. The ecosystem surrounding JavaScript libraries promised a boost in developer experience. What we got was dependency hell.&lt;/p&gt;

&lt;p&gt;Hydration became part of the vocabulary of several web developers in recent years. Hydration being the process of using client-side JavaScript to add state and interactivity to server-rendered HTML. React popularized many of the concepts web developers think of when we say "server-side rendering" today.&lt;/p&gt;

&lt;p&gt;What if I told you there was a way to server-side render HTML with less tooling, using a syntax that you don't need to learn another library to code? The solution just uses HTML, CSS, and JavaScript, with a bit of library code on the server. Would that be enticing?&lt;/p&gt;

&lt;p&gt;Shameless plug. I'm Stephen Belovarich, the author of FullStack Web Components, a book about coding UI libraries with custom elements. If you like what you are reading in thispost consider purchasing a copy of my book &lt;a href="https://www.newline.co/courses/fullstack-web-components?utm_source=dev.to"&gt;Fullstack Web Components&lt;/a&gt; available at newline.co. Details following this post.&lt;/p&gt;

&lt;p&gt;In this guide, I'll demonstrate how to server-side render autonomous custom elements using only browser specifications and Express middleware. In the middleware, I'll show you how to work with an open source package developed by Google to server-side render Declarative Shadow DOM templates. &lt;a href="https://lit.dev/docs/ssr/overview/" rel="noopener noreferrer"&gt;@lit-labs/ssr&lt;/a&gt; is a library package under active development by the team that maintains Lit, a popular library for developing custom elements. @lit-labs/ssr is part of the &lt;a href="https://lit.dev/docs/libraries/labs/" rel="noopener noreferrer"&gt;Lit Labs&lt;/a&gt; family of experimental packages. Even though the package is in "experimental" status, the core offering is quite stable for use with "vanilla" custom elements.&lt;/p&gt;

&lt;p&gt;You can render custom elements today server-side with @lit-labs/ssr by binding the &lt;code&gt;render&lt;/code&gt; function exported by the package to Express middleware. @lit-labs/ssr supports rendering custom elements that extend from LitElement first and foremost, although &lt;code&gt;LitElement&lt;/code&gt; itself is based off web standards, the class is extended from &lt;code&gt;HTMLElement&lt;/code&gt;. @lit-labs/ssr relies on a lit-html template to render content server-side, but lit-html happens to be compatible with another web standard named Declarative Shadow DOM.&lt;/p&gt;

&lt;h2&gt;
  
  
  Declarative Shadow DOM
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://developer.chrome.com/en/articles/declarative-shadow-dom/" rel="noopener noreferrer"&gt;Declarative Shadow DOM&lt;/a&gt; is the web specification what makes server-side rendering custom elements possible. Prior to this standard, the only way to develop custom elements was imperatively. You had to define a &lt;code&gt;class&lt;/code&gt; and inside of the &lt;code&gt;constructor&lt;/code&gt; construct a shadow root. In the below example, we have a custom element named &lt;code&gt;AppCard&lt;/code&gt; that imperatively constructs a shadow root. The benefit of a shadow root is that we gain encapsulation. CSS and HTML defined in the context of the shadow root can't leak out into the rest of the page. Styling and template remains scoped to the instance of the custom element.&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;class&lt;/span&gt; &lt;span class="nc"&gt;AppCard&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;HTMLElement&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;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;shadowRoot&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;shadowRoot&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;attachShadow&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;mode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;open&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;template&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;createElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;template&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nx"&gt;template&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="s2"&gt;`
      &amp;lt;style&amp;gt;
      &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;styles&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;
      &amp;lt;/style&amp;gt;
      &amp;lt;header&amp;gt;
        &amp;lt;slot name="header"&amp;gt;&amp;lt;/slot&amp;gt;
      &amp;lt;/header&amp;gt;
      &amp;lt;section&amp;gt;
        &amp;lt;slot name="content"&amp;gt;&amp;lt;/slot&amp;gt;
      &amp;lt;/section&amp;gt;
      &amp;lt;footer&amp;gt;
        &amp;lt;slot name="footer"&amp;gt;&amp;lt;/slot&amp;gt;
      &amp;lt;/footer&amp;gt;
      `&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nx"&gt;shadowRoot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;appendChild&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;template&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cloneNode&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="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;app-card&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;AppCard&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The above example demonstrates how an autonomous custom element named &lt;code&gt;AppCard&lt;/code&gt; declares it's shadow root imperatively in the &lt;code&gt;constructor&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;Declarative Shadow DOM allows you to define the same template for the custom element declaratively. Here is an example of the same shadow root for &lt;code&gt;AppCard&lt;/code&gt; defined with Declarative Shadow DOM.&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;app-card&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;template&lt;/span&gt; &lt;span class="na"&gt;shadowrootmode=&lt;/span&gt;&lt;span class="s"&gt;"open"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt; 
    &lt;span class="nt"&gt;&amp;lt;style&amp;gt;&lt;/span&gt;
      &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="err"&gt;styles&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;span class="nt"&gt;&amp;lt;header&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;slot&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"header"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/slot&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/header&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;slot&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"content"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/slot&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;footer&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;slot&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"footer"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/slot&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/footer&amp;gt;&lt;/span&gt; 
  &lt;span class="nt"&gt;&amp;lt;/template&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;img&lt;/span&gt; &lt;span class="na"&gt;slot=&lt;/span&gt;&lt;span class="s"&gt;"header"&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"${thumbnail}"&lt;/span&gt; &lt;span class="na"&gt;alt=&lt;/span&gt;&lt;span class="s"&gt;"${alt}"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;h2&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;&lt;/span&gt;${headline}&lt;span class="nt"&gt;&amp;lt;/h2&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;p&lt;/span&gt; &lt;span class="na"&gt;slot=&lt;/span&gt;&lt;span class="s"&gt;"content"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;${content}&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"/post/${link}"&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;&lt;/span&gt;Read Post&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/app-card&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Declarative Shadow DOM introduces the &lt;code&gt;shadowrootmode&lt;/code&gt; attribute to HTML templates. This attribute is detected by the HTML parser and applied as the shadow root of it's parent element (&lt;code&gt;&amp;lt;app-card&amp;gt;&lt;/code&gt;). The above example uses template slots to dynamically inject content into a custom element template. With Declarative Shadow DOM, any HTML defined outside of the HTML template is considered "Light DOM" that can be projected through the slot to "Shadow DOM". The usage of &lt;code&gt;${}&lt;/code&gt; syntax is merely for the example. This isn't some kind of data-binding technique. Since the template is now defined declaratively, you can reduce the definition to a &lt;code&gt;String&lt;/code&gt;. ES2015 template strings are well suited for this purpose. In the demo, we'll use template strings to define each component's template declaratively using the Declarative Shadow DOM specification.&lt;/p&gt;

&lt;p&gt;But wait? If the component's template is reduced to a string how do you inject interactivity into the component client-side? You still have to define the component imperatively for the client, but since Shadow DOM is already instantiated (the browser already parsed the Declarative Shadow DOM template), you no longer need to instantiate the template. You may still imperatively instantiate Shadow DOM if the shadow root doesn't already exist.&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;class&lt;/span&gt; &lt;span class="nc"&gt;AppCard&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;HTMLElement&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;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;shadowRoot&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// instantiate the template imperatively&lt;/span&gt;
      &lt;span class="c1"&gt;// if a shadow root doesn't exist &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;Optionally, you can hydrate the component client-side differently when a shadow root is detected.&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;class&lt;/span&gt; &lt;span class="nc"&gt;AppCard&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;HTMLElement&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;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;shadowRoot&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// bind event listeners here&lt;/span&gt;
      &lt;span class="c1"&gt;// or handle other client-side interactivity&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The architects in the crowd may notice one flaw with this approach. Won't the same template need to defined twice? Once for Declarative Shadow DOM and a second time for the declaration in the custom element's &lt;code&gt;constructor&lt;/code&gt;. Sure, but we can mitigate this by using ES2015 template strings. By implementing the template through composition, we can inject the template typically defined imperatively into the other defined declaratively. We'll make sure to reuse partial templates in each custom element developed in this workshop.&lt;/p&gt;

&lt;h2&gt;
  
  
  What You Will Build
&lt;/h2&gt;

&lt;p&gt;In this workshop, you'll server-side render four custom elements necessary to display a blog:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;AppCard&lt;/code&gt; displays a card &lt;/li&gt;
&lt;li&gt;
&lt;code&gt;AppHeader&lt;/code&gt; displays the site header&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;MainView&lt;/code&gt; displays the site header and several cards &lt;/li&gt;
&lt;li&gt;
&lt;code&gt;PostView&lt;/code&gt; displays the site header and a blog post&lt;/li&gt;
&lt;/ul&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%2Fzk6zng0oia5ovnqq4an9.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%2Fzk6zng0oia5ovnqq4an9.png" alt="The main view of the blog displays a header and cards" width="800" height="559"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Acceptance Criteria
&lt;/h2&gt;

&lt;p&gt;The blog should have two routes. One that displays a list of the latest posts, another that displays the content of a single post.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;When the user visits &lt;code&gt;http://localhost:4444&lt;/code&gt; the user should view the site header and several cards (&lt;code&gt;MainView&lt;/code&gt;).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;When the user visits &lt;code&gt;http://localhost:4444/post/:slug&lt;/code&gt; the user should view the site header and blog post content (&lt;code&gt;PostView&lt;/code&gt;). The route includes a variable "slug" which is dynamically supported by the blog post.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;The workspace is a monorepo consisting of 4 project directories:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;client: Custom elements rendered client and server-side&lt;/li&gt;
&lt;li&gt;server: Express server that handles server-side rendering&lt;/li&gt;
&lt;li&gt;shim: Custom shim for browser specifications not found in Node.js, provided by Lit&lt;/li&gt;
&lt;li&gt;style: Global styles for the blog site&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Lerna and Nx handle building the project, while nodemon handles watching for changes and rebuilding the project.&lt;/p&gt;

&lt;p&gt;The project is mainly coded with TypeScript. You'll be developing mostly server-side in this workshop which maybe a change for some people. When you run &lt;code&gt;console.log&lt;/code&gt; this log will happen on the server, not in the browser, for instance.&lt;/p&gt;

&lt;p&gt;For the workshop, you'll focus primarily on a single file found at /packages/server/src/middleware/ssr.ts. This file contains the middleware that handles server-side rendering. For the remainder of the workshop, you'll edit custom elements found in packages/client/src/. Each file includes some boilerplate to bootstrap the experience.&lt;/p&gt;

&lt;h2&gt;
  
  
  Architecture
&lt;/h2&gt;

&lt;p&gt;In this workshop &lt;strong&gt;you'll develop methods for asynchronously rendering Declarative Shadow DOM templates&lt;/strong&gt;. Each view is mapped to a &lt;code&gt;Route&lt;/code&gt;. Both &lt;code&gt;Route&lt;/code&gt; listed in the acceptance criteria are defined in this file: packages/client/src/routes.ts.&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;routes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Routes&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;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;component&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;main&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;main-view&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;mainTemplate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;pathMatch&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;post&lt;/span&gt;&lt;span class="se"&gt;\/([&lt;/span&gt;&lt;span class="sr"&gt;a-zA-Z0-9-&lt;/span&gt;&lt;span class="se"&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;/&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;component&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;post&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;post-view&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;postTemplate&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;We need a static definition of the routes somewhere. Putting the definition in an &lt;code&gt;Array&lt;/code&gt; exported from a file is an opinion. Some meta-frameworks obfuscate this definition with the name of directories parsed at build or runtime.  &lt;em&gt;In the middleware, we'll reference this &lt;code&gt;Array&lt;/code&gt; to check if a route exists at the path the user is requesting the route.&lt;/em&gt; Above the routes are defined with an identifier: &lt;code&gt;path&lt;/code&gt;. &lt;code&gt;path: "/",&lt;/code&gt; matches the root, i.e. &lt;code&gt;http://localhost:4444&lt;/code&gt;. The second example uses &lt;code&gt;pathMatch&lt;/code&gt; instead. The route used to display each post is dynamic, it should display a blog post by slug. Each route also corresponds to a &lt;code&gt;template&lt;/code&gt;, which we'll define in each "view" file as a &lt;code&gt;function&lt;/code&gt; that returns a template string. An example of a template is below.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;mainTemplate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;`&amp;lt;style&amp;gt;
    &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;styles&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;
  &amp;lt;/style&amp;gt;
  &amp;lt;div class="container"&amp;gt;
  &amp;lt;!-- put content here --&amp;gt;
  &amp;lt;/div&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;During the workshop, we'll display a static route, but soon after make each view dependent on API requests. The JSON returned from local REST API endpoints will be used as a model for the view. We'll export a named &lt;code&gt;function&lt;/code&gt; called &lt;code&gt;fetchModel&lt;/code&gt; from each view file that fetches the data and returns the model. An example of this is below.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;fetchModel&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;DataModel&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
    &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;http://localhost:4444/api/meta&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;http://localhost:4444/api/posts&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="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;responses&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;responses&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;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;())))&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;jsonResponses&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;meta&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;jsonResponses&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;posts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;jsonResponses&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;posts&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;meta&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nx"&gt;posts&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;In the above example, two calls to &lt;code&gt;fetch&lt;/code&gt; request the metadata for the site and an &lt;code&gt;Array&lt;/code&gt; of recent blog posts from two different API endpoints. The responses are mapped to the model needed to display the view.&lt;/p&gt;

&lt;p&gt;A description of each API endpoint is below, but first let's look at the flow of information.&lt;/p&gt;

&lt;p&gt;Markdown -&amp;gt; JSON w/ embedded Markdown -&amp;gt; HTML -&amp;gt; Declarative Shadow DOM Template&lt;/p&gt;

&lt;p&gt;Blog posts are stored in markdown format in the directory packages/server/data/posts/. Two API endpoints (/api/posts and /api/post/:slug) fetch the markdown from each file and return that markdown in the format of a &lt;code&gt;Post&lt;/code&gt;. The type definition of a &lt;code&gt;Post&lt;/code&gt; is as-follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Post&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;slug&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;thumbnail&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;author&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;excerpt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&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;Another endpoint handles metadata for the entire page. The interface for the data returned by this endpoint is simplified for the workshop and could be expanded for SEO purposes.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Meta&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;author&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&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;During the workshop, you'll rely on three local API endpoints  to fetch the metadata of the site and the data associated with each blog post. A description of each endpoint is below.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;http://localhost:4444/api/meta&lt;/code&gt; returns the metadata necessary to display the site header in JSON format.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;http://localhost:4444/api/post/:slug&lt;/code&gt; returns a single blog post by it's "slug", a string delineated by &lt;code&gt;-&lt;/code&gt;  in JSON format.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;http://localhost:4444/api/posts&lt;/code&gt; returns an array of blog posts in JSON format.&lt;/p&gt;

&lt;p&gt;You'll make requests to these endpoints and use the JSON responses to populate the content for each Declarative Shadow DOM template. In each file that requires data, a type definition for &lt;code&gt;Meta&lt;/code&gt; and &lt;code&gt;Post&lt;/code&gt; schema is already provided in packages/server/src/db/index.ts. These type definitions are imported into relevant files to ease in development.&lt;/p&gt;

&lt;p&gt;In addition to the three provided local endpoints, you'll be making a request to the Github API to parse the markdown returned from the included blog post files. This API is necessary because it provides the simplest way to parse code snippets found in markdown files and convert them to HTML.&lt;/p&gt;

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

&lt;p&gt;You'll need a GitHub account. You can use GitHub to sign-in to StackBlitz and later, you'll need GitHub to generate a token.&lt;/p&gt;

&lt;p&gt;If you don't already have a GitHub account, &lt;a href="https://github.com/signup" rel="noopener noreferrer"&gt;signup for Github here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you want to follow along with this tutorial, fork the &lt;a href="https://stackblitz.com/edit/node-xuhpyc?file=README.md" rel="noopener noreferrer"&gt;StackBlitz&lt;/a&gt; or &lt;a href="https://github.com/steveblue/web-components-workshop-starter" rel="noopener noreferrer"&gt;Github repo&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;When using Stackblitz, the development environment will immediately install dependencies and load an embedded browser. VS Code is available in browser for development. Stackblitz works best in Google Chrome. &lt;/p&gt;

&lt;p&gt;If using Github, fork the repository and clone the repo locally. Run &lt;code&gt;npm install&lt;/code&gt; and &lt;code&gt;npm run dev&lt;/code&gt;. Begin coding in your IDE of choice.&lt;/p&gt;

&lt;h3&gt;
  
  
  Important Note About Tokens
&lt;/h3&gt;

&lt;p&gt;During the workshop, we'll be using the &lt;a href="https://github.com/octokit" rel="noopener noreferrer"&gt;Github API&lt;/a&gt; known as Octokit to generate client-side HTML from Markdown for each blog post. If you're using Stackblitz, an API token is provided for the workshop but will be revoked soon after. If you've cloned the repo or the token is revoked, login to GitHub and &lt;a href="https://github.com/settings/tokens?type=beta" rel="noopener noreferrer"&gt;generate a new token on Github&lt;/a&gt; for use in the workshop.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Never store tokens statically in code.&lt;/strong&gt; The only reason the token is injected this way for the workshop is to bootstrap making requests with Octokit.&lt;/p&gt;

&lt;h3&gt;
  
  
  Important Note About Support
&lt;/h3&gt;

&lt;p&gt;If you are following along with StackBlitz, make sure you are using Google Chrome.&lt;/p&gt;

&lt;p&gt;Examples included in this workshop will work in every mainstream browser except Firefox. The code should work in Google Chrome, Microsoft Edge, and Apple Safari.&lt;/p&gt;

&lt;p&gt;Although Mozilla has signaled support for implementing Declarative Shadow DOM, the browser vender has yet to provide support in a stable release. A lightweight ponyfill is available to address the lack of support in Firefox.&lt;/p&gt;

&lt;p&gt;Support for Declarative Shadow DOM is subject to change in Firefox. I have faith the standard will become cross-browser compatible in the near future because Mozilla recently &lt;a href="https://github.com/mozilla/standards-positions/commit/059d634827609943502b206e728709241dace48c" rel="noopener noreferrer"&gt;changed it's position&lt;/a&gt; on Declarative Shadow DOM. I've been using &lt;a href="https://www.npmjs.com/package/@webcomponents/template-shadowroot" rel="noopener noreferrer"&gt;the ponyfill&lt;/a&gt; for over a year without issues in browsers that lack support for the standard.&lt;/p&gt;

&lt;h2&gt;
  
  
  Server Side Rendering Custom Elements with @lit-labs/ssr
&lt;/h2&gt;

&lt;p&gt;Let's get started coding, shall we? &lt;/p&gt;

&lt;p&gt;The first task is declaring a Declarative Shadow DOM template for the component named &lt;code&gt;MainView&lt;/code&gt; found in packages/client/src/view/main/index.ts. This view will ultimately replace the boilerplate currently displaying "Web Component Blog Starter" in the browser at the path &lt;a href="http://localhost:4444/" rel="noopener noreferrer"&gt;http://localhost:4444/&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Supporting Declarative Shadow DOM in MainView
&lt;/h3&gt;

&lt;p&gt;Open packages/client/src/view/main/index.ts in your IDE and find the Shadow DOM template that is defined imperatively in the &lt;code&gt;constructor&lt;/code&gt; of the &lt;code&gt;MainView class&lt;/code&gt;. We're going to declare this template instead as an ES2015 template string returned by a &lt;code&gt;function&lt;/code&gt; named &lt;code&gt;shadowTemplate&lt;/code&gt;. Cut and paste the current template into the template string returned by &lt;code&gt;shadowTemplate&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;shadowTemplate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="s2"&gt;`&amp;lt;style&amp;gt;
    &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;styles&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;
  &amp;lt;/style&amp;gt;
  &amp;lt;div class="post-container"&amp;gt;
    &amp;lt;app-header&amp;gt;&amp;lt;/app-header&amp;gt;
  &amp;lt;/div&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;For your convenience, &lt;code&gt;html&lt;/code&gt; is imported into this file, which you can use to tag the template literal. This &lt;code&gt;html&lt;/code&gt; shouldn't be confused with the &lt;code&gt;function&lt;/code&gt; exported from lit-html. It's a custom implementation of a &lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals#tagged_templates" rel="noopener noreferrer"&gt;tagged template literal&lt;/a&gt; that enables syntax highlighting of the string, if you have that enabled in your IDE.&lt;/p&gt;

&lt;p&gt;If you opted for GitHub and cloned the repo locally, you can enable &lt;a href="https://marketplace.visualstudio.com/items?itemName=Tobermory.es6-string-html" rel="noopener noreferrer"&gt;this VSCode extension&lt;/a&gt; that offers syntax highlighting of HTML and CSS inside tagged template literals. That's really the only purpose of &lt;code&gt;html&lt;/code&gt; for now, although you could exploit this function and parse the template string in different ways if you wanted. &lt;/p&gt;

&lt;p&gt;Update the imperative logic to call &lt;code&gt;shadowTemplate&lt;/code&gt; which should return the same &lt;code&gt;String&lt;/code&gt; as before.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;template&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="nf"&gt;shadowTemplate&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In an effort to reuse the template, we can pass it to the next &lt;code&gt;function&lt;/code&gt; we'll declare named &lt;code&gt;template&lt;/code&gt;. The format is the same, although this time the template normally declared imperatively is encapsulated by the HTML tag associated with the component (&lt;code&gt;&amp;lt;main-view&amp;gt;&lt;/code&gt;) and a HTML template with the attribute &lt;code&gt;shadowrootmode&lt;/code&gt; set to &lt;code&gt;open&lt;/code&gt;. Inject the &lt;code&gt;shadowTemplate()&lt;/code&gt; we defined earlier inside of the &lt;code&gt;&amp;lt;template&amp;gt;&lt;/code&gt; tags.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;template&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="s2"&gt;`&amp;lt;main-view&amp;gt;
  &amp;lt;template shadowrootmode="open"&amp;gt;
    &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nf"&gt;shadowTemplate&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;&lt;span class="s2"&gt;
  &amp;lt;/template&amp;gt;
  &amp;lt;/main-view&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;You just declared your first Declarative Shadow DOM template. Some benefits of this approach is that we can reuse the typical shadow root template declared imperatively and since we're using &lt;code&gt;function&lt;/code&gt;, inject arguments that can be passed to the ES2015 template string. This will come in handy when we want to populate the template with data returned from API endpoints.&lt;/p&gt;

&lt;p&gt;Finally, be sure to export the &lt;code&gt;template&lt;/code&gt; from the file. More on why we're exporting the &lt;code&gt;MainView class&lt;/code&gt; and &lt;code&gt;template function&lt;/code&gt; later.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;MainView&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;template&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 good start, but we'll run into an issue if we keep the code as-is. While it is possible to reuse template partials, the above example is kind of an exaggeration. You'll rarely be able to inject the entire shadow root into the Declarative Shadow DOM template like we did, especially when there are child custom elements that also need to be rendered server-side with Declarative Shadow DOM. In the above example, &lt;code&gt;&amp;lt;app-header&amp;gt;&lt;/code&gt; can't currently be rendered on the server because we haven't declared that component's template with Declarative Shadow DOM. Let's do that now.&lt;/p&gt;

&lt;h3&gt;
  
  
  Supporting Declarative Shadow DOM in Header
&lt;/h3&gt;

&lt;p&gt;To declare a new template to server-side render the &lt;code&gt;AppHeader&lt;/code&gt; component, open packages/client/src/component/header/Header.ts. &lt;/p&gt;

&lt;p&gt;Just like in the last example, cut and paste the template that is defined imperatively in the &lt;code&gt;AppHeader constructor&lt;/code&gt; into a new &lt;code&gt;function&lt;/code&gt; named &lt;code&gt;shadowTemplate&lt;/code&gt; that returns the same string. This time, inject a single argument into the function that destructs an &lt;code&gt;Object&lt;/code&gt; with two properties: &lt;code&gt;styles&lt;/code&gt; and &lt;code&gt;title&lt;/code&gt; and pass those to the template.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;shadowTemplate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;styles&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;gt;&lt;/span&gt; &lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="s2"&gt;`
  &amp;lt;style&amp;gt;
    &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;styles&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;
  &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;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;Call &lt;code&gt;shadowTemplate&lt;/code&gt; with the first argument, setting the two properties that are currently found globally in the file: &lt;code&gt;styles&lt;/code&gt; and &lt;code&gt;title&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;template&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="nf"&gt;shadowTemplate&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;styles&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It's quite alright to define arguments in this way that later populate the template. It will offer some flexibility later on when we need to map the response of an API endpoint to what the &lt;code&gt;template function&lt;/code&gt; expects. To declare the Declarative Shadow DOM template, define a new &lt;code&gt;function&lt;/code&gt; named &lt;code&gt;template&lt;/code&gt;, this time encapsulating the call to &lt;code&gt;shadowTemplate&lt;/code&gt; with the &lt;code&gt;&amp;lt;app-header&amp;gt;&lt;/code&gt; tag and HTML template.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;template&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;styles&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;gt;&lt;/span&gt; &lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="s2"&gt;`&amp;lt;app-header&amp;gt;
  &amp;lt;template shadowrootmode="open"&amp;gt;
  &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nf"&gt;shadowTemplate&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;styles&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;/template&amp;gt;
&amp;lt;/app-header&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 can reuse the shadow root template used declaratively in this case because &lt;code&gt;AppHeader&lt;/code&gt; is a leaf node, that is to say, it has no direct descendants that need to also be rendered server-side with Declarative Shadow DOM.&lt;/p&gt;

&lt;p&gt;Finally, export all the necessary parts in &lt;code&gt;MainView&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;styles&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="nx"&gt;template&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;AppHeader&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Updating MainView with the Declarative Header
&lt;/h3&gt;

&lt;p&gt;Open packages/client/src/view/main/index.ts again and import the parts from Header.js needed to render the &lt;code&gt;template&lt;/code&gt; this time renamed as &lt;code&gt;appHeaderTemplate&lt;/code&gt;. Notice how &lt;code&gt;styles as appHeaderStyles&lt;/code&gt; is used with &lt;code&gt;as&lt;/code&gt; so they don't conflict with this local &lt;code&gt;styles&lt;/code&gt; declaration in packages/client/src/view/main/index.ts.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;styles&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;appHeaderStyles&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;template&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;appHeaderTemplate&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="nx"&gt;AppHeader&lt;/span&gt;&lt;span class="p"&gt;,&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;../../component/header/Header.js&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;Update the &lt;code&gt;template function&lt;/code&gt;, replacing the &lt;code&gt;shadowTemplate&lt;/code&gt; call with &lt;code&gt;appHeaderTemplate&lt;/code&gt;, being sure to pass in the &lt;code&gt;styles&lt;/code&gt; and &lt;code&gt;title&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;template&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="s2"&gt;`&amp;lt;main-view&amp;gt;
  &amp;lt;template shadowrootmode="open"&amp;gt;
    &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nf"&gt;appHeaderTemplate&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;styles&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;appHeaderStyles&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;/template&amp;gt;
  &amp;lt;/main-view&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;Keen observers may notice an opportunity here. We don't have to always use the &lt;code&gt;styles&lt;/code&gt; and &lt;code&gt;title&lt;/code&gt; prescribed in &lt;code&gt;Header.ts&lt;/code&gt;, but use the call to &lt;code&gt;appHeaderTemplate&lt;/code&gt; as a means to override styling or set the &lt;code&gt;title&lt;/code&gt; dynamically. We'll do the latter in a later section of this workshop.&lt;/p&gt;

&lt;h3&gt;
  
  
  Supporting Declarative Shadow DOM in PostView
&lt;/h3&gt;

&lt;p&gt;Before we code the Express middleware needed to server-side render these Declarative Shadow DOM templates, we have some housekeeping to do. The second view component defined in packages/client/src/view/post/index.ts also needs a &lt;code&gt;function&lt;/code&gt; named &lt;code&gt;template&lt;/code&gt; exported from the file. This component is responsible for displaying a single blog post.&lt;/p&gt;

&lt;p&gt;Notice a pattern that is forming? Patterns are very helpful when server-side rendering components. If each component reliably exports the same named &lt;code&gt;template&lt;/code&gt;, we can ensure the middleware reliably interprets each Declarative Shadow DOM template. Standardization is helpful here.&lt;/p&gt;

&lt;p&gt;Open packages/client/src/view/post/index.ts. Cut and copy the template declared imperatively into a &lt;code&gt;function&lt;/code&gt; named &lt;code&gt;shadowTemplate&lt;/code&gt;, just like you did in the last two components.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;shadowTemplate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="s2"&gt;`&amp;lt;style&amp;gt;
    &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;styles&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;
  &amp;lt;/style&amp;gt;
  &amp;lt;div class="post-container"&amp;gt;
    &amp;lt;app-header&amp;gt;&amp;lt;/app-header&amp;gt;
    &amp;lt;div class="post-content"&amp;gt;
      &amp;lt;h2&amp;gt;Author: &amp;lt;/h2&amp;gt;
      &amp;lt;footer&amp;gt;
        &amp;lt;a href="/"&amp;gt;👈 Back&amp;lt;/a&amp;gt;
      &amp;lt;/footer&amp;gt;
    &amp;lt;/div&amp;gt;
  &amp;lt;/div&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;Call &lt;code&gt;shadowTemplate()&lt;/code&gt; in the &lt;code&gt;constructor&lt;/code&gt; of &lt;code&gt;PostView&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;template&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="nf"&gt;shadowTemplate&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Declare a new &lt;code&gt;function&lt;/code&gt; named &lt;code&gt;template&lt;/code&gt; and make sure to encapsulate the &lt;code&gt;shadowTemplate&lt;/code&gt; with Declarative Shadow DOM.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;template&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="s2"&gt;`&amp;lt;post-view&amp;gt;
  &amp;lt;template shadowrootmode="open"&amp;gt;
    &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nf"&gt;shadowTemplate&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;&lt;span class="s2"&gt;
  &amp;lt;/template&amp;gt;
&amp;lt;/post-view&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;Export &lt;code&gt;template&lt;/code&gt; from packages/client/src/view/post/index.ts.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;PostView&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;template&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We'll return to this file later, but for now that should be enough to display a bit of text and a link that navigates the user back to &lt;code&gt;MainView&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;Onto the middleware...&lt;/p&gt;

&lt;h2&gt;
  
  
  Express Middleware
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.npmjs.com/package/@lit-labs/ssr" rel="noopener noreferrer"&gt;@lit-labs/ssr&lt;/a&gt; is an npm package distributed by Lit at Google for server-side rendering Lit templates and components. The package is for use in the context of Node.js and can be used in conjunction with Express middleware. Express is a popular HTTP server for Node.js that is largely based on middleware. &lt;/p&gt;

&lt;p&gt;Express middleware are functions that intercept HTTP requests and handle HTTP responses. We'll code an Express middleware that handles requests to &lt;code&gt;http://localhost:4444&lt;/code&gt; and &lt;code&gt;http://localhost:4444/post/:slug&lt;/code&gt; Whenever a user lists these two routes, Express will calls a custom middleware &lt;code&gt;function&lt;/code&gt; already exported as &lt;code&gt;ssr&lt;/code&gt;. The algorithm in that function is what you'll be working on.&lt;/p&gt;

&lt;p&gt;The middleware you'll be working with is imported and set on two routes in packages/server/src/index.ts.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;ssr&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;./middleware/ssr.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ssr&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/post/:slug&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ssr&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When the user visits &lt;a href="http://localhost:4444/" rel="noopener noreferrer"&gt;http://localhost:4444/&lt;/a&gt; or &lt;a href="http://localhost:4444/post/" rel="noopener noreferrer"&gt;http://localhost:4444/post/&lt;/a&gt;, the middleware is activated. The notation &lt;code&gt;:slug&lt;/code&gt; in the second route for the post view denotes the middleware should expect a path segment named "slug" that will now appear on the request &lt;code&gt;params&lt;/code&gt; passed into the middleware &lt;code&gt;function&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If you've never coded Node.js, have no fear. We'll go step-by-step to code the Express middleware. &lt;/p&gt;

&lt;p&gt;Open packages/server/src/middleware/ssr.ts to get started coding the middleware. Browse the file to acquaint yourself with the available &lt;code&gt;import&lt;/code&gt; and declared &lt;code&gt;const&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;Notice how different files from the monorepo are injected into the HTML declared in &lt;code&gt;renderApp&lt;/code&gt;. Relative paths are used here with the &lt;code&gt;readFileSync&lt;/code&gt; to find the path of the global styles file named &lt;code&gt;style.css&lt;/code&gt; and then minify the styles (for increased performance). The minification could be turned off by dynamically setting the &lt;code&gt;minifyCSS&lt;/code&gt; with the &lt;code&gt;env&lt;/code&gt; variable, which is used to determine if the code is running in "development" or "production" environments.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;stylePath&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
  &lt;span class="nf"&gt;resolve&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;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cwd&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;&lt;span class="s2"&gt;../../style/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;filename&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;readStyleFile&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
  &lt;span class="nf"&gt;readFileSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;stylePath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;styles&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;minify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;readStyleFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;style.css&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;minifyCSS&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;env&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;production&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;removeComments&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;collapseWhitespace&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="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the above example, the global styles of the blog are read from a file in the monorepo and then minified depending on the environment.&lt;/p&gt;

&lt;p&gt;The middleware &lt;code&gt;function&lt;/code&gt; is found at the bottom of the file. Currently, the middleware calls &lt;code&gt;renderApp()&lt;/code&gt; and responds to the HTTP request (&lt;code&gt;req&lt;/code&gt;) by calling &lt;code&gt;res.status(200)&lt;/code&gt;, which is the HTTP response code for "success" and then &lt;code&gt;.send()&lt;/code&gt; with the ES2015 template string returned from &lt;code&gt;renderApp&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ssrResult&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;renderApp&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ssrResult&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;Select the boilerplate shown in the below image and remove the &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&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%2Fy7ak4f08zs8jw0ux4dhl.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%2Fy7ak4f08zs8jw0ux4dhl.png" alt="Image description" width="800" height="414"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Insert a new string in the template to reveal a change in the browser window. In the below example, we inject "Hello World" into the template. This will be temporary, as the &lt;code&gt;renderApp function&lt;/code&gt; will become dynamic soon.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;renderApp&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="s2"&gt;`&amp;lt;!DOCTYPE html&amp;gt;
  &amp;lt;html lang="en"&amp;gt;
    &amp;lt;head&amp;gt;
        &amp;lt;meta name="viewport" content="width=device-width, initial-scale=1"&amp;gt;
        &amp;lt;meta name="description" content="Web Components Blog"&amp;gt;
        &amp;lt;style rel="stylesheet" type="text/css"&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;styles&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;/style&amp;gt;
    &amp;lt;/head&amp;gt;
    &amp;lt;body&amp;gt; 
      &amp;lt;div id="root"&amp;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;Hello World&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;/div&amp;gt;
  &amp;lt;/body&amp;gt;&amp;lt;/html&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;h3&gt;
  
  
  Handling Routes in Middleware
&lt;/h3&gt;

&lt;p&gt;Since this middleware will handle multiple routes in Express, we need a way to detect if a route should be served. When a route isn't available, the middleware should throw a 404 Not Found error. HTML should only be served if a route is declared. &lt;/p&gt;

&lt;p&gt;In packages/client/src/routes.js each route is declared in an &lt;code&gt;Array&lt;/code&gt;. You can import this &lt;code&gt;Array&lt;/code&gt; directly from the file, ensuring to use the file extension ".js". This is how imports are handled with ES2015 modules in Node.js, which mirrors the way browsers expect imports to be declared. Eventhough we're coding in TypeScript files, ".js" is still used in the path.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;routes&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;../../../client/src/routes.js&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;Each &lt;code&gt;Route&lt;/code&gt; is typed as follows. You can reference this type to code an algorithm that returns the &lt;code&gt;Route&lt;/code&gt; based on the HTTP request. You may also rely on your IDE intellisense.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Route&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;component&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;path&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;pathMatch&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nb"&gt;RegExp&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;template&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;title&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;params&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the middleware &lt;code&gt;function&lt;/code&gt;, write an algorithm that handles two different use cases:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;If the route declares a "path", match the route exactly to the current &lt;code&gt;originalUrl&lt;/code&gt; on the HTTP request.&lt;/li&gt;
&lt;li&gt;If the your declares a "pathMatch", which is a way to match routes by &lt;code&gt;RegExp&lt;/code&gt;, call &lt;code&gt;test&lt;/code&gt; on the &lt;code&gt;RegExp&lt;/code&gt; to determine if the regular expression matches the &lt;code&gt;originalUrl&lt;/code&gt; on the HTTP request. &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;When you are finished, log the matched route. It should log the &lt;code&gt;Route&lt;/code&gt; in your Terminal. An edge case should be accounted for when the user visits &lt;a href="http://localhost:4444" rel="noopener noreferrer"&gt;http://localhost:4444&lt;/a&gt; instead of &lt;a href="http://localhost:4444/" rel="noopener noreferrer"&gt;http://localhost:4444/&lt;/a&gt; and the &lt;code&gt;path&lt;/code&gt; is declared as &lt;code&gt;/&lt;/code&gt;, there should still be a match.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;route&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;routes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;r&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="c1"&gt;// handle base url&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt; &lt;span class="o"&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="o"&gt;&amp;amp;&amp;amp;&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;originalUrl&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="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;path&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;originalUrl&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="c1"&gt;// handle other routes&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;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pathMatch&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;test&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;originalUrl&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;r&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;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;route&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 there isn't a match, &lt;code&gt;Array.prototype.find&lt;/code&gt; will return &lt;code&gt;undefined&lt;/code&gt;, so account for this by redirecting the user to a "404" route. We won't actually work on this route now, but for bonus points you could later server-side render a 404 page.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;route&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;redirect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;301&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/404&lt;/span&gt;&lt;span class="dl"&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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, add the current HTTP request &lt;code&gt;params&lt;/code&gt; to the &lt;code&gt;Route&lt;/code&gt;. This will be necessary for the single post view which has a single param &lt;code&gt;:slug&lt;/code&gt; which can now be accessed via &lt;code&gt;route.params.slug&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;  &lt;span class="nx"&gt;route&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;route&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;params&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now that you have a matched route, you should have access to the route's template stored on the &lt;code&gt;Route&lt;/code&gt;, but we first have to "build" the route in development like it were built in production. When the routes are built in the &lt;code&gt;client&lt;/code&gt; package, each route is deployed to packages/client/dist as a separate JavaScript bundle. We can simulate this deployment in the development environment by using esbuild programmatically to build the JavaScript bundle that matches each route.&lt;/p&gt;

&lt;p&gt;First, define a new &lt;code&gt;function&lt;/code&gt; named &lt;code&gt;clientPath&lt;/code&gt; that returns the path to the either the view's source in the src directory or the bundle in the dist directory of the client package. We'll need both paths because during development we'll build each view from the src directory to dist. Return a &lt;code&gt;String&lt;/code&gt; using &lt;code&gt;resolve&lt;/code&gt; and &lt;code&gt;process.cwd()&lt;/code&gt; to find the path of the custom element bundle.&lt;/p&gt;

&lt;p&gt;To be clear, the &lt;code&gt;function&lt;/code&gt; needs to return either of these paths:&lt;/p&gt;

&lt;p&gt;packages/client/src/view/main/index.js&lt;br&gt;
packages/client/dist/view/main/index.js&lt;br&gt;
packages/client/src/view/post/index.js&lt;br&gt;
packages/client/dist/view/post/index.js&lt;/p&gt;

&lt;p&gt;The first argument of the &lt;code&gt;function&lt;/code&gt; should denote the directory, while the second should provide a means to identify the file (stored as &lt;code&gt;route.component&lt;/code&gt;).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;clientPath&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;directory&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;src&lt;/span&gt;&lt;span class="dl"&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;dist&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;route&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;resolve&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;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cwd&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;&lt;span class="s2"&gt;../../client/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;directory&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/view/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;route&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;component&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/index.js`&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;Use the new &lt;code&gt;clientPath function&lt;/code&gt; in the context of the middleware to build the JavaScript for the route, calling &lt;code&gt;esbuild.build&lt;/code&gt; with the path to the source file mapped to &lt;code&gt;entryPoints&lt;/code&gt; and the &lt;code&gt;outfile&lt;/code&gt; mapped to the path to the distributed bundle in the dist directory. Every time the middleware gets called and matches a route, the appropriate bundle will be built. This will simulate how the bundles are distributed for production and utilize esbuild for development which should be fast and efficient.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt; &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;development&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;await&lt;/span&gt; &lt;span class="nx"&gt;esbuild&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;build&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;entryPoints&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;clientPath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;src&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;route&lt;/span&gt;&lt;span class="p"&gt;)],&lt;/span&gt;
      &lt;span class="na"&gt;outfile&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;clientPath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dist&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;route&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="na"&gt;format&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;esm&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;minify&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;env&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;development&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;bundle&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="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;To render the Declarative Shadow DOM template exported from each bundle, we need to dynamically import the JavaScript from the bundle. Reuse the &lt;code&gt;clientPath function&lt;/code&gt;, this time in the context of a dynamic &lt;code&gt;import&lt;/code&gt; statement to set a new &lt;code&gt;const&lt;/code&gt; named &lt;code&gt;module&lt;/code&gt;. This will give us access to whatever is exported from the view's JavaScript bundle.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="kr"&gt;module&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;clientPath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dist&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;route&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Declare another &lt;code&gt;const&lt;/code&gt; named &lt;code&gt;compiledTemplate&lt;/code&gt; that calls &lt;code&gt;template function&lt;/code&gt; exported from &lt;code&gt;module&lt;/code&gt;. &lt;code&gt;template&lt;/code&gt; returns the Declarative Shadow DOM template we defined earlier in packages/client/src/view/main/index.ts.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;compiledTemplate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kr"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;template&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Before we pass the &lt;code&gt;String&lt;/code&gt; returned by the &lt;code&gt;template function&lt;/code&gt; to another &lt;code&gt;function&lt;/code&gt; named &lt;code&gt;render&lt;/code&gt; exported from @lit-labs/ssr, we need to sanitize the HTML. &lt;code&gt;render&lt;/code&gt; will ultimately be the function that parses and streams the Declarative Shadow DOM template for the HTML response. We could also provide a custom sanitization algorithm. Whatever the outcome we need to pass the template through the &lt;code&gt;unsafeHTML function&lt;/code&gt; exported from @lit-labs/ssr. You could think of this function like &lt;code&gt;dangerouslySetInnerHTML&lt;/code&gt; from React. The &lt;code&gt;render&lt;/code&gt; function exported from @lit-labs/ssr expects any HTML template to be sanitized through &lt;code&gt;unsafeHTML&lt;/code&gt; prior to calling &lt;code&gt;render&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Make a new &lt;code&gt;function&lt;/code&gt; named &lt;code&gt;sanitizeTemplate&lt;/code&gt; that should be marked &lt;code&gt;async&lt;/code&gt; because &lt;code&gt;unsafeHTML&lt;/code&gt; itself is asynchronous. &lt;code&gt;html&lt;/code&gt; in this example is exported from the lit package, it's the function used by the Lit library for handling HTML templates. It's very similar in nature to the &lt;code&gt;html&lt;/code&gt; we tagged the template literals with earlier in the client code, but works well within the Lit ecosystem.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sanitizeTemplate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;template&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&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="nf"&gt;unsafeHTML&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;template&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the middleware &lt;code&gt;function&lt;/code&gt;, make a new &lt;code&gt;const&lt;/code&gt; and set it to a call to &lt;code&gt;sanitizeTemplate&lt;/code&gt;, passing in the &lt;code&gt;compiledTemplate&lt;/code&gt;. Finally, pass &lt;code&gt;template&lt;/code&gt; to &lt;code&gt;renderApp&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;template&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;sanitizeTemplate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;compiledTemplate&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;ssrResult&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;renderApp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;template&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add a first argument to &lt;code&gt;renderApp&lt;/code&gt; appropriately named &lt;code&gt;template&lt;/code&gt;. Replace the "Hello World" from earlier with a call to &lt;code&gt;render&lt;/code&gt;, passing in the &lt;code&gt;template&lt;/code&gt;. &lt;code&gt;render&lt;/code&gt; is the &lt;code&gt;function&lt;/code&gt; exported from @lit-labs/ssr that is responsible for rendering the Declarative Shadow DOM template for the entire view. There is some logic &lt;code&gt;render&lt;/code&gt; for handling components built with Lit, but it can also accept vanilla Declarative Shadow DOM templates.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;renderApp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;template&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="s2"&gt;`&amp;lt;!DOCTYPE html&amp;gt;
  &amp;lt;html lang="en"&amp;gt;
    &amp;lt;head&amp;gt;
        &amp;lt;meta name="viewport" content="width=device-width, initial-scale=1"&amp;gt;
        &amp;lt;meta name="description" content="Web Components Blog"&amp;gt;
        &amp;lt;style rel="stylesheet" type="text/css"&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;styles&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;/style&amp;gt;
    &amp;lt;/head&amp;gt;
    &amp;lt;body&amp;gt; 
      &amp;lt;div id="root"&amp;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;template&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;/div&amp;gt;
  &amp;lt;/body&amp;gt;&amp;lt;/html&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;If you use intellisense to reveal more about &lt;code&gt;render&lt;/code&gt;, you'll learn &lt;code&gt;render&lt;/code&gt; accepts any template that can be parsed my lit-html, another package in the Lit ecosystem. You'll also find out the &lt;code&gt;function&lt;/code&gt; returns a "string iterator".&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%2F6rgqufdjbl8l8upubo2s.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%2F6rgqufdjbl8l8upubo2s.png" alt="Image description" width="800" height="395"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;By now you'll notice the template doesn't render correctly. Instead &lt;code&gt;[object Generator]&lt;/code&gt; is printed in the browser. What is going on here? The hint comes from what &lt;code&gt;render&lt;/code&gt; returns: a "string iterator". &lt;code&gt;Generator&lt;/code&gt; was introduced in ES2015 and it's an often under appreciated, yet powerful aspect of the JavaScript language. Lit is using Generators to support streaming the value over the request/response lifecycle of a HTTP request. &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%2Fdljeog7v66xtxdxgrefq.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%2Fdljeog7v66xtxdxgrefq.png" alt="Image description" width="800" height="267"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Handling The Generator
&lt;/h3&gt;

&lt;p&gt;An easy way to support the output of &lt;code&gt;render&lt;/code&gt; is to convert &lt;code&gt;renderApp&lt;/code&gt; to a &lt;code&gt;Generator function&lt;/code&gt;. That is rather easy. &lt;code&gt;Generator&lt;/code&gt; can be defined using the &lt;code&gt;function*&lt;/code&gt; syntax, which defines a &lt;code&gt;function&lt;/code&gt; that returns a &lt;code&gt;Generator&lt;/code&gt;. Calling a &lt;code&gt;Generator&lt;/code&gt; does not immediately execute the algorithm defined in the &lt;code&gt;function*&lt;/code&gt; body. You must define &lt;code&gt;yield&lt;/code&gt; expressions, that specify a value returned by the iterator. &lt;code&gt;Generator&lt;/code&gt; are just one kind of &lt;code&gt;Iterator&lt;/code&gt;. &lt;code&gt;yield*&lt;/code&gt; delegates something to another &lt;code&gt;Generator&lt;/code&gt;, in our example &lt;code&gt;render&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;Convert &lt;code&gt;renderApp&lt;/code&gt; to a &lt;code&gt;Generator function&lt;/code&gt;, breaking up the  HTML document into logical &lt;code&gt;yield&lt;/code&gt; or &lt;code&gt;yield*&lt;/code&gt; expressions, like in the below example.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nf"&gt;renderApp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;template&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="s2"&gt;`&amp;lt;!DOCTYPE html&amp;gt;
  &amp;lt;html lang="en"&amp;gt;
    &amp;lt;head&amp;gt;
        &amp;lt;meta name="viewport" content="width=device-width, initial-scale=1"&amp;gt;
        &amp;lt;meta name="description" content="Web Components Blog"&amp;gt;
        &amp;lt;style rel="stylesheet" type="text/css"&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;styles&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;/style&amp;gt;
    &amp;lt;/head&amp;gt;
    &amp;lt;body&amp;gt; 
      &amp;lt;div id="root"&amp;gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;yield&lt;/span&gt;&lt;span class="o"&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;template&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="s2"&gt;`&amp;lt;/div&amp;gt;
  &amp;lt;/body&amp;gt;&amp;lt;/html&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;Once you're complete, the output in the browser window should change. Rather anticlimactic, huh? &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%2Fypbta63qqm0sy8mbnl4b.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%2Fypbta63qqm0sy8mbnl4b.png" alt="Image description" width="639" height="344"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There is a hint for the reason this is happening in the documentation for &lt;code&gt;render&lt;/code&gt;. That &lt;code&gt;function&lt;/code&gt; "streams" the result and we haven't yet handled the stream. Before we do, update the bottom of the middleware &lt;code&gt;function&lt;/code&gt;, being sure to &lt;code&gt;await&lt;/code&gt; the result of &lt;code&gt;renderApp&lt;/code&gt;, which is now asynchronous due to being a &lt;code&gt;Generator function&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ssrResult&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;renderApp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;template&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Streaming the Result
&lt;/h3&gt;

&lt;p&gt;Streams in Node.js can be expressed as &lt;code&gt;Buffer&lt;/code&gt;. Ultimately what we need to do is convert the stream to a &lt;code&gt;String&lt;/code&gt; that can be sent to the client in the HTTP response as an HTML document. &lt;code&gt;Buffer&lt;/code&gt; can be converted to a &lt;code&gt;String&lt;/code&gt; by calling &lt;code&gt;toString&lt;/code&gt;, which also accepts &lt;code&gt;utf-8&lt;/code&gt; formatting as an argument. UTF-8 is necessary because it is defined as the default character encoding for HTML. &lt;/p&gt;

&lt;p&gt;Streams are asynchronous. We can use a &lt;code&gt;for&lt;/code&gt; combined with an &lt;code&gt;await&lt;/code&gt; to push each "chunk" of the stream to a &lt;code&gt;Buffer&lt;/code&gt;, then combine all the &lt;code&gt;Buffer&lt;/code&gt; using &lt;code&gt;Buffer.concat&lt;/code&gt; and convert the result to a UTF-8 encoded &lt;code&gt;String&lt;/code&gt;. Make a new &lt;code&gt;function&lt;/code&gt; named &lt;code&gt;streamToString&lt;/code&gt; that does that, giving it an argument named &lt;code&gt;stream&lt;/code&gt;. &lt;code&gt;streamToString&lt;/code&gt; return the &lt;code&gt;String&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;streamToString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;stream&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;chunks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
  &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="k"&gt;await &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;chunk&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;chunks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;chunk&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;Buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;concat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;chunks&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The above seems like it would just accept our stream returned from &lt;code&gt;renderApp&lt;/code&gt;, but we first should pass the stream through &lt;code&gt;Readable&lt;/code&gt;, a utility in Node.js that allows us to read the stream. Make another &lt;code&gt;async function&lt;/code&gt; named &lt;code&gt;renderStream&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;renderStream&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;stream&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="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;streamToString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Readable&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;stream&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;Finally, in the middleware &lt;code&gt;function&lt;/code&gt; make another variable named &lt;code&gt;stream&lt;/code&gt; and call &lt;code&gt;renderStream&lt;/code&gt;, passing in the &lt;code&gt;ssrResult&lt;/code&gt;. Then, call &lt;code&gt;res.send&lt;/code&gt; with the &lt;code&gt;stream&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt; &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;stream&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;renderStream&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ssrResult&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
 &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Voilà! The title defined in the Declarative Shadow DOM template is now visible in the browser.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq2fhjp2aqfqq5h9o9z4g.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%2Fq2fhjp2aqfqq5h9o9z4g.png" alt="Image description" width="800" height="466"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you inspect the HTML you'll notice artifacts left over from the &lt;code&gt;render function&lt;/code&gt;. These comments are left behind by Lit and they seem useful possibly for parsing library specific code. Since we just used browser spec, our code may not use them really. &lt;/p&gt;

&lt;p&gt;*&lt;em&gt;Declarative Shadow DOM template has successfully been server-side rendered. *&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The hard part is over. We'll return to the middleware to enhance parts of it later, but a large majority of the work is done. The middleware you just coded can now be used to render Declarative Shadow DOM templates.&lt;/p&gt;

&lt;p&gt;Let's add some content to the view. Next, you'll update the &lt;code&gt;Card&lt;/code&gt; custom element to export a Declarative Shadow DOM template, then import the template into the &lt;code&gt;MainView&lt;/code&gt; custom element. Then, you'll populate the view with cards that display metadata about each blog post.&lt;/p&gt;

&lt;h3&gt;
  
  
  Updating Card with Declarative Shadow DOM
&lt;/h3&gt;

&lt;p&gt;In this section, you'll learn how to handle HTML template slots with Delcarative Shadow DOM templates after working on the most complex template yet, a reusable card that only accept content through HTML template slots. &lt;/p&gt;

&lt;p&gt;To update the &lt;code&gt;Card&lt;/code&gt; open packages/client/src/component/card/Card.ts. Just like in previous examples, cut and paste the template that is declared imperatively in the &lt;code&gt;constructor&lt;/code&gt; to a template string returned by a new &lt;code&gt;function&lt;/code&gt; named &lt;code&gt;shadowTemplate&lt;/code&gt;. Add a single argument to this &lt;code&gt;function&lt;/code&gt; that allows you to pass in the styling for the template. Notice how the header, content, footer layout of each &lt;code&gt;Card&lt;/code&gt; accepts a named HTML template slot.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;shadowTemplate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;styles&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="s2"&gt;`
  &amp;lt;style&amp;gt;
    &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;styles&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;
  &amp;lt;/style&amp;gt;
  &amp;lt;header&amp;gt;
    &amp;lt;slot name="header"&amp;gt;&amp;lt;/slot&amp;gt;
  &amp;lt;/header&amp;gt;
  &amp;lt;section&amp;gt;
    &amp;lt;slot name="content"&amp;gt;&amp;lt;/slot&amp;gt;
  &amp;lt;/section&amp;gt;
  &amp;lt;footer&amp;gt;
    &amp;lt;slot name="footer"&amp;gt;&amp;lt;/slot&amp;gt;
  &amp;lt;/footer&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;Set the &lt;code&gt;innerHTML&lt;/code&gt; in the custom element &lt;code&gt;constructor&lt;/code&gt; to the new &lt;code&gt;shadowTemplate function&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;template&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="nf"&gt;shadowTemplate&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;styles&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Link in the example of &lt;code&gt;Header&lt;/code&gt;, &lt;code&gt;Card&lt;/code&gt; is a leaf-node, so we can reuse the &lt;code&gt;shadowTemplate&lt;/code&gt; wholesale within the Declarative Shadow DOM template. Make a new &lt;code&gt;function&lt;/code&gt; named &lt;code&gt;template&lt;/code&gt; that passes in a single argument and encapsulate the &lt;code&gt;shadowTemplate&lt;/code&gt; with the proper syntax.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;template&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;styles&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="s2"&gt;`&amp;lt;app-card&amp;gt;
  &amp;lt;template shadowrootmode="open"&amp;gt; 
  &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nf"&gt;shadowTemplate&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;styles&lt;/span&gt; &lt;span class="p"&gt;})}&lt;/span&gt;&lt;span class="s2"&gt;
  &amp;lt;/template&amp;gt;
&amp;lt;/app-card&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;Cards should display a thumbnail, the blog post title, a short description, and link. Declare new properties on the first argument of the &lt;code&gt;template function&lt;/code&gt; that account for &lt;code&gt;content&lt;/code&gt;, &lt;code&gt;headline&lt;/code&gt;, &lt;code&gt;link&lt;/code&gt;, and &lt;code&gt;thumbnail&lt;/code&gt;, in addition to &lt;code&gt;styles&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;We can still project content into the slots in a Declarative Shadow DOM template. Shadow DOM is contained within the &lt;code&gt;shadowTemplate&lt;/code&gt; function. Whatever elements are placed outside of the &lt;code&gt;&amp;lt;template&amp;gt;&lt;/code&gt; are considered Light DOM and can be projected through the slots using the &lt;code&gt;slot&lt;/code&gt; attribute. &lt;/p&gt;

&lt;p&gt;For each of the properties on the model, make a new element. A new &lt;code&gt;&amp;lt;img&amp;gt;&lt;/code&gt; tag with a &lt;code&gt;slot&lt;/code&gt; attribute set to &lt;code&gt;header&lt;/code&gt; will project into the custom element's &lt;code&gt;&amp;lt;header&amp;gt;&lt;/code&gt;. Set the &lt;code&gt;src&lt;/code&gt; attribute to an interpolated &lt;code&gt;${thumbnail}&lt;/code&gt; and the &lt;code&gt;alt&lt;/code&gt; to an interpolated &lt;code&gt;${content}&lt;/code&gt;, to describe the image for screen readers. Additionally define a &lt;code&gt;&amp;lt;h2&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;p&amp;gt;&lt;/code&gt;. If any part of the template becomes too complex, you can wrap the entire template partial in string interpolation. This is the case with setting the &lt;code&gt;href&lt;/code&gt; attribute on the &lt;code&gt;&amp;lt;a&amp;gt;&lt;/code&gt; tag, which displays a "Read Post" link for the user.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;template&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;headline&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;link&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;thumbnail&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;styles&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="s2"&gt;`&amp;lt;app-card&amp;gt;
  &amp;lt;template shadowrootmode="open"&amp;gt; &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nf"&gt;shadowTemplate&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;styles&lt;/span&gt; &lt;span class="p"&gt;})}&lt;/span&gt;&lt;span class="s2"&gt; &amp;lt;/template&amp;gt;
  &amp;lt;img slot="header" src="&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;thumbnail&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;content&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;" /&amp;gt;
  &amp;lt;h2 slot="header"&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;headline&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;/h2&amp;gt;
  &amp;lt;p slot="content"&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;/p&amp;gt;
  &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="s2"&gt;`&amp;lt;a href="/post/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;link&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;" slot="footer"&amp;gt;Read Post&amp;lt;/a&amp;gt;`&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;
&amp;lt;/app-card&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;Since each element utilizes a named HTML template slot that matches a slot found in Shadow DOM for this custom element, each element will be project into Shadow DOM. If two elements share a named slot, they will be projected in the order they are defined.&lt;/p&gt;

&lt;p&gt;Finally, export &lt;code&gt;styles&lt;/code&gt; and &lt;code&gt;template&lt;/code&gt; from the file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;styles&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;template&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;AppCard&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We'll switch our attention now to packages/client/src/view/main/index.ts where we need to update the Declarative Shadow DOM template to accept the new &lt;code&gt;template&lt;/code&gt; exported from Card.ts.&lt;/p&gt;

&lt;h3&gt;
  
  
  Fetching the Data Model
&lt;/h3&gt;

&lt;p&gt;Views in modern websites are rarely static. Usually a view must be populated with content from a database. In our project, data is stored in markdown files for each blog post. Either way, a REST API could be used to fetch data from an endpoint and populate the view with content. In this next section, we'll develop a pattern for fetching data from a REST API and integrating the resulting JSON with the middleware you coded in a pervious section. &lt;/p&gt;

&lt;p&gt;You'll inject the data from two endpoints into the &lt;code&gt;MainView&lt;/code&gt; Declarative Shadow DOM template. JSON returned from '&lt;a href="http://localhost:4444/api/meta" rel="noopener noreferrer"&gt;http://localhost:4444/api/meta&lt;/a&gt; can be used to render &lt;code&gt;AppHeader&lt;/code&gt;, while the &lt;code&gt;Array&lt;/code&gt; of &lt;code&gt;Post&lt;/code&gt; returned from &lt;a href="http://localhost:4444/api/posts" rel="noopener noreferrer"&gt;http://localhost:4444/api/posts&lt;/a&gt; will be used to render a list of &lt;code&gt;AppCard&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Open packages/client/src/view/main/index.ts to begin coding this section. &lt;/p&gt;

&lt;p&gt;Just like we did with &lt;code&gt;template&lt;/code&gt;, we are standardizing a new pattern. This time for declaring asynchronous API calls that fetch data for the purpose of injecting that data into each Declarative Shadow DOM template. Essentially, we're talking about the "model" for the "view", so we'll call the new &lt;code&gt;function&lt;/code&gt; "fetchModel". &lt;/p&gt;

&lt;p&gt;This &lt;code&gt;function&lt;/code&gt; should return a &lt;code&gt;Promise&lt;/code&gt;. With TypeScript, we can strictly type define the &lt;code&gt;Promise&lt;/code&gt; and match that definition with the first argument of &lt;code&gt;template&lt;/code&gt;. Eventually this data model will get passed to &lt;code&gt;template&lt;/code&gt;, enabling us to hydrate the view with content.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;fetchModel&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;DataModel&lt;/span&gt;&lt;span class="o"&gt;&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 typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;template&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;DataModel&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This may seem disconnected at first, because nowhere in index.ts do these two &lt;code&gt;function&lt;/code&gt; work together. In the Express middleware we have access to exports from the bundle, so if we &lt;code&gt;export fetchModel&lt;/code&gt;, we can also call the &lt;code&gt;function&lt;/code&gt; in the context of the middleware and pass the result to &lt;code&gt;template&lt;/code&gt;, which expects the same &lt;code&gt;DataModel&lt;/code&gt;. This is why we need a standardized pattern!&lt;/p&gt;

&lt;p&gt;An implementation of &lt;code&gt;fetchModel&lt;/code&gt; for &lt;code&gt;MainView&lt;/code&gt; would be as follows. Use &lt;code&gt;Promise.all&lt;/code&gt; to call each endpoint with &lt;code&gt;fetch&lt;/code&gt;, then map each response to the JSON returned from the endpoints, and finally call &lt;code&gt;then&lt;/code&gt; again to map the resulting JSON to the schema expected in &lt;code&gt;DataModel&lt;/code&gt;. The JSON response from &lt;a href="http://localhost:4444/api/meta" rel="noopener noreferrer"&gt;http://localhost:4444/api/meta&lt;/a&gt; will be used to populate &lt;code&gt;AppHeader&lt;/code&gt;, while &lt;a href="http://localhost:4444/api/posts" rel="noopener noreferrer"&gt;http://localhost:4444/api/posts&lt;/a&gt; will be used to populate content for a list of &lt;code&gt;AppCard&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;fetchModel&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;DataModel&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
    &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;http://localhost:4444/api/meta&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;http://localhost:4444/api/posts&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="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;responses&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;responses&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;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;())))&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;jsonResponses&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;meta&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;jsonResponses&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;posts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;jsonResponses&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;posts&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;meta&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nx"&gt;posts&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;Update &lt;code&gt;template&lt;/code&gt; with a new argument named &lt;code&gt;data&lt;/code&gt; and type define it as &lt;code&gt;DataModel&lt;/code&gt;. Update the &lt;code&gt;appHeaderTemplate title&lt;/code&gt; with the &lt;code&gt;title&lt;/code&gt; accessed by &lt;code&gt;data.meta.title&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;template&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;DataModel&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="s2"&gt;`&amp;lt;main-view&amp;gt;
  &amp;lt;template shadowrootmode="open"&amp;gt;
    &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nf"&gt;appHeaderTemplate&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;styles&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;appHeaderStyles&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;data&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;title&lt;/span&gt; &lt;span class="p"&gt;})}&lt;/span&gt;&lt;span class="s2"&gt;
  &amp;lt;/template&amp;gt;
  &amp;lt;/main-view&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 need to integrate the cards that display a preview of each blog post. Import &lt;code&gt;AppCard&lt;/code&gt;, &lt;code&gt;styles&lt;/code&gt; and &lt;code&gt;template&lt;/code&gt; from Card.js, being sure to rename imports where appropriate so they don't clash with any local variables.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;styles&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;appCardStyles&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;template&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;appCardTemplate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;AppCard&lt;/span&gt;&lt;span class="p"&gt;,&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;../../component/card/Card.js&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;Integrate &lt;code&gt;appCardTemplate&lt;/code&gt; into the &lt;code&gt;template function&lt;/code&gt;, mapping the &lt;code&gt;Array&lt;/code&gt; found at &lt;code&gt;data.posts&lt;/code&gt; to the model necessary to display each &lt;code&gt;Card&lt;/code&gt;. You need to convert the &lt;code&gt;Array&lt;/code&gt; to a &lt;code&gt;String&lt;/code&gt;, so call &lt;code&gt;join&lt;/code&gt; with an empty &lt;code&gt;String&lt;/code&gt; as the argument to convert the &lt;code&gt;Array&lt;/code&gt; to a &lt;code&gt;String&lt;/code&gt;. Optionally, use the utility named &lt;code&gt;joinTemplates&lt;/code&gt; imported into the file instead of calling &lt;code&gt;join&lt;/code&gt; directly. Also inject the styles for each &lt;code&gt;Card&lt;/code&gt; here.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;template&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;DataModel&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="s2"&gt;`&amp;lt;main-view&amp;gt;
  &amp;lt;template shadowrootmode="open"&amp;gt;
    &amp;lt;style&amp;gt;
      &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;styles&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;
    &amp;lt;/style&amp;gt;
    &amp;lt;div class="post-container"&amp;gt;
    &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nf"&gt;appHeaderTemplate&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;styles&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;appHeaderStyles&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;data&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;title&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;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;posts&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="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="o"&gt;=&amp;gt;&lt;/span&gt;
          &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nf"&gt;appCardTemplate&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
            &lt;span class="na"&gt;styles&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;appCardStyles&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;headline&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;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;excerpt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;thumbnail&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;thumbnail&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;link&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;slug&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="p"&gt;})}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;
      &lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="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;/div&amp;gt;
  &amp;lt;/template&amp;gt;
  &amp;lt;/main-view&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;Export all of the following from index.ts.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;template&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;fetchModel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;AppCard&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;AppHeader&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;MainView&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Handling fetchModel in Middleware
&lt;/h3&gt;

&lt;p&gt;To integrate the &lt;code&gt;fetchModel&lt;/code&gt; exported from the bundle into the middleware, open packages/server/src/middleware/ssr.ts.&lt;/p&gt;

&lt;p&gt;At the top of the middleware &lt;code&gt;function&lt;/code&gt; declare a new variable named &lt;code&gt;fetchedData&lt;/code&gt;. Declare a &lt;code&gt;let&lt;/code&gt; here because sometimes &lt;code&gt;fetchedData&lt;/code&gt; may be populated with a &lt;code&gt;function&lt;/code&gt;, for static routes, possibly not.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;fetchedData&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After the line where we import the bundle, check if the ES2015 module exports &lt;code&gt;fetchModel&lt;/code&gt; with a conditional expression. If truthy, set &lt;code&gt;fetchedData&lt;/code&gt; to the result of &lt;code&gt;module.fetchModel()&lt;/code&gt; using an &lt;code&gt;await&lt;/code&gt; because &lt;code&gt;fetchModel&lt;/code&gt; returns a &lt;code&gt;Promise&lt;/code&gt;. We don't necessarily want to make &lt;code&gt;fetchModel&lt;/code&gt; required for any hypothetical static layouts. &lt;/p&gt;

&lt;p&gt;Anticipating the next route that displays a single post, pass in the &lt;code&gt;route&lt;/code&gt; to &lt;code&gt;fetchModel&lt;/code&gt;. Our existing implementation will effectively ignore this argument, but we need information on the route.params in the next example. Finally, pass &lt;code&gt;fetchedData&lt;/code&gt; into the call for &lt;code&gt;module.template&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="kr"&gt;module&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;clientPath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dist&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;route&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="kr"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fetchModel&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;fetchedData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="kr"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetchModel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;route&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;compiledTemplate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kr"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;template&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fetchedData&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should now be able to view a server-side rendered header and list of cards at &lt;a href="http://localhost:4444" rel="noopener noreferrer"&gt;http://localhost:4444&lt;/a&gt;. This view is rendered entirely server-side. Network requests are made server-side, the Declarative Shadow DOM template is constructed and then iterated upon by @lit-labs/ssr &lt;code&gt;render Generator&lt;/code&gt;. Next, you'll take what you've learned and render a single post view using the same methods.&lt;/p&gt;

&lt;h3&gt;
  
  
  Rendering A Single Post
&lt;/h3&gt;

&lt;p&gt;If you click on any "Read More" links you'll be greeted with a rather unimpressive layout. A blank "Author:" field and a hand pointing "Back" should greet you. This is the result of the boilerplate we worked on earlier.&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%2Fz0kylxxbm0zig41z0dqc.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%2Fz0kylxxbm0zig41z0dqc.png" alt="Image description" width="800" height="471"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Upon navigation to this view, you should notice the Terminal update with a different &lt;code&gt;Route&lt;/code&gt; if you still have the &lt;code&gt;console.log&lt;/code&gt; enabled.&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%2F0wgbbhzpajcuvfyfb1db.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%2F0wgbbhzpajcuvfyfb1db.png" alt="Image description" width="618" height="204"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In this section we're going to populate the single post view with the content of a blog post. Each blog post is written in markdown and stored in the packages/server/data/posts directory. Each post can be accessed via a REST API endpoint at &lt;a href="http://localhost:4444/api/post/:slug" rel="noopener noreferrer"&gt;http://localhost:4444/api/post/:slug&lt;/a&gt;. If there is a post with a matching "slug", the endpoint returns the blog post in JSON, with the markdown found on the JSON response. &lt;/p&gt;

&lt;p&gt;Open packages/client/src/view/post/index.ts to begin coding against the single post view.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;styles&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;appHeaderStyles&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;template&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;appHeaderTemplate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;AppHeader&lt;/span&gt;&lt;span class="p"&gt;,&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;../../component/header/Header.js&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;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;template&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;DataModel&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="s2"&gt;`&amp;lt;post-view&amp;gt;
  &amp;lt;template shadowrootmode="open"&amp;gt;
    &amp;lt;style&amp;gt;
      &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;styles&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;
    &amp;lt;/style&amp;gt;
    &amp;lt;div class="post-container"&amp;gt;
      &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nf"&gt;appHeaderTemplate&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;styles&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;appHeaderStyles&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;data&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;title&lt;/span&gt; &lt;span class="p"&gt;})}&lt;/span&gt;&lt;span class="s2"&gt;
      &amp;lt;div class="post-content"&amp;gt;
        &amp;lt;h2&amp;gt;Author: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;author&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;/h2&amp;gt;
        &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;
        &amp;lt;footer&amp;gt;
          &amp;lt;a href="/"&amp;gt;👈 Back&amp;lt;/a&amp;gt;
        &amp;lt;/footer&amp;gt;
      &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
  &amp;lt;/template&amp;gt;
&amp;lt;/post-view&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 can take the &lt;code&gt;fetchModel function&lt;/code&gt; from the main view and modify it for the single post view. Copy and paste the function from main/index.ts to post/index.ts, modifying it to accept a new argument. Remember when we passed the &lt;code&gt;route&lt;/code&gt; to &lt;code&gt;fetchModel&lt;/code&gt; in the middleware? This is why. We need the &lt;code&gt;slug&lt;/code&gt; property found on &lt;code&gt;route.params&lt;/code&gt; to make the request to &lt;a href="http://localhost:4444/api/post/:slug" rel="noopener noreferrer"&gt;http://localhost:4444/api/post/:slug&lt;/a&gt;. Modify the &lt;code&gt;fetchModel function&lt;/code&gt; until you get a working example, like below.&lt;/p&gt;

&lt;p&gt;After we receive the markdown, convert the markdown into useable HTML. The GitHub API is provided in this file to help with this purpose. The blog posts contain code snippets and &lt;code&gt;Octokit&lt;/code&gt; is a convenient utility for parsing the markdown and converting it into HTML. Set a new &lt;code&gt;const&lt;/code&gt; named &lt;code&gt;request&lt;/code&gt; with the response from both local API endpoints and then &lt;code&gt;await&lt;/code&gt; another HTTP request to the OctoKit API by calling &lt;code&gt;octokit.request&lt;/code&gt;. We want to make a &lt;code&gt;POST&lt;/code&gt; request to the &lt;code&gt;/markdown&lt;/code&gt; endpoint, passing in the &lt;code&gt;request.post.content&lt;/code&gt; to the &lt;code&gt;text&lt;/code&gt; property on the body of the request, while making sure to specific the API version in the &lt;code&gt;headers&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;fetchModel&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt; &lt;span class="p"&gt;}):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;any&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;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="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;request&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
      &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;http://localhost:4444/api/meta&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`http://localhost:4444/api/post/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;slug&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;])&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;responses&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;responses&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;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;())))&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;jsonResponses&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;jsonResponses&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;post&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;jsonResponses&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;post&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="nx"&gt;postContentTemplate&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;octokit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST /markdown&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;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;request&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;content&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;X-GitHub-Api-Version&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;2022-11-28&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;html&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;postContentTemplate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;res&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;&lt;code&gt;postContentTemplate&lt;/code&gt; will return the converted Markdown into HTML via the &lt;code&gt;data&lt;/code&gt; property. Return that in the &lt;code&gt;fetchModel function&lt;/code&gt;, along with the &lt;code&gt;meta&lt;/code&gt; and &lt;code&gt;post&lt;/code&gt; on a new property named &lt;code&gt;html&lt;/code&gt;. When you are finished, export all the relevant parts for the middleware.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;template&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;fetchModel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;AppHeader&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;PostView&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you refresh or navigate back to /, then click "Read More" on any card, you now should be able to view a single blog post. You just completed your second server-side rendered view with Declarative Shadow DOM! This is the last view of the workshop. Remaining sections will cover additional content for handling SEO, hydration, and more.&lt;/p&gt;

&lt;h3&gt;
  
  
  Handling Metadata for SEO
&lt;/h3&gt;

&lt;p&gt;In this particular scenario, a lot can be gleaned from the blog post content for SEO. Each markdown file has a header which contains metadata that could be used for SEO purposes.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;---
title: "Form-associated custom elements FTW!"
slug: form-associated-custom-elements-ftw
thumbnail: /assets/form-associated.jpg
author: Myself
excerpt: Form-associated custom elements is a web specification that allows engineers to code custom form controls that report value and validity to `HTMLFormElement`...
---
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This header gets transformed by the &lt;a href="http://localhost:4444/api/post" rel="noopener noreferrer"&gt;http://localhost:4444/api/post&lt;/a&gt; API endpoint into JSON using the matter package. You can review for yourself in packages/server/src/route/post.ts. &lt;/p&gt;

&lt;p&gt;You could expand upon this content, delivering relevant metadata for JSON-LD or setting &lt;code&gt;&amp;lt;meta&amp;gt;&lt;/code&gt; tags in the HTML document. For the purposes of this workshop, we'll only set one aspect of the &lt;code&gt;document.head&lt;/code&gt;, the page &lt;code&gt;&amp;lt;title&amp;gt;&lt;/code&gt;, but you can extrapolate on this further and modify &lt;code&gt;renderApp&lt;/code&gt; to project specifications.&lt;/p&gt;

&lt;p&gt;Open packages/server/src/middleware/ssr.ts to get started for coding for this section and navigate to the middleware &lt;code&gt;function&lt;/code&gt;. Some point before the multiple &lt;code&gt;await&lt;/code&gt;, we can pass relevant SEO metadata to the &lt;code&gt;Route&lt;/code&gt;. If we wanted to override the &lt;code&gt;route.title&lt;/code&gt; with the title of each blog post, we could do that by setting the property with the value returned from &lt;code&gt;fetchedData.meta.title&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;  &lt;span class="nx"&gt;route&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="nx"&gt;route&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="nx"&gt;route&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="nx"&gt;fetchedData&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;title&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pass &lt;code&gt;route&lt;/code&gt; into the &lt;code&gt;renderApp function&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ssrResult&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;renderApp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;template&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;route&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Set the &lt;code&gt;&amp;lt;title&amp;gt;&lt;/code&gt; tag with the &lt;code&gt;route.title&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nf"&gt;renderApp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;template&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;route&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="s2"&gt;`&amp;lt;!DOCTYPE html&amp;gt;
  &amp;lt;html lang="en"&amp;gt;
    &amp;lt;head&amp;gt;
        &amp;lt;title&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;route&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;/title&amp;gt;
   ...
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Wherever you derive SEO metadata could vary per project, but in our case we can store this metadata on each markdown file. Since we have repeatable patterns like &lt;code&gt;Route&lt;/code&gt; and &lt;code&gt;fetchModel&lt;/code&gt;, we can reliably deliver metadata to each HTML document server-side rendered in the middleware.&lt;/p&gt;

&lt;h3&gt;
  
  
  Hydration
&lt;/h3&gt;

&lt;p&gt;So far all the coding you've done has been completely server-side. Any JavaScript that ran happened on the server and each Declarative Shadow DOM template was parsed by the browser. If there's any other JavaScript in the custom element that needs to run in the browser, like binding a callback to a click listener, that isn't happening yet. We need to hydrate the custom elements.&lt;/p&gt;

&lt;p&gt;There's an easy and performant fix. We could host the bundle for each route locally and add a script tag to the HTML document that makes the request, but a more performant solution would be to inline the JavaScript, eliminating the network request entirely. &lt;/p&gt;

&lt;p&gt;&lt;code&gt;readFileSync&lt;/code&gt; is a Node.js utility that allows us to read the bundle from a file and convert the content to a &lt;code&gt;String&lt;/code&gt;. Set the &lt;code&gt;String&lt;/code&gt; to a new &lt;code&gt;const&lt;/code&gt; named &lt;code&gt;script&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="kr"&gt;module&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;clientPath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dist&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;route&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;script&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;readFileSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;clientPath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dist&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;route&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pass the &lt;code&gt;script&lt;/code&gt; to the &lt;code&gt;renderApp function&lt;/code&gt; in a third argument.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ssrResult&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;renderApp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;template&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;route&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;script&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add a &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; tag to the end of the HTML document, setting the content with the &lt;code&gt;String&lt;/code&gt; named &lt;code&gt;script&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nf"&gt;renderApp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;route&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;template&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;script&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="s2"&gt;`&amp;lt;!DOCTYPE html&amp;gt;
  &amp;lt;html lang="en"&amp;gt;
    &amp;lt;head&amp;gt;
        &amp;lt;title&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;route&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;/title&amp;gt;
        &amp;lt;meta name="viewport" content="width=device-width, initial-scale=1"&amp;gt;
        &amp;lt;meta name="description" content="Web Components Blog"&amp;gt;
        &amp;lt;style rel="stylesheet" type="text/css"&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;styles&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;/style&amp;gt;
    &amp;lt;/head&amp;gt;
    &amp;lt;body&amp;gt; 
      &amp;lt;div id="root"&amp;gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;yield&lt;/span&gt;&lt;span class="o"&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;template&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="s2"&gt;`&amp;lt;/div&amp;gt;
    &amp;lt;script type="module"&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;script&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;/script&amp;gt;
  &amp;lt;/body&amp;gt;&amp;lt;/html&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;We can test hydration is now available in &lt;code&gt;AppHeader&lt;/code&gt;. Open&lt;br&gt;
packages/client/src/component/header/Header.ts. &lt;/p&gt;

&lt;p&gt;Add an &lt;code&gt;else&lt;/code&gt; statement to the conditional already defined in the &lt;code&gt;constructor&lt;/code&gt;. This conditional &lt;code&gt;if (!this.shadowRoot)&lt;/code&gt; is checking if a shadow root doesn't exist and if truthy, imperatively declares a new shadow root. Since the custom element has a template declared with Declarative Shadow DOM, the content inside the &lt;code&gt;if&lt;/code&gt; won't run. We can use an &lt;code&gt;else&lt;/code&gt; to run additional logic, client-side only. Modify the &lt;code&gt;&amp;lt;h1&amp;gt;&lt;/code&gt; that already exists (because it was server-side rendered) by appending &lt;code&gt;Hydrated&lt;/code&gt; to the element &lt;code&gt;innerText&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AppHeader&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;HTMLElement&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;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;shadowRoot&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;shadowRoot&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;attachShadow&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;mode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;open&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;template&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;createElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;template&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nx"&gt;template&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="nf"&gt;shadowTemplate&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;styles&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="nx"&gt;shadowRoot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;appendChild&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;template&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cloneNode&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="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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;title&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;shadowRoot&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;h1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;innerText&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;shadowRoot&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;h1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;innerText&lt;/span&gt; &lt;span class="o"&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;title&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; Hydrated`&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;Remove the &lt;code&gt;else&lt;/code&gt; once your test is complete. This section demoed how to hydrate custom elements client-side, but how does the server understand any of this JavaScript? Some of you maybe wondering how did the server interpret &lt;code&gt;class AppHeader extends HTMLElement&lt;/code&gt; when &lt;code&gt;HTMLElement&lt;/code&gt; doesn't exist in Node.js. This final section is an explainer of how this works with @lit-labs/ssr. Coding is complete. Congratulations! You've finished the workshop.&lt;/p&gt;

&lt;h3&gt;
  
  
  Shimming Browser Spec
&lt;/h3&gt;

&lt;p&gt;Lit shims browser specifications it needs to run on the server via a &lt;code&gt;function&lt;/code&gt; exported from @lit-labs/ssr/lib/dom-shim.js named &lt;code&gt;installWindowOnGlobal&lt;/code&gt;. If all your project relies on is autonomous custom elements extended from &lt;code&gt;HTMLElement&lt;/code&gt; you can get by just with calling this &lt;code&gt;function&lt;/code&gt; prior to anything else in Node.js. &lt;/p&gt;

&lt;p&gt;A custom implementation of the shim is found at packages/shim/src/index.ts. I used this custom shim for the chapters in the book Fullstack Web Components because the views we server-side render in the book contain Customized built-in elements, which are not shimmed by default. Lit was thoughtful enough to allow engineers to extend the shim with other mocks necessary to server-side render browser specifications. Use this file as an example of how you could shim browser spec in your server-side rendered project.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;installShimOnGlobal&lt;/code&gt; is exported from this package in the monorepo and imported into packages/server/src/index.ts where it's called before any other code.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;installShimOnGlobal&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;../../shim/dist/index.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nf"&gt;installShimOnGlobal&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It's necessary to shim browser specifications before any other code runs in Node.js. That's why the shim is executed first.&lt;/p&gt;

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

&lt;p&gt;In this workshop, you server-side rendered a blog using Declarative Shadow DOM and the @lit-labs/ssr package with Express middleware. You took autonomous custom elements that declared a shadow root imperatively and made reusable templates that also support Declarative Shadow DOM. Declarative Shadow DOM is a standard that allows web developers to encapsulate styling and template with a special HTML template, declaratively. The "special" part of the HTML template is the element must use the &lt;code&gt;shadowrootmode&lt;/code&gt; attribute. &lt;/p&gt;

&lt;p&gt;You learned how to use the &lt;code&gt;render Generator&lt;/code&gt; exported from @lit-labs/ssr to server-side render template partials in a HTML document. We only rendered one template, but you could take what you learned to render multiple template partials, depending on the needs of the project.&lt;/p&gt;

&lt;p&gt;The usage of a static configuration for each &lt;code&gt;Route&lt;/code&gt;, exporting &lt;code&gt;template&lt;/code&gt; and &lt;code&gt;fetchModel&lt;/code&gt; from each view bundle were opinions. This is just one way of working. The main takeaway is for server-side rendering, you should standardize portions of the system to streamline development and ease with integration.&lt;/p&gt;

&lt;p&gt;We didn't cover the entire build system in this workshop. Under the hood, nodemon was watching for changes while esbuild is responsible for building for production. If you build for production, you will find the output is 100% minified and scores 100% for performance in Lighthouse. Someone could streamline development further with Vite, the main benefit one would gain is hot module reloading.&lt;/p&gt;

&lt;p&gt;We also didn't cover how to use @lit-labs/ssr with LitElement and that was on purpose. Lit is a wonderful library. Use it at your discretion. I find it much more interesting that Lit, because it is built from browser specifications, can operate with browser spec like Declarative Shadow DOM without coding directly with Lit. I hope you find this interesting as well. There is another aspect to this I would like to highlight.&lt;/p&gt;

&lt;p&gt;When I started in web development in the 90s, I appreciated the egalitarian nature of HTML and JavaScript. Anyone could code a website, like anyone could read and write a book. Over time, front end web development has gotten way too complicated. Frameworks and libraries that sought to simplify developer experience did so at the cost of user experience, while also making the barrier to entry far greater for anyone wanting to learn how to code a site. I demonstrated how to server-side render a website using only browser standards and leveraging a few tools from Lit. Anyone can learn how to code Declarative Shadow DOM, just like decades ago anyone could learn how to code HTML. &lt;/p&gt;

&lt;p&gt;I hope you found it easy to accomplish server-side rendering with Declarative Shadow DOM and can't wait to see what you build. Comment below with a link so we can see those 100% performance scores in Lighthouse.&lt;/p&gt;

&lt;p&gt;If you liked this tutorial, you will find many more like it in my book about Web Components.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fullstack Web Components
&lt;/h2&gt;

&lt;p&gt;Are you looking to code Web Components now, but don't know where to get started? I wrote a book titled &lt;a href="https://www.newline.co/courses/fullstack-web-components?utm_source=dev.to"&gt;Fullstack Web Components&lt;/a&gt;, a hands-on guide to coding UI libraries and web applications with custom elements. In Fullstack Web Components, you'll...&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Code several components using autonomous, customized built-in, and form-associated custom elements, Shadow DOM, HTML templates, CSS variables, and Declarative Shadow DOM&lt;/li&gt;
&lt;li&gt;Develop a micro-library with TypeScript decorators that streamlines UI component development&lt;/li&gt;
&lt;li&gt;Learn best practices for maintaining a UI library of Web Components with Storybook&lt;/li&gt;
&lt;li&gt;Code an application using Web Components and TypeScript&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://www.newline.co/courses/fullstack-web-components?utm_source=dev.to"&gt;Fullstack Web Components is available now at newline.co&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%2Fjbdgc4z2i84e2xailhsv.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%2Fjbdgc4z2i84e2xailhsv.png" alt="Fullstack Web Components Book Cover" width="800" height="1004"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webcomponents</category>
      <category>html</category>
      <category>css</category>
      <category>javascript</category>
    </item>
    <item>
      <title>The Problem With React</title>
      <dc:creator>Stephen Belovarich</dc:creator>
      <pubDate>Fri, 17 Mar 2023 16:23:48 +0000</pubDate>
      <link>https://dev.to/steveblue/the-problem-with-react-46mg</link>
      <guid>https://dev.to/steveblue/the-problem-with-react-46mg</guid>
      <description>&lt;p&gt;In this post I'll tell the other side of the story. The parts the React Documentary failed to mention.&lt;/p&gt;

&lt;p&gt;The recent React documentary was not a good look, but you wouldn't know it looking at the comments on Youtube. Some may refrain from posting anything critical, fearing retribution by members of the community. It's a real thing. Haters gonna hate.&lt;/p&gt;

&lt;p&gt;An ethical way to make a documentary is to be objective, but the narrator is clearly crafting a narrative that is biased toward React. Why do we even need a narrator? Are we caught in a cult of personality? The documentary is a warped take on reality. There is no counterpoint, a complete lack of diverse perspectives, so we don't get the complete story.&lt;/p&gt;

&lt;p&gt;Who am I to tell you anything about React? I'm not a React maintainer. I'm a web developer that used React in an enterprise org, someone who taught functional programming with React to others, implemented a large React UI library, and coded a React app. I've been developing for the web for over 20 years. At least I'm not begging you to subscribe to my Youtube channel.&lt;/p&gt;

&lt;p&gt;What's the problem with React?&lt;/p&gt;

&lt;h2&gt;
  
  
  Churn
&lt;/h2&gt;

&lt;p&gt;My time with React involved engineers migrating class components to functional, enzyme to react testing library, fixing issues with build configurations, tracking a multitude of dependencies, patching security exploits. Time that could have been spent coding features and enhancements wasted. The job of an engineer at a corporation isn't to merely maintain the codebase, our job is to deliver value for the business, to delivering features and bug fixes for customers.&lt;/p&gt;

&lt;p&gt;After many months of work, React finally upgraded their documentation. If you haven't already migrated your class components to functional, you soon will because &lt;a href="https://react.dev/reference/react/legacy"&gt;in the React docs&lt;/a&gt; class components are now deprecated and will be removed in a later version. This is just one example of the churn.&lt;/p&gt;

&lt;p&gt;Several developers first learned how to code React using the help of &lt;a href="https://create-react-app.dev"&gt;Create React App&lt;/a&gt;, but probably found they had to roll their own build with Rollup, Webpack or Vite while working at a corporation. There is no mention of how to bootstrap client-side React apps in the new React documentation. Create React App is gone from the docs, probably for good reason because the project was unmaintained. React &lt;a href="https://react.dev/learn/start-a-new-react-project"&gt;doesn't even recommend client-side development in the new docs&lt;/a&gt;, favoring several server-side implementations including Next.js, Gatsby, Expo, and React Server Components.&lt;/p&gt;

&lt;p&gt;If the recommendation is now all React development should be done with a meta framework, the problem with going all-in server-side is that some front end engineers at corporations have no control over the server. I was one of these engineers. No argument could pull Java away from the stack in favor of Node.js, an often more performant tool that could have even saved the corporation operational costs. React has effectively abandoned engineers like me. We have no guidance for bootstrapping a React app solely for the client.&lt;/p&gt;

&lt;p&gt;This is what happened to me after trying to convince others at a large enterprise organization to move some logic to the server.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://i.giphy.com/media/QVP7DawXZitKYg3AX5/giphy.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://i.giphy.com/media/QVP7DawXZitKYg3AX5/giphy.gif" alt="" width="400" height="222"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This problem of valuing DX over UX isn't unique to React, other libraries and frameworks suffer from the same thing. What's possibly unique to React is revealed by that famous quote from Mark Zuckerberg. "Move fast and break things." I'm sorry, but at this won't fly at other corporations. It's not like React even gets the DX right. DX is a hot mess, especially when multiple parts of the ecosystem are ever changing.&lt;/p&gt;

&lt;h2&gt;
  
  
  So What?
&lt;/h2&gt;

&lt;p&gt;Just because React is popular now doesn't mean it always will be. I think we already hit peak React, but haven't seen any conclusive analytical evidence to validate that, just a curve trending downward the last couple years in the State of JS survey.&lt;/p&gt;

&lt;p&gt;The job market is heavily React, but it won't always be this way. A decade ago, before React even gained popularity, KnockoutJS jobs were a rare opportunity in a sea of jQuery. It's happened before and it's happening again.&lt;/p&gt;

&lt;p&gt;There is a great sea change happening in web development. Several libraries are springing up to support UI component development with Web Components. Frameworks like Qwik are changing the game by removing hydration and moving a majority of work to the server. Change is happening now. React engineers rode a wild wave that is about to come crashing down.&lt;/p&gt;

&lt;p&gt;By the way, I realize jQuery is still around. I just dipped into a codebase that uses jQuery. React will most likely be around in a decade. I'm not saying React is going away entirely, I'm saying React in ten years will be regarded like jQuery is today. The two libraries have similar faults. jQuery’s faults were a lack of framework and failure to coordinate the open source community. React repeated similar mistakes and how everyone in the community has to cope with those mistakes. It’s time to start preparing because React is in decline. That’s not to say there won’t always be novel use cases for React: React Native or even using Virtual DOM to provide an XML like interface for complex libraries like three.js. React will still be around, but not nearly as popular as it is today.&lt;/p&gt;

&lt;h2&gt;
  
  
  How To Cope With Change
&lt;/h2&gt;

&lt;p&gt;If you hope to make it for a long time in this profession, it's time to start learning concepts outside of the React ecosystem. Make sure you learn the fundamentals that apply to every library and framework. That means HTML, CSS, and JavaScript. Go beyond functional programming to understand prototypical inheritance and Object-Oriented programming, which also have applications in front-end development. Take some time to learn new standards landing in the browsers. Learn TypeScript, because it's here to stay, whether as an extension of the language or at some point, types baked into the specification. Learn how servers work, http, WebSocket, and so forth. These fundamentals help you fix issues, contribute to open source, spur innovation.&lt;/p&gt;

&lt;p&gt;It's time to start thinking, "OK, how would I do that without React?"&lt;/p&gt;

&lt;p&gt;P.S. If you have had a great experience with React so far, I’m happy for you. This post is more about how engineering decisions effect a business. This isn’t an attack on your choice of using React. On the contrary, this is a warning. The ecosystem is too brittle to survive, causes too much churn for businesses that have invested in it. React is the victim of it’s own eventual demise, much like jQuery. It’s coming, so be prepared. If I’m wrong in ten years, I’ll admit I’m wrong, but have a hard time believing history won’t repeat itself with so many signs it already is.&lt;/p&gt;

</description>
      <category>react</category>
      <category>webdev</category>
      <category>javascript</category>
    </item>
    <item>
      <title>I Asked ChatGPT to Code Web Components and This is What Happened</title>
      <dc:creator>Stephen Belovarich</dc:creator>
      <pubDate>Wed, 18 Jan 2023 08:00:00 +0000</pubDate>
      <link>https://dev.to/steveblue/i-asked-chatgpt-to-code-web-components-and-this-is-what-happened-13a1</link>
      <guid>https://dev.to/steveblue/i-asked-chatgpt-to-code-web-components-and-this-is-what-happened-13a1</guid>
      <description>&lt;p&gt;The past couple months and I've heard of web engineers tinkering with ChatGPT while another engineer told me they relied on the output for the basis of every coding decision at work. I found myself in the first camp, poking at ChatGPT to generate anything from code examples to blog outlines, ideas for novels, or generative artwork. I was genuinely curious. A.I. will write the code for me? You mean I don't have to think? Sign me up.&lt;/p&gt;

&lt;p&gt;I was primarily interested if ChatGPT could be used to generate anything close to production-ready code. I had talked with an engineer using ChatGPT as a basis for coding anything at their job. I wanted to understand what that experience was like. Granted, I'm a ChatGPT novice, so this article won't get overly technical about using ChatGPT by any means. I highly doubt I unlocked the tool's full potential. &lt;/p&gt;

&lt;p&gt;I'm a front-end web engineer that codes Web Components and have coded applications with several JavaScript frameworks. I asked ChatGPT to generate some Web Components, because that's what I know extremely well. I wanted to see how precise ChatGPT could produce a simple view comprised of Web Components. I wrote &lt;a href="https://www.newline.co/courses/fullstack-web-components" rel="noopener noreferrer"&gt;a book about Web Components&lt;/a&gt; recently, so I know first-hand how the code should be formatted. &lt;/p&gt;

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

&lt;p&gt;I logged into &lt;a href="https://chat.openai.com" rel="noopener noreferrer"&gt;https://chat.openai.com&lt;/a&gt; and prompted ChatGPT with a simple request to generate an application login form with Web Components.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg39437ydct7uxza4ernt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg39437ydct7uxza4ernt.png" alt="Image description" width="800" height="152"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;ChatGPT responded with the following code snippet that used the popular Web Components library Lit. I didn't specifically ask for ChatGPT to use a JavaScript library, but here we are. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl3z6az11klh8cx1tdcg7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl3z6az11klh8cx1tdcg7.png" alt="Image description" width="800" height="118"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;template&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;form&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;label&lt;/span&gt; &lt;span class="na"&gt;for=&lt;/span&gt;&lt;span class="s"&gt;"username"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Username:&lt;span class="nt"&gt;&amp;lt;/label&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;"text"&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"username"&lt;/span&gt; &lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="na"&gt;input=&lt;/span&gt;&lt;span class="s"&gt;"${this._onUsernameInput}"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;br&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;label&lt;/span&gt; &lt;span class="na"&gt;for=&lt;/span&gt;&lt;span class="s"&gt;"password"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Password:&lt;span class="nt"&gt;&amp;lt;/label&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;"password"&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"password"&lt;/span&gt; &lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="na"&gt;input=&lt;/span&gt;&lt;span class="s"&gt;"${this._onPasswordInput}"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;br&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="na"&gt;click=&lt;/span&gt;&lt;span class="s"&gt;"${this._onLoginButtonClick}"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Login&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/form&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/template&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
  &lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;LitElement&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="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;lit-element&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;LoginForm&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;LitElement&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;get&lt;/span&gt; &lt;span class="nf"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;username&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&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;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;username&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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;password&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="p"&gt;}&lt;/span&gt;

    &lt;span class="nf"&gt;_onUsernameInput&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="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;username&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;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;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nf"&gt;_onPasswordInput&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="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;password&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;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;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nf"&gt;_onLoginButtonClick&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// Send a login request with the username and password&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="s2"&gt;`Attempting to login with username: &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;username&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; and password: &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;password&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nf"&gt;render&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;form&amp;gt;
          &amp;lt;label for="username"&amp;gt;Username:&amp;lt;/label&amp;gt;
          &amp;lt;input type="text" id="username" @input="&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;_onUsernameInput&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&amp;gt;
          &amp;lt;br&amp;gt;
          &amp;lt;label for="password"&amp;gt;Password:&amp;lt;/label&amp;gt;
          &amp;lt;input type="password" id="password" @input="&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;_onPasswordInput&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&amp;gt;
          &amp;lt;br&amp;gt;
          &amp;lt;button @click="&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;_onLoginButtonClick&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&amp;gt;Login&amp;lt;/button&amp;gt;
        &amp;lt;/form&amp;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="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;login-form&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;LoginForm&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The duplicate templates surprised me and seem like an error. One declared as a HTML template, the other declared in the &lt;code&gt;render&lt;/code&gt; method. The HTML template seems superfluous. &lt;/p&gt;

&lt;p&gt;ChatGPT followed the code snippet with an explanation and example of how to implement the &lt;code&gt;LoginForm&lt;/code&gt; custom element defined in the &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt;. Most of that explanation describing how LitElement behaves is omitted here for sake of brevity. The last bit of the explanation was interesting. A callout the form needed further measures to address security, to keep the transmission of the password secure.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpnu4y8in7dx829s96ayz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpnu4y8in7dx829s96ayz.png" alt="Image description" width="800" height="198"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;ChatGPT generated a boilerplate example with Lit. It's interesting that was the first choice, given I hadn't prompted ChatGPT to generate any code with a library, just Web Components which are a set of browser specifications. &lt;/p&gt;

&lt;p&gt;I wasn't satisfied with the example using LitElement. Nothing against Lit. Lit is actually the first library I would adopt for Web Components that need to be developed at scale. I merely don't want any dependencies in a login view, which should be highly performant. Zero dependencies is a benefit of coding Web Components, especially for a view application like a login form which could be code split from the main bundle.&lt;/p&gt;

&lt;p&gt;I prompted ChatGPT to produce the same example but without a JavaScript library.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx8flt9pvn6twkzc5c2yr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx8flt9pvn6twkzc5c2yr.png" alt="Image description" width="800" height="164"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5uxgav0ov5d4kmbtgch5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5uxgav0ov5d4kmbtgch5.png" alt="Image description" width="800" height="119"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;template&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"login-form-template"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;form&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;label&lt;/span&gt; &lt;span class="na"&gt;for=&lt;/span&gt;&lt;span class="s"&gt;"username"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Username:&lt;span class="nt"&gt;&amp;lt;/label&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;"text"&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"username"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;br&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;label&lt;/span&gt; &lt;span class="na"&gt;for=&lt;/span&gt;&lt;span class="s"&gt;"password"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Password:&lt;span class="nt"&gt;&amp;lt;/label&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;"password"&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"password"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;br&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"login-button"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Login&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/form&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/template&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
  &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;LoginForm&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;HTMLElement&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="c1"&gt;// Attach the template to the element&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;template&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;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;login-form-template&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;templateContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;template&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;appendChild&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;templateContent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cloneNode&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="c1"&gt;// Get references to the form elements&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;_usernameInput&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;#username&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;_passwordInput&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;#password&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;_loginButton&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;#login-button&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

      &lt;span class="c1"&gt;// Add event listeners&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;_usernameInput&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;input&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;_onUsernameInput&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;_passwordInput&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;input&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;_onPasswordInput&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;_loginButton&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;click&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_onLoginButtonClick&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;_onUsernameInput&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="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;_username&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;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;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nf"&gt;_onPasswordInput&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="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;_password&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;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;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nf"&gt;_onLoginButtonClick&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// Send a login request with the username and password&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="s2"&gt;`Attempting to login with username: &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;_username&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; and password: &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;_password&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="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;login-form&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;LoginForm&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;The usage of HTML templates in this example make a lot more sense. &lt;code&gt;LoginForm&lt;/code&gt; references each HTML template to append the username and password inputs and the button to display a login form. Event listeners are attached to methods on the class that store the username and password as private properties, denoted by the underscore. A call to &lt;code&gt;customElements.define&lt;/code&gt; registers the component class with the browser.&lt;/p&gt;

&lt;p&gt;ChatGPT followed the code example with a similar explanation (omitted here).&lt;/p&gt;

&lt;p&gt;The above example is alright, although I wanted to follow best practices of making the input and button separate components from the login form. This promotes reusability. The input and button could be exported from a UI library. I asked ChatGPT to generate the same code but this time allowing for a separation of concerns.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4laz41jx786va0sn3kog.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4laz41jx786va0sn3kog.png" alt="Image description" width="800" height="241"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fi3wjpmcno4ybgjveb2q4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fi3wjpmcno4ybgjveb2q4.png" alt="Image description" width="800" height="148"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;ChatGPT produced three separate HTML templates and classes for the form, input, and button.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;template&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"login-form-template"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;form&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;login-input&lt;/span&gt; &lt;span class="na"&gt;label=&lt;/span&gt;&lt;span class="s"&gt;"Username"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/login-input&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;login-input&lt;/span&gt; &lt;span class="na"&gt;label=&lt;/span&gt;&lt;span class="s"&gt;"Password"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"password"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/login-input&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;login-button&amp;gt;&lt;/span&gt;Login&lt;span class="nt"&gt;&amp;lt;/login-button&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/form&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/template&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;template&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"login-input-template"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;label&lt;/span&gt; &lt;span class="na"&gt;for=&lt;/span&gt;&lt;span class="s"&gt;"input"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/label&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"input"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/template&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;template&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"login-button-template"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;button&amp;gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/template&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
  &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;LoginForm&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;HTMLElement&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="c1"&gt;// Attach the template to the element&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;template&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;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;login-form-template&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;templateContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;template&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;appendChild&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;templateContent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cloneNode&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="c1"&gt;// Get references to the form elements&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;_usernameInput&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;login-input[label="Username"]&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;_passwordInput&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;login-input[label="Password"]&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;_loginButton&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;login-button&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

      &lt;span class="c1"&gt;// Add event listeners&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;_usernameInput&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;input&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;_onUsernameInput&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;_passwordInput&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;input&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;_onPasswordInput&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;_loginButton&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;click&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_onLoginButtonClick&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;_onUsernameInput&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="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;_username&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;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;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nf"&gt;_onPasswordInput&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="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;_password&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;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;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nf"&gt;_onLoginButtonClick&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// Send a login request with the username and password&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="s2"&gt;`Attempting to login with username: &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;_username&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; and password: &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;_password&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="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;login-form&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;LoginForm&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;LoginInput&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;HTMLElement&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="c1"&gt;// Attach the template to the element&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;template&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;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;login-input-template&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;templateContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;template&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;appendChild&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;templateContent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cloneNode&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="c1"&gt;// Get references to the input element&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;_input&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;#input&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;_label&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;label&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

      &lt;span class="c1"&gt;// Set attributes&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;_input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&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;getAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;label&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;_input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getAttribute&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="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;text&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;_label&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;innerText&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;getAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;label&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="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;login-input&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;LoginInput&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;LoginButton&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;HTMLElement&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="c1"&gt;// Attach the template to the element&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;template&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;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;login-button-template&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;templateContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;template&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;appendChild&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;templateContent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cloneNode&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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;innerText&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;textContent&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;login-button&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;LoginButton&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;Interesting callouts from the above code. &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;LoginInput&lt;/code&gt; sets the label using attributes, but doesn't utilize &lt;code&gt;observedAttributes&lt;/code&gt; or &lt;code&gt;attributeChangedCallback&lt;/code&gt;, two methods available to every custom element that enable engineers to effectively respond to changes in attribute values. Using those callbacks are the only reliable means to track changes in attribute values.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;LoginButton&lt;/code&gt; sets in the label of the button using a strange line: &lt;code&gt;this.innerText = this.textContent;&lt;/code&gt; I would have preferred a method that set the content with a &lt;code&gt;&amp;lt;slot&amp;gt;&lt;/code&gt; via the HTML template or an attribute. &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Some engineers may be put off by the usage of HTML templates here, but I appreciate how ChatGPT used specifications in this case to provide a stable template for each component. Instead you would declare the templates in the constructor of each component &lt;code&gt;class&lt;/code&gt;, a lot like many examples out there.&lt;/p&gt;

&lt;h2&gt;
  
  
  Adding TypeScript
&lt;/h2&gt;

&lt;p&gt;Next, I asked ChatGPT to produce the same code, but this time with TypeScript. This would give me an idea of how to make the components type safe. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo8kgv59xcke4dlv0tvhi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo8kgv59xcke4dlv0tvhi.png" alt="Image description" width="800" height="162"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;LoginForm&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;HTMLElement&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kr"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;_usernameInput&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;HTMLInputElement&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kr"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;_passwordInput&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;HTMLInputElement&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kr"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;_loginButton&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;HTMLButtonElement&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kr"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;_username&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kr"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;_password&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;string&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="c1"&gt;// Attach the template to the element&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;template&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;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;login-form-template&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;HTMLTemplateElement&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;templateContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;template&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;appendChild&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;templateContent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cloneNode&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="c1"&gt;// Get references to the form elements&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;_usernameInput&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;#username&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;HTMLInputElement&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;_passwordInput&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;#password&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;HTMLInputElement&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;_loginButton&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;#login-button&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;HTMLButtonElement&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// Add event listeners&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;_usernameInput&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;input&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;_onUsernameInput&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;_passwordInput&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;input&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;_onPasswordInput&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;_loginButton&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;click&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_onLoginButtonClick&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="kr"&gt;private&lt;/span&gt; &lt;span class="nf"&gt;_onUsernameInput&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Event&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;_username&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;HTMLInputElement&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="kr"&gt;private&lt;/span&gt; &lt;span class="nf"&gt;_onPasswordInput&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Event&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;_password&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;HTMLInputElement&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="kr"&gt;private&lt;/span&gt; &lt;span class="nf"&gt;_onLoginButtonClick&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Send a login request with the username and password&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="s2"&gt;`Attempting to login with username: &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;_username&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; and password: &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;_password&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="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;login-form&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;LoginForm&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;ChatGPT only output the &lt;code&gt;LoginForm&lt;/code&gt; class and failed to generate the other components in Typescript, so I asked again. There were a few other instances where I had to prompt ChatGPT a second, or even third time, to get an acceptable answer. ChatGPT struggled with large code examples. Finally ChatGPT delivered a full TypeScript implementation.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh4869lj7z6c6rxr7h6fr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh4869lj7z6c6rxr7h6fr.png" alt="Image description" width="800" height="178"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;LoginInput&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;HTMLElement&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;_input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;HTMLInputElement&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;private&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;HTMLLabelElement&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="c1"&gt;// Attach the template to the element&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;template&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;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;login-input-template&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;HTMLTemplateElement&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;templateContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;template&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;appendChild&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;templateContent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cloneNode&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="c1"&gt;// Get references to the input element&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;_input&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;#input&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;HTMLInputElement&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;_label&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;label&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;HTMLLabelElement&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// Set attributes&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;_input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&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;getAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;label&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;_input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;type&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;getAttribute&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="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;text&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;_label&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;innerText&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;getAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;label&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="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;login-input&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;LoginInput&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;LoginButton&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;HTMLElement&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="c1"&gt;// Attach the template to the element&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;template&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;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;login-button-template&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;HTMLTemplateElement&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;templateContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;template&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;appendChild&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;templateContent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cloneNode&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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;innerText&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;textContent&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;login-button&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;LoginButton&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;LoginForm&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;HTMLElement&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;_usernameInput&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;HTMLInputElement&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;_passwordInput&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;HTMLInputElement&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;_loginButton&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;HTMLButtonElement&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;_username&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;_password&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&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="c1"&gt;// Attach the template to the element&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;template&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;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;login-form-template&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;HTMLTemplateElement&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;templateContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;template&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;appendChild&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;templateContent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cloneNode&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="c1"&gt;// Get references to the form elements&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;_usernameInput&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;login-input[label="Username"]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;HTMLInputElement&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;_passwordInput&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;login-input[label="Password"]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;HTMLInputElement&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;_loginButton&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;login-button&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;HTMLButtonElement&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// Add event listeners&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;_usernameInput&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;input&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;_onUsernameInput&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;_passwordInput&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;input&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;_onPasswordInput&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;_loginButton&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;click&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_onLoginButtonClick&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="k"&gt;private&lt;/span&gt; &lt;span class="nf"&gt;_onUsernameInput&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Event&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;_username&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;HTMLInputElement&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;private&lt;/span&gt; &lt;span class="nf"&gt;_onPasswordInput&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Event&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;_password&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;HTMLInputElement&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;private&lt;/span&gt; &lt;span class="nf"&gt;_onLoginButtonClick&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Send a login request with the username and password&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="s2"&gt;`Attempting to login with username: &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;_username&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; and password: &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;_password&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="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;login-form&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;LoginForm&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The TypeScript output was spot on. I was particularly glad to see strict typing of the &lt;code&gt;event.target&lt;/code&gt; in a style that was familiar. Pertinent properties on each class are labeled &lt;code&gt;private&lt;/code&gt; and typed appropriately.&lt;/p&gt;

&lt;h2&gt;
  
  
  Styling The Components
&lt;/h2&gt;

&lt;p&gt;I wanted to add styling to the input and button so I prompted ChatGPT to add CSS to the components, although it kept setting styling through &lt;code&gt;this.style&lt;/code&gt; and not by injecting CSS through the &lt;code&gt;&amp;lt;style&amp;gt;&lt;/code&gt; tag which I am accustomed. This is due to the assumption the code examples relied on the previous generated HTML templates. The styling could be provided in the HTML templates, although ChatGPT never chose to place the styles there. I generally prefer declaring CSS in the &lt;code&gt;constructor&lt;/code&gt; of each &lt;code&gt;class&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;I started a new prompt, this time leading with the styling and ChatGPT generated a completely different solution that aligned to my expectations. To achieve this I was more descriptive.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F767t9sojkc668qnnfmvq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F767t9sojkc668qnnfmvq.png" alt="Image description" width="800" height="431"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;ChatGPT chose to default to &lt;code&gt;LitElement&lt;/code&gt; again for this request despite the instruction to "not require a JavaScript library". &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz0lgex5zfbh1km6fptcc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz0lgex5zfbh1km6fptcc.png" alt="Image description" width="800" height="771"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I asked again, this time calling out &lt;code&gt;LitElement&lt;/code&gt; and ChatGPT responded accordingly even though I had a slight typo.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foo849heu97wk4gz4spu7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foo849heu97wk4gz4spu7.png" alt="Image description" width="800" height="160"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Femf40s3txwjxbvfh75s8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Femf40s3txwjxbvfh75s8.png" alt="Image description" width="800" height="262"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;ChatGPT output three separate code samples, one for each component this time around. This made the output way more reliable than when ChatGPT generated one long code snippet with everything.&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="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MyInput&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;HTMLElement&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="nf"&gt;attachShadow&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;mode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;open&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;shadowRoot&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="s2"&gt;`
            &amp;lt;style&amp;gt;
                :host {
                    display: block;
                    background-color: #333;
                    color: #fff;
                }
            &amp;lt;/style&amp;gt;
            &amp;lt;input type="text" /&amp;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="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;my-input&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;MyInput&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each component declares the specified styles in the constructor, setting the &lt;code&gt;innerHTML&lt;/code&gt; of each &lt;code&gt;shadowRoot&lt;/code&gt; to include a &lt;code&gt;&amp;lt;style&amp;gt;&lt;/code&gt; tag. I greatly prefer this format over setting &lt;code&gt;this.style&lt;/code&gt;. The following &lt;code&gt;MyButton class&lt;/code&gt; even sets the &lt;code&gt;cursor: pointer&lt;/code&gt; to enhance accessibility.&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="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MyButton&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;HTMLButtonElement&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="nf"&gt;attachShadow&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;mode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;open&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;shadowRoot&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="s2"&gt;`
            &amp;lt;style&amp;gt;
                :host {
                    background-color: #868e96;
                    color: #000;
                    padding: 0.5em 1em;
                    border: none;
                    border-radius: 0.25em;
                    cursor: pointer;
                }
            &amp;lt;/style&amp;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="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;my-button&lt;/span&gt;&lt;span class="dl"&gt;'&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="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;extends&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;button&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;ChatGPT chose to extend &lt;code&gt;HTMLButtonElement&lt;/code&gt;, making the above component a customized built-in element. This allows any instance of &lt;code&gt;MyButton&lt;/code&gt; to take on the characteristics of &lt;code&gt;HTMLButtonElement&lt;/code&gt;, meaning you don't have to declare event listeners for accessibility because they are baked in. The styling declared in the host that selects :host won’t work because ShadowDOM isn’t available to customized built-ins that extend from &lt;code&gt;HTMLButtonElement&lt;/code&gt;. ChatGPT failed to provide an example of how to use &lt;code&gt;MyButton&lt;/code&gt; in HTML, which differs from any custom element extended from &lt;code&gt;HTMLElement&lt;/code&gt; because of the &lt;code&gt;is&lt;/code&gt; attribute. ChatGPT didn't also call out this code would be inoperable in Safari due to WebKit's stance that blocks customized built-ins from working in Safari (&lt;a href="https://github.com/WebKit/standards-positions/issues/97" rel="noopener noreferrer"&gt;which is currently being reconsidered for inclusion, although WebKit has not signaled support&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;Finally, ChatGPT declared a custom element for the form itself, setting the &lt;code&gt;shadowRoot&lt;/code&gt; with boilerplate styling for the labels, although omitting the &lt;code&gt;is&lt;/code&gt; attribute from the instance of &lt;code&gt;&amp;lt;button&amp;gt;&lt;/code&gt;, which is required of customized built-in elements.&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="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MyFormLayout&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;HTMLElement&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="nf"&gt;attachShadow&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;mode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;open&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;shadowRoot&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="s2"&gt;`
            &amp;lt;style&amp;gt;
                :host {
                    display: block;
                }
                label {
                    display: block;
                    font-size: 0.8em;
                    margin-bottom: 0.5em;
                }
            &amp;lt;/style&amp;gt;
            &amp;lt;form&amp;gt;
                &amp;lt;label&amp;gt;Username&amp;lt;/label&amp;gt;
                &amp;lt;my-input&amp;gt;&amp;lt;/my-input&amp;gt;
                &amp;lt;label&amp;gt;Password&amp;lt;/label&amp;gt;
                &amp;lt;my-input type="password"&amp;gt;&amp;lt;/my-input&amp;gt;
                &amp;lt;button&amp;gt;
                    &amp;lt;slot name="submit"&amp;gt;&amp;lt;/slot&amp;gt;
                &amp;lt;/button&amp;gt;
            &amp;lt;/form&amp;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="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;my-form-layout&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;MyFormLayout&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The above examples were derived from a request to ChatGPT to generate a separation of concerns between the components and style them. One problem is found on the last line of the &lt;code&gt;MyFormLayout&lt;/code&gt; example. Element tag names can only include one dash according to spec, so &lt;code&gt;my-form-layout&lt;/code&gt; should be &lt;code&gt;my-formlayout&lt;/code&gt; to be parsable by the browser. Otherwise, the &lt;code&gt;HTMLButtonElement&lt;/code&gt; doesn't employ the &lt;code&gt;is&lt;/code&gt; attribute which is necessary due to &lt;code&gt;MyButton&lt;/code&gt; being declared as a customized built-in element.  &lt;/p&gt;

&lt;p&gt;The logic for authentication is missing from the prior examples. I ask ChatGPT to add the functionality to &lt;code&gt;MyFormLayout&lt;/code&gt; and was surprised the output was different than before.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq65ljfv2py1as6ctbnse.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq65ljfv2py1as6ctbnse.png" alt="Image description" width="800" height="164"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foibuludvna3qxtvpakag.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foibuludvna3qxtvpakag.png" alt="Image description" width="800" height="153"&gt;&lt;/a&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;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MyFormLayout&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;HTMLElement&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="nf"&gt;attachShadow&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;mode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;open&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;shadowRoot&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="s2"&gt;`
            &amp;lt;style&amp;gt;
                :host {
                    display: block;
                }
                label {
                    display: block;
                    font-size: 0.8em;
                    margin-bottom: 0.5em;
                }
            &amp;lt;/style&amp;gt;
            &amp;lt;form&amp;gt;
                &amp;lt;label&amp;gt;Username&amp;lt;/label&amp;gt;
                &amp;lt;my-input id="username"&amp;gt;&amp;lt;/my-input&amp;gt;
                &amp;lt;label&amp;gt;Password&amp;lt;/label&amp;gt;
                &amp;lt;my-input type="password" id="password"&amp;gt;&amp;lt;/my-input&amp;gt;
                &amp;lt;button id="submit-button"&amp;gt;
                    &amp;lt;slot name="submit"&amp;gt;&amp;lt;/slot&amp;gt;
                &amp;lt;/button&amp;gt;
            &amp;lt;/form&amp;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="nx"&gt;shadowRoot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;submit-button&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;click&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;handleSubmit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nf"&gt;handleSubmit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;preventDefault&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;username&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;shadowRoot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;username&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;HTMLInputElement&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;password&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;shadowRoot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;password&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;HTMLInputElement&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="c1"&gt;// logic to check the entered credentials against a user database or API&lt;/span&gt;
        &lt;span class="c1"&gt;// if the credentials are valid, redirect the user to the home page&lt;/span&gt;
        &lt;span class="c1"&gt;// else display an error message&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;username&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;user&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;password&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;password&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="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;location&lt;/span&gt;&lt;span class="p"&gt;.&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="s2"&gt;home.html&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="nf"&gt;alert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Invalid username or password&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="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;my-form-layout&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;MyFormLayout&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;ChatGPT added a single event listener for the submit button, then read the value of the username and password from each input, and implemented some boilerplate validation that checks the value of username and password fields, along with inline documentation to explain the changes. Additional documentation followed the code snippet. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg7vcu28pg89gijp4vytb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg7vcu28pg89gijp4vytb.png" alt="Image description" width="800" height="352"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It's nice there is a warning the example "is not recommended for production" considering the boilerplate code doesn't secure the password with any sort of hashing algorithm. The description of the code is suitable to gain a basic understanding of how everything works.&lt;/p&gt;

&lt;p&gt;The pseudo code validating the username and password doesn't make much sense. There would most likely be much more robust validation logic here. You could coax ChatGPT into producing an algorithm close to your intended business logic, but it's unlikely to get it just right. &lt;/p&gt;

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

&lt;p&gt;When I heard an engineer was using ChatGPT to generate code everyday for his job, I was interested to see what that was like. Tools like this are no substitute for the knowledge gained by coding something from scratch. While it's entirely possible to generate boilerplate code with ChatGPT, I can't recommend it. Like other responses for "factual" content, ChatGPT will often answer with either completely wrong information or base part of an answer on incorrect assumptions. The same is true of code snippets. As we observed, ChatGPT omitted valuable information about the generated code and generated incorrectly formatted code. Overall, ChatGPT did an alright job at generating Web Components. With some alterations the generated code could be used as the basis for coding a login view. For code generation ChatGPT possibly doesn't replace tools like plop or Nx, which give you consistent control over boilerplate.&lt;/p&gt;

&lt;p&gt;There's possibly more advanced queries I could have made to generate the login view code. I'm just a novice. &lt;/p&gt;

&lt;p&gt;It's fairly interesting that ChatGPT defaulted to generating Web Components with LitElement. This is a testament to how synonymous the library is becoming with Web Components.&lt;/p&gt;

&lt;p&gt;In another query (not featured here), I prompted ChatGPT to make the input and button form-associated custom elements which the service promptly ignored. In a separate query from that, ChatGPT struggled to generate a single example that implemented &lt;code&gt;formAssociated&lt;/code&gt; or &lt;code&gt;ElementInternals&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;ChatGPT could get better over time, but for now I feel quite secure in my job as a front-end web developer. If I were completely new to Web Components, the incorrect and omitted code would be awfully confusing. Due to the inaccuracy of the code samples, In the future, maybe our robot overloads will take our jobs, but I don't think we will be replaced by A.I. anytime soon.&lt;/p&gt;

&lt;p&gt;OK, now what I really think. We are doomed, at least in the immediate future. Bloggers will use the ChatGPT to generate posts. The rest of us won't get the content we so desperately deserve. We'll be forced to sift through cursory explanations of code that are incorrect or malformed. I suppose that was already happening before ChatGPT, but I'm predicting it's only going to be worse. Luckily there's entire books devoted to the topic of Web Components.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fullstack Web Components Book
&lt;/h2&gt;

&lt;p&gt;Want to learn how to code UI Libraries and Web Applications with Web Components?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.newline.co/courses/fullstack-web-components" rel="noopener noreferrer"&gt;Fullstack Web Components&lt;/a&gt; is the complete guide to building UI Libraries with Web Components. Through ten chapters of hands-on exercises, you'll learn every aspect to the set of specifications in the context of developing a UI Library and Web Application. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsvpbg90fy2sq7lfjatmx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsvpbg90fy2sq7lfjatmx.png" alt="Front cover of Fullstack Web Components Book" width="600" height="754"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.newline.co/courses/fullstack-web-components" rel="noopener noreferrer"&gt;Purchase a copy of Fullstack Web Components&lt;/a&gt; today and begin your journey into the wonderful world of front-end development with Web Components.&lt;/p&gt;

</description>
      <category>discuss</category>
    </item>
    <item>
      <title>Form-associated custom elements FTW!</title>
      <dc:creator>Stephen Belovarich</dc:creator>
      <pubDate>Tue, 14 Jun 2022 15:30:40 +0000</pubDate>
      <link>https://dev.to/steveblue/form-associated-custom-elements-ftw-16bi</link>
      <guid>https://dev.to/steveblue/form-associated-custom-elements-ftw-16bi</guid>
      <description>&lt;p&gt;Before Shadow DOM, you needed a framework to encapsulate component templates or styling. Shadow DOM was a game-changer because it allows you to code UI components without their logic clashing with other components using just the web platform. Shadow DOM poses challenges when HTML elements encapsulated by Shadow DOM need to participate in a form.&lt;/p&gt;

&lt;p&gt;In this post, I'll provide an overview of form-associated custom elements. Form-associated custom elements is a web specification that allows engineers to code custom form controls that report value and validity to &lt;code&gt;HTMLFormElement&lt;/code&gt;, while also promoting a fully accessible user experience.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;With the encapsulation provided by Shadow DOM, engineers can code UI components where the CSS styling doesn't collide with other components. Shadow DOM provides a DOM tree for an element separated from the rest of the Document Object Model (DOM). The separation of concerns promoted by Shadow DOM is a boon for coding reusable UI components.&lt;/p&gt;

&lt;p&gt;While Shadow DOM has several benefits, there are some complications when elements embedded in Shadow DOM have to interact with &lt;code&gt;HTMLFormElement&lt;/code&gt;. Suppose you wanted to code a custom checkbox component using Shadow DOM. Checkboxes usually require a significant amount of CSS styling that overrides the browser defaults to match a given mockup. You code an autonomous custom element and style the &lt;code&gt;HTMLInputElement&lt;/code&gt; with &lt;code&gt;type="checkbox"&lt;/code&gt; in the context of Shadow DOM so the styling doesn't conflict with other elements. You give the component a tag name of &lt;code&gt;my-checkbox&lt;/code&gt;. Just when you think you're following best practices, you place an instance of the custom element as a child of &lt;code&gt;HTMLFormElement&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;form&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;my-checkbox&amp;gt;&amp;lt;/my-checkbox&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/form&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Upon inspection in Dev Tools, you may notice the &lt;code&gt;HTMLInputElement&lt;/code&gt; cannot participate with the form. You can inspect this phenomenon in this &lt;a href="https://codesandbox.io/embed/custom-element-and-form-brpp29?fontsize=14&amp;amp;hidenavigation=1&amp;amp;theme=dark" rel="noopener noreferrer"&gt;CodeSandbox&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;&lt;code&gt;HTMLInputElement&lt;/code&gt; by design can report value and validity back to &lt;code&gt;HTMLFormElement&lt;/code&gt;, but only when &lt;code&gt;HTMLInputElement&lt;/code&gt; is a direct descendent of &lt;code&gt;HTMLFormElement&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;form&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;"checkbox"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/form&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When coding reusable components it's a good idea to provide an interface for web engineers that's familiar. It's typical for &lt;code&gt;HTMLInputElement&lt;/code&gt; that are direct descendants of &lt;code&gt;HTMLFormElement&lt;/code&gt; to have access to the parent form directly on the element. You can inspect this behavior in the following &lt;a href="https://codesandbox.io/embed/htmlformelement-hdied1?fontsize=14&amp;amp;hidenavigation=1&amp;amp;theme=dark" rel="noopener noreferrer"&gt;CodeSandbox&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Since the &lt;code&gt;HTMLInputElement&lt;/code&gt; is found in an entirely different DOM tree (Shadow DOM), the &lt;code&gt;HTMLFormElement&lt;/code&gt; doesn't recognize the &lt;code&gt;HTMLInputElement&lt;/code&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%2F7sygxv8opy3711m7oml5.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%2F7sygxv8opy3711m7oml5.png" alt="my-checkbox contains a HTMLInputElement embedded in Shadow DOM"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In short, &lt;code&gt;HTMLInputElement&lt;/code&gt; embedded in Shadow DOM can't participate in the form.&lt;/p&gt;

&lt;h2&gt;
  
  
  Enter Form-associated custom elements
&lt;/h2&gt;

&lt;p&gt;In 2019, a new specification was proposed that solves this issue. Form-associated custom elements allow web engineers to use the benefits of Shadow DOM while providing an API that enables custom elements to participate in &lt;code&gt;HTMLFormElement&lt;/code&gt;. Form-associated custom elements have all the benefits of autonomous custom elements. They can implement Shadow DOM and use the typical custom lifecycle hooks because they inherit from &lt;code&gt;HTMLElement&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If you've coded autonomous custom elements, learning how to code form-associated custom elements is fairly similar. In the following examples, I'll demonstrate how a checkbox embedded in Shadow DOM can participate in an HTML form by implementing &lt;code&gt;formAssociated&lt;/code&gt; and &lt;code&gt;ElementInternals&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;Before I mentioned that form controls that are native to the browser like &lt;code&gt;HTMLInputElement&lt;/code&gt; automatically participate in &lt;code&gt;HTMLFormElement&lt;/code&gt;. The form control is added to an Array-like interface on &lt;code&gt;HTMLFormElement&lt;/code&gt;, allowing web engineers to loop through the form controls to handle common tasks like validation. &lt;/p&gt;

&lt;p&gt;For the &lt;code&gt;Checkbox&lt;/code&gt; component to participate in &lt;code&gt;HTMLFormElement&lt;/code&gt; the same way, you simply need to set the value of a static property named &lt;code&gt;formAssociated&lt;/code&gt; to &lt;code&gt;true&lt;/code&gt;. the below example written with TypeScript does just that.&lt;/p&gt;

&lt;p&gt;If you wish to follow along, fork this &lt;a href="https://codesandbox.io/embed/custom-element-and-form-brpp29?fontsize=14&amp;amp;hidenavigation=1&amp;amp;theme=dark" rel="noopener noreferrer"&gt;CodeSandbox&lt;/a&gt; and start coding.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Checkbox&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;HTMLElement&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="nx"&gt;formAssociated&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&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="p"&gt;....&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Inversely, if you wish to reference &lt;code&gt;HTMLFormElement&lt;/code&gt; on instances of &lt;code&gt;Checkbox&lt;/code&gt;, similar to how &lt;code&gt;HTMLInputElement&lt;/code&gt; behaves when it's a direct descendant of &lt;code&gt;HTMLFormElement&lt;/code&gt;, a method inherited from &lt;code&gt;HTMLElement&lt;/code&gt; called &lt;code&gt;attachInternals&lt;/code&gt; can be called with provides the same interface, along with the Accessibility Object Model (AOM). By setting a  property on &lt;code&gt;Checkbox&lt;/code&gt; named &lt;code&gt;_internals&lt;/code&gt; to what's returned by &lt;code&gt;attachInternals&lt;/code&gt;, you effectively add the &lt;code&gt;ElementInternals&lt;/code&gt; interface to &lt;code&gt;Checkbox&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Checkbox&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;HTMLElement&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;_internals&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ElementInternals&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="nx"&gt;formAssociated&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&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;_internals&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;attachInternals&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Parity
&lt;/h3&gt;

&lt;p&gt;Later in this post, I'll provide an example of how you can reference a method on the &lt;code&gt;ElementInternals&lt;/code&gt; interface that aids with validation. Before that, we should resolve some discrepancies between &lt;code&gt;Checkbox&lt;/code&gt; and a typical &lt;code&gt;HTMLInputElement&lt;/code&gt;. If we expect engineers to reuse this component, it should behave similarly to &lt;code&gt;HTMLInputElement&lt;/code&gt;, which has a well-known interface. To provide parity between &lt;code&gt;HTMLInputElement&lt;/code&gt; and &lt;code&gt;Checkbox&lt;/code&gt;, let's define some getters and setters on &lt;code&gt;Checkbox&lt;/code&gt;. First, make a getter that returns a reference of the &lt;code&gt;HTMLInputElement&lt;/code&gt; so you can easily reference the element with &lt;code&gt;this.checkbox&lt;/code&gt; throughout the logic of the component.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;  &lt;span class="kd"&gt;get&lt;/span&gt; &lt;span class="nf"&gt;checkbox&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nx"&gt;HTMLInputElement&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&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;shadowRoot&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;input&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;HTMLInputElement&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;Next, define a getter and setter for the state of the checkbox. It's probably a good idea to make the &lt;code&gt;HTMLInputElement&lt;/code&gt; the single source of truth here. Any getter and setter defined on &lt;code&gt;Checkbox&lt;/code&gt; either returns the value or sets the value of &lt;code&gt;checked&lt;/code&gt; on &lt;code&gt;this.checkbox&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;  &lt;span class="kd"&gt;get&lt;/span&gt; &lt;span class="nf"&gt;checked&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&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;checkbox&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="kd"&gt;set&lt;/span&gt; &lt;span class="nf"&gt;checked&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="nx"&gt;boolean&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;checkbox&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;checked&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&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;We could introduce several more properties on &lt;code&gt;Checkbox&lt;/code&gt; to provide parity between it and a typical &lt;code&gt;HTMLInputElement&lt;/code&gt;, but we'll stop there for now. &lt;/p&gt;

&lt;h3&gt;
  
  
  Lessons Learned
&lt;/h3&gt;

&lt;p&gt;While coding a UI library filled with form-associated custom elements, I found a couple of challenges in making the components reusable. Suppose you wanted to add validation logic to &lt;code&gt;Checkbox&lt;/code&gt; and have the &lt;code&gt;class&lt;/code&gt; another method called &lt;code&gt;onValidate&lt;/code&gt;, including all the logic there. In the below example, I call &lt;code&gt;setValidity&lt;/code&gt; on the &lt;code&gt;ElementInternals&lt;/code&gt; interface, which reports the validity of the input to &lt;code&gt;HTMLFormElement&lt;/code&gt;. This is convenient, however placing this logic here doesn't give a web engineer the ability to configure validations per business logic in different scenarios.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;  &lt;span class="nf"&gt;onValidate&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="nx"&gt;boolean&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="o"&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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_internals&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setValidity&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;customError&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;required&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="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="kc"&gt;true&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;_internals&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setValidity&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;A higher-level validation pattern is required that would allow engineers to loop through form controls and validate an entire form.&lt;/p&gt;

&lt;p&gt;Another challenge had to do with making inline validation messages accessible. Getting screen readers to interpret validation messages as errors that should be read aloud at first seems tricky because of Shadow DOM, although is possible using WAI-ARIA attributes. Suppose this were the template instead of just the input. If the form control is invalid, custom logic could populate the &lt;code&gt;&amp;lt;div class="message"&amp;gt;&lt;/code&gt; with relevant content. WAI-ARIA attributes provide an immediate response for screen readers.&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;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"control"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"checkbox"&lt;/span&gt; &lt;span class="na"&gt;aria-describedby=&lt;/span&gt;&lt;span class="s"&gt;"message"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/div&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;"message"&lt;/span&gt;
         &lt;span class="na"&gt;aria-role=&lt;/span&gt;&lt;span class="s"&gt;"alert"&lt;/span&gt;
         &lt;span class="na"&gt;aria-live=&lt;/span&gt;&lt;span class="s"&gt;"assertive"&lt;/span&gt;
         &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"message"&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Autonomous custom elements that employ Shadow DOM provide encapsulation for component template and styling. Form-associated custom elements level up custom elements by allowing them to participate in a HTML form much like the "native" &lt;code&gt;HTMLInputElement&lt;/code&gt;. Form-associated custom elements open the doors for custom user experiences like this joystick component, which reports it's value to &lt;code&gt;HTMLFormElement&lt;/code&gt; like any other form control.&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%2Flahkd3cl67al45ngpwct.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%2Flahkd3cl67al45ngpwct.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Form-associated custom elements are both practical and transformative, allowing engineers to code reusable form controls without the need for JavaScript frameworks and libraries. If you want to code a highly performant user interface that depends heavily on forms, form-associated custom elements are where it's at!&lt;/p&gt;

&lt;h2&gt;
  
  
  Fullstack Web Components
&lt;/h2&gt;

&lt;p&gt;Are you looking to code Web Components now, but don't know where to get started? I wrote &lt;a href="https://www.newline.co/courses/fullstack-web-components" rel="noopener noreferrer"&gt;a book titled Fullstack Web Components&lt;/a&gt;, a hands-on guide to coding UI libraries and web applications with custom elements. In Fullstack Web Components, you'll...&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Code several components using autonomous, customized built-in, and form-associated custom elements, Shadow DOM, HTML templates, CSS variables, and Declarative Shadow DOM&lt;/li&gt;
&lt;li&gt;Develop a micro-library with TypeScript decorators that streamlines UI component development&lt;/li&gt;
&lt;li&gt;Learn best practices for maintaining a UI library of Web Components with Storybook&lt;/li&gt;
&lt;li&gt;Code an application using Web Components and TypeScript&lt;/li&gt;
&lt;li&gt;Server-side render Web Components with Node.js and @lit-labs/ssr&lt;/li&gt;
&lt;/ul&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%2Fcka75oj2i0jusdw2atmt.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%2Fcka75oj2i0jusdw2atmt.png" alt="Fullstack Web Components"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Fullstack Web Components &lt;a href="https://www.newline.co/courses/fullstack-web-components" rel="noopener noreferrer"&gt;is available now on newline.co&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>webcomponents</category>
      <category>html</category>
      <category>javascript</category>
    </item>
    <item>
      <title>The State Of Web Components in 2022</title>
      <dc:creator>Stephen Belovarich</dc:creator>
      <pubDate>Sat, 07 May 2022 03:13:41 +0000</pubDate>
      <link>https://dev.to/steveblue/the-state-of-web-components-in-2022-1ip3</link>
      <guid>https://dev.to/steveblue/the-state-of-web-components-in-2022-1ip3</guid>
      <description>&lt;p&gt;While writing &lt;a href="https://www.newline.co/courses/fullstack-web-components?utm_source=dev.to"&gt;a book about Web Components&lt;/a&gt;, I found nothing in my research that described the current state of the Web Components ecosystem. In this post, I hope to clear up any misconceptions about Web Components and provide several links to resources about Web Components. I'll review the current level of adoption, the top libraries and tooling available, and the browser support for the set of specifications known as Web Components.&lt;/p&gt;

&lt;h2&gt;
  
  
  TLDR;
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;~18% of page loads &lt;a href="https://chromestatus.com/metrics/feature/timeline/popularity/1689" rel="noopener noreferrer"&gt;tracked by Google&lt;/a&gt; contain a Web Component&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.npmjs.com/package/@stencil/core" rel="noopener noreferrer"&gt;Stencil&lt;/a&gt; is the most downloaded library for developing Web Components&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/open-wc/custom-elements-manifest" rel="noopener noreferrer"&gt;Custom Elements Manifest&lt;/a&gt; powers documentation for &lt;a href="https://storybook.js.org/docs/web-components/get-started/introduction" rel="noopener noreferrer"&gt;Storybook&lt;/a&gt; where Web Components became a first-class citizen in 2021&lt;/li&gt;
&lt;li&gt;Several online communities about Web Components sprung up during the pandemic including this &lt;a href="https://community.webcomponents.dev" rel="noopener noreferrer"&gt;Forem instance devoted to Web Components&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Adoption
&lt;/h2&gt;

&lt;p&gt;Web Components have been growing in popularity among web developers ever since Custom Elements v1 became available in every evergreen browser: Chrome, Safari, Firefox, and Edge. According to &lt;a href="https://www.youtube.com/watch?v=YBwgkr_Sbx0" rel="noopener noreferrer"&gt;Google&lt;/a&gt;, in 2019 “&amp;gt; 5% of page loads use a Web Component”. Today, &lt;a href="https://chromestatus.com/metrics/feature/timeline/popularity/1689" rel="noopener noreferrer"&gt;Chrome Platform Status&lt;/a&gt; reveals that ~18% of page loads tracked by Google contain a Web Component. This statistic is derived by pages that register a custom element on the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/CustomElementRegistry" rel="noopener noreferrer"&gt;CustomElementRegistry&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Libraries and Tooling
&lt;/h2&gt;

&lt;p&gt;Over the years several libraries that enable Web Component development have appeared. &lt;a href="https://webcomponents.dev" rel="noopener noreferrer"&gt;https://webcomponents.dev&lt;/a&gt; tracks &lt;a href="https://webcomponents.dev/blog/all-the-ways-to-make-a-web-component/" rel="noopener noreferrer"&gt;61 variants of a single component&lt;/a&gt; coded with Web Component specifications. &lt;a href="https://divriots.com" rel="noopener noreferrer"&gt;divriots&lt;/a&gt;, a company that "builds great software to empower front-end teams", analyzes bundle size and performance of several Web Component libraries and publishes the findings in a semi-annual blog post titled "All the Ways to Make a Web Component".&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;4 variants render faster than the same component coded with Svelte&lt;/li&gt;
&lt;li&gt;5 variants have a bundle size smaller than the same component coded with Svelte&lt;/li&gt;
&lt;li&gt;54 variants coded with custom elements have a bundle-size smaller and render faster than the same component coded with React or Vue&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Top Five Web Component Libraries By Downloads
&lt;/h3&gt;

&lt;p&gt;The following JavaScript libraries are ranked by weekly npm downloads. These libraries either use Web Components "under the hood" or compile down to Web Components.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;a href="https://www.npmjs.com/package/@stencil/core" rel="noopener noreferrer"&gt;Stencil&lt;/a&gt; - 357,795&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.npmjs.com/package/lit" rel="noopener noreferrer"&gt;Lit&lt;/a&gt; - 282,068&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.npmjs.com/package/@microsoft/fast-element" rel="noopener noreferrer"&gt;FAST&lt;/a&gt; - 37,489&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.npmjs.com/package/@lwc/compiler" rel="noopener noreferrer"&gt;Lightning Web Components&lt;/a&gt; - 35,452&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.npmjs.com/package/solid-js" rel="noopener noreferrer"&gt;Solid&lt;/a&gt; - 26,261&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;Weekly downloads sourced from npm on June 24, 2022.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Tooling
&lt;/h3&gt;

&lt;p&gt;In the past year, Web Components became &lt;a href="https://storybook.js.org/docs/web-components/get-started/introduction" rel="noopener noreferrer"&gt;a first-class citizen in Storybook&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/open-wc/custom-elements-manifest" rel="noopener noreferrer"&gt;Custom Elements Manifest&lt;/a&gt; is "a file format that describes custom elements". The &lt;a href="https://github.com/open-wc/custom-elements-manifest/tree/master/packages/analyzer" rel="noopener noreferrer"&gt;@custom-elements-manifest/analyzer&lt;/a&gt; package generates a JSON file that can provide rich information about Web Components. Storybook uses this tool to generate documentation of Web Components.&lt;/p&gt;

&lt;h2&gt;
  
  
  Browser Support
&lt;/h2&gt;

&lt;p&gt;Every major browser supports custom elements according to &lt;a href="https://caniuse.com/custom-elementsv1" rel="noopener noreferrer"&gt;caniuse.com&lt;/a&gt;, with the exception that Apple Safari doesn't support customized built-in elements. Web Components is a set of specifications that include custom elements, Shadow DOM, and HTML templates. caniuse.com uses an outdated marketing term (custom elements v1) for the specifications that allow new HTML tags to be defined, which should probably be known as "custom elements". v1 differentiates the specifications from the prototypical version of custom elements (v0) that have been deprecated since 2014. This marketing term is confusing, mostly because HTML is a living document. caniuse.com also doesn't track some specifications related to Web Components, which may confuse newcomers.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://html.spec.whatwg.org/multipage/custom-elements.html#custom-elements" rel="noopener noreferrer"&gt;HTML Living Standard&lt;/a&gt; provides documentation for custom elements, including the three main types: autonomous, customized built-in, and form-associated custom elements. Custom elements are one part of the specifications that comprise Web Components, along with Shadow DOM and HTML templates. Other specifications enhance Web Components in some way, like CSS custom properties, but are not under the umbrella of Web Components. Below each specification is described, along with the level of browser support.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://html.spec.whatwg.org/multipage/custom-elements.html#autonomous-custom-element" rel="noopener noreferrer"&gt;Autonomous custom elements&lt;/a&gt; allow engineers to define custom HTML elements. Autonomous custom elements have shipped in every modern browser.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://html.spec.whatwg.org/multipage/custom-elements.html#custom-elements-customized-builtin-example" rel="noopener noreferrer"&gt;Customized built-in elements&lt;/a&gt; allow reuse of behaviors from existing HTML elements, which can be a boon for Accessibility in some use cases. Customized built-in elements are available in every modern browser except Apple Safari. A WebKit representative has said in the past &lt;a href="https://github.com/WICG/webcomponents/issues/544#issuecomment-239301184" rel="noopener noreferrer"&gt;WebKit will never support customized built-in elements&lt;/a&gt;. WebKit has supported autonomous custom elements and other Web Components specifications including HTML templates and Shadow DOM since 2019. Despite marketing that suggests Safari is either &lt;a href="https://twitter.com/jensimmons/status/1513960262923137040?s=20&amp;amp;t=3e51Xdx8YQ-zuUVzVCi8Yw" rel="noopener noreferrer"&gt;on par&lt;/a&gt; or &lt;a href="https://twitter.com/jensimmons/status/1513960262923137040?s=20&amp;amp;t=3e51Xdx8YQ-zuUVzVCi8Yw" rel="noopener noreferrer"&gt;leading&lt;/a&gt; in support for specifications, WebKit drags behind other browsers in terms of supporting Web Components. A &lt;a href="https://github.com/ungap/custom-elements-builtin" rel="noopener noreferrer"&gt;polyfill&lt;/a&gt; exists for customized built-ins that is necessary for Apple Safari.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://html.spec.whatwg.org/multipage/custom-elements.html#custom-elements-face-example" rel="noopener noreferrer"&gt;Form-associated custom elements&lt;/a&gt; enable custom elements to participate in the HTML form lifecycle. This specification allows developers to make entirely new form controls, or when inputs are embedded or Shadow DOM, overcome issues that block form controls from reporting validity and value to &lt;code&gt;HTMLFormElement&lt;/code&gt;, while also enabling Accessibility. form-associated custom elements are available in Chrome, Edge, and Firefox Developer Edition. There is &lt;a href="https://bugs.webkit.org/show_bug.cgi?id=197960" rel="noopener noreferrer"&gt;some evidence&lt;/a&gt; form-associated custom elements will be available in WebKit in the future. A &lt;a href="https://github.com/calebdwilliams/element-internals-polyfill" rel="noopener noreferrer"&gt;polyfill&lt;/a&gt; exists for browsers that have not implemented form-associated custom elements.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_shadow_DOM" rel="noopener noreferrer"&gt;Shadow DOM&lt;/a&gt; provides encapsulation for HTML markup and styling for custom elements. Shadow DOM is available in every modern browser.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/template" rel="noopener noreferrer"&gt;HTML Templates&lt;/a&gt; is a way to cache HTML in DOM that isn't immediately rendered but can be reused for use in the document. HTML templates are available in every modern browser.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/--*" rel="noopener noreferrer"&gt;CSS custom properties&lt;/a&gt;, otherwise known as CSS variables, are a viable means to provide "design tokens" that cross the Shadow boundary. While not necessarily considered a Web Component specification, CSS custom properties allow engineers to essentially reuse styling in custom elements that employ Shadow DOM. CSS custom properties are found in every modern browser.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.w3.org/TR/css-shadow-parts-1/" rel="noopener noreferrer"&gt;CSS Shadow Parts&lt;/a&gt; allow shadow hosts to "selectively expose chosen elements from their shadow tree to the outside page for styling purposes." CSS Shadow Parts are useful for theming or other style customizations in custom elements that use Shadow DOM. CSS Shadow Parts are in every modern browser.&lt;/p&gt;

&lt;h2&gt;
  
  
  Specification Proposals
&lt;/h2&gt;

&lt;p&gt;Web Components are a set of browser specifications. Spec is introduced over time: proposed, iteration upon, developed, and integrated into browsers. Web Components specifications are maintained and proposed in several places. An index is found on &lt;a href="https://github.com/WICG/webcomponents" rel="noopener noreferrer"&gt;Web Incubator CG Github repository README.md&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Various specifications are in some stage of the proposal process that impacts Web Components. Those specifications are described below.&lt;/p&gt;

&lt;h3&gt;
  
  
  Declarative Shadow DOM
&lt;/h3&gt;

&lt;p&gt;In early 2020, an explainer for &lt;a href="https://github.com/mfreed7/declarative-shadow-dom/blob/master/README.md" rel="noopener noreferrer"&gt;Declarative Shadow DOM&lt;/a&gt; was introduced, a specification that enables Shadow DOM to be declared in an HTML template, rather than imperatively in a JavaScript class definition. Declarative Shadow DOM enables the server-side rendering of custom elements. Since then, Google Chrome and Microsoft Edge have implemented Declarative Shadow DOM. A &lt;a href="https://web.dev/declarative-shadow-dom/#polyfill" rel="noopener noreferrer"&gt;ponyfill&lt;/a&gt; is required for the specification in Mozilla Firefox and Apple Safari.&lt;/p&gt;

&lt;p&gt;In &lt;a href="https://webvision.mozilla.org/full/#thedeclarativeweb" rel="noopener noreferrer"&gt;Mozilla's vision for the evolution of the web&lt;/a&gt;, the foundation sees "room for improving or adding primitives that provide declarative alternatives for situations that currently require JavaScript". This indicates Mozilla is likely to implement specifications such as Declarative Shadow DOM, as do comments like &lt;a href="https://github.com/whatwg/dom/pull/892#issuecomment-1087510456" rel="noopener noreferrer"&gt;this one&lt;/a&gt;, although Mozilla has been &lt;a href="https://mozilla.github.io/standards-positions/#declarative-shadow-dom" rel="noopener noreferrer"&gt;critical of the specification&lt;/a&gt; in the past. WebKit has not signaled support for Declarative Shadow DOM. Progress on Declarative Shadow DOM can be tracked &lt;a href="https://github.com/whatwg/dom/pull/892" rel="noopener noreferrer"&gt;here&lt;/a&gt; and &lt;a href="https://github.com/whatwg/dom/issues/831" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;It is worth noting that &lt;a href="https://github.com/lit/lit" rel="noopener noreferrer"&gt;Lit&lt;/a&gt; has moved forward with providing a package named &lt;a href="https://github.com/lit/lit/tree/main/packages/labs/ssr" rel="noopener noreferrer"&gt;@lit-labs/ssr that server-side renders custom elements&lt;/a&gt; and makes heavy use of Declarative Shadow DOM.&lt;/p&gt;

&lt;h3&gt;
  
  
  Constructable Stylesheets
&lt;/h3&gt;

&lt;p&gt;In 2018, an explainer was introduced for &lt;a href="https://github.com/WICG/construct-stylesheets/blob/main/explainer.md" rel="noopener noreferrer"&gt;Constructable Stylesheets&lt;/a&gt;. Constructable Stylesheets allow users to compose CSS styles programmatically without the need for a &lt;code&gt;&amp;lt;style&amp;gt;&lt;/code&gt; tag. This promotes the reuse of CSS styles, helping to solve a problem common in UI library development where the same CSS is repeated throughout the codebase. Constructable Stylesheets operate across shadow boundaries, replacing the need for CSS selectors in earlier versions of Web Components specifications (/deep/, &amp;gt;&amp;gt;&amp;gt; and ::shadow). Constructable Stylesheets are currently available in Google Chrome and Microsoft Edge. A &lt;a href="https://github.com/calebdwilliams/construct-style-sheets" rel="noopener noreferrer"&gt;polyfill&lt;/a&gt; exists for browsers that omit the spec.&lt;/p&gt;

&lt;p&gt;Mozilla is &lt;a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1520690" rel="noopener noreferrer"&gt;currently implementing Constructable Stylesheets&lt;/a&gt; for Mozilla Firefox. WebKit has not signaled support for the specification.&lt;/p&gt;

&lt;h3&gt;
  
  
  HTML Template Instantiation
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://github.com/WICG/webcomponents/blob/159b1600bab02fe9cd794825440a98537d53b389/proposals/Template-Instantiation.md" rel="noopener noreferrer"&gt;HTML Template Instantiation&lt;/a&gt; was proposed by Apple in 2017. HTML Template Instantiation would allow HTML templates to instantiate "with some parts of it substituted, conditionally included, or repeated based on JavaScript values", similar to the syntax available in Angular, Vue, or Ember. A &lt;a href="https://github.com/github/template-parts" rel="noopener noreferrer"&gt;ponyfill&lt;/a&gt; exists, despite a complete lack of browser support.&lt;/p&gt;

&lt;h3&gt;
  
  
  Other Specifications
&lt;/h3&gt;

&lt;p&gt;Web Components specifications early in the proposal process include &lt;a href="https://github.com/webcomponents-cg/community-protocols/issues/30" rel="noopener noreferrer"&gt;Partial Hydration&lt;/a&gt;, &lt;a href="https://github.com/webcomponents-cg/community-protocols/blob/main/proposals/pending-task.md" rel="noopener noreferrer"&gt;Pending Task Protocol&lt;/a&gt;, and &lt;a href="https://github.com/webcomponents-cg/community-protocols/blob/main/proposals/context.md" rel="noopener noreferrer"&gt;Context Protocol&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;For further reading about upcoming specifications, read the &lt;a href="https://w3c.github.io/webcomponents-cg/#cross-root-aria" rel="noopener noreferrer"&gt;Web Components Community Group 2021 Spec / API Status Document&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Community
&lt;/h2&gt;

&lt;p&gt;Despite a global pandemic, the growing popularity of Web Components has spurred a community surrounding Web Components to start forming.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://web-components-cg.netlify.app" rel="noopener noreferrer"&gt;Web Components Community Group&lt;/a&gt; aids with "collaboration between people working on web components libraries, tools, documentation, and standards". This group helps advance standards, organize conferences and meetups, and much more. A &lt;a href="//webcomponentcommunity.slack.co"&gt;Slack&lt;/a&gt; organization exists for Web Components where engineers can organize.&lt;/p&gt;

&lt;p&gt;A &lt;a href="https://community.webcomponents.dev" rel="noopener noreferrer"&gt;Forem instance devoted to Web Components&lt;/a&gt; became available in 2022.&lt;/p&gt;

&lt;p&gt;This &lt;a href="https://twitter.com/i/communities/1509750356040994816" rel="noopener noreferrer"&gt;Web Components Twitter Community&lt;/a&gt; was formed in 2022.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.reddit.com/r/WebComponents/" rel="noopener noreferrer"&gt;Reddit has featured a Web Component community&lt;/a&gt; for some time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fullstack Web Components
&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%2Fjbdgc4z2i84e2xailhsv.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%2Fjbdgc4z2i84e2xailhsv.png" alt="Fullstack Web Components Book Cover"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Are you looking to code Web Components now, but don't know where to get started? I wrote a book titled &lt;a href="https://www.newline.co/courses/fullstack-web-components?utm_source=dev.to"&gt;Fullstack Web Components&lt;/a&gt;, a hands-on guide to coding UI libraries and web applications with custom elements. In Fullstack Web Components, you'll...&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Code several components using autonomous, customized built-in, and form-associated custom elements, Shadow DOM, HTML templates, CSS variables, and Declarative Shadow DOM&lt;/li&gt;
&lt;li&gt;Develop a micro-library with TypeScript decorators that streamlines UI component development&lt;/li&gt;
&lt;li&gt;Learn best practices for maintaining a UI library of Web Components with Storybook&lt;/li&gt;
&lt;li&gt;Code an application using Web Components and TypeScript&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://www.newline.co/courses/fullstack-web-components?utm_source=dev.to"&gt;Fullstack Web Components is available now at newline.co&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webcomponents</category>
      <category>javascript</category>
      <category>css</category>
      <category>html</category>
    </item>
    <item>
      <title>I spent 48 Hours coding in Nova and forgot about VS Code</title>
      <dc:creator>Stephen Belovarich</dc:creator>
      <pubDate>Fri, 30 Oct 2020 15:30:38 +0000</pubDate>
      <link>https://dev.to/steveblue/i-spent-48-hours-with-nova-and-forgot-all-about-vs-code-15lf</link>
      <guid>https://dev.to/steveblue/i-spent-48-hours-with-nova-and-forgot-all-about-vs-code-15lf</guid>
      <description>&lt;p&gt;I spent 48 hours with Nova, a new native IDE for MacOS released by Panic on September 14th. &lt;/p&gt;

&lt;p&gt;Two days ago I downloaded the free trial of Nova, installed the TypeScript extension, snagged a theme, opened a TypeScript project and started coding.&lt;/p&gt;

&lt;p&gt;Nova is fast. Files open with haste. Find in Project... delivers search results fast. There's no visible slowdown editing large files. When I open multiple text editor panes and terminals Nova doesn't skip a beat. The native IDE just works. Not everything is straight out of MacOS. According to Panic, their engineers coded the text editor from scratch after discovering some bugs in Apple’s text layout engine and ended up with a more performant experience.&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%2Frzs0vzzfds0vgl2kk8bl.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%2Frzs0vzzfds0vgl2kk8bl.gif" alt="Mind blown"&gt;&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;Nova's text editor is delightful. There’s multiline editing, intuitive autocompletion, code hints. The TypeScript extension features some refactoring functionality for all those code smells. Panic found some novel uses for the MacBook Pro Touch Bar including running your npm script at the press of a button, as if typing npm run start took too long.&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%2Fi%2F81anr05tkizykr9w7kbo.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%2Fi%2F81anr05tkizykr9w7kbo.png" alt="Open Project Pane"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The design is clean and intuitive, very familiar to anyone using MacOS. Finding stuff is easy. When I click on the whimsical iconography there’s subtle user feedback. I open a dialog and find myself staring into the depths of outer space. Nova is honestly a little weird. Panic is the same company that released a handheld game device with a hand crank... because they could. It's part of the charm.&lt;/p&gt;

&lt;p&gt;Nova has all the things you've come to expect from a modern IDE for JavaScript development. There’s extensions for TypeScript, Prettier and ESLint, Git integration, integrated terminal and development server. You can connect to a variety of servers including Amazon S3, Azure, and Rackspace, or via protocols like SSH, FTP, WebDAV HTTPS. I appreciate this feature for small projects. Panic syncs your server configurations across workstations. The nova command line tool opens files and workspaces from the MacOS Terminal.&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%2Fi%2F87l1gti19ilvmnytiws9.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%2Fi%2F87l1gti19ilvmnytiws9.png" alt="Split Pane Layout in Nova displaying TypeScript and Terminal Output"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;VS Code definitely has some advantages over Nova including better Git integration, a robust debugging experience, large extension ecosystem. Despite all those wonderful features, VS Code pauses briefly before opening some files. VS Code doesn't tokenize large files because it bogs down the application. VS Code feels out of place on MacOS. Nova UI is snappy in comparison. Nova is a very capable JavaScript IDE. The performance of a native app alone is worth it, but the MacOS feel prompted me to switch.&lt;/p&gt;

&lt;p&gt;At $99 ($49 yearly subscription after the first year), Nova is reasonably priced. If you have a serial number from Panic’s legacy IDE Coda, the initial price drops to $79. Nova is a ground up rewrite of Coda.&lt;/p&gt;

&lt;p&gt;If you’re looking for an alternative to VS Code, &lt;a href="https://www.nova.app" rel="noopener noreferrer"&gt;download the free trial of Nova&lt;/a&gt;. You may be surprised how a native IDE can really improve developer experience.&lt;/p&gt;

&lt;p&gt;Disclaimer: I’m not paid by or affiliated with Panic.&lt;/p&gt;

</description>
      <category>typescript</category>
      <category>webdev</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Coding Is About People Too... Ya Know?</title>
      <dc:creator>Stephen Belovarich</dc:creator>
      <pubDate>Wed, 03 Jun 2020 18:22:25 +0000</pubDate>
      <link>https://dev.to/steveblue/coding-is-about-people-too-ya-know-29la</link>
      <guid>https://dev.to/steveblue/coding-is-about-people-too-ya-know-29la</guid>
      <description>&lt;p&gt;We are experiencing an inflection point in American society. Gubernatorial orders to shelter in place have kept us indoors. Those of us fortunate enough to have a job despite 25% unemployment are working from home. We blasted off astronauts in a corporate engineered capsule to the International Space Station while Black Lives Matter protestors flee blasts of flash bang grenades. 2020 is an election year where the President is facing the highest disapproval rating of any President in the history of America. We must fill the void left by our leaders with the voices of the community. &lt;/p&gt;

&lt;p&gt;With so much going on, I beg web developers to take some time away from coding and consider how their words and actions affect other people. So much of the time we are downright myopic, focusing only on syntax, frameworks, and tooling. I truly believe our silence concerning the welfare of fellow Americans makes us complicit in their misfortune. Black people have endured far too many atrocities to bare. Black mothers have to fear their children are next to be murdered facing the lens of an iPhone camera.&lt;/p&gt;

&lt;p&gt;Will the legacy of tech in the lives of black people be the devices that record their murder? &lt;/p&gt;

&lt;p&gt;&lt;iframe class="tweet-embed" id="tweet-1265326913016528900-68" src="https://platform.twitter.com/embed/Tweet.html?id=1265326913016528900"&gt;
&lt;/iframe&gt;

  // Detect dark theme
  var iframe = document.getElementById('tweet-1265326913016528900-68');
  if (document.body.className.includes('dark-theme')) {
    iframe.src = "https://platform.twitter.com/embed/Tweet.html?id=1265326913016528900&amp;amp;theme=dark"
  }



&lt;/p&gt;

&lt;p&gt;Black Lives Matter is about gaining equity for black people. Why are we creating barriers to entry in tech for people of color? How can we change hiring practices to reverse this?&lt;/p&gt;

&lt;p&gt;&lt;iframe class="tweet-embed" id="tweet-1268019656096350210-63" src="https://platform.twitter.com/embed/Tweet.html?id=1268019656096350210"&gt;
&lt;/iframe&gt;

  // Detect dark theme
  var iframe = document.getElementById('tweet-1268019656096350210-63');
  if (document.body.className.includes('dark-theme')) {
    iframe.src = "https://platform.twitter.com/embed/Tweet.html?id=1268019656096350210&amp;amp;theme=dark"
  }



&lt;/p&gt;

&lt;p&gt;What happens when leaders in technology don't recognize they are part of the problem?&lt;/p&gt;

&lt;p&gt;&lt;iframe class="tweet-embed" id="tweet-1267944290770128896-93" src="https://platform.twitter.com/embed/Tweet.html?id=1267944290770128896"&gt;
&lt;/iframe&gt;

  // Detect dark theme
  var iframe = document.getElementById('tweet-1267944290770128896-93');
  if (document.body.className.includes('dark-theme')) {
    iframe.src = "https://platform.twitter.com/embed/Tweet.html?id=1267944290770128896&amp;amp;theme=dark"
  }



&lt;/p&gt;

&lt;p&gt;Coding is about people. Coding provides upward mobility for those who participate. Code is for the benefit of people!&lt;/p&gt;

&lt;p&gt;I'm a teacher. One of my students came up to me after graduation a few years back almost in tears. I asked what was wrong but it turns out these were tears of joy. He explained how he just received a job offer, the first in his family to make over $100,000 salary. This remains a profound moment in my life when I realized there is more to coding.&lt;/p&gt;

&lt;p&gt;How we use the power of the hire is up to us. We have an opportunity to change lives and I fear we'll squander it. &lt;/p&gt;

&lt;p&gt;How can we change hiring practices to be more inclusive of minorities in America? How do we effectively challenge our leaders to enact change? What do you have to contribute to the discussion?&lt;/p&gt;

&lt;p&gt;The system is broken. What are you going to do about it?&lt;/p&gt;

</description>
      <category>discuss</category>
    </item>
    <item>
      <title>Fetching API Data with Web Components and SSR</title>
      <dc:creator>Stephen Belovarich</dc:creator>
      <pubDate>Tue, 12 May 2020 16:50:44 +0000</pubDate>
      <link>https://dev.to/steveblue/fetching-api-data-with-web-components-and-ssr-21mk</link>
      <guid>https://dev.to/steveblue/fetching-api-data-with-web-components-and-ssr-21mk</guid>
      <description>&lt;p&gt;I recently launched a site built entirely with Web Components. Several views where static, meaning I could just server side render them wholesale without must hassle. When it came to the blog I felt stuck. The blog is dependent on the asynchronous response from a REST API. How was I going to server side render this view?&lt;/p&gt;

&lt;p&gt;The solution turned out to be easy!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://i.giphy.com/media/41xF6qBDznhIY/giphy.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://i.giphy.com/media/41xF6qBDznhIY/giphy.gif"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;BlogComponent&lt;/code&gt; shown in the code snippet below is a custom element that represents the view. During the &lt;code&gt;connectedCallback&lt;/code&gt; the method &lt;code&gt;getModel&lt;/code&gt; is called.&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;class&lt;/span&gt; &lt;span class="nc"&gt;BlogComponent&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;HTMLElement&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;getModel&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nf"&gt;getModel&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="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;rej&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="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;http://localhost:4444/api/blog&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;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;renderPosts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nf"&gt;res&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="p"&gt;})&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;rej&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&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;renderPosts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;code&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="nx"&gt;rendering&lt;/span&gt; &lt;span class="nx"&gt;DOM&lt;/span&gt; &lt;span class="nx"&gt;elements&lt;/span&gt;
   &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;lots&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;createElement&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;appendChild&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;yada&lt;/span&gt; &lt;span class="nx"&gt;yada&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;The &lt;code&gt;getModel&lt;/code&gt; method returns a Promise so when the component is server side rendered the Express middleware can wait for &lt;code&gt;getModel&lt;/code&gt; to finish before responding with the rendered view. The important thing to consider here is when server side rendering Express will need to wait for the HTTP request to finish before responding to a client with the rendered HTML for the page. &lt;/p&gt;

&lt;p&gt;In the Promise, I used &lt;code&gt;fetch&lt;/code&gt; to make the HTTP request to &lt;code&gt;/api/blog&lt;/code&gt;. I call &lt;code&gt;res()&lt;/code&gt; after the component renders the view using the data from the JSON response. In this example, &lt;code&gt;renderPosts&lt;/code&gt; is a blocking function. You may use any pattern you see fit. Perhaps you want to implement MVC or service pattern for your components. Go ahead! There just needs to be a way at the component level server side rendering can analyze the class and determine "Does this component require data before I render it?"&lt;/p&gt;

&lt;p&gt;During a build step this component is bundled with every other component and route configuration for server side rendering. I map a reference to each component to the path the user will visit, along with a title that can be viewed in the browser window.&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;routes&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;path&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="na"&gt;component&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;HomeComponent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Home&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;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/blog&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;component&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;BlogComponent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Blog&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;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/404&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;component&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;FileNotFoundComponent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;File Not Found&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;The bundle is imported into Express middleware that handles the server side rendering. For server side rendering custom elements I used the &lt;code&gt;@skatejs/ssr&lt;/code&gt; package. The middleware is below.&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;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@skatejs/ssr/register&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;render&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@skatejs/ssr&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;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;url&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;path&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;fs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;fs&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;routes&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;path/to/bundle.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;indexPath&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;path/to/index.html&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;dom&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&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;indexPath&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;generateHTML&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;template&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;route&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;dom&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;dom&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="s2"&gt;`&amp;lt;title&amp;gt;&amp;lt;/title&amp;gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;`&amp;lt;title&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;route&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;/title&amp;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;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&amp;lt;div id="root"&amp;gt;&amp;lt;/div&amp;gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;`&amp;lt;div id="root"&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;template&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;/div&amp;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;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/__ssr&lt;/span&gt;&lt;span class="se"&gt;\(\)&lt;/span&gt;&lt;span class="sr"&gt;/g&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;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;next&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;let&lt;/span&gt; &lt;span class="nx"&gt;component&lt;/span&gt; &lt;span class="o"&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;route&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;routes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rt&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;rt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&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="nx"&gt;pathname&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;route&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;redirect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;301&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/404&lt;/span&gt;&lt;span class="dl"&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="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;component&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;route&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;component&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;component&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;preRender&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;component&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;preRender&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getModel&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;preRender&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getModel&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;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&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="nx"&gt;template&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&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;preRender&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;generateIndex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;template&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;route&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;dom&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dom&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;async/await made this code somewhat compact. After the middleware establishes a component is mapped to this route by parsing the request url and checking against a route in the imported config, the component is instantiated. If a route doesn't match, the browser will be redirected to a &lt;code&gt;404&lt;/code&gt; route.&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;route&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;routes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rt&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;rt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&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="nx"&gt;pathname&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;route&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;redirect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;301&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/404&lt;/span&gt;&lt;span class="dl"&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="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;component&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;route&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;component&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;component&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;preRender&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;component&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;If a class passes through this middleware has the &lt;code&gt;getModel&lt;/code&gt; method, &lt;code&gt;getModel&lt;/code&gt; is called using the &lt;code&gt;await&lt;/code&gt; keyword. The &lt;code&gt;getModel&lt;/code&gt; method returns a Promise that ensures the component has rendered the template after successfully making the HTTP request. The code is wrapped in a try / catch in case something fails (either the HTTP request or the render method in the component). &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;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;preRender&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getModel&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;preRender&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getModel&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;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&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;But wait, you are calling &lt;code&gt;fetch&lt;/code&gt; from node, but inside code that is normally client side?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://i.giphy.com/media/Zba7dZIYpdwkM90Oag/giphy.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://i.giphy.com/media/Zba7dZIYpdwkM90Oag/giphy.gif"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Remember how I said the components are bundled specifically for server side rendering in a build step? In the entry point for that bundle, I imported &lt;code&gt;node-fetch&lt;/code&gt; and put it on the global namespace.&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;fetch&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;node-fetch&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nb"&gt;global&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;fetch&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;fetch&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;I put objects on the global namespace here normally when I want to mock browser based APIs for server side rendering. &lt;code&gt;node-fetch&lt;/code&gt; is an implementation of the fetch API that allows the component to make the HTTP request in node.&lt;/p&gt;

&lt;p&gt;The next &lt;code&gt;await&lt;/code&gt; that follows is &lt;code&gt;@skatejs/ssr&lt;/code&gt; method for rendering Web Components server side. &lt;code&gt;@skatejs/ssr&lt;/code&gt; is the magic sauce. The &lt;code&gt;render&lt;/code&gt; method takes the component in the first argument and returns the HTML. This HTML will be in the response from the server, but first I have to inject the component HTML into the DOM.&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;template&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&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;preRender&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;generateIndex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;template&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;route&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;dom&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;The &lt;code&gt;generateIndex&lt;/code&gt; method takes the HTML that was retriever earlier in the code from a static file and places the server side rendered HTML in the &lt;code&gt;#root&lt;/code&gt; element. I had to massage the output of the &lt;code&gt;render&lt;/code&gt; method a bit and remove any calls to a &lt;code&gt;__ssr&lt;/code&gt; function that it was injecting for &lt;code&gt;@skatejs&lt;/code&gt; formatted components. The &lt;code&gt;generateHTML&lt;/code&gt; method also sets the content of the &lt;code&gt;title&lt;/code&gt; tag for SEO purposes.&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;generateHTML&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;template&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;route&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;dom&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;dom&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="s2"&gt;`&amp;lt;title&amp;gt;&amp;lt;/title&amp;gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;`&amp;lt;title&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;route&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;/title&amp;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;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&amp;lt;div id="root"&amp;gt;&amp;lt;/div&amp;gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;`&amp;lt;div id="root"&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;template&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;/div&amp;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;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/__ssr&lt;/span&gt;&lt;span class="se"&gt;\(\)&lt;/span&gt;&lt;span class="sr"&gt;/g&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The results are impressive. Express responds with the server side rendered blog posts on the initial request from the browser.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fdhnelbvx33jdh6ls6g1h.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%2Fi%2Fdhnelbvx33jdh6ls6g1h.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Easy Rant
&lt;/h2&gt;

&lt;p&gt;A few months ago I read somewhere it can't be done. "You can't server side render web components" they said, justifying reasoning for sticking with React rather than adopting custom elements v1. &lt;a href="https://dev.to/steveblue/coding-my-personal-site-with-web-components-4941"&gt;One of my latest projects&lt;/a&gt; demonstrates you can not only SSR static pages with custom elements, but also components that are dependent on asynchronous data from a REST API.&lt;/p&gt;

&lt;p&gt;You can do anything with custom elements you can do with component based JavaScript libraries and frameworks, with less code and possibly more performant than Virtual DOM. It really comes down to implementation. In this example I demoed a simple implementation for server side rendering a web component using a class method. You're free to use any pattern. Free as a bird. Don't you want to be a bird?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://i.giphy.com/media/2WYvNJqL8yXLy/giphy.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://i.giphy.com/media/2WYvNJqL8yXLy/giphy.gif"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>webcomponents</category>
      <category>javascript</category>
      <category>node</category>
    </item>
    <item>
      <title>I Coded My Personal Site With Web Components</title>
      <dc:creator>Stephen Belovarich</dc:creator>
      <pubDate>Wed, 06 May 2020 19:43:00 +0000</pubDate>
      <link>https://dev.to/steveblue/coding-my-personal-site-with-web-components-4941</link>
      <guid>https://dev.to/steveblue/coding-my-personal-site-with-web-components-4941</guid>
      <description>&lt;p&gt;Ugh. The chore of refactoring a personal site. We all should do it but who has the time? This is the seventh iteration of my personal site, so I was in dire need of some inspiration. I'm looking for a part-time job teaching web development, so that was some motivation. &lt;/p&gt;

&lt;p&gt;I needed more to get me excited. All it took was solving problems with vanilla JavaScript and CSS. I'm over JavaScript frameworks. I’m done trying to wrangle all the tooling needed to develop modern JavaScript anything. I kept it simple. I designed the site mainly in browser which led to some fits and starts but the payoff was worth it. Little things like the way the nav animates made me excited to keep going. It was nice getting back to my roots. A decade ago I was a one man full service agency, designing and developing marketing sites and e-commerce solutions for small businesses. It helped I was also building something fun.&lt;/p&gt;

&lt;p&gt;The last iteration of the site was very different. At the time in 2015 I wanted to showcase my mastery over AngularJS and WebGL. The site drained your battery due to all the graphics processing, was not very SEO friendly (SPA), and well we know how well AngularJS has aged. Any modern JavaScript framework is a better alternative, however for a site that is mostly static content I decided to move in a different direction.&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%2Fi%2Fxek9ooa4ot3f1m4yk63g.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%2Fi%2Fxek9ooa4ot3f1m4yk63g.gif" alt="scrolling through my new personal site"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Nope, this is not Gatsby. Sorry folks. I don't believe in the hype. I do believe in spec. I decided to implement my entire site with the custom elements v1 spec. As long as the solution could meet several criteria I would be fine.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;be viable five years into the future&lt;/li&gt;
&lt;li&gt;demonstrate my ability to architect and implement UI &lt;/li&gt;
&lt;li&gt;SEO friendly&lt;/li&gt;
&lt;li&gt;fun and easy to update, minimal maintenance&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I chose Web Components instead of a modern JavaScript UI framework for several reasons. Spec is relatively future proof. Browsers will continue to implement the custom elements v1 spec for years to come, while JavaScript frameworks go in and out of fashion. I don't want to be in the same place in five years, regretting that I implemented my site with a JavaScript framework that is no longer supported. No... I want to look back and think "I made it so I only have myself to blame." 🤣&lt;/p&gt;

&lt;p&gt;Last year I developed a microlibrary for coding Web Components with TypeScript decorators called &lt;a href="https://readymade-ui.github.io/readymade/" rel="noopener noreferrer"&gt;Readymade&lt;/a&gt;. I used this library throughout the project, implementing each view and component with Readymade. Readymade is a thin layer around the custom elements spec, reducing some of the typical boilerplate while adding some features with minimal overhead. If I went with another library I would have used Stencil or LitElement, but I chose to stay the course with Readymade despite some shortcomings.&lt;/p&gt;

&lt;p&gt;Coding my personal site with Web Components just feels right. I honestly don't understand the JavaScript community's aversion to custom elements. I definitely prefer this way of working compared to React. There are some quirks when it comes to styling custom elements, but when isn't there? I feel like abstracting CSS to make styles reusable is always a problem no matter what you are doing. With Readymade I get one-way data binding, but the library doesn't have the equivalent of &lt;code&gt;ngFor&lt;/code&gt; in Angular: a way to loop over a data model and bind it to template. That would be handy for dynamic layouts. To be honest that is the only feature I miss from frameworks. I don't miss the dependency management. I don't miss the tooling. I don't miss Virtual DOM. This is a fantastic way to work!&lt;/p&gt;

&lt;p&gt;To make my new site SEO friendly, I needed to server side render each view. Server side rendering Web Components was a breeze. One of the few third party packages I used in the project was &lt;code&gt;@skatejs/ssr&lt;/code&gt;, a package that enables SSR in node.js for the custom elements v1 spec. &lt;/p&gt;

&lt;p&gt;SkateJS needs to interpret the JavaScript for each component, so I bundled the components for the server, exporting each custom element tied to a route. Some of the components act as a view, while other components are smaller units of functionality. I devised a &lt;a href="https://github.com/steveblue/personal/blob/master/src/client/server.ts" rel="noopener noreferrer"&gt;route config&lt;/a&gt; that could be injected into the &lt;a href="https://github.com/steveblue/personal/blob/master/src/server/middleware/ssr.ts" rel="noopener noreferrer"&gt;server-side code&lt;/a&gt;, which is also Express middleware. When a user visits a particular route, SkateJS renders the components on the server and then Express responds to the request for the route with the compiled HTML. One of the pain points for this project was when I ended up having to implement a Router that was compatible with both Readymade and SkateJS. It turned out not to be so bad. &lt;a href="https://github.com/steveblue/personal/blob/master/src/client/app/router/src/router.ts" rel="noopener noreferrer"&gt;76 lines of code later&lt;/a&gt; and I had a router that worked.&lt;/p&gt;

&lt;p&gt;I used &lt;a href="https://parceljs.org/" rel="noopener noreferrer"&gt;Parcel&lt;/a&gt; for a development environment, coupled with a separate node process running an Express server that hosts a REST API. The API is only for the blog page. I display post thumbnails and descriptions from my posts on dev.to. To limit the amount of requests going to the DEV API, I setup my environment to query for the posts and then store them locally in production. I implemented a simple &lt;a href="https://github.com/typicode/lowdb" rel="noopener noreferrer"&gt;JSON database with LowDB&lt;/a&gt; which seems to be proficient for this project.&lt;/p&gt;

&lt;p&gt;Web Components will be supported in web browsers well into the future, so when deciding which client-side technology would be viable in five to ten years custom elements seemed like a no-brainer. I can still do all the things I could have with a JavaScript library, only with a smaller footprint and less tooling. My hands touched virtually everything end to end. I feel like I learned a lot. I had a lot of control over the stack, so I could pivot easily. &lt;/p&gt;

&lt;p&gt;Possibly the best story to come out of this project is about performance. The site when bundled for production weighs in around 23Kb (gzipped). That includes the microlibrary, router, the HTML, CSS, and JavaScript that actually displays content on the page. 23Kb is smaller than some JavaScript libraries. I consider that a huge win. Users don't have to download a ton of JavaScript. The homepage can load ~3.5sec over the "Fast 3G" network emulation in Chrome Dev Tools.&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%2Fi%2F15yoh7raucfgdx9xpnc6.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%2Fi%2F15yoh7raucfgdx9xpnc6.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The site was just released so there are probably some minimal production bugs lurking around in some responsive states and devices. I decided to ship the MVP and circle back later to fix some of these issues.&lt;/p&gt;

&lt;p&gt;Visit &lt;a href="https://stephenbelovarich.com" rel="noopener noreferrer"&gt;stephenbelovarich.com&lt;/a&gt; if you're interested in viewing my new personal site. The &lt;a href="https://github.com/steveblue/personal" rel="noopener noreferrer"&gt;code is public on Github&lt;/a&gt; for anyone interested in what the development environment looks like.&lt;/p&gt;

&lt;p&gt;What do you think?&lt;/p&gt;

</description>
      <category>webcomponents</category>
      <category>webdev</category>
      <category>career</category>
      <category>showdev</category>
    </item>
    <item>
      <title>Are You a Developer or Engineer? Why?</title>
      <dc:creator>Stephen Belovarich</dc:creator>
      <pubDate>Wed, 29 Apr 2020 23:23:09 +0000</pubDate>
      <link>https://dev.to/steveblue/are-you-a-developer-or-engineer-why-5f45</link>
      <guid>https://dev.to/steveblue/are-you-a-developer-or-engineer-why-5f45</guid>
      <description>&lt;p&gt;I consider myself a web engineer, but I'm curious how others perceive their role. Do you consider yourself a developer or engineer (or maybe some other title) and why? Does it matter?&lt;/p&gt;

</description>
      <category>discuss</category>
    </item>
    <item>
      <title>Community Is Everything Right Now</title>
      <dc:creator>Stephen Belovarich</dc:creator>
      <pubDate>Mon, 16 Mar 2020 19:35:07 +0000</pubDate>
      <link>https://dev.to/steveblue/community-is-everything-right-now-3d66</link>
      <guid>https://dev.to/steveblue/community-is-everything-right-now-3d66</guid>
      <description>&lt;p&gt;The DEV community has the opportunity to maintain the status quo during the crisis. DEV can give us a sense of normalcy in the midst of a worldwide pandemic. A lot of us need that. While we adjust to this new reality, we need to consider our local community too. This may not seem evident in a time of panic buying, social distancing and self-quarantines, but community is everything right now. &lt;/p&gt;

&lt;p&gt;While we are advised to refrain from congregating in large groups, to self-quarantine and practice social distancing, we can't forget about our local community. We can't afford to have the mindset: "let someone else take care of the less fortunate". Government will most likely fail to bail us out. We need to band together to make it through the pandemic. &lt;/p&gt;

&lt;p&gt;I urge you to do whatever you can to help your local community. There are several things you can do. Approach a local non-profit or community organization and ask how you can be helpful. Don't assume you know.&lt;/p&gt;

&lt;p&gt;I'm not advocating going out into your community without precaution. I'm not telling you to defy government mandates. Keep calm. Practice social distancing. Use every precaution you can. Most importantly, stay healthy. If you're sick, stay home. Self quarantine. &lt;/p&gt;

&lt;p&gt;What happens when the people who are not prepared whatsoever have to self quarantine? What happens when all the restaurant workers living paycheck to paycheck have to go without pay? There are people in our communities who will need help and have no one to help them.&lt;/p&gt;

&lt;p&gt;This crisis will test what we are made of. Let's not look back on this moment and remember the panic buying. Instead let's remember how we came together to help each other.&lt;/p&gt;

&lt;p&gt;I just made a donation of canned goods to the local food pantry. Now I am taking some time to consider how else I can be helpful. &lt;/p&gt;

&lt;p&gt;What are you doing to help your community?&lt;/p&gt;

&lt;p&gt;How can we use our skills to help others in this crisis?&lt;/p&gt;

</description>
      <category>watercooler</category>
      <category>psa</category>
    </item>
    <item>
      <title>The hidden cost of “don’t reinvent the wheel”</title>
      <dc:creator>Stephen Belovarich</dc:creator>
      <pubDate>Fri, 28 Feb 2020 18:48:14 +0000</pubDate>
      <link>https://dev.to/steveblue/the-hidden-cost-of-don-t-reinvent-the-wheel-1e3l</link>
      <guid>https://dev.to/steveblue/the-hidden-cost-of-don-t-reinvent-the-wheel-1e3l</guid>
      <description>&lt;p&gt;There’s a catchphrase I’ve heard from the even the most experienced web engineers in the past few years.&lt;/p&gt;

&lt;h2&gt;
  
  
  Don’t reinvent the wheel
&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%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fxxshg5qxwyk7iiqrpb3x.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%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fxxshg5qxwyk7iiqrpb3x.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Most of the time when I hear this argument “don’t reinvent the wheel”, it sounds like it comes from a place of comfort. Engineers are comfortable using the existing tool even when it causes bloat, is badly architected, or could be a cause of poor performance experienced by the end user. The user is who really loses when engineers make decisions in their own best interests.&lt;/p&gt;

&lt;p&gt;Maybe the engineer is heavily invested in a framework and doesn’t want to learn something new. The problem could be more systemic. Maybe your company hasn’t moved away from the same stack in several years. These are much harder obstacles to overcome.&lt;/p&gt;

&lt;p&gt;When you have to implement a new feature, more often than not there is a solution already built by the open source community. It seems like a no brainer. Use the tool. There are some things you ought to consider before pulling a library off the shelf.&lt;/p&gt;

&lt;h2&gt;
  
  
  The learning curve
&lt;/h2&gt;

&lt;p&gt;When you adopt open source tools you often don't know what you are getting. The README highlights all the wonderful things you will get.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://i.giphy.com/media/NT239BZIrr2W3TdSin/giphy.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://i.giphy.com/media/NT239BZIrr2W3TdSin/giphy.gif"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Installing the package is the first step. You follow the instructions step by step in the README until you run into a problem where the code doesn’t work. You look for documentation. It’s not really there. You google it. Nothing. You dig through the issues on Github. Nada. Finally you decide to look at the source code to scan for issues. The coding style is foreign to you. Stepping through the code you notice it has been optimized.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://i.giphy.com/media/W9wKBo6K06Xv2/giphy.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://i.giphy.com/media/W9wKBo6K06Xv2/giphy.gif"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Never underestimate the time it takes to learn a new tool. Sometimes it takes awhile before you fully understand the impact of using an open source package. You can hit roadblocks you didn’t anticipate.&lt;/p&gt;

&lt;h2&gt;
  
  
  Customization can be difficult
&lt;/h2&gt;

&lt;p&gt;Even when a npm package does the job extremely well, there are parts of the tool that don't fit with the company's business logic or design patterns. Forking an open source project could be an option, but who want's to maintain that? You might think the feature your team needs could be contributed back to the open source project, but sometimes that contribution could be met with opposition. &lt;/p&gt;

&lt;p&gt;A lot of the time engineers use open source tools to implement UI, thinking it fast tracks development in some way. This could mean using a chart library or bootstrap. I have heard engineers say "tough luck if it doesn't conform to the design styleguide, we need to push out a feature this sprint". That's just not cool. As engineers we should be working together with design and UX professionals to implement solutions for the end user. The decision to buy instead of build could come at the cost of collaboration. The tool could speed up delivery, but how will it look in front of leadership when you have to explain you didn't listen to design and UX?&lt;/p&gt;

&lt;h2&gt;
  
  
  Innovation is stifled
&lt;/h2&gt;

&lt;p&gt;The heavy adoption of open source tools comes at another cost. We are stifling innovation. When nearly everyone is learning the hottest new JavaScript library instead of fundamentals, we lose the skills required to build something from scratch. We lose our connection to the language itself because we are always interacting with an abstraction: the JavaScript framework. The industry is stuck the patterns of the library. We have the entire history of computer science to draw from and opportunities to design new patterns, but all I hear is "redux".&lt;/p&gt;

&lt;p&gt;Some of us poke our heads out every once in awhile and say "we can do it different", but do the rest of us hear their message?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://i.giphy.com/media/l2JefPZCmOlpYY9MY/giphy.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://i.giphy.com/media/l2JefPZCmOlpYY9MY/giphy.gif"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Bloat
&lt;/h2&gt;

&lt;p&gt;A more obvious cost of bundling libraries with our applications is the resulting bloat. Several third party libraries cannot be treeshaken. You can't pull in only the parts of the library you want to use. Instead you have to bundle the entire library with your application.&lt;/p&gt;

&lt;p&gt;The more dependencies, the larger the bundle, the longer it takes the end user to download the JavaScript. This is particularly important for mobile users that make up roughly 50% of global traffic. It's highly possible a homegrown solution means less code and a lighter bundle. &lt;/p&gt;

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

&lt;p&gt;"Don't reinvent the wheel" is a phrase I've heard countless times over the years. I am told if a library exists, use it. Then I go and implement a custom solution anyways. The result usually means less code, customized for a set the business logic. I write documentation so the learning curve is shallow. The willingness to architect and implement a custom solution maybe one key difference between Senior and Junior engineers or even Principal and Senior engineers.&lt;/p&gt;

&lt;p&gt;In the past few years browser APIs have evolved. We have several tools baked into the browser that JavaScript libraries ignore. As evergreen browsers become more prevalent, it's time we start using these APIs more. &lt;/p&gt;

&lt;p&gt;Last year when I was coding &lt;a href="https://readymade-ui.github.io/readymade/" rel="noopener noreferrer"&gt;Readymade&lt;/a&gt;, I implemented one-way data binding with a minimal amount of code using ES2015 Proxy. The library overcomes limitations of DOM events by using the BroadcastChannel API for events. A "hello world" to generate a Readymade component is ~1Kb, gzipped. When implementing all the Readymade features, the bundle is slightly larger. This is just one example of how vanilla JavaScript and browser API can reduce the bundle size. Without a custom solution, I would have very few options for custom element development that didn't generate more library code. I wouldn't have been able to define the developer experience and make the API easy to use.&lt;/p&gt;

&lt;p&gt;I can hear it now. "Why didn't you use Svelte?"&lt;/p&gt;

&lt;p&gt;I'm confident if more people in our industry took the time to learn browser API and JavaScript we could further innovation. Instead of focusing all of your energy mastering libraries, consider how you would implement a library without dependencies. Maybe the result will be something lighter, faster, and delightful to use.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://i.giphy.com/media/l1J9rz4tPqzGWZZMQ/giphy.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://i.giphy.com/media/l1J9rz4tPqzGWZZMQ/giphy.gif"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
    </item>
  </channel>
</rss>
