ファイルモジュールの詳細

TypeScriptのモジュール機能は非常に強力であり、便利な機能です。ここでは、その強力さと実際のユースケースを説明します。

説明:commonjs、amd、esモジュール、その他

最初に、このあたりのモジュールシステムの分かりづらい矛盾を明確にする必要があります。私は単に推奨を伝え、このノイズを取り除きたいと思います。その他の動くかもしれない方法をすべて説明することはしません。

同じTypeScriptのコードからmoduleオプションに応じて異なるJavaScriptを生成されます。無視できるものは次のとおりです(既に過去のものとなった技術については詳しく説明しません):

  • AMD:使用しないでください。ブラウザでしか使えませんでした。

  • SystemJS:良い実験でしたが、ESモジュールによって置き換えられました。

  • ESモジュール:モダンなブラウザでのみ利用できます。

これはJavaScriptを生成するためのオプションです。これらのオプションの代わりに、module:commonjsを使うことをおすすめします。

どのようにTypeScriptモジュールを書くかについては、まだ意見が完全には、まとまっていません。将来問題が起きることを避けるには下記のようにしてください:

  • import foo = require('foo')、つまり、import/requireを避けて、ESモジュールの構文を使用してください。

それではESモジュールの構文を見てみましょう。

概要: module:commonjsを使い、ESモジュールの構文を使ってモジュールをimport/export/作成します。

ESモジュールの構文

  • 変数(または型)のエクスポートは、キーワードexportを前に置くだけです。簡単です。

// `foo.ts`ファイル
export let someVar = 123;
export type SomeType = {
foo: string;
};
  • 変数または型を専用のexport文でエクスポートする

// `foo.ts`ファイル
let someVar = 123;
type SomeType = {
foo: string;
};
export {
someVar,
SomeType
};
  • 名前を変更して専用のexport文で変数や型をエクスポートする

// `foo.ts`ファイル
let someVar = 123;
export { someVar as aDifferentName };
  • importを使用して変数または型をインポートする

// `foo.ts`ファイル
import { someVar, SomeType } from './foo';
  • 名前を変更してimportを使って変数や型をインポートする

// `bar.ts`ファイル
import { someVar as aDifferentName } from './foo';
  • import * asを使って1つの名前にモジュールすべてをインポートする

    // `bar.ts`ファイル
    import * as foo from './foo';
    // `foo.someVar`や`foo.SomeType`など`foo.ts`がエクスポートしているものを何でも利用できます
  • 副作用のためだけに1つのファイルをインポートする:

import 'core-js'; // 共通のpolyfillライブラリ
  • 別のモジュールからすべてのものを再エクスポートする

export * from './foo';
  • 別のモジュールから一部のものを再エクスポートする

export { someVar } from './foo';
  • 名前を変更して別のモジュールから一部のものだけを再エクスポートする

export { someVar as aDifferentName } from './foo';

デフォルトエクスポート/インポート

あなたが後で知るように、私はデフォルトエクスポートが好きではありません。しかし、知っておく必要があります。そのため、ここで、デフォルトエクスポートの使い方を説明します。

  • export defaultを使ってエクスポートする

    • 変数の前に(let / const / varは必要ありません)

    • 関数の前

    • クラスの前

// 何らかの変数
export default someVar = 123;
// または関数
export default function someFunction() { }
// またはクラス
export default class SomeClass { }
  • import someName from "someModule"という構文を使用してインポートする(インポートしたものには任意の名前を付けることができます):

import someLocalNameForThisFile from "../foo";

モジュールのパス解決

私はmoduleResolution: commonjsを指定することを前提にしています。これはあなたのTypeScript設定にも追加しておくべきオプションです。この設定はmodule:commonjsを設定していれば、暗黙的に設定されます。

2つの異なる種類のモジュールがあります。それらは、import文のパス部分によって区別されます(たとえば、import foo from 'パス')。

  • 相対パスのモジュール(.で始まるパス:./someFile../../someFolder/someFileなど)

  • 動的に参照されるモジュール('core-js''typestyle''react''react / core'など)

主な違いは、モジュールがファイルシステム上でどのように解決されるかです。

相対パスの解決

簡単です。単に相対的なパスに従います :)

  • ファイルbar.tsimport * as foo from './foo';を実行した場合、fooを同じフォルダに置く必要がある

  • ファイルbar.tsimport * as foo from '../foo';を実行する場合、fooは1つ上のフォルダ内に存在する必要がある

  • ファイルbar.tsimport * as foo from '../someFolder/foo';を実行する場合、1つ上のフォルダにfooが存在するsomeFolderというフォルダが存在する必要がある

もしくは他に思いついた相対パスが使えます :)

動的な解決

インポートパスが相対でない場合、検索は Node.jsスタイルのモジュール解決 によって行われます。ここでは簡単な例を示します。

  • import * as foo from 'foo'と書いた場合、以下の場所が順番にチェックされます

    • ./node_modules/foo

    • ../node_modules/foo

    • ../../node_modules/foo

    • ファイルシステムのルートに到達するまで続く

  • import * as foo from 'something/foo'と書いた場合、以下の場所が順番にチェックされます

    • ./node_modules/something/foo

    • ../node_modules/something/foo

    • ../../node_modules/something/foo

    • ファイルシステムのルートに到達するまで続く

どのようにパスが解決されるか

上記で言及している場所が「チェックされる」とは、下記のように、その場所がチェックされることを意味しています。例えばfooというパスを指定しているとします:

  • その場所に、foo.tsがあれば、解決されます。

  • その場所がフォルダで、foo/index.tsファイルがある場合、解決されます。

  • その場所がフォルダで、foo/package.jsonが存在し、package.jsonのtypesキーで指定されたファイルがある場合は、解決されます。

  • その場所がフォルダで、package.jsonが存在し、package.jsonのmainキーで指定されたファイルが存在する場合、解決されます。

私が言うファイルは、実際には.ts/.d.ts.jsを意味しています。

以上です。あなたは既にモジュール解決のエキスパートです。

型の動的検索を上書きする

あなたは、declare module '何らかのパス'を使ってプロジェクトのモジュールをグローバルに宣言することができます。そして、import文はそのパスを魔法のように解決します。

例:

// globals.d.ts
declare module 'foo' {
// 何らかの変数の宣言
export var bar: number; /* 例 */
}

そして、このようにすることができます:

// anyOtherTsFileInYourProject.ts
import * as foo from 'foo';
// 動的ルックアップを行わない場合、TypeScriptは、 foo を {bar:number} と推測します:

型だけをimport/requireする

次の文は:

import foo = require('foo');

実際には2つのことをしています:

  • fooモジュールの型情報をインポートする

  • fooモジュールの実行時の依存関係を指定する

開発者は、型情報のみをロードし、実行時の依存関係が発生しないようにすることも可能です。この先を理解する前に、宣言空間を読んでおくとよいでしょう。

変数宣言空間にインポートされた名前を使用しない場合、そのインポートは、生成されたJavaScriptから完全に削除されます。例を見るのが最も簡単です。これが理解できたら、ユースケースを紹介します。

例1

import foo = require('foo');

生成されるJavaScript:

想像の通り、fooが使われないため、空ファイルが生成されます。

例2

import foo = require('foo');
var bar: foo;

生成されるJavaScript:

var bar;

これは、foo(またはfoo.basなどのプロパティ)が変数として一度も使用されないためです。

例3

import foo = require('foo');
var bar = foo;

生成されるJavaScript(commonjsと仮定します):

var foo = require('foo');
var bar = foo;

これはfooが変数として使われているためです。

ユースケース: 遅延ロード(Lazy loading)

型推論は事前に行う必要があります。これは、ファイルbarでファイルfooのある型を使用したい場合、次のようにしなければならないことを意味します:

import foo = require('foo');
var bar: foo.SomeType;

しかし、あるいは実行時に特定の場合だけfooをロードしたいかもしれません。そのような場合には、importした名前を型アノテーションとしてのみ使用し、変数として使用しないでください。これにより、TypeScriptにより挿入される実行時依存関係をすべて削除します。そして、あなたは独自のモジュールローダーに固有のコードを書いて実際のモジュールを手動でインポートします。

例として、次のcommonjsベースのコードを考えてみましょう。このコードでは、特定の関数が呼び出された時だけ'foo'モジュールをロードします:

import foo = require('foo');
export function loadFoo() {
// `foo` を遅延ロードします。そして、オリジナルのモジュールは型アノテーションとして *だけ* 利用します。
var _foo: typeof foo = require('foo');
// これで、 `foo` の代わりに `_foo` を変数として扱うことができます。
}

amd(requirejsを使用)での同様のサンプルは次のようになります:

import foo = require('foo');
export function loadFoo() {
// `foo` を遅延ロードします。そして、オリジナルのモジュールは型アノテーションとして *だけ* 利用します。
require(['foo'], (_foo: typeof foo) => {
// これで、 `foo` の代わりに `_foo` を変数として扱うことができます。
});
}

このパターンは一般的に使用されます。

  • Webアプリケーションにおいて、特定のルートの場合のみ特定のJavaScriptをロードする

  • Nodeアプリケーションにおいて、アプリケーションの起動を高速化したい場合に、特定のモジュールだけをロードする

ユースケース:循環依存を避ける

遅延ロードの使用例と同様に、特定のモジュールローダ(commonjs/nodeと、amd/requirejs)は循環依存のため、うまく動作しません。そのような場合には、1つの方向に遅延ロードを行うようにし、反対方向のロードの前にロードしておくと便利です。

ユースケース:確実にインポートする

場合によっては、副作用のためだけにファイルをロードすることもできます(例えばCodeMirror addonsのように、モジュールを他のライブラリに登録させる場合など)。しかし、単にimport/requireを行うと、コンパイルされたJavaScriptがモジュールに依存しておらず、モジュールバンドラ(例えばwebpack)が、そのインポートを完全に無視してしまうかもしれません。このような場合、例えば下記のように ensureImport変数を使用して、コンパイルされたJavaScriptがモジュールに依存するようにすることができます。

import foo = require('./foo');
import bar = require('./bar');
import bas = require('./bas');
const ensureImport: any =
foo
|| bar
|| bas;