I'm proud to release @lume/element v0.17.0.
This release adds support for Solid.js memos and effects for Custom Elements via decorator syntax.
Concisely write custom HTML elements, type checked in React, Vue, Svelte, Solid.js, React, Stencil, and more.
Here's a glance at what elements authored with @lume/element look like in JavaScript:
import {Element, element, numberAttribute, eventAttribute} from '@lume/element'
import {signal, memo, effect, startEffects, stopEffects} from 'classy-solid'
@element
class FurryDog extends Element {
@numberAttribute years = 7
@eventAttribute onwoof = null
@memo get dogYears() {
return this.years * 7
}
@effect #log() {
console.log('dog years:', this.dogYears)
this.dispatchEvent(new Event('woof')) // bark any time age changes
}
template = () => <div>dog years: {this.dogYears}</div>
css = `:host {color: cornflowerblue}`
}
const dog = document.createElement('furry-dog')
document.body.append(dog) // initially logs "dog years: 49"
dog.years = 8 // logs "dog years: 56"
// ...later, effects cleaned up when removed from DOM...
dog.remove()
// ...later, effects re-started if element reconnected...
document.body.append(dog)
Use the custom element in HTML, or declarative framework templates.
HTML:
<furry-dog years="5" id="dog" onwoof="console.log('woof!')"></furry-dog>
<script>
dog.years = 6
</script>
React, Preact:
const [age, setAge] = useState(3)
return <furry-dog years={age} onwoof={() => console.log('woof!')}/>
Solid.js JSX:
const [age, setAge] = createSignal(2)
return <furry-dog years={age()} on:woof={() => console.log('woof!')} />
Vue:
<script setup>
const age = ref(5)
</script>
<template>
<furry-dog :years="age" @woof="console.log('woof!')" />
</template>
Svelte:
<script>
let age = $state(4);
</script>
<furry-dog years={age} onwoof={() => console.log('woof!')} />
Type checking is available for all the above frameworks, including in the templates of the elements defined with @lume/element (templates are powered by (@ryansolid's) Solid.js). See the docs on how to connect your element definition to the type systems of each framework, and the various examples.
@lume/element
Easily and concisely write Custom Elements with simple templates and reactivity.
Use the custom elements on their own in plain HTML or vanilla JavaScript, or in Vue, Svelte, Solid.js, Stencil.js, React, and Preact, with full type checking autocompletion, and intellisense in all the template systems of those frameworks, in any IDE that supports TypeScript such as VS Code.
Write your elements once, then use them in any app, with a complete developer experience no matter which base component system your app uses.
npm install @lume/element
💡Tip:
If you are new to Custom Elements, first learn about the basics of Custom Element APIs available natively in browsers. Lume Element simplifies the creation of Custom Elements compared to writing them with vanilla APIs, but sometimes vanilla APIs are all that is needed.
Live demos
- Lume 3D HTML (The landing page, all of Lume's 3D elements, and the live code editors…
Top comments (3)
So how does this "magic" work - I understand these are Web Components (I had never heard about Custom Elements, but I understand they're a kind of Web Components), and you then provide framework "wrappers" around them for the various frontend frameworks?
Sounds a bit similar to what Ionic did when they "ported" their components from Angular to React and Vue - they used Web Components to pull this off ...
The browser API is
window.customElements. There's no API with "web components" in its naming, it's more like a term for various built-in APIs that you can put together.I like "custom elements", or even "custom HTML elements", more than "web components" because everything else is already a web component: React components, Vue components, Solid.js components, Svelte components, etc, they're all web components! I've even seen the non-custom-element components called "web components" at a big org (and they were not referring to native custom elements, but were distinguishing from other components such as "android components" or "iOS components"). From that perspective, "custom elements" is much nicer because it aligns very well with "built-in elements", "HTML elements", or just "elements" (built-in or not), and I always teach them that way. "Web components" is too overloaded, and possibly even confusing for people who aren't deep enough into web yet.
No framework wrappers, that'd be too much work!
@lume/elementprovides type helpers to attach type definitions into the various frameworks. Write a custom element class definition, then connect its types into chosen framework, and it will be those elements being used directly rather than wrappers.For example, after writing a custom element, it's type definition can be hooked into React by using the
ReactElementAttributeshelper:Then we can put the definition for React consumers in a separate file that React users can import (we wouldn't want to define React types in the same file as the class, or a user of Vue/Svelte/etc will get type errors):
And finally, a React user can use the element with type checking:
An index file that imports all react type defs for all elements in a custom element lib makes it easy for React users to import a single entry one time in their app entry point, and get type definitions for all elements of the custom element library, f.e.
import type {} 'super-lib/index.react.d.ts'.There is not any compromise in usage either, the experience in any framework is complete without sacrificing any features of the custom element (at least when written with
@lume/element). All@lume/elementprops can accept JS values or attribute strings (no matter what differing frameworks choose to pass in), and the event types are tested in all major frameworks, f.e.ontotherescueprop types in React/Preact/Solid.js/Svelte,@totherescuein Vue, and evenel.ontotherescue = fnorel.setAttribute('ontotherescue', '...js code...')with native DOM APIs, so interoperability is maximal. The lume/element repo has an example for each framework.There is more info on type definitions in the link I gave on how to connect your element definition to the type systems of each framework.
I still want to add support for Custom Elements Manifest, to extend types into Lit and other Custom Element frameworks with arbitrary template syntaxes in a more idiomatic way geared for compatibility across custom element libraries (and all the non-custom-element libraries too) and IDEs in other languages beyond TypeScript, as Custom Elements Manifest is the format the community is conventionalizing for this.
Thanks, makes sense, learned something ... "Web components" is just the term that I knew, but I agree it's pretty vague and generic - "Custom elements" seems more clear and precise!