~repos /atoms-element
git clone https://pyrossh.dev/repos/atoms-element.git
A simple web component library for defining your custom elements. It works on both client and server.
1269af94
—
Peter John 4 years ago
add ssr and improve example
- example/main.js +30 -0
- package-lock.json +5 -4
- package.json +14 -1
- readme.md +26 -7
- src/index.js +3 -3
- src/ssr.js +24 -0
example/main.js
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { defineElement, html, object, number, string, useState } from '../src/index.js';
|
|
2
|
+
import { ssr } from '../src/ssr.js';
|
|
3
|
+
|
|
4
|
+
const attrTypes = {
|
|
5
|
+
name: string.isRequired,
|
|
6
|
+
meta: object({
|
|
7
|
+
start: number,
|
|
8
|
+
}),
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
const Counter = ({ name, meta }) => {
|
|
12
|
+
const [count, setCount] = useState(meta?.start || 0);
|
|
13
|
+
|
|
14
|
+
return html`
|
|
15
|
+
<div>
|
|
16
|
+
<div class="font-bold mb-2">Counter: ${name}</div>
|
|
17
|
+
<div class="flex flex-1 flex-row text-3xl text-gray-700">
|
|
18
|
+
<button @click=${() => setCount((v) => v - 1)}>-</button>
|
|
19
|
+
<div class="mx-20">
|
|
20
|
+
<h1 class="text-1xl">${count}</h1>
|
|
21
|
+
</div>
|
|
22
|
+
<button @click=${() => setCount((v) => v + 1)}>+</button>
|
|
23
|
+
</div>
|
|
24
|
+
</div>
|
|
25
|
+
`;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
defineElement('app-counter', Counter, attrTypes);
|
|
29
|
+
|
|
30
|
+
console.log(ssr(html`<app-counter name="1"></app-counter>`));
|
package-lock.json
CHANGED
|
@@ -7,6 +7,9 @@
|
|
|
7
7
|
"": {
|
|
8
8
|
"version": "1.1.0",
|
|
9
9
|
"license": "MIT",
|
|
10
|
+
"dependencies": {
|
|
11
|
+
"parse5": "^6.0.1"
|
|
12
|
+
},
|
|
10
13
|
"devDependencies": {
|
|
11
14
|
"jest": "^27.0.5"
|
|
12
15
|
},
|
|
@@ -3195,8 +3198,7 @@
|
|
|
3195
3198
|
"node_modules/parse5": {
|
|
3196
3199
|
"version": "6.0.1",
|
|
3197
3200
|
"resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz",
|
|
3198
|
-
"integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw=="
|
|
3201
|
+
"integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw=="
|
|
3199
|
-
"dev": true
|
|
3200
3202
|
},
|
|
3201
3203
|
"node_modules/path-exists": {
|
|
3202
3204
|
"version": "4.0.0",
|
|
@@ -6424,8 +6426,7 @@
|
|
|
6424
6426
|
"parse5": {
|
|
6425
6427
|
"version": "6.0.1",
|
|
6426
6428
|
"resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz",
|
|
6427
|
-
"integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw=="
|
|
6429
|
+
"integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw=="
|
|
6428
|
-
"dev": true
|
|
6429
6430
|
},
|
|
6430
6431
|
"path-exists": {
|
|
6431
6432
|
"version": "4.0.0",
|
package.json
CHANGED
|
@@ -3,18 +3,28 @@
|
|
|
3
3
|
"version": "1.1.0",
|
|
4
4
|
"description": "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.",
|
|
5
5
|
"keywords": [
|
|
6
|
-
"atoms-element",
|
|
7
6
|
"pyros.sh",
|
|
7
|
+
"atoms-element",
|
|
8
8
|
"web component",
|
|
9
9
|
"custom element",
|
|
10
|
+
"server side rendering",
|
|
11
|
+
"client",
|
|
12
|
+
"server",
|
|
10
13
|
"hooks",
|
|
11
14
|
"react",
|
|
12
15
|
"lit-html",
|
|
13
16
|
"lit-html-server",
|
|
17
|
+
"haunted",
|
|
18
|
+
"tonic",
|
|
19
|
+
"atomico",
|
|
14
20
|
"fuco"
|
|
15
21
|
],
|
|
16
22
|
"license": "MIT",
|
|
17
23
|
"main": "src/index.js",
|
|
24
|
+
"files": [
|
|
25
|
+
"src",
|
|
26
|
+
"example"
|
|
27
|
+
],
|
|
18
28
|
"typings": "src/index.d.ts",
|
|
19
29
|
"author": "pyros.sh",
|
|
20
30
|
"type": "module",
|
|
@@ -35,5 +45,8 @@
|
|
|
35
45
|
"singleQuote": true,
|
|
36
46
|
"arrowParens": "always",
|
|
37
47
|
"trailingComma": "all"
|
|
48
|
+
},
|
|
49
|
+
"dependencies": {
|
|
50
|
+
"parse5": "^6.0.1"
|
|
38
51
|
}
|
|
39
52
|
}
|
readme.md
CHANGED
|
@@ -7,10 +7,24 @@ A simple web component library for defining your custom elements. It works on bo
|
|
|
7
7
|
principles of react. Data props are attributes on the custom element by default so its easier to debug and functions/handlers are
|
|
8
8
|
methods attached to the element.
|
|
9
9
|
|
|
10
|
+
I initially started researching if it was possible to server render web components but found out not one framework supported it. I liked using
|
|
11
|
+
[haunted](https://github.com/matthewp/haunted) as it was react like with hooks but was lost on how to implement server rendering. Libraries like
|
|
12
|
+
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.
|
|
13
|
+
|
|
14
|
+
After a year of thinking about it and researching it I found out this awesome framework [Tonic](https://github.com/optoolco/tonic).
|
|
15
|
+
That was the turning point I figured out how they implemented it using a simple html parser.
|
|
16
|
+
|
|
10
|
-
|
|
17
|
+
After going through all these libraries,
|
|
11
18
|
|
|
12
19
|
1. [lit-html](https://github.com/lit/lit)
|
|
13
|
-
2. [
|
|
20
|
+
2. [lit-html-server](https://github.com/popeindustries/lit-html-server)
|
|
21
|
+
3. [haunted](hhttps://github.com/matthewp/haunted)
|
|
22
|
+
4. [Tonic](https://github.com/optoolco/tonic)
|
|
23
|
+
5. [Atomico](https://github.com/atomicojs/atomico)
|
|
24
|
+
6. [fuco](https://github.com/wtnbass/fuco)
|
|
25
|
+
|
|
26
|
+
And figuring out how each one implemented their on custom elements I came up with atoms-element. It still doesn't have proper rehydration lit-html just replaces the DOM under the web component for now. Atomico has implemented proper SSR with hydration so I might need to look into that in the future or
|
|
27
|
+
use it instead of lit-html. Since I made a few modifications like json attributes and attrTypes I don't know if it will easy.
|
|
14
28
|
|
|
15
29
|
## Installation
|
|
16
30
|
|
|
@@ -18,12 +32,13 @@ It uses slightly modified versions of these libraries,
|
|
|
18
32
|
npm i atoms-element
|
|
19
33
|
```
|
|
20
34
|
|
|
21
|
-
##
|
|
35
|
+
## Example
|
|
22
36
|
|
|
23
37
|
```js
|
|
24
|
-
import { defineElement, html,
|
|
38
|
+
import { defineElement, html, object, number, string, useState } from 'atoms-element';
|
|
39
|
+
import { ssr } from 'atoms-element/src/ssr.js';
|
|
25
40
|
|
|
26
|
-
const
|
|
41
|
+
const attrTypes = {
|
|
27
42
|
name: string.isRequired,
|
|
28
43
|
meta: object({
|
|
29
44
|
start: number,
|
|
@@ -47,7 +62,11 @@ const Counter = ({ name, meta }) => {
|
|
|
47
62
|
`;
|
|
48
63
|
};
|
|
49
64
|
|
|
50
|
-
defineElement('app-counter', Counter,
|
|
65
|
+
defineElement('app-counter', Counter, attrTypes);
|
|
51
66
|
|
|
52
|
-
console.log(
|
|
67
|
+
console.log(ssr(html`<app-counter name="1"></app-counter>`));
|
|
53
68
|
```
|
|
69
|
+
|
|
70
|
+
This works in node, to make it work in the client side you need to either compile atoms-element using esintall or esbuild to an esm module and then
|
|
71
|
+
rewrite the path from 'atoms-element' to 'web_modules/atoms-element/index.js' or if you can host the node_modules in a web server and change the path to
|
|
72
|
+
the import 'node_modules/atoms-element/index.js'
|
src/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { html, render as litRender, directive, NodePart, AttributePart, PropertyPart, isPrimitive
|
|
1
|
+
import { html, render as litRender, directive, NodePart, AttributePart, PropertyPart, isPrimitive } from './lit-html.js';
|
|
2
2
|
|
|
3
3
|
const registry = {};
|
|
4
4
|
const isBrowser = typeof window !== 'undefined';
|
|
@@ -459,7 +459,7 @@ export function defineElement(name, fn, attrTypes = {}) {
|
|
|
459
459
|
registry[name] = {
|
|
460
460
|
fn,
|
|
461
461
|
attrTypes,
|
|
462
|
-
|
|
462
|
+
Clazz: class extends AtomsElement {
|
|
463
463
|
constructor(attrs) {
|
|
464
464
|
super();
|
|
465
465
|
this.name = name;
|
|
@@ -491,7 +491,7 @@ export function defineElement(name, fn, attrTypes = {}) {
|
|
|
491
491
|
if (window.customElements.get(name)) {
|
|
492
492
|
return;
|
|
493
493
|
} else {
|
|
494
|
-
window.customElements.define(name, registry[name].
|
|
494
|
+
window.customElements.define(name, registry[name].Clazz);
|
|
495
495
|
}
|
|
496
496
|
}
|
|
497
497
|
}
|
src/ssr.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import parse5 from 'parse5';
|
|
2
|
+
import { getElement, render } from '../src/index.js';
|
|
3
|
+
|
|
4
|
+
export const find = async (node) => {
|
|
5
|
+
for (const child of node.childNodes) {
|
|
6
|
+
if (getElement(child.tagName)) {
|
|
7
|
+
const element = getElement(child.tagName);
|
|
8
|
+
const elementInstance = new element.Clazz(child.attrs);
|
|
9
|
+
const res = elementInstance.render();
|
|
10
|
+
const frag = parse5.parseFragment(res);
|
|
11
|
+
child.childNodes.push(...frag.childNodes);
|
|
12
|
+
}
|
|
13
|
+
if (child.childNodes) {
|
|
14
|
+
find(child);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export const ssr = (template) => {
|
|
20
|
+
const text = render(template);
|
|
21
|
+
const h = parse5.parseFragment(text);
|
|
22
|
+
find(h);
|
|
23
|
+
return parse5.serialize(h);
|
|
24
|
+
};
|