- scriptc compile du TypeScript standard en petits binaires natifs exécutables sans Node, V8 ni moteur JavaScript, tout en conservant la vérification de types du véritable compilateur TypeScript et la compatibilité avec le comportement de Node
- Il détermine si la compilation statique est possible selon la structure du code, génère par défaut du code natif et n'utilise quickjs-ng pour exécuter le JavaScript des paquets npm et le code de type
anyque lorsque l'option--dynamicest choisie - Il prend en charge les classes, les génériques,
async/await, les exceptions, les expressions régulières, ainsi que les API serveur de Node,fetchet les dépendances npm ; les syntaxes non prises en charge sont rejetées avec un code d'erreur, un cadre de code et des suggestions de correction - Plus de 800 programmes sont exécutés à la fois sur Node et en binaire natif afin de comparer la sortie et le code de fin, et les erreurs mémoire sont vérifiées avec AddressSanitizer et un audit du comptage de références
- Sur Apple Silicon, le temps de démarrage est d'environ 2.4ms, les binaires statiques pèsent 170 à 200KB, le RSS typique est de 1 à 4MB et, en mode dynamique avec dépendances embarquées, le binaire atteint environ 3MB
Modèle de compilation statique
- Il utilise le TypeScript existant sans dialecte ni annotation supplémentaires, en appliquant la rigueur de vérification de
tsconfig.jsonet la véritable bibliothèquees2025de TypeScript- Si le projet contient
@types/node, ceux-ci sont également inclus dans la vérification de types - Le code atteignable qui ne peut pas être abaissé produit un diagnostic précis et interrompt la compilation
- Si le projet contient
scriptc coverageaffiche le nombre d'instructions analysées, la part compilée statiquement, ainsi que les éléments bloquants et les codes d'erreur- Dans l'exemple, 99% des 4,481 instructions, soit 4,451, sont compilées statiquement
- Le mode d'exécution est explicitement séparé en trois étapes
- Compilation statique : mode par défaut, conversion en code natif sans moteur JavaScript
- Exécution dynamique : avec
--dynamic, il embarque environ 620KB de quickjs-ng pour exécuter le JavaScript des paquets npm et le code de typeany- Toutes les valeurs provenant du code statique sont validées à l'exécution
- Si le type déclaré et la valeur diffèrent, une
TypeErrorinterceptable est levée sans corrompre la mémoire
- Rejet : le code impossible à traiter est refusé avec un code d'erreur, un cadre de code et, le plus souvent, des pistes de correction, sans compilation incorrecte silencieuse
TypeScript pris en charge et bibliothèque standard
- Les fonctionnalités du langage prises en charge incluent les classes à héritage simple et dispatch dynamique, les closures, la monomorphisation des génériques, les unions discriminées, la déstructuration, le spread, les template literals, les getter/setter et les itérateurs
- Le dispatch dynamique est dévirtualisé lorsque la sûreté peut être prouvée
- Les unions discriminées sont traitées via les valeurs de tag en s'appuyant sur le narrowing de TypeScript
async/awaitest implémenté avec des fibres à pile et un ordonnancement aligné sur JavaScript- Les exceptions et
finally, ainsi que les paramètres optionnels, par défaut et rest, sont pris en charge
- Les expressions régulières utilisent le même interpréteur de bytecode compatible ECMAScript que QuickJS, et il n'est lié qu'aux binaires qui utilisent des regex
- La bibliothèque standard inclut des chaînes respectant la sémantique UTF-16, ainsi que des tableaux,
MapetSetavec les mêmes règles d'ordre et d'identité que JavaScript- Les conversions de type de
JSONpassent par une validation à l'exécution - Une hiérarchie
Errorprenant en chargecatchtypé, ainsi queMath, les typed arrays etBuffer, sont également fournis
- Les conversions de type de
API Node et web
- Les API Node prises en charge incluent
fs,path,process,child_process,os,crypto,url/URL,zlib, les timers et les gestionnaires de signauxfsfournit des API synchrones et Promisechild_processprend en charge les flux pipe- La boucle d'événements n'a aucune dépendance externe
- La pile serveur comprend
net,http,https,tls,dgram,dns,fs.watch,readlineet permet de compiler de vrais serveurs proxy- TLS utilise mbedTLS embarqué
- Une partie des API web WHATWG comme
fetch, les streams,HeadersetAbortSignalest implémentée sur la même pile réseau/TLS native- Les redirections, gzip,
AbortSignal.timeoutet les causes d'erreur au format Node sont pris en charge - Aucune dépendance à libcurl ni au HTTP système n'est utilisée
- Les redirections, gzip,
Dépendances npm et exécution dynamique
- En mode
--dynamic, il utilise la résolution de modules de Node et vérifie les types à partir des fichiers.d.tsfournis par les paquets - Le JavaScript des paquets npm est embarqué dans le binaire au moment du build ;
node_modulesn'est donc pas lu à l'exécution scriptc coverage --dynamicindique pour chaque instruction si elle s'exécute dans la zone statique ou dynamique, ainsi que les éléments bloquants restants- Le moteur JavaScript n'est embarqué que lorsque le mode dynamique est explicitement choisi, ce qui évite toute augmentation silencieuse de la taille du binaire
Exactitude et sûreté mémoire
- Les tests différentiels exécutent plus de 800 programmes à la fois sur Node et sur les binaires natifs, puis comparent
stdout,stderret les codes de fin octet par octet- L'affichage des nombres suit la représentation aller-retour la plus courte, avec validation par fuzzing sur 1 million de
doublecomparés à Node - Les serveurs sont testés en connectant un vrai pilote client aux deux implémentations
- L'affichage des nombres suit la représentation aller-retour la plus courte, avec validation par fuzzing sur 1 million de
- L'ensemble complet des tests est rejoué sous AddressSanitizer et audit du comptage de références, et le build échoue en cas de fuite ou de use-after-free
- Les différences de comportement volontaire par rapport à Node se comptent par dizaines et concernent surtout les détails internes de timing et les propriétés des objets d'erreur
- Chaque différence est documentée, numérotée, et aucune divergence cachée n'est tolérée
Caractéristiques de performance
- Les mesures sur Apple Silicon comparent Node, Go, Rust et Zig sur les mêmes tâches et une sortie identique octet par octet
- Le temps de démarrage est d'environ 2.4ms, contre environ 47ms pour Node, comparable à Zig et devant Go et Rust
- La taille des binaires statiques est de 170 à 200KB, et d'environ 3MB avec
--dynamicet les dépendances embarquées- À titre de comparaison, le binaire Go cité est d'environ 2MB, et Node SEA de 60 à 100MB
- L'usage mémoire typique est de 1 à 4MB de RSS, contre 67 à 116MB pour Node
- À l'exécution, il reste compétitif face aux langages système sur la plupart des tâches tout en conservant la sémantique
f64alignée sur JavaScript- L'inférence d'entiers et l'analyse de possession figurent sur la feuille de route
Échappatoires explicites
comptime(() => ...)exécute du TypeScript au moment de la compilation dans une VM isolée à l'intérieur du compilateur, puis injecte le résultat sous forme de littéral dans le binaire--ffirelie directement des déclarations TypeScript ne contenant qu'une signature à des appels C ABI, et lie les archives, objets et bibliothèques système déclarés dans le manifeste- La frontière est explicite et inclut des informations de longueur
- Le fonctionnement détaillé est décrit dans le guide Native FFI
- Des assertions de type vérifiées comme
JSON.parse(...) as Configinsèrent du code de validation à l'exécution- En cas d'échec de validation, une exception est levée avec le chemin erroné et les types attendu/réel, par exemple
expected number at $.port, got string
- En cas d'échec de validation, une exception est levée avec le chemin erroné et les types attendu/réel, par exemple
Architecture du compilateur
- Le traitement suit l'ordre
TypeScript → parsing et vérification de types par tsc → lowering → IR typé → C → clang → exécutable natif packages/compilerinclut un frontend basé sur l'API tsc, la validation/sérialisation de l'IR, ainsi que les backends LLVM et C- Seul l'IR sert d'interface entre frontend et backend
- LLVM est le générateur de code par défaut, et une voie de repli transparente est utilisée pour les programmes hors périmètre de prise en charge
- C est conservé comme backend de référence permanent, et
--backend cproduit un résultat lisible avec les informations de lignes source
packages/runtimeimplémente des valeurs à comptage de références avec collecteur de cycles, des fibres à pile, une boucle d'événementskqueue, la pile serveur et une sortie numérique compatible JavaScript- Le binaire n'embarque que les fonctionnalités réellement utilisées grâce à un linkage par fonctionnalité
packages/clifournit les commandesscriptc build,scriptc run,scriptc coverage
Installation et développement
- Installation avec
npm install -g scriptc; clang est requis - La plateforme principale est macOS arm64, et les binaires Linux et Windows sont cross-compilés
- Chaque plateforme est validée via un parcours de tests différentiels distinct
- Le développement commence avec
pnpm install && pnpm buildpnpm testexécute l'ensemble des tests différentiels et les snapshots de diagnosticsSCRIPTC_SAN=1 pnpm testexécute les mêmes tests sous ASan et audit du comptage de référencespnpm scriptc build x.ts --emit-irconserve le C généré etx.ir.json
- Chaque fonctionnalité est ajoutée avec des tests différentiels, et une fusion n'est possible que si les tests classiques et de sûreté mémoire passent tous
1 commentaires
Avis sur Hacker News
Vercel semble publier environ une fois par mois un projet qui fait parler de lui, afin de maintenir sa crédibilité et sa visibilité. Je ne pense pas qu’une entreprise ou un projet sérieux utiliserait scriptc.
Respect aux contributeurs, mais le code donne fortement l’impression d’avoir été généré avec Claude, et le fait que Claude ne soit pas indiqué comme contributeur rend la chose encore plus suspecte.
Projet / discussion HN liée
Porffor poursuit le même objectif depuis un moment. Son développeur, CanadaHonk, est extrêmement brillant, mais le projet ne passe encore qu’environ 68 % de Test262.
Sauf si je comprends mal la portée du projet, la vitesse à laquelle Vercel a avancé paraît assez suspecte.
C’est un projet Vercel typique. Publié depuis 5 jours, entièrement fait en vibe coding, 1 500 étoiles sans raison, ne résout le problème de personne, et sera probablement abandonné d’ici quelques mois au mieux.
Plutôt que de me contenter de critiquer, je l’ai essayé sur plusieurs projets locaux, mais tous ont produit des centaines d’erreurs lors de l’analyse de portée du code, ce qui le rendait en pratique inutilisable.
Si l’on écrit tout depuis zéro sans bibliothèques externes, on pourra sans doute compiler en binaire, mais dans ce cas il n’y a aucune raison de ne pas utiliser Rust, Go, Zig, D, C, V, Ada, C++, Nim, Swift, Kotlin Native ou Haskell, des langages conçus dès le départ pour une vraie compilation.
L’intérêt de TypeScript n’est pas seulement son expressivité, mais aussi sa compatibilité avec l’immense écosystème npm. La plupart des packages ne définissent leur interface qu’avec des déclarations de types et distribuent le code réel en JavaScript ; pour les utiliser, il faut donc en pratique un moteur JavaScript.
Si vous partez de zéro et ne comptez utiliser aucun package npm, mieux vaut utiliser AssemblyScript. Node recommande explicitement de ne pas distribuer de packages en TypeScript, car TypeScript n’est pas rétrocompatible même entre versions mineures, et les réglages du compilateur ne sont pas non plus portables d’un package à l’autre.
Un projet comme celui-ci permet de rester en première page de services comme HN. C’est une stratégie de croissance : investir des tokens pour créer un projet séduisant dont personne ne veut vraiment, le publier pour élargir sa portée, puis recommencer.
Dans 12 mois, 90 % des projets open source pourraient être des productions de vibe coding qui ont l’air intéressantes mais n’ont pas de vrais utilisateurs. On peut désormais générer facilement même un compilateur complet, mais l’essentiel reste la maintenance à long terme et la communauté ; un titre accrocheur ne suffit pas à retenir les utilisateurs.
Si Vercel est sérieux, il devrait assumer les coûts et risques réels en l’adoptant comme son runtime expérimental.
C’est un excellent domaine de problème. J’ai appliqué un travail similaire, consistant à créer avec l’IA un compilateur qui optimise du code au runtime, à Zod : zod-compiler.
Il compile les schémas Zod au moment du build en simples chaînes d’opérations booléennes, ce qui les rend 2 à 74 fois plus rapides sans modification du code, et le plugin remplace les appels Zod par du parsing compilé. La plupart des optimisations ont été écrites par Claude au fil de plus de 100 itérations.
Comme avec scriptc, on peut comparer les résultats avec le vrai Zod, ce qui évite de juger subjectivement l’exactitude. Cette approche s’applique aux compilateurs, outils de sérialisation, formatteurs, planificateurs de requêtes, etc., dès lors qu’il existe une implémentation de référence et des benchmarks.
J’ai exécuté avec Claude des benchmarks de scriptc et Node. Même dans le meilleur cas, celui du tableau d’octets, scriptc reste environ 7,5 fois plus lent que Node 24 après optimisation dédiée.
En revanche, le démarrage de l’exécutable est 12 fois plus rapide (1,5 ms contre 18,6 ms), il utilise 72 fois moins de mémoire (2,5 MiB contre 181 MiB), et produit un exécutable unique de 370 Ko sans dépendance runtime.
C’est bien de reconnaître le besoin de petits exécutables natifs rapides, mais l’expérience de plusieurs décennies de Java me rend sceptique quant à la praticité. Dans les années 1990, GCJ était techniquement correct, mais ne bénéficiait pas du soutien de l’écosystème.
Plus tard, GraalVM Native a abordé le problème de manière plus complète, et les principales bibliothèques et frameworks ont commencé à travailler leur compatibilité, mais même aujourd’hui il reste très difficile de faire tourner parfaitement en natif une application existante pourtant simple. Des initiatives comme scriptc sont bienvenues, mais le chemin vers une utilisation pratique semble long et difficile.
L’une des raisons de la disparition d’Excelsior est probablement que GraalVM et OpenJ9 sont disponibles gratuitement. PTC et Aicas fonctionnent encore bien grâce à une clientèle embarquée et temps réel qui attire peu l’attention.
Si le développement se poursuit, cela pourrait devenir une avancée majeure du niveau de .NET AOT. Le projet n’est public que depuis quelques jours, donc pour l’instant on ne peut que l’essayer légèrement, mais s’il n’est pas abandonné et continue de progresser, il pourrait beaucoup aider l’écosystème.
Le code produit par l’IA couvre un spectre de qualité aussi large que le code écrit par des humains. Pour un logiciel important, il faut le générer selon les mêmes critères que si on l’écrivait soi-même et relire tout le code ; utilisé ainsi, c’est une excellente méthode. Les projets avec trop peu de revue risquent d’avoir une qualité plus faible et une responsabilité moindre de la part des développeurs, ce qui les rend plus difficiles à adopter.
Les discussions en ligne dérivent vers deux extrêmes, « tout est généré par IA » et « il ne faut jamais utiliser l’IA », mais dans la réalité, le juste milieu raisonnable consiste à accélérer un jugement réfléchi. Sinon, on hésite à utiliser ces logiciels en raison du risque de mauvaise qualité ou d’abandon.