TypeScript 2:无类型npm模块的自定义键入


93

在尝试了其他地方发布的建议之后,我发现自己无法运行使用无类型NPM模块的打字稿项目。以下是一个最小的示例以及我尝试过的步骤。

对于这个最小的示例,我们将假装lodash没有现有的类型定义。因此,我们将忽略该包,@types/lodash并尝试将其类型文件手动添加lodash.d.ts到我们的项目中。

资料夹结构

  • node_modules
    • Lodash
  • src
    • foo.ts
  • 打字
    • 习俗
      • lodash.d.ts
    • 全球
    • 索引
  • package.json
  • tsconfig.json
  • Types.json

接下来,文件。

文件 foo.ts

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

console.log('Weeee');

lodash.d.ts直接从原始@types/lodash包复制文件。

文件 index.d.ts

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

文件 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"
}

文件 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"
  ]
}

文件 typings.json

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

如您所见,我尝试了多种不同的导入类型方法:

  1. 通过直接导入 foo.ts
  2. typings物业package.json
  3. 通过使用typeRootstsconfig.json与文件typings/index.d.ts
  4. 通过使用显式types输入tsconfig.json
  5. 通过将types目录包括在tsconfig.json
  6. 通过制作自定义typings.json文件并运行typings install

但是,当我运行Typescript时:

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

我究竟做错了什么?

Answers:


202

不幸的是,这些事情目前还没有得到很好的记录,但是即使您能够使其正常工作,我们也要仔细检查一下配置,以便您了解每个部分的工作以及它与打字稿处理和加载类型的关系。

首先让我们看一下您收到的错误:

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

实际上,此错误并非来自您的导入或引用,也不是您尝试在ts文件中的任何地方使用lodash的。而是由于对如何使用typeRootsand types属性有误解,因此让我们对其进行更多详细说明。

关于typeRoots:[]和的事情types:[]性能是它们不是通用的方式来加载任意声明(*.d.ts)文件。

这两个属性与新的TS 2.0功能直接相关,该功能允许打包和加载NPM包中的键入声明。

了解这些非常重要,因为这些仅适用于NPM格式的文​​件夹(即,包含package.json index.d.ts)。

的默认typeRoots值为:

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

默认情况下,这意味着打字稿将进入该node_modules/@types文件夹,并尝试将在该文件夹中找到的每个子文件夹加载为一个 npm package

重要的是要了解,如果文件夹没有类似npm软件包的结构,则此操作将失败。

这就是您所发生的情况,也是初始错误的根源。

您已将typeRoot切换为:

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

这意味着打字稿现在将扫描./typings文件夹中的子文件夹并尝试将找到的每个子文件夹作为npm模块加载。

因此,让我们假设您已经typeRoots设置了指向的位置,./typings但是还没有任何types:[]属性设置。您可能会看到以下错误:

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

这是因为tsc正在扫描您的./typings文件夹并找到子文件夹customglobal。然后,它试图将它们解释为npm包类型键入,但是这些文件夹中没有index.d.ts或存在package.json,因此您会收到错误消息。

现在让我们谈谈types: ['lodash']您要设置的属性。这是做什么的?默认情况下,打字稿将加载在中找到的所有子文件夹typeRoots。如果指定types:属性,它将仅加载那些特定的子文件夹。

在您的情况下,您要告诉它加载./typings/lodash文件夹,但该文件夹不存在。这就是为什么您得到:

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

因此,让我们总结一下我们学到的知识。引入了Typescript 2.0 typeRootstypes用于加载打包在npm软件包中的声明文件。如果d.ts遵循npm软件包约定,文件夹中不包含自定义类型或单个松散文件,则这两个新属性不是您要使用的属性。Typescript 2.0并没有真正改变它们的使用方式。您只需以多种标准方式之一将这些文件包括在编译上下文中:

  1. 直接将其包含在.ts文件中: ///<reference path="../typings/custom/lodash.d.ts" />

  2. 包括./typings/custom/lodash.d.ts在您的files: []财产中。

  3. 包括./typings/index.d.ts在您的files: []媒体资源中(然后递归地包括其他类型。

  4. 添加./typings/**到您的includes:

我们希望,在此基础上讨论,你就可以知道为什么你的变化,你疯了tsconfig.json让事情再次合作。

编辑:

我忘记提到的一件事是,typeRootsand types属性实际上仅对自动加载全局声明有用。

例如,如果您

npm install @types/jquery

而且您使用的是默认的tsconfig,那么该jquery类型包将自动加载,并且$在所有脚本中都可用,而无需进行任何进一步的操作///<reference/>import

typeRoots:[]属性旨在添加其他类型的软件包将从其自动加载的位置。

types:[]属性的主要用例是禁用自动加载行为(通过将其设置为空数组),然后仅列出要全局包含的特定类型。

从各种类型加载类型包的另一种方法typeRoots是使用new ///<reference types="jquery" />指令。请注意,types而不是path。同样,这仅对全局声明文件有用,通常不这样做import/export

现在,这是引起与混淆的原因之一typeRoots。记住,我说的typeRoots是关于模块的全球包含。但是,@types/folder它也涉及标准模块分辨率(与您的typeRoots设置无关)。

具体来说,明确导入模块总是绕过所有 includesexcludesfilestypeRootstypes选项。因此,当您这样做时:

import {MyType} from 'my-module';

上面提到的所有属性都被完全忽略。在相关性模块的分辨率baseUrlpathsmoduleResolution

基本上,当使用node模块的分辨率,它会开始寻找一个文件名my-module.tsmy-module.tsxmy-module.d.ts开始在文件夹中的指向baseUrl配置。

如果找不到文件,则它将查找名为的文件夹my-module,然后搜索package.json带有typings属性的文件夹,如果内部有package.json或没有typings属性告诉它要加载哪个文件,则将index.ts/tsx/d.ts在该文件夹中搜索。

如果仍然无法成功,它将在node_modules起始于的文件夹中搜索相同的内容baseUrl/node_modules

另外,如果找不到这些,它将搜索baseUrl/node_modules/@types所有相同的东西。

如果仍然没有发现任何东西就开始去上一级目录和搜索node_modules,并node_modules/@types在那里。它会一直向上移动目录,直到到达文件系统的根目录为止(甚至将节点模块移到项目外部)。

我要强调的一件事是,模块分辨率完全忽略了typeRoots您设置的任何内容。因此,如果您配置了typeRoots: ["./my-types"],则在明确的模块解析期间将不会搜索到此内容。它仅用作文件夹,您可以在其中放置要使整个应用程序使用的全局定义文件,而无需进一步导入或引用。

最后,您可以使用路径映射(即paths属性)覆盖模块行为。因此,例如,我提到typeRoots在尝试解析模块时不参考任何自定义。但是,如果您喜欢,可以按以下方式进行此行为:

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

这是对所有与左侧匹配的导入执行的操作,尝试在尝试包含右侧之前修改右侧的导入(*右侧的代表您的初始导入字符串。例如,如果导入:

import {MyType} from 'my-types';

它将首先尝试导入,就像您已经写过一样:

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

然后,如果找不到,它将再次尝试使用前缀(数组中的第二项只是 *意味着初始导入)。

因此,通过这种方式,您可以添加其他文件夹来搜索自定义声明文件,甚至可以搜索自定义.ts模块。import

您还可以为特定模块创建自定义映射:

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

这会让你做

import {MyType} from 'my-types';

但是然后从中读取这些类型 some/custom/folder/location/my-awesome-types-file.d.ts


1
感谢您的详细回答。事实证明,这些解决方案是孤立地工作的,但是它们不能很好地融合在一起。它确实可以澄清问题,所以我会给您赏金。如果您有时间回答其他问题,那对其他人可能真的很有帮助。(1)是否有理由让typeRoots只接受特定的文件夹结构?似乎是任意的。(2)如果更改typeRoots,则与规范相反,打字稿在node_modules中仍包含@types文件夹。(3)什么是打字输入?与输入目的有何paths不同include
乔迪格

1
我编辑了答案,如果您还有其他问题,请告诉我。
dtabuenc '16

1
谢谢,我读了剩下的,哇。为您找到所有这些提供支持。看起来这里有很多不必要的复杂行为。我希望Typescript在将来能使它们的配置属性更加自我记录。
乔迪格


2
最后一个例子不应该像"paths" :{ "my-types": ["some/custom/folder/location/my-awesome-types-file"] }吗?
科恩

6

编辑:过时。阅读上面的答案。

我仍然不明白这一点,但是我找到了解决方案。使用以下内容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"
  ]
}

删除typings.json和一切文件夹下typingslodash.d.ts。同时删除所有///...参考


3

"*": ["./types/*"] tsconfig路径中的这一行修复了2个小时后的所有问题。

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

类型是文件夹名称,它位于node_module旁边,即在客户端文件夹(或src文件夹) types/third-party-lib/index.d.ts
级别index.d.ts具有declare module 'third-party-lib';

注意:上面的配置是一个不完整的配置,只是为了了解它的类型,路径,包含和排除的外观。


1

我知道这是一个老问题,但是打字稿工具一直在不断变化。我认为目前最好的选择就是依赖tsconfig.json中的“ include”路径设置。

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

默认情况下,除非您进行特定更改,否则所有* .ts和所有* .d.ts文件src/都将自动包含在内。我认为这是包括自定义类型声明文件而不进行自定义typeRoots和的最简单/最佳方法types

参考:


0

我想分享一些我最近的观察,以扩展我们在上面找到的详细描述。首先,重要的是要注意,VS Code对于必须完成的事情经常有不同的看法。

让我们看一个我最近工作的例子:

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

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

VS Code可能会抱怨: No need to reference "plotly.js", since it is imported. (no-reference import) tslint(1)

如果我们启动该项目,则编译时不会出错,但如果删除该行,则在启动过程中将出现以下错误:

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

如果我们将reference types指令放置在import语句之后,也会出现相同的错误消息。

重要提示/// <reference types="plotly.js" />必须在类型脚本文件的最前面!请参阅相关文档:链接

三斜杠指令仅在其包含文件的顶部有效。

我还建议阅读tsconfig.json和typeRoot部分的文档:链接

类型包是具有名为index.d.ts的文件的文件夹或具有类型字段的具有package.json的文件夹。

上面的参考指令在以下情况下适用:

类型/plotly.js/index.d.ts :(链接

  declare namespace plotly {
    export interface ...
  }

tsconfig.json:

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

注意:在上述设置中,两个“ plotly.js”表示两个不同的文件夹和文件(库/定义)。导入适用于“ node_modules / plotly.js”文件夹(由npm install plotly.js),而引用应用于type / plotly.js。

为了解决VS Code的抱怨和“两个” plotly.js的歧义,我最终完成了以下配置:

  • 所有文件都保留在原始位置
  • 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:

  • “ ts-node”:“〜8.3.0”,
  • “ tslint”:“〜5.18.0”,
  • “ typescript”:“〜3.7.5”
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.