How to deliver Custom Elements

Andrea Giammarchi
3 min readMay 16, 2020

--

Many developers believe that using 3rd parts library to define Custom Elements is a blocker, mostly because you need to bundle such library per each component, or trust a globally defined variable you can use … and we can never know when/if it’s being made available, right?

This post is about trashing all these believes, through a very simple platform compatible “hack” I’ve discovered myself for uce based components.

About Custom Elements Dependencies

So here the thing: if our Custom Element uses other Custom Elements to grant the desired behavior, we better ensure ourselves all needed components have been defined:

Promise.all([
customElements.whenDefined('aside-ce'),
customElements.whenDefined('main-ce')
]).then(() => {
customElements.define('content-ce', ...);
});

Imagine our <content-ce> container will be populated with <aside-ce> and <main-ce> nodes here and there, and it wouldn’t work as expected if these are not available.

Ensuring our dependencies are in too, is the least we can do to grant the desired experience, isn’t it?

About Custom Elements Extends

But how about those cases where we need some “base component” to extend its behavior?

customElements.whenDefined('base-ce').then(() => {
const BaseCE = customElements.get('base-ce');
customElements.define('rich-ce', class extends BaseCE {
// ... crazy stuff in here ...
});
});

Components Shouldn’t Care About Dependencies

Differently from most common paradigms in these days, where libraries, or even few CSS frameworks, assume jQuery is globally available, as example, or that everything is running behind some developer tool-chain, Custom Elements have a dedicated API which purpose is exactly to grant that whatever component we need to make it work, is either defined, or “don’t bother”.

That’s literally the only reason customElements.whenDefined(...) Promise exists: to be sure that anything our components needs to work, is already there … and what’s the great advantage of all this?

Lazy Dependencies

No matter if any Web page delivers our components, these will work once the page owner will, in a way or another, inject those dependencies.

Sure, most dependencies graphs used in these days won’t automatically inject those needed by our component, but as we can’t really decide where our components would land, should we care at all?

The lazytag module, as example, has a unique goal of resolving Custom Elements on demand, so that any Web page would use, still on demand, only components needed to render as meant such page.

On demand” components are those available in the page layout, so that anything would trigger lazy evaluation, as long as the element name, or its is="..." attribute, is discovered.

This means publishers have at least two options:

  • preload anything needed in their website to work, including components they include in their layout
  • use lazitag and call it a day, providing a .js and .css file per each component they need

About Libraries

Well, this is where my µce hack plays a role, but as I’ve probably discovered it, I think it should be everyone else knowledge too, to make the Web, and our Custom Elements, more awesome everywhere!

So how would you consume a 3rd parts module, without being sure such module is available on the global scope, or bundled?

customElements.whenDefined('uce-lib').then(() => {
const {define} = customElements.get('uce-lib');
define('uce-is-awesome', {
render() {
this.html`this is literally it 🎉!`;
}
});
});

That’s literally it: when we need uce as library, within anything it exports, we can use its Custom Element facade to retrieve its module:

customElements.define('uce-lib', class extends HTMLElement {
static get define() { return define; }
static get render() { return render; }
static get html() { return html; }
static get svg() { return svg; }
});

Above snippet is how uce exports all its features to whoever would like to consume them.

It works in every browser, it doesn’t couple our uce based components to bundlers, dependencies, or anything … whenever the uce library gets injected, that’s the moment our components will define, and gracefully enhance, the DOM.

Not Only µce

Well, the pattern should be explicit enough to enable any sort of Custom Elementsstand-alone” modules, to run all over the Web without caring about what’s the surrounding env: dependencies are loaded through the registry, and worry-less components that provide graceful enhancement can land already everywhere, in a way or another, and be enriched/upgraded on demand 🎉

--

--

Andrea Giammarchi

Web, Mobile, IoT, and all JS things since 00's. Formerly JS engineer at @nokia, @facebook, @twitter.