Ardi: Welcome to the Weightless Web
Tags: webcomponents, javascript, dx, webdev
Published: 2022-10-12 18:52:48

👋🏻 Hi all, this is my first post here. Be nice 😝

I've been working on a new Web Component framework called [Ardi](https://ardi.netlify.app). Ardi packs a ton of features into a tiny 3kb package.

---

## Features

- Reactive props
- Reactive state management
- Declarative rendering
  - Conditional rendering
  - Loops (supporting keyed elements)
  - @event handlers in the template
  - _FAST_ rendering thanks to [µhtml](https://github.com/WebReflection/uhtml)
- A ready() helper to handle effects after the initial render
- A intersect() helper to handle effects after the component is scrolled into view, i.e.
  - Lazy-loading data
  - Scroll-triggered animations
- Works out-of-the-box with React, Vue, Svelte, Angular, etc
  - [Demo](https://ardi.netlify.app/docs.html#frameworks)
- Allows developers to bring their own templating library, i.e. [Handlebars](https://ardi.netlify.app/playground.html?component=helloHbs) or [JSX](https://ardi.netlify.app/playground.html?component=helloJsx).

---

## Goals

The question you're probably already asking is "for f***'s sake, why do we need another web component framework?" and that's a really good question. I had a few specific goals in mind when I started working on Ardi.

1. Make web components fast
2. Make web components fun and familiar
3. Make web components work with any existing framework

---

## 1. Make web components fast

Though I love custom elements, there are a few issues that make them problematic for production work.

### CLS

**Challenge**: Client-side custom elements either 1) cause horrible CLS when they load or 2) need to block rendering until they are loaded to prevent CLS. **Solution**: That means the framework itself needs to be _tiny_ and fast itself. Compressed and minified, Ardi is only 3kb, and you can load it from NPM or a CDN.

### Efficient Rendering

**Challenge**: With declarative rendering in other libraries, oftentimes entire DOM trees are re-painted because of simple prop or state changes that could have been handled faster by imperative DOM manipulation. I wanted a framework that, like Lit, only updated content or attributes that had changed instead of re-painting entire DOM elements and trees. **Solution**: I chose [µhtml](https://github.com/WebReflection/uhtml) for the default templating system because it accomplishes this goal and other advanced templating features in a _tiny_ bundle size. To make rendering even faster and smoother, I throttled uhtml's rendering using requestAnimationFrame.

> In the devtools on the right, notice how the whole element isn't re-painted, just the parts that have changed.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/g9y4ltkkgia98fgf7beq.gif)

---

## 2. Make web components fun and familiar

I wanted a simple and modern DX that lets me start exploring a new idea quickly without typing a bunch of boilerplate code first. I also wanted to make it easy for developers to bring their favorite templating systems like Handlebars or JSX.

### Boilerplate

**Challenge**: Many of the current web component libraries make you import and extend classes, write opinionated boilerplate code and manually define the custom element. **Solution**: Reduce boilerplate code as much as possible.

{% codepen https://codepen.io/jameslovallo/pen/YzLddOK default-tab=js,result height=350 %}

### State Management

**Challenge**: Custom elements do not provide any state management solution. State management is a huge part of what makes modern frameworks _modern_ and is an essential component of declarative rendering. **Solution** Ardi provides its own reactivity solution in the form of this.state.

```js
ardi({
  component: 'keyboard-demo',

  state: {
    recording: false,
    tracks: [],
  },
})

```

In the example above, the component's state contains a "recording" boolean and a "tracks" array. When this.state.recording is set to true, any note played on the keyboard is saved with a timestamp into a new array. When recording is set to false, the array of recorded notes is saved into the tracks array. When this.state.tracks is updated, the list of recorded tracks at the bottom of the component is updated immediately, just like you would expect if you were using React, Vue, or similar.

### Learning Curve

**Challenge**: I've worked with a lot of developers who are very opinionated and passionate about one templating system, and who are therefore reluctant to try out new frameworks or new standards like custom elements. **Solution**: Ardi is very _unopinionated_ and allows you to easily override the default render() function to support other templating libraries like [Handlebars](https://ardi.netlify.app/playground.html?component=helloHbs) or [JSX](https://ardi.netlify.app/playground.html?component=helloJsx) (click those links for demos).

---

## 3. Make web components work with any existing framework

The true magic of web components is being able to build them once and use them anywhere, but there are still some limitations to the API.

### Reactivity and Interoperability

**Challenge**: Properties are not the same thing as attributes. When you (or React, or Vue) update an attribute on a custom element, nothing happens. That change is not reflected in the component's properties. Custom elements do provide an observedAttributes getter and attributeChangedCallback, but it is cumbersome to 1) define observed attributes, 2) recognize when those attributes have changed, 3) get the value of the attribute, 4) convert that value from a string to another data type, and 5) update the template appropriately. **Solution**: Make it easy to create reactive props, each of which has a _setter function_ and an optional default value.

```js
ardi({
  component: 'keyboard-demo',

  props: {
    instrument: [(v) => (v === 'sitar' ? 'acoustic' : v), 'piano'],
    octaves: [Number, 2],
    start: [Number, 3],
    sustain: [Number, 2],
  },
})

```

In the example above, if the "octaves" attribute is changed on the element, this.octaves will also be set to the value of Number(this.getAttribute('octaves')) and the template will be updated. If this.octaves is changed, the attribute will also be updated, along with the template. Data is kept perfectly in-sync.

### Ardi ❤️ React
{% codepen https://codepen.io/jameslovallo/pen/XWqNNNo default-tab=result height=500 %}

### Ardi ❤️ Vue
{% codepen https://codepen.io/jameslovallo/pen/KKRgrrd default-tab=result height=500 %}

---

## Check it out!

If you're looking for a lightweight, un-opinionated and highly-optimized web component framework, I'd encourage you to check out the [docs](https://ardi.netlify.app/), play with the [demos](https://ardi.netlify.app/#demos), or try it out in the [playground](https://ardi.netlify.app/playground.html).

If you like Ardi, like this post or  [give it a star](https://github.com/jameslovallo/ardi). If you have an issue or you're interested in contributing, feel free to [open an issue](https://github.com/jameslovallo/ardi/issues) or [create a pull request](https://github.com/jameslovallo/ardi/pulls).