Here I am playing around with an approach for generating (mostly static) pages
via Facet Web Components that
nontheless also work without JavaScript.
This is achieved by a middleware in the server that finds all Facet component
definitions in the returned HTML and inserts them as a declarative shadow dom
into the usages.
While doing so, it strips out any <script> elements, and
inserts transitively used components.
If and when the JavaScript loads, all the declarative shadow DOMs get replaced
by the Facet component, restoring interactivity.
To prevent having to include the component definitions into every page, the server also appends a common HTML file into before processing. This leads to absolutely awful HTML, that is still parsed correctly.
The following is an example of a simple interactive custom element.
It's implementation looks something like this:
<template component="press-counter">
Pressed
<button>
+
<script on="click">
const span = root.querySelector("#counter");
span.innerText = +span.innerText + 1;
</script>
</button> <span id="counter">0</span> times.
</template>
It can be invoked like any other HTML element, i.e. <press-counter></press-counter>.
Global mixins are also supported, allthough the prepend attribute
is ignored.
This is used to consolidate the style between my components with their Shadow
DOM and the page specific content in the lightdom.
For this, I defined the style as usual with a <link
rel="stylesheet"> element in the <head>, and
<style> elements in a global mixin, which imports the same
stylesheet as the <head>.
This looks like this:
<template mixin="style" global>
<style>
@import "/style.css" layer;
</style>
</template>
The @import at-rule is usually hard to use correctly, because it
leads to resources which are only discovered late in the rendering of the page.
Here we are loading exactly the same stylesheet which was already loaded in the
<head> which is then simply re-used without any extra network
requests.