まくろぐ

TypeScriptの型: 既存の JavaScript ライブラリに型情報を追加する(.d.ts ファイル)

更新:
作成:

アンビエント宣言とは

TypeScript の アンビエント宣言 (Ambient Declarations) を行うと、既存の JavaScript ライブラリに型情報を付加することができます。 この仕組みを利用すると、

  • サードパーティ製の JavaScript ライブラリ(npm パッケージ)や、自作の JavaScript ライブラリ(ただし TypeScript 化はしたくないもの)を TypeScript コードから使用する
  • jQuery などのブラウザ上でロードされるライブラリを TypeScript コードから使用する

といったことが可能になります。 さらに、VisualStudio Code などのエディタ上で型情報の補完ができるようになります。

アンビエント宣言 (declare)

どこか別の場所でロードされる予定の JavaScript モジュールに対して、自力で型情報を付けたい場合は declare キーワードを使用します。 例えば、jQuery はもともと JavaScript 用のライブラリなので、TypeScript 用の型情報は提供していませんが、次のように自力で型情報を付けることで、TypeScript コードから利用できるようになります(実際には、DefinitelyTyped プロジェクトが提供する @types/jquery を使用するのが簡単です)。

index.ts
// $ という変数を参照できるようにする(実体は実行時に後付けで定義される予定)
declare var $: any;

// これで、$ という未定義の変数を参照してもエラーにならない
$('#id').html('Hello!');

この仕組みを使わずに、いきなり $ を参照してしまうと、そのような変数は定義されていないというエラーになってしまいます。 TypeScript トランスパイラは、declare によって付けられた型情報を正しいものと判断するため、この型情報は間違えないように指定する必要があります。 また、実行時にはそのオブジェクトの実体がどこかで生成されていなければいけません。

もっと明確な型付けを行うなら次のような感じで、クラスや関数などの型を定義していきます。

index.ts
declare class jQuery {
  html(html: string): void;
}
declare function $(query: string): jQuery;

$('#id').html('Hello!');

型宣言ファイル (.d.ts)

プロジェクト全体でアンビエント宣言を共有したい場合は、次のような型宣言ファイル .d.ts をソースツリーのルートに配置します。

globals.d.ts
declare var $: any;

.d.ts という拡張子は、オブジェクトの実体ではなく、型情報のみが含まれていることを示しています。 このファイルは TypeScript ビルド時のみに参照されるものであり、.js ファイルの生成は行われません。

アンビエントモジュール (declare module)

declare キーワードは、単一の変数や関数、クラスに型情報を与えるものですが、declare module 'モジュール名' のように使用すると、モジュール単位で型情報を付加することができます。 この仕組みを アンビエントモジュール (Ambient Modules) と呼びます。 これで型情報を定義しておくと、指定したモジュールをインポートしたときに、自動的に型情報が付加されるようになります。

次の例では、foo モジュール用の型情報を定義しています。

globals.d.ts
// foo モジュール用の型宣言
declare module 'foo' {
  export val bar: number;
}

次のように foo モジュールをインポートすると、bar 変数が export されているモジュールとして扱うことができます。

型情報のインポート
import * as foo from 'foo';
console.log(foo.bar);

典型的なのは、CSS Modules を使用できるようにするための次のような定義です。 TSDoc/JSDoc 形式でドキュメンテーションコメントを記述しておけば、VS Code などのエディタ上でドキュメント表示してくれるようになります。

globals.d.ts
declare module '*.css' {
  /** CSS クラスを参照するためのオブジェクトです。 */
  const styles: { [className: string]: string };
  export default styles;
}

対象モジュールを any としてだけ扱えればよいのであれば、次のように型定義部分を省略可能です。

globals.d.ts
declare module '*.css';

これで、次のように .css ファイルをインポートしたときに、TypeScript の型エラーが発生しなくなります。

import * as styles from './App.css';

存在する JavaScript ファイルに型宣言ファイルを提供する

プロジェクト内にすで存在する JavaScript モジュール(.js ファイル)に対して、後付けで型宣言ファイル (.d.ts) を提供したい場合があります。 例えば、社内の別の人が作成した JavaScript ライブラリを、TypeScript コードから参照したい場合などです。 そのような場合は、対象の .js ファイルと同じ階層に、同じモジュール名で モジュール名.d.ts というファイルを作成します。 例えば、次のような JavaScript ライブラリがあったとします。

lib/util.js
exports.greet = function(name) {
  console.log(`Hello, ${name}`);
}

この lib/util.js は、greet 関数を公開していますが、パラメータと戻り値の型が定義されておらず、そのままでは TypeScript コードから使用できません(noImplictAny 設定時)。 この lib/util.js ファイルの内容を変更せずに、型情報付きの TypeScript モジュールとして利用したいときは、同じディレクトリに lib/util.d.ts ファイルを作成 します。

lib/util.d.ts
export function greet(name: string): void;

すると、JavaScript で作成されたライブラリを、型情報付きのモジュール lib/util として参照できるようになります。 VisualStudio Code などで次のように型情報の補完が効くようになります。

/p/s7wk5k3/img-001.png
図: main.ts

TypeScript のビルド時には、import で指定されたモジュール名に対して .ts.tsx.d.ts という順でファイル検索が行われます。 そのため、ここでは lib/util.d.ts という型宣言ファイルが読み込まれることになります。 一方、実行時に使用されるのは lib/util.js というライブラリ本体側のファイルであることに注意してください。 .js ファイルと .d.ts ファイルを同じディレクトリに格納して、インポート時のパスが一致するようにしているのは、このあたりに理由があります。 .d.ts ファイルが一種の緩衝材のようになり、TypeScript と JavaScript の世界をうまくつないでくれています。

既存ライブラリの例

各 NPM パッケージに埋め込まれた型宣言ファイルを使う

NPM パッケージとして公開されている JavaScript ライブラリには、自分自身の NPM パッケージ内に TypeScript 用の型宣言ファイル (.d.ts) を含んでいるものもあります。 型宣言ファイルのパスは、次のように package.json ファイルの types プロパティで指定されています。

package.json
{
  "name": "awesome",
  "author": "Vandelay Industries",
  "version": "1.0.0",
  "main": "./lib/main.js",
  "types": "./lib/main.d.ts"
}

このような NPM パッケージは、そのまま TypeScript ライブラリとしてインポートできます。

DefinitelyTyped で公開される @types/xxx を使う

jQuery ライブラリなどの有名な JavaScript ライブラリの型宣言ファイルは、DefinitelyTyped プロジェクトでまとめて公開されています。 パッケージ名は、 @types/XXX という名前になっており、次のようにインストールすることができます。

jQuery 用の TypeScript 型宣言をインストール
$ npm install --save-dev @types/jquery

これでインストールされるのは型宣言のみなので、jQuery ライブラリ本体は何らかの方法で参照できるようにしておく必要があります。 最終的に生成された JavaScript を Web ブラウザから実行するのであれば、HTML ファイルの script タグなどで jQuery 本体をロードすることになるでしょう。 ここでは、テストのため、npm コマンドでインストールしてしまうことにします。

jQuery 本体をインストール(JavaScript 用の NPM パッケージ)
$ npm install --save jquery
☝️ ワンポイント @types/jquery はビルド時のみ必要なので --save-dev フラグでインストールしていますが、jquery モジュールは実行時に必要になるので --save フラグでインストールしていることに注意してください。

このようにインストールした型宣言ファイルは node_modules/@types 以下に格納されており、TypeScript はデフォルトでこのディレクトリ以下のモジュールをインポートするパスを通しています(tsconfig.jsoncompilerOptions.typeRoots プロパティでカスタマイズ可能)。 つまり、@types/ というプレフィックスを付けずに、次のようにインポートすることができます

main.ts
import * as $ from 'jquery';
const footerElem = $('#footer');

このように、あたかも最初から TypeScript 用のライブラリとして提供されているかのように JavaScript ライブラリを参照できるようになります(実際には .d.ts の型情報だけ見てトランスパイルしてるだけですが)。

ただこれだと、生成される JavaScript ファイル内に import 文が残ってしまうので、jQuery の場合は、次のようにコメント形式で .d.ts の定義を参照する方法がよいかもしれません。

/// <reference path="../node_modules/@types/jquery/index.d.ts" />
const footerElem = $('#footer');

その他

String ではなく string を使う

型宣言ファイル (.d.ts) の中では、大文字で始まる String ではなく、TypeScript プリミティブな string 型を使い、効率的なコードを生成するようにします。 NumberBooleanSymbolObject なども同様に、すべて小文字の型の方を使います。

.ts ファイルから .d.ts ファイルを生成する

TypeScript ファイル (.ts) をトランスパイルするときに、.js ファイルに加えて .d.ts ファイルを生成したいときは、ビルドオプションで --declaration を指定します。 TypeScript で作成したライブラリを NPM パッケージとして公開するときは、このように作成した .js ファイルと .d.ts ファイルをパッケージングします(逆に .ts ファイルはパッケージに入れてはいけません)。

関連記事

まくろぐ
サイトマップまくへのメッセージ