A Greenwood based demonstration repo showcase full-stack "web modules" (HTML / CSS / JS) using Import Attributes.
You will need at least Node >= 21.0.0 to run the demo. If you are using nvm, you can just run nvm use
.
- Clone the repo
- Run
npm ci
To view the demo, run npm start
and open http://localhost:1984
in your browser.
The Hero component defines all its assets in standalone files and uses import
to pull them into the custom element definition using the attributes syntax.
import sheet from './hero.css' with { type: "css" };
import template from "./hero.html" with { type: "html" };
import json from "./hero.json" with { type: "json" };
export default class HeroBanner extends HTMLElement {
connectedCallback() {
if (!this.shadowRoot) {
this.attachShadow({ mode: 'open' });
this.shadowRoot.appendChild(template.content.cloneNode(true));
}
this.shadowRoot.adoptedStyleSheets = [sheet];
}
}
customElements.define('app-hero', HeroBanner);
- CSS and JSON are leveraging web standard APIs. In the case of CSS, using Constructable Stylesheets
- HTML is based on a proposal being drafted for the W3C which would return a
<template>
element.
With these patches up-streamed, Greenwood and WCC will be able to support CSS and JSON out of the box, and HTML through a plugin. This includes rendering in the browser and on the server, e.g. NodeJS.
CSS | JSON | HTML | |
---|---|---|---|
CSR | ✅ | ✅ | ✅ |
SSR | ✅ | ✅ | ✅ |
A cross-section of where browsers stand on supporting these capabilities
CSS | JSON | HTML | |
---|---|---|---|
Chrome | ✅ | ✅ | 🚫 |
FF | ✅ | ✅ | 🚫 |
Safari TP | ✅ | ✅ | 🚫 |
Safari 17.x | ✅ | ✅ | 🚫 |
Safari 16.x | ❓ | ❓ | 🚫 |
Safari 15.x | 🚫 | 🚫 | 🚫 |
While this demonstration repo only deals with templates that are static, dynamic templating is also a goal, although it, like HTML modules, needs to be standardized (see the DOM Parts proposal). The hope is that soon a component author will be able to do this
<div class="hero">
<h2>{{ heading }}</h2>
<!-- ... -->
</div>
import template from "./hero.html" with { type: "html" };
export default class HeroBanner extends HTMLElement {
connectedCallback() {
if (!this.shadowRoot) {
template.replace('heading', this.getAttribute('heading'));
this.attachShadow({ mode: 'open' });
this.shadowRoot.appendChild(template.content.cloneNode(true));
}
this.shadowRoot.adoptedStyleSheets = [sheet];
}
}
customElements.define('app-hero', HeroBanner);