atoms-element v5.0.0

#js

git clone https://git.pyrossh.dev/atoms-element

A simple web component library for defining your custom elements. It works on both client and server.


readme.md
b8566f5 1
# atoms-element
b8566f5 2
318927e 3
A simple web component library for defining your custom elements. It works on both client and server. It supports hooks and follows the same principles of react.
318927e 4
Data props are attributes on the custom element by default so its easier to debug and functions/handlers are attached to the element.
b8566f5 5
1269af9 6
I initially started researching if it was possible to server render web components but found out not one framework supported it. I liked using
9283413 7
[haunted](https://github.com/matthewp/haunted) as it was react-like with hooks but was lost on how to implement server rendering. Libraries like
1269af9 8
JSDOM couldn't be of use since it didn't support web components and I didn't want to use puppeteer for something like this.
1269af9 9
1269af9 10
After a year of thinking about it and researching it I found out this awesome framework [Tonic](https://github.com/optoolco/tonic).
1269af9 11
That was the turning point I figured out how they implemented it using a simple html parser.
1269af9 12
1269af9 13
After going through all these libraries,
b8566f5 14
20e01ad 15
1. [lit-html](https://github.com/lit/lit)
1269af9 16
2. [lit-html-server](https://github.com/popeindustries/lit-html-server)
7df8e29 17
3. [haunted](https://github.com/matthewp/haunted)
1269af9 18
4. [Tonic](https://github.com/optoolco/tonic)
1269af9 19
5. [Atomico](https://github.com/atomicojs/atomico)
1269af9 20
6. [fuco](https://github.com/wtnbass/fuco)
1269af9 21
e955350 22
And figuring out how each one implemented their on custom elements I came up with atoms-element. It now does proper rehydration: the server renders with [@lit-labs/ssr](https://github.com/lit/lit/tree/main/packages/labs/ssr), which emits hydration marker comments into the light DOM, and the client attaches to that same DOM via [@lit-labs/ssr-client](https://github.com/lit/lit/tree/main/packages/labs/ssr-client)'s `hydrate()` instead of tearing it down and re-rendering from scratch — so things like focus survive the first client render.
e955350 23
0000000 24
State lives directly in attributes rather than a separate reducer/store: `Fn.attrs = { count: Number }` declares a set of typed, required attributes up front (using the constructor itself as the type, the way Vue's props do), and `createElement` hands each one to the render function as a live `{ value }` getter/setter, validated against the declared type — mistyped or missing values throw immediately instead of failing silently.
b8566f5 25
1269af9 26
## Example
b8566f5 27
b8566f5 28
```js
a4427c2 29
import { classMap, createElement, css, html, renderHtml } from 'atoms-element/index.js';
e955350 30
e955350 31
const Counter = ({ name, count }) => {
e955350 32
  const increment = () => { count.value += 1; };
e955350 33
  const decrement = () => { count.value -= 1; };
a4427c2 34
b8566f5 35
  return html`
a4427c2 36
    <p>Counter: ${name}</p>
a4427c2 37
    <div class="controls">
a4427c2 38
      <button type="button" aria-label="Decrement" @click=${decrement}>-</button>
a4427c2 39
      <output class=${classMap({ warning: count.value > 10 })}>${count.value}</output>
a4427c2 40
      <button type="button" aria-label="Increment" @click=${increment}>+</button>
b8566f5 41
    </div>
b8566f5 42
  `;
b8566f5 43
};
e955350 44
Counter.attrs = { count: Number };
b8566f5 45
a4427c2 46
Counter.styles = css`
a4427c2 47
  :scope {
a4427c2 48
    display: block;
a4427c2 49
    margin-top: 2.5rem;
a4427c2 50
    color: rgb(55 65 81);
a4427c2 51
    --color-danger: rgb(239 68 68);
a4427c2 52
    --color-button-bg: rgb(209 213 219);
a4427c2 53
    --color-button-bg-hover: rgb(229 231 235);
a4427c2 54
  }
a4427c2 55
  p {
a4427c2 56
    margin: 0 0 0.5rem;
a4427c2 57
  }
a4427c2 58
  .controls {
a4427c2 59
    display: flex;
a4427c2 60
    align-items: center;
a4427c2 61
  }
a4427c2 62
  output {
a4427c2 63
    margin: 0 5rem;
a4427c2 64
    font-size: 1.875rem;
a4427c2 65
    font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
a4427c2 66
  }
a4427c2 67
  output.warning {
a4427c2 68
    color: var(--color-danger);
a4427c2 69
  }
a4427c2 70
  button {
a4427c2 71
    background-color: var(--color-button-bg);
a4427c2 72
    color: inherit;
a4427c2 73
    border-radius: 0.25rem;
a4427c2 74
    padding: 0.5rem 1rem;
a4427c2 75
    font-size: 1.875rem;
a4427c2 76
  }
a4427c2 77
  button:hover {
a4427c2 78
    background-color: var(--color-button-bg-hover);
a4427c2 79
  }
a4427c2 80
  button:focus {
a4427c2 81
    outline: none;
a4427c2 82
  }
a4427c2 83
`;
a4427c2 84
318927e 85
createElement({ url: 'app-counter.js' }, Counter);
b8566f5 86
e955350 87
console.log(renderHtml(html`<app-counter name="1" count="0"></app-counter>`));
b8566f5 88
```