こんにちは、CX 事業本部製造ビジネステクノロジー部の若槻です。
今回は、TypeScript の型定義ファイル(d.ts)周りの仕様を検証をしながら確認する機会があったのでご紹介します。
検証
Node.js & TypeScript のプロジェクト作成
検証環境として Node.js & TypeScript のプロジェクトを新規作成します。作成手順は下記を参考にします。
環境作成用のコマンドを実行します。
# package.json を作成
npm init -y
# TypeScript をインストール
npm install typescript --save-dev
# TSConfig を作成
npx tsc --init --rootDir src --outDir lib --esModuleInterop --resolveJsonModule --lib es6,dom --module commonjs
@types/node の導入
ここで src/sample.ts
ファイルを作成します。
mkdir src
touch src/sample.ts
ファイル内容は下記の通りです。Node.js の os
モジュールをインポートして使用しています。
src/sample.ts
import os from "os"
console.log("Platform: " + os.platform());
ここで tsc(TypeScript コンパイラ)を実行します。--noEmit
オプションを付けることでコンパイル結果の出力を抑制しています。
$ tsc --noEmit
src/sample.ts:1:16 - error TS2307: Cannot find module 'os' or its corresponding type declarations.
1 import os from "os"
~~~~
Found 1 error in src/sample.ts:1
実行結果は、os
に対応する型宣言(corresponding type declarations)が見つからないというエラーになりました。
ここで Node.js のプログラムに必要な型定義ファイルである @types/node
をインストールします。
npm install @types/node --save-dev
これにより node_modules/@types/node
ディレクトリ配下に型定義ファイルが作成されます。以下はその一部ですが、Node.js で使用できるモジュールの型宣言が含まれています。
$ tree node_modules/@types -L 2
node_modules/@types
└── node
├── LICENSE
├── README.md
├── assert
├── assert.d.ts
├── async_hooks.d.ts
├── buffer.d.ts
├── child_process.d.ts
├── cluster.d.ts
├── console.d.ts
├── constants.d.ts
├── crypto.d.ts
├── dgram.d.ts
├── diagnostics_channel.d.ts
├── dns
├── dns.d.ts
├── dom-events.d.ts
├── domain.d.ts
├── events.d.ts
├── fs
├── fs.d.ts
├── globals.d.ts
├── globals.global.d.ts
├── http.d.ts
├── http2.d.ts
├── https.d.ts
├── index.d.ts
├── inspector.d.ts
├── module.d.ts
├── net.d.ts
├── os.d.ts
├── package.json
├── path.d.ts
├── perf_hooks.d.ts
├── process.d.ts
├── punycode.d.ts
├── querystring.d.ts
├── readline
├── readline.d.ts
├── repl.d.ts
├── stream
├── stream.d.ts
├── string_decoder.d.ts
├── test.d.ts
├── timers
├── timers.d.ts
├── tls.d.ts
├── trace_events.d.ts
├── ts4.8
├── tty.d.ts
├── url.d.ts
├── util.d.ts
├── v8.d.ts
├── vm.d.ts
├── wasi.d.ts
├── worker_threads.d.ts
└── zlib.d.ts
すると tsc が正常にパスするようになりました。
tsc --noEmit
ここで、npm の node パッケージの配信ページを確認すると、パッケージ名の右横に「DT」アイコンが表示されています。これは型定義ファイルがこのパッケージ自身には含まれていないが、DefinitelyTyped に登録されていることを示しています。
上記の「DT」アイコンのリンクから、下記のように型定義ファイルの配信ページに遷移できます。node
の場合はこの @types/node を別途インストールする必要がある、ということが分かります。
また DefinitelyTyped は GitHub リポジトリで管理されています。
下記のように多くのパッケージの型定義ファイルが管理されています。
コンパイル実行
次に TypeScript のコンパイル実行について確認します。
下記の ts ファイルを作成します。
src/sample.ts
interface Person {
firstName: string;
lastName: string;
}
function greeter(person: Person): string {
return "Hello, " + person.firstName + " " + person.lastName;
}
tsc
(TypeScript コンパイル)コマンドを実行します。
tsc
すると lib
ディレクトリに Node.js の実行可能ファイル (js
) が作成されます。
lib/sample.js
"use strict";
function greeter(person) {
return "Hello, " + person.firstName + " " + person.lastName;
}
また同じコマンドを -d
オプション付きで実行します。
tsc -d
すると、lib
ディレクトリに型定義ファイル(d.ts)が作成されます。
lib/sample.d.ts
interface Person {
firstName: string;
lastName: string;
}
declare function greeter(person: Person): string;
src/sample.ts
ファイルでは、TypeScript による上記の型宣言が含まれていることが分かります。
型定義ファイルの自作
さてここまでは型定義ファイルを npm からインストールまたは tsc による自動生成によって作成してきましたが、最後に自作をしてみたいと思います。
まずは src/sample.ts
ファイルを下記のように修正し、Person
の型宣言を削除します。
src/sample.ts
// interface Person {
// firstName: string;
// lastName: string;
// }
function greeter(person: Person): string {
return "Hello, " + person.firstName + " " + person.lastName;
}
tsc を実行すると、Person
に対応する型定義ファイルが見つからないというエラーになります。
$ tsc --noEmit
src/sample.ts:6:26 - error TS2304: Cannot find name 'Person'.
6 function greeter(person: Person): string {
~~~~~~
Found 1 error in src/sample.ts:6
そこで、先程作成した d.ts ファイルをコピーして src/types/sample.d.ts
ファイルを作成します。
mkdir src/types
cp lib/sample.d.ts src/types/sample.d.ts
再度 tsc を実行すると今度はパスしました。types
ディレクトリ配下の型定義ファイルが参照されていることが分かります。
tsc --noEmit
さらに src/sample.ts
ファイルを下記のように修正し、関数宣言 greeter
を削除した上で greeter
関数を呼び出すようにします。
src/sample.ts
// interface Person {
// firstName: string;
// lastName: string;
// }
// function greeter(person: Person): string {
// return "Hello, " + person.firstName + " " + person.lastName;
// }
greeter({firstName: 'Jane', lastName: 'User'})
tsc を実行するとこちらもパスしました。greeter
関数の実装は削除されていますが、types
ディレクトリ配下に型宣言があることでコンパイルが通りました。
tsc --noEmit
同様の仕様を利用することにより Jest や Vitest などのテストフレームワークで見られるようなグローバル関数(import 不要の関数)は実現されています。
おわりに
TypeScript の型定義ファイル(d.ts)周りの仕様を検証をしながら確認する機会があったのでご紹介しました。
今まで曖昧にしか理解できていなかった TypeScript の型宣言の仕組みが少し理解できました。やはり公式(もしくは信頼の置ける)ドキュメントを読みながら手を動かすことが理解への一番の近道であることを改めて痛感しました。
参考
以上