SVELTE, El framework nuevo del barrio

¿Que es Svelte?

Es un nuevo framework de Javascript que intenta hacerse hueco entre los 3 grandes frameworks mas usados y extendidos en la actualidad, React, Vue Angular.

A continuación vamos a listar algunas de las ventajas que ofrece Svelte:

  • Hace el trabajo en tiempo de compilación consiguiendo que sea mas eficiente.
  • No utiliza Virtual DOM, actualizando directamente el DOM.
  • Es sencillo de aprender y es muy ligero.
  • Es reactivo como los otros frameworks
  • Tiene también su propio gestor de estados.

Ahora que sabemos que es y las ventajas que nos ofrece vamos a comenzar a utilizarlo.

Iniciando un proyecto

Según indica la documentación oficial del framework usaremos degit para obtener la copia del repositorio ya que es mucho mas rápido que clonar el repositorio con git, y esto es debido a que no tienes que descargar todo el historial de git.

Para usar degit usaremos el siguiente comando:

npm install -g degit

Cuando todo este instalado, bajaremos el framework Svelte:

degit sveltejs/template myApp
cd myApp
npm i
npm run dev

Con los comandos anteriores ya tendríamos el proyecto descargado, la instalación de las dependencias y su ejecución en nuestro ordenador.

Comencemos con el código

Vamos a empezar por lo mas simple que se puede hacer en Svelte (y cualquier lenguaje, librería, framework, etc..), y es definiendo que son las variables y como se usan.

Podemos definir una variable como un elemento que se usa para almacenar un dato, para posteriormente acceder a el.

Svelte que usa como lenguaje base Javascript, la definición y uso de las variables es la misma.

Cuando necesitemos usar valores que puedan cambiar su contenido usaremos la palabra reservada let, y cuando necesitemos definir una constante usaremos la palabra reservada const.

Debemos recordar que const solo funciona para los tipos de datos primitivos, para los objetos no funcionaría salvo que cambie la referencia asociada al objeto.

let edad = 10;
const provincia = 'Madrid';
const obj = { provincia: 'Madrid' };

console.log(edad); // 10
console.log(provincia); // Madrid
console.log(obj); // { provincia: 'Madrid' }

edad = 20;
console.log(edad); // 20
provincia = 'Guadalajara'; // Error en la consola
obj.provincia = 'Guadalajara';
console.log(obj); // { provincia: 'Guadalajara' }

Ahora que sabemos como definir variables y constantes, vamos hacer un pequeño ejemplo.

<script>
   let num1 = 10;
   let num2 = 20;
</script>
<main>
   <h1>La suma de { num1 } y { num2 } es igual a { num1 + num2 }</h1>
</main>
<style>
   main {
      text-align: center;
      padding: 1em;
      max-width: 240px;
      margin: 0 auto;
   }
   h1 {
      color: #ff3e00;
      text-transform: uppercase;
      font-size: 4em;
      font-weight: 100;
   }
   @media (min-width: 640px) {
      main {
         max-width: none;
      }
   }
</style>

Como hemos podido observar el enlace de datos (data binding) se consigue usando los caracteres ‘{ }’.

Si has trabajado con otros framewoks de Javascript veras ciertas similitudes, de hecho si añadiésemos por ejemplo un elemento html podríamos cambiar su valor, usando la palabra reservada bind:.

<script>
    let num1 = 10;
    let num2 = 20;
</script>

<main>
    <h1>La suma de { num1 } y { num2 } es igual a { num1 + num2 }</h1>
    <div>
        Número 1<input type="range" min="0" max="100" bind:value={num1} />
    </div>
    <div>
        Número 2<input type="range" min="0" max="100" bind:value={num2} />
    </div>
</main>

Manejo de eventos en Svelte

Svelte ofrece un sistema para el manejo de eventos muy simple, añadiendo la palabra reservada on: seguido del evento.

Vamos a realizar unos ejemplos para comprobar su uso.

<script>
    let num1 = 10;
    let num2 = 20;
    let cont = 0;
    let aviso = '';
    const contador = () => {
        cont ++;
    };
    const over = () => {
        aviso = 'Dentro de la caja';
    };
    const out = () => {
        aviso = 'Fuera de la caja';
    };
</script>

<main>
    <div class="card">
        <h1>La suma de { num1 } y { num2 } es igual a { num1 + num2 }</h1>
        <div>
            Número 1<input type="range" min="0" max="100" bind:value={num1} />
        </div>
        <div>
            Número 2<input type="range" min="0" max="100" bind:value={num2} />
        </div>
    </div>
    <div class="card">
        <p>
            Contador { cont }
        </p>
        <button on:click={contador}>Suma</button>
        <p>
            El cursor esta... { aviso }
        </p>
        <div class="box" on:mouseover={over} on:mouseout={out}></div>
    </div>
</main>

<style>
    main {
        text-align: center;
        padding: 1em;
        max-width: 240px;
        margin: 0 auto;
    }

    h1 {
        color: #ff3e00;
        text-transform: uppercase;
        font-size: 4em;
        font-weight: 100;
    }

    @media (min-width: 640px) {
        main {
            max-width: none;
        }
    }

    .card {
                box-shadow: 0 0 10px 3px rgba(0,0,0,0.1);
        padding: 10px;
        margin: 15px;
    }

    .box {
                width: 50%;
                height: 100px;
                margin: 0 auto;
        background: #eeeeee;
        cursor: pointer;
    }
</style>

«Propiedades computadas» en Svelte

Si miramos la documentación de Svelte no veremos por ningún sitio propiedad computada, pero en uno de sus apartados nos indica declaración reactiva.

Viendo el funcionamiento de una declaración reactiva o mejor dicho variable reactiva, podemos observar que se comporta como una propiedad computada, de hecho se ejecuta antes de que el componente se actualice y si su contenido cambia de valor entonces esta se ejecutara.

Para declarar nuestra variable reactiva usaremos el carácter ‘$:’ seguido del nombre de nuestra variable, y a continuación le asignaremos el código que necesitemos ejecutar.

<script>
    let num1 = 10;
    let num2 = 20;
    let cont = 0;
    let aviso = '';
    let name = '';
    let age = 0;
    const contador = () => {
        cont ++;
    };
    const over = () => {
        aviso = 'Dentro de la caja';
    };
    const out = () => {
        aviso = 'Fuera de la caja';
    };
    $: saludo = `Tu nombre es ${name} y tienes ${age || 0} años`;
    $: acceso = age < 18 ? 'No autorizado' : 'Adelante';
    $: {
        if (age === 0) {
            saludo = `Bienvenido!!!, ha nacido ${name} y tiene ${age || 0} años`;
        }
    }
</script>

<main>
    <div class="card">
        <h1>La suma de { num1 } y { num2 } es igual a { num1 + num2 }</h1>
        <div>
            Número 1<input type="range" min="0" max="100" bind:value={num1} />
        </div>
        <div>
            Número 2<input type="range" min="0" max="100" bind:value={num2} />
        </div>
    </div>
    <div class="card">
        <p>
            Contador { cont }
        </p>
        <button on:click={contador}>Suma</button>
        <p>
            El cursor esta... { aviso }
        </p>
        <div class="box" on:mouseover={over} on:mouseout={out}></div>
    </div>
    <div class="card">
        <p>{ saludo } --- { acceso }</p>
        Nombre: <input type="text" bind:value={name} placeholder="Indica un nombre" />
        Edad: <input type="number" bind:value={age} placeholder="Indica una edad" min="0" />
    </div>
</main>

Ampliando nuestro HTML mediante bloques

Como ocurre con en frameworks(Vuejs, Angular, etc..) y lenguajes de programación (PHP, Java, etc..) , estos nos ofrecen la posibilidad de añadir código del propio lenguaje y framework dentro de nuestro HTML, obteniendo un HMTL bastante dinámico.

Pues en Svelte ocurre lo mismo, tiene ciertas sentencias que nos permite construir un HTML mas dinámico.

A continuación algunos ejemplos:

<script>
    let num1 = 10;
    let num2 = 20;
    let cont = 0;
    let aviso = '';
    let name = '';
    let age = 0;
    let aleatorio = 0;
    const contador = () => {
        cont ++;
    };
    const over = () => {
        aviso = 'Dentro de la caja';
    };
    const out = () => {
        aviso = 'Fuera de la caja';
    };
    const rand = () => {
        aleatorio = Math.floor((Math.random() * 100) + 1);
    };
    $: saludo = `Tu nombre es ${name} y tienes ${age || 0} años`;
    $: acceso = age < 18 ? 'No autorizado' : 'Adelante';
    $: {
        if (age === 0) {
            saludo = `Bienvenido!!!, ha nacido ${name} y tiene ${age || 0} años`;
        }
    }
</script>

<main>
    <div class="card">
        <h1>La suma de { num1 } y { num2 } es igual a { num1 + num2 }</h1>
        <div>
            Número 1<input type="range" min="0" max="100" bind:value={num1} />
        </div>
        <div>
            Número 2<input type="range" min="0" max="100" bind:value={num2} />
        </div>
    </div>
    <div class="card">
        <p>
            Contador { cont }
        </p>
        <button on:click={contador}>Suma</button>
        <p>
            El cursor esta... { aviso }
        </p>
        <div class="box" on:mouseover={over} on:mouseout={out}></div>
    </div>
    <div class="card">
        <p>{ saludo } --- { acceso }</p>
        Nombre: <input type="text" bind:value={name} placeholder="Indica un nombre" />
        Edad: <input type="number" bind:value={age} placeholder="Indica una edad" min="0" />
    </div>
    <div class="card">
        { #if aleatorio <= 50 }
            <h1>[{ aleatorio }] Menor que o igual 50</h1>
        { :else }
            <h1>[{ aleatorio }] Mayor que 50</h1>
        { /if }
        <button on:click={rand}>Aleatorio</button>
    </div>
</main>

En el ejemplo anterior hemos visto el uso de un bloque llamado if dentro del HTML, que se comporta como un if de Javascript o cualquier otro lenguaje de programación.

El siguiente ejemplo es mas interesante, ya que vamos a iterar gracias al bloque each dentro de nuestro HTML.

<script>
    let num1 = 10;
    let num2 = 20;
    let cont = 0;
    let aviso = '';
    let name = '';
    let age = 0;
    let aleatorio = 0;
    const provincias = ['Alava','Albacete','Alicante','Almería','Asturias','Avila','Badajoz','Barcelona','Burgos','Cáceres',
                                            'Cádiz','Cantabria','Castellón','Ciudad Real','Córdoba','La Coruña','Cuenca','Gerona','Granada','Guadalajara',
                                            'Guipúzcoa','Huelva','Huesca','Islas Baleares','Jaén','León','Lérida','Lugo','Madrid','Málaga','Murcia','Navarra',
                                            'Orense','Palencia','Las Palmas','Pontevedra','La Rioja','Salamanca','Segovia','Sevilla','Soria','Tarragona',
                                            'Santa Cruz de Tenerife','Teruel','Toledo','Valencia','Valladolid','Vizcaya','Zamora','Zaragoza'];
    const contador = () => {
        cont ++;
    };
    const over = () => {
        aviso = 'Dentro de la caja';
    };
    const out = () => {
        aviso = 'Fuera de la caja';
    };
    const rand = () => {
        aleatorio = Math.floor((Math.random() * 100) + 1);
    };
    $: saludo = `Tu nombre es ${name} y tienes ${age || 0} años`;
    $: acceso = age < 18 ? 'No autorizado' : 'Adelante';
    $: {
        if (age === 0) {
            saludo = `Bienvenido!!!, ha nacido ${name} y tiene ${age || 0} años`;
        }
    }
</script>

<main>
    <div class="card">
        <h1>La suma de { num1 } y { num2 } es igual a { num1 + num2 }</h1>
        <div>
            Número 1<input type="range" min="0" max="100" bind:value={num1} />
        </div>
        <div>
            Número 2<input type="range" min="0" max="100" bind:value={num2} />
        </div>
    </div>
    <div class="card">
        <p>
            Contador { cont }
        </p>
        <button on:click={contador}>Suma</button>
        <p>
            El cursor esta... { aviso }
        </p>
        <div class="box" on:mouseover={over} on:mouseout={out}></div>
    </div>
    <div class="card">
        <p>{ saludo } --- { acceso }</p>
        Nombre: <input type="text" bind:value={name} placeholder="Indica un nombre" />
        Edad: <input type="number" bind:value={age} placeholder="Indica una edad" min="0" />
    </div>
    <div class="card">
        { #if aleatorio <= 50 }
            <h1>[{ aleatorio }] Menor que o igual 50</h1>
        { :else }
            <h1>[{ aleatorio }] Mayor que 50</h1>
        { /if }
        <button on:click={rand}>Aleatorio</button>
    </div>
    <div class="card">
    Provincias
        <select name="provincia" id="provincia" placeholder="Selecciona tu provincia">
            {#each provincias as item, index}
                <option value={index}>{ item }</option>
            {/each}
        </select>
    </div>
</main>

Con un par de lineas de código y el uso del bloque each hemos conseguido hacer un poco de magia en nuestro HTML.

Ahora veremos un ejemplo un poquito mas completo usando variables reactivas, else dentro del bloque each y la manipulación de un Array.

<script>
    let num1 = 10;
    let num2 = 20;
    let cont = 0;
    let aviso = '';
    let name = '';
    let age = 0;
    let aleatorio = 0;
    const provincias = ['Alava','Albacete','Alicante','Almería','Asturias','Avila','Badajoz','Barcelona','Burgos','Cáceres',
                                            'Cádiz','Cantabria','Castellón','Ciudad Real','Córdoba','La Coruña','Cuenca','Gerona','Granada','Guadalajara',
                                            'Guipúzcoa','Huelva','Huesca','Islas Baleares','Jaén','León','Lérida','Lugo','Madrid','Málaga','Murcia','Navarra',
                                            'Orense','Palencia','Las Palmas','Pontevedra','La Rioja','Salamanca','Segovia','Sevilla','Soria','Tarragona',
                                            'Santa Cruz de Tenerife','Teruel','Toledo','Valencia','Valladolid','Vizcaya','Zamora','Zaragoza'];
    let tareas = [
        {
            id: 1,
            tarea: 'Xxxxxx',
            descripcion: 'zxc zc x zxclñkñkl klñlsdfñlskdfk sdf'
        },
        {
            id: 2,
            tarea: 'Yyyyyy',
            descripcion: 'zxc zc x zxclñkñkl klñlsdfñlskdfk sdf'
        },
        {
            id: 3,
            tarea: 'Aaaaaa',
            descripcion: 'zxc zc x zxclñkñkl klñlsdfñlskdfk sdf'
        },
        {
            id: 4,
            tarea: 'Bbbbbb',
            descripcion: 'zxc zc x zxclñkñkl klñlsdfñlskdfk sdf'
        },
        {
            id: 5,
            tarea: 'Cccccc',
            descripcion: 'zxc zc x zxclñkñkl klñlsdfñlskdfk sdf'
        },
    ];
    const removeTask = () => {
        /* 
            Clonamos cada vez que pulsamos para que cambie la referencia y la "variable reactive (propiedad computada)" se entere que ha modificado, 
            y pueda volver a "renderizar"	el contenido en el listado
        */
        tareas = [...tareas];
        tareas.shift();
    };
    const contador = () => {
        cont ++;
    };
    const over = () => {
        aviso = 'Dentro de la caja';
    };
    const out = () => {
        aviso = 'Fuera de la caja';
    };
    const rand = () => {
        aleatorio = Math.floor((Math.random() * 100) + 1);
    };
    $: saludo = `Tu nombre es ${name} y tienes ${age || 0} años`;
    $: acceso = age < 18 ? 'No autorizado' : 'Adelante';
    $: {
        if (age === 0) {
            saludo = `Bienvenido!!!, ha nacido ${name} y tiene ${age || 0} años`;
        }
    }
    $: taskList = tareas;
</script>

<main>
    <div class="card">
        <h1>La suma de { num1 } y { num2 } es igual a { num1 + num2 }</h1>
        <div>
            Número 1<input type="range" min="0" max="100" bind:value={num1} />
        </div>
        <div>
            Número 2<input type="range" min="0" max="100" bind:value={num2} />
        </div>
    </div>
    <div class="card">
        <p>
            Contador { cont }
        </p>
        <button on:click={contador}>Suma</button>
        <p>
            El cursor esta... { aviso }
        </p>
        <div class="box" on:mouseover={over} on:mouseout={out}></div>
    </div>
    <div class="card">
        <p>{ saludo } --- { acceso }</p>
        Nombre: <input type="text" bind:value={name} placeholder="Indica un nombre" />
        Edad: <input type="number" bind:value={age} placeholder="Indica una edad" min="0" />
    </div>
    <div class="card">
        { #if aleatorio <= 50 }
            <h1>[{ aleatorio }] Menor que o igual 50</h1>
        { :else }
            <h1>[{ aleatorio }] Mayor que 50</h1>
        { /if }
        <button on:click={rand}>Aleatorio</button>
    </div>
    <div class="card">
        Provincias
        <select name="provincia" id="provincia" placeholder="Selecciona tu provincia">
            {#each provincias as item, index}
                <option value={index}>{ item }</option>
            {/each}
        </select>
        <div class="task-container">
            Listado de tareas<button on:click={removeTask} title="Eliminar una tarea">-</button>
            {#each taskList as item}
                <div class="task">
                    <ul>
                        <li><b>ID:</b> { item.id }</li>
                        <li><b>Tarea:</b> { item.tarea }</li>
                        <li><b>Descripcion:</b> {item.descripcion }</li>
                    </ul>
                </div>
            {:else}
                <h1>No tienes ninguna tarea pendiente</h1>
            {/each}
        </div>
    </div>
</main>

Por último y para cerrar esta primera parte de Svelte, vamos a ver el uso del bloque await.

Este bloque nos permite dentro del propio HTML realizar operaciones asíncronas para tener HTML dinámico en base a una promesa y sus diferentes estados (pendiente, terminada o rechazada).

A continuación un pequeño ejemplo usando un API Rest de Star Wars:

<script>
    let num1 = 10;
    let num2 = 20;
    let cont = 0;
    let aviso = '';
    let name = '';
    let age = 0;
    let aleatorio = 0;
    const provincias = ['Alava','Albacete','Alicante','Almería','Asturias','Avila','Badajoz','Barcelona','Burgos','Cáceres',
                                            'Cádiz','Cantabria','Castellón','Ciudad Real','Córdoba','La Coruña','Cuenca','Gerona','Granada','Guadalajara',
                                            'Guipúzcoa','Huelva','Huesca','Islas Baleares','Jaén','León','Lérida','Lugo','Madrid','Málaga','Murcia','Navarra',
                                            'Orense','Palencia','Las Palmas','Pontevedra','La Rioja','Salamanca','Segovia','Sevilla','Soria','Tarragona',
                                            'Santa Cruz de Tenerife','Teruel','Toledo','Valencia','Valladolid','Vizcaya','Zamora','Zaragoza'];
    let tareas = [
        {
            id: 1,
            tarea: 'Xxxxxx',
            descripcion: 'zxc zc x zxclñkñkl klñlsdfñlskdfk sdf'
        },
        {
            id: 2,
            tarea: 'Yyyyyy',
            descripcion: 'zxc zc x zxclñkñkl klñlsdfñlskdfk sdf'
        },
        {
            id: 3,
            tarea: 'Aaaaaa',
            descripcion: 'zxc zc x zxclñkñkl klñlsdfñlskdfk sdf'
        },
        {
            id: 4,
            tarea: 'Bbbbbb',
            descripcion: 'zxc zc x zxclñkñkl klñlsdfñlskdfk sdf'
        },
        {
            id: 5,
            tarea: 'Cccccc',
            descripcion: 'zxc zc x zxclñkñkl klñlsdfñlskdfk sdf'
        },
    ];
    const removeTask = () => {
        /* 
            Clonamos cada vez que pulsamos para que cambie la referencia y la "variable reactive (propiedad computada)" se entere que ha modificado, 
            y pueda volver a "renderizar"	el contenido en el listado
        */
        tareas = [...tareas];
        tareas.shift();
    };
    const contador = () => {
        cont ++;
    };
    const over = () => {
        aviso = 'Dentro de la caja';
    };
    const out = () => {
        aviso = 'Fuera de la caja';
    };
    const rand = () => {
        aleatorio = Math.floor((Math.random() * 100) + 1);
    };
    $: saludo = `Tu nombre es ${name} y tienes ${age || 0} años`;
    $: acceso = age < 18 ? 'No autorizado' : 'Adelante';
    $: {
        if (age === 0) {
            saludo = `Bienvenido!!!, ha nacido ${name} y tiene ${age || 0} años`;
        }
    }
    $: taskList = tareas;
    const starWars = () => {
        return new Promise(async(resolve, reject) => {
            const url = 'https://swapi.dev/api/people/';
            const result = await fetch(url);
            const data = await result.json();
            setTimeout(() => {
                if (result.ok) {
                    resolve(data.results);
                } else {
                    reject('conectado con la API');
                }
            }, 3 * 1000); // Simulando una pequeña pausa
        })
    }
</script>

<main>
    <div class="card">
        <h1>La suma de { num1 } y { num2 } es igual a { num1 + num2 }</h1>
        <div>
            Número 1<input type="range" min="0" max="100" bind:value={num1} />
        </div>
        <div>
            Número 2<input type="range" min="0" max="100" bind:value={num2} />
        </div>
    </div>
    <div class="card">
        <p>
            Contador { cont }
        </p>
        <button on:click={contador}>Suma</button>
        <p>
            El cursor esta... { aviso }
        </p>
        <div class="box" on:mouseover={over} on:mouseout={out}></div>
    </div>
    <div class="card">
        <p>{ saludo } --- { acceso }</p>
        Nombre: <input type="text" bind:value={name} placeholder="Indica un nombre" />
        Edad: <input type="number" bind:value={age} placeholder="Indica una edad" min="0" />
    </div>
    <div class="card">
        { #if aleatorio <= 50 }
            <h1>[{ aleatorio }] Menor que o igual 50</h1>
        { :else }
            <h1>[{ aleatorio }] Mayor que 50</h1>
        { /if }
        <button on:click={rand}>Aleatorio</button>
    </div>
    <div class="card">
        Provincias
        <select name="provincia" id="provincia" placeholder="Selecciona tu provincia">
            {#each provincias as item, index}
                <option value={index}>{ item }</option>
            {/each}
        </select>
        <div class="task-container">
            Listado de tareas<button on:click={removeTask} title="Eliminar una tarea">-</button>
            {#each taskList as item}
                <div class="task">
                    <ul>
                        <li><b>ID:</b> { item.id }</li>
                        <li><b>Tarea:</b> { item.tarea }</li>
                        <li><b>Descripcion:</b> {item.descripcion }</li>
                    </ul>
                </div>
            {:else}
                <h1>No tienes ninguna tarea pendiente</h1>
            {/each}
        </div>
    </div>
    <div class="card">
        <h1>Conectando a la API de Star Wars</h1>
        <div>
            { #await starWars() }
         		<div class="lds-ripple"><div></div><div></div></div>
         	{ :then result }
         		{#each result as item}
                <div class="task">
                    <ul>
                        <li><b>Cumpleaños:</b> { item.birth_year }</li>
                        <li><b>Color de ojos:</b> { item.eye_color }</li>
                        <li><b>Total peliculas:</b> { item.films.length }</li>
                        <li><b>Sexo:</b> { item.gender }</li>
                        <li><b>Color de pelo:</b> { item.hair_color }</li>
                        <li><b>Altura:</b> { item.height } cm</li>
                        <li><b>Peso:</b> { item.mass } kg</li>
                        <li><b>Nombre:</b> { item.name }</li>
                        <li><b>Color de piel:</b> { item.skin_color }</li>
                    </ul>
                </div>
            {:else}
                <h1>No existe ningún registro mas</h1>
            {/each}
         	{ :catch err }
         		<div>{ err }</div>
         	{ /await }
        </div>
    </div>
</main>

Bien, con esta primera entrada hemos tocado la base de Svelte para hacernos una idea de lo fácil y potente que puede llegar a ser.

No obstante si queréis ampliar y mejorar el conocimiento sobre este nuevo framework podéis acceder a su documentación y al repositorio del proyecto en git.