TypeScript 2: niestandardowe typy dla nietypowego modułu npm

93

Po wypróbowaniu sugestii opublikowanych w innych miejscach nie mogę uruchomić projektu maszynopisu, który korzysta z nietypowego modułu NPM. Poniżej znajduje się minimalny przykład i kroki, które próbowałem.

W tym minimalnym przykładzie będziemy udawać, że lodashnie ma istniejących definicji typów. W związku z tym zignorujemy pakiet @types/lodashi spróbujemy ręcznie dodać plik z typami lodash.d.tsdo naszego projektu.

Struktura folderów

  • node_modules
    • lodash
  • src
    • foo.ts
  • typy
    • zwyczaj
      • lodash.d.ts
    • światowy
    • index.d.ts
  • package.json
  • tsconfig.json
  • typings.json

Następnie pliki.

Plik foo.ts

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

console.log('Weeee');

Plik lodash.d.tsjest kopiowany bezpośrednio z oryginalnego @types/lodashpakietu.

Plik index.d.ts

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

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

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

Plik typings.json

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

Jak widać, próbowałem importować tekst na wiele różnych sposobów:

  1. Poprzez bezpośrednie importowanie w foo.ts
  2. Przez typingsnieruchomość wpackage.json
  3. Używając typeRootsin tsconfig.jsonz plikiemtypings/index.d.ts
  4. Używając jawnego typeswtsconfig.json
  5. Dołączając typeskatalog dotsconfig.json
  6. Tworząc niestandardowy typings.jsonplik i uruchamiająctypings install

Jednak kiedy uruchamiam Typescript:

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

Co ja robię źle?

Jodiug
źródło

Odpowiedzi:

209

Niestety, te rzeczy nie są obecnie dobrze udokumentowane, ale nawet jeśli udało ci się to uruchomić, przejdźmy do twojej konfiguracji, abyś zrozumiał, co robi każda część i jaki ma to związek z tym, jak maszynopis przetwarza i ładuje pisma.

Najpierw przyjrzyjmy się otrzymywanemu błędowi:

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

Ten błąd w rzeczywistości nie pochodzi z importu lub referencji ani z próby użycia lodash w dowolnym miejscu w plikach ts. Raczej wynika to z niezrozumienia, jak używać typeRootsitypes właściwości , więc przejdźmy do nich bardziej szczegółowo.

Chodzi o to, że właściwości typeRoots:[]i NIEtypes:[] są uniwersalnymi sposobami ładowania dowolnej deklaracji (*.d.ts plików ).

Te dwie właściwości są bezpośrednio powiązane z nową funkcją TS 2.0, która umożliwia pakowanie i ładowanie deklaracji typowania z pakietów NPM .

Jest to bardzo ważne, aby zrozumieć, że działają one tylko z folderami w formacie NPM (tj. Folder zawierający plik package.json lub index.d.ts ).

Wartość domyślna typeRootsto:

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

Domyślnie oznacza to, że maszynopis trafi do node_modules/@typesfolderu i spróbuje załadować każdy podfolder, który tam znajdzie, jako pakiet npm .

Ważne jest, aby zrozumieć, że to się nie powiedzie, jeśli folder nie ma struktury podobnej do pakietu npm.

To właśnie dzieje się w twoim przypadku i jest źródłem twojego początkowego błędu.

Zmieniłeś typeRoot na:

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

Oznacza to, że maszynopis będzie teraz skanował ./typingsfolder w poszukiwaniu podfolderów i spróbuje załadować każdy znaleziony podfolder jako moduł npm.

Załóżmy więc, że właśnie typeRootsskonfigurowałeś ustawienia, na które chcesz wskazać, ./typingsale nie masz jeszcze żadnych types:[]ustawień właściwości. Prawdopodobnie zobaczysz te błędy:

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

Dzieje się tak, ponieważ tscskanuje twój ./typingsfolder i znajduje podfoldery customi global. Następnie próbuje zinterpretować je jako wpisywanie typu pakietu npm, ale nie ma tych folderów index.d.tslub package.jsonw tych folderach, więc pojawia się błąd.

Porozmawiajmy teraz trochę o types: ['lodash']ustawianej właściwości. Co to robi? Domyślnie maszynopis załaduje wszystkie podfoldery, które znajdzie w twoim typeRoots. Jeśli określisz types:właściwość, załadowane zostaną tylko te określone podfoldery.

W twoim przypadku mówisz mu, aby załadował ./typings/lodashfolder, ale nie istnieje. Dlatego otrzymujesz:

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

Podsumujmy więc, czego się nauczyliśmy. Wprowadzono Typescript 2.0 typeRootsi typesdo ładowania plików deklaracji spakowanych w paczkach npm . Jeśli masz niestandardowe typy lub pojedyncze luźne d.tspliki, które nie są zawarte w folderze zgodnie z konwencjami pakietu npm, te dwie nowe właściwości nie są tym, czego chcesz użyć. Typescript 2.0 nie zmienia tak naprawdę sposobu ich wykorzystania. Musisz tylko uwzględnić te pliki w kontekście kompilacji na jeden z wielu standardowych sposobów:

  1. Bezpośrednio dołączanie go do .tspliku: ///<reference path="../typings/custom/lodash.d.ts" />

  2. W tym ./typings/custom/lodash.d.tsw twojej files: []nieruchomości.

  3. Uwzględnianie ./typings/index.d.tsw Twojej files: []własności (która następnie rekurencyjnie obejmuje inne typy.

  4. Dodawanie ./typings/**doincludes:

Miejmy nadzieję, że na podstawie tej dyskusji będziesz w stanie powiedzieć, dlaczego zmiany, które oszalałeś w swoich tsconfig.jsonstworzeniach, znów zadziałają.

EDYTOWAĆ:

Jedną rzeczą, o której zapomniałem wspomnieć, jest to, że typeRootsi typeswłaściwości są naprawdę przydatne tylko do automatycznego ładowania deklaracji globalnych.

Na przykład jeśli ty

npm install @types/jquery

I używasz domyślnego tsconfig, wtedy ten pakiet typów jquery zostanie załadowany automatycznie i $będzie dostępny we wszystkich twoich skryptach bez konieczności wykonywania dalszych czynności ///<reference/>lubimport

typeRoots:[]Nieruchomość jest przeznaczona, aby dodać kolejne lokalizacje, z których typ pakiety zostaną załadowane frrom automatycznie.

Przez types:[]m.in. podstawowy use-case jest wyłączenie automatycznego ładowania zachowanie (poprzez ustawienie go na pustej tablicy), a następnie tylko wymieniając konkretne typy, które chcesz umieścić w skali globalnej.

Innym sposobem załadowania pakietów typów z różnych typeRootsjest użycie nowej ///<reference types="jquery" />dyrektywy. Zwróć uwagę na typeszamiast path. Ponownie jest to przydatne tylko w przypadku plików deklaracji globalnych, zazwyczaj takich, które tego nie robią import/export.

Oto jedna z rzeczy, które powodują zamieszanie z typeRoots. Pamiętaj, powiedziałem, że typeRootschodzi o globalne włączenie modułów. Ale @types/folderjest również zaangażowany w standardową rozdzielczość modułu (niezależnie od typeRootsustawień).

W szczególności, wyraźnie importowanie modułów zawsze omija wszystkie includes, excludes, files, typeRootsi typesopcje. Więc kiedy to zrobisz:

import {MyType} from 'my-module';

Wszystkie wyżej wymienione właściwości są całkowicie ignorowane. Odpowiednie właściwości podczas rozdzielczość modułubaseUrl, pathsi moduleResolution.

Zasadniczo, przy użyciu noderozdzielczość modułu, rozpocznie poszukiwania nazwy pliku my-module.ts, my-module.tsx, my-module.d.tszaczynając od katalogu wskazywanym przez baseUrlkonfiguracji.

Jeśli nie znajdzie pliku, będzie szukał folderu o nazwie, my-modulea następnie wyszuka właściwość package.jsonz typingswłaściwością, jeśli w środku jest właściwość package.jsonlub jej nie ma, wskazując, typingsktóry plik ma załadować, a następnie wyszuka index.ts/tsx/d.tsw tym folderze.

Jeśli to się nie powiedzie, wyszuka te same rzeczy w node_modulesfolderze, zaczynając od twojego baseUrl/node_modules.

Ponadto, jeśli ich nie znajdzie, wyszuka baseUrl/node_modules/@typeste same rzeczy.

Jeśli nadal nie znajdzie niczego, zacznie przechodzić do katalogu nadrzędnego i szukać node_modulesi node_modules/@typestam. Będzie przechodzić w górę katalogów, aż dotrze do katalogu głównego systemu plików (nawet pobierze moduły węzłów poza projektem).

Chcę tylko podkreślić, że rozdzielczość modułu całkowicie ignoruje wszelkie typeRootsustawienia. Więc jeśli skonfigurowałeś typeRoots: ["./my-types"], to nie będzie przeszukiwane podczas jawnego rozwiązywania modułu. Służy tylko jako folder, w którym możesz umieścić globalne pliki definicji, które chcesz udostępnić całej aplikacji bez konieczności dalszego importowania lub odniesienia.

Na koniec możesz nadpisać zachowanie modułu za pomocą mapowania ścieżek (tj. pathsWłaściwości). Na przykład wspomniałem, że typeRootsprzy próbie rozwiązania modułu nie jest konsultowany żaden zwyczaj . Ale jeśli ci się podobało, możesz to zrobić w następujący sposób:

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

W przypadku wszystkich importów, które pasują do lewej strony, spróbuj zmodyfikować import tak, jak po prawej stronie, zanim spróbujesz go uwzględnić ( *po prawej stronie oznacza początkowy ciąg importu. Na przykład, jeśli importujesz:

import {MyType} from 'my-types';

Najpierw spróbuje importu, tak jakbyś napisał:

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

A potem, jeśli nie znajdzie, spróbuje ponownie bez przedrostka (druga pozycja w tablicy to tylko *to, co oznacza początkowy import.

W ten sposób możesz dodać dodatkowe foldery, aby wyszukać niestandardowe pliki deklaracji, a nawet niestandardowe .tsmoduły, które chcesz mieć import.

Możesz również tworzyć niestandardowe mapowania dla określonych modułów:

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

To by ci pozwoliło

import {MyType} from 'my-types';

Ale potem przeczytaj te typy z some/custom/folder/location/my-awesome-types-file.d.ts

dtabuenc
źródło
1
Dziękuję za szczegółową odpowiedź. Okazuje się, że rozwiązania działają w izolacji, ale nie są dobrze dopasowane. To wyjaśnia sprawy, więc dam ci nagrodę. Gdybyś znalazł czas na udzielenie odpowiedzi na kilka dodatkowych punktów, mogłoby to być bardzo pomocne dla innych osób. (1) Czy istnieje powód, dla którego typeRoots akceptuje tylko określoną strukturę folderów? Wydaje się arbitralne. (2) Jeśli zmienię typeRoots, maszynopis nadal zawiera foldery @types w node_modules, w przeciwieństwie do specyfikacji. (3) Co to jest pathsi czym się różni od includepisania na maszynie?
Jodiug
1
Zmieniłem odpowiedź, daj mi znać, jeśli masz więcej pytań.
dtabuenc
1
Dziękuję, przeczytałem resztę i wow. Podziękowania dla ciebie, że znalazłeś to wszystko. Wygląda na to, że działa tu wiele niepotrzebnie złożonych zachowań. Mam nadzieję, że w przyszłości Typescript sprawi, że ich właściwości konfiguracyjne będą bardziej samodokumentujące.
Jodiug
1
Nie powinno być link do tej odpowiedzi od typescriptlang.org/docs/handbook/tsconfig-json.html
hgoebl
2
Czy nie powinien wyglądać ostatni przykład "paths" :{ "my-types": ["some/custom/folder/location/my-awesome-types-file"] }?
Koen.
6

Edycja: nieaktualne. Przeczytaj odpowiedź powyżej.

Nadal tego nie rozumiem, ale znalazłem rozwiązanie. Użyj następujących 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"
  ]
}

Usuń typings.jsoni wszystko w folderze typingsoprócz lodash.d.ts. Usuń także wszystkie ///...odniesienia

Jodiug
źródło
3

"*": ["./types/*"] Ta linia w ścieżkach tsconfig naprawiła wszystko po 2 godzinach walki.

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

typy to nazwa folderu, która znajduje się obok modułu_węzła, tj. na poziomie folderu klienta (lub folderu src ) types/third-party-lib/index.d.ts
index.d.ts madeclare module 'third-party-lib';

Uwaga: powyższa konfiguracja jest niekompletną konfiguracją, aby pokazać, jak wygląda z typami, ścieżkami, włączaniem i wykluczaniem.

Uday Sravan K.
źródło
1

Wiem, że to stare pytanie, ale narzędzia maszynopisu ciągle się zmieniają. Myślę, że najlepszą opcją w tym momencie jest po prostu poleganie na ustawieniach ścieżki „include” w tsconfig.json.

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

Domyślnie, o ile nie wprowadzisz określonych zmian, wszystkie pliki * .ts i wszystkie * .d.ts w ramach src/zostaną automatycznie uwzględnione. Myślę, że jest to najłatwiejszy / najlepszy sposób na dołączenie plików deklaracji typu niestandardowego bez dostosowywania typeRootsi types.

Odniesienie:

realharry
źródło