Vue d'ensemble
J'ai travaillé sur FidgetMap depuis un certain temps. Je pense que je pourrais accélérer beaucoup de processus (y compris la boucle de rendu de base) en ajoutant Travailleurs du Web pour gérer une grande partie des tâches de rendu de manière asynchrone. L'ensemble du jeu est dessiné en écrivant les données RVB dans des tableaux puis en les composant sur une toileIl s'agit donc d'un candidat idéal pour le programme Travailleurs du Web. C'est assez fou de penser qu'il est arrivé jusqu'ici sans aucun fil conducteur, mais nous y sommes.
Je ne vais pas me lancer dans une leçon d'histoire sur les Web Workers, mais je me contenterai de dire qu'ils me permettront de passer une grande partie du rendu à un thread d'arrière-plan (plusieurs threads d'arrière-plan dans mon cas) afin de libérer le thread d'exécution principal. L'ensemble du jeu devrait être plus rapide avec certaines tâches compartimentées en arrière-plan.
La partie principale du jeu est dessinée directement sur un toile, comme mentionné ci-dessus, mais les contrôles de l'interface utilisateur tels que le menu et les boutons interactifs sont écrits en React. Inutile de réinventer la roue en ce qui concerne le texte et le rendu de style flex. J'ai donc construit le projet en utilisant créer une application-react (avec Typescript). Je veux écrire mes Web Workers dans le même style, de préférence dans le même code source. Mais tu ne peux pas simplement importer des modules Web Worker et les transformer en travailleurs. Tu dois instancier un travailleur avec une URL vers un fichier JavaScript séparé (ou un bundle).
CRA est génial. Il cache toute configuration et rend super facile le démarrage d'un projet React. Mais il n'est pas si génial pour les configurations avancées comme celle-ci, où tu dois compiler ton appli en un fichier JS et potentiellement plusieurs Web Workers dans des fichiers séparés. La recommandation habituelle est de éjecter de create-react-app. Cela te permet de jouer avec la configuration selon tes besoins, mais un grand pouvoir implique une grande responsabilité. Une fois que tu as éjecté, tu dois maintenir la configuration et les scripts toi-même. Il n'y a plus de mise à jour facile d'un projet bien testé.
Alors comment faire pour avoir des cibles de compilation différentes pour des points d'entrée différents ? Parcelle à la rescousse. ParcelJS est un outil de construction à configuration zéro qui fonctionne avec Typescript prêt à l'emploi. Tu dis juste "hey Parcel, ce fichier d'entrée" et il fait la chose™. Assez parlé, laisse-moi te montrer comment j'ai fait.
La solution
Commençons par le commencement. Utilise create-react-app pour démarrer un nouveau projet avec Typescript. Va dans le répertoire de ton projet et crée une nouvelle application comme suit :
npx create-react-app workers-example --template typescript
Va maintenant dans le répertoire et installe Parcel.
cd travailleurs-exemple npm i --save-dev parcel
Rad, maintenant nous travaillons techniquement avec deux systèmes de construction ; celui intégré dans create-react-app et Parcel que nous venons d'ajouter. Créons un nouveau répertoire à la racine du projet appelé "workers". C'est là que nous mettrons le code source de workers. Il se trouve dans un répertoire distinct de src puisqu'ils seront chacun leur propre petit paquet. Nous y ajouterons également un exemple de travailleur.
mkdir travailleurs cd travailleurs touch sampleWorker.ts
Mettons quelque chose dans notre exemple de travailleur pour que nous puissions voir que cela fonctionne.
self.onmessage = (e : MessageEvent) => {
self.postMessage("hello, world from the worker") ;
} ;Nous allons maintenant ajouter un nouveau script à notre package.json afin de pouvoir créer des travailleurs à l'aide de npm. Nous allons construire tous les travailleurs dans le répertoire public sous un sous-répertoire afin que notre application puisse facilement les utiliser à partir de là.
{
// ...reste du fichier
"scripts" : {
// ... reste des scripts
"worker:build" : "parcel build --dist-dir public/workers --"
}
}Ok, maintenant nous pouvons construire des travailleurs ! Exécute ceci pour voir la magie :
npm run worker:build workers/sampleWorker.ts
Parcel utilisera sampleWorker.ts comme point d'entrée. Il verra qu'il s'agit de Typescript et fera ce qu'il faut sans aucun plugin ou configuration supplémentaire. Lorsque la commande se termine, tu devrais trouver ton travailleur nouvellement construit dans public/workers ainsi qu'une carte des sources. C'est super !
Vois-le en action
Mettons à jour le fichier App automatique pour que nous puissions voir notre nouveau travailleur en action. Copie/colle ceci dans ton App.tsx pour interfacer avec le travailleur que nous avons construit.
import React, { FC, useCallback, useEffect, useRef } from 'react' ;
const App : FC = () => {
const workerRef = useRef<worker>() ;
const sendMessageToWorker = useCallback(() => {
workerRef.current ?.postMessage({}) ;
}, []) ;
useEffect(() => {
workerRef.current = new Worker("/workers/sampleWorker.js") ;
workerRef.current ?.addEventListener('message', (event) => {
alert('message reçu du travailleur : ' + JSON.stringify(event.data)) ;
}) ;
return () => workerRef.current ?.terminate() ;
}, []) ;
return (
<div>
<button type="button" onclick="{sendMessageToWorker}">
Envoyer un message au travailleur
</button>
</div>
) ;
}
export default App ;
Aller plus loin - construire automatiquement
Construis les travailleurs lors de la construction de l'application
Maintenant, nous pouvons construire des travailleurs de la manière la plus basique. Il serait préférable de ne pas ajouter les travailleurs compilés à notre source, mais de les construire avec le paquet normal. Nous pouvons y parvenir assez facilement.
Tout d'abord, ajoutons le répertoire des travailleurs publics (et le répertoire de cache des colis) à notre .gitignore afin de ne pas les commettre.
// dans .gitignore .parcel-cache public/workers
Ensuite, nous allons ajouter une commande pour construire tous les travailleurs dans ton répertoire de travailleurs. De retour dans package.json...
{
// ...reste du fichier
"scripts" : {
// ... reste des scripts
"worker:build" : "parcel build --dist-dir public/workers --",
"workers:build:all" : "npm run worker:build ./workers/*"
}
}Parcel sauve une fois de plus la situation. Il utilisera chaque fichier du répertoire des travailleurs comme point d'entrée et créera un paquet séparé pour chacun d'entre eux. Cela te permet d'avoir des travailleurs complètement compartimentés. Chacun peut importer des node_modules et ces dépendances feront partie du paquet final pour chaque travailleur.
Mais nous ne voulons pas avoir à lancer une commande de construction séparée. Nous voulons qu'elle le fasse en même temps que la commande npm run build la plus basique. Nous pouvons y parvenir en utilisant un autre paquetage appelé npm-run-all. Il te permet d'exécuter plusieurs commandes npm à la fois, soit en séquence, soit en parallèle.
npm i --save-dev npm-run-all
Maintenant, de retour dans package.json, nous allons déplacer la commande de construction existante vers un nom différent afin que nous puissions faire en sorte que la commande de construction normale fasse plusieurs choses
{
// ...reste du fichier
"scripts" : {
// ... reste des scripts
"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/*"
}
}D'accord ! Maintenant, chaque fois que tu construiras, tu construiras d'abord tous tes travailleurs puis tu exécuteras la construction normale react-scripts qui crée ton paquet create-react-app.
Maintenant, tu peux utiliser tes travailleurs entièrement transposés et regroupés n'importe où dans ton paquetage d'applications existant en faisant :
const worker = new Worker("/workers/sampleWorker.js") ;
Construis les travailleurs en développement à chaque fois qu'ils changent
La dernière pièce du puzzle est la construction des workers lorsque tu exécutes `npm start`. Parcel dispose d'une commande "watch" intégrée, mais elle suppose que ton fichier s'exécute dans un environnement doté d'une variable `window`, ce qui n'est pas le cas des workers. Nous pouvons donc facilement mettre en place notre propre observateur en utilisant un paquetage appelé `node-watch`. Commence par l'installer :
npm i --save-dev node-watch
Maintenant, crée un nouveau script appelé `watchWorkers.js`. Je l'ai placé dans un sous-répertoire appelé "scripts". Copie ceci dans watchWorkers.js
var watch = require('node-watch') ;
var { spawn } = require('child_process') ;
// ceci construira les travailleurs initialement lorsque le script sera exécuté
spawn(
'npm',
['run', 'workers:build'],
{ stdio : 'inherit' }
) ;
watch('./workers/', { recursive : true }, function(evt, name) {
// ceci construira chaque travailleur individuel au fur et à mesure qu'il est mis à jour
spawn(
'npm',
['run', 'worker:build', name],
{ stdio : 'inherit' }
) ;
}) ;
Nous avons maintenant un script qui reconstruira chaque travailleur au fur et à mesure qu'il change. Une fois de plus, modifions package.json pour qu'il fasse partie de notre processus normal
{
// ...reste du fichier
"scripts" : {
// ... reste des scripts
"start" : "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"
}
}Maintenant, chaque fois que tu lanceras `npm start`, tu exécuteras le start react-scripts normal qui surveillera les changements du bundle d'app, mais tu exécuteras aussi ton propre worker watch qui retranspilera chaque worker au fur et à mesure qu'il sera mis à jour.

