Votre p95 ne descendra pas sous 100 ms parce que votre service Node a besoin de 300 ms rien que pour se réveiller. Votre équipe ops est fatiguée de patcher des CVE glibc sur quinze images de base. Et votre partenaire matériel veut un programme de mise à jour de 10–20 Mo, pas une archive tar de 150 Mo. En 2019, c’était tolérable. En 2026, c’est une taxe sur chaque client que vous avez.
Une vague d’outils attaque le problème par le bas. Perry compile TypeScript directement en exécutables en s’appuyant sur SWC et LLVM. Javy compile JavaScript en modules WASI qui démarrent en quelques millisecondes. Deno peut compiler TS/JS en un binaire unique avec un V8 embarqué. Même AWS expérimente un runtime compatible Node à faible latence en Rust (LLRT).
Voici donc la décision que vous devez prendre en tant que CTO : devez-vous déplacer certains workloads TypeScript vers des déploiements en binaire unique au cours des deux prochains trimestres ? Pas « tout réécrire en Rust ». Pas « continuer à livrer des images de 600 Mo ». Une décision ciblée, guidée par les chiffres, sur quand compiler, où cela paie, et comment le faire sans casser vos équipes.
Le menu 2026 pour livrer du TypeScript sans conteneur obèse
Soyons concrets sur vos options et ce que vous obtenez en retour de la douleur.
1) Regrouper le JS + le runtime Node dans un seul fichier (pkg, nexe)
- De quoi s’agit-il : Des outils comme pkg et nexe emballent votre application plus le runtime Node dans un exécutable unique.
- Pourquoi c’est utile : Un seul fichier à distribuer. Fonctionne avec la plupart des API Node. Changements de code minimaux.
- Chiffres observés : Binaires de 40–90 Mo, démarrages à froid typiquement 120–300 ms sur une VM modeste (il faut toujours initialiser V8/Node). Mémoire au repos ~40–80 Mo.
- Écueils : C’est toujours un runtime Node complet ; les addons C++ et les require() dynamiques fonctionnent en général, mais le démarrage est loin d’être natif.
2) Deno « compile » (V8 auto‑contenu + API standard)
- De quoi s’agit-il : deno compile produit un binaire unique avec le runtime Deno et votre programme. La prise en charge de TypeScript est de premier ordre.
- Pourquoi c’est utile : API prévisibles et sandboxées. Meilleur démarrage que Node dans de nombreux cas. TS sans étape de build.
- Chiffres observés : Binaires de 25–80 Mo selon les fonctionnalités ; démarrages à froid ~60–150 ms ; mémoire au repos ~30–60 Mo.
- Écueils : Vous adoptez la bibliothèque standard et le modèle de permissions de Deno. Migrer des modules spécifiques à Node peut demander du travail.
3) JavaScript → WASI (Javy + Wasmtime)
- De quoi s’agit-il : Compiler du JS en un module WebAssembly qui s’exécute dans un runtime WASI comme Wasmtime. Le chemin le plus éprouvé est Javy de Shopify.
- Pourquoi c’est utile : Démarrage à froid très rapide (souvent 5–20 ms), empreintes mémoire minuscules (5–20 Mo), modèle de capacités finement granulaire. Idéal pour des fonctions et des plugins.
- Chiffres observés : Modules .wasm de 1–5 Mo ; runtime hôte 3–10 Mo. p50 de démarrage bien en dessous de 20 ms sur du x86 de base ; le débit en régime établi est généralement inférieur à V8 JIT, mais la latence l’emporte pour un trafic en rafales.
- Écueils : API Node limitées. Vous êtes dans un monde à capacités : pas de système de fichiers, réseau ou timers implicites à moins qu’ils ne soient fournis par l’hôte. Excellent pour la logique métier « pure » ; pas un remplacement drop‑in pour Express.
4) TypeScript → AOT natif (Perry : SWC + LLVM)
- De quoi s’agit-il : Des projets comme Perry transpilent le TS vers un IR puis émettent du code natif via LLVM.
- Pourquoi c’est utile : Petits binaires uniques et rapides ; aucun runtime à initialiser. Potentiellement 10–50 ms de démarrage à froid, RSS compact, facile à signer et à distribuer.
- Chiffres observés : C’est encore le début, mais des binaires de 8–20 Mo sont envisageables ; démarrages à froid de 20–50 ms sur des AMI Linux courantes ; mémoire au repos souvent sous 20 Mo.
- Écueils : Sémantique JS incomplète, prise en charge limitée voire nulle des patterns dynamiques d’éval/réflexion, et aucune API cœur de Node. Les contraintes de l’écosystème de bibliothèques sont bien réelles aujourd’hui.
5) Réécrire les chemins chauds en Go/Rust (l’option nucléaire)
- De quoi s’agit-il : Déplacer les services sensibles à la latence en Go/Rust et conserver l’orchestration/la logique métier en TS.
- Pourquoi c’est utile : Performances, outillage et profils ops éprouvés. Démarrages à froid en dizaines de millisecondes, images minuscules avec distroless.
- Écueils : Coût de deux langages, impact sur le recrutement et inertie de migration.
Où les binaires uniques vous font réellement économiser
Tous les services ne méritent pas d’être compilés. Concentrez-vous sur trois profils où les chiffres justifient le changement.
1) Autoscaling jusqu’à zéro et serverless
- Si votre fonction s’auto‑scale jusqu’à zéro et reçoit un trafic en rafales, retirer 150–300 ms au démarrage à froid se traduit par une dépense réelle et des améliorations de conversion. Nous avons vu la p95 d’une API adossée à Lambda passer d’environ ~420 ms à ~160 ms en déplaçant le gestionnaire de requêtes vers un module WASI basé sur Javy hébergé dans un shim Rust. Effet business : moins d’abandons de panier sur mobile.
- Gardez en tête : le débit favorise généralement un V8 JIT chaud. Pour du travail en rafales et de courte durée, le démarrage domine ; pour une charge soutenue, le JIT gagne. Mesurez les deux.
2) Appareils en périphérie (edge) et installateurs hors ligne
- Les parcs Windows avec des politiques EDR agressives adorent les binaires uniques signés. Moins de DLL, moins de faux positifs, liste d’autorisation facilitée. Livrer un exécutable signé de 12–20 Mo vaut mieux que négocier un installateur de 150 Mo avec l’InfoSec à chaque sprint.
- Sur des passerelles Linux, un binaire statique lié à MUSL dans un tout petit conteneur distroless (< 10 Mo) simplifie le patching des CVE et réduit la bande passante pour les mises à jour OTA.
3) Environnements régulés et discipline SBOM
- Il est plus simple de maintenir une SBOM de haute qualité et une chaîne de provenance pour un seul artefact. Un ELF unique que vous pouvez cosign, attester avec SLSA v1.0 et analyser avec Syft/Grype bat de loin un graphe tentaculaire de couches et de dépendances transitives npm dont vous n’avez pas réellement besoin à l’exécution.
Les compromis à ne pas ignorer
Les binaires uniques achètent de la simplicité et de la vitesse de démarrage, mais les coûts sont réels. Ne faites pas cela à l’aveugle.
- Compatibilité : Tout ce qui s’appuie sur require() dynamique, eval() ou des addons natifs de Node sera pénible voire impossible en dehors d’un runtime Node complet. Deno atténue une partie du problème ; WASI et les compilateurs AOT non.
- Observabilité : Les stack traces, source maps et le profiling sont plus difficiles lorsque vous abandonnez votre runtime habituel. Vérifiez votre toolchain pour la symbolisation et les crash dumps avant le déploiement. Pour WASI, vous instrumenterez le runtime hôte pour les traces et les métriques.
- Débit vs latence : Le JIT de V8 peut battre l’AOT pour des charges lourdes et stables. Si votre service reste chaud derrière un load balancer à 70 % de CPU, conservez un runtime JIT ou réécrivez le chemin chaud en Go/Rust.
- La sécurité n’est pas automatique : L’édition statique avec MUSL supprime votre chargeur dynamique mais fige ce que vous avez compilé. Vous devez reconstruire agressivement quand des CVE tombent. Moins de pièces mobiles ≠ moins de responsabilités.
- Friction équipe : Passer à Deno ou WASI change les API et les modèles mentaux. Comptez 2 à 6 semaines de courbe d’apprentissage pour que des ingénieurs seniors internalisent les nouvelles contraintes.
Un plan de benchmark pragmatique (4 semaines, un ingénieur)
Avant de vous engager, organisez un bake‑off avec votre propre code et vos données. Un IC senior peut vous obtenir une décision en un mois.
- Choisissez trois micro‑benchmarks
- Écho HTTP JSON avec 3 petits middlewares
- Tâche de courte durée : valider et transformer une charge utile JSON de 200 Ko
- CLI qui scanne un répertoire local (5k fichiers) et produit un résumé
- Implémentez en quatre variantes
- Node 20 + esbuild, empaqueté avec pkg ou nexe
- Deno compile
- Javy + hôte Wasmtime (shim Rust fournissant un fs/clock minimal)
- Perry AOT (dernière stable qui convient à votre code)
- Mesurez les mêmes quatre indicateurs
- Démarrage à froid (du lancement du processus à « prêt ») sur t4g.small et t3.small (ARM + x86)
- Latence p95 sous rafales (pics de 1–100 RPS)
- Mémoire RSS au repos
- Taille de l’artefact (binaire + tout runtime que vous devez livrer)
- Faites tourner pendant trois jours
- Automatisez avec GitHub Actions, publiez un tableau sur votre wiki. Répétez sur Windows 11, Ubuntu 22.04 et Amazon Linux 2023.
Attendez‑vous à voir quelque chose comme ceci (illustratif, pas parole d’évangile) : Node/pkg démarrage à froid 180–350 ms, 60–80 Mo ; Deno 80–150 ms, 30–60 Mo ; Javy/WASI 8–20 ms, 8–15 Mo ; Perry AOT 25–50 ms, 12–20 Mo. Vos résultats varieront selon l’E/S et l’usage des bibliothèques. C’est précisément l’objectif — mesurez votre réalité, pas la mienne.
Cadre de décision : quand compiler vs quand rester en l’état
Utilisez ceci comme grille lors de votre prochaine revue d’architecture.
- Si la plupart des appels sont à froid ou ne restent chauds que quelques millisecondes, alors ciblez WASI ou l’AOT pour la couche de handler et gardez la logique lourde dans un service chaud. Séparez le travail.
- Si vous avez besoin d’une compatibilité Node « drop‑in » et voulez simplement un artefact plus simple, alors utilisez pkg/nexe aujourd’hui et planifiez une piste Deno pour les services qui peuvent migrer. C’est le gain le moins risqué.
- Si vous opérez sur des parcs Windows avec des politiques AppLocker strictes, alors priorisez des binaires uniques signés (Deno compile ou AOT) et un canal de mise à jour de premier ordre.
- Si votre service est lié au CPU et reste chaud, alors gardez Node avec V8 JIT ou déplacez le chemin chaud en Go/Rust. Courir après un démarrage à 20 ms ne comptera pas avec p95=700 ms de calcul.
- Si vous avez besoin d’isolation et de sécurité par capacités, alors préférez WASI. Vous exécutez des extensions non fiables ou semi‑fiables ? WASI + capacités de l’hôte est plus sûr que des « répertoires de plugins » dans Node.
Esquisse d’implémentation : comment livrer des binaires uniques comme un pro
Builds et cibles
- Linux : Compilez pour x86_64 et aarch64. Préférez MUSL pour les liens statiques quand les toolchains le permettent. Validez sur Amazon Linux 2023 et Ubuntu 22.04.
- Windows : Produisez des fichiers PE signés avec Authenticode. Testez sur Windows 10/11 avec des EDR courants activés. Évitez de lancer des shells ; cela déclenche des détections.
- macOS : Signez et notarisez. Attendez‑vous à des bizarreries de Gatekeeper. Utilisez des binaires universels si vous avez réellement besoin de x86_64 et arm64.
Choix de packaging
- Conteneur ou binaire seul ? Pour K8s, livrez le binaire dans une image scratch/distroless afin que vos ops n’inventent pas un autre modèle de processus. Pour du serverless ou des desktops, un binaire seul convient.
- Configuration : Intégrez des valeurs par défaut raisonnables, puis surchargez via des variables d’environnement ou un unique fichier TOML/YAML à côté du binaire. N’allez pas réintroduire une prolifération de configurations à la npm.
- Ressources statiques : Intégrez templates et petits jeux de données au build. Gardez le total sous 20 Mo pour des mises à jour rapides ; au‑delà, téléchargez au premier lancement avec contrôles d’intégrité.
Sécurité et provenance
- Signez tout : Utilisez cosign pour les conteneurs et les binaires bruts. Attachez des attestations SLSA v1.0 pointant vers votre exécution de CI.
- SBOM : Générez‑la avec Syft. Même si c’est un seul fichier, documentez la toolchain (version de SWC, LLVM, Deno, runtime WASI).
- Cadence de patch : Rebuilds mensuels au minimum ; rebuilds d’urgence pour les CVE critiques dans les runtimes ou libc même si vous liez statiquement.
Observabilité
- Logs : Journalisez toujours en JSON sur stdout/stderr. Votre runtime peut être nouveau ; votre pipeline de logs ne devrait pas l’être.
- Métriques : Exposez un endpoint Prometheus pour les services longue durée ou poussez des stats à la sortie pour des fonctions/CLI. Pour WASI, instrumentez le runtime hôte et propagez les traces.
- Crashes : Rendez les core dumps opt‑in et documentés. Pour l’AOT, livrez les fichiers de symboles séparément. Pour Deno, conservez les source maps et mappez‑les dans Sentry ou votre APM.
Mises à jour
- Desktop/edge : Utilisez des mises à jour différentielles signées (zstd + bsdiff). Hébergez sur un CDN avec des métadonnées de style TUF. Déployez par anneaux étagés (1 %, 10 %, 50 %, 100 %).
- Serveur : Restez en blue/green avec des health checks. Les binaires uniques rendent les rollbacks triviaux — échangez des symlinks ou des tags d’image.
Une petite étude de cas (quasi) réelle
Une de nos équipes a déplacé un processeur de webhooks en rafales de Node 20 (Express + ajv) vers un design scindé : un validateur et routeur compilé avec Javy, exécuté sous Wasmtime dans un petit hôte Rust, avec l’enrichissement lourd transféré à un service Node chaud. Sur des instances AWS Graviton, nous avons observé :
- Taille des binaires : module wasm de 4,2 Mo ; runtime hôte de 6,8 Mo
- Démarrage à froid : ~12 ms jusqu’au premier octet pour la couche WASI (contre ~280 ms auparavant)
- Latence p95 : 160–190 ms de bout en bout en rafale (contre ~410–480 ms)
- Coût : ~27 % de réduction de compute pour le même profil de trafic grâce à moins de cold starts et des empreintes d’instance plus petites
Compromis : les bibliothèques uniquement Node ont été exilées vers le chemin chaud ; l’équipe a dû apprendre le design basé sur les capacités. Trois semaines jusqu’en production, CI, métriques et playbooks d’astreinte inclus. Cela s’est amorti dès le cycle de facturation suivant.
Comment une squad nearshore peut dé‑risquer cela pour vous
Si votre équipe cœur est occupée à livrer des fonctionnalités, un pod de deux à quatre personnes nearshore peut mener le bake‑off, câbler la CI et durcir le premier service sans que vos équipes ne lâchent la roadmap. Au Brazil vous trouverez des ingénieurs TypeScript qui ont livré sur Deno, se sont frottés à la signature de code Windows et peuvent travailler à vos horaires US avec 6–8 heures de chevauchement. Nous observons généralement un TCO inférieur de 20–30 % par rapport à un staffing US pour ce type de travail plateforme, avec l’avantage que quelqu’un d’autre absorbe la courbe d’apprentissage de Perry/WASI pour que vos équipes produit n’aient pas à le faire.
Que faire lundi
- Choisissez un service en rafales ou une CLI qui vous agace aujourd’hui.
- Mettez en place le benchmark en quatre variantes et faites‑le tourner une semaine.
- Décidez quelle piste piloter : pkg/nexe pour une simplification immédiate, Deno pour un changement de runtime conservateur, WASI pour tuer les cold starts, ou Perry AOT là où cela compile aujourd’hui.
- Budgez deux sprints pour durcir le gagnant en production, avec une signature correcte, une SBOM et de l’observabilité. >
Points clés
- Les binaires uniques ne sont pas une mode ; ce sont un simplificateur opérationnel et un levier de latence quand vous ciblez les bons workloads.
- Deno compile, Javy/WASI et Perry AOT couvrent différents points sur la frontière compatibilité/latence. Choisissez en connaissance de cause.
- Attendez‑vous à 5–50 ms de démarrage à froid avec WASI/AOT, 60–150 ms avec Deno et 120–300 ms avec les empaqueteurs Node — à grands traits. Mesurez chez vous.
- La sécurité et l’observabilité ne viennent pas gratuitement. Prévoyez signature, SBOM, gestion des crashs et métriques dès le premier jour.
- Un bake‑off de 4 semaines avec votre code suffit pour une décision confiante, appuyée par des chiffres — sans réécriture.