TypeScript 2: mecanografía personalizada para módulo npm sin tipo

93

Después de probar sugerencias publicadas en otros lugares , me encuentro incapaz de ejecutar un proyecto mecanografiado que usa un módulo NPM sin escribir. A continuación se muestra un ejemplo mínimo y los pasos que probé.

Para este ejemplo mínimo, pretendemos que lodashno tiene definiciones de tipo existentes. Como tal, ignoraremos el paquete @types/lodashe intentaremos agregar manualmente su archivo de mecanografía lodash.d.tsa nuestro proyecto.

Estructura de carpetas

  • node_modules
    • lodash
  • src
    • foo.ts
  • mecanografia
    • personalizado
      • lodash.d.ts
    • global
    • index.d.ts
  • package.json
  • tsconfig.json
  • typings.json

A continuación, los archivos.

Expediente foo.ts

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

console.log('Weeee');

El archivo lodash.d.tsse copia directamente del @types/lodashpaquete original .

Expediente index.d.ts

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

Expediente 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"
}

Expediente 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"
  ]
}

Expediente typings.json

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

Como puede ver, he probado muchas formas diferentes de importar mecanografía:

  1. Importándolo directamente en foo.ts
  2. Por una typingspropiedad enpackage.json
  3. Usando typeRootsin tsconfig.jsoncon un archivotypings/index.d.ts
  4. Al usar un explícito typesentsconfig.json
  5. Incluyendo el typesdirectorio entsconfig.json
  6. Haciendo un typings.jsonarchivo personalizado y ejecutandotypings install

Sin embargo, cuando ejecuto Typecript:

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

¿Qué estoy haciendo mal?

Jodiug
fuente

Respuestas:

206

Desafortunadamente, estas cosas no están actualmente muy bien documentadas, pero incluso si pudo hacerlo funcionar, repasemos su configuración para que comprenda lo que hace cada parte y cómo se relaciona con la forma en que el mecanografiado procesa y carga las mecanografías.

Primero repasemos el error que está recibiendo:

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

Este error en realidad no proviene de sus importaciones o referencias o de su intento de usar lodash en cualquier parte de sus archivos ts. Más bien proviene de un malentendido sobre cómo usar las propiedades typeRootsy types, así que vamos a entrar en más detalles sobre ellas.

Lo que pasa con las propiedades typeRoots:[]y types:[]es que NO son formas de propósito general para cargar *.d.tsarchivos de declaración arbitrarios ( ).

Estas dos propiedades están directamente relacionadas con la nueva característica TS 2.0 que permite empaquetar y cargar declaraciones de escritura de paquetes NPM .

Es muy importante comprender que estos funcionan solo con carpetas en formato NPM (es decir, una carpeta que contiene un package.json o index.d.ts ).

El valor predeterminado para typeRootses:

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

Por defecto, esto significa que mecanografiado irá a la node_modules/@typescarpeta e intentará cargar cada subcarpeta que encuentre allí como un paquete npm .

Es importante comprender que esto fallará si una carpeta no tiene una estructura similar a un paquete npm.

Esto es lo que está sucediendo en su caso y la fuente de su error inicial.

Ha cambiado typeRoot para ser:

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

Esto significa que mecanografiado ahora escaneará la ./typingscarpeta en busca de subcarpetas e intentará cargar cada subcarpeta que encuentre como un módulo npm.

Así que supongamos que acaba de tener una typeRootsconfiguración a la que apuntar, ./typingspero aún no tiene ninguna types:[]configuración de propiedad. Es probable que vea estos errores:

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

Esto se debe a que tscestá escaneando su ./typingscarpeta y encontrando las subcarpetas customy global. Luego está tratando de interpretarlos como tipo de paquete npm, pero no hay index.d.tso package.jsonen estas carpetas, por lo que aparece el error.

Ahora hablemos un poco sobre la types: ['lodash']propiedad que está configurando. ¿Qué hace esto? De forma predeterminada, mecanografiado cargará todas las subcarpetas que encuentre dentro de su typeRoots. Si especifica una types:propiedad, solo cargará esas subcarpetas específicas.

En su caso, le está diciendo que cargue la ./typings/lodashcarpeta, pero no existe. Por eso obtienes:

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

Así que resumamos lo que hemos aprendido. Se introdujo Typecript 2.0 typeRootsy typespara cargar archivos de declaración empaquetados en paquetes npm . Si tiene mecanografía personalizada o d.tsarchivos sueltos individuales que no están contenidos en una carpeta siguiendo las convenciones del paquete npm, estas dos nuevas propiedades no son las que desea usar. Typecript 2.0 no cambia realmente cómo se consumirían. Solo tiene que incluir estos archivos en su contexto de compilación de una de las muchas formas estándar:

  1. Incluirlo directamente en un .tsarchivo: ///<reference path="../typings/custom/lodash.d.ts" />

  2. Incluido ./typings/custom/lodash.d.tsen su files: []propiedad.

  3. Incluyendo ./typings/index.d.tsen su files: []propiedad (que luego incluye recursivamente las otras tipificaciones.

  4. Añadiendo ./typings/**a tuincludes:

Con suerte, en base a esta discusión, podrá decir por qué los cambios que tsconfig.jsonhizo que las cosas funcionaran nuevamente.

EDITAR:

Una cosa que olvidé mencionar es que typeRootsy la typespropiedad solo es útil para la carga automática de declaraciones globales.

Por ejemplo si tu

npm install @types/jquery

Y está utilizando el tsconfig predeterminado, entonces ese paquete de tipos de jquery se cargará automáticamente y $estará disponible en todos sus scripts sin tener que hacer más ///<reference/>oimport

La typeRoots:[]propiedad está destinada a agregar ubicaciones adicionales desde donde los paquetes de tipo se cargarán automáticamente.

El types:[]caso de uso principal de la propiedad es deshabilitar el comportamiento de carga automática (configurándolo en una matriz vacía) y luego solo enumerar los tipos específicos que desea incluir globalmente.

La otra forma de cargar paquetes de tipos de varios typeRootses usar la nueva ///<reference types="jquery" />directiva. Observe el en typeslugar de path. Nuevamente, esto solo es útil para archivos de declaración global, generalmente los que no lo hacen import/export.

Ahora, aquí está una de las cosas que causa confusión con typeRoots. Recuerde, dije que typeRootsse trata de la inclusión global de módulos. Pero @types/foldertambién está involucrado en la resolución de módulo estándar (independientemente de su typeRootsconfiguración).

En concreto, la importación de módulos de forma explícita siempre se salta todas includes, excludes, files, typeRootsy typesopciones. Entonces, cuando lo hagas:

import {MyType} from 'my-module';

Todas las propiedades mencionadas anteriormente se ignoran por completo. Las propiedades relevantes durante la resolución del módulo son baseUrl, pathsy moduleResolution.

Básicamente, cuando se utiliza nodela resolución del módulo, se iniciará la búsqueda de un nombre de archivo my-module.ts, my-module.tsx, my-module.d.tsa partir de la carpeta a la que apunta su baseUrlconfiguración.

Si no encuentra el archivo, entonces buscará una carpeta con nombre my-moduley luego buscará una package.jsoncon una typingspropiedad, si hay package.jsono no hay una typingspropiedad dentro que le indique qué archivo cargar, buscará index.ts/tsx/d.tsdentro de esa carpeta.

Si aún no tiene éxito, buscará estas mismas cosas en la node_modulescarpeta que comienza en su baseUrl/node_modules.

Además, si no los encuentra, buscará baseUrl/node_modules/@typestodas las mismas cosas.

Si todavía no se encontró nada va a empezar a ir al directorio padre y la búsqueda node_modulesy node_modules/@typesallí. Seguirá subiendo por los directorios hasta que llegue a la raíz de su sistema de archivos (incluso obteniendo módulos de nodo fuera de su proyecto).

Una cosa que quiero enfatizar es que la resolución del módulo ignora por completo cualquier typeRootsconfiguración que establezca. Entonces, si configuró typeRoots: ["./my-types"], esto no se buscará durante la resolución explícita del módulo. Solo sirve como una carpeta donde puede colocar los archivos de definición global que desea que estén disponibles para toda la aplicación sin necesidad de importarlos o hacer referencia.

Por último, puede anular el comportamiento del módulo con asignaciones de ruta (es decir, la pathspropiedad). Entonces, por ejemplo, mencioné que typeRootsno se consulta ninguna costumbre al intentar resolver un módulo. Pero si te gustó, puedes hacer que este comportamiento suceda así:

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

Lo que hace esto es para todas las importaciones que coinciden con el lado izquierdo, intente modificar la importación como en el lado derecho antes de intentar incluirla (el *en el lado derecho representa su cadena de importación inicial. Por ejemplo, si importa:

import {MyType} from 'my-types';

Primero intentaría la importación como si hubiera escrito:

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

Y luego, si no lo encuentra, lo intentará de nuevo sin el prefijo (el segundo elemento de la matriz es solo lo *que significa la importación inicial.

De esta manera, puede agregar carpetas adicionales para buscar archivos de declaración personalizados o incluso .tsmódulos personalizados que desee import.

También puede crear asignaciones personalizadas para módulos específicos:

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

Esto te dejaría hacer

import {MyType} from 'my-types';

Pero luego lea esos tipos de some/custom/folder/location/my-awesome-types-file.d.ts

dtabuenc
fuente
1
Gracias por tu respuesta detallada. Resulta que las soluciones funcionan de forma aislada, pero no se combinan bien. Aclara las cosas, así que te daré la recompensa. Si pudiera encontrar tiempo para responder algunos puntos más, podría ser de gran ayuda para otras personas. (1) ¿Existe alguna razón para que typeRoots solo acepte una estructura de carpeta específica? Parece arbitrario. (2) Si cambio typeRoots, mecanografiado todavía incluye las carpetas @types en node_modules, contrariamente a la especificación. (3) ¿Qué es pathsy en qué se diferencia de includela mecanografía?
Jodiug
1
Edité la respuesta, avíseme si tiene más preguntas.
dtabuenc
1
Gracias, leí el resto y wow. Apoyos para ti por encontrar todo esto. Parece que hay una gran cantidad de comportamientos innecesariamente complejos en funcionamiento aquí. Espero que Typecript haga que sus propiedades de configuración sean un poco más autodocumentadas en el futuro.
Jodiug
1
Debería haber un enlace a esta respuesta de typescriptlang.org/docs/handbook/tsconfig-json.html
hgoebl
2
¿No debería ser el último ejemplo "paths" :{ "my-types": ["some/custom/folder/location/my-awesome-types-file"] }?
Koen.
6

Editar: desactualizado. Lea la respuesta anterior.

Sigo sin entender esto, pero encontré una solución. Utilice lo siguiente 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"
  ]
}

Eliminar typings.jsony todo lo que hay debajo de la carpeta typingsexcepto lodash.d.ts. También elimine todas las ///...referencias

Jodiug
fuente
3

"*": ["./types/*"] Esta línea en las rutas tsconfig arregló todo después de 2 horas de lucha.

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

tipos es el nombre de la carpeta, que se encuentra al lado de node_module, es decir, en el nivel de la carpeta del cliente (o carpeta src ) types/third-party-lib/index.d.ts
index.d.ts tienedeclare module 'third-party-lib';

Nota: La configuración anterior es una configuración incompleta, solo para dar una idea de cómo se ve con tipos, rutas, incluir y excluir en ella.

Uday Sravan K
fuente
1

Sé que esta es una pregunta antigua, pero las herramientas mecanografiadas han cambiado continuamente. Creo que la mejor opción en este punto es simplemente confiar en la configuración de ruta "incluir" en tsconfig.json.

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

De forma predeterminada, a menos que realice cambios particulares, todos los archivos * .ts y * .d.ts de debajo se src/incluirán automáticamente. Creo que esta es la forma más fácil / mejor de incluir archivos de declaración de tipo personalizado sin personalizar typeRootsy types.

Referencia:

Realharry
fuente
0

Me gustaría compartir algunas de mis observaciones recientes para ampliar la descripción detallada que encontramos arriba . Primero, es importante tener en cuenta que VS Code a menudo tiene una opinión diferente sobre cómo se deben hacer las cosas.

Veamos un ejemplo que trabajé recientemente:

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

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

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

En caso de que iniciemos el proyecto se compila sin error, pero en caso de que eliminemos esa línea aparecerá el siguiente error durante el inicio:

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

El mismo mensaje de error aparece en caso de que coloquemos la reference typesdirectiva después de las declaraciones de importación.

Importante : ¡ /// <reference types="plotly.js" />Debe estar en la parte frontal del archivo de script de tipo! Ver documentación relacionada: enlace

Las directivas de triple barra solo son válidas en la parte superior de su archivo contenedor.

También recomiendo leer la documentación en tsconfig.json y en la sección typeRoot: enlace

Un paquete de tipos es una carpeta con un archivo llamado index.d.ts o una carpeta con un paquete.json que tiene un campo de tipos.

La directiva de referencia anterior funciona en el siguiente escenario:

tipos / plotly.js / index.d.ts: ( enlace )

  declare namespace plotly {
    export interface ...
  }

Y

tsconfig.json:

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

Nota : En la configuración anterior, los dos "plotly.js" significan dos carpetas y archivos diferentes (biblioteca / definición). La importación se aplica a la carpeta "node_modules / plotly.js" (agregada por npm install plotly.js), mientras que la referencia se aplica a types / plotly.js.

Para que mi proyecto resuelva las quejas de VS Code y la ambigüedad de los "dos" plotly.js, terminé con la siguiente configuración:

  • todo el archivo permaneció en la ubicación original
  • 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 en mi sistema:

  • "ts-node": "~ 8.3.0",
  • "tslint": "~ 5.18.0",
  • "mecanografiado": "~ 3.7.5"
SchLx
fuente