-
-
Save simonw/2df444ce0bd75c8bf91beb7a6516ba5b to your computer and use it in GitHub Desktop.
<!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 these, | |
with the exception of inheritable styles such as color, | |
font and line-height. | |
*/ | |
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> |
Do you understand why the script is inside the
<template>
element, rather than immediately following it? Seems like that would avoid the need to append it onto the body.
This was an idea about how to ship .html
files that work as single file components.
Everything a web component needs would be shipped together as a template tag.
The host HTML page then would only need to take the script tags from all the templates in the page and append them to it's body to instantiate every web component on the page.
This is a way to illustrate how to use technology that already exists in the browser to eliminate the need for a complicated build system or framework. As simple as a single file component could get.
Obviously this omits the server that would need to emit the template tags but it hopefully is enough to demonstrate a simple path is possible.
In fact this proof of concept was the inspiration for https://enhance.dev which takes this simple idea and takes it to a logical conclusion.
No build step using only web standards and can leverage single file components as HTML files.
Do you understand why the script is inside the
<template>
element, rather than immediately following it? Seems like that would avoid the need to append it onto the body.