Visión general

He estado trabajando en FidgetMap desde hace un tiempo. Creo que podría acelerar muchos procesos (incluido el bucle básico de renderizado) añadiendo Trabajadores web para manejar muchas de las tareas de renderizado de forma asíncrona. Todo el juego se dibuja escribiendo datos RGB en matrices y luego componiéndolos en un lienzopor lo que es realmente un candidato perfecto para Trabajadores web. Es bastante descabellado pensar que haya llegado tan lejos sin ningún hilo de fondo, pero aquí estamos.

No voy a entrar en toda una lección de historia sobre los Web Workers, basta con decir que me permitirán pasar gran parte de la renderización a un hilo en segundo plano (múltiples hilos en segundo plano en mi caso) para liberar el hilo de ejecución principal. Todo el juego debería ser más rápido con ciertas tareas compartimentadas en segundo plano.

La parte principal del juego se dirige directamente a un lonacomo se ha mencionado anteriormente, pero los controles de la interfaz de usuario, como el menú y los botones interactivos, están escritos en React. No tiene sentido reinventar la rueda en lo que respecta a la representación de texto y estilo flex. Así que construí el proyecto utilizando crear-react-app (con Typescript). Quiero escribir mis Web Workers con el mismo estilo, preferiblemente en el mismo código fuente. Pero no puedes limitarte a importar módulos Web Worker y luego convertirlos en trabajadores. Tienes que instanciar un trabajador con una URL a un archivo (o paquete) JavaScript independiente.

CRA es genial. Oculta cualquier configuración y hace que sea superfácil iniciar un proyecto React. Pero no es tan bueno en configuraciones avanzadas como ésta, en la que necesitas compilar tu aplicación en un archivo JS y potencialmente varios Web Workers en archivos separados. La recomendación habitual es expulsar de create-react-app. Esto te permite juguetear con la configuración según necesites, pero un gran poder conlleva una gran responsabilidad. Una vez expulsado, tienes que mantener tú mismo la configuración y los scripts. Se acabó la actualización fácil de un proyecto bien probado.

Entonces, ¿cómo podemos hacer diferentes objetivos de compilación para diferentes puntos de entrada? Parcela al rescate.  ParcelJS es una herramienta de construcción sin configuración que funciona con Typescript desde el primer momento. Sólo tienes que decir "oye Parcela, este archivo de entrada" y hace The Thing™. Basta de charla, déjame mostrarte cómo lo hice.

La solución

Empecemos por el principio. Utiliza create-react-app para iniciar un nuevo proyecto con Typescript. Ve al directorio de tu proyecto y crea una nueva aplicación como ésta:

npx create-react-app workers-ejemplo --template typescript

Ahora entra en el directorio e instalemos Parcela

cd trabajadores-ejemplo
npm i --save-dev paquete

Rad, ahora técnicamente estamos trabajando con dos sistemas de compilación: el incorporado en create-react-app y Parcel que acabamos de añadir. Vamos a crear un nuevo directorio en la raíz del proyecto llamado "workers". Ahí es donde pondremos el código fuente de nuestros workers. Está en un directorio distinto de src, ya que cada uno será su propio pequeño paquete. También pondremos ahí una trabajadora de ejemplo.

mkdir trabajadores
cd trabajadores
toca ejemploTrabajador.ts

Pongamos algo en nuestro trabajador de muestra para ver que funciona

self.onmessage = (e: EventoMensaje) => {
    self.postMessage("hola, mundo desde el trabajador");
};

Ahora añadiremos un nuevo script a nuestro package.json para poder construir workers usando npm. Construiremos todos los workers en el directorio público bajo un subdirectorio para que nuestra aplicación pueda utilizarlos fácilmente desde allí.

{
// ...resto del archivo
  "scripts": {
    // ... resto de scripts
    "trabajador:construir": "parcel build --dist-dir public/workers --"
  }
}

Bien, ¡ahora podemos construir trabajadores! Ejecuta esto para ver la magia:

npm run worker:build workers/sampleWorker.ts

Parcel utilizará sampleWorker.ts como punto de entrada. Verá que es Typescript y hará lo correcto sin ningún plugin o configuración adicional. Cuando el comando termine, deberías encontrar tu recién construido trabajador en public/workers junto con un mapa de fuentes. ¡Qué bien!

Míralo en acción

Vamos a actualizar el archivo automático de la App para que podamos ver a nuestro nuevo trabajador en acción. Copia/pega esto en tu App.tsx para interactuar con el trabajador que hemos construido.

import React, { FC, useCallback, useEffect, useRef } from 'react';
const App: FC = () => {
  const workerRef = useRef<worker>();
  const enviarMensajeAlTrabajador = useCallback(() =&gt; {
    workerRef.current?.postMessage({});
  }, []);
  useEfecto(() =&gt; {
    workerRef.current = nuevo Worker("/workers/sampleWorker.js");
    workerRef.current?.addEventListener('mensaje', (evento) =&gt; {
      alert('mensaje recibido del trabajador: ' + JSON.stringify(evento.datos));
    });
    return () =&gt; workerRef.current?.terminate();
  }, []);
  devolver (
    <div>
      <button type="button" onclick="{sendMessageToWorker}">
        Enviar mensaje al trabajador
      </button>
    </div>
  );
}
export default App;

Llevarlo más lejos - construir automáticamente

Construye los trabajadores cuando se construya la app

Ahora podemos compilar obreras de la forma más básica. Sería mejor que no añadiéramos los trabajadores compilados a nuestro código fuente, sino que los construyéramos junto con el paquete normal. Podemos conseguirlo con bastante facilidad.

En primer lugar, vamos a añadir el directorio público de trabajadores (y el directorio de caché de paquetes) a nuestro .gitignore para no confirmarlos.

// en .gitignore
.parcel-cache
público/trabajadores

A continuación, añadiremos un comando para construir todos los trabajadores en tu directorio de trabajadores. De vuelta en package.json...

{
// ...resto del archivo
  "scripts": {
    // ... resto de scripts
    "trabajador:construir": "parcel build --dist-dir public/workers --",
    "workers:build:all": "npm run worker:build ./workers/*"
  }
}

Una vez más, Parcela salva el día. Utilizará cada archivo del directorio de trabajadores como punto de entrada y creará un paquete distinto para cada uno. Esto te permite tener trabajadores totalmente compartimentados. Cada uno puede importar de node_modules y esas dependencias pasarán a formar parte del paquete final de cada trabajador.

Pero no queremos tener que ejecutar un comando de compilación aparte. Queremos que lo haga junto con el más básico npm run build. Podemos conseguirlo utilizando otro paquete llamado npm-run-all. Esto te permite ejecutar varios comandos npm a la vez, en secuencia o en paralelo.

npm i --save-dev npm-run-all

Ahora, de vuelta en package.json, vamos a mover el comando de compilación existente a un nombre diferente para que podamos hacer que el comando de compilación normal haga varias cosas

{
// ...resto del archivo
  "scripts": {
    // ... resto de scripts
    "construir": "npm-run-all --sequential workers:build:all rs:build",
    "rs:build": "react-scripts build",
    "worker:build": "parcel build --dist-dir public/workers --",
    "workers:build:all": "npm run worker:build ./workers/*"
  }
}

Muy bien. Ahora, cada vez que construyas, construirás primero todos tus trabajadores y luego ejecutarás la construcción normal de react-scripts que crea tu paquete create-react-app.

Ahora puedes utilizar tus trabajadores totalmente transpilados y empaquetados en cualquier parte de tu paquete de aplicaciones existente haciendo:

    const worker = nuevo Worker("/workers/sampleWorker.js");

Forma a los trabajadores en el desarrollo siempre que cambien

La última pieza del puzzle es construir los workers cuando ejecutes `npm start`. Parcel tiene un comando "watch" incorporado, pero asume que tu archivo se está ejecutando en un entorno con una variable `window`, cosa que no hacen los workers. Así que podemos implementar nuestro propio vigilante fácilmente utilizando un paquete llamado `node-watch`. Empieza por instalarlo:

npm i --save-dev node-watch

Ahora crea un nuevo script llamado `watchWorkers.js`. Yo lo puse en un subdirectorio llamado "scripts". Copia esto en watchWorkers.js

var watch = require('nodo-watch');
var { spawn } = require('proceso_hijo');
// esto creará los trabajadores inicialmente cuando se ejecute el script
crear(
    npm',
    ['run', 'workers:build'],
    { stdio: 'heredar' }
);
watch('./workers/', { recursive: true }, function(evt, name) {
    // esto construirá cada trabajador individual a medida que se actualicen
    crear(
        'npm',
        ['ejecutar', 'trabajador:construir', nombre],
        { stdio: 'heredar' }
    );
});

Ahora tenemos un script que reconstruirá cada trabajador a medida que cambie. Una vez más, modifiquemos package.json para que forme parte de nuestro proceso normal

{
// ...resto del archivo
  "scripts": {
    // ... resto de scripts
    "inicio": "npm-run-all --parallel workers:watch rs:start",
    "rs:start": "react-scripts start",
    "build": "npm-run-all --sequential workers:build:all rs:build",
    "rs:build": "react-scripts build",
    "worker:build": "parcel build --dist-dir public/workers --",
    "workers:build:all": "npm run worker:build ./workers/*",
    "workers:watch": "node ./scripts/watchWorkers.js"
  }
}

Ahora, cada vez que ejecutes `npm start`, ejecutarás el react-scripts start normal que vigilará los cambios en el paquete de aplicaciones, pero también ejecutarás tu propio worker watch que retranspilará cada worker a medida que se actualicen.

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

es_ESES
Scroll al inicio