Typescript: Soutenir des projets de « taille moyenne »

Créé le 10 juin 2015  ·  147Commentaires  ·  Source: microsoft/TypeScript

L'écoute de @nycdotnet m'a

La proposition ici a d'abord commencé à l'époque préhistorique (même avant le 11), lorsque les dinosaures parcouraient la terre brûlée. Bien que rien dans cette proposition ne soit nouveau en soi, je pense qu'il est grand temps que nous nous attaquions au problème. La propre proposition de Steve est #3394.

Le problème

Actuellement, dans TypeScript, il est plutôt facile de démarrer et de démarrer, et nous le facilitons chaque jour (avec l'aide de choses comme #2338 et le travail sur System.js). C'est merveilleux. Mais il y a un petit obstacle à mesure que la taille du projet augmente. Nous avons actuellement un modèle mental qui ressemble à ceci :

  • Projets de petite taille : utilisez tsconfig.json, gardez la plupart de vos sources dans le répertoire courant
  • Projets de grande taille : utilisez des versions personnalisées, placez la source là où vous en avez besoin

Pour les projets de petite taille, tsconfig.json vous offre un moyen facile à configurer pour démarrer avec l'un des éditeurs de manière multiplateforme. Pour les projets à grande échelle, vous finirez probablement par passer à des systèmes de construction en raison des exigences variées des projets à grande échelle, et le résultat final sera quelque chose qui fonctionnera pour vos scénarios mais qui sera difficile à outiller car il est beaucoup trop difficile à outiller la variété des systèmes de construction et des options.

Steve, dans son interview, souligne que ce n'est pas tout à fait le bon modèle du monde, et j'ai tendance à être d'accord avec lui. Au lieu de cela, il existe trois tailles de projet :

  • Projets de petite taille : utilisez tsconfig.json, gardez la plupart de vos sources dans le répertoire courant
  • Projets de taille moyenne : ceux avec des versions standard et des composants partagés
  • Projets de grande taille : utilisez des versions personnalisées, placez la source là où vous en avez besoin

À mesure que vous augmentez la taille du projet, selon Steve, vous devez pouvoir évoluer à travers l'étape moyenne, sinon la prise en charge des outils diminue trop rapidement.

Proposition

Pour y remédier, je propose que nous accompagnions des projets « de taille moyenne ». Ces projets ont des étapes de construction standard qui pourraient être décrites dans tsconfig.json aujourd'hui, à l'exception du fait que le projet est construit à partir de plusieurs composants. L'hypothèse ici est qu'il existe un certain nombre de projets à ce niveau qui pourraient être bien servis par ce soutien.

Buts

Fournissez une expérience facile à utiliser pour les développeurs créant des projets "de taille moyenne" à la fois pour la compilation en ligne de commande et lors de l'utilisation de ces projets dans un IDE.

Non-buts

Cette proposition n'inclut _pas_ de compilation facultative, ni aucune étape en dehors de ce que le compilateur gère aujourd'hui. Cette proposition ne couvre pas non plus le groupage ou l'emballage, qui seront traités dans une proposition distincte. Bref, comme mentionné dans le nom, cette proposition ne couvre que les projets de « taille moyenne » et non les besoins de ceux à grande échelle.

Concevoir

Pour prendre en charge des projets de taille moyenne, nous nous concentrons sur le cas d'utilisation d'un tsconfig.json référençant un autre.

Exemple de tsconfig.json d'aujourd'hui :

{
    "compilerOptions": {
        "module": "commonjs",
        "noImplicitAny": true,
        "sourceMap": true
    },
    "files": [
        "core.ts",
        "sys.ts"
    ]
}

Section "dépendances" de tsconfig.json proposée :

{
    "compilerOptions": {
        "module": "commonjs",
        "noImplicitAny": true,
        "sourceMap": true
    },
    "dependencies": [
        "../common", 
        "../util"
    ],
    "files": [
        "core.ts",
        "sys.ts"
    ]
}

Les dépendances pointent vers :

  • Un répertoire, où un tsconfig.json peut être trouvé
  • Un tsconfig.json directement

Les dépendances sont hiérarchiques. Pour éditer le projet complet, vous devez ouvrir le bon répertoire qui contient la racine tsconfig.json. Cela implique que les dépendances ne peuvent pas être cycliques. Bien qu'il soit possible de gérer les dépendances cycliques dans certains cas, dans d'autres cas, à savoir ceux avec des types qui ont des dépendances circulaires, il peut ne pas être possible de faire une résolution complète.

Comment ça fonctionne

Les dépendances sont construites en premier, dans l'ordre dans lequel elles sont répertoriées dans la section « dépendances ». Si la génération d'une dépendance échoue, le compilateur se fermera avec une erreur et ne continuera pas à générer le reste du projet.

Au fur et à mesure que chaque dépendance se termine, un fichier « .d.ts » représentant les sorties est mis à la disposition de la version actuelle. Une fois toutes les dépendances terminées, le projet en cours est construit.

Si l'utilisateur spécifie un sous-répertoire en tant que dépendance, et implique également sa compilation en ne fournissant pas de section 'files', la dépendance est compilée lors de la compilation des dépendances et est également supprimée de la compilation du projet en cours.

Le service de langue peut voir dans chaque dépendance. Étant donné que chaque dépendance sera chassée de son propre tsconfig.json, cela peut signifier que plusieurs instances de service linguistiques devront être créées. Le résultat final serait un service linguistique coordonné capable de refactoriser, de naviguer dans le code, de trouver toutes les références, etc. à travers les dépendances.

Limites

L'ajout d'un répertoire en tant que dépendance qui n'a pas de tsconfig.json est considéré comme une erreur.

Les sorties des dépendances sont supposées être autonomes et séparées du projet en cours. Cela implique que vous ne pouvez pas concaténer la sortie .js d'une dépendance avec le projet en cours via tsconfig.json. Des outils externes, bien sûr, peuvent fournir cette fonctionnalité.

Comme mentionné précédemment, les dépendances circulaires sont considérées comme une erreur. Dans le cas simple :

UN B
\ C

A est le 'projet courant' et dépend de deux dépendances : B et C. Si B et C n'ont pas eux-mêmes de dépendances, ce cas est trivial. Si C dépend de B, B est mis à disposition de C. Ceci n'est pas considéré comme circulaire. Si, cependant, B dépend de A, cela est considéré comme circulaire et serait une erreur.

Si, dans l'exemple, B dépend de C et que C est autonome, cela ne serait pas considéré comme un cycle. Dans ce cas, l'ordre de compilation serait C, B, A, ce qui suit la logique que nous avons pour ///ref.

Optimisations/améliorations facultatives

Si une dépendance ne doit pas être reconstruite, son étape de génération est ignorée et la représentation '.d.ts' de la génération précédente est réutilisée. Cela pourrait être étendu pour gérer si la compilation d'une dépendance a construit des dépendances qui apparaîtront plus tard dans la liste des «dépendances» du projet en cours (comme cela s'est produit dans l'exemple donné dans la section Limitations).

Plutôt que de traiter les répertoires transmis comme des dépendances qui n'ont pas de tsconfig.json comme des cas d'erreur, nous pourrions éventuellement appliquer des « fichiers » par défaut et les paramètres du projet en cours à cette dépendance.

Committed Monorepos & Cross-Project References Suggestion

Commentaire le plus utile

Documents/poste de blog en cours ci-dessous (le modifiera en fonction des commentaires)

J'encourage tous ceux qui suivent ce fil à essayer. Je travaille maintenant sur le scénario monorepo pour résoudre les derniers bugs/fonctionnalités et je devrais bientôt avoir des conseils à ce sujet


Références du projet

Les références de projet sont une nouvelle fonctionnalité de TypeScript 3.0 qui vous permettent de structurer vos programmes TypeScript en morceaux plus petits.

En faisant cela, vous pouvez considérablement améliorer les temps de génération, appliquer une séparation logique entre les composants et organiser votre code de manière nouvelle et meilleure.

Nous introduisons également un nouveau mode pour tsc , le drapeau --build , qui fonctionne main dans la main avec les références de projet pour permettre des compilations TypeScript plus rapides.

Un exemple de projet

Regardons un programme assez normal et voyons comment les références de projet peuvent nous aider à mieux l'organiser.
Imaginez que vous ayez un projet avec deux modules, converter et units , et un fichier de test correspondant pour chacun :

/src/converter.ts
/src/units.ts
/test/converter-tests.ts
/test/units-tests.ts
/tsconfig.json

Les fichiers de test importent les fichiers d'implémentation et effectuent des tests :

// converter-tests.ts
import * as converter from "../converter";

assert.areEqual(converter.celsiusToFahrenheit(0), 32);

Auparavant, cette structure était plutôt difficile à utiliser si vous utilisiez un seul fichier tsconfig :

  • Il était possible pour les fichiers d'implémentation d'importer les fichiers de test
  • Il n'était pas possible de compiler test et src en même temps sans que src apparaisse dans le nom du dossier de sortie, ce que vous ne voulez probablement pas
  • Changer uniquement les éléments internes dans les fichiers d'implémentation nécessitait une nouvelle vérification du
  • Changer uniquement les tests nécessitait une nouvelle vérification de l'implémentation, même si rien n'a changé

Vous pouvez utiliser plusieurs fichiers tsconfig pour résoudre certains de ces problèmes, mais de nouveaux apparaissent :

  • Il n'y a pas de vérification de mise à jour intégrée, vous finissez donc toujours par exécuter tsc deux fois
  • L'appel de tsc deux fois augmente le temps de démarrage
  • tsc -w ne peut pas s'exécuter sur plusieurs fichiers de configuration à la fois

Les références de projet peuvent résoudre tous ces problèmes et plus encore.

Qu'est-ce qu'une référence de projet ?

tsconfig.json fichiers references . C'est un tableau d'objets qui spécifie les projets à référencer :

{
    "compilerOptions": {
        // The usual
    },
    "references": [
        { "path": "../src" }
    ]
}

La propriété path de chaque référence peut pointer vers un répertoire contenant un fichier tsconfig.json , ou vers le fichier de configuration lui-même (qui peut avoir n'importe quel nom).

Lorsque vous référencez un projet, de nouvelles choses se produisent :

  • L'importation de modules à partir d'un projet référencé chargera à la place son fichier de déclaration de sortie ( .d.ts )
  • Si le projet référencé produit un outFile , les déclarations du fichier de sortie .d.ts seront visibles dans ce projet
  • Le mode build (voir ci-dessous) construira automatiquement le projet référencé si nécessaire

En vous séparant en plusieurs projets, vous pouvez considérablement améliorer la vitesse de vérification des types et de compilation, réduire l'utilisation de la mémoire lors de l'utilisation d'un éditeur et améliorer l'application des regroupements logiques de votre programme.

composite

Les projets référencés doivent avoir le nouveau paramètre composite activé.
Ce paramètre est nécessaire pour garantir que TypeScript peut déterminer rapidement où trouver les sorties du projet référencé.
L'activation du drapeau composite change plusieurs choses :

  • Le paramètre rootDir , s'il n'est pas défini explicitement, prend par défaut le répertoire contenant le fichier tsconfig
  • Tous les fichiers d'implémentation doivent correspondre à un modèle include ou être répertoriés dans le tableau files . Si cette contrainte est violée, tsc vous indiquera quels fichiers n'ont pas été spécifiés
  • declaration doit être activé

declarationMaps

Nous avons également ajouté la prise en charge des cartes de source de déclaration .
Si vous activez --declarationMap , vous pourrez utiliser les fonctionnalités de l'éditeur telles que "Aller à la définition" et Renommer pour naviguer et éditer de manière transparente le code à travers les limites du projet dans les éditeurs pris en charge.

prepend avec outFile

Vous pouvez également activer l'ajout de la sortie d'une dépendance à l'aide de l'option prepend dans une référence :

   "references": [
       { "path": "../utils", "prepend": true }
   ]

L'ajout d'un projet inclura la sortie du projet au-dessus de la sortie du projet en cours.
Cela fonctionne à la fois pour les fichiers .js et pour les fichiers .d.ts , et les fichiers de carte source seront également émis correctement.

tsc n'utilisera jamais que les fichiers existants sur le disque pour effectuer ce processus, il est donc possible de créer un projet où un fichier de sortie correct ne peut pas être généré car la sortie de certains projets serait présente plus d'une fois dans le fichier résultant .
Par exemple:

  ^ ^ 
 /   \
B     C
 ^   ^
  \ /
   D

Il est important dans cette situation de ne pas ajouter de préfixe à chaque référence, car vous vous retrouverez avec deux copies de A dans la sortie de D - cela peut conduire à des résultats inattendus.

Mises en garde pour les références de projet

Les références de projet comportent quelques compromis dont vous devez être conscient.

Étant donné que les projets dépendants utilisent des fichiers .d.ts qui sont créés à partir de leurs dépendances, vous devrez soit archiver certaines sorties de génération, soit générer un projet après l'avoir cloné avant de pouvoir naviguer dans le projet dans un éditeur sans voir de faux les erreurs.
Nous travaillons sur un processus de génération de .d.ts en coulisses qui devrait être en mesure d'atténuer cela, mais pour l'instant, nous recommandons d'informer les développeurs qu'ils doivent compiler après le clonage.

De plus, pour préserver la compatibilité avec les workflows de build existants, tsc ne construira --build .
Apprenons-en plus sur --build .

Mode de construction pour TypeScript

Une fonctionnalité attendue depuis longtemps est la construction incrémentielle intelligente pour les projets TypeScript.
Dans la version 3.0, vous pouvez utiliser le drapeau --build avec tsc .
C'est effectivement un nouveau point d'entrée pour tsc qui se comporte plus comme un orchestrateur de build qu'un simple compilateur.

L'exécution de tsc --build ( tsc -b en abrégé) effectuera les actions suivantes :

  • Retrouvez tous les projets référencés
  • Détecter s'ils sont à jour
  • Construire des projets obsolètes dans le bon ordre

Vous pouvez fournir tsc -b avec plusieurs chemins de fichiers de configuration (par exemple tsc -b src test ).
Tout comme tsc -p , il n'est pas nécessaire de spécifier le nom du fichier de configuration lui-même s'il s'appelle tsconfig.json .

tsc -b Ligne de commande

Vous pouvez spécifier n'importe quel nombre de fichiers de configuration :

 > tsc -b                                # Build the tsconfig.json in the current directory
 > tsc -b src                            # Build src/tsconfig.json
 > tsc -b foo/release.tsconfig.json bar  # Build foo/release.tsconfig.json and bar/tsconfig.json

Ne vous inquiétez pas de l'ordre des fichiers que vous transmettez sur la ligne de commande - tsc les réorganisera si nécessaire afin que les dépendances soient toujours construites en premier.

Il existe également des drapeaux spécifiques à tsc -b :

  • --verbose : imprime un journal détaillé pour expliquer ce qui se passe (peut être combiné avec tout autre indicateur)
  • --dry : Affiche ce qui serait fait mais ne construit rien en fait
  • --clean : Supprime les sorties des projets spécifiés (peut être combiné avec --dry )
  • --force : Agir comme si tous les projets étaient obsolètes
  • --watch : Mode montre (ne peut être combiné avec aucun drapeau sauf --verbose )

Mises en garde

Normalement, tsc produira des sorties ( .js et .d.ts ) en présence d'erreurs de syntaxe ou de type, sauf si noEmitOnError est activé.
Faire cela dans un système de construction incrémentielle serait très mauvais - si l'une de vos dépendances obsolètes avait une nouvelle erreur, vous ne la verriez qu'une seule fois car une construction ultérieure ignorerait la construction du projet maintenant à jour.
Pour cette raison, tsc -b agit comme si noEmitOnError était activé pour tous les projets.

Si vous archivez des sorties de compilation ( .js , .d.ts , .d.ts.map , etc.), vous devrez peut-être exécuter une compilation --force après un certain contrôle de source opérations selon que votre outil de contrôle de source conserve ou non les horodatages entre la copie locale et la copie distante.

msbuild

Si vous avez un projet msbuild, vous pouvez activer le mode de construction en ajoutant

    <TypeScriptBuildMode>true</TypeScriptBuildMode>

dans votre fichier proj. Cela permettra la construction incrémentielle automatique ainsi que le nettoyage.

Notez que comme avec tsconfig.json / -p , les propriétés de projet TypeScript existantes ne seront pas respectées - tous les paramètres doivent être gérés à l'aide de votre fichier tsconfig.

Certaines équipes ont mis en place des workflows basés sur msbuild dans lesquels les fichiers tsconfig ont le même ordre de graphe implicite que les projets gérés avec lesquels ils sont associés.
Si votre solution est comme celle-ci, vous pouvez continuer à utiliser msbuild avec tsc -p avec des références de projet ; ceux-ci sont entièrement interopérables.

Conseils

Structure globale

Avec plus tsconfig.json fichiers héritage du fichier de configuration pour centraliser vos options de compilateur communes.
De cette façon, vous pouvez modifier un paramètre dans un fichier plutôt que d'avoir à modifier plusieurs fichiers.

Une autre bonne pratique consiste à avoir un fichier "solution" tsconfig.json qui contient simplement references pour tous vos projets de nœuds feuilles.
Ceci présente un point d'entrée simple ; Par exemple, dans le référentiel TypeScript, nous exécutons simplement tsc -b src pour créer tous les points de terminaison car nous répertorions tous les sous-projets dans src/tsconfig.json
Notez qu'à partir de 3.0, ce n'est plus une erreur d'avoir un tableau files vide si vous avez au moins un reference dans un fichier tsconfig.json .

Vous pouvez voir ces modèles dans le référentiel TypeScript - voir src/tsconfig_base.json , src/tsconfig.json et src/tsc/tsconfig.json comme exemples clés.

Structuration des modules relatifs

En général, peu de choses sont nécessaires pour faire la transition d'un dépôt à l'aide de modules relatifs.
Placez simplement un fichier tsconfig.json dans chaque sous-répertoire d'un dossier parent donné et ajoutez reference s à ces fichiers de configuration pour correspondre à la superposition prévue du programme.
Vous devrez soit définir le outDir sur un sous-dossier explicite du dossier de sortie, soit définir le rootDir sur la racine commune de tous les dossiers du projet.

Structuration pour outFiles

La disposition des compilations utilisant outFile est plus flexible car les chemins relatifs importent moins.
Une chose à garder à l'esprit est que vous voudrez généralement ne pas utiliser prepend jusqu'au "dernier" projet - cela améliorera les temps de construction et réduira la quantité d'E/S nécessaire dans une construction donnée.
Le référentiel TypeScript lui-même est une bonne référence ici - nous avons des projets de "bibliothèque" et des projets de "point de terminaison" ; Les projets « endpoint » sont aussi petits que possible et n'intègrent que les bibliothèques dont ils ont besoin.

Structuration pour monorepos

À FAIRE : Expérimentez davantage et découvrez cela. Rush et Lerna semblent avoir des modèles différents qui impliquent des choses différentes de notre côté

Tous les 147 commentaires

Oh mon Dieu!

:+1:

Ouais! Cela est parfaitement logique pour les cas d'utilisation que vous avez fournis. Fournir aux outils que nous utilisons une meilleure vue sur la façon dont notre code est destiné à être utilisé est certainement la bonne chose à faire. Chaque fois que je F12 dans un fichier .d.ts par erreur, j'ai envie d'étrangler un chaton !

Jonathan,

Merci beaucoup pour vos aimables commentaires et pour avoir décidé de prendre cela. TypeScript est un outil formidable et cette fonctionnalité aidera de nombreuses personnes qui souhaitent composer leurs bases de code de taille moyenne qui ne peuvent justifier l'inefficacité imposée par d'énormes projets qui reposent sur une division stricte des préoccupations (par exemple les portails Azure ou le projet Monacos de le monde avec > 100kloc et de nombreuses équipes indépendantes). En d'autres termes, cela aidera vraiment les « personnes ordinaires ». Aussi, d'autres ont déjà proposé des trucs pour ça, par exemple @NoelAbrahams (#2180), et d'autres donc je ne peux pas revendiquer l'originalité ici. C'est juste quelque chose dont j'ai besoin depuis un moment.

Je pense que votre proposition est excellente. Le seul inconvénient que je vois par rapport à ma proposition (#3394), que j'ai maintenant fermée, est l'absence d'un mécanisme de secours pour les références.

Considérez le scénario réel suivant que j'ai détaillé ici : https://github.com/Microsoft/TypeScript/issues/3394#issuecomment -109359701

J'ai un projet TypeScript grunt-ts qui dépend d'un autre projet csproj2ts. Presque tous ceux qui travaillent sur grunt-ts voudront également travailler sur csproj2ts car il a une portée très limitée de fonctionnalités. Cependant, pour quelqu'un comme moi, ce serait formidable de pouvoir travailler sur les deux projets simultanément et de refactoriser/aller à la définition/trouver toutes les références à travers eux.

Lorsque j'ai fait ma proposition, j'ai suggéré que l'objet de dépendances soit un objet littéral avec des solutions de secours nommées. Une version plus conforme à votre proposition serait :

"dependencies": {
   "csproj2ts": ["../csproj2ts","node_modules/csproj2ts/csproj2ts.d.ts"],
   "SomeRequiredLibrary": "../SomeRequiredLibraryWithNoFallback"
}

Pour le simplifier pour qu'il reste un tableau, je suggère l'implémentation alternative suivante d'une future section hypothétique dependencies du fichier grunt-ts tsconfig.json :

"dependencies": [
   ["../csproj2ts","node_modules/csproj2ts/csproj2ts.d.ts"],
   "../SomeRequiredLibraryWithNoFallback"
]

La règle de résolution pour chaque élément de type tableau dans dependencies serait : le _premier_ élément qui se trouve dans chacun est celui qui est utilisé, et le reste est ignoré. Les éléments de type chaîne sont gérés comme l'indique la proposition de Jonathan.

Il s'agit d'une solution très légèrement plus compliquée à mettre en œuvre, mais elle donne au développeur (et aux auteurs de bibliothèques) une _beaucoup plus_ de flexibilité. Pour les développeurs qui n'ont pas besoin de développer sur csproj2ts (et n'ont donc pas ../csproj2ts/tsconfig.json fichier ../csproj2ts/tsconfig.json , la proposition fonctionnera exactement comme vous l'avez décrit ci-dessus.

Dans l'exemple ci-dessus, le "../SomeRequiredLibraryWithNoFallback" devrait être là comme dans votre proposition existante, et son absence serait une erreur du compilateur.

Merci beaucoup d'avoir pensé à cela.

Il y a deux problèmes ici que nous devons résoudre, il y a la construction et il y a le support des services linguistiques.

Pour Build, je ne pense pas que tsconfig soit le bon endroit pour cela. il s'agit clairement d'un problème de système de build. avec cette proposition, le compilateur dactylographié doit être dans les entreprises de :

  • déterminer la dépendance
  • vérifications à jour des assertions
  • gestion de la configuration (version vs débogage)

Tout cela relève clairement de la responsabilité des systèmes de construction ; ce sont des problèmes difficiles et il existe déjà des outils qui le font, par exemple MSBuild, grunt, gulp, etc.
Une fois que tsconfig et tsc deviennent le pilote de build, vous voudriez qu'il utilise tous les processeurs pour créer des sous-arborescences non liées, ou qu'il ait des commandes de post et pré build pour chaque projet, et éventuellement de créer d'autres projets également. encore une fois, je pense qu'il existe des outils de construction qui sont bons dans ce qu'ils font, et nous n'avons pas besoin de recréer cela.

Pour le service linguistique,
Je pense qu'il est bon que l'outil connaisse plusieurs fichiers tsconfig et puisse en déduire et vous aider davantage, mais cela ne devrait pas affecter votre version. je considérerais quelque chose comme :

"files" : [
    "file1.ts",
    {
        "path": "../projectB/out/projectB.d.ts",
         "sourceProject": "../projectB/"
     }
]

Où tsc ne regardera que le "chemin" mais les outils peuvent regarder d'autres informations et essayer d'être aussi utiles que possible.

Je reconnais l'existence du problème, mais je ne pense pas que la construction et l'outillage groupés soient la bonne solution. tsconfig.json doit rester un sac de configuration (c'est-à-dire une alternative json aux fichiers de réponses) et ne pas devenir un système de build. un tsconfig.json représente un seul appel tsc. tsc doit rester comme un seul compilateur de projet.

Les projets MSBuild dans VS sont un exemple d'utilisation d'un système de génération pour créer des fonctionnalités IDE et maintenant, les ppl n'en sont pas satisfaits car il est trop volumineux.

Merci pour ta réponse Mohamed. Permettez-moi de reformuler pour voir si je comprends:

  • Vous pensez que la tâche de coordonner les builds multi-projets doit rester du domaine des outils de build dédiés.
  • Vous pensez qu'il peut y avoir quelque chose à cette suggestion pour le service de langage TypeScript.
  • Vous pensez qu'exécuter tsc --project sur ce tsconfig.json reviendrait à exécuter tsc file1.ts ../project/out/project.d.ts . Cependant, ouvrir un tel projet dans VS ou un autre éditeur avec le service de langage TypeScript permettrait "d'aller à la définition" pour amener le développeur au _fichier TypeScript réel_ où la fonctionnalité a été définie (plutôt à la définition dans projectB.d.ts )

Ai-je ce droit ?

Si oui, je pense que c'est très juste. Dans ma proposition initiale (https://github.com/Microsoft/TypeScript/issues/3394), j'ai dit que mon idée était incomplète car elle n'incluait pas l'étape de copier les résultats émis à partir de l'endroit où ils seraient sortis dans la bibliothèque référencée à l'endroit où la bibliothèque de référence les attendrait au moment de l'exécution. Je pense que vous dites "pourquoi aller à mi-chemin de la construction alors que ce qui est vraiment nécessaire, c'est le support des services linguistiques".

Pour modifier un peu les données de votre exemple, seriez-vous prêt à prendre en charge quelque chose comme ça ?

"files" : [
    "file1.ts",
    {
        "path": "externalLibraries/projectB.d.ts",
         "sourceProject": "../projectB/"
     }
]

L'hypothèse est que le projet actuel serait livré avec une définition pour projectB qui serait utilisée par défaut, mais si la source réelle pour projectB est disponible, la source réelle serait utilisée à la place.

@nycdotnet tu as bien résumé; Je voudrais créer un système qui est faiblement couplé et permet de mélanger et de faire correspondre différents outils de construction avec différents IDE, tout en bénéficiant d'une excellente expérience de conception.

Super!

Je suis d'accord avec @mhegazy et en fait, je pense qu'il est important que TypeScript arrête de se considérer comme un « compilateur » et commence à se considérer comme un « vérificateur de type » et un « transpileur ». Maintenant, il existe un support de transpilation de fichier unique. Je ne vois aucune raison de créer des fichiers JavaScript compilés, sauf lors de l'exécution/du regroupement. Je ne vois pas non plus pourquoi il est nécessaire de générer les définitions de référence externes lors de la vérification de type lorsque la source de script dactylographiée réelle est disponible.

La résolution des fichiers et des packages est de la responsabilité du système de build que vous utilisez (navigateur, systemjs, webpack, etc.), donc pour que l'outil fonctionne, le service de langue doit être capable de résoudre les fichiers de la même manière que n'importe quel système/plate-forme de build que vous utilisent. Cela signifie soit implémenter un LanguageServicesHost personnalisé pour chaque système de build, soit fournir un outil pour chacun qui génère les entrées de mappage correctes dans tsconfig.json. L'un ou l'autre de ces éléments est acceptable.

@nycdotnet Je pense que votre cas d'utilisation pour plusieurs chemins de secours serait mieux géré en utilisant npm link ../csproj2ts ?

Je suis d'accord avec @mhegazy que nous devrions garder la construction séparée du compilateur/transpileur. J'aime inclure la dépendance tsconfig -> tsconfig dans la section des fichiers en supposant que si la section ne répertorie aucun fichier *.ts, elle les recherche toujours. Par exemple

"des dossiers" : [
{
"path": "externalLibraries/projectB.d.ts",
"sourceProjet": "../projectB/"
}
]

Inclut toujours tous les fichiers ts dans le répertoire et le sous-répertoire contenant le fichier tsconfig.json.

@dbaeumer, vous souhaitez ensuite l'avoir dans une propriété différente, n'est-ce pas ? actuellement, si files est défini, il est toujours utilisé et nous ignorons la partie include *.ts.

@mhegazy pas nécessairement une section différente bien que cela rendrait les choses plus claires à la fin. Tout ce que je veux éviter, c'est d'être obligé de répertorier tous les fichiers si j'utilise une dépendance tsconfig -> tsconfig. Dans l'exemple ci-dessus, je voudrais toujours ne pas répertorier les fichiers *.ts pour les alimenter dans le compilateur.

Je pense que c'est bien nécessaire. Je ne pense pas que nous puissions éviter la question de construction cependant. Cela ne signifie pas que nous devons implémenter un système de construction avec cette proposition, mais nous aurions dû réfléchir à de bons conseils qui fonctionnent dans une configuration telle que celle proposée. Il ne sera pas trivial de réussir (et nous devons le résoudre pour Visual Studio).

Dans la proposition ci-dessus (où le .d.ts et la source sont référencés), détecte-t-il si le .d.ts est obsolète (c'est-à-dire doit être reconstruit) ? Les opérations telles que Refactor/Rename fonctionnent-elles sur plusieurs projets (c'est-à-dire qu'elles mettent à jour le nom dans la source du projet référencé, pas seulement son fichier .d.ts qui sera écrasé lors de la prochaine génération) ? GoToDef me renvoie-t-il au code d'origine du projet référencé (pas au milieu d'un fichier .d.ts géant pour l'ensemble du projet) ? Cela semblerait impliquer de devoir résoudre, et dans certains cas analyser, la source des projets référencés, auquel cas le .d.ts est-il utile ?

La solution générale, que nous avons aujourd'hui, vous avez un .d.ts comme sortie de build d'un projet, puis référencé comme entrée dans l'autre projet. Cela fonctionne bien pour la construction, donc pas besoin de changer cela.

Le problème est le scénario d'édition. vous ne voulez pas parcourir un fichier généré lors de l'édition. Ma solution proposée est de fournir un "indice" d'où proviennent les fichiers .d.ts générés. Le service de langue ne chargera alors pas le fichier .d.ts et chargera à la place un "projet" à partir du chemin d'indication. De cette façon, goto def vous amènera au fichier d'implémentation au lieu du fichier .d.ts et de la même manière, les erreurs fonctionneraient sans avoir besoin d'une compilation.

des opérations telles que renommer, se « propageront » d'un projet à un autre, de la même manière, trouveront des références, feront l'affaire.

Aujourd'hui, il appartient entièrement à l'hôte (l'IDE, peu importe) de trouver le fichier tsconfig.json, bien que TS fournisse ensuite des API pour le lire et l'analyser. Comment envisageriez-vous que cela fonctionne s'il y avait plusieurs tsconfig.json situés de manière hiérarchique ? L'hôte serait-il toujours responsable de la résolution du fichier initial mais pas les autres ou l'hôte serait-il responsable de la résolution de tous les tsconfigs ?

On dirait qu'il y a un compromis entre commodité/convention et flexibilité.

Cela ne commencerait-il pas par la possibilité de créer des fichiers d.ts comme décrit dans #2568 (ou au moins d'avoir des importations relatives) ?

@spion je ne suis pas sûr de voir la dépendance ici. vous pouvez avoir plusieurs sorties d'un projet, il n'y a pas un seul fichier de délectation. le système de construction devrait être capable de le savoir et de les connecter en tant qu'entrées à des projets dépendants.

@mhegazy Oups, désolé. En regardant à nouveau la question, il semble que cela soit davantage lié au service linguistique. j'ai lu ce qui suit

  • Projets de taille moyenne : ceux avec des versions standard et des composants partagés

et a automatiquement supposé qu'il était lié à une meilleure prise en charge des worfklows de construction npm/browserify (ou webpack) où des parties du projet sont des modules externes.

AFAIK, il n'y a pas encore de moyen de générer des fichiers .d.ts pour les modules externes ? Si tel est le cas, la seule façon pour un service linguistique de lier des projets qui importent des modules externes serait d'avoir quelque chose comme ceci dans tsconfig.json :

{ 
  "provides": "external-module-name"
}

qui informerait le LS lorsque les projets sont référencés dans un autre tsconfig.json

AFAIK, il n'y a pas encore de moyen de générer des fichiers .d.ts pour les modules externes ?

Je ne pense pas que ce soit vrai. appeler tsc --m --d générera un fichier de déclaration qui est lui-même un module externe. la logique de résolution va essayer de trouver un .ts et sinon un .d.ts avec le même nom,

@spion TypeScript peut générer des fichiers d.ts pour les modules externes comme l' a dit https://github.com/TypeStrong/dts-bundle

@mhegazy désolé, je voulais dire pour les "modules externes ambiants", c'est-à-dire si j'écris external-module-name dans TypeScript et importe une de ses classes depuis un autre module :

import {MyClass} from 'external-module-name'

il n'y a aucun moyen de faire en sorte que tsc génère le fichier .d.ts approprié qui déclare 'external-module-name'

@nycdotnet Je connais dts-bundle et dts-generator mais si le service de langue doit connaître la source de mon autre projet, il devrait également savoir quel nom de module il fournit pour pouvoir suivre correctement les importations

Quel est le statut de cette fonctionnalité ? il semble que ce soit une option importante pour les projets de taille moyenne. Comment configurer un projet dont la source se trouve dans différents dossiers avec une configuration "requirejs" spécifique ?

@llgcode s'il vous plaît jeter un oeil à https://github.com/Microsoft/TypeScript/issues/5039 , cela devrait être disponible dans typescript@next .

Je ne comprends pas vraiment ce qu'il y a de si difficile à écrire un fichier gulp avec des tâches qui compilent les sous-projets exactement comme vous en avez besoin pour des projets de taille moyenne. Je le fais même dans des projets de petite taille. La seule raison pour laquelle j'utilise tsconfig.json est pour VS Code

Premièrement, je n'utilise pas gulp. Deuxièmement, cela peut être un gros projet où vous ne voulez pas tout recompiler à chaque fois. Mais si vous avez une bonne solution avec gulp, faites-moi savoir comment faire.

@llgcode Eh bien, gulp est un gulp.task() . Dans votre tâche, vous pouvez prendre un flux de fichiers d'entrée avec gulp.src() puis les .pipe() à travers un pipeline de transformations, comme la compilation, la concaténation, la minification, les sourcesmaps, la copie d'actifs ... Vous pouvez faire tout ce qui est possible avec les modules Node et NPM.
Si vous devez compiler plusieurs projets, définissez simplement une tâche qui le fait. Si vous souhaitez utiliser plusieurs tsconfig.json, gulp -typescript prend en charge cela, ou vous pouvez simplement lire les fichiers json. Des builds incrémentiels sont également possibles. Je ne sais pas comment votre projet est structuré, si vous les avez dans différents dépôts et utilisez des sous-modules, ou autre. Mais gulp est 100% flexible.

Ok merci semble être un excellent outil. si j'ai require avec un mappage comme require("mylibs/lib") et que mes fichiers sont par exemple dans un dossier project/src/lib.js, alors la complétion ne fonctionnera pas dans atom et je ne sais pas comment taperscript ou gulp résoudra le mappage/config fait avec "mylibs" et un chemin local. Je pense donc que ces nouveaux chemins d'options dans #5039 sont une bonne solution à ce problème.

@llgcode Eh bien, avec gulp, vous pouvez obtenir tous les fichiers (y compris les fichiers .d.ts) avec des globs, voir https://www.npmjs.com/package/gulp-typescript#resolving -files.

Je pense juste que TypeScript essaie de faire beaucoup de choses ici, c'est un transpileur et non un outil de construction. La gestion des "dépendances" comme mentionné dans la proposition est vraiment la tâche des gestionnaires de packages ou des systèmes de contrôle de version et leur câblage est la tâche des outils de construction.

@felixfbecker Je ne suis pas d'accord. Chaque compilateur (avec vérification de type) que je connais a une option comme celle-ci. par exemple:
gcc -> inclure des fichiers
java -> chemin de classe et chemin source
aller -> GOPATH
python -> PYTHONPATH
le compilateur/transpileur a besoin de savoir quels fichiers source doivent être transpilés quels fichiers source sont simplement des fichiers include/lib.
Un outil de construction comme gulp est nécessaire pour savoir quoi faire lorsqu'un fichier change.

D'accord avec @llgcode . De plus, en plus d'être exposé en tant que compilateur, TypeScript expose également en tant que service de langage, qui fournit une mise en évidence de la syntaxe (détection en fait) et une fonctionnalité de complétion à l'IDE. Et CELA doit également parcourir l'arbre des dépendances.

@llgcode @unional Points valides. Une chose qui peut également aider est de faire files propriété tsconfig.json accepter globs de sorte que vous pouvez définir tous les fichiers dans tous les dossiers que vous souhaitez inclure. Mais je vois d'où vous venez et pourquoi on peut vouloir plusieurs tsconfig.json pour des projets plus importants.

AFAIK pour CommonJS, cela est déjà pris en charge via node_modules et npm link ../path/to/other-project

Le lien npm ne fonctionne pas dès que vous commencez à réutiliser les bibliothèques entre les projets. Si vous utilisez une bibliothèque commune entre deux projets distincts de votre choix, (en prenant rxjs comme exemple), le script dactylographié vous dira que « Observable n'est pas assignable à Observable ». C'est parce que les chemins d'inclusion suivent les dossiers de liens symboliques vers deux dossiers node_modules différents et bien qu'il s'agisse de la même bibliothèque. Les solutions de contournement entraînent la création de tâches gulp ou de dépôts npm locaux/privés, revenant essentiellement à l'option de grand projet.

@EricABC c'est probablement parce qu'ils utilisent des déclarations de modules externes ambiants, auquel cas ils devraient également inclure des définitions pour les nouveaux fichiers .d.ts basés sur node_modules . En dehors de cela, il ne devrait pas y avoir de problème car les types TS ne sont vérifiés que structurellement, donc peu importe s'ils proviennent de modules différents ou s'ils ont des noms différents tant que les structures correspondent.

Merci @spion ,

Une chose qui peut également aider est de faire en sorte que la propriété files dans tsconfig.json accepte les globs...

Il y a une propriété include en discussion

Questions et remarques :

  • dependencies devrait autoriser le chemin complet de tsconfig.json car tsc le permet
  • Pourquoi introduire un nouveau mot-clé ( dependencies ) alors que files existe déjà et est correct ?
    Exemple:
{
    "compilerOptions": {
        // ...
    },
    "files": [
        "../common/tsconfig.json", // <== takes the `files` part of the tsconfig.json
        "../common/tsconfig.util.json", // <==
        "core.ts",
        "sys.ts"
    ]
}
  • Que se passe-t-il si la dépendance tsconfig.json spécifie également compilerOptions ?


Allons plus loin/wild :-) et permettons éventuellement (à l'avenir) compilerOptions , exclude ... de référencer un autre tsconfig.json :

// File app/tsconfig.json
{
    "compilerOptions": "../common/tsconfig.compilerOptions.json",
    "files": [
        "../common/tsconfig.json",
        "../common/tsconfig.util.json",
        "core.ts",
        "sys.ts"
    ],
    "exclude": "../common/exclude.json"
}

// File ../common/tsconfig.compilerOptions.json
{
    "compilerOptions": {
        "module": "commonjs",
        "noImplicitAny": true,
        "sourceMap": true
    }
}

// File ../common/exclude.json
{
    "exclude": [
        "node_modules",
        "wwwroot"
    ]
}

// File ../common/tsconfig.util.json
{
    "files": [
        "foo.ts",
        "bar.ts"
    ]
}

Vous avez la logique : files , compilerOptions , exclude ... Fichier .json => simple et évolutif. Vous pouvez ainsi diviser un tsconfig.json en plusieurs fichiers si vous le souhaitez et les réutiliser.

En lisant une partie de votre discussion, il semble plus pertinent de bien comprendre ce "service linguistique" / définition de goto. Les débogueurs JavaScript utilisent sourceMaps. Maintenant, si tsc générait des données sourceMap non seulement dans les fichiers .js mais aussi dans les fichiers .d.ts...

Au-delà de cela, je ne vois vraiment pas beaucoup d'avantages à déclencher des builds de projets enfants à partir du fichier tsconfig.json. Si vous avez besoin de ce type basique de dépendances de temps de construction, un simple script shell ferait le travail. Si vous avez besoin de construction incrémentale intelligente, en revanche, l'approche proposée semble trop simple. Dans de nombreux scénarios, tsc finit par n'être qu'une étape de construction parmi d'autres. Comment étrange d'écrire des dépendances pour tsc dans tsconfig.json mais un autre fichier pour le reste? Encore une fois, pour des choses simples, tsc étant la seule étape de construction, un script shell ferait.

Quoi qu'il en soit, que diriez-vous de générer un mappage de source dans les fichiers .d.ts comme dans les fichiers .js ?

nous utilisons simplement des modules node + lien npm, la seule chose qui ne fonctionne pas est que le moduleResolution: node n'est pas compatible avec les modules ES6, qui permettent des optimisations inlining / tree-shaking (voir aussi #11103 )

Je ne veux pas sortir du sujet, mais à certains égards, cela semble parallèle aux défis de travailler avec plusieurs projets de packages Node localement. Je ne pense pas que ce soit aussi simple que d'utiliser "npm link". Ils peuvent avoir des scripts de construction différents, vous devrez tous les exécuter dans le bon ordre, il est plus difficile de le faire de manière incrémentielle, il est plus difficile d'utiliser le mode de surveillance, il est plus difficile de déboguer les choses et il est plus difficile de résoudre les cartes source. Selon l'éditeur de votre choix, cela peut être encore plus difficile.

En général, j'abandonne, je mets le tout dans un projet géant, puis je le sépare en packages séparés une fois qu'ils se sont stabilisés, mais cela n'aide que parce que le défi est relevé moins fréquemment. Je trouve toute l'expérience vraiment, vraiment ennuyeuse. Est-ce que j'ai raté quelque chose ?

Donc, quoi qu'il en soit, j'espère juste pouvoir enfin avoir une solution élégante pour toute cette expérience de développement.

Juste en disant que ce serait formidable pour notre travail quotidien !

En raison de la nature du développement Web, nous avons plusieurs projets ts, chaque projet contenant plusieurs fichiers ts compilés dans un seul fichier js ( --outFile ). Ce sont soit des projets de type application (ils font une chose spécifique ou ajoutent une fonctionnalité spécifique) soit des projets de type lib (code réutilisable pour les applications). Souvent, nous travaillons sur plusieurs de ces projets ts en même temps, améliorant les bibliothèques pour faciliter le développement des applications. Mais je ne pense pas qu'aucun de mes collègues développeurs n'ait à aucun moment tous nos projets ts sur leurs environnements locaux.

Pour améliorer notre flux de travail, nos options actuelles sont

  • Tout jeter dans 1 ts project

    • Nous pouvons référencer directement les fichiers .ts, ce qui est bien plus agréable que d'utiliser les fichiers d.ts

    • Mais nous avons besoin d'un outil externe pour mapper et concaténer des ensembles de fichiers, car nous devons limiter les fichiers js demandés sur les interwebs tout en maintenant la modularité des applications (qui sont spécifiques à une fonctionnalité ou à une page).

    • Comme mentionné, tous ces projets ts ne sont pas nécessaires à tout moment pour tout le monde, ce qui ajouterait beaucoup de poids au système de fichiers. Quelqu'un qui travaille sur le projet X peut avoir besoin des projets A, B et D, mais quelqu'un qui travaille sur Y peut avoir besoin de A et C.

  • Gardez les projets séparés (notre situation actuelle)

    • Nous devons référencer les fichiers d.ts compilés des autres projets ts, car l'inclusion d'un fichier .ts directement l'ajouterait à la sortie. Ce serait beaucoup plus rapide si nous pouvions simplement f12 directement dans notre propre code source.

    • Et nous devons ajouter des outils/scripts pour compiler plusieurs de ces projets en même temps. Actuellement, soit nous lançons les commandes tsc -d -w partir de plusieurs terminaux, soit nous lançons un script qui le fait pour tous les sous-répertoires où un tsconfig est trouvé.

    • Je comprends que d'autres outils peuvent aider avec cela (par exemple gulp) ou peut-être que nous pouvons trouver quelque chose avec les fichiers globs dans les tsconfigs, mais cela n'aiderait pas avec le premier numéro des fichiers d.ts.

Notre (nos) projet (s) n'est tout simplement pas assez grand pour garder le développement des bibliothèques strictement séparé des applications, mais pas assez petit pour que nous puissions simplement tout rassembler. Nous manquons d'options gracieuses avec le tapuscrit.

Si dependencies peut être cette option du meilleur des deux mondes, ce serait incroyable ; la fonctionnalité detags à tous les fichiers ts d'une dépendance, mais compiler la sortie en fonction du tsconfig de cette dépendance.

Y a-t-il une mise à jour sur ce sujet. Comment pouvons-nous avoir plusieurs projets tapuscrits qui se compilent indépendamment les uns des autres ? Comment peut-on décrire une telle dépendance dans tsconfig.json ?

Celle-ci est désormais incluse dans la feuille de route dans la future section intitulée « Support pour les références de projets ». Donc, je suppose qu'un fichier .tsconfig pourra lier un autre fichier .tsconfig en tant que dépendance.

Y a-t-il une mise à jour sur ce sujet. Comment pouvons-nous avoir plusieurs projets tapuscrits qui se compilent indépendamment les uns des autres ? Comment peut-on décrire une telle dépendance dans tsconfig.json ?

La dépendance de build doit être encodée dans votre système de build. les systèmes de construction comme gulp, grunt, brocoli, msbuild, basal, etc. sont conçus pour gérer de tels cas.
Pour les informations de type, la sortie d'un projet doit inclure un .d.ts et qui doit être transmis en entrée à l'autre.

@mhegazy Notre projet fonctionne comme ça. Nous avons un certain nombre de packages dans un monorepo lerna, chaque package a ses propres dépendances dans package.json, et ces mêmes dépendances dans la propriété "types" dans leur tsconfig.json . Chaque projet est compilé avec --outFile (c'est un projet plus ancien qui n'a pas encore migré vers les modules ES), et la clé "typings" package.json pointe vers le .d.ts groupé

Nous utilisons gulp pour le regroupement/la surveillance.

Cela fonctionne pour la plupart, mais il y a quelques problèmes:

  • En raison de problèmes tels que #15488 et #15487, nous devons avoir un lien explicite pour que les références fonctionnent correctement.
  • Go-to-definition vous amènera à un fichier .d.ts groupé. Idéalement, cela vous amènerait à la source dans un autre projet.
  • Le moyen le plus rapide de faire une compilation complète est de lerna run build --sort (en fait tsc dans chaque répertoire), qui a une surcharge supplémentaire car il engendrera un processus de compilation TypeScript pour chaque package, effectuant beaucoup de travail répété .

Je surveille de près cette question car nous sommes également dans la même situation que d'autres ont décrite.
Plusieurs "projets", chacun avec son fichier tsconfig.json.

Le processus de construction fonctionne comme l' a souligné .d.ts et qui est utilisé comme entrée pour les projets dépendants.

Le vrai problème est le support IDE : lors de la recherche de références, elles ne sont trouvées que dans le cadre d'un seul tsconfig.json . Pire encore, les effets en cascade d'un fichier modifié ne se propagent pas à travers les projets car les fichiers dépendants en dehors de la portée tsconfig.json ne sont pas recompilés. Ceci est très mauvais pour la maintenance de nos projets et provoque parfois des erreurs de construction qui auraient pu être détectées dans l'IDE.

It's happening

OH MON DIEU

Un scénario mis à jour dans lequel j'aimerais que cela implique des composants React. Nous avons un référentiel de composants qui contient des modules JSX (atomes, molécules et organismes) qui rendent les composants d'interface utilisateur appropriés dans toutes les applications de notre entreprise. Ce référentiel de composants est utilisé par tous les développeurs frontaux lorsqu'ils travaillent sur leurs applications individuelles. Ce serait TELLEMENT BIEN si je pouvais avoir une expérience de service de langage TypeScript qui me permettrait de modifier l'interface utilisateur de mon application spécifique et "Aller à la définition" dans le référentiel de composants d'interface utilisateur communs. Aujourd'hui, nous devons regrouper ces composants individuellement et les copier. C'est le problème "faites votre propre plomberie de projet" que j'aimerais voir résolu (pour lequel il y a une très belle histoire dans le monde .NET avec des projets sous une solution).

Références de projet : évolutivité intégrée pour TypeScript

introduction

TypeScript a augmenté jusqu'à des projets de centaines de milliers de lignes, mais ne prend pas en charge nativement ce type d'échelle en tant que comportement intégré. Les équipes ont développé des solutions de contournement d'efficacité variable et il n'existe pas de méthode standardisée pour représenter les grands projets. Bien que nous ayons considérablement amélioré les performances du vérificateur de type au fil du temps, il existe toujours des limites strictes en termes de vitesse raisonnablement atteinte par TS,
et des contraintes telles que l'espace d'adressage de 32 bits qui empêchent le service de langue de s'étendre « à l'infini » dans les scénarios interactifs.

Intuitivement, la modification d'une ligne de JSX dans un composant frontal ne devrait références de partitionner leur code en blocs plus petits. En permettant aux outils de fonctionner sur de plus petits morceaux de travail à la fois, nous pouvons améliorer la réactivité et resserrer la boucle de développement de base.

Nous pensons que notre architecture actuelle a très peu de "repas gratuits" en termes d'améliorations drastiques des performances ou de la consommation de mémoire. Au lieu de cela, ce partitionnement est un compromis explicite qui augmente la vitesse au détriment d'un certain travail initial. Les développeurs devront passer un certain temps à raisonner sur le graphe de dépendance de leur système, et certaines fonctionnalités interactives (par exemple, les renommages entre projets) peuvent être indisponibles jusqu'à ce que nous améliorions davantage l'outillage.

Nous identifierons les contraintes clés imposées par ce système et établirons des directives pour le dimensionnement du projet, la structure des répertoires et les modèles de construction.

Scénarios

Il y a trois scénarios principaux à considérer.

Modules relatifs

Certains projets utilisent largement les importations relatives . Ces importations sont résolues sans ambiguïté vers un autre fichier sur le disque. Des chemins comme ../../core/utils/otherMod seraient courants à trouver, bien que des structures de répertoires plus plates soient généralement préférées dans ces dépôts.

Exemple

Voici un exemple du projet perseus de la Khan Academy :

Adapté de https://github.com/Khan/perseus/blob/master/src/components/graph.jsx

const Util = require("../util.js");
const GraphUtils = require("../util/graph-utils.js");
const {interactiveSizes} = require("../styles/constants.js");
const SvgImage = require("../components/svg-image.jsx");

Observations

Bien que la structure du répertoire implique une structure de projet, elle n'est pas nécessairement définitive. Dans l'exemple de Khan Academy ci-dessus, will peut déduire que util , styles et components seraient probablement leur propre projet. Mais il est également possible que ces répertoires soient assez petits et soient en fait regroupés dans une seule unité de construction.

Mono-repo

Un mono-repo se compose d'un certain nombre de modules qui sont importés via des chemins import * as C from 'core/thing ) peuvent être courantes. Habituellement, mais pas toujours, chaque module racine est en fait publié sur NPM.

Exemple

Adapté de https://github.com/angular/angular/blob/master/packages/forms/src/validators.ts

import {InjectionToken, ɵisObservable as isObservable, ɵisPromise as isPromise} from '@angular/core';
import {forkJoin} from 'rxjs/observable/forkJoin';
import {map} from 'rxjs/operator/map';
import {AbstractControl, FormControl} from './model';

Observations

L'unité de division n'est pas nécessairement la première partie du nom du module. rxjs , par exemple, compile en fait ses sous-parties ( observable , operator ) séparément, comme le fait tout package étendu (par exemple @angular/core ).

Fichier de sortie

TypeScript peut concaténer ses fichiers d'entrée dans un seul fichier JavaScript de sortie. Les directives de référence, ou l'ordre des fichiers dans tsconfig.json, créent un ordre de sortie déterministe pour le fichier résultant. Ceci est rarement utilisé pour les nouveaux projets, mais est toujours répandu parmi les anciennes bases de code (y compris TypeScript lui-même).

Exemple

https://github.com/Microsoft/TypeScript/blob/master/src/compiler/tsc.ts

/// <reference path="program.ts"/>
/// <reference path="watch.ts"/>
/// <reference path="commandLineParser.ts"/>

https://github.com/Microsoft/TypeScript/blob/master/src/harness/unittests/customTransforms.ts

/// <reference path="..\..\compiler\emitter.ts" />
/// <reference path="..\harness.ts" />

Observations

Certaines solutions utilisant cette configuration chargeront chaque outFile via une balise script distincte (ou l'équivalent), mais d'autres (par exemple, TypeScript lui-même) nécessitent la concaténation des fichiers précédents car ils créent des sorties monolithiques .

Références du projet : une nouvelle unité d'isolement

Quelques observations critiques d'interactions avec de vrais projets :

  • TypeScript est généralement "rapide" (< 5-10s) lors de la vérification de projets de moins de 50 000 LOC de code d'implémentation (non-.d.ts)
  • Les fichiers .d.ts, en particulier sous skipLibCheck , sont presque "gratuits" en termes de vérification de type et de coût de mémoire
  • Presque tous les logiciels peuvent être subdivisés en composants inférieurs à 50 000 LOC
  • Presque tous les grands projets imposent déjà une certaine structuration de leurs fichiers par répertoire d'une manière qui produit des sous-composants de taille moyenne
  • La plupart des modifications se produisent dans les composants du nœud feuille ou proche du nœud feuille qui ne nécessitent pas de re-vérification ou de réémission de l'ensemble de la solution

En mettant tout cela ensemble, s'il était possible de ne vérifier qu'un seul morceau de code d'implémentation de 50 000 LOC à la fois, il n'y aurait presque pas d'interactions "lentes" dans un scénario interactif, et nous ne manquerions presque jamais de mémoire.

Nous introduisons un nouveau concept, une référence de projet , qui déclare un nouveau type de dépendance entre deux unités de compilation TypeScript où le code d'implémentation de l'unité dépendante n'est pas vérifié ; à la place, nous chargeons simplement sa sortie .d.ts à partir d'un emplacement déterministe.

Syntaxe

Une nouvelle option references (À FAIRE : Bikeshed!) est ajoutée à tsconfig.json :

{
  "extends": "../tsproject.json",
  "compilerOptions": {
    "outDir": "../bin",
    "references": [
      { "path": "../otherProject" }
    ]
  }
}

Le tableau references spécifie un ensemble d'autres projets à référencer à partir de ce projet.
Chaque references objet path pointe vers un fichier tsconfig.json ou un dossier contenant un fichier tsconfig.json .
D'autres options peuvent être ajoutées à cet objet au fur et à mesure que nous découvrons leurs besoins.

Sémantique

Les références de projet modifient le comportement suivant :

  • Lorsque la résolution de module se résout en un fichier .ts dans un sous-répertoire du rootDir d'un projet, elle se résout à la .d.ts dans le outDir

    • Si cette résolution échoue, nous pouvons probablement la détecter et émettre une erreur plus intelligente, par exemple Referenced project "../otherProject" is not built plutôt qu'un simple "fichier non trouvé"

  • Rien d'autre (À FAIRE : pour l'instant ?)

Restrictions pour la performance des projets référencés

Pour améliorer de manière significative les performances de génération, nous devons nous assurer de restreindre le comportement de TypeScript lorsqu'il voit une référence de projet.

Plus précisément, les choses suivantes devraient être vraies :

  • Ne jamais lire ou analyser les fichiers .ts d'entrée d'un projet référencé
  • Seul le tsconfig.json d'un projet référencé doit être lu à partir du disque
  • La vérification à jour ne devrait

Pour tenir ces promesses, nous devons imposer certaines restrictions aux projets auxquels vous faites référence.

  • declaration est automatiquement défini sur true . C'est une erreur d'essayer de remplacer ce paramètre
  • rootDir défaut à "." (le répertoire contenant le fichier tsconfig.json ), plutôt que d'être déduit de l'ensemble des fichiers d'entrée
  • Si un tableau files est fourni, il doit fournir les noms de tous les fichiers d'entrée

    • Exception : les fichiers inclus dans les références de type (par exemple, ceux dans node_modules/@types ) n'ont pas besoin d'être spécifiés

  • Tout projet référencé doit lui-même avoir un tableau references (qui peut être vide).

Pourquoi "declaration": true ?

Les références de projet améliorent la vitesse de génération en utilisant des fichiers de déclaration (.d.ts) à la place de leurs fichiers d'implémentation (.ts).
Ainsi, naturellement, tout projet référencé doit avoir le paramètre declaration activé.
Ceci est impliqué par "project": true

Pourquoi modifier rootDir ?

Le rootDir contrôle la façon dont les fichiers d'entrée sont mappés aux noms de fichiers de sortie. Le comportement par défaut de TypeScript est de calculer le répertoire source commun du graphique complet des fichiers d'entrée. Par exemple, l'ensemble des fichiers d'entrée ["src/a.ts", "src/b.ts"] produira les fichiers de sortie ["a.js", "b.js"] ,
mais l'ensemble des fichiers d'entrée ["src/a.ts", "b.ts"] produira les fichiers de sortie ["src/a.js", "b.js"] .

Le calcul de l'ensemble des fichiers d'entrée nécessite l'analyse récursive de chaque fichier racine et de toutes ses références,
ce qui coûte cher dans un grand projet. Mais nous ne pouvons pas changer ce comportement aujourd'hui sans casser les projets existants dans le mauvais sens, donc ce changement ne se produit que lorsque le tableau references est fourni.

Pas de circularité

Naturellement, les projets ne peuvent pas former un graphique avec une quelconque circularité. (À FAIRE : Quels problèmes cela cause-t-il réellement , à part les cauchemars d'orchestration de build ?) Si cela se produit, vous verrez un message d'erreur qui indique le chemin circulaire qui s'est formé :

TS6187: Project references may not form a circular graph. Cycle detected:
    C:/github/project-references-demo/core/tsconfig.json ->
    C:/github/project-references-demo/zoo/tsconfig.json ->
    C:/github/project-references-demo/animals/tsconfig.json ->
    C:/github/project-references-demo/core/tsconfig.json

tsbuild

Cette proposition est intentionnellement vague sur la façon dont elle serait utilisée dans un système de construction "réel". Très peu de projets dépassent la limite "rapide" des 50 000 LOC sans introduire autre chose que tsc pour la compilation du code .ts.

Le scénario utilisateur de "Vous ne pouvez pas construire foo parce que bar n'est pas encore construit" est une sorte de tâche évidente "Allez chercher ça" dont un ordinateur devrait s'occuper, plutôt qu'un fardeau mental pour les développeurs.

Nous nous attendons à ce que des outils comme gulp , webpack , etc, (ou leurs plugins TS respectifs) intègrent la compréhension des références du projet et gèrent correctement ces dépendances de build, y compris la vérification à jour.

Pour nous assurer que cela est possible , nous fournirons une implémentation de référence pour un outil d'orchestration de génération TypeScript qui illustre les comportements suivants :

  • Vérification rapide de la mise à jour
  • Ordre des graphiques du projet
  • Construire la parallélisation
  • (À FAIRE : autres ?)

Cet outil doit utiliser uniquement des API publiques et être bien documenté pour aider les auteurs d'outils de construction à comprendre la bonne façon d'implémenter les références de projet.

À FAIRE

Rubriques à remplir pour compléter complètement cette proposition

  • Comment effectuer la transition d'un projet existant

    • Fondamentalement, déposez simplement les fichiers tsconfig.json , puis ajoutez les références nécessaires pour corriger les erreurs de construction

  • Impact sur baseUrl

    • Rend la mise en œuvre difficile mais n'a effectivement aucun impact sur l'utilisateur final

  • Brève discussion sur les projets d'imbrication (TL ; DR, cela doit être autorisé)
  • Scénario de contour consistant à subdiviser un projet devenu « trop grand » en projets plus petits sans casser les consommateurs
  • Comprenez le scénario de Lerna

    • Le point de données disponible (N = 1) indique qu'ils n'en auraient pas besoin car leur build est déjà efficacement structuré de cette façon

    • Trouvez plus d'exemples ou de contre-exemples pour mieux comprendre comment les gens font cela

  • Avons-nous besoin d'un paramètre dtsEmitOnly pour les personnes qui dirigent leur JS via, par exemple, webpack/babel/rollup ?

    • Peut-être que references + noEmit implique cela

Fantastique!

Comprenez le scénario de Lerna

  • Le point de données disponible (N = 1) indique qu'ils n'en auraient pas besoin car leur build est déjà efficacement structuré de cette façon

« ceci » fait-il référence à la proposition ou à l'implémentation de la génération de référence ? Bien que vous puissiez utiliser lerna pour faire la construction (mon équipe le fait), c'est noueux et serait beaucoup plus efficace si TS (ou un outil construit à partir de cette proposition) prenait soin de lui-même.

La section TODO est TODO pour l'ensemble de la proposition

Joli!

Tout projet référencé doit lui-même avoir un tableau de références (qui peut être vide).

Est-ce vraiment nécessaire ? Ne serait-il pas suffisant qu'un tel package .d.ts fichiers
(Dans ce cas, il n'est peut-être même pas nécessaire qu'il y ait aussi un tsconfig.json ?)

Mon cas d'utilisation : considérons un projet (par exemple tiers) qui n'utilise pas outDir , donc .ts , .js et .d.ts seront à côté de et TS essaiera actuellement de compiler le .ts au lieu d'utiliser le .d.ts .

La raison de ne pas utiliser outDir pour moi est d'autoriser plus facilement les importations de style import "package/subthing" , qui autrement devraient être par exemple import "package/dist/subthing" avec outDir: "dist" .
Et de pouvoir utiliser soit le package NPM, soit directement son référentiel source (par exemple avec npm link ).

(Il serait utile que package.json permette de spécifier un répertoire dans main , mais hélas...)

Avons-nous besoin d'un paramètre dtsEmitOnly pour les personnes qui dirigent leur JS via, par exemple, webpack/babel/rollup ?

Absolument! C'est une grosse pièce manquante pour le moment. Actuellement, vous pouvez obtenir un seul fichier d.ts lorsque vous utilisez outFile , mais lorsque vous passez aux modules et utilisez un bundler, vous le perdez. Pouvoir émettre un seul fichier d.ts pour le point d'entrée d'un module (avec export as namespace MyLib ) serait incroyable. Je sais que des outils externes peuvent le faire, mais ce serait vraiment génial s'ils étaient intégrés aux services d'émetteur et de langue.

Est-ce vraiment nécessaire ? Ne serait-il pas suffisant qu'un tel package contienne des fichiers .d.ts ?

Nous avons besoin de quelque chose dans le tsconfig cible qui nous indique où s'attendre à ce que les fichiers de sortie soient. Une version précédente de cette proposition avait "Vous devez spécifier un rootDir explicite " ce qui était plutôt lourd (vous deviez écrire "rootDir": "." dans chaque tsconfig). Puisque nous voulons inverser une variété de comportements dans ce monde, il était plus logique de simplement dire que vous obtenez un comportement "projet" si vous avez un tableau de références et que ce soit la chose qui est déconnectée, plutôt que de spécifier un tas de drapeaux que vous auriez à déclarer explicitement.

Cette proposition s'alignerait étroitement sur la façon dont nous avons déjà structuré nos projets TypeScript. Nous avons subdivisé en unités plus petites qui ont chacune un tsconfig.json et sont construites indépendamment via gulp. Les projets se référencent mutuellement en référençant les fichiers d.ts.

Dans un monde idéal, le projet référencé n'aurait pas besoin d'être pré-construit. c'est à dire. TypeScript effectue une « construction » du projet référencé et conserve l'équivalent « d.ts » en mémoire dans le service de langage. cela permettrait aux modifications apportées dans le projet "source" d'apparaître dans le projet "dépendant" sans avoir besoin d'une reconstruction.

Nous avons besoin de quelque chose dans le tsconfig cible qui nous indique où s'attendre à ce que les fichiers de sortie soient.

Cela n'est vrai que lorsque outDir est utilisé, n'est-ce pas ?

Comme dans : si j'ai un tsconfig qui :

  • n'utilise PAS outDir (mais a declaration: true , bien sûr), alors nous n'avons pas besoin de rootDir , ni de references
  • a outDir , alors vous auriez besoin que references et/ou rootDir (et declaration: true ) soient définis

La raison de la demande est que je pourrais alors activer le "mode projet" pour n'importe quel package TS en le référençant simplement, c'est-à-dire qu'il est sous mon contrôle.

Dans ce cas, il serait également bon qu'il fonctionne aussi dès qu'il trouve le fichier .d.ts qu'il recherche (c'est-à-dire qu'il ne se plaindra pas s'il n'y a pas de fichiers .ts ou de fichiers tsconfig). Parce que cela permettra un autre cas de "remplacement" d'une version NPM (qui peut n'avoir que des fichiers .d.ts) par sa version source si nécessaire.

Par exemple, considérons les packages NPM MyApp et SomeLib.
SomeLib pourrait avoir tsconfig : declaration: true .

Dépôt comme :

package.json
tsconfig.json
index.ts
sub.ts

Compilé, cela devient :

package.json
tsconfig.json
index.ts
index.d.ts
index.js
sub.ts
sub.d.ts
sub.js

Cette structure permet par exemple

// somewhere in MyApp
import something from "SomeLib/sub";

Dans le package NPM publié, je dois actuellement toujours supprimer les fichiers .ts, sinon toutes les sources seront recompilées par TS si MyApp utilise SomeLib :

Ainsi, sur NPM, cela devient :

package.json
index.d.ts
index.js
sub.d.ts
sub.js

Maintenant, si je mets references: ["SomeLib"] dans le tsconfig de MyApp, ce serait bien s'il fonctionne « tel quel sub.d.ts au bon endroit.


Question connexe mais différente :

Je me rends compte maintenant que SI l'auteur de SomeLib met references dans son tsconfig, cela permettrait de publier des packages NPM AVEC les fichiers .ts, à l'avenir. Mais alors, je suppose que TS les recompilera toujours lorsqu'un package dépendant ne placera pas explicitement references: ["SomeLib"] dans leur tsconfig.

Ou est-ce que l'intention est également que references dans MyLib introduira automatiquement une « limite de projet » lorsque seulement import le fera (c'est-à-dire pas references le fera) ?

IIRC, l'une des idées initiales était que si un module était par exemple localisé via node_modules , alors les fichiers .d.ts seraient préférés aux fichiers .ts , mais cela a été changé plus tard , car l'heuristique ("à travers node_modules ") était trop problématique en général. Peut-être qu'avoir une "limite de projet" explicite résoudrait ce problème (par exemple, un projectRoot: true , au lieu ou en plus d'avoir references ) ?

Pour le cas de lerna, j'espérais une solution plus simple.

  1. Dans les répertoires de packages individuels, les packages ne doivent ne doivent

    • cela vous permet de diviser des packages individuels en référentiels séparés et de simplement les cloner par l'outil de référentiel principal, par exemple ProseMirror : https://github.com/ProseMirror/prosemirror

  2. Dans le référentiel racine "workspace" (qui peut contenir tout le code, mais pourrait aussi simplement cloner d'autres référentiels), avoir les références dans son tsconfig.json

    • ceci est fait juste pour que l'outillage puisse reconnaître et suivre les références jusqu'à la source au lieu d'entrer dans les fichiers .d.ts.

Tout mon souci est qu'une fois que vous ajoutez une référence en utilisant un chemin descendant relatif "../xx" aux fichiers de configuration de projet individuels, ils ne sont

L'ajout du nouveau concept d'"espace de travail" tsconfig.json résout ce problème. De cette façon, si vous par exemple "git clone" le package individuel, l'installation de ses dépendances de la manière normale (par exemple en utilisant npm ou wire) devrait vous permettre de travailler dessus séparément, car les dépendances compilées apporteraient leurs fichiers de définition. Si vous clonez l'intégralité de l'espace de travail et exécutez la commande pour importer tous les packages, la configuration de l'espace de travail vous permettra de parcourir toutes les sources.

Notez qu'un espace tsconfig.json travail package.json https://yarnpkg.com/lang/en/docs/workspaces/

J'ai fait une petite preuve de concept ici

https://github.com/spion/typescript-workspace-plugin

Ajoutez simplement le plugin à tous vos fichiers tsconfig.json des dépôts individuels

{
  "plugins": [{"name": "typescript-workspace-plugin"}]
}

Ensuite, au niveau supérieur package.json à côté de l'entrée "workspaces" de fil, ajoutez une entrée "workspace-sources":

{
  "workspaces": ["packages/*"],
  "workspace-sources": {
    "*": ["packages/*/src"]
  }
}

Le champ fonctionne exactement comme le champ "paths" dans tsconfig.json, mais il n'affecte que le service de langue des projets individuels, en les pointant vers les sources du package. Restaure la fonctionnalité "aller à la définition / type" appropriée et similaire.

Ce n'est vrai que lorsque outDir est utilisé, n'est-ce pas ?

Correct. Nous avions émis l'hypothèse que presque tout le monde avec un grand projet utilise outDir . Je serais intéressé d'entendre parler de projets qui ne

Maintenant, si je mets des références : ["SomeLib"] dans le tsconfig de MyApp, ce serait bien si cela fonctionne "en l'état" pour la version NPM et la version source de SomeLib

Grand fan, j'aime beaucoup cette idée. Je dois me demander si c'est vraiment nécessaire ou non.

Une mise en garde ici est que je pense que les auteurs de packages doivent soit a) publier les fichiers .ts et tsconfig ensemble dans un endroit où TS les trouve, soit b) ne publier

Ou est-ce que l'intention est également que les références dans MyLib introduisent automatiquement une "limite de projet" lors de son importation (c'est-à-dire sans référencement) ?

J'ai parlé avec references ne "voit" .ts en dehors du dossier du projet - cela inclut les fichiers sous exclude d répertoires. Ce changement à lui seul fait fonctionner le scénario de lerna ("work" signifiant "les références de module se résolvent toujours en .d.ts") hors de la boîte, ainsi que d'autres.

J'ai besoin de regarder davantage le modèle « espace de travail ».

Ce n'est vrai que lorsque outDir est utilisé, n'est-ce pas ?

Correct. Nous avions émis l'hypothèse que presque tout le monde avec un grand projet utilise outDir. Je serais intéressé d'entendre parler de projets qui ne

Nous avons 67 projets TS dans la solution Visual Studio qui sont compilés sans outdir et sans grunttasks postbuild pour créer la structure de répertoire de sortie (et uglify et autres post-traitements).

La plupart des projets ont un tel tsconfig.json

 "include": [
    "../baseProj/Lib/jquery.d.ts",
    "../baseProj/baseProj.d.ts"
  ]

J'ai pris le temps de lire la proposition de références et de corriger - les utilisateurs de l'espace de travail AFAICT lerna et fil n'ont besoin d'aucune des fonctionnalités de l'espace de travail proposées ici :

  1. Nous avons déjà un graphique de dépendance basé sur package.json, nous connaissons donc l'ordre dans lequel exécuter la construction. En fait, lerna a une commande d'exécution générique qui peut l'exécuter dans l'ordre, et il n'est pas difficile d'écrire un outil qui ajoute également du parallélisme le cas échéant. skipLibCheck devrait rendre l'impact sur les performances négligeable, mais je ne l'ai pas vérifié.
  2. Lerna et Yarn créent déjà des liens symboliques vers les autres modules dans l'emplacement node_modules approprié. En conséquence, toutes les personnes à charge peuvent suivre le package.json de l'autre module, lire le champ types/typings et trouver le fichier de définition de type module.d.ts référencé.

Ce que nous n'avons pas, et ce que fournit le plugin que j'ai écrit, c'est un moyen de charger toutes les sources en même temps. Lorsque je dois apporter des modifications à deux modules ou plus en même temps, je ne veux pas que "aller à la définition" et "aller à la définition de type" m'envoient au fichier .d.ts. Je veux qu'il m'envoie à l'emplacement du code source d'origine, afin que je puisse peut-être le modifier. Sinon, je chargerais simplement le répertoire du projet individuel et les liens symboliques node_modules créés par lerna/yarn fonctionneraient simplement.

Même chose pour nous. Au lieu de Lerna, nous utilisons Rush pour calculer notre graphique de dépendance, mais l'effet est le même. Lorsque nous construisons des projets, tsc n'est qu'une des nombreuses tâches qui doivent être exécutées. Nos options de compilateur sont calculées par un système de build plus important, et nous passons à un modèle où tsconfig.json n'est pas un fichier d'entrée, mais plutôt une sortie générée (principalement au profit de VS Code).

Ce que nous n'avons pas, et ce que fournit le plugin que j'ai écrit, c'est un moyen de charger toutes les sources en même temps. Lorsque je dois apporter des modifications à deux modules ou plus en même temps, je ne veux pas que "aller à la définition" et "aller à la définition de type" m'envoient au fichier .d.ts. Je veux qu'il m'envoie à l'emplacement du code source d'origine, afin que je puisse peut-être le modifier.

+1 ce serait génial.

Si nous rêvons d'une meilleure prise en charge multi-projets, ma première demande serait un service de compilation, quelque chose comme le fonctionnement de VS Code IntelliSense. Nos builds seraient nettement plus rapides si Rush pouvait invoquer tsc 100 fois sans avoir à faire tourner le moteur du compilateur 100 fois. Le compilateur est l'une de nos étapes de construction les plus coûteuses. Dans un monorepo, les temps de construction sont vraiment importants.

@iclanton @nickpape-msft @qz2017

Oui s'il vous plaît!

Je pense que l'un des résultats les plus utiles du système de projet serait si
'aller à la définition' est allé au fichier source au lieu du fichier d.ts et
'trouver toutes les références' recherché dans l'arborescence des références du projet.

Vraisemblablement, cela déverrouillerait également les refactorisations de type « renommage global ».

Le jeu. 9 novembre 2017 à 21h30 Salvatore Previti [email protected]
a écrit:

Oui s'il vous plaît!

-
Vous recevez ceci parce que vous êtes abonné à ce fil.
Répondez directement à cet e-mail, consultez-le sur GitHub
https://github.com/Microsoft/TypeScript/issues/3469#issuecomment-343356868 ,
ou couper le fil
https://github.com/notifications/unsubscribe-auth/AANX6d19Zz7TCd_GsP7Kzb-9XJAisG6Hks5s07VXgaJpZM4E-oPT
.

Nous avons parlé avec

Nice, et dans ce cas, pourquoi faudrait-il spécifier des références spécifiques?
Il semble qu'il suffirait d'avoir un drapeau (comme projectRoot: true ).
Par exemple, quelle serait la différence entre references: ["foo"] et seulement references: [] ?
Parce que si je import "foo" ou import "bar" , les deux ignoreront alors tous les fichiers .ts .

Ainsi, dans ce cas, la proposition devient :

Compte tenu de ce tsconfig.json (TODO bikeshed sur projectRoot ):

{
  "extends": "../tsproject.json",
  "compilerOptions": {
    "projectRoot": true
   }
}

Lorsque tsc doit ensuite résoudre quelque chose en dehors du dossier du projet (y compris les fichiers sous les répertoires exclus), il ne regardera que les fichiers .d.ts (il peut également _préférer_ .d.ts et revenir aux tsconfig et/ou .ts s'il ne voit que cela).

Cela rend la résolution lors de la compilation rapide et simple.
Cela fonctionne pour les références monorepo (c'est- import "../foo" dire import "foo" dire
Cela fonctionne pour les packages NPM et leur représentation du code source.
Et cela supprime le besoin de résoudre les machines tsconfig.json pendant la compilation, bien que le message d'erreur s'il ne trouve pas le .d.ts sera moins utile.

Cela semble un peu trop beau pour être vrai, si c'est en effet aussi simple, alors j'oublie probablement quelque chose d'important :)


Comme d'autres le soulignent également, il est toujours très important qu'« IntelliSense » continue de fonctionner avec les fichiers .ts.

Donc, si une demande de « aller à la définition », « trouver des références » etc est alors fait, il devrait utiliser une cartographie inverse pour localiser le correspondant .ts fichier à partir du .d.ts fichier , il utilise donc loin.

Ce mappage peut être effectué en utilisant par exemple :

  • en utilisant une chaîne de commentaire intégrée comme //# sourceURL = ../src/foo.ts dans le .d.ts

    • un sourcemap plus élaboré pourrait être utilisé pour mapper à partir d'un .d.ts « enroulé » vers le .ts

  • en résolvant le fichier .js et en utilisant son sourcemap pour localiser le `.ts

Cela introduit le sujet de la reconstruction du .d.ts lorsque ce .ts est modifié, mais je ne suis pas sûr que cela devrait être résolu par cette proposition. Par exemple, c'est déjà le cas aujourd'hui qu'il doit y avoir un processus pour reconstruire les fichiers .js , même à partir du projet racine lui-même. Je suppose donc qu'il serait prudent de supposer que si cela est présent, il y aura également quelque chose pour reconstruire les dépendances. Cela pourrait être un tas de tsc parallèles pour chaque package, cela pourrait être tsbuild , cela pourrait être un IDE intelligent avec compileOnSave comportement similaire à

@pgonzal @michaelaird https://github.com/spion/typescript-workspace-plugin ne fait que cela - il restaure aller à la définition et trouver toutes les références pour un projet d'espace de travail multi-tsconfig fil/lerna/etc.

Intéressant... Je me demande si on pourrait faire en sorte que ça marche avec Rush. Nous allons jeter un oeil.

Pour info, un autre outil simple que nous avons construit dans le cadre de cet effort était wsrun

Semblable à lerna run , il exécute une commande pour tous les packages de l'espace de travail.

Étant donné que les dépendances dactylographiées doivent être compilées dans l'ordre, wsrun est capable d'exécuter une commande de package dans un ordre topologique basé sur leurs dépendances package.json . Il prend en charge le parallélisme lors de la construction. Ce n'est pas parallèle de manière optimale, mais cela peut être amélioré plus tard.

Juste une note, oao est un autre outil monorepo pour le fil. Il a récemment ajouté la prise en charge de l'ordre « topologique » des commandes.

Je voudrais juste poster le mot "refactoring" ici car c'est un objectif important pour nous, bien que probablement tangentiel à la proposition actuelle (https://github.com/Microsoft/TypeScript/issues/3469#issuecomment-341317069) et pas souvent mentionné dans ce numéro.

Notre cas d'utilisation est un monorepo avec plusieurs projets TS, il peut être réduit à un common-library plus quelques applications : app1 et app2 . Lorsque vous travaillez dans l'IDE, ceux-ci doivent être traités comme un seul projet, par exemple, le renommage doit fonctionner sur les trois modules, mais app1 et app2 sont également deux cibles de construction distinctes. Bien que je convienne que la construction est généralement une préoccupation distincte, la vérité est que nos applications sont assez petites et que faire quelque chose comme cd app1 && tsc nous conviendrait parfaitement.

Si TypeScript propose un bon moyen de prendre en charge cela, ce serait génial.

Pour la refactorisation/références inter-projets monorepo, j'ai trouvé que cette configuration fonctionnait pour moi si vous travaillez en vscode :

racine tsconfig :

"compilerOptions": {
   "baseUrl": ".",
   // global types are different per project
   "types": [],
   "paths": {
      "lib": ["packages/lib/src"],
      "xyz1": ["packages/xyz1/src"],
      "xyz2": ["packages/xyz2/src"],
   }
},
"include": ["./stub.ts"], // empty file with export {} to stop vscode complaining about no input files
"exclude": ["node_modules"]

packages/xyz1/tsconfig.json

{
  "extends": "../../tsconfig",
   "compilerOptions": {
      "types": ["node"],
   },
   "include": ["src/**/*"]
}

packages/xyz2/tsconfig.json

{
  "extends": "../../tsconfig",
   "compilerOptions": {
      "types": ["webpack-env"]
   },
  "include": ["src/**/*"]
} 

packages/lib/tsconfig.json

{
   "extends": "../../tsconfig",
    "compilerOptions": { ... },
    "include": ["src/**/*"],
    // special file to load referenced projects when inside in lib package, without it they won't be 
    // visible until you open some file in these projects 
    "files": ["./references.ts"],
}

packages/lib/tsconfig-build.json

{
   "extends": "./tsconfig",
    // exclude referenced projects when building
   "files": []
}

packages/lib/references.ts

import "xyz1";
import "xyz2";

export {};

Vous avez besoin correct main propriété dans le forfait de package.json et peut - être types même pour des paquets non-lib, par exemple:

  "main": "src/main.tsx",
  "types": "src/main.tsx",

De cette façon, refactoriser/renommer quelque chose dans lib refactorisera également les références dans xyz1 et xyz2 . De cette manière, les projets pourraient également avoir différentes bibliothèques globales/cibles.

Chez Gradle, ils l'appellent simplement - construction composite .

Au fait, est-ce que je peux commencer si je veux contribuer au compilateur TypeScript ? J'ai cloné le repo et ce n'est pas une mince affaire (j'ai l'habitude de lire la source : angular, ionic, express quand je ne peux pas l'obtenir à partir de la doc ou que je suis loin d'Internet...) J'ai vraiment besoin de l'aide d'un pour m'indiquer le chemin à parcourir, s'il vous plaît.

Merci!
Un avenir radieux pour TypeScript.

J'ai un prototype que j'aimerais que les gens essaient s'ils utilisent des chemins de modules relatifs. Il existe un exemple de dépôt sur https://github.com/RyanCavanaugh/project-references-demo qui décrit le scénario de base et montre comment cela fonctionne.

Pour essayer localement :

git clone https://github.com/RyanCavanaugh/TypeScript
git checkout pr-lkg
npm install
npm run build
npm link

Puis dans ton autre dossier :

npm link typescript

Je garderai la balise pr-lkg pointant sur le dernier commit de travail au fur et à mesure que les choses changent. Ensuite sur mes todos:

  • [x] Ajouter de meilleurs messages d'erreur lorsque les projets dépendants ne sont pas générés
  • [ ] Appliquer le comportement de redirection aux chemins de modules non relatifs
  • [ ] Exposer une API pour les outils de construction à consommer

@RyanCavanaugh Je ne peux pas vraiment le construire en raison d'erreurs telles que le module del manquant ou Error: Cannot find module 'C:\github\TypeScript\built\local\tsc.js' (peut-être votre chemin local ?) Mais dans l'ensemble, la structure est superbe.

S'agit-il uniquement de tsc ou cela prendra-t-il également en compte les refactorisations à l'échelle du projet dans VSCode avec le serveur de langue ?

... aussi la balise pr-lkg est manquante.

La balise est là (ce n'est pas une branche). Voir https://github.com/RyanCavanaugh/TypeScript/tree/pr-lkg

Le references dans tsconfig.json est un opt-in par dépendance, ne serait-il pas plus logique qu'il s'applique à tout ce qui se résout en dehors de rootDir ?

J'imagine quelque chose comme une propriété sandbox qui pourrait ressembler à :

# tsconfig.json
{
  "compilerOptions": {
    "outDir": "lib",
    "sandbox": "."
  },
  "include": ["src/index.ts"]
}

Sandbox définirait également rootDir sur la même valeur. Au lieu de fournir explicitement des chemins vers des répertoires contenant tsconfig.json , la résolution de module normale s'appliquerait et vous pourriez rechercher dans l'arborescence FS pour trouver le tsconfig.json automatiquement.

# package.json
{
  "name": "animals",
  "module": "src",
  "typings": "lib",
  "dependencies": {
    "core": "*"
  }
}

Par ici:

  • Vous n'avez pas besoin de maintenir deux listes synchronisées ( references et dependencies ).
  • Le package n'a aucune connaissance du système de fichiers en dehors du sien.
  • Utilisez une stratégie de résolution de module de nœud au lieu de chemins personnalisés.

@RyanCavanaugh , pour autant que je ces changements et je ne pourrai pas envoyer de tels projets pour test, par exemple, sur travis-ci.org. Droit?

Notes d'une rencontre aujourd'hui avec @billti / @mhegazy


  • Find References / Rename doit fonctionner, au moins pour les scénarios où le contexte de compilation nécessaire pour déterminer la fermeture des projets tient en mémoire
  • Le renommage à l'échelle de la solution marchera pour trouver un fichier "solution", travaillera en arrière pour trouver des projets de référencement, puis chargera l'
  • tsbuild doit gérer un mode -w
  • Les solutions ne peuvent pas avoir de sous-projets provenant de l'extérieur du dossier de solutions
  • Besoin d'une documentation claire pour, par exemple, gulp, webpack

Barre latérale : ce changement de nom ne fonctionne pas aujourd'hui

function f() {
  if (Math.random() > 0.5) {
    return { foo: 10 };
  } else {
    return { foo: 20 };
}
// rename foo here doesn't rename *both* instances in the function body
f().foo;

Merci Ryan, Mohamed et Bill. Faire fonctionner le scénario Rechercher des références/Renommer sur plusieurs projets était l'un des principaux cas d'utilisation que j'avais à l'esprit lorsque j'ai formulé la plainte initiale concernant le fait que TypeScript ne prend pas en charge les projets de taille moyenne. Les projets de taille moyenne sont modulaires mais pas de grande envergure. La proposition et le travail que j'ai vus ici jusqu'à présent ressemblent davantage à un jeu d'évolutivité. C'est très important pour la santé à long terme de TypeScript, mais c'est surtout avantageux pour les grands projets, pas pour les moyens. Les choses que j'entends dans ce commentaire de Ryan ressemblent davantage à ce qui est nécessaire pour améliorer l'ergonomie du développement de projets de taille moyenne en TypeScript.

Comme toujours, merci beaucoup à toute l'équipe TypeScript pour vos efforts ! Vous faites un travail formidable.

Il y a une astuce avec l'espace de travail lerna/yarn qui vous rendra la vie beaucoup plus facile.
Pointez les entrées main et types dans votre package.json des sous-projets vers votre src/index. ts et le scénario Rechercher des références/Renommer fonctionnera simplement.
Et vous pourrez compiler l'intégralité de votre projet avec un seul tsc en cours d'exécution.
Vous pouvez le faire pour certains de vos packages, vous pouvez le faire pour tous. ton appel.

Il y a quelques inconvénients et pièges (si vous avez une augmentation dans un package, ou l'importation de symboles globaux, cela polluera l'ensemble de votre programme), mais en général, cela fonctionne très bien.
Lorsque vous souhaitez publier sur NPM, il vous suffit de définir les principaux et types sur les valeurs appropriées (dans le cadre de votre build, ou ainsi)

Avec la configuration ci-dessus, j'ai plus ou moins toutes les fonctionnalités attendues

voici une astuce avec lerna/yarn workspace qui vous facilitera grandement la vie.
Pointez les entrées main et types dans votre package.json des sous-projets vers votre fichier src/index.ts, et le scénario Find References/Rename fonctionnera simplement.

D'après mon expérience, le problème avec cette configuration est que TypeScript commencera à traiter les fichiers ts des packages _external_ comme s'ils étaient les _sources_ du package qui les nécessite, et non comme des bibliothèques externes. Cela provoque un certain nombre de problèmes.

  • Les packages externes sont compilés plusieurs fois, à chaque fois en utilisant le tsconfig du package _requireing_. Si les packages requis ont des tsconfigs différentes (par exemple des bibliothèques différentes), cela peut provoquer l'apparition de fausses erreurs de compilation sur le package requis jusqu'à ce qu'il soit à nouveau compilé.

  • Les packages exigeants se compilent également plus lentement car ils contiennent plus de fichiers que nécessaire.

  • Le rootDir de tous les packages devient le répertoire de niveau supérieur, permettant potentiellement l'inclusion directe de n'importe quel fichier TS de n'importe quel package, au lieu de simplement inclure à partir de index . Si les développeurs ne font pas attention, ils peuvent contourner l'API de package requise. De plus, si le processus de génération n'est pas robuste, les packages requis peuvent finir par contenir du code dupliqué provenant du package requis qui était censé être externe.

Dans nos projets, nous avons exclu de dépendre des fichiers TS en raison des inconvénients. Toutes les dépendances inter-paquets sont sur les fichiers index.d.ts , donc le compilateur les traite comme externes et tout va bien.

Bien sûr, dépendre de .d.ts a le problème de nécessiter une construction en plusieurs étapes (impossible avec des outils tels que webpack) et le problème d'une mauvaise expérience IDE (renommés, références ne dépassant pas les limites du package ).

Je suis d'accord avec certains des autres sentiments - typescript est un compilateur, pas un système de construction. Nous avons besoin de nos outils pour prendre en charge de meilleures constructions multi-projets. Je sais qu'il existe des options dans la communauté qui commencent à le faire. Par exemple, C# a un compilateur nommé Roslyn et un outil de génération nommé MSBuild.

Discussion aujourd'hui avec @mhegazy sur la façon de faire fonctionner le renommage avec aussi peu de peine que possible.

Le pire des cas non dégénéré pour renommer ressemble à ceci :

// alpha.ts
const v = { a: 1 };
export function f() { return v; }
export function g() { return v; }

// alpha.d.ts (generated)
export function f(): { a: number };
export function g(): { a: number };

// beta.ts (in another project)
import { f } from '../etc/alpha';
f().a;

// gamma.ts (in yet another project)
import { g } from '../etc/alpha';
g().a;

L'observation clé est qu'il est impossible de savoir que renommer f().a devrait renommer g().a moins que vous ne puissiez voir alpha.ts pour corréler les deux.

Une esquisse du plan de mise en œuvre :

  • Avoir des représentations "riches" et "allégées" en mémoire SourceFile des fichiers .d.ts. Les fichiers « Lean » sont lus à partir du disque ; les "riches" sont produits à la suite de la génération de .d.ts en mémoire
  • Les fichiers source « riches » .d.ts ont des références depuis leurs nœuds d'identifiant vers le(s) nom(s) d'origine dans le fichier source d'origine
  • Lors du changement de nom, nous allons d'abord en définition sur le symbole en question, comme d'habitude. Si cela provient d'un .d.ts référencé par le projet, nous le chargeons de l'implémentation et créons son fichier .d.ts "Rich"

    • Remarque : ce processus est itératif, c'est-à-dire qu'il peut nécessiter de remonter plusieurs niveaux jusqu'à ce qu'il atterrisse dans un fichier d'implémentation réel (celui qui n'est pas une redirection .d.ts en raison d'une référence de projet)

  • Utilisez maintenant les pointeurs "riches" pour déterminer quels autres identifiants dans les .d.ts du projet proviennent du même identifiant de fichier source
  • Dans chaque projet en aval, recherchez l'identifiant renommé et voyez si son résultat "go-to-def" est l' un des emplacements "démarrés à partir du même symbole" dans le .d.ts
  • Effectuer le renommage dans le fichier d'implémentation

@RyanCavanaugh ira à la définition/trouver que toutes les références fonctionneront avec ce modèle ?

Notes d'une discussion précédente avec Anders et Mohamed autour d'un grand nombre de questions ouvertes

  • Est-ce que prepend s'applique également à .d.ts ? Oui
  • Que faisons-nous des @internal dans la branche dogfood ? Nous devons conserver les déclarations internes dans les fichiers .d.ts locaux mais ne voulons pas qu'elles apparaissent dans les versions de sortie

    • Supprimer --stripInternal

    • Mais ne le dépréciez pas (encore...?)

    • Ryan pour écrire l'outil remove-internal (fait)

    • build -> Le processus LKG supprime les déclarations @internal

  • Que se passe-t-il si vous modifiez un fichier .d.ts de par exemple @types ?

    • Besoin de forcer manuellement une reconstruction si vous voulez voir d'éventuelles nouvelles erreurs ☹️

    • Pourrait éventuellement écrire un fichier "fichiers que j'ai regardé.txt" dans le dossier de sortie si cela devient vraiment problématique

  • Le noEmitOnError obligatoire ? Oui.

    • Sinon, les reconstructions masqueraient les erreurs !

  • referenceTarget -> composable ✨ 🚲 🏡 ✨
  • Qu'en est-il des projets de nœud terminal qui ne souhaitent pas que la déclaration soit émise, mais qui souhaitent une reconstruction rapide ?

    • tsbuild ou l'équivalent peuvent vérifier leur conformité aux exigences non pertinentes en amont de composable

  • Références circulaires, (comment) fonctionnent-elles ?

    • Par défaut, non

    • Vous pouvez spécifier par exemple { path: "../blah", circular: true } si vous voulez faire cela

    • Si vous le faites, il vous appartient de vous assurer que votre build est déterministe et atteint toujours un point fixe (peut-être pas ?!)

    • Le remappage d'une importation circulaire:true est un remappage facultatif (mais prioritaire)

Recueil

  • @weswigham a une autre idée de renommer dont nous devons discuter avec @mhegazy

J'ai déjà perdu. Je voulais surtout garder l'interprétation des sourcesmaps hors du compilateur (responsabilités séparées pour des outils séparés) mais pendant votre absence, j'ai quand même travaillé à l'ajouter (car une définition apparemment transparente est souhaitable).

@RyanCavanaugh
Est-ce que renommer/trouver toutes les références fonctionnent dans les projets référencés après la fusion #23944 ? Devrions-nous également utiliser composite: true et projectReferences: [] au cas où seuls les services linguistiques (mais pas tsbuild) sont nécessaires ?

Est-ce que renommer/trouver toutes les références fonctionnent dans les projets référencés après la fusion #23944 ?

pas encore. mais nous y travaillons ensuite.

Devrions-nous également utiliser composite: true et projectReferences: [] au cas où seuls les services linguistiques (mais pas tsbuild) sont nécessaires ?

je ne suis pas sûr de comprendre la question .. qu'entendez-vous par « service linguistique » et non « construire » ?

je ne suis pas sûr de comprendre la question .. qu'entendez-vous par « service linguistique » et non « construire » ?

Je ne suis intéressé que par la prise en charge de l'éditeur (renommer/trouver toutes les références/etc...) sur plusieurs projets en monorepo, pas dans le nouvel outil de construction (aka build mode ) (#22997) puisque j'utilise babel pour ma compilation.

Cela devrait juste fonctionner. build est une fonctionnalité opt-in, vous n'êtes pas obligé de l'utiliser si vous ne le souhaitez pas.. similaire à la façon dont tsc n'est pas requis pour votre expérience de service linguistique dans VSCode par exemple.

Cependant, vous devrez probablement créer des déclarations et des cartes de déclaration pour produire les métadonnées requises pour que les références inter-projets fonctionnent.

Je ne sais pas si j'ai bien compris tous les aspects de la proposition, mais serait-il possible que des projets individuels n'en fassent pas référence à d'autres par chemin, mais par nom à la place ? Le projet d'espace de travail devrait avoir un moyen de spécifier chaque chemin de projet, similaire aux espaces de travail de fil via des globs, ou peut-être en répertoriant chaque nom de projet individuel :

En gros, au lieu de :

"dependencies": [
    "../common", 
    "../util"
],

Pouvons-nous s'il vous plaît avoir

"dependencies": [
    "common", 
    "util"
],

et avoir un espace de travail tsconfig.json

"workspace": {
  "common": "packages/common",
  "util": "packages/util"
}

Ou mieux encore, la syntaxe des chemins :

"workspace": {
  "*":"packages/*"
}

Avantages:

  • possibilité de spécifier différentes règles de recherche en fonction du système de module
  • possibilité, par exemple, de se replier sur node_modules lorsque le package est téléchargé en dehors de l'espace de travail
  • possibilité d'agréger librement plusieurs espaces de travail sur lesquels travailler en clonant plusieurs dépôts et en ayant une configuration différente pour les chemins, afin que vous puissiez travailler sur encore plus de packages en parallèle.

Ou à tout le moins, pouvons-nous avoir les noms de non-chemin (ceux qui ne commencent pas par './' ou '../') réservés pour une utilisation future...

Je ne sais pas à quel point cela est lié, mais Yarn 1.7 a récemment introduit un concept "d'espaces de travail ciblés", voir ce billet de blog .

Est-ce que quelqu'un ici connaît suffisamment les deux espaces de travail et le travail que @RyanCavanaugh fait autour des références de projet TypeScript / du mode de construction pour peut-être laisser un commentaire expliquant s'ils se rapportent? Mon intuition est que _quelque part_ entre les espaces de travail Yarn (npm les obtiendra aussi cette année) et les futures versions de TypeScript se trouve un bon support pour les monorepos avec plusieurs projets et bibliothèques partagées. (Nous ressentons la douleur, actuellement.)

J'aimerais vraiment avoir une mise à jour sur l'avancement de cette fonctionnalité. Nous prévoyons de déplacer Aurelia vNext dans un monorepo d'ici un mois environ. Notre nouvelle version est 100% TypeScript et nous aimerions utiliser un système de projet TS officiel plutôt que Lerna, si nous le pouvons. Nous sommes également heureux d'être les premiers à adopter/tester les nouvelles fonctionnalités :)

La prise en charge principale et goto def utilisant la prise en charge de la tsc --b pour la prise en charge des builds est déjà disponible et est destiné à TS 3.0. La base de code dactylographié a été déplacée pour l'utiliser. Nous testons actuellement ce support en utilisant le dépôt typescript.

Ce qu'il faut encore faire à ce stade : 1. trouver toutes les références/renommer pour travailler sur des scénarios multi-projets. 2. l'adressage de la mise à jour des fichiers .d.ts en arrière-plan dans l'éditeur, et 3. la prise en charge de --watch pour les scénarios multi-projets. aussi beaucoup, beaucoup de tests.

Ce billet est sur les livres depuis 3 ans. Encore 3/6 articles en suspens ?

@claudduguay Il s'agit d'un changement fondamental par rapport aux projets pris en charge par TypeScript, il est temps de célébrer, ne pensez-vous pas ? Je suis extrêmement heureux pour cela!

@mhegazy C'est une excellente nouvelle. Je suis très heureux d'apprendre que l'équipe TS le nourrit également sur son propre projet. J'attends avec impatience que les dernières choses soient terminées et que cela soit une option pour Aurelia :) Dès qu'il y aura de la documentation sur la configuration, nous déplacerons notre vNext vers le nouveau système de projet. Je ne peux pas attendre !

@mhegazy Pouvez-vous nous expliquer comment tout cela fonctionnerait avec les fichiers package.json et les projets basés sur les modules ES2015 ? Par exemple, dans Aurelia vNext, nous avons des packages comme @aurelia/kernel , @aurelia/runtime , @aurelia/jit , etc. Ce sont les noms de modules qui seront utilisés dans import déclarations tout au long des différents projets. Comment le compilateur TS comprendra-t-il que ces noms de modules correspondent aux différents dossiers référencés ? Va-t-il récupérer les fichiers package.json placés dans chaque dossier référencé ? En quoi cela différera-t-il de Lerna ou Yarn Workspaces ? Mon premier regard sur les liens ci-dessus me fait penser que je devrais utiliser des projets TS (build) en combinaison avec Lerna (dep linking et publication) pour obtenir une solution fonctionnelle, mais je ne vois pas comment TS va se construire correctement s'il ne peut pas s'intégrer avec package.json et node_modules. La source du repo TS est assez différente de votre projet Lerna moyen (rien de tel vraiment), donc je commence à me demander si cela va pouvoir répondre à nos besoins. Toute autre information que vous pouvez fournir, et esp. une configuration de solution de démonstration fonctionnelle similaire à celle que j'ai décrite ici serait très utile. Merci!

Je partage les mêmes questions que @EisenbergEffect. En particulier, j'espère également que cela fonctionnera bien avec un monorepo géré par lerna .

Deux scénarios monorepo à considérer

Commençons par une configuration où vous avez tout lié symboliquement avec lerna :

/packages
  /a
    /node_modules
      /b -> symlink to b with package.json "types" pointing to dist/index.d.ts
  /b
    /dist
      /index.d.ts -> built output of entry point declaration file

La chose clé que nous voulons arriver ici est de reconstruire b nous avons construit a ssi a est obsolète. Nous ajoutons donc "references": [ { "path": "../b" } ] aux a de tsconfig.json et exécutons tsc --build dans a pour obtenir les versions amont correctes de b . Dans ce monde, les références de projet servent simplement de représentation du graphe de construction et permettent des reconstructions d'incréments plus intelligentes. Idéalement, lerna et TS pourraient coopérer ici et refléter les dépendances de package.json en tsconfig.json cas échéant.

Un autre scénario (probablement moins courant) serait si vous ne faisiez pas de lien symbolique mais que vous vouliez toujours « agir comme si » vous travailliez sur un ensemble de packages en direct. Vous pouvez le faire aujourd'hui avec une cartographie de chemin assez fastidieuse, et certaines personnes le font. Les références de projet ici aideraient également à l'ordre de construction, mais il serait très souhaitable d'avoir la prise en charge d'une propriété dans le fichier tsconfig de référence pour créer automatiquement un mappage de chemin chaque fois qu'il est référencé ; par exemple si vous aviez

{
  "compilerOptions": { "outDir": "bin" },
  "package": "@RyanCavanaugh/coolstuff"
}

puis l'ajout de "references": [{ "path": "../cool" }] ajouterait automatiquement un mappage de chemin de @RyanCavanaugh/coolstuff -> ../cool/bin/ . Nous n'avons pas encore ajouté cela, mais nous pouvons l'examiner s'il s'avère que c'est un scénario plus courant.

Idéalement, lerna et TS pourraient coopérer ici et refléter les dépendances de package.json dans tsconfig.json, le cas échéant

Plutôt que de compter sur des outils externes, nous pourrions choisir de lire votre package.json (à condition qu'il soit à côté de votre tsconfig) comme références potentielles si composite: true est défini (vérifiez si chaque package résolu a un tsconfig.json , s'il en a un, considérez-le comme un nœud de projet constructible et répétez). Puisque tout est lié symboliquement à sa place, nous ne devrions même pas avoir besoin de modifier la résolution (beaucoup ? aucune ?) pour gérer l'espace de travail. Lerna ne met en place aucun élément spécifique à ts (ou spécifique à la construction), comme c'est le cas, il met simplement tout en place et gère la gestion des versions. Ce serait une optimisation par rapport à ce que nous faisons aujourd'hui, c'est-à-dire charger les fichiers ts (puisque nous préférons ceux-ci aux déclarations) et tout recompiler indépendamment de l'obsolescence.

@RyanCavanaugh Cela semble assez excitant. Une idée si cela fonctionnera avec la stratégie de lien symbolique common/temp/package.json qui contient le sur-ensemble de toutes les dépendances pour tous les packages du référentiel. Ensuite, nous utilisons pnpm pour effectuer une seule opération d'installation pour ce package synthétique. (PNPM utilise des liens symboliques pour créer un graphe orienté acyclique au lieu de la structure arborescente de NPM, ce qui élimine les instances de bibliothèque dupliquées). Ensuite, Rush crée un dossier node_modules pour chaque projet du référentiel, composé de liens symboliques pointant vers les dossiers appropriés sous common/temp . Le résultat est entièrement compatible avec TypeScript et l'algorithme de résolution standard NodeJS. C'est très rapide car il existe un fichier d'emballage et une équation de version pour l'ensemble du référentiel, tout en permettant à chaque package de spécifier ses propres dépendances.

Nous ne mettons rien de spécial dans tsconfig.json pour ce modèle. Si une configuration TypeScript spéciale est requise pour la fonctionnalité goto-definition, nous voudrions idéalement la générer automatiquement lors de l'installation plutôt que de la stocker dans Git.

@pgonzal Merci pour le lien vers Rush ! Je n'avais pas encore vu ça. Je vais vérifier ça ce soir.

@RyanCavanaugh Merci pour l'explication. Votre premier scénario avec Lerna est le plus proche de ce que nous aurions. Voici notre référentiel UX avec TS et lerna comme exemple de quelque chose que nous voudrions utiliser le nouveau support de projet sur https://github.com/aurelia/ux

@weswigham On dirait que ce que vous décrivez conviendrait également à notre scénario. Exemple de dépôt ci-dessus.

Juste une note que dans le cas des espaces de travail de fil, les modules ne sont pas liés symboliquement dans le répertoire de chaque package individuel, à la place ils sont liés dans l'espace node_modules travail node_modules .

C'est d'ailleurs pourquoi je pense que les références qui ne commencent pas par un point ('./' ou '../') doivent être réservées pour le futur. Espérons que celles-ci finiront par être des "références nommées", gérées via la stratégie de résolution de module active au lieu d'être traitées comme des chemins relatifs.

@spion nous utiliserons simplement un nom de propriété autre que path pour cela si nécessaire (par exemple "references": [ { "module": "@foo/baz" } ] ); Je ne veux pas prêter à confusion où "bar" et "./bar" signifient la même chose dans files mais une chose différente dans references

Documents/poste de blog en cours ci-dessous (le modifiera en fonction des commentaires)

J'encourage tous ceux qui suivent ce fil à essayer. Je travaille maintenant sur le scénario monorepo pour résoudre les derniers bugs/fonctionnalités et je devrais bientôt avoir des conseils à ce sujet


Références du projet

Les références de projet sont une nouvelle fonctionnalité de TypeScript 3.0 qui vous permettent de structurer vos programmes TypeScript en morceaux plus petits.

En faisant cela, vous pouvez considérablement améliorer les temps de génération, appliquer une séparation logique entre les composants et organiser votre code de manière nouvelle et meilleure.

Nous introduisons également un nouveau mode pour tsc , le drapeau --build , qui fonctionne main dans la main avec les références de projet pour permettre des compilations TypeScript plus rapides.

Un exemple de projet

Regardons un programme assez normal et voyons comment les références de projet peuvent nous aider à mieux l'organiser.
Imaginez que vous ayez un projet avec deux modules, converter et units , et un fichier de test correspondant pour chacun :

/src/converter.ts
/src/units.ts
/test/converter-tests.ts
/test/units-tests.ts
/tsconfig.json

Les fichiers de test importent les fichiers d'implémentation et effectuent des tests :

// converter-tests.ts
import * as converter from "../converter";

assert.areEqual(converter.celsiusToFahrenheit(0), 32);

Auparavant, cette structure était plutôt difficile à utiliser si vous utilisiez un seul fichier tsconfig :

  • Il était possible pour les fichiers d'implémentation d'importer les fichiers de test
  • Il n'était pas possible de compiler test et src en même temps sans que src apparaisse dans le nom du dossier de sortie, ce que vous ne voulez probablement pas
  • Changer uniquement les éléments internes dans les fichiers d'implémentation nécessitait une nouvelle vérification du
  • Changer uniquement les tests nécessitait une nouvelle vérification de l'implémentation, même si rien n'a changé

Vous pouvez utiliser plusieurs fichiers tsconfig pour résoudre certains de ces problèmes, mais de nouveaux apparaissent :

  • Il n'y a pas de vérification de mise à jour intégrée, vous finissez donc toujours par exécuter tsc deux fois
  • L'appel de tsc deux fois augmente le temps de démarrage
  • tsc -w ne peut pas s'exécuter sur plusieurs fichiers de configuration à la fois

Les références de projet peuvent résoudre tous ces problèmes et plus encore.

Qu'est-ce qu'une référence de projet ?

tsconfig.json fichiers references . C'est un tableau d'objets qui spécifie les projets à référencer :

{
    "compilerOptions": {
        // The usual
    },
    "references": [
        { "path": "../src" }
    ]
}

La propriété path de chaque référence peut pointer vers un répertoire contenant un fichier tsconfig.json , ou vers le fichier de configuration lui-même (qui peut avoir n'importe quel nom).

Lorsque vous référencez un projet, de nouvelles choses se produisent :

  • L'importation de modules à partir d'un projet référencé chargera à la place son fichier de déclaration de sortie ( .d.ts )
  • Si le projet référencé produit un outFile , les déclarations du fichier de sortie .d.ts seront visibles dans ce projet
  • Le mode build (voir ci-dessous) construira automatiquement le projet référencé si nécessaire

En vous séparant en plusieurs projets, vous pouvez considérablement améliorer la vitesse de vérification des types et de compilation, réduire l'utilisation de la mémoire lors de l'utilisation d'un éditeur et améliorer l'application des regroupements logiques de votre programme.

composite

Les projets référencés doivent avoir le nouveau paramètre composite activé.
Ce paramètre est nécessaire pour garantir que TypeScript peut déterminer rapidement où trouver les sorties du projet référencé.
L'activation du drapeau composite change plusieurs choses :

  • Le paramètre rootDir , s'il n'est pas défini explicitement, prend par défaut le répertoire contenant le fichier tsconfig
  • Tous les fichiers d'implémentation doivent correspondre à un modèle include ou être répertoriés dans le tableau files . Si cette contrainte est violée, tsc vous indiquera quels fichiers n'ont pas été spécifiés
  • declaration doit être activé

declarationMaps

Nous avons également ajouté la prise en charge des cartes de source de déclaration .
Si vous activez --declarationMap , vous pourrez utiliser les fonctionnalités de l'éditeur telles que "Aller à la définition" et Renommer pour naviguer et éditer de manière transparente le code à travers les limites du projet dans les éditeurs pris en charge.

prepend avec outFile

Vous pouvez également activer l'ajout de la sortie d'une dépendance à l'aide de l'option prepend dans une référence :

   "references": [
       { "path": "../utils", "prepend": true }
   ]

L'ajout d'un projet inclura la sortie du projet au-dessus de la sortie du projet en cours.
Cela fonctionne à la fois pour les fichiers .js et pour les fichiers .d.ts , et les fichiers de carte source seront également émis correctement.

tsc n'utilisera jamais que les fichiers existants sur le disque pour effectuer ce processus, il est donc possible de créer un projet où un fichier de sortie correct ne peut pas être généré car la sortie de certains projets serait présente plus d'une fois dans le fichier résultant .
Par exemple:

  ^ ^ 
 /   \
B     C
 ^   ^
  \ /
   D

Il est important dans cette situation de ne pas ajouter de préfixe à chaque référence, car vous vous retrouverez avec deux copies de A dans la sortie de D - cela peut conduire à des résultats inattendus.

Mises en garde pour les références de projet

Les références de projet comportent quelques compromis dont vous devez être conscient.

Étant donné que les projets dépendants utilisent des fichiers .d.ts qui sont créés à partir de leurs dépendances, vous devrez soit archiver certaines sorties de génération, soit générer un projet après l'avoir cloné avant de pouvoir naviguer dans le projet dans un éditeur sans voir de faux les erreurs.
Nous travaillons sur un processus de génération de .d.ts en coulisses qui devrait être en mesure d'atténuer cela, mais pour l'instant, nous recommandons d'informer les développeurs qu'ils doivent compiler après le clonage.

De plus, pour préserver la compatibilité avec les workflows de build existants, tsc ne construira --build .
Apprenons-en plus sur --build .

Mode de construction pour TypeScript

Une fonctionnalité attendue depuis longtemps est la construction incrémentielle intelligente pour les projets TypeScript.
Dans la version 3.0, vous pouvez utiliser le drapeau --build avec tsc .
C'est effectivement un nouveau point d'entrée pour tsc qui se comporte plus comme un orchestrateur de build qu'un simple compilateur.

L'exécution de tsc --build ( tsc -b en abrégé) effectuera les actions suivantes :

  • Retrouvez tous les projets référencés
  • Détecter s'ils sont à jour
  • Construire des projets obsolètes dans le bon ordre

Vous pouvez fournir tsc -b avec plusieurs chemins de fichiers de configuration (par exemple tsc -b src test ).
Tout comme tsc -p , il n'est pas nécessaire de spécifier le nom du fichier de configuration lui-même s'il s'appelle tsconfig.json .

tsc -b Ligne de commande

Vous pouvez spécifier n'importe quel nombre de fichiers de configuration :

 > tsc -b                                # Build the tsconfig.json in the current directory
 > tsc -b src                            # Build src/tsconfig.json
 > tsc -b foo/release.tsconfig.json bar  # Build foo/release.tsconfig.json and bar/tsconfig.json

Ne vous inquiétez pas de l'ordre des fichiers que vous transmettez sur la ligne de commande - tsc les réorganisera si nécessaire afin que les dépendances soient toujours construites en premier.

Il existe également des drapeaux spécifiques à tsc -b :

  • --verbose : imprime un journal détaillé pour expliquer ce qui se passe (peut être combiné avec tout autre indicateur)
  • --dry : Affiche ce qui serait fait mais ne construit rien en fait
  • --clean : Supprime les sorties des projets spécifiés (peut être combiné avec --dry )
  • --force : Agir comme si tous les projets étaient obsolètes
  • --watch : Mode montre (ne peut être combiné avec aucun drapeau sauf --verbose )

Mises en garde

Normalement, tsc produira des sorties ( .js et .d.ts ) en présence d'erreurs de syntaxe ou de type, sauf si noEmitOnError est activé.
Faire cela dans un système de construction incrémentielle serait très mauvais - si l'une de vos dépendances obsolètes avait une nouvelle erreur, vous ne la verriez qu'une seule fois car une construction ultérieure ignorerait la construction du projet maintenant à jour.
Pour cette raison, tsc -b agit comme si noEmitOnError était activé pour tous les projets.

Si vous archivez des sorties de compilation ( .js , .d.ts , .d.ts.map , etc.), vous devrez peut-être exécuter une compilation --force après un certain contrôle de source opérations selon que votre outil de contrôle de source conserve ou non les horodatages entre la copie locale et la copie distante.

msbuild

Si vous avez un projet msbuild, vous pouvez activer le mode de construction en ajoutant

    <TypeScriptBuildMode>true</TypeScriptBuildMode>

dans votre fichier proj. Cela permettra la construction incrémentielle automatique ainsi que le nettoyage.

Notez que comme avec tsconfig.json / -p , les propriétés de projet TypeScript existantes ne seront pas respectées - tous les paramètres doivent être gérés à l'aide de votre fichier tsconfig.

Certaines équipes ont mis en place des workflows basés sur msbuild dans lesquels les fichiers tsconfig ont le même ordre de graphe implicite que les projets gérés avec lesquels ils sont associés.
Si votre solution est comme celle-ci, vous pouvez continuer à utiliser msbuild avec tsc -p avec des références de projet ; ceux-ci sont entièrement interopérables.

Conseils

Structure globale

Avec plus tsconfig.json fichiers héritage du fichier de configuration pour centraliser vos options de compilateur communes.
De cette façon, vous pouvez modifier un paramètre dans un fichier plutôt que d'avoir à modifier plusieurs fichiers.

Une autre bonne pratique consiste à avoir un fichier "solution" tsconfig.json qui contient simplement references pour tous vos projets de nœuds feuilles.
Ceci présente un point d'entrée simple ; Par exemple, dans le référentiel TypeScript, nous exécutons simplement tsc -b src pour créer tous les points de terminaison car nous répertorions tous les sous-projets dans src/tsconfig.json
Notez qu'à partir de 3.0, ce n'est plus une erreur d'avoir un tableau files vide si vous avez au moins un reference dans un fichier tsconfig.json .

Vous pouvez voir ces modèles dans le référentiel TypeScript - voir src/tsconfig_base.json , src/tsconfig.json et src/tsc/tsconfig.json comme exemples clés.

Structuration des modules relatifs

En général, peu de choses sont nécessaires pour faire la transition d'un dépôt à l'aide de modules relatifs.
Placez simplement un fichier tsconfig.json dans chaque sous-répertoire d'un dossier parent donné et ajoutez reference s à ces fichiers de configuration pour correspondre à la superposition prévue du programme.
Vous devrez soit définir le outDir sur un sous-dossier explicite du dossier de sortie, soit définir le rootDir sur la racine commune de tous les dossiers du projet.

Structuration pour outFiles

La disposition des compilations utilisant outFile est plus flexible car les chemins relatifs importent moins.
Une chose à garder à l'esprit est que vous voudrez généralement ne pas utiliser prepend jusqu'au "dernier" projet - cela améliorera les temps de construction et réduira la quantité d'E/S nécessaire dans une construction donnée.
Le référentiel TypeScript lui-même est une bonne référence ici - nous avons des projets de "bibliothèque" et des projets de "point de terminaison" ; Les projets « endpoint » sont aussi petits que possible et n'intègrent que les bibliothèques dont ils ont besoin.

Structuration pour monorepos

À FAIRE : Expérimentez davantage et découvrez cela. Rush et Lerna semblent avoir des modèles différents qui impliquent des choses différentes de notre côté

Je recherche également des commentaires sur le #25164

@RyanCavanaugh Très belle écriture et l'excellente fonctionnalité, ce serait vraiment bien de l'essayer, en particulier. après avoir passé des jours à organiser notre grand projet en sous-projets avec des références de fichiers de configuration.

J'ai quelques remarques :

  1. Qu'est-ce qu'un monorepo ? Ce serait bien de décrire un peu plus ce cas d'utilisation.
  2. Dans la plupart des cas (en particulier pour les gros projets), de nombreux artefacts supplémentaires sont générés lors de la construction. Dans notre cas, il s'agit de CSS, HTML, fichiers image, etc., via gulp. Je me demande comment l'utilisation de tels outils de construction s'adapterait à cette nouvelle façon de faire les choses. Disons que j'aimerais exécuter watch non seulement sur les fichiers *.ts, mais sur tous nos autres fichiers (styles, balisage, etc.). Comment faire ça? Besoin de courir, disons gulp watch et tsc -b -w en parallèle ?

@vvs a monorepo est une collection de packages NPM généralement gérés par un outil comme Rush ou Lerna

Si vous utilisez gulp, vous voudriez utiliser un chargeur qui comprend les références de projet nativement pour obtenir la meilleure expérience. @rbuckton a fait du travail ici car certains développeurs utilisent un fichier gulpfile en interne ; peut-être qu'il peut peser sur à quoi ressemblent les bons modèles là-bas

@RyanCavanaugh Cela a l'air bien. Je suis très intéressé par les conseils de Lerna :)

@RyanCavanaugh ça a l'air super, je travaille actuellement à l'essayer avec notre lerna monorepo.

La seule chose peu claire pour moi dans votre article était l'option prepend . Je n'ai pas tout à fait compris à quel problème il s'adresse, dans quelle situation vous voudriez l'utiliser et ce qui se passe si vous ne l'utilisez pas.

C'est génial! Je travaille sur ts-loader et des projets connexes. Des modifications seront-elles probablement nécessaires pour prendre en charge cela dans les projets qui utilisent LanguageServiceHost / WatchHost TypeScript ?

(Voir https://github.com/TypeStrong/ts-loader/blob/master/src/servicesHost.ts pour un exemple de ce que je veux dire.)

Si c'est le cas, tous les conseils/RP sont reçus avec reconnaissance ! En fait, si vous vouliez que cela soit testé dans le monde des packs Web, je serais heureux de vous aider à sortir une version de ts-loader qui prend en charge cela.

Bien sûr, si ça "fonctionne", c'est encore mieux :sourire:

Bon travail!

@yortus @EisenbergEffect J'ai mis en place un exemple de dépôt de lerna sur https://github.com/RyanCavanaugh/learn-a avec un README décrivant les étapes que j'ai suivies pour le faire fonctionner.

Si je comprends bien, tsc -b X ne fera rien si tout (X et toutes ses dépendances et dépendances transitives) est à jour ? Vous vous demandez si c'est quelque chose que nous pourrions obtenir même sans le drapeau -b pour des projets individuels sans références ? (moins les dépendances dans ce cas, bien sûr)

C'est plutôt cool. J'ai tendance à utiliser un Lerna une configuration comme celle-ci (pour séparer le repo mono par fonction). Je suppose que cela fonctionnerait tout aussi bien.

{
"lerna": "2.11.0",
"paquets": [
"paquets/composants/ ","paquets/bibliothèques/ ",
" packages/frameworks/ "," packages/applications/ ",
"paquets/outils/*"
],
"version": "0.0.0"
}

Donc c'est disponible sur typescript@next ?

Je vais tester cela avec notre dépôt d'espace de travail de fil. Nous devons utiliser nohoist pour quelques modules qui ne prennent pas encore en charge les espaces de travail, donc ce sera bien de voir comment il le gère.

@RyanCavanaugh J'ai pris le repo pour un test ce soir. J'ai ouvert un problème sur le repo pour signaler certains problèmes que j'avais. Merci encore d'avoir mis tout ça ensemble. J'ai hâte de l'utiliser bientôt.

Vraiment intéressant! Actuellement dans mon entreprise, nous utilisons mon propre outil appelé mtsc pour prendre en charge le mode de surveillance de plusieurs projets en même temps. Nous avons environ 5 projets qui doivent être compilés et regardés dans le même référentiel.

Les projets ont des configurations différentes comme ; Ciblage ECMA (es5, es6), types (node, jest, DOM, etc.), émission (certains utilisent webpack et d'autres compilent eux-mêmes en js). Ils partagent tous une chose, et c'est le plugin tslint , le reste peut être tout différent. Mon outil exécute également tslint après une compilation de projet (par projet et arrêté si un projet se recompile avant que tslint ne soit terminé).

Ma principale préoccupation avec la proposition actuelle est que vous ne pouvez pas dire quels projets partagent quelles ressources. Nous avons un serveur et un projet client, qui utilisent tous les deux un dossier utilitaire spécial, mais dont nous ne voulons pas voir les erreurs de compilation deux fois. Mais cela peut être corrigé avec un filtre, donc ce n'est pas grave :)

J'ai essayé le nouveau mode --build avec notre monorepo lerna, qui se compose actuellement de 17 packages interdépendants. Il a fallu du temps pour que tout fonctionne, mais maintenant tout fonctionne, et pouvoir construire progressivement est une grande amélioration pour nous.

J'ai rencontré quelques problèmes que je décris ci-dessous. J'espère que ces commentaires seront utiles pour l'équipe TS et qu'ils pourront aider d'autres personnes à faire fonctionner le mode --build pour leurs projets.

Commentaires pour le mode tsc --build

1. Faux "\est obsolète car le fichier de sortie '2.map' n'existe pas"

J'ai reçu ce message pour chaque paquet de chaque version, donc chaque version est devenue une reconstruction complète même lorsque rien n'a été modifié. J'ai remarqué que @RyanCavanaugh a déjà corrigé ce problème dans #25281, donc ce n'est plus un problème si vous mettez à jour vers le 20180628 ou plus tard tous les soirs. Les problèmes suivants supposent que vous avez mis à jour au moins le 20180628 tous les soirs.

2. "\est à jour avec les fichiers .d.ts de ses dépendances" alors qu'il ne l'est pas

EDIT : signalé au n° 25337.

Pour reproduire ce problème, configurez le dépôt d' learn-a @RyanCavanaugh selon ses instructions . Exécutez tsc -b packages --verbose pour voir que tout est construit la première fois. Changez maintenant la ligne 1 de pkg1/src/index.ts en import {} from "./foo"; et enregistrez-la. Exécutez à nouveau tsc -b packages --verbose . La compilation de pkg2 est ignorée, même si pkg1 été modifié d'une manière qui casse la source de pkg2 . Vous pouvez maintenant voir un gribouillis rouge dans pkg2/src/index.ts . Construisez à nouveau avec tsc -b packages --force et l'erreur de construction s'affiche. Les problèmes suivants supposent de construire avec --force pour contourner ce problème.

3. Fichiers .d.ts générés provoquant des erreurs de construction « identifiant en double » dans les packages en aval

EDIT : signalé au n° 25338.

Pour reproduire ce problème, configurez le dépôt d' learn-a @RyanCavanaugh selon ses instructions . Exécutez maintenant lerna add @types/node pour ajouter les saisies Node.js aux trois packages. Exécutez tsc -b packages --force pour confirmer qu'il fonctionne toujours correctement. Ajoutez maintenant le code suivant à pkg1/src/index.ts :

// CASE1 - no build errors in pkg1, but 'duplicate identifier' build errors in pkg2
// import {parse} from 'url';
// export const bar = () => parse('bar');

// CASE2 - no build errors in pkg1 or in downstream packages
// import {parse, UrlWithStringQuery} from 'url';
// export const bar = (): UrlWithStringQuery => parse('bar');

// CASE3 - no build errors in pkg1 or in downstream packages
// export declare const bar: () => import("url").UrlWithStringQuery;

// CASE4 - no build errors in pkg1, but 'duplicate identifier' build errors in pkg2
// import {parse} from 'url';
// type UrlWithStringQuery = import("url").UrlWithStringQuery;
// export const bar = (): UrlWithStringQuery => parse('bar');

Décommentez un cas à la fois et exécutez tsc -b packages --force . Les cas 1 et 4 provoquent un déluge d'erreurs de construction dans pkg2 . Avec les cas 2 et 3, il n'y a pas d'erreurs de construction. La différence importante avec les cas 1 et 4 semble être la première ligne du pkg1/lib/index.d.ts généré :

/// <reference path="../node_modules/@types/node/index.d.ts" />

Les cas 2 et 3 ne génèrent pas cette ligne. Lorsque pkg2 est construit dans les cas 1 et 4, il inclut deux copies identiques des déclarations @types/node à des chemins différents, et cela provoque les erreurs « identifiant dupliqué ».

C'est peut-être par conception puisque les cas 2 et 3 fonctionnent. Cependant, cela semble assez déroutant. Il n'y a pas d'erreurs de construction ou d'avertissements dans pkg1 pour aucun de ces 4 cas, mais le comportement de construction en aval est très sensible au style exact des déclarations exportées. Je pense que soit (a) pkg1 devrait erreur pour les cas 1 et 4, ou (b) les quatre cas devraient avoir le même comportement de construction en aval, ou (c) il devrait y avoir des conseils clairs de l'équipe TS sur comment rédiger des déclarations pour éviter ce problème.

4. Problèmes avec import types Generata .d.ts fichiers lors de l' utilisation des espaces de travail de fil

Lorsque vous essayez de faire fonctionner correctement en mode de construction avec notre 17 paquet monorepo, je travaillais dans un certain nombre d'erreurs de construction causés par les chemins relatifs à import types Generata .d.ts fichiers. J'ai finalement compris que le problème était lié au levage des modules. C'est-à-dire que lorsque vous utilisez des espaces de travail de fil, tous les modules installés sont hissés dans le répertoire monorepo-root node_modules , y compris les liens symboliques pour tous les packages du monorepo. J'ai changé le monorepo pour utiliser la propriété packages dans lerna.json , ce qui fait que lerna utilise son propre algorithme d'amorçage sans levage, et cela a résolu le problème. C'est une approche meilleure/plus sûre de toute façon, bien que plus lente.

Je ne sais pas si TS a l'intention de prendre en charge les configurations par module, donc je n'ai pas développé de repro pour les problèmes que j'ai rencontrés, mais je pourrais essayer d'en faire un s'il y a un intérêt. Je pense que le problème peut être que certaines versions obtiennent le même type via le répertoire de niveau supérieur packages (selon les paramètres de tsconfig) et le répertoire de niveau supérieur node_modules (selon import dans les fichiers .d.ts générés). Cela fonctionne parfois en raison du typage structurel, mais échoue pour des éléments tels que les symboles uniques exportés.

5. Configuration répétitive

Configurer un monorepo pour utiliser lerna nécessite simplement de mettre quelque chose comme "packages": ["packages/*"] dans lerna.json . Lerna calcule la liste exacte des packages en développant globstars, puis calcule le graphique de dépendances exact en examinant les dépendances déclarées dans le package.json chaque package. Vous pouvez ajouter et supprimer des packages et des dépendances à volonté, et lerna maintient sans avoir besoin de toucher à sa configuration.

Le mode TypeScript --build implique un peu plus de cérémonie. Les modèles Glob ne sont pas reconnus, donc tous les packages doivent être explicitement répertoriés et maintenus (par exemple dans packages/tsconfig.json ) dans le dépôt d' learn-a @RyanCavanaugh . Le mode de construction ne regarde pas les dépendances de package.json , donc chaque paquet doit maintenir la liste des autres paquets dont il dépend à la fois dans son fichier package.json (sous "dependencies" ) ainsi que c'est tsconfig.json fichier "references" ).

C'est un inconvénient mineur, mais je l'inclus ici car j'ai trouvé le rigmarole perceptible par rapport à l'approche lerna's .

6. tsc crash avec des augmentations globales de modules

EDIT : signalé au n° 25339.

Pour reproduire ce problème, configurez le dépôt d' learn-a @RyanCavanaugh selon ses instructions . Exécutez maintenant lerna add @types/multer pour ajouter des saisies multer aux trois packages. Exécutez tsc -b packages --force pour confirmer qu'il fonctionne toujours correctement. Ajoutez maintenant la ligne suivante à pkg1/src/index.ts :

export {Options} from 'multer';

Exécutez à nouveau tsc -b packages --force . Le compilateur plante à cause d'une assertion violée. J'ai regardé brièvement la trace et l'assertion de la pile, et cela semble avoir quelque chose à voir avec l'augmentation globale de l'espace Express noms

merci @yortus pour le retour. compter l'apprécier. pour 3, je pense que c'est https://github.com/Microsoft/TypeScript/issues/25278.

Pour 4, je ne suis pas familier avec le levage de modules en tant que concept. pouvez-vous élaborer et/ou partager une reproduction ?

@mhegazy, beaucoup de ceux qui utilisent lerna et le fil utilisent des espaces de travail (y compris moi-même). Plus d'infos ici : https://yarnpkg.com/lang/en/docs/workspaces/

J'utilise actuellement des espaces de travail de fil, lerna, tsconfigs étendus où le tsconfig de base déclare paths partagé pour tous les packages avec le module hissé trouvé sous root/node_modules . Quand j'entends yarn et monorepo , je pense workspaces parce que c'est l'objectif même de la fonctionnalité - pour faciliter l'utilisation et réduire la duplication. Je m'attendais à ce que ce changement supprime simplement mon long/douloureux paths déclaré dans mon tsconfig de base.

Voici un exemple de notre tsconfig root monorepo (si cela peut vous aider):

{
  "extends": "./packages/build/tsconfig.base.json",
  "compilerOptions": {
    "baseUrl": "./packages",
    "paths": {
      "@alienfast/build/*": ["./build/src/*"],
      "@alienfast/common-node/*": ["./common-node/src/*"],
      "@alienfast/common/*": ["./common/src/*"],
      "@alienfast/concepts/*": ["./concepts/src/*"],
      "@alienfast/faas/*": ["./faas/src/*"],
      "@alienfast/math/*": ["./math/src/*"],
      "@alienfast/notifications/*": ["./notifications/src/*"],
      "@alienfast/ui/*": ["./ui/src/*"],
      "@alienfast/build": ["./build/src"],
      "@alienfast/common-node": ["./common-node/src"],
      "@alienfast/common": ["./common/src"],
      "@alienfast/concepts": ["./concepts/src"],
      "@alienfast/faas": ["./faas/src"],
      "@alienfast/math": ["./math/src"],
      "@alienfast/notifications": ["./notifications/src"],
      "@alienfast/ui": ["./ui/src"],
    }
  },
  "include": ["./typings/**/*", "./packages/*/src/**/*"],
  "exclude": ["node_modules", "./packages/*/node_modules"]
}

Je vais essayer de bifurquer pour un échantillon :
https://github.com/RyanCavanaugh/learn-a

Voici un PR à ne pas fusionner vers le dépôt de
https://github.com/RyanCavanaugh/learn-a/pull/3/files

Nous avons également utilisé le levage de modules dans Jupyterlab , avec du lerna et du fil. Cela nous permet essentiellement de partager nos dépendances installées entre tous nos packages, de sorte qu'elles n'existent qu'une seule fois dans le système de fichiers, au niveau du projet racine.

Je comprends que les espaces de travail sont un peu plus propres que d'avoir à utiliser la commande link entre tous les packages afin qu'ils puissent accéder les uns aux autres (ou au moins accéder à leurs dépendances).

Comme ci-dessus, le levage de module déplace toutes les dépendances vers un répertoire racine node_modules . Cela tire parti du fait que la résolution du module de nœud parcourra toujours l'arborescence des répertoires et parcourra tous les répertoires node_modules jusqu'à ce qu'elle trouve le module requis. Les modules individuels de votre monorepo sont ensuite liés symboliquement dans cette racine node_modules et tout fonctionne. Le billet de blog sur la laine l' explique probablement mieux que moi.

Le levage n'est pas garanti. Si vous avez des versions incompatibles du même package, elles ne seront pas hissées. De plus, de nombreux outils existants ne prennent pas en charge le levage car ils font des hypothèses sur l'emplacement de node_modules ou ils ne suivent pas correctement la résolution du module de nœud. Pour cette raison, il existe un paramètre nohoist qui peut désactiver le levage pour des modules ou des dépendances spécifiques.

J'ai ajouté un sixième élément à mes commentaires précédents . tsc plante dans le scénario qui y est décrit.

@mhegazy Je ne suis pas sûr que l'article 3 soit lié à #25278. #25278 décrit l'émission d'une déclaration invalide. Mes fichiers de déclaration générés étaient syntaxiquement et sémantiquement valides, mais ont entraîné la construction de projets en aval avec deux copies des typages de nœuds, ce qui a entraîné des erreurs « identifiant en double ».

Comme ci-dessus, le levage de module déplace toutes les dépendances vers un répertoire racine node_modules. Cela tire parti du fait que la résolution du module de nœud parcourra toujours l'arborescence des répertoires et parcourra tous les répertoires node_modules jusqu'à ce qu'elle trouve le module requis.

Btw il y a un inconvénient à ce modèle, qu'il conduit à des "dépendances fantômes" où un projet peut importer une dépendance qui n'a pas été explicitement déclarée dans son fichier package.json. Lorsque vous publiez votre bibliothèque, cela peut causer des problèmes tels que (1) une version différente de la dépendance installée par rapport à ce qui a été testé/attendu, ou (2) la dépendance manque complètement car elle a été hissée à partir d'un projet non lié qui n'est pas installé dans ce contexte. PNPM et Rush ont tous deux des choix architecturaux destinés à se protéger contre les dépendances fantômes.

J'ai une question générale à propos de tsc --build : Le compilateur TypeScript cherche-t-il à assumer le rôle d'orchestrer la construction des projets dans un monorepo ? Normalement, la chaîne d'outils va avoir tout un pipeline de tâches, comme :

  • prétraitement
  • génération de chaînes localisées
  • conversion d'actifs en JavaScript (css, images, etc.)
  • compilation (contrôle de type / transpilation)
  • cumuler les fichiers .js (par exemple, webpack)
  • cumuler les fichiers .d.ts (par exemple API Extractor)
  • post-traitement incluant les tests unitaires et la génération de documentation

Normalement, un système tel que Gulp ou Webpack gère ce pipeline, et le compilateur n'est qu'une étape au milieu de la chaîne. Parfois, un outil secondaire exécute également la construction d'une autre manière, par exemple Jest+ts-jest pour jest --watch .

Est-ce que tsc vise à gérer ces choses lui-même ? Et sinon, y a-t-il un moyen pour un orchestrateur de construction conventionnel de résoudre le graphe de dépendance lui-même, et par exemple d'appeler à plusieurs reprises tsc dans chaque dossier de projet dans le bon ordre (après la mise à jour du prétraitement) ?

Ou, si la conception consiste à traiter un monorepo entier en une seule passe (alors qu'aujourd'hui nous construisons chaque projet dans un processus NodeJs séparé), je suis également curieux de savoir comment les autres tâches de construction participeront : par exemple, allons-nous exécuter webpack sur tous les projets à la fois ? (Dans le passé, cela entraînait des problèmes de mémoire insuffisante.) Allons-nous perdre la capacité d'exploiter la concurrence multiprocessus ?

Ce ne sont pas des critiques BTW. J'essaie juste de comprendre la situation dans son ensemble et l'utilisation prévue.

@pgonzal à droite, il existe de nombreuses parties non-tsc pour construire un monorepo du monde réel. Pour notre lerna monorepo, j'ai adopté l'approche suivante :

  • chaque package du monorepo définit éventuellement un script prebuild et/ou un script postbuild dans son package.json . Ceux-ci contiennent les aspects non-tsc de la construction.
  • dans le package.json du monorepo, il y a ces scripts :
    "prebuild": "lerna run prebuild", "build": "tsc --build monorepo.tsconfig.json --verbose", "postbuild": "lerna run postbuild",
  • C'est ça. L'exécution de yarn build au niveau monorepo exécute les scripts prebuild pour chaque package qui les définit, puis exécute l'étape tsc --build , puis exécute tous les postbuild scripts. (Par convention en npm et en fil, l'exécution de npm run foo est à peu près la même que npm run prefoo && npm run foo && npm run postfoo .)

Comment gérez-vous jest --watch ou webpack-dev-server ? Par exemple, lorsqu'un fichier source est modifié, les étapes de pré-construction/post-construction sont-elles réexécutées ?

Cela a-t-il des implications sur ts-node et les workflows associés ? Certaines de nos applications d'assistance s'exécutent "directement" à partir de TypeScript, comme "start": "ts-node ./src/app.ts" ou "start:debug": "node -r ts-node/register --inspect-brk ./src/app.ts" .

Signalé un autre problème avec le mode de construction à #25355.

Merci pour tous les excellents commentaires et enquêtes jusqu'à présent. J'apprécie vraiment tous ceux qui ont pris le temps d'essayer des choses et de gonfler les pneus.

@yortus concernant https://github.com/Microsoft/TypeScript/issues/3469#issuecomment -400439520

Excellente écriture, merci encore de nous l'avoir fournie. Vos problèmes dans l'ordre -

  1. Fixé
  2. PR au #25370
  3. Discuter du problème enregistré - pas immédiatement évident de savoir quelle est la bonne solution, mais nous ferons quelque chose
  4. Enquête (ci-dessous)
  5. Connecté #25376
  6. Techniquement sans rapport avec --build AFAICT. Il s'agit d'une nouvelle assertion que nous avons ajoutée récemment ; Nathan enquête

@rosskevin 🎉 pour le PR sur le repo learn-a ! Je vais fusionner cela dans une branche afin que nous puissions mieux comparer et contraster.

@pgonzal concernant https://github.com/Microsoft/TypeScript/issues/3469#issuecomment-401577442

J'ai une question générale à propos de tsc --build : le compilateur TypeScript cherche-t-il à assumer le rôle d'orchestrer la construction des projets dans un monorepo ?

Excellente question ; Je veux répondre à celle-ci très clairement : certainement pas .

Si vous êtes heureux aujourd'hui en utilisant tsc pour construire votre projet, nous voulons que vous soyez heureux demain en utilisant tsc -b pour construire votre projet en plusieurs parties. Si vous êtes heureux aujourd'hui en utilisant gulp pour construire votre projet, nous voulons que vous soyez heureux demain en utilisant gulp pour construire votre projet en plusieurs parties. Nous avons le contrôle sur le premier scénario, mais nous avons besoin des auteurs d'outils et de plugins pour nous aider avec le second, c'est pourquoi même tsc -b n'est qu'un mince wrapper sur les API exposées que les auteurs d'outils peuvent utiliser pour aider les références de projet à bien jouer dans leurs modèles de construction.

Le contexte plus large est qu'il y a eu un débat interne assez solide pour savoir si tsc -b devrait même exister, ou plutôt être un outil / point d'entrée séparé - la construction d'un orchestrateur de construction à usage général est une tâche énorme et non une tâche que nous sommes s'inscrire pour. Pour notre propre référentiel, nous avons utilisé tsc avec un framework de gestionnaire de tâches léger et utilisons maintenant tsc -b avec le même gestionnaire de tâches, et je m'attendrais à ce que toute autre personne migrant garde également sa chaîne de construction existante en place avec seulement de petits ajustements.

@borekb concernant https://github.com/Microsoft/TypeScript/issues/3469#issuecomment-401593804

Cela a-t-il des implications sur le nœud ts et les flux de travail associés ? Certaines de nos applications d'assistance s'exécutent « directement » à partir de TypeScript

Pour les scripts à fichier unique, qui ne peuvent implicitement pas avoir de références de projet, l'impact est nul.

@EisenbergEffect avait quelques questions dans le learn-a sur le renommage entre projets et d'autres fonctionnalités de service linguistique. La grande question ouverte ici est de savoir si nous pourrons ou non obtenir cette fonctionnalité dans un état utilisable pour la version 3.0. Si tel est le cas, le renommage inter-projet "fonctionnera simplement", sous réserve qu'il nous est évidemment impossible de trouver de manière concluante tous les projets en aval et de les mettre à jour - ce sera un "meilleur effort" basé sur certaines heuristiques pour rechercher d'autres projets.

Si nous pensons que le renommage entre projets n'est pas suffisamment stable + complet pour 3.0, nous bloquerons probablement les opérations de renommage uniquement lorsque le symbole renommé se trouve dans le fichier de sortie .d.ts d'un autre projet - vous permettre de le faire serait très déroutant car le fichier .d.ts serait mis à jour lors d'une version ultérieure du projet en amont après la modification du projet en amont, ce qui signifie qu'il pourrait facilement s'écouler des jours entre le moment où vous effectuez un renommage local et le moment où vous réalisez que le code de déclaration n'avait pas été mis à jour.

Pour des fonctionnalités telles que Go to Definition, celles-ci fonctionnent aujourd'hui dans VS Code et fonctionneront immédiatement dans les futures versions de Visual Studio. Ces fonctionnalités nécessitent toutes l'activation des fichiers .d.ts.map (activez declarationMap ). Il y a du travail par fonctionnalité pour éclairer cela, donc si vous voyez quelque chose ne fonctionnant pas comme prévu, enregistrez un bogue car nous avons peut-être manqué certains cas.

Problèmes ouverts que je suis en train de suivre à ce stade :

  • Renommer inter-projets - @andy-ms est en train de mettre en œuvre
  • Besoin d'analyser les configurations de modules hissés et de comprendre leurs implications - sur moi
  • Il devrait y avoir une version de l'exemple learn-a dépôt yarn , et une autre qui utilise pnpm , et une autre qui utilise l'un de ceux en mode hissé

Questions ouvertes

  • Devrions-nous implémenter une logique pour lire les package.json s pour en déduire les références du projet ? Connecté #25376
  • L'émission de .d.ts.map être implicitement activée pour les projets composite ?

@RyanCavanaugh à ajouter à

Problèmes ouverts que je suis en train de suivre à ce stade

Nous avons également mentionné avoir un cache de sortie incrémentiel, séparé de l'emplacement de sortie réel du projet, pour gérer des choses comme la mise à jour des déclarations en arrière-plan dans le LS (aujourd'hui, les modifications ne se propagent pas à travers les limites du projet dans l'éditeur jusqu'à ce que vous construisiez), stripInternal , et les processus de génération en mutation (où nos sorties de génération sont mutées en place et ne conviennent donc pas aux opérations LS).

désolé pour une question stupide, puisqu'elle est cochée dans la feuille de route, comment puis-je activer cette fonctionnalité ?

@aleksey-bykov vous pouvez l'utiliser dans typescript@next.

Je viens d'essayer cela sur notre monorepo alimenté par espace de travail de fil et cela fonctionne bien.

Une chose que j'ai remarquée, c'est que tsc --build --watch signale des erreurs mais ne sort rien pour dire que la version est maintenant corrigée. Le mode de surveillance standard tsc dans 2.9 a commencé à donner un nombre d'erreurs et c'est bien de voir un zéro là-bas pour que vous sachiez que la construction est terminée.

j'ai un dossier plein de *.d.ts et rien d'autre que suis-je censé faire à ce sujet :

  • faites-en un projet et référencez-le (essayé, n'a pas fonctionné)
  • utilisez "include" pour cela

@timfish que les commentaires correspondent à d'autres que j'ai entendus ; connecté #25562

@aleksey-bykov https://github.com/Microsoft/TypeScript/issues/3469#issuecomment -400439520 devrait aider à expliquer certains concepts

@RyanCavanaugh, il semble que le référencement de projet ne fonctionne que pour la résolution de commonjs et de module de nœud, n'est-ce pas?

dans ton exemple :

import * as p1 from "@ryancavanaugh/pkg1";
import * as p2 from "@ryancavanaugh/pkg2";

p1.fn();
p2.fn4();
  1. qu'est-ce que le module @ryancavanaugh , cela a-t-il quelque chose à voir avec la façon dont TS résout les modules ?
  2. cet exemple est-il censé fonctionner avec AMD (résolution de module classique) ?
  3. outFile requis pour trouver des définitions ?
  4. où les fichiers d.ts sont censés être pour que le projet de référencement les trouve (puis-je toujours utiliser outDir ? TS les trouvera-t-il là-bas ?)

j'ai 2 projets simples essentials et common et les choses en commun ne peuvent pas résoudre les choses compilées dans l'essentiel :

image

@aleksey-bykov

  1. C'est juste un nom de module étendu, résolu sous l'algorithme de résolution de module de nœud habituel
  2. Vous pouvez utiliser des références de projet avec n'importe quel système de module, y compris classique, mais les noms d'exemple (modules étendus) ne sont pas très conviviaux à utiliser en dehors du nœud
  3. Non
  4. TypeScript recherche les fichiers .d.ts à l'endroit où le projet référencé les construit

Si vous avez un exemple de dépôt ou quelque chose, je peux diagnostiquer pourquoi vous obtenez cette erreur

@RyanCavanaugh s'il vous plaît faites
exemple.zip

@RyanCavanaugh , il semble également que tsc --build --watch ne génère initialement aucun fichier tant qu'il ne voit pas une modification d'un fichier source.

Fil trop long (à la fois dans le temps et dans l'espace) ; reprenons la discussion au numéro chanceux 100 * 2^8 : #25600

Cette page vous a été utile?
0 / 5 - 0 notes

Questions connexes

CyrusNajmabadi picture CyrusNajmabadi  ·  3Commentaires

jbondc picture jbondc  ·  3Commentaires

Roam-Cooper picture Roam-Cooper  ·  3Commentaires

blendsdk picture blendsdk  ·  3Commentaires

wmaurer picture wmaurer  ·  3Commentaires