JavaScript は、コードのモジュール化を処理するさまざまな方法を長い歴史を持っています。2012 年から存在する TypeScript は、これらの多くの形式に対するサポートを実装してきましたが、時間の経過とともに、コミュニティと JavaScript 仕様は ES モジュール (または ES6 モジュール) と呼ばれる形式に収束しました。import
/export
構文としてご存知かもしれません。
ES モジュールは 2015 年に JavaScript 仕様に追加され、2020 年までにほとんどの Web ブラウザと JavaScript ランタイムで幅広くサポートされるようになりました。
ハンドブックでは、ES モジュールとその普及した前身である CommonJS の module.exports =
構文の両方を重点的に取り上げます。他のモジュールパターンに関する情報は、モジュール の参照セクションにあります。
JavaScript モジュールの定義方法
TypeScript では、ECMAScript 2015 と同様に、最上位レベルの import
または export
を含むファイルはモジュールと見なされます。
逆に、最上位レベルの import または export 宣言がないファイルは、その内容がグローバルスコープで使用可能である(したがってモジュールでも使用可能である)スクリプトとして扱われます。
モジュールは、グローバルスコープではなく、独自のスコープ内で実行されます。つまり、モジュールで宣言された変数、関数、クラスなどは、エクスポート形式のいずれかを使用して明示的にエクスポートされない限り、モジュールの外部からは表示されません。逆に、別のモジュールからエクスポートされた変数、関数、クラス、インターフェースなどを利用するには、インポート形式のいずれかを使用してインポートする必要があります。
非モジュール
開始する前に、TypeScript がモジュールと見なすものを理解することが重要です。JavaScript 仕様では、import
宣言、export
、または最上位レベルの await
を含まない JavaScript ファイルはすべて、スクリプトでありモジュールではないと宣言されています。
スクリプトファイル内では、変数と型は共有グローバルスコープに宣言され、複数の入力ファイルを1つの出力ファイルに結合するoutFile
コンパイラオプションを使用するか、HTMLで複数の<script>
タグを使用してこれらのファイル(正しい順序で!)を読み込むと想定されます。
現在import
やexport
がないファイルがモジュールとして扱われるようにするには、次の行を追加します。
tsTry
export {};
これにより、何もエクスポートしないモジュールとしてファイルが変更されます。この構文は、モジュールのターゲットに関係なく機能します。
TypeScript のモジュール
TypeScript でモジュールベースのコードを作成する際には、主に 3 つの点を考慮する必要があります。
- 構文: ものをインポートおよびエクスポートするために使用する構文は何か?
- モジュール解決: モジュール名(またはパス)とディスク上のファイルの関係は何か?
- モジュール出力ターゲット: 生成される JavaScript モジュールはどのようなものにする必要があるか?
ES モジュール構文
ファイルは、export default
を介してメインエクスポートを宣言できます。
tsTry
// @filename: hello.tsexport default functionhelloWorld () {console .log ("Hello, world!");}
これは次のようにインポートされます。
tsTry
importhelloWorld from "./hello.js";helloWorld ();
デフォルトエクスポートに加えて、default
を省略することで、export
を介して複数の変数と関数のエクスポートを行うことができます。
tsTry
// @filename: maths.tsexport varpi = 3.14;export letsquareTwo = 1.41;export constphi = 1.61;export classRandomNumberGenerator {}export functionabsolute (num : number) {if (num < 0) returnnum * -1;returnnum ;}
これらは、import
構文を使用して別のファイルで使用できます。
tsTry
import {pi ,phi ,absolute } from "./maths.js";console .log (pi );constabsPhi =absolute (phi );
追加のインポート構文
インポートは、import {old as new}
のような形式を使用して名前を変更できます。
tsTry
import {pi asπ } from "./maths.js";console .log (π );
上記の構文を単一のimport
に組み合わせて使用できます。
tsTry
// @filename: maths.tsexport constpi = 3.14;export default classRandomNumberGenerator {}// @filename: app.tsimportRandomNumberGenerator , {pi asπ } from "./maths.js";RandomNumberGenerator ;console .log (π );
エクスポートされたすべてのオブジェクトを* as name
を使用して単一のネームスペースにまとめることができます。
tsTry
// @filename: app.tsimport * asmath from "./maths.js";console .log (math .pi );constpositivePhi =math .absolute (math .phi );
import "./file"
を使用して、ファイルを取り込み、現在のモジュールに変数を *含めない* ことができます。
tsTry
// @filename: app.tsimport "./maths.js";console .log ("3.14");
この場合、import
は何もしません。ただし、maths.ts
内のすべてのコードは評価され、他のオブジェクトに影響を与える副作用が発生する可能性があります。
TypeScript 固有の ES モジュール構文
型は、JavaScript の値と同じ構文を使用してエクスポートおよびインポートできます。
tsTry
// @filename: animal.tsexport typeCat = {breed : string;yearOfBirth : number };export interfaceDog {breeds : string[];yearOfBirth : number;}// @filename: app.tsimport {Cat ,Dog } from "./animal.js";typeAnimals =Cat |Dog ;
TypeScript は、型のインポートを宣言するための 2 つの概念を使用して、import
構文を拡張しています。
import type
これは、型のみをインポートできるインポートステートメントです。
tsTry
// @filename: animal.tsexport type'createCatName' cannot be used as a value because it was imported using 'import type'.1361'createCatName' cannot be used as a value because it was imported using 'import type'.Cat = {breed : string;yearOfBirth : number };export typeDog = {breeds : string[];yearOfBirth : number };export constcreateCatName = () => "fluffy";// @filename: valid.tsimport type {Cat ,Dog } from "./animal.js";export typeAnimals =Cat |Dog ;// @filename: app.tsimport type {createCatName } from "./animal.js";constname =createCatName ();
インラインtype
インポート
TypeScript 4.5 では、インポートされた参照が型であることを示すために、個々のインポートに type
を接頭辞として付けることもできます。
tsTry
// @filename: app.tsimport {createCatName , typeCat , typeDog } from "./animal.js";export typeAnimals =Cat |Dog ;constname =createCatName ();
これらにより、Babel、swc、esbuild などの TypeScript 以外のトランスパイラは、安全に削除できるインポートを認識できます。
CommonJS の動作を伴う ES モジュール構文
TypeScript には、CommonJS および AMD の require
に直接対応する ES モジュール構文があります。ES モジュールを使用したインポートは、ほとんどの場合、これらの環境からの require
と同じですが、この構文により、TypeScript ファイルと CommonJS 出力の 1 対 1 の対応が保証されます。
tsTry
importfs = require("fs");constcode =fs .readFileSync ("hello.ts", "utf8");
この構文の詳細については、モジュールリファレンスページをご覧ください。
CommonJS 構文
CommonJS は、npm で配布されるほとんどのモジュールの形式です。上記の ES モジュール構文を使用して記述している場合でも、CommonJS 構文のしくみを簡単に理解しておくと、デバッグが容易になります。
エクスポート
識別子は、module
というグローバルオブジェクトの exports
プロパティを設定することでエクスポートされます。
tsTry
functionabsolute (num : number) {if (num < 0) returnnum * -1;returnnum ;}module .exports = {pi : 3.14,squareTwo : 1.41,phi : 1.61,absolute ,};
その後、これらのファイルは require
ステートメントを使用してインポートできます。
tsTry
constmaths =require ("./maths");maths .pi ;
または、JavaScript のデストラクチャリング機能を使用して少し簡素化できます。
tsTry
const {squareTwo } =require ("./maths");squareTwo ;
CommonJS と ES モジュールの相互運用性
デフォルトインポートとモジュールネームスペースオブジェクトインポートの区別に関して、CommonJS と ES モジュールの間には機能の不一致があります。TypeScript には、esModuleInterop
を使用して、2 つの異なる制約セット間の摩擦を軽減するためのコンパイラフラグがあります。
TypeScript のモジュール解決オプション
モジュール解決とは、import
または require
ステートメントから文字列を取得し、その文字列が参照するファイルを確認するプロセスです。
TypeScript には、クラシックとノードの 2 つの解決戦略があります。コンパイラオプション module
が commonjs
でない場合のデフォルトであるクラシックは、下位互換性のために含まれています。ノード戦略は、.ts
と .d.ts
の追加チェックを使用して、Node.js が CommonJS モードで動作する方法を複製します。
TypeScript 内のモジュール戦略に影響を与える TSConfig フラグは多数あります。moduleResolution
、baseUrl
、paths
、rootDirs
。
これらの戦略の動作に関する完全な詳細については、モジュール解決リファレンスページを参照してください。
TypeScript のモジュール出力オプション
出力される JavaScript に影響を与えるオプションが 2 つあります。
target
は、どの JS 機能をダウングレードする(古い JavaScript ランタイムで実行されるように変換する)か、そのままにするかを決定します。module
は、モジュールがお互いにやり取りするために使用するコードを決定します。
使用する target
は、TypeScript コードを実行すると予想される JavaScript ランタイムで使用可能な機能によって決まります。それは、サポートする最も古い Web ブラウザ、実行を期待する Node.js の最低バージョン、または Electron など、ランタイムからの独自の制約から生じる可能性があります。
モジュール間のすべての通信はモジュールローダーを介して行われ、コンパイラオプション module
は使用するローダーを決定します。ランタイムでは、モジュールローダーは、モジュールを実行する前に、そのモジュールのすべての依存関係の場所を特定し、実行する役割を担います。
例えば、ES モジュール構文を使用した TypeScript ファイルを以下に示します。これには、module
のいくつかの異なるオプションが示されています。
tsTry
import {valueOfPi } from "./constants.js";export consttwoPi =valueOfPi * 2;
ES2020
tsTry
import { valueOfPi } from "./constants.js";export const twoPi = valueOfPi * 2;
CommonJS
tsTry
"use strict";Object.defineProperty(exports, "__esModule", { value: true });exports.twoPi = void 0;const constants_js_1 = require("./constants.js");exports.twoPi = constants_js_1.valueOfPi * 2;
UMD
tsTry
(function (factory) {if (typeof module === "object" && typeof module.exports === "object") {var v = factory(require, exports);if (v !== undefined) module.exports = v;}else if (typeof define === "function" && define.amd) {define(["require", "exports", "./constants.js"], factory);}})(function (require, exports) {"use strict";Object.defineProperty(exports, "__esModule", { value: true });exports.twoPi = void 0;const constants_js_1 = require("./constants.js");exports.twoPi = constants_js_1.valueOfPi * 2;});
ES2020 は、元の
index.ts
と事実上同じです。
module
の TSConfig リファレンスで、利用可能なすべてのオプションとその出力された JavaScript コードを確認できます。
TypeScript 名前空間
TypeScript には、ES モジュール標準より前に存在する namespaces
と呼ばれる独自のモジュール形式があります。この構文には、複雑な定義ファイルを作成するための多くの便利な機能があり、DefinitelyTyped でも 依然として積極的に使用されています。非推奨ではありませんが、名前空間のほとんどの機能は ES モジュールに存在するため、JavaScript の方向性に合わせて ES モジュールを使用することをお勧めします。名前空間の詳細については、名前空間リファレンスページを参照してください。