veilletech.fr
10 sept. Feed du jour
#07 CLOUDFLARE Article

Workers : Node par défaut, 64 Mio de code

Les polyfills que vous aviez écrits ont perdu leur raison d'être.

Cloudflare a réécrit le registre de modules de workerd : les API Node sont désormais actives par défaut, la taille maximale d'un Worker passe à 64 MiB sur tous les plans et la limite de taille compressée disparaît. Le nouveau registre traite les spécificateurs comme de vraies URL, ce qui débloque import.meta.resolve(), require(esm) aux règles Node, la compilation paresseuse et des caches de code partagés. Il s'active explicitement, par un drapeau de compatibilité.

3 min de lectureintermédiairevidéo 1:20
Partager
Sommaire4 sections
  1. Ce qui se passe
  2. Ce qui change vraiment
  3. Trois conséquences à connaître
  4. À retenir

Ce qui se passe

Deux murs tombaient déjà cet été côté Workers : la compatibilité Node est activée par défaut depuis le 4 août, et la taille maximale d'un Worker est passée à 64 Mio sur tous les plans le 4 septembre, la limite sur la taille compressée ayant été supprimée.

Restait le troisième : la manière dont le runtime résout, charge et met en cache les modules. Cloudflare a réécrit le registre de modules de workerd, et il s'active dès aujourd'hui par un drapeau :

JSON
{
  "compatibility_flags": ["new_module_registry"]
}

Il n'a pas de date d'activation par défaut : quelle que soit votre compatibility_date, il faut poser le drapeau explicitement. Les Workers déjà déployés continuent de fonctionner comme avant.

Ce qui change vraiment

Une seule décision commande tout le reste : un spécificateur d'import n'est plus un chemin de fichier, c'est une URL. L'ancien registre les traitait comme des chemins, ce qui rendait import.meta.url impossible à implémenter proprement et faisait de node: ou cloudflare: des préfixes de chaîne traités à part au lieu de protocoles.

Il compilait aussi l'intégralité du bundle en amont, qu'un module soit importé ou non, et gardait une copie privée par isolat V8. Or Cloudflare exécute plusieurs répliques d'isolat du même Worker pour répartir la charge sur les cœurs : la même source était donc compilée plusieurs fois et tenue en mémoire autant de fois. Le nouveau registre compile paresseusement au premier import et partage les caches entre répliques.

Trois conséquences à connaître

Une chaîne de requête distingue deux instances. Le registre applique les règles d'identité de module du navigateur : même source, import.meta.url différent, état de premier niveau séparé.

JavaScript
import { increment as incA } from './counter.js?a';
import { increment as incB } from './counter.js?b';

incA(); // 1
incA(); // 2
incB(); // 1 — instance distincte, avec sa propre copie de l'état

Ce n'est pas un moyen de forcer une réévaluation : le même spécificateur avec la même chaîne de requête rend toujours la même instance.

Les attributs d'import sont validés, là où l'ancien registre les ignorait silencieusement — en violation de la spécification. json est le seul type autorisé, le seul dont la proposition TC39 soit en Stage 4 ; text et bytes sont reconnus mais rejetés avec un message explicite, et toute clé autre que type devient une erreur franche :

JavaScript
import data from './config.json' with { type: 'json', cache: 'no' };
// TypeError: Unsupported import attribute: "cache"

require() sur un module ES suit les règles de Node. Un export nommé 'module.exports' prend la main, sinon vous récupérez l'objet namespace — sauf pour les modules node: intégrés à workerd, qui rendent directement leur export par défaut, si bien que require('node:buffer').Buffer se comporte comme prévu. La restriction qui va avec mérite d'être connue : si le module requis, ou n'importe quoi dans son graphe, contient un await de premier niveau, require() lève une erreur au lieu de bloquer — exactement comme ERR_REQUIRE_ASYNC_MODULE côté Node. Il faut passer par import().

À retenir

S'ajoutent des erreurs enfin cohérentes quel que soit le chemin de chargement — Module not found est une Error, un spécificateur non analysable une TypeError —, ce qui rend fiable un branchement sur la classe d'erreur dans un loader maison, et les imports WebAssembly en source phase (import source wasmModule from './add.wasm'), qui rendent un WebAssembly.Module sans passer par l'export par défaut.

La marche à suivre tient en trois temps : poser le drapeau sur une préproduction, relancer la suite de tests, puis regarder en priorité le code que vous aviez contourné à coups de polyfills et de bundling forcé. C'est celui-là qui peut disparaître.

Source : How we rebuilt Cloudflare Workers' module registry for Node.js compatibility