TypeScript 2: typages personnalisés pour le module npm non typé


93

Après avoir essayé des suggestions publiées ailleurs , je me trouve incapable de lancer un projet dactylographié utilisant un module NPM non typé. Voici un exemple minimal et les étapes que j'ai essayées.

Pour cet exemple minimal, nous ferons semblant de lodashne pas avoir de définitions de type existantes. En tant que tel, nous ignorerons le package @types/lodashet essayerons d'ajouter manuellement son fichier de typage lodash.d.tsà notre projet.

Structure des dossiers

  • node_modules
    • lodash
  • src
    • foo.ts
  • typages
    • Douane
      • lodash.d.ts
    • global
    • index.d.ts
  • package.json
  • tsconfig.json
  • typings.json

Ensuite, les fichiers.

Fichier foo.ts

///<reference path="../typings/custom/lodash.d.ts" />
import * as lodash from 'lodash';

console.log('Weeee');

Le fichier lodash.d.tsest copié directement à partir du @types/lodashpackage d' origine .

Fichier index.d.ts

/// <reference path="custom/lodash.d.ts" />
/// <reference path="globals/lodash/index.d.ts" />

Fichier package.json

{
  "name": "ts",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "typings": "./typings/index.d.ts",
  "dependencies": {
    "lodash": "^4.16.4"
  },
  "author": "",
  "license": "ISC"
}

Fichier tsconfig.json

{
  "compilerOptions": {
    "target": "ES6",
    "jsx": "react",
    "module": "commonjs",
    "sourceMap": true,
    "noImplicitAny": true,
    "experimentalDecorators": true,
    "typeRoots" : ["./typings"],
    "types": ["lodash"]
  },
  "include": [
    "typings/**/*",
    "src/**/*"
  ],
  "exclude": [
    "node_modules",
    "**/*.spec.ts"
  ]
}

Fichier typings.json

{
    "name": "TestName",
    "version": false,
    "globalDependencies": {
        "lodash": "file:typings/custom/lodash.d.ts"
    }
}

Comme vous pouvez le voir, j'ai essayé de nombreuses façons différentes d'importer des typages:

  1. En l'important directement dans foo.ts
  2. Par une typingspropriété àpackage.json
  3. En utilisant typeRootsdans tsconfig.jsonun fichiertypings/index.d.ts
  4. En utilisant un explicite typesdanstsconfig.json
  5. En incluant le typesrépertoire danstsconfig.json
  6. En créant un typings.jsonfichier personnalisé et en exécutanttypings install

Pourtant, lorsque j'exécute Typescript:

E:\temp\ts>tsc
error TS2688: Cannot find type definition file for 'lodash'.

Qu'est-ce que je fais mal?

Réponses:


205

Malheureusement, ces choses ne sont pas très bien documentées actuellement, mais même si vous avez pu le faire fonctionner, passons en revue votre configuration afin que vous compreniez ce que fait chaque partie et comment elle se rapporte à la façon dont le typographie traite et charge les typages.

Passons d'abord en revue l'erreur que vous recevez:

error TS2688: Cannot find type definition file for 'lodash'.

Cette erreur ne provient en fait pas de vos importations ou références ou de votre tentative d'utiliser lodash n'importe où dans vos fichiers ts. Cela vient plutôt d'un malentendu sur la façon d'utiliser les propriétés typeRootset types, alors allons-y un peu plus en détail.

Le problème typeRoots:[]avec les types:[]propriétés et c'est qu'elles ne sont PAS des moyens généraux de charger des *.d.tsfichiers de déclaration arbitraires ( ).

Ces deux propriétés sont directement liées à la nouvelle fonctionnalité TS 2.0 qui permet d'empaqueter et de charger des déclarations de typage à partir de packages NPM .

Il est très important de comprendre que ceux-ci ne fonctionnent qu'avec des dossiers au format NPM (c'est-à-dire un dossier contenant un package.json ou index.d.ts ).

La valeur par défaut pour typeRootsest:

{
   "typeRoots" : ["node_modules/@types"]
}

Par défaut, cela signifie que dactylographié ira dans le node_modules/@typesdossier et essaiera de charger chaque sous-dossier qu'il y trouvera en tant que package npm .

Il est important de comprendre que cela échouera si un dossier n'a pas de structure de type package npm.

C'est ce qui se passe dans votre cas et la source de votre erreur initiale.

Vous avez changé de typeRoot pour être:

{
    "typeRoots" : ["./typings"]
}

Cela signifie que dactylographié analysera maintenant le ./typingsdossier pour les sous-dossiers - et essayer de charger chaque sous-dossier qu'il trouve en tant que module npm.

Supposons donc que vous ayez juste une typeRootsconfiguration sur laquelle pointer ./typingsmais que vous n'avez pas encore de types:[]configuration de propriété. Vous verrez probablement ces erreurs:

error TS2688: Cannot find type definition file for 'custom'.
error TS2688: Cannot find type definition file for 'global'.

En effet, il tscscanne votre ./typingsdossier et trouve les sous-dossiers customet global. Il essaie ensuite de les interpréter comme une saisie de type de package npm, mais il n'y a pas index.d.tsou package.jsondans ces dossiers et vous obtenez l'erreur.

Parlons maintenant un peu de la types: ['lodash']propriété que vous définissez. Qu'est ce que cela fait? Par défaut, dactylographié chargera tous les sous-dossiers qu'il trouve dans votre fichier typeRoots. Si vous spécifiez untypes: propriété, il ne chargera que ces sous-dossiers spécifiques.

Dans votre cas, vous lui dites de charger le ./typings/lodashdossier mais il n'existe pas. C'est pourquoi vous obtenez:

error TS2688: Cannot find type definition file for 'lodash'

Alors résumons ce que nous avons appris. Typescript 2.0 introduit typeRootset typespour le chargement des fichiers de déclaration emballés dans les packages npm . Si vous avez des typages personnalisés ou des d.tsfichiers lâches uniques qui ne sont pas contenus dans un dossier suivant les conventions de package npm, ces deux nouvelles propriétés ne sont pas ce que vous souhaitez utiliser. Typescript 2.0 ne change pas vraiment la façon dont ils seraient consommés. Il vous suffit d'inclure ces fichiers dans votre contexte de compilation de l'une des nombreuses manières standard:

  1. L'inclure directement dans un .tsfichier: ///<reference path="../typings/custom/lodash.d.ts" />

  2. Y compris ./typings/custom/lodash.d.tsdans votre files: []propriété.

  3. Y compris ./typings/index.d.tsdans votre files: []propriété (qui inclut alors récursivement les autres typages.

  4. Ajout ./typings/**à votreincludes:

Avec un peu de chance, sur la base de cette discussion, vous serez en mesure de dire pourquoi les changements que vous avez apportés à vos tsconfig.jsonchoses fonctionnent à nouveau.

ÉDITER:

Une chose que j'ai oublié de mentionner est que typeRootset la typespropriété n'est vraiment utile que pour le chargement automatique des déclarations globales.

Par exemple si vous

npm install @types/jquery

Et vous utilisez le tsconfig par défaut, alors ce package de types jquery sera chargé automatiquement et $sera disponible dans tous vos scripts sans avoir à faire plus ///<reference/>ouimport

La typeRoots:[]propriété est destinée à ajouter des emplacements supplémentaires à partir desquels les packages de types seront chargés automatiquement.

Le types:[]principal cas d'utilisation de la propriété consiste à désactiver le comportement de chargement automatique (en le définissant sur un tableau vide), puis à répertorier uniquement les types spécifiques que vous souhaitez inclure globalement.

L'autre façon de charger des packages de type à partir des différents typeRootsest d'utiliser la nouvelle ///<reference types="jquery" />directive. Notez le typesau lieu de path. Encore une fois, cela n'est utile que pour les fichiers de déclaration globale, généralement ceux qui ne le font pas import/export.

Maintenant, voici l'une des choses avec lesquelles il y a confusion typeRoots. N'oubliez pas, j'ai dit qu'il typeRootss'agissait de l'inclusion globale de modules. Mais @types/folderest également impliqué dans la résolution de module standard (quel que soit votre typeRootsréglage).

Plus précisément, les modules explicitement importateurs contournements toujours tous includes, excludes, files, typeRootset typesoptions. Alors quand vous faites:

import {MyType} from 'my-module';

Toutes les propriétés mentionnées ci-dessus sont complètement ignorées. Les propriétés pertinentes au cours de la résolution du module sont baseUrl, pathset moduleResolution.

En fait, lorsque vous utilisez la noderésolution du module, il commencera à chercher un nom de fichier my-module.ts, my-module.tsx, my-module.d.tsau niveau du dossier pointé par votre baseUrlconfiguration.

S'il ne trouve pas le fichier, il recherchera un dossier nommé my-module, puis recherchera un package.jsonavec une typingspropriété, s'il n'y a package.jsonou aucune typingspropriété à l'intérieur lui indiquant quel fichier charger, il recherchera alors index.ts/tsx/d.tsdans ce dossier.

Si cela ne fonctionne toujours pas, il recherchera ces mêmes éléments dans le node_modulesdossier en commençant par votre baseUrl/node_modules.

De plus, s'il ne les trouve pas, il recherchera baseUrl/node_modules/@typestoutes les mêmes choses.

S'il ne trouve toujours rien, il commencera à aller dans le répertoire parent et à rechercher node_moduleset node_modules/@typeslà. Il continuera à remonter dans les répertoires jusqu'à ce qu'il atteigne la racine de votre système de fichiers (même en obtenant des modules de nœuds en dehors de votre projet).

Une chose que je tiens à souligner est que la résolution du module ignore complètement tout typeRootsce que vous définissez. Donc, si vous avez configuré typeRoots: ["./my-types"], cela ne sera pas recherché pendant la résolution explicite du module. Il sert uniquement de dossier dans lequel vous pouvez placer les fichiers de définition globale que vous souhaitez mettre à la disposition de l'ensemble de l'application sans avoir à importer ou à référencer davantage.

Enfin, vous pouvez remplacer le comportement du module avec des mappages de chemins (c'est-à-dire la pathspropriété). Ainsi, par exemple, j'ai mentionné qu'aucune coutume typeRootsn'est consultée lors de la tentative de résolution d'un module. Mais si vous avez aimé, vous pouvez faire en sorte que ce comportement se produise comme suit:

"paths" :{
     "*": ["my-custom-types/*", "*"]
 }

Pour toutes les importations qui correspondent au côté gauche, essayez de modifier l'importation comme dans le côté droit avant d'essayer de l'inclure (le *côté droit représente votre chaîne d'importation initiale. Par exemple, si vous importez:

import {MyType} from 'my-types';

Il essaierait d'abord l'importation comme si vous aviez écrit:

import {MyType} from 'my-custom-types/my-types'

Et puis s'il ne le trouve pas, il essaiera à nouveau sans le préfixe (le deuxième élément du tableau est juste *ce qui signifie l'importation initiale.

Ainsi, vous pouvez ajouter des dossiers supplémentaires pour rechercher des fichiers de déclaration personnalisés ou même des .tsmodules personnalisés que vous souhaitez pouvoir utiliser import.

Vous pouvez également créer des mappages personnalisés pour des modules spécifiques:

"paths" :{
   "*": ["my-types", "some/custom/folder/location/my-awesome-types-file"]
 }

Cela vous permettrait de faire

import {MyType} from 'my-types';

Mais alors lisez ces types de some/custom/folder/location/my-awesome-types-file.d.ts


1
Merci pour votre réponse détaillée. Il s'avère que les solutions fonctionnent isolément, mais elles ne se mélangent pas bien. Cela clarifie les choses, alors je vais vous donner la prime. Si vous pouviez trouver le temps de répondre à quelques points supplémentaires, cela pourrait être très utile pour d'autres personnes. (1) Y a-t-il une raison pour que typeRoots n'accepte qu'une structure de dossier spécifique? Cela semble arbitraire. (2) Si je change de typeRoots, typescript inclut toujours les dossiers @types dans node_modules, contrairement à la spécification. (3) Qu'est-ce que c'est pathset en quoi diffère-t-il des includefins de frappe?
Jodiug

1
J'ai édité la réponse, faites-moi savoir si vous avez d'autres questions.
dtabuenc

1
Merci, j'ai lu le reste et wow. Props à vous pour trouver tout cela. On dirait qu'il y a beaucoup de comportements inutilement complexes à l'œuvre ici. J'espère que Typescript rendra leurs propriétés de configuration un peu plus auto-documentées à l'avenir.
Jodiug

1
Il devrait y avoir un lien vers cette réponse à partir de typescriptlang.org/docs/handbook/tsconfig-json.html
hgoebl

2
Le dernier exemple ne devrait-il pas être comme "paths" :{ "my-types": ["some/custom/folder/location/my-awesome-types-file"] }?
Koen.

6

Edit: obsolète. Lisez la réponse ci-dessus.

Je ne comprends toujours pas cela, mais j'ai trouvé une solution. Utilisez ce qui suit tsconfig.json:

{
  "compilerOptions": {
    "target": "ES6",
    "jsx": "react",
    "module": "commonjs",
    "sourceMap": true,
    "noImplicitAny": true,
    "experimentalDecorators": true,
    "baseUrl": ".",
    "paths": {
      "*": [
        "./typings/*"
      ]
    }
  },
  "include": [
    "src/**/*"
  ],
  "exclude": [
    "node_modules",
    "**/*.spec.ts"
  ]
}

Supprimer typings.jsonet tout sous le dossier typingssauf lodash.d.ts. Supprimer également toutes les ///...références


3

"*": ["./types/*"] Cette ligne dans les chemins tsconfig a tout corrigé après 2 heures de lutte.

{
  "compilerOptions": {
    "moduleResolution": "node",
    "strict": true,
    "baseUrl": ".",
    "paths": {
      "*": ["./types/*"]
    },
    "jsx": "react",
    "types": ["node", "jest"]
  },
  "include": [
    "client/**/*",
    "packages/**/*"
  ],
  "exclude": [
    "node_modules/**/*"
  ]
}

types est le nom du dossier, qui se trouve à côté de node_module ie dans le niveau du dossier client (ou dossier src ) types/third-party-lib/index.d.ts
index.d.ts adeclare module 'third-party-lib';

Remarque: la configuration ci-dessus est une configuration incomplète, juste pour donner une idée de son apparence avec les types, les chemins, les inclus et les exclure.


1

Je sais que c'est une vieille question, mais les outils dactylographiés sont en constante évolution. Je pense que la meilleure option à ce stade est simplement de s'appuyer sur les paramètres de chemin "include" dans tsconfig.json.

  "include": [
        "src/**/*"
    ],

Par défaut, sauf si vous apportez des modifications particulières, tous les fichiers * .ts et tous les fichiers * .d.ts sous src/seront automatiquement inclus. Je pense que c'est le moyen le plus simple / le meilleur d'inclure des fichiers de déclaration de type personnalisé sans personnaliser typeRootset types.

Référence:


0

Je voudrais partager certaines de mes observations récentes pour prolonger la description détaillée que nous trouvons ci-dessus . Tout d'abord, il est important de noter que VS Code a souvent des opinions différentes sur la façon dont les choses doivent être faites.

Voyons un exemple sur lequel j'ai travaillé récemment:

src / app / components / plot-plotly / plot-plotly.component.ts:

/// <reference types="plotly.js" />
import * as Plotly from 'plotly.js';

VS Code peut se plaindre que: No need to reference "plotly.js", since it is imported. (no-reference import) tslint(1)

Si nous démarrons le projet, il se compile sans erreur, mais si nous supprimons cette ligne, l'erreur suivante apparaîtra au démarrage:

ERROR in src/app/components/plot-plotly/plot-plotly.component.ts:19:21 - error TS2503: Cannot find namespace 'plotly'.

Le même message d'erreur apparaît au cas où nous plaçons le reference types directive après les instructions d'importation.

Important : le /// <reference types="plotly.js" />doit être au début du fichier de script de type! Voir la documentation associée: lien

Les directives à triple barre oblique ne sont valides qu'en haut de leur fichier contenant.

Je recommande également de lire la documentation sur le tsconfig.json et sur la section typeRoot: lien

Un package de types est un dossier avec un fichier appelé index.d.ts ou un dossier avec un package.json qui a un champ types.

La directive de référence ci-dessus fonctionne dans le scénario suivant:

types / plotly.js / index.d.ts: ( lien )

  declare namespace plotly {
    export interface ...
  }

ET

tsconfig.json:

  "compilerOptions": {
    ...
    "typeRoots": [
      "types/",
      "node_modules/@types"
    ],
    ...  

Remarque : Dans la configuration ci-dessus, les deux "plotly.js" signifient deux dossiers et fichiers différents (bibliothèque / définition). L'importation s'applique au dossier "node_modules / plotly.js" (ajouté parnpm install plotly.js ), tandis que la référence s'applique sur types / plotly.js.

Pour mon projet de résolution des plaintes de VS Code et de l'ambiguïté des "deux" plotly.js, je me suis retrouvé avec la configuration suivante:

  • tous les fichiers sont restés à l'emplacement d'origine
  • tsconfig.json:
  "compilerOptions": {
    ...
    "typeRoots": [
      "./",
      "node_modules/@types"
    ],
    ...  
  • src / app / components / plot-plotly / plot-plotly.component.ts:
  /// <reference types="types/plotly.js" />
  import * as Plotly from 'plotly.js';

  plotDemo() {

  // types using plotly namespace
  const chunk: plotly.traces.Scatter = {
      x: [1, 2, 3, 4, 5],
      y: [1, 3, 2, 3, 1],
      mode: 'lines+markers',
      name: 'linear',
      line: { shape: 'linear' },
      type: 'scatter'
  };

  // module call using Plotly module import
  Plotly.newPlot(...);
  }

DevDeps sur mon système:

  • "ts-node": "~ 8.3.0",
  • "tslint": "~ 5.18.0",
  • "dactylographié": "~ 3.7.5"
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.