title: Understanding Kristofer Joseph's Single File Web Component
url: https://til.simonwillison.net/web-components/understanding-single-file-web-component
hash_url: 8016f83f512107dd26e5780b08ea7305

<p><a href="https://twitter.com/brianleroux/status/1453472609518034944" rel="nofollow">Via Brian LeRoux</a> I found <a href="https://gist.github.com/kristoferjoseph/c4e47389ae0f0447db175b914e471628">single-file-web-component.html</a> by Kristofer Joseph. It's really clever! It demonstrates how to build a <code>&lt;hello-world&gt;&lt;/hello-world&gt;</code> custom Web Component in a single HTML file, using some neat tricks.</p>
<p>For my own education I spent some time picking it apart and built my own annotated version of the code showing what I learned.</p>
<p>Justin Fagnani <a href="https://twitter.com/justinfagnani/status/1453531485240102916" rel="nofollow">provided useful feedback</a> on this on Twitter.</p>
<div class="highlight highlight-text-html-basic"><pre><span class="pl-c1">&lt;!DOCTYPE html<span class="pl-kos">&gt;</span></span>
<span class="pl-kos">&lt;</span><span class="pl-ent">html</span> <span class="pl-c1">lang</span>="<span class="pl-s">en</span>"<span class="pl-kos">&gt;</span>
  <span class="pl-kos">&lt;</span><span class="pl-ent">head</span><span class="pl-kos">&gt;</span>
    <span class="pl-kos">&lt;</span><span class="pl-ent">meta</span> <span class="pl-c1">charset</span>="<span class="pl-s">UTF-8</span>" /&gt;
    <span class="pl-kos">&lt;</span><span class="pl-ent">title</span><span class="pl-kos">&gt;</span>Single File Web Component<span class="pl-kos">&lt;/</span><span class="pl-ent">title</span><span class="pl-kos">&gt;</span>
    <span class="pl-kos">&lt;</span><span class="pl-ent">style</span><span class="pl-kos">&gt;</span>
      <span class="pl-ent">body</span> {
        <span class="pl-c1">background-color</span><span class="pl-kos">:</span> <span class="pl-pds"><span class="pl-kos">#</span>eee</span>;
        <span class="pl-c1">font-family</span><span class="pl-kos">:</span> Helvetica<span class="pl-kos">,</span> sans-serif;
      }
      <span class="pl-ent">h1</span> {
        <span class="pl-c1">color</span><span class="pl-kos">:</span> blue;
        <span class="pl-c1">background-color</span><span class="pl-kos">:</span> pink;
      }
    <span class="pl-kos">&lt;/</span><span class="pl-ent">style</span><span class="pl-kos">&gt;</span>
  <span class="pl-kos">&lt;/</span><span class="pl-ent">head</span><span class="pl-kos">&gt;</span>
  <span class="pl-kos">&lt;</span><span class="pl-ent">body</span><span class="pl-kos">&gt;</span>
    <span class="pl-kos">&lt;</span><span class="pl-ent">template</span> <span class="pl-c1">id</span>="<span class="pl-s">single-file</span>"<span class="pl-kos">&gt;</span>
      <span class="pl-kos">&lt;</span><span class="pl-ent">style</span><span class="pl-kos">&gt;</span>
        <span class="pl-c">/*</span>
<span class="pl-c">          These styles affect only content inside the shadow DOM.</span>
<span class="pl-c"></span>
<span class="pl-c">          Styles in the outside document mostly do not affect stuff</span>
<span class="pl-c">          here, but there are some exceptions: font-family affects</span>
<span class="pl-c">          this &lt;h1&gt; for example. I don't understand the rules here.</span>
<span class="pl-c">        */</span>
        <span class="pl-ent">h1</span> {
          <span class="pl-c1">color</span><span class="pl-kos">:</span> red;
        }
      <span class="pl-kos">&lt;/</span><span class="pl-ent">style</span><span class="pl-kos">&gt;</span>

      <span class="pl-kos">&lt;</span><span class="pl-ent">h1</span><span class="pl-kos">&gt;</span>Hello world (web component)<span class="pl-kos">&lt;/</span><span class="pl-ent">h1</span><span class="pl-kos">&gt;</span>
      <span class="pl-c">&lt;!--</span>
<span class="pl-c">      This code still works if you remove the type="module" parameter.</span>
<span class="pl-c"></span>
<span class="pl-c">      Using that parameter enables features such as 'import ... from'</span>
<span class="pl-c"></span>
<span class="pl-c">      More importantly it stops variables inside the script tag from</span>
<span class="pl-c">      leaking out to the global scope - if you remove type="module"</span>
<span class="pl-c">      from here then the HelloWorld class becomes visible.</span>
<span class="pl-c">      --&gt;</span>
      <span class="pl-kos">&lt;</span><span class="pl-ent">script</span> <span class="pl-c1">type</span>="<span class="pl-s">module</span>"<span class="pl-kos">&gt;</span>
        <span class="pl-k">class</span> <span class="pl-v">HelloWorld</span> <span class="pl-k">extends</span> <span class="pl-v">HTMLElement</span> <span class="pl-kos">{</span>
          <span class="pl-en">constructor</span><span class="pl-kos">(</span><span class="pl-kos">)</span> <span class="pl-kos">{</span>
            <span class="pl-c">/*</span>
<span class="pl-c">              If you remove the call to super() here Firefox shows an error:</span>
<span class="pl-c">              "Uncaught ReferenceError: must call super constructor before</span>
<span class="pl-c">              using 'this' in derived class constructor'"</span>
<span class="pl-c">            */</span>
            <span class="pl-smi">super</span><span class="pl-kos">(</span><span class="pl-kos">)</span><span class="pl-kos">;</span>
            <span class="pl-k">const</span> <span class="pl-s1">template</span> <span class="pl-c1">=</span> <span class="pl-smi">document</span><span class="pl-kos">.</span><span class="pl-en">getElementById</span><span class="pl-kos">(</span><span class="pl-s">"single-file"</span><span class="pl-kos">)</span><span class="pl-kos">;</span>
            <span class="pl-c">/*</span>
<span class="pl-c">              mode: 'open' means you are allowed to access</span>
<span class="pl-c">              document.querySelector('hello-world').shadowRoot to get</span>
<span class="pl-c">              at the DOM inside. Set to 'closed' and the .shadowRoot</span>
<span class="pl-c">              property will return null.</span>
<span class="pl-c">            */</span>
            <span class="pl-smi">this</span><span class="pl-kos">.</span><span class="pl-en">attachShadow</span><span class="pl-kos">(</span><span class="pl-kos">{</span> <span class="pl-c1">mode</span>: <span class="pl-s">"open"</span> <span class="pl-kos">}</span><span class="pl-kos">)</span><span class="pl-kos">.</span><span class="pl-en">appendChild</span><span class="pl-kos">(</span>
              <span class="pl-s1">template</span><span class="pl-kos">.</span><span class="pl-c1">content</span><span class="pl-kos">.</span><span class="pl-en">cloneNode</span><span class="pl-kos">(</span><span class="pl-c1">true</span><span class="pl-kos">)</span>
            <span class="pl-kos">)</span><span class="pl-kos">;</span>
            <span class="pl-c">/* </span>
<span class="pl-c">              template.content is a 'DocumentFragment' array.</span>
<span class="pl-c"></span>
<span class="pl-c">              template.content.cloneNode() without the true performs</span>
<span class="pl-c">              a shallow clone, which provides a empty DocumentFragment</span>
<span class="pl-c">              array.</span>
<span class="pl-c"></span>
<span class="pl-c">              template.content.cloneNode(true) provides one with 6 items</span>
<span class="pl-c">            */</span>
          <span class="pl-kos">}</span>
          <span class="pl-en">connectedCallback</span><span class="pl-kos">(</span><span class="pl-kos">)</span> <span class="pl-kos">{</span>
            <span class="pl-c">// https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_custom_elements#using_the_lifecycle_callbacks</span>
            <span class="pl-smi">console</span><span class="pl-kos">.</span><span class="pl-en">log</span><span class="pl-kos">(</span><span class="pl-s">"Why hello there 👋"</span><span class="pl-kos">)</span><span class="pl-kos">;</span>
          <span class="pl-kos">}</span>
        <span class="pl-kos">}</span>
        <span class="pl-s1">customElements</span><span class="pl-kos">.</span><span class="pl-en">define</span><span class="pl-kos">(</span><span class="pl-s">"hello-world"</span><span class="pl-kos">,</span> <span class="pl-v">HelloWorld</span><span class="pl-kos">)</span><span class="pl-kos">;</span>
      <span class="pl-kos">&lt;/</span><span class="pl-ent">script</span><span class="pl-kos">&gt;</span>
    <span class="pl-kos">&lt;/</span><span class="pl-ent">template</span><span class="pl-kos">&gt;</span>

    <span class="pl-kos">&lt;</span><span class="pl-ent">h1</span><span class="pl-kos">&gt;</span>This is not a web component<span class="pl-kos">&lt;/</span><span class="pl-ent">h1</span><span class="pl-kos">&gt;</span>

    <span class="pl-kos">&lt;</span><span class="pl-ent">hello-world</span><span class="pl-kos">&gt;</span><span class="pl-kos">&lt;/</span><span class="pl-ent">hello-world</span><span class="pl-kos">&gt;</span>

    <span class="pl-kos">&lt;</span><span class="pl-ent">script</span><span class="pl-kos">&gt;</span>
      <span class="pl-k">const</span> <span class="pl-s1">sf</span> <span class="pl-c1">=</span> <span class="pl-smi">document</span><span class="pl-kos">.</span><span class="pl-en">getElementById</span><span class="pl-kos">(</span><span class="pl-s">"single-file"</span><span class="pl-kos">)</span><span class="pl-kos">;</span>
      <span class="pl-c">/*</span>
<span class="pl-c">        Before executing this line, sf.content.lastElementChild</span>
<span class="pl-c">        is the &lt;script type="module"&gt; element hidden away inside</span>
<span class="pl-c">        the &lt;template&gt; - we remove it from the template here and</span>
<span class="pl-c">        append it to the document.body, causing it to execute in</span>
<span class="pl-c">        the main document and activate the &lt;hello-world&gt; tag.</span>
<span class="pl-c">      */</span>
      <span class="pl-smi">document</span><span class="pl-kos">.</span><span class="pl-c1">body</span><span class="pl-kos">.</span><span class="pl-en">appendChild</span><span class="pl-kos">(</span><span class="pl-s1">sf</span><span class="pl-kos">.</span><span class="pl-c1">content</span><span class="pl-kos">.</span><span class="pl-c1">lastElementChild</span><span class="pl-kos">)</span><span class="pl-kos">;</span>
    <span class="pl-kos">&lt;/</span><span class="pl-ent">script</span><span class="pl-kos">&gt;</span>
  <span class="pl-kos">&lt;/</span><span class="pl-ent">body</span><span class="pl-kos">&gt;</span>
<span class="pl-kos">&lt;/</span><span class="pl-ent">html</span><span class="pl-kos">&gt;</span></pre></div>


<p class="created">Created 2021-10-27T17:33:08-07:00, updated 2021-10-27T19:48:23-07:00 · <a href="https://github.com/simonw/til/commits/main/web-components/understanding-single-file-web-component.md">History</a> · <a href="https://github.com/simonw/til/blob/main/web-components/understanding-single-file-web-component.md">Edit</a></p>
