TypeScript 2: tipificações personalizadas para módulo npm não tipado

93

Depois de tentar sugestões postadas em outros lugares , não consigo fazer um projeto de texto digitado em execução que usa um módulo NPM não tipado. Abaixo está um exemplo mínimo e as etapas que tentei.

Para este exemplo mínimo, vamos fingir que lodashnão tem definições de tipo existentes. Assim, iremos ignorar o pacote @types/lodashe tentar adicionar manualmente seu arquivo de tipagem lodash.d.tsao nosso projeto.

Estrutura da pasta

  • node_modules
    • Lodash
  • src
    • foo.ts
  • datilografia
    • personalizadas
      • lodash.d.ts
    • global
    • index.d.ts
  • package.json
  • tsconfig.json
  • typings.json

A seguir, os arquivos.

Arquivo foo.ts

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

console.log('Weeee');

O arquivo lodash.d.tsé copiado diretamente do @types/lodashpacote original .

Arquivo index.d.ts

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

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

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

Arquivo typings.json

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

Como você pode ver, tentei muitas maneiras diferentes de importar tipificações:

  1. Importando-o diretamente no foo.ts
  2. Por uma typingspropriedade empackage.json
  3. Usando typeRootsemtsconfig.json com um arquivotypings/index.d.ts
  4. Usando um explícito typesemtsconfig.json
  5. Incluindo o typesdiretório emtsconfig.json
  6. Fazendo um typings.jsonarquivo personalizado e executandotypings install

Ainda assim, quando executo o Typescript:

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

O que estou fazendo de errado?

Jodiug
fonte

Respostas:

202

Infelizmente, essas coisas não estão muito bem documentadas no momento, mas mesmo que você conseguisse fazer funcionar, vamos revisar sua configuração para que você entenda o que cada parte está fazendo e como ela se relaciona com o modo como o texto datilografado processa e carrega as digitações.

Primeiro, vamos revisar o erro que você está recebendo:

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

Na verdade, esse erro não é proveniente de suas importações ou referências ou de sua tentativa de usar o lodash em qualquer lugar de seus arquivos ts. Em vez disso, vem de um mal-entendido sobre como usar as propriedades typeRootse types, então vamos entrar em mais detalhes sobre elas.

O problema com as propriedades typeRoots:[]e types:[]é que NÃO são formas de uso geral para carregar *.d.tsarquivos de declaração arbitrária ( ).

Essas duas propriedades estão diretamente relacionadas ao novo recurso TS 2.0, que permite empacotar e carregar declarações de digitação de pacotes NPM .

É muito importante entender que funcionam apenas com pastas no formato NPM (ou seja, uma pasta que contém um package.json ou index.d.ts ).

O padrão para typeRootsé:

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

Por padrão, isso significa que o typescript irá para a node_modules/@typespasta e tentará carregar todas as subpastas que encontrar lá como um pacote npm .

É importante entender que isso falhará se uma pasta não tiver uma estrutura semelhante a um pacote npm.

Isso é o que está acontecendo no seu caso e a fonte do seu erro inicial.

Você alterou typeRoot para ser:

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

Isto significa que typescript agora irá verificar o ./typingspasta para as subpastas texto digitado e tentará carregar cada subpasta que encontrar como um módulo npm.

Então, vamos fingir que você acabou de typeRootsdefinir uma configuração para apontar, ./typingsmas ainda não tem nenhuma types:[]configuração de propriedade. Você provavelmente verá estes erros:

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

Isso ocorre porque tscestá examinando sua ./typingspasta e encontrando as subpastas custome global. Em seguida, ele está tentando interpretar isso como digitação do tipo pacote npm, mas não há nenhuma index.d.tsou package.jsonnessas pastas e, portanto, você obtém o erro.

Agora vamos falar um pouco sobre a types: ['lodash']propriedade que você está definindo. O que isso faz? Por padrão, o typescript carregará todas as subpastas que encontrar em seu typeRoots. Se você especificar umtypes: propriedade, ela carregará apenas essas subpastas específicas.

No seu caso, você está dizendo para carregar a ./typings/lodashpasta, mas ela não existe. É por isso que você obtém:

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

Então, vamos resumir o que aprendemos. Typecript 2.0 introduzido typeRootse typespara carregar arquivos de declaração empacotados em pacotes npm . Se você tiver tipificações personalizadas ou d.tsarquivos soltos únicos que não estão contidos em uma pasta seguindo as convenções do pacote npm, então essas duas novas propriedades não são o que você deseja usar. O Typescript 2.0 realmente não muda como eles seriam consumidos. Você só precisa incluir esses arquivos em seu contexto de compilação de uma das muitas maneiras padrão:

  1. Incluindo-o diretamente em um .tsarquivo: ///<reference path="../typings/custom/lodash.d.ts" />

  2. Inclusive ./typings/custom/lodash.d.tsem sua files: []propriedade.

  3. Incluindo ./typings/index.d.tsem sua files: []propriedade (que então inclui recursivamente as outras tipificações.

  4. Adicionando ./typings/**ao seuincludes:

Esperançosamente, com base nesta discussão, você será capaz de dizer por que as mudanças que você tsconfig.jsonfez com que as coisas funcionassem novamente.

EDITAR:

Uma coisa que esqueci de mencionar é que as propriedades typeRootse typessão realmente úteis apenas para o carregamento automático de declarações globais.

Por exemplo se você

npm install @types/jquery

E você está usando o tsconfig padrão, então esse pacote de tipos jquery será carregado automaticamente e $estará disponível em todos os seus scripts sem ter que fazer mais nada ///<reference/>ouimport

A typeRoots:[]propriedade destina-se a adicionar locais adicionais de onde os pacotes de tipo serão carregados automaticamente.

O types:[]principal caso de uso da propriedade é desabilitar o comportamento de carregamento automático (definindo-o como uma matriz vazia) e, em seguida, listar apenas os tipos específicos que você deseja incluir globalmente.

A outra maneira de carregar pacotes de vários tipos typeRootsé usar a nova ///<reference types="jquery" />diretiva. Observe o em typesvez de path. Novamente, isso só é útil para arquivos de declaração global, normalmente aqueles que não o fazem import/export.

Agora, aqui está uma das coisas que causa confusão com typeRoots. Lembre-se, eu disse que typeRootsé sobre a inclusão global de módulos. Mas @types/foldertambém está envolvido na resolução de módulo padrão (independentemente de sua typeRootsconfiguração).

Especificamente, os módulos explicitamente importadores sempre ignora todos includes, excludes, files, typeRootse typesopções. Então, quando você faz:

import {MyType} from 'my-module';

Todas as propriedades mencionadas acima são completamente ignoradas. As propriedades relevantes durante resolução módulo são baseUrl, pathse moduleResolution.

Basicamente, ao usar noderesolução módulo, ele vai começar a procurar por um nome de arquivo my-module.ts, my-module.tsx, my-module.d.tscomeçando na pasta apontada pela sua baseUrlconfiguração.

Se não encontrar o arquivo, ele irá procurar por uma pasta chamada my-modulee então procurar por um package.jsoncom uma typingspropriedade, se houver package.jsonou nenhuma typingspropriedade dentro informando qual arquivo carregar, irá então procurar index.ts/tsx/d.tsdentro dessa pasta.

Se ainda assim não for bem-sucedido, ele pesquisará essas mesmas coisas na node_modulespasta, começando em seu baseUrl/node_modules.

Além disso, se ele não encontrar esses baseUrl/node_modules/@typesitens , ele pesquisará as mesmas coisas.

Se ainda não encontrou nada, ele vai começar a ir para o diretório pai e pesquisar node_modulese node_modules/@typeslá. Ele continuará subindo nos diretórios até atingir a raiz do seu sistema de arquivos (até mesmo obtendo módulos de nó fora do seu projeto).

Uma coisa que quero enfatizar é que a resolução do módulo ignora completamente qualquer typeRootsconfiguração definida. Portanto, se você configurou typeRoots: ["./my-types"], isso não será pesquisado durante a resolução explícita do módulo. Ele serve apenas como uma pasta onde você pode colocar os arquivos de definição global que deseja disponibilizar para todo o aplicativo, sem precisar importar ou fazer referência.

Por último, você pode substituir o comportamento do módulo com mapeamentos de caminho (ou seja, a pathspropriedade). Então, por exemplo, mencionei que nenhum costume typeRootsnão é consultado ao tentar resolver um módulo. Mas se você gostou, pode fazer esse comportamento acontecer da seguinte maneira:

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

O que isso faz é para todas as importações que correspondem ao lado esquerdo, tente modificar a importação como no lado direito antes de tentar incluí-la (o *no lado direito representa sua string de importação inicial. Por exemplo, se você importar:

import {MyType} from 'my-types';

Ele tentaria primeiro a importação como se você tivesse escrito:

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

E então, se não encontrasse, tentaria novamente sem o prefixo (o segundo item do array é apenas o *que significa a importação inicial.

Dessa forma, você pode adicionar pastas adicionais para pesquisar arquivos de declaração personalizados ou mesmo .tsmódulos personalizados que deseja import.

Você também pode criar mapeamentos personalizados para módulos específicos:

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

Isso deixaria você fazer

import {MyType} from 'my-types';

Mas então leia esses tipos de some/custom/folder/location/my-awesome-types-file.d.ts

dtabuenc
fonte
1
Obrigado pela sua resposta detalhada. Acontece que as soluções funcionam isoladamente, mas não se misturam bem. Isso esclarece as coisas, então eu vou te dar a recompensa. Se você pudesse encontrar tempo para responder mais alguns pontos, isso poderia ser realmente útil para outras pessoas. (1) Existe uma razão para typeRoots aceitar apenas uma estrutura de pasta específica? Parece arbitrário. (2) Se eu alterar typeRoots, typescript ainda inclui as pastas @types em node_modules, ao contrário das especificações. (3) O que é pathse como é diferente includepara fins de digitação?
Jodiug
1
Eu editei a resposta, entre em contato se tiver mais perguntas.
dtabuenc
1
Obrigado, li o resto e uau. Suportes para você encontrar tudo isso. Parece que há muitos comportamentos desnecessariamente complexos em ação aqui. Espero que o Typescript torne suas propriedades de configuração um pouco mais autodocumentáveis ​​no futuro.
Jodiug de
1
Deve haver um link para esta resposta em typescriptlang.org/docs/handbook/tsconfig-json.html
hgoebl
2
O último exemplo não deveria ser assim "paths" :{ "my-types": ["some/custom/folder/location/my-awesome-types-file"] }?
Koen.
6

Editar: desatualizado. Leia a resposta acima.

Ainda não entendi, mas encontrei uma solução. Use o seguinte 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"
  ]
}

Remova typings.jsone tudo na pasta, typingsexceto lodash.d.ts. Remova também todas as ///...referências

Jodiug
fonte
3

"*": ["./types/*"] Esta linha em caminhos tsconfig corrigiu tudo após 2 horas de luta.

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

types é o nome da pasta, que fica ao lado do node_module, ou seja, no nível da pasta do cliente (ou pasta src ) types/third-party-lib/index.d.ts
index.d.ts temdeclare module 'third-party-lib';

Nota: A configuração acima é uma configuração incompleta, apenas para dar uma ideia de como ela se parece com tipos, caminhos, inclusão e exclusão nela.

Uday Sravan K
fonte
1

Eu sei que esta é uma questão antiga, mas as ferramentas de digitação estão mudando continuamente. Acho que a melhor opção neste ponto é apenas confiar nas configurações de caminho "incluir" em tsconfig.json.

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

Por padrão, a menos que você faça alterações específicas, todos os arquivos * .ts e * .d.ts src/serão incluídos automaticamente. Acho que esta é a maneira mais fácil / melhor de incluir arquivos de declaração de tipo personalizado sem personalizar typeRootse types.

Referência:

realharry
fonte
0

Gostaria de compartilhar algumas de minhas observações recentes para estender a descrição detalhada que encontramos acima . Em primeiro lugar, é importante observar que o VS Code costuma ter opiniões diferentes sobre como as coisas devem ser feitas.

Vejamos um exemplo que trabalhei recentemente:

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

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

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

Caso iniciemos o projeto ele compila sem erros, mas caso removamos essa linha, o seguinte erro aparecerá durante o início:

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

A mesma mensagem de erro aparece no caso de colocarmos a reference typesdiretiva após as instruções de importação.

Importante : O /// <reference types="plotly.js" />deve estar na frente do arquivo de script de tipo! Consulte a documentação relacionada: link

As diretivas de barra tripla são válidas apenas na parte superior do arquivo que os contém.

Também recomendo ler a documentação no tsconfig.json e na seção typeRoot: link

Um pacote de tipos é uma pasta com um arquivo chamado index.d.ts ou uma pasta com um package.json que tem um campo de tipos.

A diretiva de referência acima funciona no seguinte cenário:

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

  declare namespace plotly {
    export interface ...
  }

E

tsconfig.json:

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

Nota : Na configuração acima, os dois "plotly.js" significam duas pastas e arquivos diferentes (biblioteca / definição). A importação se aplica à pasta "node_modules / plotly.js" (adicionada por npm install plotly.js), enquanto a referência se aplica a types / plotly.js.

Para o meu projeto de resolver as reclamações do VS Code e a ambigüidade dos "dois" plotly.js, acabei com a seguinte configuração:

  • todos os arquivos permaneceram no local 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 em meu sistema:

  • "ts-node": "~ 8.3.0",
  • "tslint": "~ 5.18.0",
  • "typescript": "~ 3.7.5"
SchLx
fonte