master-wiki/Read Later/2023-11-06 - How to Write Components that Work in Any Framework.md
aleidk 7437d8e57c Update from obsidian - thinkpad
Affected files:
.obsidian/plugins/dataview/data.json
.obsidian/plugins/obsidian-omnivore/data.json
Read Later/2023-10-14 - Using CSS custom properties like this is a waste - YouTube.md
Read Later/2023-10-14 - You Don’t Actually Want Open World Games - YouTube.md
Read Later/2023-10-15 - Highlighting fold text, community fork of null-ls, leetcode integration, reduce ram ....md
Read Later/2023-10-18 - The Unreasonable Effectiveness Of Plain Text.md
Read Later/2023-10-21 - How Game Reviews Actually Affect You.md
Read Later/2023-10-23 - Study shows stronger brain activity after writing on paper than on tablet or smartph....md
Read Later/2023-10-23 - Train Your Brain to Be More Creative.md
Read Later/2023-10-25 - Let's Get Webby! 🦀 🕸️.md
Read Later/2023-10-25 - What the Rust Book didn't tell you about testing....md
Read Later/2023-10-31 - Use cases for Rust.md
Read Later/2023-11-01 - Why Signals Are Better Than React Hooks.md
Read Later/2023-11-02 - The First Rule of Comments in Code.md
Read Later/2023-11-02 - Web Accessibility Tips for Developers – A11y Principles Explained.md
Read Later/2023-11-04 - Git Merge vs Rebase vs Squash ¿Qué estrategia debemos elegir-.md
Read Later/2023-11-06 - How to Write Components that Work in Any Framework.md
Read Later/2023-11-07 - How to Avoid Prop Drilling in React.md
Read Later/2023-11-10 - The Complete Guide to Time Blocking.md
Read Later/2023-11-14 - The one thing you need to finish your game.md
Read Later/2023-11-21 - Career Mistakes to Avoid as a Developer.md
Read Later/2023-11-21 - Conventional Commits.md
Read Later/2023-11-21 - How to Write Better Git Commit Messages – A Step-By-Step Guide.md
Read Later/2023-11-21 - The Life-Changing Magic of Tidying Up Your To-Do List.md
Read Later/2023-11-21 - tbaggery - A Note About Git Commit Messages.md
Read Later/2023-12-24 - Historias de usuario - Ejemplos y plantilla - Atlassian.md
Read Later/2023-12-24 - ¿Qué son los puntos de historia en la metodología ágil y cómo se estiman-.md
Read Later/2024-02-15 - React Optimization Techniques to Help You Write More Performant Code.md
Read Later/How to Learn Rust.md
Read Later/The Secret Power of ‘Read It Later’ Apps.md
2024-02-17 16:23:43 -03:00

402 lines
No EOL
20 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
id: 616d5d08-7d04-11ee-8eaa-9f56108b78ec
title: |
How to Write Components that Work in Any Framework
status: ARCHIVED
tags:
- read-later
- RSS
date_added: 2023-11-06 17:25:12
url_omnivore: |
https://omnivore.app/me/how-to-write-components-that-work-in-any-framework-18ba72d0079
url_original: |
https://www.freecodecamp.org/news/write-components-that-work-in-any-framework/
---
# How to Write Components that Work in Any Framework
## Highlights
With Custom Elements you can author your own custom HTML elements that you can reuse across your site. They can be as simple as text, images, or visual decorations. You can push them further and build interactive components, complex widgets, or entire web applications.
[source](https://omnivore.app/me/how-to-write-components-that-work-in-any-framework-18ba72d0079#bceef8c0-728e-422a-aed6-b047736cb395)
---
### Writing a web component requires understanding all of its underlying technologies
As we saw above, web components are made up of three technologies. You can also see in the hello world code snippet, that we explicitly need to know and understand these three technologies.
1. Were creating a **template element** and setting its inner HTML
2. Were creating a **shadow root**, and explicitly setting its mode to open.
3. Were cloning our **template** and appending it to our **shadow root**
4. Were registering a new **custom element** to the document
[source](https://omnivore.app/me/how-to-write-components-that-work-in-any-framework-18ba72d0079#46fc130a-1549-40c8-b950-42035c227bc4)
---
As web component authors, we need to consider a lot of things:
* Setting up the shadow DOM
* Setting up the HTML templates
* Cleaning up event listeners
* Defining properties that we want to observe
* Reacting to properties when they change
* Handling type conversions for attributes
[source](https://omnivore.app/me/how-to-write-components-that-work-in-any-framework-18ba72d0079#855f444c-49f1-4176-9537-aaeeb6a01355)
---
One such tool is called Lit, which is developed by a team at Google. [Lit](https://lit.dev/) is a lightweight library designed to make writing web components simple, by removing the need for the boilerplate weve already seen above.
[source](https://omnivore.app/me/how-to-write-components-that-work-in-any-framework-18ba72d0079#385d9ef8-13fb-4799-bff5-ef767b3df67f)
---
## Original
![How to Write Components that Work in Any Framework](https://proxy-prod.omnivore-image-cache.app/1200x600,sbNnkMyaVUIiiSYXNfn_YVuWBIhu0N84ey_fbF6pQlVw/https://www.freecodecamp.org/news/content/images/size/w2000/2023/11/og-button.png)
The browser has a built-in way of writing reusable components in the form of **web components**. Theyre an excellent choice for building interactive and reusable components that work in any frontend framework.
With that said, writing highly interactive and robust web components isnt simple. They require a lot of boilerplate and feel much less intuitive than the components you may have written in frameworks like React, Svelte, or Vue.
In this tutorial, Ill give you an example of an interactive component written as a web component, and then refactor it using a library that softens the edges and removes heaps of boilerplate.
Dont sweat it if youre not familiar with web components. In the next section, Ill do a (brief) overview of what web components are, and what theyre made out of. If you have some basic experience with them, you can skip the next section.
## What are Web Components?
Before web components, the browser didnt have a standard way of writing reusable components. Many libraries solve this problem, but they often run into limitations like performance, interoperability, and issues with web standards.
Web components are a technology made up of 3 different browser features:
* Custom elements
* Shadow DOM
* HTML Templates
Well do a quick crash course covering these technologies, but its by no means a comprehensive breakdown.
### What are Custom Elements?
==With Custom Elements you can author your own custom HTML elements that you can reuse across your site. They can be as simple as text, images, or visual decorations. You can push them further and build interactive components, complex widgets, or entire web applications.==
Youre not just limited to using them in your projects, but you can publish them and allow other developers to use them on their sites.
Here are some of the reusable components from my library [A2K](https://a2000-docs.netlify.app/). You can see that they come in all shapes and sizes, and have a range of different functionalities. Using them in your projects is similar to using any old HTML element.
![A small collection of web components from the A2K library](https://proxy-prod.omnivore-image-cache.app/1936x1576,sJHqJR3aX72y4hbKfS4SGGmJwoFGcBRrCJxB2ozU2rIU/https://www.freecodecamp.org/news/content/images/2023/11/web-components.png)
A small collection of web components from the A2K library
Heres how youd use the progress element in your project:
```xml
<!DOCTYPE html>
<html>
<head>
<title>Quick Start</title>
<meta charset="UTF-8" />
</head>
<body>
<!-- Use web components in your HTML like regular built-in elements. -->
<a2k-progress progress="50" />
<!-- a2k web components use standard JavaScript modules. -->
<script type="module">
import 'https://cdn.jsdelivr.net/npm/@a2000/progress@0.0.5/lib/src/a2k-progress.js';
</script>
</body>
</html>
```
Once youve imported the third-party scripts, you can start using the component, `a2k-progress` in this case, just like any other HTML element.
If youre building your own web components, theres virtually no limit to how complex you can make your custom elements.
I recently created a web component that renders a CodeSandbox code editor in the browser. And because its a web component, you can use it in any framework you like! If youd like to learn a little more about that, [you can read more here](https://component-odyssey.com/articles/00-sandpack-lit-universal).
### What is the Shadow DOM?
If you have a working knowledge of CSS, youll know that vanilla CSS is scoped globally. Writing something like this in your global.css:
```css
p {
color: tomato;
}
```
will give all `p` elements a nice orange/red color, assuming that no other, more specific CSS selectors are applied to a `p` element.
Take this select menu, for example:
![A select menu component with a visual design reminiscent of the old Windows operating systems](https://proxy-prod.omnivore-image-cache.app/1034x502,s9CkMnQ9nLrjRpsbeELOUs7SesB_nfO2NrbzcalU2UFE/https://www.freecodecamp.org/news/content/images/2023/11/a2k-select-menu.png)
It has a distinct character which is driven by the visual design. You might want to use this component, but if your global styles affect things like the font family, the color, or the font size, it could cause issues with the appearance of the component:
```xml
<head>
<style>
body {
color: blue;
font-size: 12px;
font-family: system-ui;
}
</style>
</head>
<body>
<a2k-select></a2k-select>
</body>
```
![The same select menu, but with a lot of its defining characteristics overridden by global CSS.](https://proxy-prod.omnivore-image-cache.app/1904x824,s6vwLXZ-23v_oU3NRFu4pagLJRfGUCz14nw0IkGnQuPU/https://www.freecodecamp.org/news/content/images/2023/11/a2k-select-menu-2.png)
This is where the Shadow DOM comes in. The Shadow DOM is an encapsulation mechanism that prevents the rest of the DOM from interfering with your web components. It ensures that the global styles of the web application dont interfere with any components that you consume. It also means that component library developers can author their components with the confidence that theyll look and behave as expected across different web applications.
Theres a lot more nuance when it comes to the Shadow DOM, as well as other features that were not going to touch on in this article. If youd like to learn more about web components though, I have an entire course ([Component Odyssey](https://component-odyssey.com/)) dedicated to teaching you how to build reusable components that work in any framework.
### HTML Templates
The last feature in our whistle-stop tour of web component features is HTML Templates.
What makes this HTML element different from other elements, is that the browser doesnt render its content to the page. If you were to write the following HTML you wouldnt see the text “Im a header” displayed on the page:
```xml
<body>
<template>
<h1>I'm a header</h1>
</template>
</body>
```
Instead of being used to render the content directly, the content of the template is designed to be copied. The copied template can then be used to render content to the page.
You can think of the template element much like the template for a 3D print. The template isnt a physical entity, but its used to create real-life clones.
You would then reference the template element in your web component, clone it, and render the clone as the markup for your component.
I wont spend any more time on these web component features, but youve probably already noticed that to write vanilla web components, there are a lot of new browser features that you need to know and understand.
Youll see in the next section that the mental model for building web components doesnt feel as streamlined as it does for other component frameworks.
## How to Build a Basic Web Component
Now that weve briefly covered the fundamental technologies powering a web component, heres how to build a _hello world_ component:
```scala
const template = document.createElement('template');
template.innerHTML = `<p>Hello World</p>`;
class HelloWorld extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
this.shadowRoot.append(template.content.cloneNode(true));
}
}
customElements.define('hello-world', HelloWorld);
```
This is the most simple component you can write, but theres already so much going on. For someone completely new to web components, and without the background knowledge I provided above, theyre going to be left with a lot of questions, and a lot of confusion.
For me, there are at least two key reasons why web components can be challenging to write, at least within the context of the hello world examples.
### The markup is decoupled from the component logic
In many frameworks, the markup of the component is often treated as a first-class citizen. Its often the content that gets returned from the component function, or has direct access to the components state, or has built-in utilities to help manipulate markup (like loops, conditionals, and so on).
This isnt the case for web components. In fact, the markup is often defined outside of the components class. Theres also no built-in way for the template to reference the current state of the component. This becomes a cumbersome limitation as the complexity of a component grows.
In the world of frontend, components are designed to help developers reuse markup in several pages. As a result, the markup and the component logic are inextricably linked, and so they should be colocated with one another.
### ==Writing a web component requires understanding all of its underlying technologies==
==As we saw above, web components are made up of three technologies. You can also see in the hello world code snippet, that we explicitly need to know and understand these three technologies.==
1. ==Were creating a== **==template element==** ==and setting its inner HTML==
2. ==Were creating a== **==shadow root==**==, and explicitly setting its mode to open.==
3. ==Were cloning our== **==template==** ==and appending it to our== **==shadow root==**
4. ==Were registering a new== **==custom element==** ==to the document==
Theres nothing inherently wrong with this, since web components are supposed to be a “lower-level” browser API, making them prime for building abstractions on top of. But for a developer coming from a React or a Svelte background, having to understand these new browser features, and then having to write components with them can feel like too much friction.
## More Advanced Web Components
Lets take a look at a more advanced web component, a counter button.
![A simple counter button. There's a clickable button, and some text showing how many times the button has been clicked](https://proxy-prod.omnivore-image-cache.app/388x228,sHWCz4gELDISUuDwuoPIHQHGqCm0zEx5YHunEIzF9BE0/https://www.freecodecamp.org/news/content/images/2023/11/counter-button.png)
You click the button, and the counter increments.
The following example contains a few extra web component concepts, like lifecycle functions and observable attributes. You dont need to understand everything going on in the code snippet. This example is really only used to illustrate how much boilerplate is required for the most basic of interactive interfaces, a counter button:
```kotlin
const templateEl = document.createElement("template");
templateEl.innerHTML = `
<button>Press me!</button>
<p>You pressed me 0 times.</p>
`;
export class OdysseyButton extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: "open" });
this.shadowRoot.appendChild(templateEl.content.cloneNode(true));
this.button = this.shadowRoot.querySelector("button");
this.p = this.shadowRoot.querySelector("p");
this.setAttribute("count", "0");
}
// Note: Web components have lifecycle methods,
// If we're setting event listeners when the component is added to the DOM, it's our job to clean
// them up when it gets removed from the DOM
connectedCallback() {
this.button.addEventListener("click", this.handleClick);
}
disconnectedCallback() {
this.button.removeEventListener("click", this.handleClick);
}
// Unlike frameworks like React, Web Components don't automatically rerender when a prop (or attribute)
// changes. Instead, we need to explicitly define which attributes we want to observe.
static get observedAttributes() {
return ["disabled", "count"];
}
// When one of the above attributes changes, this lifecycle method runs, and we can
// react to the new attribute's value accordingly.
attributeChangedCallback(name, _, newVal) {
if (name === "count") {
this.p.innerHTML = `You pressed me ${newVal} times.`;
}
if (name === "disabled") {
this.button.disabled = true;
}
}
// In HTML, attribute values are always strings. This means that it's our job to
// convert types. You can see below that we're converting a string -> number, and then back to a string
handleClick = () => {
const counter = Number(this.getAttribute("count"));
this.setAttribute("count", `${counter + 1}`);
};
```
==As web component authors, we need to consider a lot of things:==
* ==Setting up the shadow DOM==
* ==Setting up the HTML templates==
* ==Cleaning up event listeners==
* ==Defining properties that we want to observe==
* ==Reacting to properties when they change==
* ==Handling type conversions for attributes==
And there are still so many other things to consider that I havent touched on in this article.
That isnt to say that web components are bad and that you shouldnt write them. In fact, Id argue that you learn so much about the browser platform by building with them.
But I feel that there are better ways to write components if your priority is to write interoperable components in a much more streamlined and ergonomic way.
## How to Write Web Components with Less Boilerplate
As I mentioned earlier, there are a lot of tools out there to help make writing web components much easier.
==One such tool is called Lit, which is developed by a team at Google.== ==[Lit](https://lit.dev/)== ==is a lightweight library designed to make writing web components simple, by removing the need for the boilerplate weve already seen above.==
As well see, Lit does a lot of heavy lifting under-the-hood to help cut down the total lines of code by nearly half! And because Lit is a wrapper around web components and other native browser features, all your existing knowledge about web components is transferable.
To start seeing how Lit simplifies your web components. Heres the **hello world** example from earlier, but refactored to use Lit instead of a vanilla web component:
```scala
import { LitElement, html } from "lit";
export class HelloWorld extends LitElement {
render() {
return html`<p>Hello World!</p>`;
}
}`
customElements.define('hello-world', HelloWorld);
```
Theres a lot less boilerplate with the Lit component, and Lit handles the two problems I mentioned earlier, a little bit differently. Lets see how:
1. The markup is directly defined from within the component class. While you can define your templates outside of the class, its common practice to return the template from the `render` function. This is more in line with the mental model presented in other UI frameworks, where the UI is a function of the state.
2. Lit also doesnt require developers to attach the shadow DOM, or create templates and clone template elements. While having an understanding of the underlying web component features will help when developing Lit components, theyre not required for getting started, so the barrier for entry is much lower.
So now for the big finale, what does the counter component look like once weve migrated it over to Lit?
```typescript
import { LitElement, html } from "lit";
export class OdysseyCounter extends LitElement {
static properties = {
// We define the component's properties as well as their type.
// These properties will trigger the component to re-render when their values change.
// While they're not the same, you can think of these "properties" as being
// Lit's alternatives to "observed attributes"
// If the value is passed down as an attribute, Lit converts the value
// to the correct type
count: { type: Number },
disabled: { type: Boolean },
};
constructor() {
super();
// There's no need to create a shadow DOM, clone the template,
// or store references to our DOM nodes.
this.count = 0;
}
onCount() {
this.count = this.count + 1;
}
render() {
// Instead of using the attributeChangedCallback lifecycle, the
// render function has access to all of the component's properties,
// which simplifies the process of manipulating our templates.
return html`
<button ?disabled=${this.disabled} @click=${this.onCount}>
Press me!
</button>
<p>You pressed me ${this.count} times.</p>
`;
}
}`
```
The amount of code were writing is cut down by almost half! And this difference becomes more noticeable when creating more complex user interfaces.
## Why am I going on about Lit?
Im a big believer in web components, but I recognise that the barrier to entry is high for many developers. Writing complex web components requires understanding heaps of browser features and the education around web components isnt as comprehensive as other technologies, like React or Vue.
This is why I think its important to use tools like Lit can make writing performant and interoperable web components much easier. This is great if you want your components to work within any frontend framework.
If youd like to learn even more, this is the approach I teach in my upcoming course [Component Odyssey](https://component-odyssey.com/). This course is excellent for anyone who wants to understand how to write components that work in any framework.
I do this by covering the absolute basics of web components, before moving on to tools like Lit that simplify the process of writing web components without complicating your development environment. By the end, youll learn how to build and publish a component library that works across any frontend framework.
If you want early-bird discount codes for Component Odyssey, then head on [over to the site to get notified](https://component-odyssey.com/subscribe).
---
---
Learn to code for free. freeCodeCamp's open source curriculum has helped more than 40,000 people get jobs as developers. [Get started](https://www.freecodecamp.org/learn/)