- Une liste qui défend la nécessité pour TypeScript d’émettre des informations de type à l’exécution, et regroupe les contournements de ce problème comme le Type Mapping, la génération de code / les outils externes et les projets d’adaptation
- Le problème central est que, sans système de types réflexif, la sérialisation et la validation exigent soit une quantité infinie de boilerplate, soit une génération de code sur mesure fondée sur des fichiers de schéma
- Des solutions de contournement comme io-ts et zod sont proposées, mais elles imposent de redéclarer les types selon l’approche propre à chaque bibliothèque, et ces bibliothèques ne prennent pas en charge toutes les fonctionnalités de type de TypeScript
- L’effacement des types de TypeScript est reconnu comme un avantage, car il permet aux projets JavaScript d’utiliser le JavaScript émis sans connaissance de TypeScript, mais l’auteur soutient qu’il serait possible d’émettre les informations de type sous la forme d’une table de correspondance séparée du code
- Avec la demande de ne pas résoudre cela via des décorateurs, le texte propose des approches comme une fonction d’ordre supérieur reconnue par le compilateur telle que
typescript.generateRuntimeType<T>(), ainsi que des méthodes comparables aux F# Type Providers et aux C# Source Generators, afin de permettre l’usage des interfaces et la prise en charge des types de bibliothèques externes - Il renvoie à une issue GitHub vieille de 8 ans parmi les discussions existantes sur le sujet, et demande que les projets ayant rencontré le même problème envoient une PR pour être ajoutés à la liste, quel que soit le nombre d’étoiles
1 commentaires
Avis de Hacker News
Il m’a fallu lire l’énoncé du problème environ 4 fois pour comprendre ce qui était demandé, et ce genre de cas montre bien pourquoi écrire de façon concise est important
La demande réelle semble être la sûreté de typage à l’exécution, mais TypeScript a toujours tracé une ligne claire en disant qu’il ne deviendrait pas un runtime de remplacement/complémentaire à JavaScript, donc cela paraît peu probable. Le rôle de TypeScript est de compiler vers JS puis de s’effacer ; ce qui se passe ensuite dans V8, etc., est hors périmètre.
Demander que « TypeScript émette les informations de type à l’exécution » revient presque à demander un nouveau produit assez différent de TypeScript tel qu’il existe aujourd’hui
Par exemple, l’idée serait de pouvoir créer une fonction générique
validatequi accepte une interface et un objet arbitraires et les valide. Avec de la réflexion à la compilation, on pourrait générer du code JS de validation propre à chaque type, ou passerTcomme argument à l’exécution pour comparer, mais cela me semble difficile dans la philosophie actuelle de TypeScriptTypeScript connaît beaucoup d’informations de type pendant la compilation, puis les jette une fois celle-ci terminée. Il pourrait exporter ces informations dans un fichier ou les laisser sous forme de métadonnées dans
Reflect, et les jeter reste une limitation regrettable, surtout quand on pense au caractère « tout est objet » de JavaScript.Même sans obtenir une sûreté de typage TS exacte, on pourrait ajouter certaines vérifications avec des getter/setter ou des
ProxyES2015 ; et conserver les informations de type à l’exécution ouvrirait plusieurs possibilités intéressantes sous forme de métadonnées exécutablesLes
enumTS sont déjà émis sous forme d’objets JS et peuvent être consultés à l’exécution, contrairement aux types union de littéraux de chaînes. TypeScript prend aussi en charge les fonctions de garde de type ; générer automatiquement ce genre de fonctions à partir des informations du système de types ne semble donc pas particulièrement difficileOn peut penser à une analogie avec les fichiers PDB. Comme ce sont des informations que TypeScript possède déjà puis jette, cela ne nécessite pas un produit entièrement nouveau
https://github.com/microsoft/TypeScript/issues/3628
Du point de vue d’un PM TypeScript, je comprends le besoin. La validation de données nécessite souvent des vérifications de type à l’exécution, et il existe de nombreuses bibliothèques pour combler ce manque
Cela dit, le simple fait qu’il existe beaucoup de bibliothèques avec des choix de conception différents indique que ce n’est pas un problème résolu avec une réponse évidente. On en était conscient dès la conception initiale de TypeScript, et je pense que ce principe a bien tenu.
À la place, TypeScript est devenu assez puissant pour exprimer précisément, au niveau des types, ce que font réellement les bibliothèques de vérification de types à l’exécution, et les utilisateurs peuvent composer la logique de validation runtime à partir des types via des API. Cela me paraît être un niveau de flexibilité raisonnable
Par exemple, j’imagine un langage où l’on pourrait définir comme type un nombre compris dans une certaine plage ou une chaîne correspondant à un motif de code postal, et où le compilateur vérifierait la validité à l’aide de fonctions de validation écrites comme des fonctions ordinaires. Je me demande si l’absence de ce genre de fonctionnalité est un choix de conception visant à éviter une complexité inutile, ou si elle vient de contraintes techniques comme les performances
Aujourd’hui, ceux qui en ont besoin peuvent déjà écrire un préprocesseur qui génère des objets runtime à partir des informations de type avant de les passer à
tsc, mais les efforts sont dispersés. Un préprocesseur enfichable officiel et un écosystème autour pourraient permettre de trouver de bonnes solutionsIl suffirait d’allumer l’étincelle pour que la communauté aide à la maintenance, et cela couvrirait divers besoins de génération de code, de la génération de clients aux assertions de type à l’exécution
ClassIl y a une raison pour ne pas faire ça. TypeScript deviendrait une sorte de runtime au-dessus de JavaScript, et se transformerait en nouveau langage compilé vers JS
Aujourd’hui, TypeScript est plutôt proche de JavaScript avec des annotations de type. Il existe déjà beaucoup de langages compilés vers JS, donc on peut en utiliser un parmi eux. Les personnes qui veulent un TypeScript avec des types à l’exécution semblent vouloir écrire du code davantage dans le style Java/OOP que JavaScript, mais JavaScript est un langage à typage dynamique, et c’est aussi l’un de ses avantages
generateTypeInfo!()s’étend en un objet JS encodant le typeFoo, cela se compile toujours en JavaScript lisibleMais cette macro brise la propriété selon laquelle « TS = JS avec annotations de type, et compiler consiste seulement à supprimer les annotations ». Il faut en effet calculer réellement le type structurel de
Foo.De plus, comme les types TypeScript sont structurels, inspecter la structure d’un objet à l’exécution permet de connaître son type dans une certaine mesure, mais dès qu’on exige des informations nominales effacées, comme l’appartenance à une union de chaînes, ou un nom de type, cela devient vite un problème difficile à cause de la complétude de Turing du système de types TypeScript et des transformations structurelles implicites
Plus sérieusement, même sans runtime, exposer les types comme des données permet d’obtenir de la réflexion. Quand on voit le nombre de développeurs qui s’efforcent de l’imiter, ne pas le faire paraît plutôt absurde
Les types à l’exécution résolvent le problème consistant à maintenir séparément et en double un système de types pour la compilation et un système de types pour la validation des données, et cela ne semble pas directement lié à l’OOP. Même
io-ts, la bibliothèque souvent préférée pour contourner ce problème, s’appuie fortement sur la programmation fonctionnelleNaNDans les workflows que j’ai vus, TypeScript est déjà un langage compilé vers JS ; autant exploiter cet avantage au maximum
C’est plus proche de « annotations de type + Babel », et y ajouter une fonctionnalité très utile serait une bonne idée. Le fait de ne pas pouvoir transformer en toute sécurité une chaîne passée à
JSON.parseen structure typée est étrange, alors que la plupart des autres langages le permettentAvant ce qui suit, c’est un sujet valide qui mérite clairement discussion, et je ne pense pas qu’il existe objectivement une seule bonne réponse
TypeScript est une couche optionnelle au-dessus de JavaScript, à l’exception de
Enumqui émet un objet, et du code TS devient du JS si l’on efface seulement les types. Tant qu’on ne s’écarte pas beaucoup de ce principe, le vrai besoin ressemble plutôt à une bibliothèque qui génère des sérialiseurs/validateurs à partir des types. Il en existe déjà beaucoup, et cela ressemble finalement à une demande visant à faire de l’une d’elles l’option standard officielle.Personnellement, je ne veux pas voir de réflexion à l’exécution entrer dans le langage cœur de TypeScript. J’attache de la valeur au fait qu’à l’exécution il n’y ait que du JavaScript, et que le JS produit reste lisible et débogable même sans sourcemaps
Enum, il existe d’autres exceptions qui émettent du code à l’exécution, et la plupart sont également considérées comme des erreurs. Les exemples typiques étaientmoduleetnamespace, qui étaient des structures à l’exécutionCela dit, ces dernières années, ils étaient surtout utilisés dans TypeScript lui-même, et TypeScript s’en est récemment éloigné. Il existe aussi une exception assez populaire : les propriétés de paramètres, une syntaxe qui définit des membres de classe typés depuis les paramètres du constructeur, et qui semble avoir échappé à trop de controverses parce qu’elle réduit le boilerplate redondant
Plutôt que de supplier les dieux de TypeScript, je me dis qu’il vaudrait mieux négocier côté JavaScript pour intégrer la vérification de types dans JS
Si l’on a besoin de types à l’exécution, on utilise des type guards ; si l’on en a souvent besoin, on utilise
io-tsouzodpour écrire les types sous forme de validateurs/codecs/schémas. Tant qu’il n’existe pas de méthode consensuelle en JavaScript, je ne pense pas que la spec TS, le vérificateur de types et la communauté doivent prendre en charge la validation à l’exécutionzod, puis à en dériver les types. J’aime le fait que la validation évolue avec les changements de typesCertains estiment que ce genre de fonctionnalité devrait entrer dans le langage, mais comme chaque bibliothèque comporte de nombreux choix de conception discutables, il peut être préférable de choisir une implémentation adaptée aux besoins du projet.
Cela dit, des patterns comme la validation à l’exécution de
zodet la dérivation de types à partir de schémas ne s’accordent pas toujours bien avec le pattern matching dets-pattern. Les définitions de types de ces bibliothèques sont labyrinthiques, et il arrive que du code qui semble devoir marcher ne se comporte pas comme prévu. Ce serait vraiment bien si la sûreté à l’exécution et le pattern matching avec vérification d’exhaustivité pouvaient se combiner harmonieusementPromise, les décorateurs et le nouvel opérateur pipe me donnent tous l’impression que leur implémentation est partie dans la mauvaise directionJe me souviens qu’un des développeurs de TypeScript a dit que, s’ils recommençaient, ils n’auraient pas ajouté
enum, parce que c’est la seule fonctionnalité qui émet du code à l’exécutionTypeScript ne modifie pas le comportement à l’exécution, et il n’y a rien de spécial propre à TypeScript comme un
{#if}Cela dit, TypeScript possède quelques fonctionnalités qui vont au-delà de « JavaScript + annotations de type » :
namespace, la syntaxe qui définit des propriétés de classe depuis les arguments du constructeur, les anciens décorateurs expérimentaux, ou encore la syntaxe du paramètrethis, qui disparaît à la compilation mais ressemble à plus qu’une simple annotation de type sur une fonction JSUn meilleur titre serait plutôt « TypeScript, fournissez de la réflexion/des types à l’exécution »
La meilleure solution actuelle semble probablement être
emitDecoratorMetadata. https://www.typescriptlang.org/tsconfig#emitDecoratorMetadataL’auteur semble avoir mal compris les objectifs de conception de TypeScript. Le but n’est pas de produire une sortie JS propre et sans complexité, mais de faire en sorte que la sémantique à l’exécution de TypeScript reste identique à celle de JavaScript.
À l’exception regrettable de
enum, TypeScript devient du JavaScript par simple suppression des annotations de type. L’écosystème autour de TypeScript repose sur l’effacement complet des types, et si cette demande était acceptée, la prise en charge de TS par ESBuild, Deno et Bun pourrait devenir quasiment impossible. Chacun devrait en effet réimplémenter l’intégralité detscdans son propre langage.À l’inverse, les bibliothèques complexes dont se plaint l’OP sont implémentées dans l’espace utilisateur, elles restent donc compatibles avec ces outils.
keyofen liste de clés de classe, ou faire en sorte que les classes TS initialisent les clés àundefinedcomme les classes ES6.Aujourd’hui, les classes TypeScript suppriment toutes les clés qui ne sont pas explicitement définies, si bien qu’appeler
Object.keys()sur une nouvelle instance ne renvoie rien. Le simple fait de transformer les classes TS comme des classes ES6 via une option detsconfig.json, ou de permettre aux classes ES6 de coexister dans TS, rendrait la génération de code bien plus facile.De même, ce serait excellent si une syntaxe statique comme
tstypeof Foo::barétait compilée en"string". Même un RTTI de base pourrait faire disparaître immédiatement beaucoup de code TypeScript boilerplate et brouillon.Cet article fait plaisir à voir. J’ai commencé TypeScript en 2018 et, au bout d’environ deux ans, j’ai commencé à croire que ce n’était pas, par nature, une solution complète.
Je venais de Haskell/C#/F#, et même avec un système de types puissant, TS n’apporte pas beaucoup des avantages de ces langages, sauf en partie pendant le développement. Quand il s’agit de gérer le monde réel, il est en pratique bien plus limité que C#. Si l’on oublie qu’après compilation on retombe sur du JS sans vérification, il faut garder constamment à l’esprit cette abstraction qui fuit pour éviter des bugs qui devraient être impossibles dans un outil se présentant comme statiquement typé.
Si l’on doit développer en .NET, on utilise C# ; si l’on doit développer pour le Web, on utilise TypeScript. C# a un typage nominal et des génériques réifiés plutôt corrects ; TypeScript a un typage structurel et, en renonçant à la sûreté, dispose d’un système de types puissant pour exprimer des relations de types complexes.
Je doute qu’un langage qui combinerait les deux soit une bonne idée ; les deux langages fonctionnent bien dans des directions différentes, mais ces directions ne s’accordent pas vraiment.
Pour la validation de données à l’exécution, j’ai utilisé https://zod.dev et j’en ai été pleinement satisfait.
Le fait de pouvoir exprimer son intention directement sur place avec une API fluide, sans définir séparément des types nominaux distincts, était plutôt agréable.
Exemple simple : dans un domaine, ils sont nécessaires pour distinguer le nombre
2du montant monétaire2.