まくろぐ

TypeScriptの型: アンビエント宣言で既存の JavaScript ライブラリを TypeScript から使えるようにする

更新:
作成:

アンビエント宣言とは

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

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

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

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

既存の JavaScript コードに対して型情報を付加するには、 型宣言ファイル (.d.ts) を作成します。 例えば、次のような JavaScript ライブラリがあったとします。

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

この lib/util.js は、greet(name) という関数を公開していますが、そのパラメータ name には型情報が付いていませんし、戻り値の型も明示されていません。 そこで、型宣言ファイル 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 コードのビルド時に参照されるファイルは 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');

その他メモ

declare 宣言

どこか別の場所でロードされる予定の JavaScript モジュールに対して、自力で型情報を付けたい場合は declare キーワードを使用します。 前述の jQuery の型付けの例では、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!');

アンビエントモジュール (Ambient Module)

下記のような形式で型定義のブロックに module 名を付けておくと、ファイルの配置場所にかかわらず、その module 名を使って型情報をインポートできるようになります。

Ambient module の定義
declare module "module名" {
    // ...ここで型定義...
}
型情報のインポート
import { hoge } from "module名";

String ではなく string を使う

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

関連記事

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