简体   繁体   English

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

[英]TypeScript 2: custom typings for untyped npm module

After trying suggestions posted in other places , I find myself unable to get a typescript project running that uses an untyped NPM module.在尝试了其他地方发布的建议后,我发现自己无法运行使用无类型 NPM 模块的打字稿项目。 Below is a minimal example and the steps that I tried.下面是一个最小的例子和我尝试过的步骤。

For this minimal example, we will pretend that lodash does not have existing type definitions.对于这个最小的例子,我们将假设lodash没有现有的类型定义。 As such, we will ignore the package @types/lodash and try to manually add its typings file lodash.d.ts to our project.因此,我们将忽略包@types/lodash并尝试手动将其@types/lodash文件lodash.d.ts添加到我们的项目中。

Folder structure文件夹结构

  • node_modules节点模块
    • lodash洛达什
  • src源文件
    • foo.ts
  • typings打字
    • custom定制
      • lodash.d.ts lodash.d.ts
    • global全球
    • index.d.ts索引.d.ts
  • package.json包.json
  • tsconfig.json配置文件
  • typings.json打字.json

Next, the files.接下来是文件。

File foo.ts文件foo.ts

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

console.log('Weeee');

File lodash.d.ts is copied directly from the original @types/lodash package.文件lodash.d.ts是直接从原始的@types/lodash包中复制的。

File index.d.ts文件index.d.ts

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

File package.json文件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"
}

File tsconfig.json文件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"
  ]
}

File typings.json文件typings.json

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

As you can see, I have tried many different ways of importing typings:如您所见,我尝试了许多不同的导入类型的方法:

  1. By directly importing it in foo.ts通过在foo.ts直接导入
  2. By a typings property in package.json通过package.jsontypings属性
  3. By using typeRoots in tsconfig.json with a file typings/index.d.ts通过在tsconfig.json使用typeRoots和文件typings/index.d.ts
  4. By using an explicit types in tsconfig.json通过在tsconfig.json使用显式types
  5. By including the types directory in tsconfig.json通过在tsconfig.json包含types目录
  6. By making a custom typings.json file and running typings install通过制作自定义typings.json文件并运行typings.json typings install

Yet, when I run Typescript:然而,当我运行打字稿时:

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

What am I doing wrong?我做错了什么?

Unfortunately these things are not currently very well documented, but even if you were able to get it working, let's go over your configuration so that you understand what each part is doing and how it relates to how typescript processes and loads typings.不幸的是,这些东西目前没有很好的文档记录,但即使你能够让它工作,让我们回顾一下你的配置,以便你了解每个部分在做什么以及它如何与 typescript 处理和加载类型相关。

First let's go over the error you are receiving:首先让我们回顾一下您收到的错误:

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

This error is actually not coming from your imports or references or your attempt to use lodash anywhere in your ts files.这个错误实际上不是来自您的导入或引用,也不是您尝试在 ts 文件中的任何地方使用 lodash。 Rather it is coming from a misunderstanding of how to use the typeRoots and types properties, so let's go into a little more detail on those.相反,它来自对如何使用typeRootstypes属性的误解,所以让我们更详细地了解这些。

The thing about typeRoots:[] and types:[] properties is that they are NOT general-purpose ways to load arbitrary declaration ( *.d.ts ) files. typeRoots:[]types:[]属性的问题在于它们不是加载任意声明 ( *.d.ts ) 文件的通用方法。

These two properties are directly related to the new TS 2.0 feature which allows packaging and loading typing declarations from NPM packages .这两个属性与新的 TS 2.0 特性直接相关,该特性允许从NPM 包打包和加载类型声明。

This is very important to understand, that these work only with folders in NPM format (ie a folder containing a package.json or index.d.ts ).理解这一点非常重要,这些仅适用于 NPM 格式的文件夹(即包含package.jsonindex.d.ts的文件夹)。

The default for typeRoots is: typeRoots的默认值是:

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

By default this means that typescript will go into the node_modules/@types folder and try to load every sub-folder it finds there as a npm package .默认情况下,这意味着打字稿将进入node_modules/@types文件夹并尝试加载它在那里找到的每个子文件夹作为npm package

It is important to understand that this will fail if a folder does not have an npm package-like structure.重要的是要了解,如果文件夹没有类似 npm 包的结构,这将失败。

This is what is happening in your case, and the source of your initial error.这就是您的情况发生的情况,也是您初始错误的根源。

You have switched typeRoot to be:您已将 typeRoot 切换为:

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

This means that typescript will now scan the ./typings folder for subfolders and try to load each subfolder it finds as an npm module.这意味着打字稿现在将扫描./typings文件夹中的文件夹,并尝试将它找到的每个子文件夹加载为 npm 模块。

So let's pretend you just had typeRoots setup to point to ./typings but did not yet have any types:[] property setup.因此,让我们假设您刚刚将typeRoots设置为指向./typings但还没有任何types:[]属性设置。 You would likely see these errors:您可能会看到以下错误:

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

This is because tsc is scanning your ./typings folder and finding the sub-folders custom and global .这是因为tsc正在扫描您的./typings文件夹并找到子文件夹customglobal It then is trying to interpret these as npm package-type typing, but there is no index.d.ts or package.json in these folders and so you get the error.然后它试图将这些解释为 npm 包类型类型,但这些文件夹中没有index.d.tspackage.json ,因此您会收到错误消息。

Now let's talk a bit about the types: ['lodash'] property you are setting.现在让我们谈谈您正在设置的types: ['lodash']属性。 What does this do?这有什么作用? By default, typescript will load all sub-folders it finds within your typeRoots .默认情况下,打字稿将加载它在typeRoots找到的所有子文件夹。 If you specify a types: property it will only load those specific sub-folders.如果你指定一个types:属性,它只会加载那些特定的子文件夹。

In your case you are telling it to load the ./typings/lodash folder but it doesn't exist.在您的情况下,您告诉它加载./typings/lodash文件夹,但它不存在。 This is why you get:这就是为什么你得到:

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

So let's summarize what we've learned.所以让我们总结一下我们学到的东西。 Typescript 2.0 introduced typeRoots and types for loading declaration files packaged in npm packages . Typescript 2.0 引入了typeRoots和用于加载打包在npm 包中的声明文件的types If you have custom typings or single loose d.ts files that are not contained withing a folder following npm package conventions, then these two new properties are not what you want to use.如果您有自定义类型或单个松散的d.ts文件,这些文件不包含在遵循 npm 包约定的文件夹中,那么这两个新属性不是您想要使用的。 Typescript 2.0 does not really change how these would be consumed. Typescript 2.0 并没有真正改变它们的使用方式。 You just have to include these files in your compilation context in one of the many standard ways:您只需要以多种标准方式之一将这些文件包含在您的编译上下文中:

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

  2. Including ./typings/custom/lodash.d.ts in your files: [] property.在您的files: []包含./typings/custom/lodash.d.ts files: []属性。

  3. Including ./typings/index.d.ts in your files: [] property (which then recursively includes the other typings.在您的files: []包含./typings/index.d.ts files: []属性(然后递归地包含其他类型。

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

Hopefully, based on this discussion you'll be able to tell why the changes you mad to your tsconfig.json made things work again.我们希望,在此基础上讨论,你就可以告诉为什么改变你疯了你tsconfig.json让事情再次合作。

EDIT:编辑:

One thing that I forgot to mention is that typeRoots and types property are really only useful for the automatic loading of global declarations.我忘记提及的一件事是typeRootstypes属性实际上仅对全局声明的自动加载有用。

For example if you例如,如果你

npm install @types/jquery

And you are using the default tsconfig, then that jquery types package will be loaded automatically and $ will be available throughout all your scripts wihtout having to do any further ///<reference/> or import并且您使用的是默认的 tsconfig,然后该 jquery 类型包将自动加载,并且$将在您的所有脚本中可用,而无需进一步///<reference/>import

The typeRoots:[] property is meant to add additional locations from where type packages will be loaded frrom automatically. typeRoots:[]属性是为了添加额外的位置,从那里自动加载类型

The types:[] property's primary use-case is to disable the automatic loading behavior (by setting it to an empty array), and then only listing specific types you want to include globally. types:[]属性的主要用例是禁用自动加载行为(通过将其设置为空数组),然后仅列出要全局包含的特定类型。

The other way to load type packages from the various typeRoots is to use the new ///<reference types="jquery" /> directive.从各种类型typeRoots加载类型包的另一种方法是使用新的///<reference types="jquery" />指令。 Notice the types instead of path .注意types而不是path Again, this is only useful for global declaration files, typically ones that don't do import/export .同样,这仅对全局声明文件有用,通常是不执行import/export

Now, here's one of the things that causes confusion with typeRoots .现在,这是导致与typeRoots混淆的一件事。 Remember, I said that typeRoots is about the global inclusion of modules.记住,我说过typeRoots是关于模块的全局包含。 But @types/folder is also involved in standard module-resolution (regardless of your typeRoots setting).@types/folder也参与标准模块解析(无论您的typeRoots设置如何)。

Specifically, explicitly importing modules always bypasses all includes , excludes , files , typeRoots and types options.具体来说,显式导入模块总是绕过所有includesexcludesfilestypes typeRootstypes选项。 So when you do:所以当你这样做时:

import {MyType} from 'my-module';

All the above mentioned properties are completely ignored.上面提到的所有属性都被完全忽略了。 The relevant properties during module resolution are baseUrl , paths , and moduleResolution .模块解析期间的相关属性是baseUrlpathsmoduleResolution

Basically, when using node module resolution, it will start searching for a file name my-module.ts , my-module.tsx , my-module.d.ts starting at the folder pointed to by your baseUrl configuration.基本上,当使用node模块的分辨率,它会开始寻找一个文件名my-module.tsmy-module.tsxmy-module.d.ts开始在文件夹由您指出baseUrl配置。

If it doesn't find the file, then it will look for a folder named my-module and then search for a package.json with a typings property, if there is package.json or no typings property inside telling it which file to load it will then search for index.ts/tsx/d.ts within that folder.如果它没有找到该文件,那么它会寻找一个名为my-module的文件夹,然后搜索一个带有typings属性的package.json ,如果里面有package.json或没有typings属性告诉它要加载哪个文件然后index.ts/tsx/d.ts在该文件夹中搜索index.ts/tsx/d.ts

If that's still not successful it will search for these same things in the node_modules folder starting at your baseUrl/node_modules .如果仍然不成功,它将在node_modules文件夹中搜索这些相同的内容,从您的node_modules baseUrl/node_modules

In addition, if it doesn't find these, it will search baseUrl/node_modules/@types for all the same things.此外,如果没有找到这些,它会搜索baseUrl/node_modules/@types以查找所有相同的内容。

If it still didn't find anything it will start going to the parent directory and search node_modules and node_modules/@types there.如果它仍然没有找到任何东西,它将开始转到父目录并在那里搜索node_modulesnode_modules/@types It will keep going up the directories until it reaches the root of your file system (even getting node-modules outside your project).它将继续向上目录,直到它到达您的文件系统的根目录(甚至在您的项目之外获取节点模块)。

One thing I want to emphasize is that module resolution completely ignores any typeRoots you set.我想强调的一件事是模块解析完全忽略您设置的任何typeRoots So if you configured typeRoots: ["./my-types"] , this will not get searched during explicit module resolution.因此,如果您配置了typeRoots: ["./my-types"] ,则在显式模块解析期间不会搜索到它。 It only serves as a folder where you can put global definition files you want to make available to the whole application without further need to import or reference.它仅用作一个文件夹,您可以在其中放置要提供给整个应用程序的全局定义文件,而无需进一步导入或引用。

Lastly, you can override the module behavior with path mappings (ie the paths property).最后,您可以使用路径映射(即paths属性)覆盖模块行为。 So for example, I mentioned that any custom typeRoots is not consulted when trying to resolve a module.例如,我提到在尝试解析模块时不会咨询任何自定义typeRoots But if you liked you can make this behavior happen as so:但是如果你喜欢你可以让这种行为发生:

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

What this does is for all imports that match the left-hand side, try modifying the import as in the right side before trying to include it (the * on the right hand side represents your initial import string. For example if you import:这样做是针对与左侧匹配的所有导入,尝试在尝试包含它之前修改右侧的导入(右侧的*代表您的初始导入字符串。例如,如果您导入:

import {MyType} from 'my-types';

It would first try the import as if you had written:它会首先尝试导入,就像你写的一样:

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

And then if it didn't find it would try again wihtout the prefix (second item in the array is just * which means the initial import.然后如果它没有找到它会在没有前缀的情况下再次尝试(数组中的第二项只是*这意味着初始导入。

So this way you can add additional folders to search for custom declaration files or even custom .ts modules that you want to be able to import .因此,通过这种方式,您可以添加其他文件夹来搜索自定义声明文件,甚至是您希望能够import自定义.ts模块。

You can also create custom mappings for specific modules:您还可以为特定模块创建自定义映射:

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

This would let you do这会让你做

import {MyType} from 'my-types';

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

Edit: out of date.编辑:过时了。 Read the answer above.阅读上面的答案。

I still don't understand this, but I found a solution.我仍然不明白这一点,但我找到了解决方案。 Use the following tsconfig.json :使用以下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"
  ]
}

Remove typings.json and everything under folder typings except lodash.d.ts .删除typings.json文件夹下,一切都typings ,除了lodash.d.ts Also remove all the ///... references同时删除所有///...引用

"*": ["./types/*"] This line in tsconfig paths fixed everything after 2 hours struggle. "*": ["./types/*"] tsconfig 路径中​​的这一行在经过 2 小时的努力后修复了所有内容。

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

types is the folder name, which sits beside node_module ie in the level of client folder (or src folder) types/third-party-lib/index.d.ts types是文件夹名称,它位于 node_module 旁边,即在客户端文件夹(或src文件夹)的级别中types/third-party-lib/index.d.ts
index.d.ts has declare module 'third-party-lib'; index.d.tsdeclare module 'third-party-lib';

Note: The above config is an incomplete config, just to give an idea of how it looks like with types, paths, include and exclude in it.注意:上面的配置是一个不完整的配置,只是为了让你了解它在类型、路径、包含和排除中的样子。

I know this is an old question, but typescript tooling has been continuously changing.我知道这是一个老问题,但打字稿工具一直在不断变化。 I think the best option at this point is just rely on the "include" path settings in tsconfig.json.我认为此时最好的选择就是依赖 tsconfig.json 中的“包含”路径设置。

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

By default, unless you make particular changes, all *.ts and all *.d.ts files under src/ will be automatically included.默认情况下,除非您进行特定更改,否则将自动包含src/下的所有 *.ts 和所有 *.d.ts 文件。 I think this is the easiest/best way to include custom type declaration files without customizing typeRoots and types .我认为这是包含自定义类型声明文件而不自定义typeRootstypes的最简单/最好的方法。

Reference:参考:

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM