title: Understanding Kristofer Joseph’s Single File Web Component
url: https://til.simonwillison.net/web-components/understanding-single-file-web-component
hash_url: 8016f83f51
Via Brian LeRoux I found single-file-web-component.html by Kristofer Joseph. It's really clever! It demonstrates how to build a <hello-world></hello-world>
custom Web Component in a single HTML file, using some neat tricks.
For my own education I spent some time picking it apart and built my own annotated version of the code showing what I learned.
Justin Fagnani provided useful feedback on this on Twitter.
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <title>Single File Web Component</title> <style> body { background-color: #eee; font-family: Helvetica, sans-serif; } h1 { color: blue; background-color: pink; } </style> </head> <body> <template id="single-file"> <style> /* These styles affect only content inside the shadow DOM. Styles in the outside document mostly do not affect stuff here, but there are some exceptions: font-family affects this <h1> for example. I don't understand the rules here. */ h1 { color: red; } </style> <h1>Hello world (web component)</h1> <!-- This code still works if you remove the type="module" parameter. Using that parameter enables features such as 'import ... from' More importantly it stops variables inside the script tag from leaking out to the global scope - if you remove type="module" from here then the HelloWorld class becomes visible. --> <script type="module"> class HelloWorld extends HTMLElement { constructor() { /* If you remove the call to super() here Firefox shows an error: "Uncaught ReferenceError: must call super constructor before using 'this' in derived class constructor'" */ super(); const template = document.getElementById("single-file"); /* mode: 'open' means you are allowed to access document.querySelector('hello-world').shadowRoot to get at the DOM inside. Set to 'closed' and the .shadowRoot property will return null. */ this.attachShadow({ mode: "open" }).appendChild( template.content.cloneNode(true) ); /* template.content is a 'DocumentFragment' array. template.content.cloneNode() without the true performs a shallow clone, which provides a empty DocumentFragment array. template.content.cloneNode(true) provides one with 6 items */ } connectedCallback() { // https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_custom_elements#using_the_lifecycle_callbacks console.log("Why hello there 👋"); } } customElements.define("hello-world", HelloWorld); </script> </template> <h1>This is not a web component</h1> <hello-world></hello-world> <script> const sf = document.getElementById("single-file"); /* Before executing this line, sf.content.lastElementChild is the <script type="module"> element hidden away inside the <template> - we remove it from the template here and append it to the document.body, causing it to execute in the main document and activate the <hello-world> tag. */ document.body.appendChild(sf.content.lastElementChild); </script> </body> </html>
Created 2021-10-27T17:33:08-07:00, updated 2021-10-27T19:48:23-07:00 · History · Edit