OpenWebinars

Desarrollo Web

Componentes web reutilizables con Rust y Wasm: Guía práctica

El desarrollo web moderno exige velocidad, seguridad y flexibilidad. Rust y WebAssembly han surgido como herramientas poderosas para cumplir con esas demandas, permitiendo a los desarrolladores crear componentes web reutilizables que destacan por su rendimiento. En esta guía práctica aprenderás cómo combinar estas tecnologías en tus proyectos web.

Alfredo Barragán

Alfredo Barragán

Experto Full Stack con PHP y JavaScript

Lectura 5 minutos

Publicado el 10 de diciembre de 2024

Compartir

Introducción

Como bien sabes, el sector del desarrollo web evoluciona constantemente, y los desarrolladores buscan nuevas formas de crear aplicaciones más robustas y eficientes.

En este contexto, Rust y WebAssembly (Wasm) surgen como una alternativa para construir componentes web de alto rendimiento, con la premisa de ser fáciles de reutilizar.

En esta guía práctica, te explicaremos paso a paso cómo crear componentes web reutilizables usando Rust y WebAssembly. Además, analizaremos las ventajas de cada tecnología, así como los beneficios que aportan y las herramientas necesarias para su implementación.

Qué es WebAssembly (Wasm)

WebAssembly, o Wasm, es un formato de código binario optimizado para ejecutarse en navegadores web, con un alto grado de rendimiento, ha sido desarrollado por el W3C, World Wide Web Consortium.

Este formato permite el desarrollo de aplicaciones con lenguajes de programación como Rust o C++. Los programadores no desarrollan directamente en WebAssembly, sino que escriben en el lenguaje de su elección, que luego se compila en bytecode WebAssembly.

Desde hace mucho tiempo en el entorno web solo disponíamos de JavaScript para utilizarlo de forma nativa, pero ahora con Wasm lo complementa proporcionando la velocidad y eficiencia necesarias para tareas de cómputo intensivo.

Mientras que JavaScript sigue realizando la manipulación del DOM y la lógica de la interfaz de usuario. Además, WebAssembly es compatible con los principales navegadores del mercado.

Ventajas de Rust para WebAssembly

Rust se ha convertido en una opción popular para el desarrollo de WebAssembly debido a estos aspectos clave:

  • Rendimiento: ofrece un rendimiento comparable al de C y C++, lo que lo hace ideal para tareas que requieren alta eficiencia.
  • Seguridad de memoria: El sistema de propiedad y préstamo de este lenguaje previene errores comunes como las condiciones de carrera y los accesos a memoria no válida.
  • Interoperabilidad: tiene excelentes herramientas para la interoperabilidad con JavaScript, lo que facilita la integración de componentes Wasm en aplicaciones web existentes.
  • Ecosistema robusto: cuenta con un ecosistema de bibliotecas y herramientas en constante crecimiento, muchas de las cuales están optimizadas para su uso con WebAssembly.
Aprende a desarrollar webs optimizadas
Comienza 15 días gratis en OpenWebinars y accede cursos, talleres y laboratorios prácticos de JavaScript, React, Angular, HTML, CSS y más.
Registrarme ahora

Creación de componentes web reutilizables con Rust y Wasm

Estructura básica de un componente en Rust y Wasm

Para crear un componente web reutilizable con Rust y Wasm, comenzamos definiendo una estructura en Rust que representará nuestro componente. Aquí tienes un ejemplo básico:

use wasm_bindgen::prelude::*;

#[wasm_bindgen]
pub struct MyComponent {
    value: i32,
}

#[wasm_bindgen]
impl MyComponent {
    pub fn new() -> MyComponent {
        MyComponent { value: 0 }
    }

    pub fn increment(&mut self) {
        self.value += 1;
    }

    pub fn get_value(&self) -> i32 {
        self.value
    }
}

En este ejemplo desarrollado con Rust y WebAssembly, cada parte cumple un rol específico para que el componente funcione de forma eficiente y se pueda utilizar en una aplicación web.

A continuación, te explico el cometido de cada anotación:

use wasm_bindgen::prelude::*;

Esta línea importa el módulo wasm_bindgen, que es fundamental para interactuar con WebAssembly desde JavaScript. La macro #[wasm_bindgen] permite que Rust exponga funciones y estructuras a WebAssembly de manera que puedan ser utilizadas desde el código JavaScript del navegador.

#[wasm_bindgen]

Esta anotación se coloca encima de las estructuras y funciones que queremos hacer accesibles desde JavaScript. Sin #[wasm_bindgen], el código Rust no sería directamente accesible en WebAssembly, y por lo tanto, JavaScript no podría utilizar el componente.

pub struct MyComponent

Aquí definimos la estructura MyComponent, que es la parte central de nuestro componente. En Rust, una struct es una forma de agrupar datos; en este caso, estamos creando una estructura que contiene un solo campo llamado value, de tipo i32 (un número entero de 32 bits).

La palabra clave pub indica que esta estructura es pública y accesible desde fuera del módulo en el que se define, permitiendo que sea visible y utilizable desde JavaScript a través de WebAssembly.

Para utilizar este código, tienes que compilarlo a WebAssembly (Wasm) usando wasm-bindgen y luego interactuar con él en un entorno JavaScript.

Para ello tienes que seguir estos pasos:

1. Configura el proyecto en Rust

Debes de tener el compilador de Rust y las herramientas necesarias instaladas. Si no es así puedes obtenerla ejecutando el siguiente comando:

cargo install wasm-pack

2. Configura el arhivo Cargo.toml

En el archivo Cargo.toml, añade las dependencias necesarias para wasm-bindgen:

[dependencies]
wasm-bindgen = "0.2"

3. Compilar el proyecto a WebAssembly

Utiliza wasm-pack para compilar el proyecto a un paquete compatible con JavaScript:

wasm-pack build --target web

Esto generará una carpeta pkg con los archivos necesarios para usar tu módulo en JavaScript, incluidos el archivo .wasm y un archivo .js que actúa como una interfaz entre JavaScript y WebAssembly.

4. Importa y usa el paquete en JavaScript

En tu archivo JavaScript (por ejemplo, index.js), importa y utiliza el módulo Wasm:

// Importa el módulo generado por wasm-pack
import init, { MyComponent } from "./pkg/tu_paquete";

async function run() {
  // Inicializa el módulo de Wasm
  await init();

  // Crea una instancia de `MyComponent`
  const myComponent = new MyComponent();

  // Llama a los métodos
  console.log(myComponent.get_value()); // Muestra 0

  myComponent.increment();
  console.log(myComponent.get_value()); // Muestra 1
}

run();

5. Configura un servidor local para servir el archivo .wasm

Para evitar problemas con CORS, utiliza un servidor local, como serve, para probar tu proyecto:

npx serve .

Integración con HTML y CSS

Para integrar nuestro componente Rust/Wasm con HTML y CSS, utilizamos JavaScript como pasarela de unión.

Aquí te muestro un ejemplo de cómo funciona la integración:

<div id="my-component"></div>

<script type="module">
  import init, { MyComponent } from "./pkg/my_component.js";

  async function run() {
    await init();

    const component = MyComponent.new();
    const element = document.getElementById("my-component");

    element.innerHTML = `
            <button id="increment">Incrementar</button>
            <p>Valor: <span id="value"></span></p>
        `;

    const valueSpan = element.querySelector("#value");
    const incrementButton = element.querySelector("#increment");

    function updateValue() {
      valueSpan.textContent = component.get_value();
    }

    incrementButton.addEventListener("click", () => {
      component.increment();
      updateValue();
    });

    updateValue();
  }

  run();
</script>

Este fragmento de código muestra cómo integrar un componente en Rust y WebAssembly en una página HTML para crear un pequeño contador interactivo. A continuación, analizamos cada sección para comprender como funciona cada una de las secciones.

<div id="my-component"></div>

Este div actuará como el contenedor en el que se mostrará el componente del contador. JavaScript luego llenará este contenedor con un botón y un elemento de texto para mostrar el valor actual del contador.

 <script type="module">
     import init, { MyComponent } from './pkg/my_component.js';

Este código importa el módulo WebAssembly generado a partir del código Rust (en el archivo my_component.js) y la función init, que inicializa el módulo Wasm en JavaScript. También se importa MyComponent, que es el componente definido en Rust.

La función run es una función asincrónica que realiza varios pasos para inicializar y configurar el componente:

await init();

await init() inicializa el módulo WebAssembly. Esto carga el código Wasm y permite utilizar MyComponent como un objeto accesible en JavaScript.

Uso del Shadow DOM

Para encapsular aún más nuestro componente y evitar conflictos de estilos, podemos utilizar el Shadow DOM:

const shadow = element.attachShadow({ mode: "open" });

shadow.innerHTML = `
    <style>
        button {
            background-color: #4CAF50;
            border: none;
            color: white;
            padding: 15px 32px;
            text-align: center;
            text-decoration: none;
            display: inline-block;
            font-size: 16px;
            margin: 4px 2px;
            cursor: pointer;
        }
    </style>
    <button id="increment">Incrementar</button>
    <p>Valor: <span id="value"></span></p>
`;

const valueSpan = shadow.querySelector("#value");
const incrementButton = shadow.querySelector("#increment");

El Shadow DOM es especialmente útil cuando se desarrollan Web Components o componentes reutilizables que podrían ser usados en diferentes proyectos o en entornos con estilos variados.

Al encapsular sus estilos y estructura, cada componente puede mantener una apariencia y comportamiento consistentes, sin interferencias.

El Shadow DOM permite encapsular completamente los estilos y la estructura de un componente dentro de un “DOM de sombra”, separado del DOM principal de la página.

Esto se traduce, en que el contenido y los estilos del componente están aislados y no pueden afectar ni ser afectados por el estilo o estructura del resto de la página.

Herramientas esenciales para trabajar con Rust y WebAssembly

wasm-bindgen

wasm-bindgen es una herramienta que facilita la comunicación entre Rust y JavaScript. Permite generar automáticamente el código JavaScript necesario para interactuar con las funciones y estructuras de Rust compiladas a WebAssembly.

Para usar wasm-bindgen, añade este fragmento de código al archivo Cargo.toml:

[dependencies]
wasm-bindgen = "0.2"

wasm-pack

wasm-pack es una herramienta de línea de comandos que facilita la construcción y empaquetado de proyectos Rust para WebAssembly. Automatiza muchos de los pasos necesarios para compilar Rust a Wasm y generar los archivos JavaScript y TypeScript correspondientes.

Para instalar wasm-pack, ejecuta en una terminal el siguiente comando:

cargo install wasm-pack

web-sys y js-sys

web-sys y js-sys son crates que proporcionan bindings a las APIs web y JavaScript respectivamente. Permiten interactuar con el DOM, manejar eventos y utilizar funcionalidades del navegador directamente desde Rust.

Añade estas dependencias al archivo Cargo.toml:

[dependencies]
web-sys = "0.3"
js-sys = "0.3"

Beneficios de los componentes reutilizables con Rust y WebAssembly

  • Rendimiento mejorado: Los componentes construidos con Rust y WebAssembly ofrecen un rendimiento significativamente mejor en comparación con JavaScript puro, especialmente para tareas computacionalmente intensivas. Esto se traduce en aplicaciones web más rápidas y eficientes.
  • Seguridad y estabilidad: Rust, con su sistema de tipos estricto y su manejo de memoria seguro, ayuda a prevenir errores comunes en tiempo de compilación. Esto redunda en componentes más estables y seguros, reduciendo la probabilidad de fallos en producción.
  • Escalabilidad y modularidad: La naturaleza modular de los componentes web reutilizables facilita la escalabilidad de las aplicaciones. Puedes construir bibliotecas de componentes que se pueden compartir entre proyectos, mejorando la eficiencia del desarrollo y manteniendo una base de código consistente.

Ejemplos prácticos

Creación de un botón interactivo utilizando Rust y Wasm

Este es un ejemplo sencillo de creación de un botón interactivo que cambia de color al hacer clic:

use wasm_bindgen::prelude::*;
use web_sys::{Element, HtmlElement};

#[wasm_bindgen]
pub struct ColorButton {
    element: HtmlElement,
    colors: Vec<String>,
    current_color: usize,
}

#[wasm_bindgen]
impl ColorButton {
    pub fn new() -> Result<ColorButton, JsValue> {
        let window = web_sys::window().unwrap();
        let document = window.document().unwrap();
        let element = document.create_element("button")?;
        let button = element.dyn_into::<HtmlElement>()?;

        button.set_inner_text("Cambiar color");
        button.style().set_property("padding", "10px")?;

        let colors = vec![
            String::from("#ff0000"),
            String::from("#00ff00"),
            String::from("#0000ff"),
        ];

        button.style().set_property("background-color", &colors[0])?;

        Ok(ColorButton {
            element: button,
            colors,
            current_color: 0,
        })
    }

    pub fn attach(&self, parent: &Element) -> Result<(), JsValue> {
        parent.append_child(&self.element)?;
        Ok(())
    }

    pub fn on_click(&mut self) -> Result<(), JsValue> {
        self.current_color = (self.current_color + 1) % self.colors.len();
        self.element.style().set_property("background-color", &self.colors[self.current_color])?;
        Ok(())
    }
}

Desarrollo de un gráfico dinámico

Para crear un gráfico dinámico, podríamos utilizar una biblioteca de gráficos en JavaScript que tome una muestra de datos procesados con Rust:

use wasm_bindgen::prelude::*;
use js_sys::Array;

#[wasm_bindgen]
pub struct DataProcessor {
    data: Vec<f64>,
}

#[wasm_bindgen]
impl DataProcessor {
    pub fn new() -> DataProcessor {
        DataProcessor { data: Vec::new() }
    }

    pub fn add_data(&mut self, value: f64) {
        self.data.push(value);
    }

    pub fn get_processed_data(&self) -> Array {
        self.data.iter().map(|&x| JsValue::from(x * 2.0)).collect()
    }
}

Este componente podría usarse en conjunto con una biblioteca de gráficos JavaScript para crear visualizaciones aún más complejas, delegando la gestión de los datos a Rust.

Construye interfaces de usuarios personalizadas y atractivas
Lleva la formación de tu equipo al siguiente nivel con cursos, talleres y laboratorios prácticos de JavaScript, React, Angular, HTML, CSS y más.
Solicitar más información

Conclusiones

La combinación de Rust y WebAssembly ofrece un enfoque muy potente para crear componentes web reutilizables de alto rendimiento.

A medida que la web evoluciona hacia aplicaciones más complejas y exigentes, esta tecnología se posiciona como una solución prometedora para los desafíos de rendimiento y seguridad.

La tendencia hacia la utilización de este stack en el desarrollo web está ganando impulso, de forma que los desarrolladores que dominen estas tecnologías estarán bien posicionados para encontrar nuevos nichos de empleo, donde la eficiencia y rendimiento es el factor primordial.

Si quieres ampliar tus conocimientos en Rust y sus aplicaciones, te recomendamos que leas este artículo completo en OpenWebinars: Tauri: Crea aplicaciones de escritorio con tecnologías web.

Bombilla

Lo que deberías recordar de Rust y Wasm

  • WebAssembly permite ejecutar código de bajo nivel en el navegador con un rendimiento cercano al nativo.
  • Rust es un lenguaje ideal para WebAssembly debido a su rendimiento, seguridad y herramientas de interoperabilidad.
  • Los componentes web reutilizables con Rust y Wasm ofrecen mejor rendimiento, seguridad y modularidad.
  • La integración de componentes Rust/Wasm con HTML y CSS se realiza típicamente a través de JavaScript.
  • El Shadow DOM puede utilizarse para encapsular estilos y estructura de los componentes.
  • El ecosistema de Rust y WebAssembly está en constante crecimiento, ofreciendo cada vez más posibilidades para el desarrollo web.
Compartir este post

También te puede interesar