Cloudflare
Le binding WASM qui n'existe pas
Comment j'ai passé une heure à chercher pourquoi env.RENDERER était undefined dans un Cloudflare Worker, alors que wrangler.toml semblait correct.
En câblant le moteur de rendu de MyOG — du Rust compilé en WebAssembly — j’ai écrit ceci
dans wrangler.toml :
[[rules]]
type = "CompiledWasm"
globs = ["**/*.wasm"]
Puis, dans le code du Worker, j’ai lu env.RENDERER en supposant qu’un binding
portant ce nom existerait. C’était faux, et la manière dont c’était faux mérite
d’être racontée.
Ce que fait vraiment cette règle
[[rules]] ne crée aucun binding. Elle indique au bundler d’esbuild comment
traiter un fichier .wasm rencontré dans le graphe de modules : au lieu de
tenter de le lire comme du JavaScript, il le passe au runtime comme un module
WebAssembly déjà compilé.
Autrement dit, elle autorise un import. Elle ne peuple pas env.
Dans le format « modules » des Workers, un binaire WebAssembly s’importe :
import wasmModule from '@myog/renderer/wasm';
const instance = await WebAssembly.instantiate(wasmModule, {});
wasmModule est un WebAssembly.Module, pas un ArrayBuffer : la compilation
a déjà eu lieu au chargement du Worker. Il ne reste qu’à l’instancier, ce qui
coûte quelques millisecondes et s’amortit sur toute la durée de vie de
l’isolate.
L’ancien mécanisme wasm_modules de wrangler, lui, créait bien un binding —
mais il appartenait au format « service worker », abandonné depuis. La plupart
des exemples qu’on trouve encore en ligne datent de cette époque, ce qui
explique la confusion.
Pourquoi ça n’a pas été attrapé plus tôt
Deux raisons, et aucune n’est flatteuse.
D’abord, j’avais déclaré RENDERER: WebAssembly.Module dans mon interface
Bindings. TypeScript était parfaitement content : j’affirmais que la
propriété existait, il me croyait. Un type qui décrit un environnement externe
est une affirmation, pas une vérification — il ne vaut que ce que vaut la
personne qui l’a écrit.
Ensuite, l’erreur ne serait apparue qu’au premier appel de /v1/og, sous la
forme d’un Cannot read properties of undefined. Loin de la cause, et sans
rapport apparent avec un fichier de configuration.
Ce que j’en retire
Un binding déclaré dans un type TypeScript et un binding réellement injecté par
la plateforme sont deux choses différentes. La seule chose qui les réconcilie
est wrangler types, qui génère les déclarations depuis la configuration
plutôt que de les écrire à la main.
C’est maintenant dans les notes du projet, juste à côté de l’autre piège du même genre : le module WASM doit être construit avant l’API, sinon l’import échoue sur un fichier introuvable. Rust n’est donc pas optionnel, même pour qui ne touche qu’aux routes.