TypeScript 2: настраиваемые типы для нетипизированного модуля npm

93

Попробовав предложения, опубликованные в других местах , я обнаружил, что не могу запустить проект машинописного текста, который использует нетипизированный модуль NPM. Ниже приведен минимальный пример и шаги, которые я пробовал.

Для этого минимального примера мы сделаем вид, что у lodashнего нет существующих определений типов. Таким образом, мы проигнорируем пакет @types/lodashи попытаемся вручную добавить его файл типизации lodash.d.tsв наш проект.

Структура папки

  • node_modules
    • Lodash
  • 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. Используя явный typesintsconfig.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

К сожалению, эти вещи в настоящее время не очень хорошо документированы, но даже если вы смогли заставить его работать, давайте рассмотрим вашу конфигурацию, чтобы вы поняли, что делает каждая часть и как она связана с тем, как машинописный текст обрабатывает и загружает типизацию.

Сначала рассмотрим ошибку, которую вы получаете:

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']вами свойстве. Что это значит? По умолчанию машинописный текст загружает все подпапки, которые он находит в вашем 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:[]свойство предназначено для добавления дополнительных мест, откуда пакеты типов будут загружаться автоматически.

В 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 там. Он будет продолжать подниматься по каталогам, пока не достигнет корня вашей файловой системы (даже получая node-модули вне вашего проекта).

Одна вещь, которую я хочу подчеркнуть, это то, что разрешение модуля полностью игнорирует все, что 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

dtabuenc
источник
1
Спасибо за подробный ответ. Оказывается, растворы работают изолированно, но плохо смешиваются. Это проясняет ситуацию, поэтому я дам вам награду. Если бы у вас было время ответить еще на несколько вопросов, это могло бы быть действительно полезно для других людей. (1) Есть ли причина, по которой typeRoots принимает только определенную структуру папок? Это кажется произвольным. (2) Если я изменю typeRoots, машинописный текст по-прежнему будет включать папки @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

Я знаю, что это старый вопрос, но инструменты машинописного текста постоянно меняются. Я думаю, что лучший вариант на данном этапе - просто положиться на настройки пути "include" в tsconfig.json.

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

По умолчанию, если вы не внесете особых изменений, все файлы * .ts и все * .d.ts src/будут автоматически включены. Я думаю, что это самый простой / лучший способ включить файлы объявления пользовательского типа без настройкиtypeRoots и types.

Справка:

Realharry
источник