A new Web Components wonder
Heavily inspired by the recent Vue 3 announcement, uce-template promises a very similar DX, also defeating most pain-points of the modern Web.
Some background
There are very few developers that fully understand the purpose and context of Web Components, and few others not super happy about the current state.
If we also consider the controversial history of the Web Components umbrella, where HTML Imports got removed, Custom Elements shipped in two different versions, and one browser vendor doesn’t want to fully comply with latest specs, plus the fact that partial Templates updates are still nowhere, we can easily sympathize with the confusion and skepticism that surrounds this part of the standard Web, but let’s start clarifying a few things before continuing:
- developers usually go “all-in or nothing” with any technology, library, or framework, they choose, like “if I can’t do everything with this pattern, I’ll use another pattern”. But here’s the catch: Web Components are not meant to replace everything we are doing, are instead something very easy to integrate within everything we’re doing already (React, Vue, Svelte, Vanilla). We still inevitably end up writing HTML and CSS to structure pages and views, after all, and Custom Elements there are just a portable
<tag-name>
that can be used like any other<tag>
… it’s not that suddenly everything has to be a<custom-tag>
, can we agree on this? - accessibility is likely the most underrated topic on the Web, but shipping inaccessible Web Components is exactly the same as shipping inaccessible layout that use
<div class="button">
instead of<button>
, forgetsaria-x
attributes, forgets focus area, keyboard navigation, and so on and so fort. And while Custom Elements builtin extends provide a better mechanism to create accessible components out of the box, ’cause a<button>
would work exactly like a<button is="custom-button">
, yet if a button is used when a link would’ve been more appropriate, or vice-versa, we’re still shipping inaccessible, not user friendly, Web pages, nothing can help us. Accordingly, it’s our duty to grant great accessibility with every element we place on a page, and Web Components cannot take really any blame if we mess that part up. - requiring JS is inevitable, and there is no real-world use-case for “the user has no JavaScript”, simply because that’s the only programming language we have there, and we should stop considering JS a second citizen of the Web. It’s actually a very first class citizen just like HTML and CSS: these technologies are the Web, and there wouldn’t be any Web without JS, as there couldn’t be any web without HTML or CSS ( “wait, what?” … bear with me …)
I am not saying a Web page cannot contain just HTML, I am saying that every page will use CSS too because that’s backed in the browser, since all native elements have their own styles anyway, and all native elements also have their listeners, DOM events, and reaction that are likely JS driven or, since JS is the only way we, developers, have to gracefully enhance anything, we gotta know at least JS basics too. To summarize my thoughts around this topic: “if you go to the gym you shouldn’t skip leg day” it’s the same as “if you’d like to learn Web technologies you shouldn’t skip JS”.
Simple and clear: you can have a great looking document with just HTML and CSS, but you can do that also with Word or any software that targets PDFs, but as soon as you need any dynamic behavior, you’re out of luck without JS.
That’s out of control anyway …
I hear you, and I agree, it’s been years articles regarding “JS fatigue” appear every second week, and if you check the amount of competing libraries and frameworks, and the list is not even entirely there, wait ‘till you learn most of those libraries and frameworks require tooling just to show a “Hello world”, which includes a whole new level of extra learning and friction.
But since I am also a bit nostalgic, and I miss that “getting started” simplicity that made me love the Web in the first place, pretty much everything I publish in GitHub requires no tools and no transpilers to start getting some shit done!
Following the Web standards pattern, my libraries can also be incrementally integrated without requiring a complete rewrite of whatever is there already: static? dynamic? framework? library? No problems at all!
… well, I bring this philosophy with me daily:
And that’s how a new star in the Web Components’ universe was born!
The uce-template strength
With no strings attached, uce-template grants the following:
- no tools/tooling needed, but tooling friendly, and with a single tool included in the package (it’s a lib and a CLI optionally handy)
- no polyfills needed: it’s a single script away that can be imported or included in the HTML, and that’s literally it, the rest is HTML
- compatible with IE11, Safari, WebKit, Mobile, and Desktop
- it’s just ~10K all inclusive, actually ~7K for modern browsers
- cross-platform standalone components with or without CSS or JS
Yes, that’s correct, neither CSS or JS are strictly needed but there to help out without even needing to learn anything about Custom Elements at all.
<!-- multi times usage -->
<button is="rain-bow">
🦄 I am rainbow
</button><!-- definition -->
<template is="uce-template">
<button is="rain-bow"></button>
<style>
button[is="rain-bow"] {
font-weight: bold;
background-color: #91d370;
background-image: linear-gradient(
319deg, #91d370 0%, #bca0ff 37%, #f2cd54 100%
);
}
</style>
</template>
Incredibly simple, isn’t it? We can define within the current HTML page, through standard HTML tags, how our component would look like, and this is scratching only the surface of what’s possible with uce-template!
Components as .uce files
Being able to define components within a specific page is already cool, but to make our components really portable, how about having these in portable files that any HTML could use, without repeating the definition all over?
After all, what we care about is just their definition, so that having it a part helps separating concerns, where the page is just the view, and components can land at any time if found in such page or not.
So here what our rain-bow.uce
file would look like:
<button is="rain-bow"></button>
<style>
button[is="rain-bow"] {
font-weight: bold;
background-color: #91d370;
background-image: linear-gradient(
319deg, #91d370 0%, #bca0ff 37%, #f2cd54 100%
);
}
</style>
And in the examples section of the project, all instructions about how automatically load these components only once any of them has been found in the current page, so here we have already:
- zero JS needed, except a single script in the HTML page
- zero Custom Elements knowledge required
- maximum freedom in defining components that even if the whole JS would break, won’t mess up with how the page should natively look and behave
And there’s more to it …
Scoped and Shadowed style
If instead of being a builtin extend, our component is just a regular element, it is possible to automatically prefix its style definition via the scoped
attribute:
<my-unicorn></my-unicorn>
<style scoped>
button {
font-weight: bold;
background-color: #91d370;
background-image: linear-gradient(
319deg, #91d370 0%, #bca0ff 37%, #f2cd54 100%
);
}
</style>
The result is pretty much the same, except if there’s no button within the component, nothing will be styled. Please note no other button is affected.
As extra feature, style-wise, if the element has a shadow
attribute in its definition, and the style also has a shadow
property, the style will be injected on top of the component ShadowDOM root element, so that nothing around should affect its look, even worst obtrusive CSS.
Enough with the CSS part though, it’s time to reveal the “most scary part” this tiny library offers … ready?
The JS side
Listeners, reactive changes, validation, or data handling, once we add JS to the equation the potentials of each component become unimaginable, and here all it takes to add some listener to our component:
<my-unicorn>
You clicked me
<button onclick={{inc}}>
🦄 {{state.times}}
</button>
times
</my-unicorn>
<script type="module">
import {reactive} from '@uce';
export default {
setup() {
const state = reactive({ times: 0 });
const inc = () => state.times++;
return {inc, state};
}
};
</script>
And that’s pretty much it! As we can see in this live demo, to enrich some component with dynamic parts, all we have to do is to export its own definition, where the setup method, if defined, can arbitrary return an object that will be passed along to the component as is, and if such object has some reactive state, in this case with a times
property that can be incremented via a click
, the component layout will be automatically updated per each state change.
All dynamic parts can be confined within {{...}}
brackets, and anything written in there will be just real JS, so that falling back to any value would be that simple:
<user-details>
User: {{user.name || 'anonymous'}}
Role: {{user.role || 'guest'}}
</user-details>
Math operations, callbacks, everything needed, really, can be written within those double brackets, but the simpler we keep it in there, the better, for layout clarity sake 😉
About the module system
Same way I’ve imported reactive
from '@uce'
, we can define anything we need/like to import ahead of time and even asynchronously, so that no extra JS would ever be needed until one component is shown in the current, or future, visited page.
Please check again the How to / Examples section to know more about dependencies.
The CLI / Command Line Interface
This time described in the Getting Started section, the uce-template --help
would already show which option can be passed along:
uce-template v0.1.31
https://github.com/webreflection/uce-templateusage
uce-template [options] file.html
cat file.html | uce-template [options]options
-h --help this message
--babel targets ES5
--debug to not minify JS
-o output file
This CLI does one thing only: it creates optimized HTML, CSS, and JS files for either a whole HTML file, or any .uce
component definition, so that what gets shipped in production would be as tiny as possible, for blazing fast performance and less bandwidth usage for Mobile 🎉
If the --babel
flag is passed, the generated JS would be automatically transpiled to be compatible with IE11, because of course we need to keep in mind that if we write modern JS, IE11 will not be happy about it.
However, if we write IE11 compatible JS within our definitions, this tool isn’t even needed, and we can indeed test the classic counter example in IE11.
And much more
I know this project is very recent, but I am trying to write as much documentation as I can, with live demos included, in its README.md file, but feel free to reach me out anyhow (my twitter DMs are open) if there’s anything in particular you’d like me to document or explain, and thanks a lot for giving this shiny new way to write Web Components a chance ♥, and I’d like to leave here yet another example I’ve created, to have <slot>
within the definition 👋