Los componentes web son más fáciles de lo que piensas | Programar Plus

Cuando iba a una conferencia (cuando podíamos hacer esas cosas) y veía a alguien hacer una presentación sobre componentes web, siempre pensaba que era bastante ingenioso (sí, aparentemente, soy de 1950), pero siempre parecía complicado y excesivo. Mil líneas de JavaScript para guardar cuatro líneas de HTML. El orador inevitablemente pasaba por alto los montones de JavaScript para que funcionara o entraba en detalles insoportables y mis ojos se nublaban al pensar si mi viático cubría los refrigerios.

Pero en un proyecto de referencia reciente para facilitar el aprendizaje de HTML (al agregar zombis y bromas tontas, por supuesto), el completista que hay en mí decidió que tenía que cubrir cada elemento HTML en la especificación. Más allá de esas presentaciones en la conferencia, esta fue mi primera introducción a la <slot> y <template> elementos. Pero cuando traté de escribir algo preciso y atractivo, me vi obligado a profundizar un poco más.

Y he aprendido algo en el proceso: Los componentes web son mucho más fáciles de lo que recuerdo.

O los componentes web han recorrido un largo camino desde la última vez que me sorprendí soñando despierto con bocadillos en una conferencia, o dejé que mi miedo inicial hacia ellos se interpusiera en el camino para conocerlos realmente, probablemente ambas cosas.

Estoy aquí para decirle que usted, sí, puede crear un componente web. Dejemos nuestras distracciones, miedos e incluso nuestros bocadillos en la puerta por un momento y hagamos esto juntos.

Comencemos con el <template>

A <template> es un elemento HTML que nos permite crear, bueno, una plantilla: la estructura HTML para el componente web. Una plantilla no tiene que ser una gran cantidad de código. Puede ser tan simple como:

<template>
  <p>The Zombies are coming!</p>
</template>

El <template> elemento es importante porque mantiene las cosas unidas. Es como los cimientos de un edificio; es la base a partir de la cual se construye todo lo demás. Usemos este pequeño fragmento de HTML como plantilla para un <apocalyptic-warning> componente web, ya sabes, como una advertencia cuando el apocalipsis zombie está sobre nosotros.

Luego está el <slot>

<slot> es simplemente otro elemento HTML como <template>. Pero en este caso, <slot> personaliza lo que <template> renderiza en la página.

<template>
  <p>The <slot>Zombies</slot> are coming!</p>
</template>

Aquí, hemos incluido (¿es eso siquiera una palabra?) la palabra “Zombies” en el marcado de la plantilla. Si no hacemos nada con la ranura, el valor predeterminado es el contenido entre las etiquetas. Eso sería “Zombies” en este ejemplo.

Utilizando <slot> es muy parecido a tener un marcador de posición. Podemos usar el marcador de posición tal como está, o definir algo más para que entre allí. Hacemos eso con el name atributo.

<template>
  <p>The <slot name="whats-coming">Zombies</slot> are coming!</p>
</template>

El name El atributo le dice al componente web qué contenido va en qué lugar de la plantilla. En este momento, tenemos un espacio llamado whats-coming. Suponemos que los zombis llegarán primero en el apocalipsis, pero el <slot> nos da cierta flexibilidad para colocar algo más, como si termina siendo un robot, un hombre lobo o incluso un apocalipsis de componentes web.

Usando el componente

Técnicamente hemos terminado de “escribir” el componente y podemos colocarlo en cualquier lugar que queramos usarlo.

<apocalyptic-warning>
  <span slot="whats-coming">Halitosis Laden Undead Minions</span>
</apocalyptic-warning>

<template>
  <p>The <slot name="whats-coming">Zombies</slot> are coming!</p>
</template>

Ves lo que hicimos allí? ponemos el <apocalyptic-warning> componente en la página como cualquier otro <div> o lo que sea. Pero también dejamos caer un <span> allí que hace referencia a la name atributo de nuestro <slot>. y que hay entre eso <span> es lo que queremos cambiar por “Zombies” cuando se procesa el componente.

Aquí hay un pequeño problema que vale la pena mencionar: los nombres de los elementos personalizados deben tener un guión en ellos. Es solo una de esas cosas que tienes que saber al entrar en cosas. La especificación prescribe eso para evitar conflictos en caso de que HTML publique un nuevo elemento con el mismo nombre.

¿Sigues conmigo hasta ahora? No da demasiado miedo, ¿verdad? Bueno, menos los zombis. Todavía tenemos un poco de trabajo por hacer para hacer el <slot> intercambio posible, y ahí es donde empezamos a entrar en JavaScript.

Registro del componente

Como dije, necesitas algo de JavaScript para que todo esto funcione, pero no es el código súper complejo, de mil líneas y en profundidad que siempre pensé. Espero poder convencerte a ti también.

Necesita una función constructora que registre el elemento personalizado. De lo contrario, nuestro componente es como los muertos vivientes: está ahí pero no completamente vivo.

Aquí está el constructor que usaremos:

// Defines the custom element with our appropriate name, <apocalyptic-warning>
customElements.define("apocalyptic-warning",

  // Ensures that we have all the default properties and methods of a built in HTML element
  class extends HTMLElement {

    // Called anytime a new custom element is created
    constructor() {

      // Calls the parent constructor, i.e. the constructor for `HTMLElement`, so that everything is set up exactly as we would for creating a built in HTML element
      super();

      // Grabs the <template> and stores it in `warning`
      let warning = document.getElementById("warningtemplate");

      // Stores the contents of the template in `mywarning`
      let mywarning = warning.content;

      const shadowRoot = this.attachShadow({mode: "open"}).appendChild(mywarning.cloneNode(true));
    }
  });

Dejé comentarios detallados allí que explican las cosas línea por línea. Excepto la última línea:

const shadowRoot = this.attachShadow({mode: "open"}).appendChild(mywarning.cloneNode(true));

Estamos haciendo mucho aquí. Primero, estamos tomando nuestro elemento personalizado (this) y crear un operativo clandestino, quiero decir, shadow DOM. mode: open simplemente significa que JavaScript desde fuera del :root puede acceder y manipular los elementos dentro del shadow DOM, algo así como configurar el acceso de puerta trasera al componente.

A partir de ahí, se ha creado el shadow DOM y le agregamos un nodo. Ese nodo será una copia profunda de la plantilla, incluidos todos los elementos y el texto de la plantilla. Con la plantilla adjunta al DOM de la sombra del elemento personalizado, el <slot> y slot el atributo se hace cargo de hacer coincidir el contenido con el lugar al que debe ir.

Mira esto. Ahora podemos colocar dos instancias del mismo componente, renderizando contenido diferente simplemente cambiando un elemento.

Dar estilo al componente

Es posible que haya notado el estilo en esa demostración. Como era de esperar, tenemos absolutamente la capacidad de diseñar nuestro componente con CSS. De hecho, podemos incluir un <style> elemento justo en el <template>.

<template id="warningtemplate">
  <style>
    p {
      background-color: pink;
      padding: 0.5em;
      border: 1px solid red;
    }
  </style>

    <p>The <slot name="whats-coming">Zombies</slot> are coming!</p>
</template>

De esta manera, los estilos se limitan directamente al componente y nada se filtra a otros elementos en la misma página, gracias al shadow DOM.

Ahora, en mi cabeza, asumí que un elemento personalizado estaba tomando una copia de la plantilla, insertando el contenido que agregó y luego inyectándolo en la página usando el DOM oculto. Si bien eso es lo que parece en la interfaz, no es así como funciona realmente en el DOM. El contenido de un elemento personalizado permanece donde está y el DOM de la sombra se coloca encima como una superposición.

Captura de pantalla de la fuente HTML del componente de advertencia de zombis.  El elemento personalizado se expande en el dique de sombra, incluido el bloque de estilo, el elemento personalizado y la plantilla.

Y dado que el contenido está técnicamente fuera de la plantilla, cualquier selector o clase descendiente que usemos en la plantilla <style> elemento no tendrá ningún efecto en el contenido ranurado. Esto no permite la encapsulación completa de la manera que esperaba o esperaba. Pero dado que un elemento personalizado es un elemento, podemos usarlo como selector de elementos en cualquier archivo CSS antiguo, incluida la hoja de estilo principal utilizada en una página. Y aunque el material insertado no está técnicamente en la plantilla, está en el elemento personalizado y funcionarán los selectores descendientes del CSS.

apocalyptic-warning span {
  color: blue;
}

¡Pero cuidado! Los estilos del archivo CSS principal no pueden acceder a los elementos del <template> o sombra DOM.

Pongamos todo esto junto

Veamos un ejemplo, digamos un perfil para un servicio de citas zombi, como uno que puedas necesitar después del apocalipsis. Para diseñar tanto el contenido predeterminado como cualquier contenido insertado, necesitamos un <style> elemento en el <template> y estilo en un archivo CSS.

El código JavaScript es exactamente el mismo excepto que ahora estamos trabajando con un nombre de componente diferente, <zombie-profile>.

customElements.define("zombie-profile",
  class extends HTMLElement {
    constructor() {
      super();
      let profile = document.getElementById("zprofiletemplate");
      let myprofile = profile.content;
      const shadowRoot = this.attachShadow({mode: "open"}).appendChild(myprofile.cloneNode(true));
    }
  }
);

Aquí está la plantilla HTML, incluido el CSS encapsulado:

<template id="zprofiletemplate">
  <style>
    img {
      width: 100%;
      max-width: 300px;
      height: auto;
      margin: 0 1em 0 0;
    }
    h2 {
      font-size: 3em;
      margin: 0 0 0.25em 0;
      line-height: 0.8;
    }
    h3 {
      margin: 0.5em 0 0 0;
      font-weight: normal;
    }
    .age, .infection-date {
      display: block;
    }
    span {
      line-height: 1.4;
    }
    .label {
      color: #555;
    }
    li, ul {
      display: inline;
      padding: 0;
    }
    li::after {
      content: ', ';
    }
    li:last-child::after {
      content: '';
    }
    li:last-child::before {
      content: ' and ';
    }
  </style>

  <div class="profilepic">
    <slot name="profile-image"><img src="https://assets.codepen.io/1804713/default.png" alt=""></slot>
  </div>

  <div class="info">
    <h2><slot name="zombie-name" part="zname">Zombie Bob</slot></h2>

    <span class="age"><span class="label">Age:</span> <slot name="z-age">37</slot></span>
    <span class="infection-date"><span class="label">Infection Date:</span> <slot name="idate">September 12, 2025</slot></span>

    <div class="interests">
      <span class="label">Interests: </span>
      <slot name="z-interests">
        <ul>
          <li>Long Walks on Beach</li>
          <li>brains</li>
          <li>defeating humanity</li>
        </ul>
      </slot>
    </div>

    <span class="z-statement"><span class="label">Apocalyptic Statement: </span> <slot name="statement">Moooooooan!</slot></span>

  </div>
</template>

Aquí está el CSS para nuestro <zombie-profile> elemento y sus descendientes de nuestro archivo CSS principal. Observe la duplicación allí para asegurarse de que tanto los elementos reemplazados como los elementos de la plantilla tengan el mismo estilo.

zombie-profile {
  width: calc(50% - 1em);
  border: 1px solid red;
  padding: 1em;
  margin-bottom: 2em;
  display: grid;
  grid-template-columns: 2fr 4fr;
  column-gap: 20px;
}
zombie-profile img {
  width: 100%;
  max-width: 300px;
  height: auto;
  margin: 0 1em 0 0;
}
zombie-profile li, zombie-profile ul {
  display: inline;
  padding: 0;
}
zombie-profile li::after {
  content: ', ';
}
zombie-profile li:last-child::after {
  content: '';
}
zombie-profile li:last-child::before {
  content: ' and ';
}

¡Todos juntos ahora!

Si bien todavía hay algunos errores y otros matices, espero que se sienta más capacitado para trabajar con los componentes web ahora que hace unos minutos. Sumerge los dedos de los pies como lo hemos hecho aquí. Tal vez espolvoree un componente personalizado en su trabajo aquí y allá para tener una idea y dónde tiene sentido.

Eso es realmente. Ahora, ¿a qué le tienes más miedo, a los componentes web o al apocalipsis zombi? Podría haber dicho componentes web en un pasado no muy lejano, pero ahora estoy orgulloso de decir que los zombis son lo único que me preocupa (bueno, eso y si mi viático cubrirá los refrigerios…)

(Visited 7 times, 1 visits today)