TypeScript 2: การพิมพ์แบบกำหนดเองสำหรับโมดูล npm ที่ไม่ได้พิมพ์


93

หลังจากลองใช้คำแนะนำที่โพสต์ในที่อื่น ๆฉันพบว่าตัวเองไม่สามารถรับโปรเจ็กต์ typescript ที่ทำงานโดยใช้โมดูล NPM ที่ไม่ได้พิมพ์ ด้านล่างนี้เป็นตัวอย่างเล็กน้อยและขั้นตอนที่ฉันลอง

สำหรับตัวอย่างเล็กน้อยนี้เราจะแสร้งทำเป็นว่าlodashไม่มีคำจำกัดความประเภทที่มีอยู่ ดังนั้นเราจะเพิกเฉยต่อแพ็คเกจ@types/lodashและพยายามเพิ่มไฟล์การพิมพ์lodash.d.tsลงในโครงการของเราด้วยตนเอง

โครงสร้างโฟลเดอร์

  • node_modules
    • ลอดจ์
  • src
    • foo.ts
  • การพิมพ์
    • กำหนดเอง
      • lodash.d.ts
    • ทั่วโลก
    • index.d.ts
  • package.json
  • tsconfig.json
  • typings.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. โดยใช้typeRootsในtsconfig.jsonไฟล์typings/index.d.ts
  4. โดยใช้ Explicit 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'.

ผมทำอะไรผิดหรือเปล่า?

คำตอบ:


209

น่าเสียดายที่สิ่งเหล่านี้ยังไม่ได้รับการบันทึกไว้เป็นอย่างดีในขณะนี้ แต่แม้ว่าคุณจะสามารถทำให้มันใช้งานได้ แต่เรามาดูการกำหนดค่าของคุณกันดีกว่าเพื่อที่คุณจะได้เข้าใจว่าแต่ละส่วนกำลังทำอะไรอยู่และเกี่ยวข้องกับวิธีการประมวลผล typescript และโหลดการพิมพ์

ก่อนอื่นเรามาดูข้อผิดพลาดที่คุณได้รับ:

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

ข้อผิดพลาดนี้ไม่ได้มาจากการนำเข้าหรือการอ้างอิงของคุณหรือความพยายามของคุณที่จะใช้ lodash ที่ใดก็ได้ในไฟล์ ts ของคุณ ค่อนข้างจะมาจากความเข้าใจผิดเกี่ยวกับวิธีการใช้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 หากคุณมีการพิมพ์แบบกำหนดเองหรือ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ทำกับสิ่งที่คุณทำไว้จึงกลับมาใช้ได้อีกครั้ง

แก้ไข:

สิ่งหนึ่งที่ฉันลืมพูดถึงคือtypeRootsและtypesคุณสมบัติมีประโยชน์จริงๆสำหรับการโหลดการประกาศทั่วโลกโดยอัตโนมัติ

ตัวอย่างเช่นถ้าคุณ

npm install @types/jquery

และคุณกำลังใช้ tsconfig เริ่มต้นแพ็คเกจประเภท jquery นั้นจะถูกโหลดโดยอัตโนมัติและ$จะพร้อมใช้งานตลอดทั้งสคริปต์ของคุณโดยไม่ต้องดำเนินการใด ๆ เพิ่มเติม///<reference/>หรือimport

typeRoots:[]คุณสมบัติจะหมายถึงการเพิ่มสถานที่เพิ่มเติมจากที่ประเภทแพคเกจจะถูกโหลด frrom โดยอัตโนมัติ

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, และpathsmoduleResolution

โดยทั่วไปเมื่อใช้nodeความละเอียดโมดูลก็จะเริ่มต้นการค้นหาสำหรับชื่อไฟล์my-module.ts, my-module.tsx, my-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'

จากนั้นหากไม่พบให้ลองอีกครั้งโดยไม่มีคำนำหน้า (รายการที่สองในอาร์เรย์เป็นเพียง*ซึ่งหมายถึงการนำเข้าเริ่มต้น

ดังนั้นวิธีนี้คุณสามารถเพิ่มโฟลเดอร์เพิ่มเติมในการค้นหาไฟล์ประกาศกำหนดเองหรือแม้กระทั่งที่กำหนดเองโมดูลที่คุณต้องการเพื่อให้สามารถ.tsimport

คุณยังสามารถสร้างการแมปที่กำหนดเองสำหรับโมดูลเฉพาะ:

"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 typescript จะยังคงรวมโฟลเดอร์ @types ไว้ใน node_modules ซึ่งตรงกันข้ามกับข้อมูลจำเพาะ (3) อะไรคืออะไรpathsและแตกต่างจากincludeวัตถุประสงค์ของการพิมพ์อย่างไร?
Jodiug

1
ฉันแก้ไขคำตอบแล้วโปรดแจ้งให้เราทราบหากคุณมีคำถามเพิ่มเติม
dtabuenc

1
ขอบคุณฉันอ่านส่วนที่เหลือและว้าว อุปกรณ์ประกอบฉากสำหรับคุณในการค้นหาทั้งหมดนี้ ดูเหมือนว่ามีพฤติกรรมที่ซับซ้อนโดยไม่จำเป็นมากมายในที่ทำงานที่นี่ ฉันหวังว่า typescript จะทำให้คุณสมบัติการกำหนดค่าของพวกเขามีการจัดทำเอกสารด้วยตนเองมากขึ้นในอนาคต
Jodiug

1
ควรมีลิงก์ไปยังคำตอบนี้จากtypescriptlang.org/docs/handbook/tsconfig-json.html
hgoebl

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และทุกอย่างภายใต้โฟลเดอร์ยกเว้นtypings lodash.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/**/*"
  ]
}

typesคือชื่อโฟลเดอร์ซึ่งอยู่ข้าง node_module เช่นในระดับของโฟลเดอร์ไคลเอนต์ (หรือโฟลเดอร์src ) types/third-party-lib/index.d.ts
index.d.tsมีdeclare module 'third-party-lib';

หมายเหตุ:การกำหนดค่าข้างต้นเป็นการกำหนดค่าที่ไม่สมบูรณ์เพียงเพื่อให้ทราบว่ามีลักษณะอย่างไรกับประเภทเส้นทางรวมและไม่รวมไว้ในนั้น


2

ฉันรู้ว่านี่เป็นคำถามเก่า แต่การใช้เครื่องมือ typescript มีการเปลี่ยนแปลงอย่างต่อเนื่อง ฉันคิดว่าตัวเลือกที่ดีที่สุดในตอนนี้ก็แค่อาศัยการตั้งค่าเส้นทาง "รวม" ใน tsconfig.json

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

ตามค่าเริ่มต้นหากคุณไม่ทำการเปลี่ยนแปลงใด ๆ ไฟล์ * .ts และ * .d.ts ทั้งหมดที่อยู่ภายใต้src/จะรวมไว้โดยอัตโนมัติ ผมคิดว่านี่เป็นวิธีที่ง่ายที่สุด / ดีที่สุดที่จะรวมถึงประเภทที่กำหนดเองไฟล์ประกาศโดยไม่ต้องปรับแต่งและtypeRootstypes

อ้างอิง:

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.