Torna al blog

Web Components: creare elementi riutilizzabili per applicazioni moderne

Scopri come utilizzare i Web Components per creare interfacce modulari e riutilizzabili. Una guida completa a questa tecnologia nativa del browser per costruire elementi personalizzati interoperabili.

Edoardo Midali

Edoardo Midali

Developer · Content Creator

16 min di lettura
Web Components: creare elementi riutilizzabili per applicazioni moderne

Introduzione

La complessità crescente delle applicazioni web ha portato ad un'evoluzione costante degli strumenti e dei paradigmi di sviluppo. Negli ultimi anni, abbiamo assistito alla proliferazione di framework JavaScript che, seppur potenti, hanno creato ecosistemi spesso isolati e incompatibili tra loro. Questa frammentazione ha complicato la condivisione di componenti UI e ha aumentato il rischio di dipendere da tecnologie proprietarie che potrebbero diventare obsolete.

In risposta a questa sfida, il W3C ha standardizzato i Web Components: un insieme di tecnologie native del browser che permettono di creare elementi HTML personalizzati, riutilizzabili e interoperabili. Questa innovazione rappresenta un passo significativo verso un web più componibile, dove gli elementi dell'interfaccia possono essere condivisi indipendentemente dal framework o dalla libreria utilizzata.

In questo articolo, esploreremo cosa sono i Web Components, come si sono evoluti nel tempo, perché rappresentano un'opzione importante per lo sviluppo moderno, e come utilizzarli concretamente nei vostri progetti. Vedremo come questa tecnologia nativa offra un'alternativa interessante ai componenti framework-specific, permettendo di costruire interfacce modulari che resistono al passare del tempo e ai cambiamenti dell'ecosistema frontend.

Cosa sono i Web Components

I Web Components sono un insieme di tecnologie web standard che permettono di creare elementi HTML personalizzati, incapsulati e riutilizzabili. A differenza dei componenti creati con framework come React o Vue, i Web Components funzionano nativamente nei browser moderni, senza dipendenze esterne.

Le tecnologie fondamentali

I Web Components si basano su quattro tecnologie principali, anche se oggi ci si riferisce spesso a tre di esse come il nucleo di questa specifica:

  1. Custom Elements: API che permette di definire nuovi tipi di elementi HTML
  2. Shadow DOM: Meccanismo che consente di incapsulare markup e stili, isolandoli dal resto del documento
  3. HTML Templates: Elementi <template> e <slot> che consentono di dichiarare frammenti di markup riutilizzabili
  4. HTML Imports: Una modalità per importare documenti HTML (ormai deprecata e sostituita dai moduli ES)

Queste tecnologie lavorano insieme per creare componenti autonomi che possono essere utilizzati in qualsiasi applicazione web, indipendentemente dagli strumenti di sviluppo scelti.

Caratteristiche principali

I Web Components offrono diverse caratteristiche chiave:

  • Encapsulation: Lo Shadow DOM isola il CSS e il markup interno, evitando conflitti con il resto della pagina
  • Riutilizzabilità: Una volta definiti, possono essere utilizzati in qualsiasi pagina o applicazione
  • Interoperabilità: Funzionano con JavaScript vanilla e con la maggior parte dei framework
  • Standardizzazione: Essendo specifiche W3C, sono supportati nativamente dai browser moderni
  • Longevità: Sono meno soggetti all'obsolescenza rispetto ai componenti framework-specific

Concetti fondamentali

Per comprendere i Web Components, è importante familiarizzare con alcuni concetti chiave:

  • Custom Element: Un elemento HTML personalizzato con comportamenti definiti tramite JavaScript
  • Shadow DOM: Un DOM separato e incapsulato all'interno di un elemento
  • Shadow Root: Il nodo radice dello Shadow DOM
  • Shadow Boundary: Il confine che isola lo Shadow DOM dal DOM principale
  • Slot: Punti di inserimento che permettono di passare contenuto dall'esterno all'interno di un componente

La storia dei Web Components

L'evoluzione dello standard

I Web Components hanno seguito un percorso di sviluppo relativamente lungo:

  • 2011: Google introduce il concetto di Web Components al Google I/O
  • 2012-2014: Sviluppo delle specifiche iniziali presso il W3C
  • 2014-2015: Primo supporto parziale nei browser, principalmente Chrome
  • 2016: Specifiche v0 per Custom Elements e Shadow DOM
  • 2017-2018: Specifiche v1, più mature e con un consenso più ampio
  • 2018-2019: Adozione crescente delle specifiche v1 da parte dei principali browser
  • 2019-2020: Firefox completa il supporto per le API fondamentali
  • 2020-2022: Crescita dell'ecosistema, con librerie, strumenti e componenti condivisi
  • 2023-oggi: Adozione mainstream, con grandi aziende che costruiscono design system basati su Web Components

Dal polyfill al supporto nativo

Nei primi anni, i Web Components erano accessibili solo tramite polyfill come Polymer o X-Tag. Oggi, il supporto è ampiamente disponibile nei browser moderni, rendendo i polyfill sempre meno necessari.

L'impatto di framework e librerie

L'evoluzione dei Web Components è stata influenzata anche dall'ecosistema circostante:

  • Polymer: Progetto Google che ha promosso e semplificato l'uso dei Web Components
  • Stencil: Compilatore di componenti creato dal team Ionic
  • Lit: Libreria leggera per la creazione di Web Components (evoluzione di Polymer)
  • Framework ibridi: Svelte, Solid e altri che possono compilare in Web Components

Come funzionano i Web Components

Creazione di un Custom Element

Il primo passo per creare un Web Component è definire una classe che estende HTMLElement e registrarla come Custom Element:

// Definizione della classe per il componente
class HelloWorld extends HTMLElement {
  constructor() {
    super();
    // Inizializzazione del componente
  }

  // Metodi del ciclo di vita
  connectedCallback() {
    this.innerHTML = `<h1>Hello, World!</h1>`;
  }
}

// Registrazione del custom element
customElements.define("hello-world", HelloWorld);

Una volta registrato, l'elemento può essere utilizzato nel HTML come qualsiasi altro tag:

<hello-world></hello-world>

Utilizzo dello Shadow DOM

Per incapsulare il markup e gli stili, è possibile attivare lo Shadow DOM:

class EncapsulatedComponent extends HTMLElement {
  constructor() {
    super();
    // Attiva lo Shadow DOM
    this.attachShadow({ mode: "open" });

    // Aggiunge contenuto allo Shadow DOM
    this.shadowRoot.innerHTML = `
      <style>
        /* Questi stili sono limitati al componente */
        p { color: red; }
      </style>
      <p>Questo paragrafo ha stile incapsulato</p>
    `;
  }
}

customElements.define("encapsulated-component", EncapsulatedComponent);

Lo stile definito all'interno dello Shadow DOM influenzerà solo gli elementi all'interno del componente, non il resto della pagina.

Template e slot

Con <template> e <slot>, è possibile creare componenti più flessibili:

<!-- Definizione del template -->
<template id="user-card-template">
  <style>
    .card {
      border: 1px solid #ccc;
      padding: 10px;
    }
    .name {
      font-weight: bold;
    }
  </style>

  <div class="card">
    <div class="name">
      <slot name="username">Nome utente predefinito</slot>
    </div>
    <div>
      <slot name="details">Nessun dettaglio disponibile</slot>
    </div>
  </div>
</template>
class UserCard extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({ mode: "open" });

    // Clona il template e lo aggiunge allo Shadow DOM
    const template = document.getElementById("user-card-template");
    this.shadowRoot.appendChild(template.content.cloneNode(true));
  }
}

customElements.define("user-card", UserCard);

Utilizzo con slot:

<user-card>
  <span slot="username">Mario Rossi</span>
  <span slot="details">Sviluppatore Web</span>
</user-card>

Ciclo di vita di un Web Component

I Custom Elements forniscono metodi del ciclo di vita che permettono di controllare il comportamento del componente:

  • constructor(): Chiamato quando l'elemento viene creato o aggiornato
  • connectedCallback(): Invocato quando l'elemento viene aggiunto al DOM
  • disconnectedCallback(): Invocato quando l'elemento viene rimosso dal DOM
  • attributeChangedCallback(name, oldValue, newValue): Chiamato quando un attributo osservato cambia
  • adoptedCallback(): Chiamato quando l'elemento viene spostato in un nuovo documento

Per osservare gli attributi, è necessario definire un metodo statico observedAttributes:

class ResponsiveElement extends HTMLElement {
  static get observedAttributes() {
    return ["size"];
  }

  attributeChangedCallback(name, oldValue, newValue) {
    if (name === "size") {
      this.style.fontSize = `${newValue}px`;
    }
  }
}

Esempi pratici di Web Components

Vediamo alcuni esempi concreti di Web Components per casi d'uso comuni.

Esempio 1: Toggle Switch

class ToggleSwitch extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({ mode: "open" });

    this.shadowRoot.innerHTML = `
      <style>
        :host {
          display: inline-block;
        }
        .switch {
          position: relative;
          width: 60px;
          height: 34px;
        }
        .slider {
          position: absolute;
          cursor: pointer;
          top: 0;
          left: 0;
          right: 0;
          bottom: 0;
          background-color: #ccc;
          transition: .4s;
          border-radius: 34px;
        }
        .slider:before {
          position: absolute;
          content: "";
          height: 26px;
          width: 26px;
          left: 4px;
          bottom: 4px;
          background-color: white;
          transition: .4s;
          border-radius: 50%;
        }
        :host([checked]) .slider {
          background-color: #2196F3;
        }
        :host([checked]) .slider:before {
          transform: translateX(26px);
        }
      </style>
      <label class="switch">
        <div class="slider"></div>
      </label>
    `;

    this.addEventListener("click", this._onClick.bind(this));
  }

  _onClick() {
    this.checked = !this.checked;
    this.dispatchEvent(
      new CustomEvent("change", {
        detail: { checked: this.checked },
        bubbles: true,
      })
    );
  }

  get checked() {
    return this.hasAttribute("checked");
  }

  set checked(value) {
    if (value) {
      this.setAttribute("checked", "");
    } else {
      this.removeAttribute("checked");
    }
  }
}

customElements.define("toggle-switch", ToggleSwitch);

Utilizzo:

<toggle-switch id="myToggle"></toggle-switch>
<script>
  document.getElementById("myToggle").addEventListener("change", (e) => {
    console.log("Stato toggle:", e.detail.checked);
  });
</script>

Esempio 2: Tooltip personalizzato

class CustomTooltip extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({ mode: "open" });

    this.shadowRoot.innerHTML = `
      <style>
        :host {
          position: relative;
          display: inline-block;
        }
        .tooltip-content {
          visibility: hidden;
          position: absolute;
          z-index: 1;
          bottom: 125%;
          left: 50%;
          transform: translateX(-50%);
          background-color: black;
          color: white;
          padding: 5px 10px;
          border-radius: 4px;
          opacity: 0;
          transition: opacity 0.3s;
        }
        :host(:hover) .tooltip-content {
          visibility: visible;
          opacity: 1;
        }
      </style>
      <slot></slot>
      <div class="tooltip-content">
        <slot name="content">Tooltip default</slot>
      </div>
    `;
  }
}

customElements.define("custom-tooltip", CustomTooltip);

Utilizzo:

<custom-tooltip>
  Passa il mouse qui
  <span slot="content">Questo è un tooltip personalizzato!</span>
</custom-tooltip>

Esempio 3: Card componente con immagine e contenuto

class ContentCard extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({ mode: "open" });

    this.shadowRoot.innerHTML = `
      <style>
        :host {
          display: block;
          border-radius: 8px;
          overflow: hidden;
          box-shadow: 0 4px 8px rgba(0,0,0,0.1);
          margin: 16px;
          max-width: 300px;
        }
        .card-image {
          width: 100%;
          height: 200px;
          object-fit: cover;
        }
        .card-content {
          padding: 16px;
        }
        ::slotted(h2) {
          margin-top: 0;
          color: #333;
        }
      </style>
      
      <img class="card-image">
      <div class="card-content">
        <slot></slot>
      </div>
    `;

    // Accedi all'elemento immagine per impostarne l'src
    this._imageElement = this.shadowRoot.querySelector(".card-image");
  }

  static get observedAttributes() {
    return ["image"];
  }

  attributeChangedCallback(name, oldValue, newValue) {
    if (name === "image" && this._imageElement) {
      this._imageElement.src = newValue;
    }
  }
}

customElements.define("content-card", ContentCard);

Utilizzo:

<content-card image="https://esempio.com/immagine.jpg">
  <h2>Titolo della card</h2>
  <p>Descrizione della card con dettagli sul contenuto.</p>
</content-card>

Vantaggi dei Web Components

Vantaggi tecnici

  • Standard nativi: Funzionano direttamente nei browser moderni senza dipendenze esterne
  • Incapsulamento reale: Lo Shadow DOM isola veramente CSS e markup, risolvendo problemi di conflitto di stili
  • Interoperabilità: Funzionano con qualsiasi framework o anche con JavaScript vanilla
  • Performance: Essendo nativi del browser, possono offrire migliori performance rispetto a soluzioni di framework
  • Progressive Enhancement: Permettono un approccio di miglioramento progressivo

Vantaggi di sviluppo

  • Componenti riutilizzabili: Creabili una volta e utilizzabili in diversi progetti
  • Componenti trasportabili: Non legati a un framework specifico, possono spostarsi tra progetti diversi
  • Longevità: Meno soggetti all'obsolescenza dei framework
  • Interfaccia DOM familiare: Utilizzano le API DOM standard
  • Modularità: Promuovono uno sviluppo modulare e componibile

Vantaggi organizzativi

  • Design System coerenti: Ideali per costruire librerie di componenti unificate
  • Separazione delle competenze: Team diversi possono sviluppare componenti indipendenti
  • Evoluzione graduale: Possibilità di modernizzare applicazioni esistenti in modo incrementale
  • Proprietà intellettuale: Migliore protezione per componenti proprietari grazie all'incapsulamento

Quando usare i Web Components

Casi d'uso ideali

  • Design Systems: Creazione di librerie di componenti coerenti per l'intera organizzazione
  • Micro-frontend: Integrazione di porzioni di UI sviluppate da team diversi
  • Widget embedded: Componenti destinati a essere inseriti in siti di terze parti
  • Applicazioni di lunga durata: Sistemi che devono resistere ai cambiamenti di framework e tendenze
  • Applicazioni ibride: Progetti che utilizzano diversi framework o stanno migrando da uno all'altro

Quando considerare altre soluzioni

Come ogni tecnologia, i Web Components hanno i loro limiti:

  • Applicazioni small-team: Per piccoli team che lavorano su un'unica applicazione, un framework può offrire più produttività iniziale
  • Ecosistema specifico: Se si è fortemente investito in un ecosistema (React, Vue, ecc.), potrebbe essere più efficiente rimanere all'interno di esso
  • Funzionalità avanzate: Per alcune funzionalità molto avanzate di UI, i framework offrono soluzioni più pronte all'uso

Integrazione con framework e librerie

I Web Components possono essere utilizzati con la maggior parte dei framework moderni.

React

React può lavorare con Web Components, sebbene con alcune considerazioni:

// Utilizzo di un Web Component in React
function MyReactComponent() {
  const handleToggleChange = (e) => {
    console.log("Toggle cambiato:", e.detail.checked);
  };

  return (
    <div>
      <h2>Componente React con Web Component</h2>
      <toggle-switch
        onchange={handleToggleChange}
        ref={(el) => {
          // Accesso diretto al Web Component
          if (el) el.checked = true;
        }}
      />
    </div>
  );
}

Vue

Vue ha un'eccellente interoperabilità con i Web Components:

<template>
  <div>
    <h2>Componente Vue con Web Component</h2>
    <toggle-switch :checked="isChecked" @change="handleChange"></toggle-switch>
  </div>
</template>

<script>
export default {
  data() {
    return {
      isChecked: false,
    };
  },
  methods: {
    handleChange(e) {
      this.isChecked = e.detail.checked;
    },
  },
};
</script>

Angular

Angular supporta nativamente l'utilizzo di Web Components:

@Component({
  selector: "app-component",
  template: `
    <h2>Componente Angular con Web Component</h2>
    <toggle-switch
      [checked]="isChecked"
      (change)="handleChange($event)"></toggle-switch>
  `,
})
export class AppComponent {
  isChecked = false;

  handleChange(event: CustomEvent) {
    this.isChecked = event.detail.checked;
  }
}

Svelte

Svelte può compilare direttamente in Web Components:

<svelte:options tag="svelte-counter" />

<script>
  export let count = 0;

  function increment() {
    count++;
    dispatch('change', { count });
  }

  function dispatch(name, detail) {
    this.dispatchEvent(new CustomEvent(name, { detail }));
  }
</script>

<button on:click={increment}>
  Count: {count}
</button>

Strumenti e librerie per Web Components

Sebbene i Web Components possano essere scritti in JavaScript vanilla, esistono diversi strumenti che ne semplificano lo sviluppo.

Lit

Lit è una libreria leggera sviluppata da Google per semplificare la creazione di Web Components:

import { LitElement, html, css } from "lit";

class SimpleGreeting extends LitElement {
  static properties = {
    name: { type: String },
  };

  static styles = css`
    p {
      color: blue;
    }
  `;

  constructor() {
    super();
    this.name = "Mondo";
  }

  render() {
    return html`<p>Ciao, ${this.name}!</p>`;
  }
}

customElements.define("simple-greeting", SimpleGreeting);

Stencil

Stencil è un compilatore che genera Web Components, creato dal team di Ionic:

import { Component, Prop, h } from "@stencil/core";

@Component({
  tag: "my-component",
  styleUrl: "my-component.css",
  shadow: true,
})
export class MyComponent {
  @Prop() first: string;
  @Prop() last: string;

  render() {
    return (
      <div>
        Hello, {this.first} {this.last}
      </div>
    );
  }
}

Altre librerie e strumenti

  • Hybrids: Approccio funzionale alla creazione di Web Components
  • Shoelace: Raccolta di componenti UI pronti all'uso
  • Lion: Libreria di componenti a basso livello
  • FAST: Sistema di design a Web Components di Microsoft
  • Material Web Components: Implementazione di Material Design
  • WebComponentsJS: Polyfill per browser più datati

La community e l'ecosistema

Crescita e adozione

Negli ultimi anni, l'adozione dei Web Components ha visto una crescita significativa:

  • Grandi aziende: Google, Microsoft, Adobe, IBM, tra le altre, hanno investito in Web Components
  • Open source: Crescenti contributi e progetti comunitari
  • Performance: Miglioramenti continui nelle performance dei browser
  • Design Systems: Sempre più sistemi di design basati su Web Components
  • Formazione: Aumento dei contenuti educativi sull'argomento

Sfide rimanenti

Nonostante i progressi, alcune sfide persistono:

  • Frammentazione: Diversi approcci per lo sviluppo (vanilla, Lit, Stencil, ecc.)
  • Server-side rendering: Ancora complesso rispetto ad alcuni framework
  • Accessibilità: Non garantita automaticamente, richiede attenzione
  • Gestione stato: Meno sofisticata rispetto a framework come Redux o MobX
  • Tooling: Strumenti di sviluppo meno maturi rispetto ad ecosistemi come React

Confronto con altre tecnologie

Web Components vs Framework Components

Aspetto Web Components Framework Components
Standardizzazione Standard web ufficiali Specifici per framework
Interoperabilità Elevata, funzionano ovunque Limitata al loro ecosistema
Incapsulamento Reale, grazie allo Shadow DOM Simulato, attraverso convenzioni
Curva di apprendimento Media, basata su API standard Varia per framework
Ecosistema In crescita, ma meno maturo Vasto per framework popolari
Gestione stato Basilare, richiede soluzioni aggiuntive Spesso integrata
SSR Possibile ma complesso Più maturo in molti framework

Web Components vs HTMX

Aspetto Web Components HTMX
Focus Componenti riutilizzabili Interattività server-driven
Paradigma Componenti client-side Hypermedia server-side
Complessità client Maggiore Minore
Dipendenza dal server Bassa Alta
Casi d'uso ideali Design systems, widget Applicazioni CRUD, form

Il futuro dei Web Components

Tendenze emergenti

  • Declarative Shadow DOM: Permette il server-side rendering dei Web Components
  • Form Associated Custom Elements: Migliore integrazione con i form HTML
  • Progressive Enhancement: Uso crescente per migliorare i componenti HTML esistenti
  • CSS Scope: Alternative complementari allo Shadow DOM con meno restrizioni
  • Islands Architecture: Web Components come isole di interattività in pagine principalmente statiche

Evoluzioni delle specifiche

Le specifiche Web Components continuano a evolversi:

  • Constructable Stylesheets: Per stili più efficienti e condivisibili
  • ElementInternals: API per personalizzare il comportamento interno
  • ShadowRoot adoptedStyleSheets: Per condividere stili tra componenti
  • CSS Shadow Parts: Per stilizzare componenti dall'esterno
  • Container Queries: Per componenti realmente auto-contenuti e responsive

Adozione crescente

Con il miglioramento delle specifiche e degli strumenti, si prevede una crescente adozione in:

  • Design Systems aziendali: Per garantire coerenza attraverso prodotti diversi
  • Integrazioni multi-framework: Per facilitare la collaborazione tra team con stack diversi
  • Componenti di terze parti: Per widget embeddable e integrabili
  • Micro-frontend: Per architetture modulari con team indipendenti

Conclusione

I Web Components rappresentano un passo importante verso un web più componibile, dove elementi dell'interfaccia possono essere creati una volta e riutilizzati ovunque. La loro natura standard e interoperabile offre un'alternativa convincente all'approccio framework-specific, specialmente per componenti che devono essere condivisi tra progetti diversi o resistere al passare del tempo.

La maturazione delle specifiche, il miglioramento del supporto browser e la crescita dell'ecosistema di strumenti hanno reso i Web Components una scelta sempre più praticabile per lo sviluppo di applicazioni moderne. La loro capacità di incapsulare markup, stili e comportamento in un singolo pacchetto autonomo risponde a una necessità fondamentale nello sviluppo frontend contemporaneo.

Come ogni tecnologia, i Web Components non sono la soluzione universale a tutti i problemi. Il loro utilizzo ottimale richiede una valutazione attenta del contesto specifico: dimensione del progetto, requisiti di longevità, necessità di interoperabilità e competenze del team. In molti casi, possono coesistere efficacemente con framework e librerie, offrendo il meglio di entrambi i mondi.

Che si tratti di sviluppare un design system aziendale, migrare gradualmente un'applicazione legacy, o creare componenti destinati a essere incorporati in diversi contesti, i Web Components meritano sicuramente considerazione come parte del toolkit moderno dello sviluppatore frontend. La loro standardizzazione, unita alla crescente adozione da parte di aziende e community, suggerisce che continueranno a giocare un ruolo importante nel futuro dell'architettura web.

Risorse utili