@nocksock/cce
v0.0.11
Published
Base class and helpers for creating custom-elements aka web components. Built on signals. Using `lit-html` for blazingly fast and efficient rendering without a vdom.
Downloads
16
Readme
CCE (working title)
Base class and helpers for creating custom-elements aka web components.
Built on signals.
Using lit-html for blazingly fast and efficient rendering without a vdom.
[!WARNING] This is still in alpha. You may look around, you could even use it - I use it to build a couple of side projects and for prototyping.
Feature Overview
- Built on signals
- Thin wrappers around life-cycle hooks:
mount()instead ofconnectedCallback()returning a cleanuponinstead ofaddEventListener()and manually cleaning up.
- Highly efficient element re-use during rerenders thanks to
lit-html - Setup of context providers and consumers without orchestration using string keys
- and more memory efficient using the conventional
createContextroute. - using the context community standard for interop.
- and more memory efficient using the conventional
- No synthetic events, no v-dom, all web-native features.
cceshorthand function for function-component-like patterns.- Lightweight (~8kb gzip including all the features)
API Overview
class MyCustomElement extends CustomElement {
// Props are reactive and available in the render() method via `this.someProp()`
// they can be updated using setAttribute or ``<element>.someProp = newValue`
// and can be bound in render:
// html`<my-element .someProp=${someValue}></my-element>`
static props = {
simplePropDefault: "default value",
// can be a function that serves as a deserialiser/transformer
someOtherProp: (value = "also default") => value.toUpperCase(),
// meaning, this works too:
counter: Number
}
// For private state, simply use a private field. Public would also work,
// but you'll most likely want a prop then.
#somePrivateState = signal()
static style = css`
div {
background: black;
color: white;
}
`
// A more convenient alias for constructor() { super() }
// In one of the next minor versions setup can be async...
setup() {
effect(() => console.log("somOtherProp was changed!", this.someOtherProp()))
setTimeout(() => {
// ... but this pattern is convenient enough most of the time.
this.someOtherProp("new value")
}, 500)
}
mount() {
this.on('some-event', (e) => {
// this event handler will automatically be removed on unmount
// also events are stopped from propagating.
// You can still use this.addEventListener when this doesn't suite
// your needs.
// NOTE: `this.on` is only allowed to be called here at the moment.
})
return () => {
// this will be called on unmount
}
}
// note: props can be destructured
render({counter}) {
// the html tagged template literal is coming straight from lit-html
return html`
<div>
Value of someNumberProp: ${this.someNumberProp()}
<button @click=${() => this.counter(this.counter() + 1)}>
${counter()}
</button>
<button @click=${() => {
// Shorthand for dispatching events that *bubble up* and
// can be caught by shadow dom.
// `this.dispatchEvent()` works like usual, in case `on` does
// not suit your needs.
this.dispatch('simple-event')
this.dispatch('custom-event', payload)
}}>
dispatch!
</button>
<!-- shadow dom! -->
<slot></slot>
</div>
`
}
}
// Don't forget to define it!
customElements.define("my-element", MyCustomElement);cce Shorthand
Resembling function-compnent-style. Has all the same features, just a different API.
cce('my-element',
// define observable attributes/props:
{ someProp: Number }, // value can be transformer/deserialiser
// methods, props, and utils are available via arguments
({someProp, consume, mount, html, self}) => {
// the body is evaluated once
const contextValue = consume('key', 'fallback value');
mount(() => {
// do some on mount setup here
return () => {
// do any unmount cleanup here
}
})
// Return the render function, which is called on every re-render
// ie: whenever a signal updates.
return () => html`
<div>
${someProp()}
${contextValue()}
</div>
`;
}
)