File Module Details

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を前に置くだけなので簡単です。

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

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

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

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

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

    // file `bar.ts`
    import * as foo from './foo';
    // you can use `foo.someVar` and `foo.SomeType` and anything else that foo might export.
  • 副作用のためだけに一つのファイルをインポートする:

import 'core-js'; // a common polyfill library
  • 別のモジュールから全てのものを再エクスポートする

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

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

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

デフォルトのexports/imports

あなたが後で知るように、私はデフォルトのexportが好きではありません。しかし、ここではexportの構文とデフォルトのexportの使い方を説明します。

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

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

    • 関数の前

    • クラスの前

// some var
export default someVar = 123;
// OR Some function
export default function someFunction() { }
// OR Some class
export default class SomeClass { }
  • import someName using someModule構文を使用してインポートする(インポートには任意の名前を付けることができます):

import someLocalNameForThisFile from "../foo";

モジュールのパス

私はmoduleResolution:commonjsを仮定しています。これはあなたのTypeScript設定に含めるべきオプションです。この設定はmodule:commonjsによって暗黙的に設定されます。

2つの異なる種類のモジュールがあります。この区別は、import文のパスセクション(path section)によって行われます(たとえば、import foo from 'これがパスセクションです')。

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

  • その他の動的参照モジュール('core-js''typestyle''react''react / core'など)

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

相対パスモジュール(Relative path modules)

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

  • ファイル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というフォルダが存在する必要がある

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

動的ルックアップ(Dynamic lookup)

インポートパスが相対でない場合、検索は ノード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

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

場所(place)とは何か

私が「チェックされる場所」について言及する時、私は、次のことが、その場所でチェックされていることを意味しています。例えばfooの場所に対して:

  • 場所がファイルを指している場合、たとえばfoo.tsがあれば、解決されます。やったー!

  • 場所がフォルダで、foo/index.tsファイルがある場合、解決されます。やったー!

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

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

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

以上です。あなたは今モジュール解決のエキスパートです(なかなかの成果です!)。

型について動的検索を上書きする

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

// globals.d.ts
declare module 'foo' {
// Some variable declarations
export var bar: number; /*sample*/
}

その後:

// anyOtherTsFileInYourProject.ts
import * as foo from 'foo';
// TypeScript assumes (without doing any lookup) that
// foo is {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() {
// This is lazy loading `foo` and using the original module *only* as a type annotation
var _foo: typeof foo = require('foo');
// Now use `_foo` as a variable instead of `foo`.
}

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

import foo = require('foo');
export function loadFoo() {
// This is lazy loading `foo` and using the original module *only* as a type annotation
require(['foo'], (_foo: typeof foo) => {
// Now use `_foo` as a variable instead of `foo`.
});
}

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

  • Webアプリケーションにおいて、特定のルートの場合のみ特定のJavaScriptを読み込む

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

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

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

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

場合によっては、副作用(side effect)のためだけにファイルをロードすることもできます(例えば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;