はじめに
[モジュール化メカニズム](/articles/モジュール-typescript ノート 13/) により、コードを複数のモジュール(ファイル)に分割できます。コンパイル時には依存モジュールの正確なタイプを知る必要があり、まずそれを見つける必要があります(モジュール名からモジュールファイルパスへのマッピングを確立)
実際、TypeScript では、1 つのモジュール名が 1 つの.ts/.tsxまたは.d.tsファイルに対応する可能性があります(--allowJs をオンにすると、.js/.jsx ファイルに対応する可能性もあります)
基本的な思路は以下の通りです:
- まずモジュールに対応するファイル(
.ts/.tsx)を探すことを試みる - 見つからず、相対モジュール導入(
non-relative)でない場合、外部モジュール宣言(ambient module declaration)、つまりd.tsを探すことを試みる - それでも見つからない場合、エラー
Cannot find module 'ModuleA'.を報告
一。相対と非相対モジュール導入
相対モジュール導入(relative import)は/、./または../ で始まります。例えば:
import Entry from "./components/Entry";
import { DefaultHeaders } from "../constants/http";
import "/mod";
注意、相対モジュール導入は相対パス導入と等価ではありません(例えば/mod)
他の形式はすべて非相対モジュール導入(non-relative import)です。例えば:
import * as $ from "jquery";
import { Component } from " @angular/core";
両者の違いは以下の通りです:
-
相対モジュール導入:導入するファイルに対して相対的にモジュールを探し、外部モジュール宣言として解析されることはありません。(ランタイムで相対位置を保持できる)カスタムモジュールを導入するために使用
-
非相対モジュール導入:baseUrl に対して相対的、または パスマッピング に従ってモジュールを探し、外部モジュール宣言として解析される可能性があります。外部依存モジュールを導入するために使用
二。モジュール解析戦略
具体的には、2 つのモジュール解析戦略があります:
これら 2 つの戦略は--moduleResolution コンパイルオプションを通じて指定でき、デフォルトではターゲットモジュール形式に応じて決定されます(module === "AMD" or "System" or "ES6" ? "Classic" : "Node")
Classic
Classic 戦略の下では、相対モジュール導入は導入するファイルに対して相対的に解析されます。例えば:
// ソースファイル /root/src/folder/A.ts
import { b } from "./moduleB"
以下を探すことを試みます:
/root/src/folder/moduleB.ts
/root/src/folder/moduleB.d.ts
非相対モジュール導入の場合、導入するファイルを含むディレクトリから開始してディレクトリツリーを上にたどり、一致する定義ファイルを見つけようとします。例えば:
// ソースファイル /root/src/folder/A.ts
import { b } from "moduleB"
以下のファイルを探すことを試みます:
/root/src/folder/moduleB.ts
/root/src/folder/moduleB.d.ts
/root/src/moduleB.ts
/root/src/moduleB.d.ts
/root/moduleB.ts
/root/moduleB.d.ts
/moduleB.ts
/moduleB.d.ts
Node
NodeJS モジュール解析
NodeJS ではrequire を通じてモジュールを導入し、モジュール解析の具体的な動作はパラメータが相対パスか非相対パスかに依存します
相対パスの処理戦略は非常にシンプルで、以下の場合:
// ソースファイル /root/src/moduleA.js
var x = require("./moduleB");
マッチ順序は以下の通り:
-
/root/src/moduleB.jsのマッチを試みる -
次に
/root/src/moduleB/package.jsonのマッチを試み、次に主モジュールを探す(例えば{ "main": "lib/mainModule.js" }が指定されている場合、/root/src/moduleB/lib/mainModule.jsを導入) -
否则
/root/src/moduleB/index.jsのマッチを試みる。index.jsは暗黙的に该ディレクトリ下の主モジュールとみなされるため
P.S. 詳細は NodeJS ドキュメント参照:File Modules と Folders as Modules
非相対モジュール導入はnode_modules から探します(node_modules は現在のファイルの平級ディレクトリに位置する可能性もあれば、祖先ディレクトリにある可能性もあります)。NodeJS は各node_modules を上に查找し、導入するモジュールを探します。例えば:
// ソースファイル /root/src/moduleA.js
var x = require("moduleB");
NodeJS は順に以下のマッチを試みます:
/root/src/node_modules/moduleB.js
/root/src/node_modules/moduleB/package.json
/root/src/node_modules/moduleB/index.js
/root/node_modules/moduleB.js
/root/node_modules/moduleB/package.json
/root/node_modules/moduleB/index.js
/node_modules/moduleB.js
/node_modules/moduleB/package.json
/node_modules/moduleB/index.js
P.S.package.json については、実際にはそのmain フィールドが指すモジュールをロードします
P.S. NodeJS がどのようにnode_modules からモジュールをロードするかに関する詳細情報は、Loading from node_modules Folders を参照
TypeScript の NodeJS 戦略模倣
(モジュール解析戦略が"Node" の場合)TypeScript も NodeJS ランタイムのモジュール解析メカニズム をシミュレートし、コンパイル時にモジュールの定義ファイルを見つけます
具体的には、TypeScript ソースファイルのサフィックス名を NodeJS のモジュール解析ロジックに追加し、package.json 中のtypes フィールドを通じて宣言ファイルを探します(NodeJS のmain フィールドをシミュレートするのと同等)。例えば:
// ソースファイル /root/src/moduleA.ts
import { b } from "./moduleB"
以下のマッチを試みます:
/root/src/moduleB.ts
/root/src/moduleB.tsx
/root/src/moduleB.d.ts
/root/src/moduleB/package.json
/root/src/moduleB/index.ts
/root/src/moduleB/index.tsx
/root/src/moduleB/index.d.ts
P.S.package.json については、TypeScript はそのtypes フィールドが指すモジュールをロードします
このプロセスは NodeJS と非常に類似しています(まずmoduleB.js、次にpackage.json、最後にindex.js)。TypeScript のソースファイルサフィックス名に置き換えられただけです
同様に、非相対モジュール導入も同様に NodeJS の解析ロジックに従い、まずファイルを探し、次に適用可能なフォルダを探します:
// ソースファイル /root/src/moduleA.ts
import { b } from "moduleB
モジュール查找順序は以下の通り:
/root/src/node_modules/moduleB.ts|tsx|d.ts
/root/src/node_modules/moduleB/package.json
/root/src/node_modules/ @types/moduleB.d.ts
/root/src/node_modules/moduleB/index.ts|tsx|d.ts
/root/node_modules/moduleB.ts|tsx|d.ts
/root/node_modules/moduleB/package.json
/root/node_modules/ @types/moduleB.d.ts
/root/node_modules/moduleB/index.ts|tsx|d.ts
/node_modules/moduleB.ts|tsx|d.ts
/node_modules/moduleB/package.json
/node_modules/ @types/moduleB.d.ts
/node_modules/moduleB/index.ts|tsx|d.ts
NodeJS 查找ロジックとほぼ一致しますが、node_modules/ @types からd.ts 宣言ファイルを探すことが追加されます
三。追加モジュール解析マーカー
ビルド時には.ts を.js にコンパイルし、異なるソース位置から依存関係を 1 つの出力位置にコピーします。したがって、ランタイムではモジュールがソースファイルとは異なる命名を持つ可能性があり、またはコンパイル時に最後に出力されるモジュールパスが対応するソースファイルと一致しない可能性があります
これらの問題に対して、TypeScript は一連のマーカーを提供し、コンパイラにソースパス上で発生することが期待される変換を通知し、最終出力を生成します
P.S. 注意、コンパイラはいかなる変換も実行せず、これらの情報のみを使用してモジュール導入からその定義ファイルへのプロセスを解析することを指導します
Base URL
baseUrl はAMD モジュールに従うアプリケーションで非常によく見られ、モジュールのソースファイルは異なるディレクトリに位置し、ビルドスクリプトによって一緒に配置されます。ランタイムでは、これらのモジュールは単一のディレクトリに「デプロイ」されます
TypeScript ではbaseUrl を設定してコンパイラにモジュールをどこで探すかを通知し、すべての非相対モジュール導入はbaseUrl に対して相対的です。2 つの指定方法があります:
-
コマンドラインパラメータ
--baseUrl(相対パスを指定する場合、現在のディレクトリに基づいて計算) -
tsconfig.json中のbaseUrlフィールド(相対パスの場合、tsconfig.jsonが所在するディレクトリに基づいて計算)
注意、相対モジュール導入は baseUrl の影響を受けません。常に導入するファイルに対して相対的に解析されるためです
パスマッピング
一部のモジュールはbaseUrl 下にありません。例えばjquery モジュールはランタイムではnode_modules/jquery/dist/jquery.slim.min.js から来る可能性があります。この時、モジュールローダーはパスマッピングを通じてモジュール名をランタイムのファイルに対応付けます
TypeScript も同様のマッピング設定(tsconfig.json のpaths フィールド)をサポートします。例えば:
{
"compilerOptions": {
"baseUrl": ".", // "paths" が指定されている場合、これを指定する必要があります
"paths": {
"jquery": ["node_modules/jquery/dist/jquery"] // このマッピングは"baseUrl" に対して相対的です
}
}
}
注意、paths 中のパスもbaseUrl に対して相対的です。baseUrl が変われば、paths も追随して変更する必要があります
実際、より複雑なマッピングルールもサポートしており、例えば複数の代替位置など。詳細は Path mapping を参照
rootDirs 仮想ディレクトリ指定
コンパイル時、複数のディレクトリからのプロジェクトソースコードを統合して単一の出力ディレクトリに生成することがあります。一連のソースディレクトリから「仮想」ディレクトリを作成するのと同等です
rootDirs はコンパイラに「仮想」ディレクトリを構成するそれらの「ルート」パスを通知し、コンパイラが「仮想」ディレクトリを指す相対モジュール導入を解析できるようにします。まるで它们すでに同一ディレクトリに統合されているかのように。例えば:
src # ソースコード
└── views
└── view1.ts (imports './template1')
└── view2.ts
generated # 自動生成されたテンプレートファイル
└── templates
└── views
└── template1.ts (imports './view2')
ビルドツールが它们を同一の出力ディレクトリに統合すると仮定します(つまり、ランタイムでview1 とtemplate1 は一緒にある)。したがって./xxx の方式を通じて導入できます。rootDirs を通じてこの関係をコンパイラに通知し、ソースディレクトリをすべて列挙します:
{
"compilerOptions": {
"rootDirs": [
"src/views",
"generated/templates/views"
]
}
}
此后、rootDirs サブディレクトリを指す相対モジュール導入に遭遇すると、rootDirs の各項目で查找を試みます
実際、rootDirs は非常に柔軟で、配列中に任意の数のディレクトリ名を含めることができ、ディレクトリが実際に存在するかどうかに関わらず。これにより、コンパイラはタイプ安全な方式で、複雑なビルド/ランタイム特性(条件付き導入やプロジェクト固有のローダープラグインなど)を「捕捉」できます
例えば国際化のシナリオでは、ビルドツールが特殊なパス識別子(#{locale} など)を挿入して自動的に当地特定の bundle を生成します。例えば./#{locale}/messages を具体的な./zh/messages、./de/messages などにマッピングします。rootDirs を通じて簡単に解決できます:
{
"compilerOptions": {
"rootDirs": [
"src/zh",
"src/de",
"src/#{locale}"
]
}
}
ローカルにzh 言語パックがある場合、コンパイル時にはsrc/zh 下のファイルが導入されます。例えばimport messages from './#{locale}/messages はimport messages from './zh/messages' として解析されます
四。解析プロセスの追跡
モジュールは現在のディレクトリ外のファイルを参照できるため、モジュール解析関連の問題(モジュールが見つからない、または間違ったものを見つけるなど)を定位するのは容易ではありません
この時、--traceResolution オプションをオンにしてコンパイラ内部のモジュール解析プロセスを追跡できます。例えば:
$ tsc --traceResolution
# 導入されたモジュール名及び所在位置
======== Resolving module './math-lib' from '/proj/src/index.ts'. ========
# モジュール解析戦略
Explicitly specified module resolution kind: 'NodeJs'.
# 具体的なプロセス
Loading module as file / folder, candidate module location '/proj/src/math-lib', target file type 'TypeScript'.
File '/proj/src/math-lib.ts' does not exist.
File '/proj/src/math-lib.tsx' does not exist.
File '/proj/src/math-lib.d.ts' exist - use it as a name resolution result.
# 最終結果
======== Module name './math-lib' was successfully resolved to '/proj/src/math-lib.d.ts'. ========
五。関連オプション
--noResolve
通常、コンパイラは開始前にすべてのモジュール導入を解析しようとします。モジュール導入を 1 つ成功させるごとに、対応するファイルを処理するソースファイルセットに追加します
--noResolve コンパイルオプションはコンパイラがいかなるファイルも追加することを禁止します(コマンドラインを通じて传入されたものを除く)。この時仍然モジュールに対応するファイルの解析を試みますが、不再追加します。例えば���ースファイル:
// ソースファイル app.ts
import * as A from "moduleA"
import * as B from "moduleB"
tsc app.ts moduleA.ts --noResolve はmoduleA を正しく導入できますが、moduleB は見つからないエラーが報告されます(--noResolve が他のファイルの追加を許可しないため)
exclude
デフォルトでは、tsconfig.json が所在するディレクトリが TypeScript プロジェクトディレクトリです。files またはexclude を指定しない場合、该ディレクトリ及びその子孫ディレクトリ下のすべてのファイルがコンパイルプロセスに追加されます。exclude オプションを通じて一部のファイルを排除(ブラックリスト)するか、files オプションを使用してコンパイルしたいソースファイルを指定(ホワイトリスト)できます
さらに、コンパイルプロセス中に導入されたモジュールに遭遇すると、それも追加されます。exclude されているかどうかに関わらず。したがって、コンパイル時にファイルを完全に排除するには、exclude 自身に加えて、それを引用しているすべてのファイルも排除する必要があります
コメントはまだありません