Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/WebReflection/uce
µhtml based Custom Elements
https://github.com/WebReflection/uce
Last synced: 4 days ago
JSON representation
µhtml based Custom Elements
- Host: GitHub
- URL: https://github.com/WebReflection/uce
- Owner: WebReflection
- License: isc
- Created: 2020-02-25T01:10:00.000Z (over 4 years ago)
- Default Branch: main
- Last Pushed: 2024-01-26T17:43:34.000Z (10 months ago)
- Last Synced: 2024-10-29T04:48:26.635Z (11 days ago)
- Language: JavaScript
- Size: 438 KB
- Stars: 198
- Watchers: 5
- Forks: 15
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
- awesome-web-components - µce (+µhtml+µce-template+µce-loader)
README
# µce
[![Downloads](https://img.shields.io/npm/dm/uce.svg)](https://www.npmjs.com/package/uce) [![Build Status](https://travis-ci.com/WebReflection/uce.svg?branch=master)](https://travis-ci.com/WebReflection/uce) [![Coverage Status](https://coveralls.io/repos/github/WebReflection/uce/badge.svg?branch=master)](https://coveralls.io/github/WebReflection/uce?branch=master)
![windflower](./uce-head.jpg)
**Social Media Photo by [Dawid Zawiła](https://unsplash.com/@davealmine) on [Unsplash](https://unsplash.com/)**
**[µhtml](https://github.com/WebReflection/uhtml#readme)** based Custom Elements.
### 📣 Community Announcement
Please ask questions in the [dedicated discussions repository](https://github.com/WebReflection/discussions), to help the community around this project grow ♥
---
## API Overview
_µce_ exports `render`, `html`, and `svg`, from _µhtml_, plus its own way to `define` components.
In version *1.2*, it exports also a [plain-tag](https://github.com/WebReflection/plain-tag#readme) named `css`, useful to trigger _CSS_ minifiers.
Check out the [test page](https://webreflection.github.io/uce/test/) or this [code pen playground](https://codepen.io/WebReflection/pen/MWwJpWx?editors=0010).
```js
// list of all exports
import {define, render, html, svg, css} from 'uce';define('my-component', {
// if specified, it can extend built-ins too.
// by default it's 'element', as HTMLElement
extends: 'div',// if specified, it injects once per class definition
// a global element in the document <head>, but
// *not* in the shadowRoot. You can render style just fine
// within the render, if shadow DOM is desired.
// For the one-off global case, this method will be invoked with
// a selector like `div[is="my-component"]` or `some-component`
style: selector => css`${selector} {
font-weight: bold;
}`,// if specified, it's like the constructor but
// it's granted to be invoked *only once* on bootstrap
// and *always* before connected/attributeChanged/props
init() {
// µhtml is provided automatically via this.html
// it will populate the shadow root, even if closed
// or simply the node, if no attachShadow is defined
this.html`<h1>Hello 👋 µce</h1>`;
// a default props access example
// <my-ce name="ag" />
console.log(this.props.name); // "ag"
},// if there is a render method, and no `init`,
// this method will be invoked automatically on bootstrap.
// element.render(), if present, is also invoked automatically
// when `props` are defined as accessors, and one of these is
// set during some outer component render()
render() {
this.html`<h1>Hello again!</h1>`;
},// by default, props resolves all attributes by name
// const {prop} = this.props; will be an alias for
// this.getAttribute('prop') operation,
// but it can simulate what React props do,
// meaning that if it's defined as object,
// all properties will trigger automatically
// a render() call, if there is a render,
// and properties are set as accessor, so that
// the syntax to trigger these is .prop=${value}
// as opposite of the default prop=${value}
// which is observable, but it can hold only strings.
// props: {prop: value} will make this.prop work.
// If you don't want any of this machinery around props
// you can opt out by defining it as null.
// Bear in mind, the way to pass props as accessors,
// is by prefixing the attribute via `.`, that is:
// this.html`<my-comp .prop=${value}/>`;
props: null,// if present, all names will be automatically bound to the element
// right before initialization (el.method = el.method.bind(el))
// this allows usage of methods instead of `this` for inner components
bound: ['method'],// if specified, it renders within its Shadow DOM
// compatible with both open and closed modes
attachShadow: {mode: 'closed'},// if specified, observe the list of attributes
observedAttributes: ['test'],// if specified, will be notified per each
// observed attribute change
attributeChanged(name, oldValue, newValue){},// if specified, will be invoked when the node
// is either appended live, or removed
connected() {},
disconnected() {},// events are automatically attached, as long
// as they start with the `on` prefix
// the context is *always* the component,
// you'll never need to bind a method here
onClick(event) {
console.log(this); // always the current Custom Element
},// if specified with `on` prefix and `Options` suffix,
// allows adding the listener with a proper third argument
onClickOptions: {once: true}, // or true, or default false// any other method, property, or getter/setter will be
// properly configured in the defined class prototype
get test() { return Math.random(); },set test(value) { console.log(value); },
sharedData: [1, 2, 3],
method() {
return this.test;
}});
```## F.A.Q.
<details>
<summary><strong>Which polyfill should I use?</strong></summary>
<div>The [@ungap/custom-elements](https://github.com/ungap/custom-elements#readme) is the recommended polyfill to grant every Custom Elements V1 feature is available in every browser.
However, if no builtin extend is used, but legacy needs to be supported, including [@webreflection/custom-elements-no-builtin](https://github.com/WebReflection/custom-elements-no-builtin#readme) on top of the page should patch [IE 11 and other legacy browsers](https://github.com/ungap/custom-elements#compatibility).
</div>
</details><details>
<summary><strong>How to avoid bundling µce per each component?</strong></summary>
<div>This module reserves, in the Custom Elements Registry a `uce-lib` class, which only purpose is to provide all exports as static getters.
```js
// whenever uce library is loaded
customElements
.whenDefined('uce-lib')
.then(({define, render, html, svg} = customElements.get('uce-lib')) => {
// that's it: ready to go 🎉
define('my-component', {
init() {
console.log('this is awesome!');
}
});
}
);
```<strong>Using a helper</strong>
"_There's a module for that_", it's called [once-defined](https://github.com/WebReflection/once-defined#readme):
```js
import when from 'once-defined';when('uce-lib').then(({define, render, html, svg}) => {
// define your Custom Element
});
```</div>
</details><details>
<summary><strong>Without classes, how does one define private properties?</strong></summary>
<div>Private properties can be created via a _WeakMap_, which is indeed how _Babel_ transforms these anyway.
```js
const privates = new WeakMap;
define('ce-with-privates', {
init() {
// define these once
privates.set(this, {test: 1, other: '2'});
},
method() {
// and use it anywhere you need them
const {test, other} = privates.get(this);
console.log(test, other);
}
});
```</div>
</details><details>
<summary><strong>Without classes, how does one extend other components?</strong></summary>
<div>There are at least two ways to extend an _uce_ component:
* define via _uce_ your base component, and use `extends: "base-comp-name"` to extend it (built-ins supported!)
* use one or more mixin through object literalsObject literals have indeed been used as mixin for a very long time, and the pattern with _uce_ would be very similar.
The only warning is that `Object.assign`, as well as object `{...spread}`, lose getters and setters in the process, so that if you want to extend more complex components, you should consider using [assignProperties](https://github.com/WebReflection/assign-properties#readme), or a similar helper.
```js
import $ from 'assign-properties';
const mixin = (...components) => $({}, ...components);// a component literal definition
const NamedElement = {
get name () { return this.tagName; }
};// a generic NamedElement mixin
const FirstComponent = mixin(NamedElement, {
method() {
console.log(this.name);
}
});// define it via the FirstComponent mixin
define('first-component', FirstComponent);// define it via mixin
define('first-component', mixin(FirstComponent, {
otherThing() {}
}));
```</div>
</details><details>
<summary><strong>How different is <em>µce</em> from others?</strong></summary>
<div>I have written a gist that compares [uce vs lit-element](https://gist.github.com/WebReflection/ae3451c17c5e882bbc7f0714c14eefcd), so that most obvious differences are highlighted, but basically *uce* provides pretty much everything other libraries provide and vice-versa, and choosing one or another should be driven by personal taste and style, as long as most relevant differences are clear.
That is: *uce* is neither superior nor inferior to others, it tries to be as simple and concise as possible, and it has great pontentials when used via [uce-template](https://github.com/WebReflection/uce-template#readme) too.
</div>
</details><details>
<summary><strong>What happened between 1.2 and 1.5?</strong></summary>
<div>A wrong `npm publish` happened, as `1.5.0` has been pushed for no reason between 0.5 an 0.6, so that latest was picking up actually an older version of the library.
My apologies.
</div>
</details><details>
<summary><strong>What's new in v1.2?</strong></summary>
<div>So far, the only missing utility for *non* Shadow DOM cases, is a way to define *once* a generic *style* associated with a component, which is why the special `style: (selector) => css` property has been added, so that any component can automatically define any specific style, using the `selector` to confine inner nodes directives.
The `css` export is a plain template literal tag, which is completely optional, but it might help minifiers, or [rollup plugins](https://github.com/asyncLiz/rollup-plugin-minify-html-literals), to minify that code too.
```js
// note: the css import is optional
import {define, css} from 'uce';define('very-important', {
style: sel => css`
${sel} {
font-weight: bold;
text-transform: uppercase;
}
${sel}:hover {
font-size: 2rem;
}
`
});
```If the element doesn't extend a built-in, the received `sel`, as _selector_, will simply be its name, otherwise it'll be the built-in name with its `[is="..."]` attribute.
**Please note** the `style` won't interfere, or be attached anyhow, with the regular `element.style` or `this.style`, within a method, which is actually why I've chosen that name, so it's clear it's about the generic class/component style, and not its property.
</div>
</details>