feat: remove attach-effect

This commit is contained in:
Ayo Ayco 2025-03-09 00:34:10 +01:00
parent d41affd709
commit 9ead44babe
12 changed files with 2135 additions and 681 deletions

View file

@ -52,9 +52,9 @@ The result is a reactive UI on property changes.
## Project Status
It is ready for many cases we see people use custom elements for. If you have a cool project built on **WebComponent.io** we'd love to know! :)
It is ready for many simple cases we see people use custom elements for. If you have a cool project built on **WebComponent.io** we'd love to know! :)
For building some advanced interactions, we have a few issues that are still open: [#24 smart diffing](https://github.com/ayoayco/web-component-base/issues/24) & [#4 attachEffect improvements](https://github.com/ayoayco/web-component-base/issues/4)
For building advanced interactions, we have an in-progress work on smart diffing to prevent component children being wiped on interaction.
In the mean time, if you have some complex needs, we recommend using the `WebComponent` base class with a more mature rendering approach like `lit-html`... and here's a demo for that: [View on CodePen ↗](https://codepen.io/ayoayco-the-styleful/pen/ZEwNJBR?editors=1010).
@ -91,15 +91,13 @@ You can import everything separately, or in a single file each for the main expo
```js
// all in a single file
import { WebComponent, html, attachEffect } from 'web-component-base'
import { WebComponent, html } from 'web-component-base'
// in separate files
import { WebComponent } from 'web-component-base/WebComponent.js'
import { html } from 'web-component-base/html.js'
import { attachEffect } from 'web-component-base/attach-effect.js'
```
### Utilities
@ -221,7 +219,6 @@ Some feature-specific demos:
1. [Using dynamic style objects](https://codepen.io/ayoayco-the-styleful/pen/bGzXjwQ?editors=1010)
1. [Using the Shadow DOM](https://codepen.io/ayoayco-the-styleful/pen/VwRYVPv?editors=1010)
1. [Using tagged templates in your vanilla custom element](https://codepen.io/ayoayco-the-styleful/pen/bGzJQJg?editors=1010)
1. [Using attachEffect (experimental)](https://codepen.io/ayoayco-the-styleful/pen/ExrdWPv?editors=1011)
## `template` vs `render()`
@ -448,9 +445,9 @@ class ClickableText extends WebComponent {
All the functions and the base class in the library are minimalist by design and only contains what is needed for their purpose.
As of v2.0.0, the main export (with `WebComponent` + `html` + `attachEffect`) is 1.7 kB (min + gzip) according to [bundlephobia.com](https://bundlephobia.com/package/web-component-base@2.0.0), and the `WebComponent` base class is just 1.1 kB (min + brotli) according to [size-limit](http://github.com/ai/size-limit).
As of v3, the main export (with `WebComponent` + `html`) is 1.7 kB (min + gzip) according to [bundlephobia.com](https://bundlephobia.com/package/web-component-base@2.0.0), and the `WebComponent` base class is just 1.08 kB (min + brotli) according to [size-limit](http://github.com/ai/size-limit).
There is an increase in size compared to that of before this release, primarily because of advanced features (e.g., effects, html tagged templates, and props blueprints) in building complex applications.
There is an increase in size compared to that of before this release, primarily because of advanced features (e.g., html tagged templates, and props blueprints) in building complex applications.
## Inspirations and thanks

View file

@ -1,23 +0,0 @@
// @ts-check
import { WebComponent, attachEffect, html } from '../../src/index.js'
export class Counter extends WebComponent {
static props = {
count: 0,
}
onInit() {
attachEffect(this.props.count, (count) => console.log(count))
}
afterViewInit() {
attachEffect(this.props.count, (count) => console.log(count + 100))
}
get template() {
return html`<button onclick=${() => ++this.props.count} id="btn">
${this.props.count}
</button>`
}
}
customElements.define('my-counter', Counter)

View file

@ -1,24 +0,0 @@
// @ts-check
import { WebComponent, attachEffect, html } from '../../src/index.js'
export class Decrease extends WebComponent {
static props = {
count: 999,
}
onInit() {
attachEffect(this.props.count, (count) => console.log(count))
}
afterViewInit() {
attachEffect(this.props.count, (count) => console.log(count + 100))
}
get template() {
return html`<button onclick=${() => --this.props.count} id="btn">
${this.props.count}
</button>`
}
}
customElements.define('my-decrement', Decrease)

View file

@ -1,15 +0,0 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>WC demo</title>
<script type="module" src="./Counter.mjs"></script>
<script type="module" src="./Decrease.mjs"></script>
</head>
<body>
<h1>Attach Effect Test</h1>
<my-counter></my-counter>
<my-decrement></my-decrement>
</body>
</html>

View file

@ -1,36 +1,44 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>WC demo</title>
<script type="module" src="./Counter.mjs"></script>
<script type="module" src="./Toggle.mjs"></script>
<script type="module" src="./HelloWorld.mjs"></script>
<script type="module" src="./Object.mjs"></script>
<style>
* {
font-size: larger;
}
</style>
</head>
<body>
<div>Counter: <my-counter></my-counter></div>
<div>Toggle: <my-toggle></my-toggle></div>
<div>String: <my-hello-world></my-hello-world></div>
<div>
<my-object></my-object>
<p id="display-panel"></p>
</div>
<script type="module">
import { attachEffect } from '../../src/index.js'
const myObjectEl = document.querySelector('my-object')
const objectProp = myObjectEl.props.object
const displayPanelEl = document.querySelector('#display-panel')
displayPanelEl.textContent = JSON.stringify(objectProp)
attachEffect(objectProp, (object) => {
displayPanelEl.textContent = JSON.stringify(object)
})
</script>
</body>
</html>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>WC demo</title>
<script type="module" src="./Counter.mjs"></script>
<script type="module" src="./Toggle.mjs"></script>
<script type="module" src="./HelloWorld.mjs"></script>
<script type="module" src="./Object.mjs"></script>
<style>
* {
font-size: larger;
}
</style>
</head>
<body>
<div>Counter: <my-counter></my-counter></div>
<div>Toggle: <my-toggle></my-toggle></div>
<div>String: <my-hello-world></my-hello-world></div>
<div>
<my-object></my-object>
<p id="display-panel"></p>
</div>
<script type="module">
/**
* TODO: fix using custom events
*/
// import { attachEffect } from '../../src/index.js'
// const myObjectEl = document.querySelector('my-object')
// const objectProp = myObjectEl.props.object
// const displayPanelEl = document.querySelector('#display-panel')
// displayPanelEl.textContent = JSON.stringify(objectProp)
// attachEffect(objectProp, (object) => {
// displayPanelEl.textContent = JSON.stringify(object)
// })
// console.log(JSON.stringify(object))
</script>
</body>
</html>

View file

@ -101,5 +101,8 @@
"path": "./dist/utils/get-kebab-case.js",
"limit": "0.5 KB"
}
]
],
"dependencies": {
"release-it": "^18.1.2"
}
}

File diff suppressed because it is too large Load diff

View file

@ -25,13 +25,6 @@ class FeatureSet extends WebComponent {
'Use the built-in JSX-like syntax or bring your own custom templating',
url: 'https://codepen.io/ayoayco-the-styleful/pen/ZEwNJBR?editors=1010',
},
{
icon: '️🛜',
title: 'Powerful.',
description:
"Attach 'side effects' that gets triggered on property value changes",
url: '',
},
]
/**

View file

@ -1,126 +1,122 @@
<!doctype html>
<html lang="en">
<!--
<!--
Hello! This page is an example McFly page.
See more on https://ayco.io/gh/McFly
-->
<my-head>
<title>WebComponent.io: Web Components in Easy Mode</title>
<link rel="stylesheet" href="prism.css" />
<script src="prism.js" defer></script>
<script server:setup>
const project = {
name: 'WebComponent.io',
description: 'A simple reactivity system for web components',
};
const author = {
name: 'Ayo Ayco',
url: 'https://ayco.io',
year: '2023',
};
</script>
<style>
@counter-style publish-icons {
system: cyclic;
symbols: '️✅';
suffix: ' ';
}
main {
font-size: large;
<my-head>
<title>WebComponent.io: Web Components in Easy Mode</title>
<link rel="stylesheet" href="prism.css" />
<script src="prism.js" defer></script>
<script server:setup>
const project = {
name: 'WebComponent.io',
description: 'A simple reactivity system for web components',
};
const author = {
name: 'Ayo Ayco',
url: 'https://ayco.io',
year: '2023',
};
</script>
<style>
@counter-style publish-icons {
system: cyclic;
symbols: '️✅';
suffix: ' ';
}
& section ul {
list-style: publish-icons;
main {
font-size: large;
& li {
margin-bottom: 0.5em;
}
}
& section ul {
list-style: publish-icons;
& section.jumbo {
& .hero-statement {
font-size: 2em;
text-align: center;
}
}
& code-block {
overflow: auto;
& li {
margin-bottom: 0.5em;
}
}
</style>
</my-head>
<body>
<awesome-header>
<span>{{ project.name }}</span>
<span slot="description">{{ project.description }}</span>
</awesome-header>
<main>
<section class="jumbo">
<p class="hero-statement">
Build lightweight custom elements that browsers &
<em>you</em> understand.
</p>
<call-to-action></call-to-action>
<div>
<feature-set>
<h2>Features</h2>
<ul>
<li>
A robust API for synchronizing your component's UI and
properties
</li>
<li>
~1 kB base class (minified, compressed) with versatile utilities
</li>
<li>
Sensible life-cycle hooks that you understand and remember
</li>
<li>
Use the built-in JSX-like syntax or bring your own custom
templating
</li>
<li>
Attach 'side effects' that gets triggered on property value
changes
</li>
</ul>
</feature-set>
</div>
</section>
<section>
<h2>Why use this base class?</h2>
<p>
Often times, when simple websites need a quick
<a
href="https://developer.mozilla.org/en-US/docs/Web/API/Web_Components/Using_custom_elements"
target="_blank"
>custom element</a
>, the best way is still to create one extending from
<code-block inline>HTMLElement</code-block>. However, it can quickly
reach a point where writing the code from scratch can seem confusing
and hard to maintain especially when compared to other projects with
more advanced setups.
</p>
<p>
Also, when coming from frameworks with an easy declarative coding
experience (using templates), the default imperative way (e.g.,
creating instances of elements, manually attaching event handlers, and
other DOM manipulations) is a frequent pain point we see.
</p>
<p>
This project aims to address these with virtually zero tooling
required and thin abstractions from vanilla custom element APIs
giving you only the bare minimum code to be productive.
</p>
<p>
It works on current-day browsers without needing compilers,
transpilers, or polyfills.
</p>
<p>
Here's an interactive custom element:
<my-counter></my-counter>
</p>
<code-block language="js">
<pre>
& section.jumbo {
& .hero-statement {
font-size: 2em;
text-align: center;
}
}
& code-block {
overflow: auto;
}
}
</style>
</my-head>
<body>
<awesome-header>
<span>{{ project.name }}</span>
<span slot="description">{{ project.description }}</span>
</awesome-header>
<main>
<section class="jumbo">
<p class="hero-statement">
Build lightweight custom elements that browsers &
<em>you</em> understand.
</p>
<call-to-action></call-to-action>
<div>
<feature-set>
<h2>Features</h2>
<ul>
<li>
A robust API for synchronizing your component's UI and
properties
</li>
<li>
~1 kB base class (minified, compressed) with versatile utilities
</li>
<li>
Sensible life-cycle hooks that you understand and remember
</li>
<li>
Use the built-in JSX-like syntax or bring your own custom
templating
</li>
</ul>
</feature-set>
</div>
</section>
<section>
<h2>Why use this base class?</h2>
<p>
Often times, when simple websites need a quick
<a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_Components/Using_custom_elements"
target="_blank">custom element</a>, the best way is still to create one extending from
<code-block inline>HTMLElement</code-block>. However, it can quickly
reach a point where writing the code from scratch can seem confusing
and hard to maintain especially when compared to other projects with
more advanced setups.
</p>
<p>
Also, when coming from frameworks with an easy declarative coding
experience (using templates), the default imperative way (e.g.,
creating instances of elements, manually attaching event handlers, and
other DOM manipulations) is a frequent pain point we see.
</p>
<p>
This project aims to address these with virtually zero tooling
required and thin abstractions from vanilla custom element APIs
giving you only the bare minimum code to be productive.
</p>
<p>
It works on current-day browsers without needing compilers,
transpilers, or polyfills.
</p>
<p>
Here's an interactive custom element:
<my-counter></my-counter>
</p>
<code-block language="js">
<pre>
import { WebComponent, html } from &quot;https://esm.sh/web-component-base&quot;;
export class Counter extends WebComponent {
@ -136,20 +132,18 @@ export class Counter extends WebComponent {
}
}
customElements.define(&quot;my-counter&quot;, Counter);</pre
>
</code-block>
</section>
</main>
<my-footer>
<small>
<a href="https://ayco.io/sh/wcb/tree/main/site"
>Website</a
>
built with <a href="https://mcfly.js.org">McFly</a>.<br />
Copyright &copy; {{author.year}}
<a href="{{ author.url }}">{{ author.name }}</a>.
</small>
</my-footer>
</body>
</html>
customElements.define(&quot;my-counter&quot;, Counter);</pre>
</code-block>
</section>
</main>
<my-footer>
<small>
<a href="https://ayco.io/sh/wcb/tree/main/site">Website</a>
built with <a href="https://mcfly.js.org">McFly</a>.<br />
Copyright &copy; {{author.year}}
<a href="{{ author.url }}">{{ author.name }}</a>.
</small>
</my-footer>
</body>
</html>

View file

@ -20,7 +20,6 @@ export class WebComponent extends HTMLElement {
#prevDOM
#props
#typeMap = {}
#effectsMap = {}
/**
* Array of strings that tells the browsers which attributes will cause a render
@ -130,7 +129,6 @@ export class WebComponent extends HTMLElement {
}
#handler(setter, meta) {
const effectsMap = meta.#effectsMap
const typeMap = meta.#typeMap
return {
@ -141,12 +139,7 @@ export class WebComponent extends HTMLElement {
typeMap[prop] = typeof value
}
if (value.attach === 'effect') {
if (!effectsMap[prop]) {
effectsMap[prop] = []
}
effectsMap[prop].push(value.callback)
} else if (typeMap[prop] !== typeof value) {
if (typeMap[prop] !== typeof value) {
throw TypeError(
`Cannot assign ${typeof value} to ${
typeMap[prop]
@ -154,7 +147,6 @@ export class WebComponent extends HTMLElement {
)
} else if (oldValue !== value) {
obj[prop] = value
effectsMap[prop]?.forEach((f) => f(value))
const kebab = getKebabCase(prop)
setter(kebab, serialize(value))
}

View file

@ -1,13 +0,0 @@
/**
* Attach a "side effect" function that gets triggered on property value changes
* @param {Object} obj
* @param {(newValue: any) => void} callback
*/
export function attachEffect(obj, callback) {
const { proxy, prop } = Object.getPrototypeOf(obj)
proxy[prop] = {
attach: 'effect',
callback,
}
}

View file

@ -1,3 +1,2 @@
export { attachEffect } from './attach-effect.js'
export { WebComponent } from './WebComponent.js'
export { html } from './html.js'