- La PR Node.js #53725 a été fusionnée, ajoutant un support expérimental pour exécuter des fichiers TypeScript via le flag
--experimental-strip-types
- Cette fonctionnalité supprime la syntaxe de type du code source TypeScript pour le convertir en JavaScript, sans effectuer de vérification de types pendant la conversion
- L’implémentation initiale ne convertit pas les fonctionnalités propres à TypeScript comme
Enum ou namespaces, et se limite donc au code qui devient du JavaScript uniquement par suppression des types
@swc/wasm-typescript a été retenu pour l’implémentation, principalement pour sa simplicité, car il n’est pas nécessaire d’ajouter Rust ou Go à la toolchain Node.js
- Dans les discussions, les principales contraintes évoquées portent sur la règle des extensions de fichier obligatoires en ESM, la pratique courante en TypeScript où un import en
.js pointe en réalité vers un fichier .ts, ainsi que le coût des source maps et du positionnement des erreurs dans les traces de pile
Fonctionnement de --experimental-strip-types
- Avec
--experimental-strip-types, Node.js peut exécuter des fichiers TypeScript
- Node.js transpile le code source TypeScript en code source JavaScript
- Pendant la transpilation, aucune vérification de types n’est effectuée et les informations de type sont supprimées
- Exemple :
const foo: string = "foo";
const foo = "foo";
Pourquoi se limiter à la suppression des types, et les restrictions initiales
- Le cœur de cette PR est la suppression de la syntaxe de type dans TypeScript
- D’autres runtimes convertissent aussi en JavaScript des fonctionnalités propres à TypeScript qui n’existent pas en JavaScript, comme
enum
- L’implémentation initiale de cette PR ne réalise pas ces conversions
Enum non pris en charge
namespaces non pris en charge
- les autres fonctionnalités propres à TypeScript ne sont pas non plus converties
- Les détails d’implémentation et les limitations sont décrits dans
typescript.md parmi les changements de la PR
Choix de @swc/wasm-typescript comme outil d’implémentation
@swc/wasm-typescript est un petit package, composé d’un fichier wasm et d’un fichier JavaScript qui l’expose
- D’autres outils imposaient d’ajouter Rust ou Go à la toolchain Node.js
- SWC est déjà utilisé par Deno pour le même objectif, et l’auteur de la PR y voit une option éprouvée
- À terme, cette fonctionnalité pourrait être implémentée dans une couche native
- Cette PR fait référence à nodejs/loaders#217
Résolution des imports et discussion sur les extensions
- Les imports sans extension comme
import foo from "./foo" ne sont pas pris en charge
- En ESM dans Node.js, la règle des mandatory file extensions s’applique, donc l’absence de support pour les imports sans extension est considérée comme intentionnelle
- Il a été souligné que, dans de nombreuses bases de code TypeScript modulaires, il est courant d’écrire l’extension
.js dans les imports même si le fichier réel est en .ts
- Exemple :
import foo from './foo.js'
- alors que le système de fichiers ne contient pas
./foo.js, mais uniquement ./foo.ts
- L’auteur de la PR estime que ce sujet pourra être traité dans un travail itératif ultérieur, car deviner l’extension demanderait des vérifications supplémentaires du système de fichiers et des heuristiques
- si
./foo.js n’existe pas, vérifier ./foo.ts
- si
./foo.ts n’existe pas, vérifier ./foo.js
- Cette discussion est suivie dans nodejs/loaders#208
Positionnement du code source, source maps et traces de pile d’erreurs
- Lors de la revue, certains ont estimé qu’un positionnement précis dans le code source pour les traces de pile d’erreurs était nécessaire à court terme
- Une approche possible consiste à ne pas modifier les positions dans le code lors de la suppression des types, afin d’éviter la génération et le mapping de source maps
- Il a été noté que les source maps ont un coût en mémoire et en performance, et qu’elles ne sont pas simples à gérer
- L’auteur de la PR indique que SWC prend déjà en charge les source maps, et qu’un flag pourrait être ajouté pour les activer
- Il estime aussi qu’il serait possible de demander à SWC une option remplaçant la syntaxe supprimée par des espaces afin de ne pas modifier les traces de pile d’origine
Questions de conception restantes avant la stabilisation
- Le support de TypeScript se divise en deux axes
- suppression de la syntaxe de type
- ajustement des règles de résolution pour accepter les fichiers
.ts
- Des inquiétudes ont été exprimées quant au fait qu’un changement des règles de résolution puisse entrer en conflit avec les loaders TypeScript existants
- L’auteur de la PR répond que cette PR ne contient aucun override des règles de résolution et que l’extension du fichier reste requise
- Via
allowImportingTsExtensions, il serait possible de prendre en charge la forme import './file.ts', une approche jugée cohérente avec Deno
- Pour exécuter directement un même fichier source tout en produisant aussi une sortie JavaScript avec un outil de build, la responsabilité de transformer
file.ts en file.js pourrait incomber à l’outil de build
Fusion et travaux de suivi
- La PR a été fusionnée dans
nodejs:main le 24 juillet 2024
- L’ampleur du changement est de 8 commits, 89 fichiers, 2 190 lignes ajoutées et 25 lignes supprimées
- Elle porte les labels
semver-minor, experimental, module, esm, notable-change
- Il y a aussi eu une discussion sur l’opportunité de mentionner cette fonctionnalité sur la page d’information TypeScript du site web, avec une PR de mise à jour associée
- Une autre piste de suivi concerne l’ajout, dans les métadonnées de distribution, de la version de TypeScript utilisée par le binaire Node pour la suppression des types
1 commentaires
Avis sur Hacker News
La suppression des types TypeScript est impossible sans la syntaxe TypeScript. La suppression des types n’est pas une opération au niveau des tokens, et la syntaxe TypeScript continue d’évoluer.
Par exemple,
foo < bar & baz > ( x )était analysé comme(foo (x))dans TypeScript 1.5, carbar&bazn’était pas une expression de type valide, mais après l’ajout de l’opérateur de types d’intersection, c’est analysé commefoo<(bar & baz)>(x), et désambiguïsé enfoo(x).Pour continuer à utiliser les fonctionnalités TypeScript récentes, il faut compiler en JS ou maintenir sa version de Node constamment à jour ; pour les équipes qui préfèrent rester sur Node LTS, cela peut être un compromis difficile à accepter.
L’idée serait de garder une version stable dans le cœur, mais de permettre une mise à niveau séparée depuis NPM lorsque TypeScript ajoute de nouvelles fonctionnalités, introduit des changements cassants, ou lorsque l’utilisateur veut utiliser de nouvelles fonctionnalités expérimentales.
Cela permettrait de prendre en charge une version donnée de TypeScript pendant les trois ans de durée de vie d’une version de Node.js, sans bloquer les utilisateurs : https://github.com/nodejs/loaders/issues/217
Le dernier gros changement syntaxique était probablement les types conditionnels de la 2.8, quand une syntaxe ressemblant à un opérateur ternaire
ifa été introduite dans les positions de type. Si l’on veut aussi faire de la vérification de types, le modèle de types a beaucoup changé depuis la 2.x, mais la syntaxe est restée globalement stable, et l’innovation de TypeScript s’est surtout faite du côté du modèle de types et de l’inférence de types.C’est encore à un stade précoce, Stage 1, mais il existe aussi une tentative de standardiser une bonne partie de la syntaxe de types TypeScript, non pas pour la vérification de types, mais pour la suppression des types : https://github.com/tc39/proposal-type-annotations
Les équipes qui ne s’intéressent pas vraiment aux dernières fonctionnalités de V8 ou de Node et restent sur LTS ont de fortes chances d’être également moins intéressées par les toutes dernières fonctionnalités TypeScript.
Si Node.js peut exécuter directement des fichiers TypeScript, le compilateur TypeScript pourrait servir uniquement de vérificateur de types, plutôt que de faire la suppression des types et la transformation en JavaScript. C’est similaire à Python, où le vérificateur de types vérifie les types et les laisse en place, tandis que l’interpréteur ignore les annotations de type.
Fait intéressant, en Python, plusieurs vérificateurs de types populaires sont apparus avec la même syntaxe d’annotations de type, mais des sémantiques différentes. En JavaScript, en revanche, TypeScript semble être devenu de fait le seul vérificateur de types populaire.
J’ai aussi entendu dire qu’en Python, certains ajoutent des types dans le source sans les vérifier, simplement comme une syntaxe de commentaires plus pratique. Si Node.js prend en charge l’ignorance des types, ce genre d’usage deviendra possible aussi en JavaScript.
Personnellement, le plus gros inconvénient de JSDoc était la syntaxe d’import, mais cela a changé depuis TypeScript 5.5 et ce n’est désormais plus un problème.
Cela dit, certains bundlers utilisent déjà des compilateurs non officiels comme SWC pour le faire, et Node expérimente aussi SWC pour cette fonctionnalité.
Les annotations de type ne sont pas de simples commentaires : ce sont aussi des indices, comme les noms, qui permettent aux IDE d’offrir une meilleure autocomplétion.
À mon avis, cela devrait faire partie de la spécification du langage.
Je me demande comment l’écosystème NPM réagira si cette fonctionnalité devient le comportement par défaut, et non plus quelque chose caché derrière un flag. Je ne sais pas si les contributeurs continueront à produire des versions CJS et ESM lorsqu’ils publieront des modules NPM, ou s’ils se contenteront d’ajouter
engine: nodejs >= 25danspackage.jsonen supprimant l’étape de build.Personnellement, si les modules NPM qui ont des sources TS arrêtent de publier des
dist/.cjs, je trouverais ça mieux : il deviendrait plus clair où mettre le débogueur et lesconsole.log. Du point de vue des contributeurs NPM, la tentation de supprimer l’étape de build sera aussi forte.Mais si une seule dépendance publie des fichiers TS, l’idée que « tout le monde reçoit des fichiers TS » pourrait se répandre rapidement. Dès que Node.js sortira cela du flag expérimental opt-in, toute la communauté risque d’attendre implicitement des consommateurs qu’ils acceptent les fichiers TS.
Dans ce cas, d’ici quelques mois, Firefox et Safari subiront aussi une pression pour les accepter, et tous les compilateurs JS seront forcés d’ignorer les annotations de types TS.
Personnellement, ça ne me dérange pas non plus. Côté utilisateurs de TS, on ajoute déjà aux modules NPM une étape de transformation qui convertit le code TS en JS et en
.d.tspour prendre en charge d’hypothétiques utilisateurs JS. Mais si Node accepte les fichiers.ts, on pourra supprimer cette étape de transformation sans s’en rendre compte ; je me demande donc quel mécanisme empêchera les éditeurs NPM d’arrêter de publier des fichiersjs/d.tssans s’apercevoir que c’est cassé.Il permet de publier du code TypeScript sans étape de build, et fait en sorte que, lorsqu’un autre développeur l’installe, il voie le source TypeScript d’origine plutôt que du JavaScript transformé.
.tsdansnode_modulesn’est pas prise en charge. C’est l’une des principales restrictions destinées à éviter de casser l’écosystème..cjset.mjspour npm ; elles finiront probablement par publier les trois versions.Pour un projet écrit from scratch aujourd’hui, il est possible de ne publier que du TypeScript et de ne pas effectuer de transformation.
Modification : d’après un commentaire frère de satanacchio, qui semble être l’auteur de la PR et membre du comité de pilotage technique de Node.js, la prise en charge de TS concerne les éléments de premier niveau et ne s’applique pas aux bibliothèques dans
node_modules..tssur npm, mais l’équipe TypeScript y était très opposée à cause des problèmes liés àtsconfiget aux performances. J’ai quand même bon espoir.Peut-être qu’un jour Node permettra la réflexion à l’exécution de ces types depuis JS.
Ce serait un gain énorme. En Python, comme les types peuvent être introspectés, d’excellents outils comme pydantic peuvent générer des validations à partir des types.
Avec une seule notation standard, on peut obtenir la vérification de types, la validation des données à l’exécution, la génération d’API et la génération de documentation d’API.
Aujourd’hui en JS, des outils comme Zod doivent en pratique réinventer ce que TypeScript fait déjà, avec des choses comme
const mySchema = z.string();.z.string()de Zod ne fournit pas seulement une sécurité de types à la TypeScript. TypeScript fournit une vérification de types à la compilation, tandis que Zod ajoute la validation et le parsing à l’exécution.z.string()transformemySchemaen schéma fonctionnel capable de parser et de valider.mySchema.parse("some data")réussit, tandis quemySchema.parse(321)lève une exception.Je l’ai utilisé là où il fallait de la validation à l’exécution et des vérifications dans le processus ; ça fonctionnait assez bien, et on peut aussi en extraire le type, comme dans
const A = z.string(); type A = z.infer; // string.Autrement dit, si l’on définit d’abord le type dans Zod puis qu’on en infère le type, on obtient à la fois une vérification de types à la compilation et à l’exécution. Cela peut être excessif pour une petite base de code qui évolue vite, mais c’est très utile dans les situations où il faut valider dans le processus, et la charge de travail n’est pas énorme.
Cela dit, TypeScript autorise les plugins. Ils ne sont pas très utilisés, mais ils permettent d’ajouter au langage d’autres fonctionnalités qui sont transformées en JavaScript de sortie.
Pour les types à l’exécution, il existe un plugin appelé Typia, qui permet d’utiliser les signatures de types TypeScript à l’exécution dans des gardes comme
assert(myValue). Il intercepte cet appel de fonction et génère, dans le JavaScript transformé, desifexhaustifs qui vérifient la nature de la variable transmise.Je ne pense pas que cela fera partie du langage dans les 4 à 6 prochaines années, mais il existe déjà des bibliothèques qui permettent de faire ce genre de choses aujourd’hui.
Il faut en réalité un système de types sound, mais TS est intentionnellement non sound. Il faut un système de types qui n’autorise pas les mauvaises pratiques de codage comme TS, et qui impose une conception permettant d’obtenir des programmes rapides. Il faut un système de types Hindley-Milner.
Si l’on veut transformer un module en code typé, il suffirait d’ajouter
"use type". Ce mode devrait interdire les mauvaises parties comme les coercitions de types, et bloquer les éléments qui nuisent aux performances, comme changer la forme des objets ou le type des valeurs, ou créer des tableaux mélangeant n’importe quelles valeurs.Les données provenant de modules non typés seraient converties, ou déclencheraient une erreur si elles ne peuvent pas l’être ; le compilateur pourrait alors optimiser en profondeur le code typé avec des garanties de types plus fortes et sans risque de bail-out.
ValueError, ou si un objet est une instance deSomeClass. Je débute avec TS, donc je pensais qu’en dehors des tableaux, objets, nombres et chaînes, il n’y avait pas de types en JS, et que Zod ou les fonctions de garde de types renvoyaient simplement des objets ordinaires qu’il faut croire sur parole.L’expérience développeur de Bun est assez inédite dans ce domaine, et désormais la plupart des cas d’usage sont couverts, ou bien Bun ne plante pas lorsqu’on écrit des scripts d’exécution avec
bun runÀ l’inverse, avec Node, on ne peut ni le configurer pour ne pas exiger les extensions dans les imports, ni faire en sorte que
tscajoute automatiquement l’extension.jsau résultat compilé, ce qui oblige à ajouter un bundlerLa prise en charge native de TypeScript corrigera sans doute assez bien ce petit désagrément, mais même une fois stabilisée, il est difficile d’imaginer que l’expérience utilisateur ou les performances rivalisent avec Bun
L’expérience développeur et les outils sont également de tout premier ordre. Deno peut aussi faire de la vérification de types et, si je me souviens bien, embarque TSC
Bun fait beaucoup parler de lui, mais pour du travail sérieux, Deno est aujourd’hui clairement une meilleure option. Cela dit, la vision et l’exécution de Bun sont impressionnantes, et c’est une bonne chose pour les développeurs
bun installéchouait, donc je n’ai jamais réussi à le faire fonctionner correctement : https://github.com/search?q=repo%3Aoven-sh%2Fbun+bun+install...Cela fait longtemps que j’attends un runtime TypeScript, et j’aime vraiment TypeScript, mais c’est amusant d’avoir quitté Java il y a longtemps pour finalement chercher quelque chose de beaucoup plus proche de Java
Au fond, ce qu’on voulait, c’était sans doute Java avec un JIT, un système de types plus riche et du typage progressif. L’écosystème npm a aussi beaucoup de défauts, mais utiliser des bibliothèques dans cet écosystème est bien moins contraignant et plus agréable
Étonnamment, Rust se trouve à l’autre extrémité du spectre des langages, mais donne parfois une impression similaire
Modification : le terme JIT n’était pas approprié. Je l’ai écrit par paresse, mais je voulais parler des différences de temps de démarrage et de runtime quand on exécute quelque chose sur la JVM et sur V8. Java donne une impression de lourdeur, tandis que l’écosystème JavaScript paraît beaucoup plus agile
Je ne vois pas non plus du tout en quoi TypeScript serait plus proche de Java. Les deux ne partagent guère que le mauvais nom « Javascript » et la prise en charge de la programmation impérative ; pour le reste, ils me semblent très différents
En revanche, ce que Java a et qui me manque dans TypeScript, c’est la réflexion de types à l’exécution. L’écosystème TypeScript propose d’innombrables contournements, mais je les trouve tous un peu disgracieux
L’AoT me conviendrait aussi, et une option de binaire unique serait appréciable. Idéalement, il n’y aurait pas de runtime et une élimination intelligente serait possible. Si ça pouvait aussi tourner avec un JIT dans le navigateur, ce serait encore mieux
Je voudrais aussi des facilités côté compilateur et vérificateur de types, comme le pattern matching exhaustif
Des fonctionnalités de Deno que j’aime arrivent directement dans Node. Excellent
Peut-être que je n’aurai plus toujours besoin d’installer esbuild juste pour supprimer les types. J’ai hâte, car écrire et utiliser des scripts en TypeScript devrait devenir beaucoup plus simple
Ces derniers temps, je préférais Python pour les scripts ponctuels, mais côté types, je trouve personnellement TypeScript meilleur que Python. Les gros scripts qu’on rouvre quelques mois plus tard profitent particulièrement des types
ts-node, mais personnellement je préfèretsxhttps://github.com/privatenumber/tsx
Ce mois-ci a vraiment été riche en événements pour Node. D’abord node:sqlite a été ajouté dans la v22.5.0, et maintenant la prise en charge de TypeScript arrive aussi. J’aime la direction que prend Node
Je suis l’auteur de la PR. Vous pouvez me poser n’importe quelle question
J’ai commencé il y a longtemps à utiliser Node.js pour du backend, et il me semblait qu’il offrait beaucoup d’avantages par rapport à l’écriture de code en PHP, sans reprendre beaucoup des problèmes de Java
Mais Node était assez rudimentaire, et donnait l’impression d’être un langage auquel il fallait greffer diverses choses pour obtenir le langage que l’on voulait. J’ai fini par commencer à utiliser Go, qui était beaucoup plus facile à utiliser et, même s’il est parfois bien plus verbeux, la sûreté de typage rendait le codage plus simple
TypeScript semblait être une bonne option, mais au final c’était encore une couche ajoutée. Je ne vois pas bien quelle valeur on gagne à utiliser TypeScript plutôt que Go. Avoir des types explicites, c’est bien, mais cela ne résout pas les autres problèmes du langage que Go a résolus. Deno en résout aussi une partie
Le gros avantage de Node par rapport à Go, c’est qu’on peut prototyper rapidement, mais avec TypeScript cet avantage semble nettement réduit. Il est donc difficile de dire si c’est une bonne avancée, ou si cela fait perdre à Node les caractéristiques qui en faisaient une bonne option
À voir l’expression « couche ajoutée », le problème vient peut-être moins du langage lui-même que de l’absence d’un framework opinionated qui gère tout par défaut, comme Django. J’aime utiliser Django, mais il est un peu plus difficile de sortir des sentiers battus
cdktf; cela nous a paru un choix plus logique que Go