TypeScript 2:為未定型的 npm 模組自訂型別定義
在嘗試了其他地方提供的建議後,我發現自己無法讓使用未定義類型的 NPM 模組的 TypeScript 專案運行。以下是一個最小範例以及我嘗試過的步驟。
對於這個最簡單的範例,我們將假裝 lodash 沒有現有的型別定義。因此,我們將忽略套件 @types/lodash,並嘗試手動將它的型別檔案 lodash.d.ts 添加到我們的專案中。
資料夾結構
node_modules
- lodash
src
- foo.ts
打字
自訂
- lodash.d.ts
接下來,文件。
檔案 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"
}
}
如你所見,我已嘗試了多種不同的引入類型定義的方法:
- 透過直接在
foo.ts中匯入它 - 透過
package.json中的typings屬性 - 通過在
tsconfig.json中使用typeRoots並搭配檔案typings/index.d.ts - 透過在
tsconfig.json中使用明確的types - 透過在
tsconfig.json中包含types目錄 - 透過製作自訂的
typings.json檔案並執行typings install
然而,當我執行 Typescript 時:
E:\temp\ts>tsc
error TS2688: Cannot find type definition file for 'lodash'.
我做錯了什麼?
答案#1
不幸的是,這些事情目前並沒有很好的文檔,但即使你能讓它運作,我們還是來看看你的配置,以便你了解每個部分的作用,以及它如何與 TypeScript 處理和加載型別的方式相關聯。
首先讓我們來看看你收到的錯誤訊息:
error TS2688: Cannot find type definition file for 'lodash'.
這個錯誤其實並不是來自你的匯入或引用,也不是來自你在 ts 文件中使用 lodash 的嘗試。而是來自於對如何使用 typeRoots 和 types 屬性的誤解,所以我們來更詳細地說明一下這些內容。
關於 typeRoots:[] 和 types:[] 屬性的重點是,它們 不是 用來載入任意宣告 (*.d.ts) 檔案的一般用途方法。
這兩個屬性與新的 TS 2.0 功能直接相關,該功能允許從 NPM 套件 打包和加載型別聲明。
這一點非常重要,需要理解,這些僅適用於 NPM 格式的資料夾(即包含 package.json 或 index.d.ts 的資料夾)。
typeRoots 的預設值是:
{
"typeRoots" : ["node_modules/@types"]
}
預設情況下,這意味著 TypeScript 會進入 node_modules/@types 資料夾,並嘗試將它找到的每個子資料夾作為一個 npm 套件 加載。
重要的是要明白,如果資料夾沒有類似 npm 套件的結構,這將會失敗。
這就是在你的情況下發生的事情,以及你最初錯誤的根源。
你已將 typeRoot 更改為:
{
"typeRoots" : ["./typings"]
}
這意味著 TypeScript 現在將會掃描 ./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 資料夾並找到子資料夾 custom 和 global。然後它嘗試將這些解釋為 npm 套件類型的型別,但這些資料夾中沒有 index.d.ts 或 package.json,因此你會收到錯誤。
現在讓我們來談談你所設定的 types: ['lodash'] 屬性。這個屬性是做什麼的呢?預設情況下,TypeScript 會載入它在你的 typeRoots 中找到的 所有 子資料夾。如果你指定了 types: 屬性,它只會載入那些特定的子資料夾。
在你的情況下,你是告訴它加載 ./typings/lodash 資料夾,但該資料夾不存在。這就是你收到以下訊息的原因:
error TS2688: Cannot find type definition file for 'lodash'
所以讓我們總結一下我們學到的內容。Typescript 2.0 引入了 typeRoots 和 types 來加載打包在 npm packages 中的聲明文件。如果你有自定義的 typings 或單個散裝的 d.ts 文件,而這些文件沒有按照 npm package 的慣例放在資料夾中,那麼這兩個新屬性就不是你想使用的。Typescript 2.0 並沒有真正改變這些文件的使用方式。你只需要以其中一種標準方式將這些文件包含在你的編譯上下文中即可:
直接將它包含在
.ts文件中:///<reference path="../typings/custom/lodash.d.ts" />在你的
files: []屬性中包括./typings/custom/lodash.d.ts。在你的
files: []屬性中包含./typings/index.d.ts(這樣將遞歸包含其他類型定義)。將
./typings/**添加到你的includes:
希望根據這次討論,你能夠理解為什麼你對 tsconfig.json 所做的修改讓事情再次運作起來。
編輯:
我忘了提到的一件事是,typeRoots 和 types 屬性實際上只有在 自動 加載全局聲明時才真正有用。
例如,如果你
npm install @types/jquery
如果你使用的是預設的 tsconfig,那麼 jQuery types 套件將會自動被載入,並且 $ 將可以在所有腳本中使用,而無需進行任何額外的 ///<reference/> 或 import 操作。
typeRoots:[] 屬性是用來添加額外的位置,從這些位置中類型 套件 將會自動被加載。
types:[] 屬性的主要用途是禁用自動加載行為(通過將其設置為空數組),然後僅列出您希望在全局包含的特定類型。
從各種 typeRoots 載入型別套件的另一種方式是使用新的 ///<reference types="jquery" /> 指令。注意這裡使用的是 types 而不是 path。再次說明,這僅對全域宣告檔有用,通常是那些不使用 import/export 的檔案。
現在,這裡有一件關於 typeRoots 會引起混淆的事情。記住,我說過 typeRoots 是關於全域模組的包含。但 @types/folder 也參與標準模組解析(無論你的 typeRoots 設定為何)。
具體來說,明確地匯入模組總是會繞過所有的 includes、excludes、files、typeRoots 和 types 選項。所以當你這麼做時:
import {MyType} from 'my-module';
上述所有提到的屬性都會被完全忽略。模組解析過程中相關的屬性是 baseUrl、paths 和 moduleResolution。
基本上,當使用 node 模組解析時,它會從你的 baseUrl 配置所指向的資料夾開始搜尋檔案名稱 my-module.ts、my-module.tsx、my-module.d.ts。
如果找不到該檔案,它將會尋找名為 my-module 的資料夾,然後搜尋具有 typings 屬性的 package.json,如果該資料夾中沒有 package.json 或 typings 屬性來告訴它要載入哪個檔案,則會在該資料夾中搜尋 index.ts/tsx/d.ts。
如果仍然不成功,它將從你的 baseUrl/node_modules 開始,在 node_modules 資料夾中搜尋這些相同的東西。
此外,如果它找不到這些,它將會在 baseUrl/node_modules/@types 中搜尋所有相同的項目。
如果它仍然沒有找到任何東西,它將開始前往父目錄並在那裡搜索 node_modules 和 node_modules/@types。它會繼續向上搜索目錄,直到到達你的檔案系統根目錄(甚至會查找在你專案外部的 node_modules)。
我想強調的一點是,模組解析會完全忽略你設置的任何 typeRoots。所以如果你配置了 typeRoots: ["./my-types"],在明確的模組解析過程中,這個資料夾不會被搜索。它僅作為一個資料夾,你可以將全局定義文件放在裡面,讓整個應用程式都可以使用,而無需進一步導入或引用。
最後,你可以使用路徑映射(即 paths 屬性)來覆寫模組行為。例如,我提到過在嘗試解析模組時,不會諮詢任何自訂的 typeRoots。但是如果你願意,你可以讓這個行為發生如下:
"paths" :{
"*": ["my-custom-types/*", "*"]
}
這段作用是對所有與左側匹配的匯入,嘗試在嘗試包含之前按右側修改匯入(右側的 * 代表你最初的匯入字串。例如,如果你匯入:
import {MyType} from 'my-types';
它會先嘗試將導入視為你已經寫了:
import {MyType} from 'my-custom-types/my-types'
然後如果找不到,它會嘗試再次執行而不使用前綴(陣列中的第二個項目只是 *,這意味著初始導入)。
這樣一來,你就可以添加額外的資料夾來搜尋自訂的宣告檔案,甚至是你想要能夠 import 的自訂 .ts 模組。
你也可以為特定模組建立自訂對應:
"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 讀取那些類型
答案#2
編輯:過時了。請閱讀上面的答案。
我仍然不理解這個,但我找到了解決方案。使用以下 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 以及 typings 資料夾下的所有內容,但保留 lodash.d.ts。同時刪除所有 ///... 的引用
