Reutilizando web components generados por Stencil

0
4558

 

Índice de contenidos

 

1. Introducción

En este tutorial vamos a ver con un ejemplo práctico cómo implementar un componente con Stencil y posteriormente su integración  en una aplicación sin framework y en otra con ReactJS.

Recordemos que Stencil es un compilador (no pretende ser un framework ni una librería web) que nos va a permitir obtener web components a partir de componentes implementados con las APIs que nos ofrece. Estas APIs, Stencil las ha diseñado siguiendo los mejores conceptos de otros frameworks JavaScript (sobre todo de React Fiber la forma de renderizar y de Angular la estructura y la sintaxis con decoradores). Para una introducción a Stencil recomiendo el tutorial Introducción a StencilJS y demo de integración con Angular

El ejemplo que vamos a realizar es muy sencillo pero nos va a servir para ver algunas características de la herramienta y como hace la «magia» de generarnos componentes 100% reutilizables.  

El ejemplo consiste en implementar un componente, le he llamado color-picker, que nos permita seleccionar un color. Sus características son:

  • Permite que se le asigne un valor de un color inicial (sino el propio componente tiene su valor por defecto).
  • Es resetteable. Una vez seleccionado un color podemos restaurar, a través de un botón, su valor inicial.
  • Muestra el código del color seleccionado.

 

Posteriormente integraremos el color-picker, generado como web component gracias a Stencil, en una app sin framework y en otra con React. En las dos apps se utilizará el componente para cambiarle el color a un texto. El resultado final sería este:

 

​2. Entorno

Este tutorial está escrito usando el siguiente entorno:

  • Hardware: Portátil Macbook Pro 15″ (2.5 GHz Intel Core i7, 16GB DDR3)
  • Sistema Operativo: Mac OS Mojave
  • IDE: Visual Studio Code Versión 1.29.1
  • Nodejs 10.12.0
  • React 16.6

 

​3. Implementando el color-picker con Stencil

Lo primero que vamos a hacer es crear el proyecto:

npm init stencil

Nos salen tres opciones de tipos de proyecto. Vamos a seleccionar la opción ‘component’. Como nombre del proyecto pondremos my-components. Después ejecutamos:

cd my-components
npm install

 

La estructura del proyecto que nos ha creado Stencil es la siguiente:

 

Dentro de la carpeta components es donde vamos a crear todos nuestros componentes. Como vemos ya se ha creado uno por defecto. Vamos a eliminarlo y comenzar a crear nuestro color-picker desde cero.

Dentro de components creamos la carpeta color-picker y dentro de ésta creamos los ficheros color-picker.css y color-picker.tsx.

color-picker.css

.color-picker {
  border: 2px solid #afafaf;
  display: inline-flex;
  max-width: 175px;
  align-items: center;
  padding: 0;
  margin: 0;
}

.color-picker input {
  cursor: pointer;
  border-width: 0;
  width: 50px;
  height: 30px;
}

.color-picker span {
  padding: 0 10px 0 10px;
  width: 70px;
  text-transform: uppercase;
  color: #afafaf;
}

.color-picker button {
  border: none;
  border-left: 1px solid #afafaf;
  font-size: 0.9em;
  width: 30px;
  height: 25px;
}

 

color-picker.tsx

import { Component, Prop, State, Event, EventEmitter } from '@stencil/core';

@Component({
  tag: 'color-picker',
  styleUrl: 'color-picker.css'
})
export class ColorPicker {

  @Prop() defaultValue: string = "#ff0000";
  @Prop() resettable: boolean = false;

  @State() value: string;

  @Event() colorChanged: EventEmitter;

  componentWillLoad() {
    this.setValue(this.defaultValue);
  }

  handleChange(event) {
    this.setValue(event.target.value);
  }

  setValue(value) {
    this.value = value;
    this.colorChanged.emit(this.value);
  }

  reset() {
    this.setValue(this.defaultValue);
  }

  renderResetButton() {
    if (this.resettable) {
      return <button onClick={() => this.reset()}>X</button>;
    }
  }

  render() { 
    return (
      <div class="color-picker">
        <input type="color" value={this.value} onChange={(ev) => this.handleChange(ev)} />
        <span>{this.value}</span>
        { this.renderResetButton() }
     </div>
    );
  }
}

 

Básicamente el color-picker es un wrapper de un input nativo html de tipo ‘color’, que además le añade las características comentadas en la introducción.

Las partes del componente son:

  • Decorador @Component, donde especificamos el nombre del componente y su fichero de estilos.
  • Las propiedades que se van a exponer hacia fuera, defaultValue (color por defecto) y resettable (Indica si el componente puede ser reiniciado). Estas propiedades por defecto son inmutables desde dentro del componente. Para hacerlas mutables hay que anotarlas con @Prop({ mutable: true })
  • Decoramos con @State los atributos internos del componente que van a ser modificados en su lógica, como en nuestro caso el atributo value.
  • El event emmitter, es el encargado de, una vez seleccionado el color, se emita un evento de tipo ‘colorChanged’ hacia fuera. Esto va a permitir que clientes del componente, suscritos al evento, sean notificados cada vez que cambie el color.
  • El método render, que devuelve la descripción necesaria para pintar el componente. Como se puede ver esta escrito con la sintaxis JSX. Es un concepto heredado de React que permite escribir la parte visual del componente declarativamente.

 

Para probar lo que hemos hecho vamos a usar el componente en la misma aplicación de Stencil. En el fichero index.html eliminamos el componente <my-component> y añadimos el color-picker:

<color-picker default-value = "#0000FF" resettable></color-picker>

 

La pagina index.html quedaría así:

<!DOCTYPE html>
<html dir="ltr" lang="en">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=5.0">
    <title>Stencil Component Starter</title>
    <script src="/build/mycomponent.js"></script>
  </head>
  <body>
    <color-picker default-value = "#0000FF" resettable></color-picker>
  </body>
</html>

 

Ejecutamos npm start y si todo ha ido bien veremos el color-picker en ejecución!

Generando la distribución

Ahora que ya tenemos el componente listo, vamos a compilarlo y generar el empaquetado final que usarán las aplicaciones clientes.

El proyecto que hemos creado ya viene con el package.json y el stencil.config.ts preparado para generarnos la compilación en la carpeta ‘dist’. Solo vamos a cambiar el stencil.config.ts el namespace por my-components. Ejecutamos:

npm run build

Esto nos genera una carpeta dist con el siguiente contenido:

 

Para reutilizar el componente en otra app lo correcto sería publicar la librería generada en un repositorio npm. Para el ejemplo actual lo que haremos es copiar directamente la carpeta dist y pegarla en las apps clientes que vamos a ver a continuación.

 

​4. Integración en un proyecto sin framework

Como lo generado por Stencil es un web component estándar, éste puede ser usado en cualquier tipo de aplicación, use framework JavaScript o no.

En este caso vamos a ver su integración en una app sin framework.

Vamos a crear una carpeta color-picker-demo, dentro nos creamos un fichero index.html donde vamos a poner la referencia a la librería que nos generó Stencil que incluye al color-picker. Copiamos la carpeta dist en la carpeta color-picker-demo.

El fichero index.html quedaría así:

<!DOCTYPE html>
<html lang="en">
  <head> 
    <script src="./dist/my-components.js"></script>
    <style type="text/css">
      body {
        font-family: "Trebuchet MS", Helvetica, sans-serif;
     }
   
     div {
       position: relative;
       margin: auto;
       width: 50%;
       padding: 40px;
     }

     p {
       font-size: 3.6em;
     }

    </style>
  </head>
  <body>

    <div>
      <color-picker default-value = "#0000FF" resettable></color-picker>
      <p>Stencil is a great <b>Tool</b></p>
    </div>

    <script>
      const colorPicker = document.querySelector('color-picker');

      colorPicker.addEventListener("colorChanged", function(e) {
        document.querySelector("p").style.color = e.detail;
      }); 
    </script>

  </body>
</html>

 

Lo que hemos hecho es usar el color-picker para darle color a un texto que tenemos en un elemento <p>.

Le hemos asignado un valor (#0000FF) inicial y le hemos dicho que lo queremos resetteable. La suscripción al evento ‘colorChanged la hemos hecho desde JavaScript.

5. Integración en un proyecto con ReactJS

Veamos ahora la integración en un proyecto con React.

Creamos el proyecto con create-react-app

npx create-react-app color-picker-demo

Como no tenemos subido la librería de componentes en ningún repositorio npm lo que haremos es copiar la carpeta dist generada, directamente en una carpeta my-components dentro de node-modules.

Para incluir la librería de componentes en el proyecto React editamos el fichero index.js de nuestro proyecto React creado con create-react-app para añadir el import y la llamada a defineCustomElements(window). El fichero quedaría así:

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';

import { defineCustomElements } from 'my-components/dist/loader';

ReactDOM.render(<App />, document.getElementById('root'));

// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: http://bit.ly/CRA-PWA
serviceWorker.unregister();
defineCustomElements(window);

 

Modificamos el fichero App.js para utilizar nuestro color-picker.

App.js

import ReactDOM from 'react-dom';
import Greeting from './Greeting';
import Clock from './Clock';

class App extends Component {
  constructor(props) {
    super(props);
    this.state = {color: ''};
    this.handleColorChanged = this.handleColorChanged.bind(this);
  }

  handleColorChanged(e) {
    this.setState({
      color: e.detail
    });
  }

  componentDidMount() {
    this.refs['colorPicker'].addEventListener('colorChanged', this.handleColorChanged);
  }

  render() {
    return (
      <div className="App">
        <color-picker ref="colorPicker" default-value="#0000ff" resetable />
        <p style={{ color: this.state.color }}>Stencil is a great <b>Tool</b></p>
      </div>
   );
  }
}

export default App;

Solo aclarar que para suscribir el listener  al evento del color picker es necesario hacerlo directamente a través de la referencia react al web component:

this.refs['colorPicker'].addEventListener('colorChanged', this.handleColorChanged);

Añadimos algo de estilo para darle más tamaño al texto:

App.css

.App {
  position: relative;
  margin: auto;
  width: 50%;
  padding: 40px;
}

p {
  font-family: "Trebuchet MS", Helvetica, sans-serif;
  font-size: 3.6em;
}

Ejecutamos el proyecto:

npm start

Y tenemos nuestro color-picker funcionando en una app con React:

6. Integración en un proyecto con Angular

Veamos ahora la integración en un proyecto con Angular.

Creamos el proyecto con @angular/cli

ng new ng-color-picker-demo

Como no tenemos subido la librería de componentes en ningún repositorio npm podemos copiar la carpeta dist generada, directamente en una carpeta my-components dentro de node-modules o también podemos instalar la dependencia de forma local, ejecutando

npm install --save path/my-components

Ahora editamos el fichero angular.json para incluir nuestra librería en la sección de assets y que pueda estar accesible desde el index.html.

...
polyfills": "src/polyfills.ts",
"tsConfig": "src/tsconfig.app.json",
"assets": [
    "src/favicon.ico",
    "src/assets",
    { "glob": "**/*", "input": "./node_modules/my-components", "output": "/my-components/" }
],
"styles": [
     "src/styles.css"
],
...

Editamos el fichero index.html para añadir la referencia a nuestra librería del «head»:


De esta forma podemos utilizar nuestra librería tanto dentro como fuera del ámbito de Angular. Pero si la queremos utilizarla dentro del ámbito de Angular, tenemos que indicarle al framwork que estamos utilizando etiquetas que el no conoce y que, por favor, lo admita y no muestre errores. Para hacer esto dentro del AppModule de la aplicación añadimos el siguiente schema:

import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppComponent } from './app.component';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule
  ],
  providers: [],
  bootstrap: [AppComponent],
  schemas: [CUSTOM_ELEMENTS_SCHEMA]
})
export class AppModule { }

Ahora podemos editar el fichero app.component.html para añadir nuestro color-picker el cuál puede ser tratado como un componente de Angular más donde los inputs se bindean con [] y los eventos se manejan con ().


Siendo «defaultColor» un atributo del componente AppComponent y la función «handleColorChanged» un método de dicho componente que se encargará de gestionar el evento colorChanged. Es imprescindible que la información se pase con $event y en el detail de ese objeto viaja lo que se emite desde el componente de StencilJS.

7. Conclusiones

Con este sencillo ejemplo has podido ver como haciendo uso de Stencil podemos crear componentes rápidamente utilizando una serie de conceptos y técnicas que ya implementan otros frameworks JavaScript y posteriormente generar web components completamente reutilizables en cualquier tipo de aplicación.

 

8. Referencias

 

DEJA UNA RESPUESTA

Por favor ingrese su comentario!

He leído y acepto la política de privacidad

Por favor ingrese su nombre aquí

Información básica acerca de la protección de datos

  • Responsable:
  • Finalidad:
  • Legitimación:
  • Destinatarios:
  • Derechos:
  • Más información: Puedes ampliar información acerca de la protección de datos en el siguiente enlace:política de privacidad