ライブラリ構造

概要

大雑把に言えば宣言ファイルを構造化する方法は、ライブラリの使用方法によって異なります。 JavaScriptで使用するライブラリを提供する方法はたくさんありますが、それに合わせて宣言ファイルを書く必要があります。

このガイドでは、共通ライブラリパターンを識別する方法と、そのパターンに対応する宣言ファイルを記述する方法について説明します。

主要なライブラリー構造パターンの各タイプには、テンプレートセクションに対応するファイルもあります。 これらのテンプレートから使い始めることで、より早く理解を深めるのに役立てることができます。

ライブラリの種類の識別

まず、TypeScript宣言ファイルが表すことができるライブラリの種類を確認します。 各種類のライブラリがどのように使用され、どのように書かれているか、実際のライブラリの例を挙げて簡単に説明します。

ライブラリの構造を特定することは、その宣言ファイルを書くための第一歩となります。 その使用のされ方とコードに基づいて、構造を特定するためのヒントを示します。

ライブラリのドキュメントや構造によっては、他のものよりも簡単になることもあるでしょう。 あなたが、やりやすいと感じる方法をお勧めします。

グローバル・ライブラリ

グローバルライブラリは、グローバルスコープからアクセス可能なものの1つです。(つまり、importの形式を使用しない) 多くの場合は、単純に1つまたは複数のグローバル変数を提供します。 例えば、jQueryを使っているのであれば、$変数を単純に次のように参照して使用することができます。

$(() => { console.log('hello!'); } );

通常は、HTMLのscriptタグの中でライブラリを使用する方法を、グローバル・ライブラリのドキュメントの案内で確認していることでしょう。

<script src="http://a.great.cdn.for/someLib.js"></script>

現在、グローバルにアクセス可能な人気の高いライブラリの多くは、UMDライブラリとして書かれています(下記参照)。 UMDライブラリのドキュメントは、グローバル・ライブラリのドキュメントと区別するのが難しくなっています。 グローバルの宣言ファイルを書く前に、ライブラリがUMDでないことを確認してください。

コードからグローバル・ライブラリを識別

グローバル・ライブラリのコードは、通常は非常に単純です。 グローバルな"Hello, world"ライブラリが次のようなもの、

function createGreeting(s) {
    return "Hello, " + s;
}

もしくは次のようなものだとします。

window.createGreeting = function(s) {
    return "Hello, " + s;
}

グローバル・ライブラリのコードには、一般的に次のようなこと傾向があります。

  • トップ階層にvar、または関数宣言がある。
  • 1つ以上のwindow.someNameへの割り当てがある。
  • documentwindowのような、プリミティブなDOMの存在が想定されている。

反対に次のような傾向がありません。

  • requiredefineのような、モジュールローダーの使用や確認をしている。
  • var fs = require("fs");形式の、CommonJS / Node.jsスタイルのインポートを行っている。
  • define(...)の呼び出し。
  • ドキュメントに、ライブラリのrequireまたはインポートの方法が説明されている。
グローバルライブラリの例

一般的にグローバルライブラリをUMDライブラリに変換するのは簡単であるため、 人気ライブラリの中で未だにグローバルスタイルのままのものは、ほとんどありません。

ただし、ライブラリが小さく、DOMを必要とする(または依存関係を持たない)ライブラリは、依然としてグローバルである可能性があります。

グローバル・ライブラリのテンプレート

テンプレートファイルglobal.d.tsは、 サンプルライブラリmyLibを定義します。 「名前の衝突の防止」の補足を必ずお読みください。

モジュールライブラリ

ライブラリには、モジュールローダーの環境下でしか動かないものがあります。 例えば、expressはNode.jsでしか動作しないため、 CommonJSのrequire関数を使用して読み込まなければいけません。

ECMAScript 2015(ES2015、ECMAScript、ES6としても知られている)、CommonJS、RequireJSは、 モジュールのインポートに対して同じような概念を持ちます。 JavaScriptのCommonJS(Node.js)であれば、例えば次のように書きます。

var fs = require("fs");

TypeScriptまたはES6であれば、importキーワードが同じ目的で提供されます。

import fs = require("fs");

通常、モジュールライブラリのドキュメントには、次のいずれかの行が含まれています。

var someLib = require('someLib');

または、

define(..., ['someLib'], function(someLib) {

});

グローバル・モジュールと同様に、これらの例はUMDモジュールのドキュメントに記載されているはずなので、 コードやドキュメントを確認してみてください。

コードからモジュール・ライブラリを識別

モジュール・ライブラリは一般的に、少なくとも次のような特徴があります。

  • 無条件でのrequireまたはdefineの呼び出しがある。
  • import * as a from 'b';export c;のような宣言がある。
  • exportsまたはmodule.exportsへの割り当てがある。

稀に下記のことを行います。

  • windowまたはglobalのプロパティへの割り当て。

UMD

UMDモジュールは、モジュール(importを通して)、またはグローバル(モジュール・ローダー無しの実行環境)のいずれかで使用することができます。 Moment.jsのような、 多くの人気ライブラリはこの方式で書かれています。 例えば、Node.jsまたはRequireJSを使用する場合は次のように書くことができます。

import moment = require("moment");
console.log(moment.format());

一方、素(vanilla)のブラウザ環境では次のように書きます。

console.log(moment.format());
UMDライブラリの識別

UMDモジュールは、 モジュールローダー環境の存在を調べます。 これは、次のように見つけるのが簡単なパターンです。

(function (root, factory) {
    if (typeof define === "function" && define.amd) {
        define(["libName"], factory);
    } else if (typeof module === "object" && module.exports) {
        module.exports = factory(require("libName"));
    } else {
        root.returnExports = factory(root.libName);
    }
}(this, function (b) {

ライブラリのコード、特にファイルの先頭に、typeof definetypeof windowtypeof moduleの検査がある場合は、 ほぼ間違いなくUMDライブラリでしょう。

また、UMDライブラリのドキュメントではrequireによる「Node.jsで使用する」例と、 スクリプトを読み込むために<script>タグを使った「ブラウザで使用する」例を説明しているはずです。

UMDライブラリの例

多くの人気ライブラリが、現在UMDパッケージとして利用可能になっています。 これには、 jQueryMoment.jsLodashなど、多くのものが含まれます。

テンプレート

モジュール用に下記の3つのテンプレートが利用可能です。

モジュールを関数のように呼び出すことが出来るのであれば、module-function.d.tsを使用してください。

var x = require("foo");
// Note: calling 'x' as a function
var y = x(42);

必ず、補足の"モジュール呼び出しシグネチャへのES6の影響"に目を通しておいてください。

newを使用したコンストラクタ呼び出しをするモジュールであれば、module-class.d.tsを使用してください。

var x = require("bar");
// Note: using 'new' operator on the imported variable
var y = new x("hello");

これについても、同様に補足に目を通しておいてください。

もし、モジュールが関数的な呼び出しもコンスタクタ呼び出しも出来ないのであれば、 module.d.tsを使用してください。

モジュールプラグイン / UMDプラグイン

モジュールプラグインは、別のモジュール(UMDまたはモジュール)の形状(shape)を変更します。 例えば、Moment.jsではmoment-rangemomentオブジェクトに新しいrangeメソッドを追加します。

宣言ファイルを書く目的であれば、変更されるモジュールがプレーン・モジュールであろうとUMDモジュールであろうと、同じコードを書くことになるでしょう。

テンプレート

module-plugin.d.tsテンプレートを使用して下さい。

グローバルプラグイン

グローバルプラグインは、あるグローバルの形状(shape)を変更するグローバルなコードです。 グローバル変更モジュールと同様に、これらは実行時に衝突する可能性を高めます。

例えば、幾つかのライブラリが新しい関数をArray.prototypeString.prototypeに追加することが挙げられます。

グローバルプラグインの識別

グローバルプラグインを識別するのは、それらのドキュメントを見れば一目瞭然です。

次のような例を確認することができるはずです。

var x = "hello, world";
// 組み込みの方に新しいメソッドを作成
console.log(x.startsWithHello());

var y = [1, 2, 3];
// 組み込みの方に新しいメソッドを作成
console.log(y.reverseAndSort());
テンプレート

global-plugin.d.tsを使用してください。

グローバル変更モジュール

グローバル変更モジュールは、インポート時にグローバルスコープ内の既存の値を変更します。 例えば、インポート時にString.prototypeに新しいメンバを追加するライブラリが存在するかもしれません。

このパターンは、実行時に衝突の可能性があるためやや危険ではありますが、それでも宣言ファイルを書くことはできます。

グローバル変更モジュールの識別

グローバル変更モジュールは、それらのドキュメントから比較的簡単に識別することができます。 通常、それらはグローバルプラグインに似ていますが、機能を有効にするにはrequireの呼び出しが必要になります。

ドキュメントに、次のようなコードを確認することができるでしょう。

// 'require'の呼び出しでは、その戻り値は使用しません。
var unused = require("magic-string-time");
/* or */
require("magic-string-time");

var x = "hello, world";
// 組み込みの型に新しいメソッドを追加します。
console.log(x.startsWithHello());

var y = [1, 2, 3];
// 組み込みの型に新しいメソッドを追加します。
console.log(y.reverseAndSort());
テンプレート

global-modifying-module.d.tsテンプレートを使用してください。

依存について

依存関係にはいくつかの種類があります。

グローバルライブラリへの依存

あなたのライブラリがグローバルライブラリに依存する場合、/// <reference types="..." />を使用してください。

/// <reference types="someLib" />

function getThing(): someLib.thing;

モジュールへの依存

あなたのライブラリがモジュールに依存する場合、import文を使用してください。

import * as moment from "moment";

function getThing(): moment;

UMDライブラリへの依存

グローバルライブラリからの場合

あなたのグローバルライブラリがUMDモジュールへ依存する場合、/// <reference types="..." />を使用してください。

モジュールまたはUMDライブラリからの場合

あなたのモジュールまたはUMDライブラリがUMDライブラリに依存している場合、import文を使用してください。

import * as someLib from 'someLib';

UMDライブラリへの依存を宣言するのに、/// <referenceは使用しないでください。

補足

名前の衝突の防止

グローバル宣言ファイルを書くと、グローバルスコープ内に多くの型が定義される可能性があることに注意してください。 多くの宣言ファイルがプロジェクトに含まれている際に、解決することが出来ない名前の衝突が発生しかねないため、 なるべくこれを避けることを強くお勧めします。

これをフォローするシンプルな規則は、ライブラリが定義しているグローバル変数に基づいて、宣言する型に名前空間を適用することです。 例えば、ライブラリがグローバルな値'cats'を定義する場合は、次のように書いてください。

declare namespace cats {
    interface KittySettings { }
}

次のようには書かないでください。

// at top-level
interface CatsKittySettings { }

また、これに従うことで宣言ファイルを壊さずにライブラリをUMDに移行できることが保証されます。

モジュールプラグインのES6での影響

プラグインの中には、既存のモジュールによってトップ階層でエクスポートされているものに対して追加または変更するものがあります。 これはCommonJSや他のローダーでは認められていますが、ES6モジュールでは不変であるとみなされるため、これを適用するのは不可能です。

TypeScriptはローダーに依存しないため、このポリシーがコンパイル時に強制されることはありませんが、 ES6モジュールローダーに移行しようと検討している開発者は、これに注意する必要があります。

モジュール呼び出しシグネチャでのES6の影響

Expressなどの人気ライブラリの多くは、インポート時に呼び出し可能な関数として公開されます。 例えば、一般的にExpressは次のように使用されます。

import exp = require("express");
var app = exp();

ES6モジュールローダーでは、最上位層のオブジェクト(ここではexpとしてインポート)はプロパティしか持つことができず、 最上位層のモジュールオブジェクトが呼び出し可能になることはありません。

ここでの最も一般的な解決策は、呼び出し可能/コンストラクタの呼び出し可能なオブジェクトのために、 defaultのエクスポートを定義することです。 一部のモジュールローダーのshimはこの状況を自動的に検出し、最上位オブジェクトをdefaultエクスポートに置き換えます。

 Back to top

© https://github.com/Microsoft/TypeScript-Handbook

このページは、ページトップのリンク先のTypeScript-Handbook内のページを翻訳した内容を基に構成されています。 下記の項目を確認し、必要に応じて公式のドキュメントをご確認ください。 もし、誤訳などの間違いを見つけましたら、 @tomofまで教えていただければ幸いです。

  • ドキュメントの情報が古い可能性があります。
  • "訳注:"などの断わりを入れた上で、日本人向けの情報やより分かり易くするための追記を行っている事があります。