~repos /atoms-element

#js

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
Files changed (6) hide show
  1. example/main.js +30 -0
  2. package-lock.json +5 -4
  3. package.json +14 -1
  4. readme.md +26 -7
  5. src/index.js +3 -3
  6. 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
- It uses slightly modified versions of these libraries,
17
+ After going through all these libraries,
11
18
 
12
19
  1. [lit-html](https://github.com/lit/lit)
13
- 2. [fuco](https://github.com/wtnbass/fuco)
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
- ## Usage
35
+ ## Example
22
36
 
23
37
  ```js
24
- import { defineElement, html, render, object, number, string } from 'atoms-element';
38
+ import { defineElement, html, object, number, string, useState } from 'atoms-element';
39
+ import { ssr } from 'atoms-element/src/ssr.js';
25
40
 
26
- const propTypes = {
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, propTypes);
65
+ defineElement('app-counter', Counter, attrTypes);
51
66
 
52
- console.log(render(html`<app-counter name="1"></app-counter>`));
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, Template } from './lit-html.js';
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
- clazz: class extends AtomsElement {
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].clazz);
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
+ };