TypeScript 2: các kiểu chữ tùy chỉnh cho mô-đun npm không định kiểu


93

Sau khi thử các đề xuất được đăng ở những nơi khác , tôi thấy mình không thể chạy dự án sắp chữ sử dụng mô-đun NPM chưa được định kiểu. Dưới đây là một ví dụ tối thiểu và các bước mà tôi đã thử.

Đối với ví dụ tối thiểu này, chúng tôi sẽ giả vờ rằng lodashkhông có định nghĩa loại hiện có. Như vậy, chúng tôi sẽ bỏ qua gói @types/lodashvà cố gắng thêm tệp lodash.d.tsđánh máy của nó vào dự án của chúng tôi theo cách thủ công .

Cấu trúc thư mục

  • node_modules
    • nhà nghỉ
  • src
    • foo.ts
  • đánh máy
    • tập quán
      • lodash.d.ts
    • toàn cầu
    • index.d.ts
  • package.json
  • tsconfig.json
  • typings.json

Tiếp theo, các tập tin.

Tập tin foo.ts

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

console.log('Weeee');

Tệp lodash.d.tsđược sao chép trực tiếp từ @types/lodashgói gốc .

Tập tin index.d.ts

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

Tập tin 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"
}

Tập tin 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"
  ]
}

Tập tin typings.json

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

Như bạn có thể thấy, tôi đã thử nhiều cách nhập kiểu chữ khác nhau:

  1. Bằng cách nhập trực tiếp nó vào foo.ts
  2. Bởi một typingstài sản ởpackage.json
  3. Bằng cách sử dụng typeRootstrong tsconfig.jsonvới một tệptypings/index.d.ts
  4. Bằng cách sử dụng một cách rõ ràng typestrongtsconfig.json
  5. Bằng cách đưa typesthư mục vàotsconfig.json
  6. Bằng cách tạo một typings.jsontệp tùy chỉnh và chạytypings install

Tuy nhiên, khi tôi chạy Typescript:

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

Tôi đang làm gì sai?

Câu trả lời:


206

Thật không may, những điều này hiện không được ghi chép đầy đủ, nhưng ngay cả khi bạn có thể làm cho nó hoạt động, hãy xem qua cấu hình của bạn để bạn hiểu từng phần đang hoạt động như thế nào và nó liên quan như thế nào đến cách xử lý bảng chữ và tải các bản đánh máy.

Trước tiên, hãy xem lại lỗi bạn đang gặp phải:

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

Lỗi này thực sự không đến từ việc nhập hoặc tham chiếu của bạn hoặc việc bạn cố gắng sử dụng lodash ở bất kỳ đâu trong tệp ts của mình. Thay vào đó, nó đến từ sự hiểu lầm về cách sử dụng các thuộc tính typeRootstypes, vì vậy chúng ta hãy đi vào chi tiết hơn một chút về chúng.

Vấn đề typeRoots:[]types:[]thuộc tính là chúng KHÔNG phải là cách có mục đích chung để tải *.d.tscác tệp khai báo ( ) tùy ý .

Hai thuộc tính này liên quan trực tiếp đến tính năng TS 2.0 mới cho phép đóng gói và tải các khai báo nhập từ các gói NPM .

Điều này rất quan trọng cần hiểu, rằng chúng chỉ hoạt động với các thư mục ở định dạng NPM (tức là một thư mục chứa package.json hoặc index.d.ts ).

Giá trị mặc định typeRootslà:

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

Theo mặc định, điều này có nghĩa là chỉ định kiểu sẽ đi vào node_modules/@typesthư mục và cố gắng tải mọi thư mục con mà nó tìm thấy ở đó dưới dạng gói npm .

Điều quan trọng là phải hiểu rằng điều này sẽ không thành công nếu một thư mục không có cấu trúc giống như gói npm.

Đây là những gì đang xảy ra trong trường hợp của bạn và là nguồn gốc của lỗi ban đầu của bạn.

Bạn đã chuyển typeRoot để trở thành:

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

Điều này có nghĩa là bản đánh máy bây giờ sẽ quét ./typingsthư mục cho thư mục con và cố gắng tải từng thư mục con mà nó tìm thấy dưới dạng mô-đun npm.

Vì vậy, giả sử bạn vừa typeRootsthiết lập để trỏ đến ./typingsnhưng chưa types:[]thiết lập thuộc tính nào . Bạn có thể sẽ thấy những lỗi sau:

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

Điều này là do tscđang quét ./typingsthư mục của bạn và tìm các thư mục con customglobal. Sau đó, nó đang cố gắng giải thích những điều này là kiểu nhập kiểu gói npm, nhưng không có index.d.tshoặcpackage.json trong các thư mục này và do đó bạn gặp lỗi.

Bây giờ chúng ta hãy nói một chút về thuộc types: ['lodash']tính bạn đang thiết lập. Điều này làm gì? Theo mặc định, typecript sẽ tải tất cả các thư mục con mà nó tìm thấy trong của bạn typeRoots. Nếu bạn chỉ định mộttypes: tính, nó sẽ chỉ tải các thư mục con cụ thể đó.

Trong trường hợp của bạn, bạn đang yêu cầu nó tải ./typings/lodashthư mục nhưng nó không tồn tại. Đây là lý do tại sao bạn nhận được:

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

Vì vậy, hãy tóm tắt những gì chúng ta đã học được. Typecript 2.0 được giới thiệu typeRootstypesđể tải các tệp khai báo được đóng gói trong gói npm . Nếu bạn có các kiểu đánh máy tùy chỉnh hoặc d.tscác tệp rời đơn lẻ không được chứa trong một thư mục theo quy ước gói npm, thì hai thuộc tính mới này không phải là thứ bạn muốn sử dụng. Typecript 2.0 không thực sự thay đổi cách chúng được sử dụng. Bạn chỉ cần đưa các tệp này vào ngữ cảnh biên dịch của mình theo một trong nhiều cách tiêu chuẩn:

  1. Trực tiếp đưa nó vào một .tstệp: ///<reference path="../typings/custom/lodash.d.ts" />

  2. Kể cả ./typings/custom/lodash.d.tstrong files: []tài sản của bạn .

  3. Bao gồm ./typings/index.d.tstrong thuộc tính của bạn files: [](sau đó bao gồm đệ quy các kiểu đánh máy khác.

  4. Thêm ./typings/**vào của bạnincludes:

Hy vọng rằng, dựa trên cuộc thảo luận này, bạn sẽ có thể biết lý do tại sao những thay đổi mà bạn tức giận đối với tsconfig.jsonnhững thứ bạn đã làm lại hoạt động trở lại.

BIÊN TẬP:

Một điều mà tôi quên đề cập đến là typeRootstypesbất động sản là thực sự chỉ hữu ích cho việc tự động tải của tờ khai toàn cầu.

Ví dụ nếu bạn

npm install @types/jquery

Và bạn đang sử dụng tsconfig mặc định, sau đó gói loại jquery đó sẽ được tải tự động và $sẽ có sẵn trong tất cả các tập lệnh của bạn mà bạn phải thực hiện thêm bất kỳ thao tác nào ///<reference/>hoặcimport

Các typeRoots:[]tài sản có nghĩa là để thêm các địa điểm khác từ nơi loại gói sẽ được nạp frrom tự động.

Các types:[]trường hợp sử dụng chính tài sản là để vô hiệu hóa các hành vi tải tự động (bằng cách đặt nó vào một mảng trống), và sau đó chỉ liệt kê các loại cụ thể mà bạn muốn bao gồm toàn cầu.

Cách khác để tải các gói loại từ các gói khác nhau typeRootslà sử dụng ///<reference types="jquery" />chỉ thị mới . Chú ý typesthay vì path. Một lần nữa, điều này chỉ hữu ích cho các tệp khai báo chung, thường là những tệp không có import/export.

Bây giờ, đây là một trong những thứ gây nhầm lẫn typeRoots. Hãy nhớ rằng, tôi đã nói đó typeRootslà về sự bao gồm toàn cầu của các mô-đun. Nhưng @types/foldercũng liên quan đến độ phân giải mô-đun tiêu chuẩn (bất kể typeRootscài đặt của bạn là gì ).

Cụ thể, các module nhập khẩu rõ ràng luôn luôn bỏ qua tất cả includes, excludes, files, typeRootstypescác tùy chọn. Vì vậy, khi bạn làm:

import {MyType} from 'my-module';

Tất cả các thuộc tính nêu trên hoàn toàn bị bỏ qua. Các thuộc tính có liên quan trong quá trình giải quyết mô-đunbaseUrl, paths, và moduleResolution.

Về cơ bản, khi sử dụng nodeđộ phân giải module, nó sẽ bắt đầu tìm kiếm một tên file my-module.ts, my-module.tsx, my-module.d.tsbắt đầu từ thư mục trỏ đến bởi bạn baseUrlcấu hình.

Nếu nó không tìm thấy tệp, thì nó sẽ tìm kiếm một thư mục có tên my-modulevà sau đó tìm kiếm package.jsonmột thuộc typingstính, nếu có package.jsonhoặc không có thuộc typingstính bên trong cho nó biết tệp cần tải thì nó sẽ tìm kiếm index.ts/tsx/d.tstrong thư mục đó.

Nếu vẫn không thành công, nó sẽ tìm kiếm những thứ tương tự trong node_modulesthư mục bắt đầu từ của bạn baseUrl/node_modules.

Ngoài ra, nếu nó không tìm thấy những thứ này, nó sẽ tìm kiếm baseUrl/node_modules/@typestất cả những thứ tương tự.

Nếu nó vẫn không tìm thấy bất cứ điều gì nó sẽ bắt đầu đi đến thư mục cha và tìm kiếm node_modulesnode_modules/@typesở đó. Nó sẽ tiếp tục đi lên các thư mục cho đến khi nó đạt đến thư mục gốc của hệ thống tệp của bạn (thậm chí nhận được mô-đun nút bên ngoài dự án của bạn).

Một điều tôi muốn nhấn mạnh là độ phân giải mô-đun hoàn toàn bỏ qua bất kỳ điều gì typeRootsbạn đặt. Vì vậy, nếu bạn đã định cấu hình typeRoots: ["./my-types"], điều này sẽ không được tìm kiếm trong quá trình phân giải mô-đun rõ ràng. Nó chỉ đóng vai trò là một thư mục nơi bạn có thể đặt các tệp định nghĩa chung mà bạn muốn cung cấp cho toàn bộ ứng dụng mà không cần nhập hoặc tham khảo thêm.

Cuối cùng, bạn có thể ghi đè hành vi mô-đun bằng ánh xạ đường dẫn (tức là thuộc pathstính). Vì vậy, ví dụ, tôi đã đề cập rằng bất kỳ tùy chỉnh nào typeRootskhông được tham khảo khi cố gắng giải quyết một mô-đun. Nhưng nếu bạn thích, bạn có thể làm cho hành vi này xảy ra như vậy:

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

Điều này thực hiện đối với tất cả các lần nhập khớp với phía bên trái, hãy thử sửa đổi phép nhập như ở phía bên phải trước khi cố gắng đưa vào ( *phía bên phải biểu thị chuỗi nhập ban đầu của bạn. Ví dụ: nếu bạn nhập:

import {MyType} from 'my-types';

Trước tiên, nó sẽ thử nhập như thể bạn đã viết:

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

Và sau đó nếu nó không tìm thấy nó sẽ thử lại với tiền tố (mục thứ hai trong mảng chỉ *có nghĩa là nhập ban đầu.

Vì vậy, bằng cách này, bạn có thể thêm các thư mục bổ sung để tìm kiếm các tệp khai báo tùy chỉnh hoặc thậm chí các .tsmô-đun tùy chỉnh mà bạn muốn import.

Bạn cũng có thể tạo ánh xạ tùy chỉnh cho các mô-đun cụ thể:

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

Điều này sẽ để bạn làm

import {MyType} from 'my-types';

Nhưng sau đó đọc những loại đó từ some/custom/folder/location/my-awesome-types-file.d.ts


1
Cảm ơn câu trả lời chi tiết của bạn. Hóa ra các giải pháp hoạt động riêng lẻ, nhưng chúng không kết hợp với nhau. Nó làm rõ mọi thứ vì vậy tôi sẽ đưa cho bạn tiền thưởng. Nếu bạn có thể tìm thấy thời gian để trả lời thêm một số điểm, điều đó có thể thực sự hữu ích cho người khác. (1) Có lý do gì để typeRoots chỉ chấp nhận một cấu trúc thư mục cụ thể không? Nó có vẻ tùy tiện. (2) Nếu tôi thay đổi typeRoots, thì typecript vẫn bao gồm các thư mục @types trong node_modules, trái với thông số kỹ thuật. (3) Nó khác gì pathsvà khác includevới mục đích của việc đánh máy như thế nào?
Jodiug

1
Tôi đã chỉnh sửa câu trả lời, hãy cho tôi biết nếu bạn có thêm bất kỳ câu hỏi nào.
dtabuenc

1
Cảm ơn bạn, tôi đã đọc phần còn lại và wow. Đạo cụ cho bạn để tìm tất cả những điều này. Có vẻ như có rất nhiều hành vi phức tạp không cần thiết tại nơi làm việc. Tôi hy vọng Typescript sẽ làm cho các thuộc tính cấu hình của chúng tự lập tài liệu hơn một chút trong tương lai.
Jodiug

1
Nên có một liên kết đến câu trả lời này từ typescriptlang.org/docs/handbook/tsconfig-json.html
hgoebl

2
Ví dụ cuối cùng không nên như thế "paths" :{ "my-types": ["some/custom/folder/location/my-awesome-types-file"] }nào?
Koen.

6

Chỉnh sửa: lỗi thời. Đọc câu trả lời ở trên.

Tôi vẫn không hiểu điều này, nhưng tôi đã tìm ra giải pháp. Sử dụng như sau 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"
  ]
}

Loại bỏ typings.jsonvà tất cả mọi thứ trong thư mục typingsngoại trừ lodash.d.ts. Đồng thời xóa tất cả các ///...tham chiếu


3

"*": ["./types/*"] Dòng này trong đường dẫn tsconfig đã sửa mọi thứ sau 2 giờ vật lộn.

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

các loại là tên thư mục, nằm bên cạnh node_module, tức là trong cấp thư mục khách (hoặc thư mục src ) types/third-party-lib/index.d.ts
index.d.tsdeclare module 'third-party-lib';

Lưu ý: Cấu hình trên là cấu hình chưa hoàn chỉnh, chỉ để cung cấp ý tưởng về cách nó trông như thế nào với các loại, đường dẫn, bao gồm và loại trừ trong đó.


1

Tôi biết đây là một câu hỏi cũ, nhưng công cụ sắp chữ liên tục thay đổi. Tôi nghĩ lựa chọn tốt nhất tại thời điểm này là chỉ dựa vào cài đặt đường dẫn "bao gồm" trong tsconfig.json.

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

Theo mặc định, trừ khi bạn thực hiện các thay đổi cụ thể, tất cả các tệp * .ts và tất cả các tệp * .d.ts bên dưới src/sẽ tự động được đưa vào. Tôi nghĩ đây là cách dễ nhất / tốt nhất để bao gồm các tệp khai báo kiểu tùy chỉnh mà không cần tùy chỉnh typeRootstypes .

Tài liệu tham khảo:


0

Tôi muốn chia sẻ một số quan sát gần đây của tôi để mở rộng mô tả chi tiết mà chúng tôi tìm thấy ở trên . Đầu tiên, điều quan trọng cần lưu ý là VS Code thường có quan điểm khác nhau về cách mọi thứ phải được thực hiện.

Hãy xem một ví dụ tôi đã làm việc gần đây:

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

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

VS Code có thể phàn nàn rằng: No need to reference "plotly.js", since it is imported. (no-reference import) tslint(1)

Trong trường hợp chúng tôi bắt đầu dự án, nó sẽ biên dịch mà không có lỗi, nhưng trong trường hợp chúng tôi xóa dòng đó, lỗi sau sẽ xuất hiện khi bắt đầu:

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

Thông báo lỗi tương tự xuất hiện trong trường hợp chúng ta đặt reference typeschỉ thị sau các câu lệnh nhập.

Quan trọng : /// <reference types="plotly.js" />Phải có ở mặt trước của tệp script kiểu! Xem tài liệu liên quan: link

Các lệnh ba dấu gạch chéo chỉ có giá trị ở đầu tệp chứa chúng.

Tôi cũng khuyên bạn nên đọc tài liệu trên tsconfig.json và trên phần typeRoot: liên kết

Gói loại là một thư mục có tệp được gọi là index.d.ts hoặc một thư mục với package.json có trường loại.

Chỉ thị tham chiếu trên hoạt động trong trường hợp sau:

type / plotly.js / index.d.ts: ( liên kết )

  declare namespace plotly {
    export interface ...
  }

tsconfig.json:

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

Lưu ý : Trong thiết lập ở trên, hai "plotly.js" có nghĩa là hai thư mục và tệp khác nhau (thư viện / định nghĩa). Việc nhập áp dụng cho thư mục "node_modules / plotly.js" (được thêm bởinpm install plotly.js ), trong khi tham chiếu áp dụng cho các loại / plotly.js.

Đối với dự án của tôi để giải quyết các khiếu nại về Mã VS và sự không rõ ràng của "hai" plotly.js, tôi đã kết thúc với cấu hình sau:

  • tất cả tệp vẫn ở vị trí ban đầu
  • 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 trên hệ thống của tôi:

  • "ts-node": "~ 8.3.0",
  • "tslint": "~ 5.18.0",
  • "typecript": "~ 3.7.5"
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.