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. Використовуючи typeRootsin tsconfig.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'.

Що я роблю не так?

Відповіді:


206

На жаль, в даний час ці речі не дуже добре задокументовані, але навіть якщо ви змогли змусити його працювати, давайте переглянемо вашу конфігурацію, щоб ви зрозуміли, що робить кожна частина і як це пов’язано з тим, як машинопис обробляє та завантажує типізації.

Спочатку розглянемо помилку, яку ви отримуєте:

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

За замовчуванням це означає, що машинопис піде в node_modules/@typesпапку і спробує завантажити кожну знайдену там папку як пакет npm .

Важливо розуміти, що це не вдасться, якщо папка не має пакетоподібної структури 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папки та пошук підпапок customта global. Потім він намагається інтерпретувати їх як набір типу пакунка npm, але в цих папках немає index.d.tsабо package.jsonв них з’являється помилка.

А тепер давайте трохи поговоримо про types: ['lodash']властивість, яку ви встановлюєте. Що це робить? За замовчуванням typecript завантажує всі вкладені папки, знайдені у вашому 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, pathsі moduleResolution.

В принципі, при використанні 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'

А потім, якщо він не знайшов, спробує знову без префікса (другий елемент масиву - це саме те, *що означає початковий імпорт.

Таким чином, ви можете додати додаткові папки для пошуку файлів спеціальних декларацій або навіть користувацьких .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, typecript все одно включає папки @types у node_modules, всупереч специфікації. (3) Що таке pathsі чим воно відрізняється includeдля набору тексту?
Джодюг

1
Я відредагував відповідь. Повідомте мене, якщо у вас виникнуть запитання.
dtabuenc

1
Дякую, решту прочитав і нічого собі. Реквізит вам для пошуку всього цього. Схоже, на роботі тут багато зайво складної поведінки. Сподіваюся, Typescript зробить їх властивості конфігурації трохи більш самодокументуваннями в майбутньому.
Джодюг

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';

Примітка: Наведена вище конфігурація є неповною конфігурацією, лише для того, щоб дати уявлення про те, як вона виглядає з типами, шляхами, включає та виключає в ній.


1

Я знаю, що це старе запитання, але інструменти машинопису постійно змінюються. Я думаю, що найкращим варіантом на даний момент є просто покластись на налаштування шляху "включити" у tsconfig.json.

  "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директиву після операторів імпорту.

Важливо : /// <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), тоді як посилання стосується типів / plotly.js.

Що стосується мого проекту з врегулювання коду VS та невизначеності "двох" 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",
  • "машинопис": "~ 3.7.5"
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.