Aller au contenu principal

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.