I'm building a small application, and planning to use web components (rather than use a UI library). I don't plan to use any bundlers etc., as this is going to be a small personal site.
I would like to store each web component in a separate ES6 JS module file, and here is an example of my setup:
hello-planet.mjs
export class HelloPlanet extends HTMLElement {
constructor() {
super();
}
connectedCallback(){
// Attach a shadow root to the element.
let shadowRoot = this.attachShadow({mode: 'open'});
let planet = this.getAttribute('planet')
shadowRoot.innerHTML = `<p>hello ${planet}</p>`;
}
}
hello-world.mjs
export class HelloWorld extends HTMLElement {
constructor(){
super()
}
connectedCallback(){
// Attach a shadow root to the element.
let shadowRoot = this.attachShadow({mode: 'open'});
['Mercury','Venus','Earth','Mars','Jupiter','Saturn','Uranus','Neptune'].forEach(planet=>{
let el = document.createElement('hello-planet')
el.setAttribute('planet', planet);
shadowRoot.appendChild(el)
})
}
}
main.mjs
// ordering of imports does not matter
import {HelloPlanet} from './hello-planet.mjs';
import {HelloWorld} from './hello-world.mjs';
customElements.define('hello-world', HelloWorld);
customElements.define('hello-planet', HelloPlanet);
// this will typically be handled by a client side router (e.g. Navigo)
let el = document.createElement('hello-world');
let body = document.querySelector('body');
body.appendChild(el);
index.html (only calls main.mjs, browser will download the rest of the scripts)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="main.mjs" type="module"></script>
<title>Web components test</title>
</head>
<body>
</body>
</html>
Questions:
Thank you!
- I've not seen this approach in any examples I've encountered so far, so wondering if is a good approach? Or there is a better approach than this in terms of organizing web components.
It's perfectly fine. Creating your elements programmatically has many advantages, mainly there is no need to query your own shadow root to get access to child elements/components. If need be, you can directly hold references or even create those in class properties, e.g.:
export class HelloWorld extends HTMLElement {
planets = ['Mercury', 'Venus', 'Earth', 'Mars', 'Jupiter', 'Saturn', 'Uranus', 'Neptune']
.map(
planet => Object.assign(document.createElement('hello-planet'), { planet })
)
)
constructor() {
super().attachShadow({ mode: 'open' }).append(...this.planets);
}
}
Sidenote: Creating the shadow root can and should safely be done in the constructor.
- When I need template+styles, how can this approach be extended to read those from different files i.e. html and css in separate files (so we have separation of concerns)?
For CSS, we have CSS module scripts:
import styles from './hello-world.css' assert { type: 'css' }
then, in your constructor, do
constructor() {
// ...
this.shadowRoot.adoptedStylesheets.push(styles);
}
For HTML, this importing feature unfortunately is still work in progress.