React.JS | Cómo crear Web Components

Aunque el concepto de React.JS y Web Components es muy parecido en el fondo, crear componentes reutilizables, resuelven problemas diferentes porque se aplican a contextos muy distinto ya que, Web Components se basa en crear mini-componentes que puedan ser incrustados en cualquier desarrollo web y en React.JS se crean componentes que pueden ser reutilizados dentro de cualquier desarrollo con React. Pero ¿es posible mezclar ambos mundos? La respuesta es, y además de una forma muy sencilla.

Web Componentes con React.JS

Crear web components con React es algo relativamente sencillo siguiendo los mismos principios que si lo fuéramos a crear sin tener React de por medio. Tan sólo es necesario extender la clase HTMLElement y hacer uso del método connectedCallback, donde escribiremos el código para generar el elemento «host« del web component y todo el contenido, incluyendo los slots (placeholders) que sean necesarios. Los estilos, por lo que he podido investigar, hay que incluirlos dentro del propio web component, ya sea mediante «inline styles», importando una hoja de estilos y después añadiéndola al host o, usando custom properties. De esta última parte aún me queda mucha lectura que hacer ya que las opciones que he encontrado hasta el momento, no me permiten mantener estilos comunes entre mis componentes React y mis web components, lo cual no es lo mantenible que me gustaría. En Twitter abrí un hilo muy interesando con gente que sabe bastante del tema como @manz, @jorgecasar y @jnroji entre otros.

Finalmente se expone el componente definiendo la etiqueta a usar enlazada al componente mediante customElements.define (Web_Component_Tag, React_Component)

Ejemplo:

class XSearch extends HTMLElement {
  connectedCallback() {
    const mountPoint = document.createElement('span');
    this.attachShadow({ mode: 'open' }).appendChild(mountPoint);

    const name = this.getAttribute('name');
    const url = 'https://www.google.com/search?q=' + encodeURIComponent(name);
    ReactDOM.render(<a href={url}>{name}</a>, mountPoint);
  }
}
customElements.define('x-search', XSearch);

Vídeo

Demo

Como siempre, no debemos quedarnos con la teoría y tenemos que probar la tecnología para saber qué podemos hacer con ella y cómo podemos exprimirla, así que pongámonos manos a la obra.

Crear los web components

Para la demo he eliminado el código de index.tsx porque sólo quiero exponer los web components. He creado un web component basado en el de la documentación con alguna cosita más y otro un poco más personalizado con el que quería hacer pruebas y que se renderiza como un botón.

class XSearch extends HTMLElement {
  connectedCallback() {
    const host = document.createElement('span');
    const styles = {
        'display': 'inline-block',
        'padding': "20px",
        'margin': "20px",
        'text-align': "center",
        'line-height': '32px',
        'border': '1px solid cyan',
        'border-radius': '20px',
        '&:hover': {
          'background': 'navy',
          'color': '#fff'
        }
      };
    this.attachShadow({ mode: 'open' }).appendChild(host);

    const name = this.getAttribute('name') as string;
    const url = 'https://www.google.com/search?q=' + encodeURIComponent(name);
    ReactDOM.render(
      <a href={url} style={styles}>
        <slot></slot>{name}
      </a>, host);
  }
}
customElements.define('x-search', XSearch);

class XButton extends HTMLElement {
  styles = {
    'padding': "20px",
    'margin': "20px",
    'text-align': "center",
    'line-height': '32px',
    'border': '1px solid cyan',
    'border-radius': '20px'
  };

  connectedCallback() {
    const host = document.createElement('span');
    this.attachShadow({ mode: 'open' }).appendChild(host);

    const text = this.getAttribute('text') as string;
    ReactDOM.render(
      <button style={this.styles}>
        {text} 
        <div>
          <slot name='slot-icon'></slot>
          <slot></slot>
        </div>
      </button>
      , host);
  }
}
customElements.define('x-button', XButton);

Web component de enlace

El primer web component, como se puede observar, se insertará como un «span» que contiene un enlace a Google con el valor del atributo «text» con el que se instancie desde la web que lo integre. Además, le he añadido un slot para renderizar el texto que se ponga entre las etiquetas del web component que, en este caso es «x-search» tal y como se expone desde el método customElements.define (Web_Component_Tag, React_Component)

Web component de botón

El segundo web component, se renderiza dentro de un «span» y dentro de él, un «button» con el «text» que le hayamos pasado como atributo, además de dos slots, uno de ellos con nombre, para poder mostrar un icono y otro texto. Finalmente, se expone el web component con la etiqueta «x-button» para poder ser usado desde aplicaciones web.

Instanciar el web component

Una vez creados los web components, tan sólo será necesario hacer uso del javascript generado para poder instanciarlos. Aprovechando el index.html que viene con la plantilla de React que ya tiene el fichero de javascript inyectado, podemos probar si funcionan borrando el «div» root y poniendo las etiquetas de nuestros web components.

  <body>
    <noscript>You need to enable JavaScript to run this app.</noscript>
    <div>
      <x-search name="web component">Buscar en Google: </x-search>
    </div>
    <div>
      <x-button text="Botón con web component">
        <img src="https://www.pinclipart.com/picdir/big/66-666008_wolverine-clip-art.png" 
            slot="slot-icon"
            style="height: 32px">
        <span>Text in unnamed slot</span>
      </x-button>
    </div>
  </body>

Código en GitHub

Podéis ver el código de esta demo en mi repositorio de GitHub

Conclusión

Como se puede observar, crear los web components con React es bastante sencillo (omitiendo el styling) y podemos usar los slots para tener «Light DOM», es decir, el código HTML que desee mostrar el sistema que usa el web component junto con el «Shadow DOM», aquel código HTML que es opaco al sistema que integra el web component. Esto nos permitirá crear micro aplicaciones de FrontEnd que puedan ser integrables en otros sistemas como por ejemplo buscadores, sin necesidad de usar iframes y de una forma más segura, flexible y configurable.

Espero que este artículo os sirva para empezar a usar web components.

Enjoy coding!