This was a weekend project to hack up a tiny example web component framework using no build techniques while still attempting to provide the same level of DX offered by "build" frameworks. It's called SvWeb Components because I am bad at naming and the patterns kinda resemble svelte.
- Reactive Signals: The framework uses reactive signals to manage state. These signals can be used to store values and automatically update the DOM when those values change.
- Binding to DOM: It provides mechanisms to bind reactive signals to DOM elements for both displaying data and handling user inputs, enabling real-time updates without manual DOM manipulation.
- Computed Signals: In addition to basic reactive signals, the framework supports computed signals, which derive their values from other signals, allowing for dynamic and complex data transformations.
To use the framework, include your JavaScript logic in a module script tag and reference any necessary external scripts, such as the framework's main functionality (/main.js
).
Signals are created using the createSignal
function imported from the framework's core. A signal can hold any value and is used to manage the component's state.
import { createSignal } from "/main.js";
const [value, setValue] = createSignal(initialValue);
The framework allows direct binding of signals to DOM elements for both displaying and updating values. This is typically done through a reference system (ref
attribute) that identifies elements within the component.
value.bindToText(refs.elementRef);
refs.buttonRef.onclick = () => setValue(newValue);
Computed signals are defined similarly to basic signals but take a function that determines their value based on other signal values. They are useful for displaying derived or calculated data.
const computedValue = createSignal(
([dependentSignal]) => `Computed value: ${dependentSignal()}`,
[dependentSignal]
);
Below is a simplified example illustrating how to create a counter component that increments or decrements a count:
<!-- Counter Component -->
<script type="module" name="x-counter">
import { createSignal } from "/main.js";
const [count, setCount] = createSignal(0);
count.bindToText(refs.output);
refs.inc.onclick = () => setCount(count() + 1);
refs.dec.onclick = () => setCount(count() - 1);
</script>
<p ref="output"></p>
<button ref="inc">Increment</button>
<button ref="dec">Decrement</button>
This will create a custom element <x-counter>
that can be used anywhere in your markup. Check out the src/index.html
file for examples.
Just add a script tag to your component and styles will be scoped using shadowDOM
<style>
button {
padding: 15px;
}
</style>
Simple separate multiple components in a single file with a component boundary:
--- component boundary ---
See the src/components.html
file for examples.