TypeScriptとNode.jsでのモジュール解決の仕組みを理解する!初心者向け完全ガイド
生徒
「TypeScriptで他のファイルからコードを読み込むとき、どうやって探してくるんですか?」
先生
「それはモジュール解決という仕組みで行われています。TypeScriptとNode.jsが連携して、必要なファイルを見つけ出すんですよ。」
生徒
「モジュール解決って何ですか?難しそうですね…」
先生
「大丈夫です。図書館で本を探すのと同じような仕組みなので、順番に見ていきましょう!」
1. モジュール解決とは?
モジュール解決とは、TypeScriptやNode.jsがimportやrequireで指定されたファイルやパッケージを探し出す仕組みのことです。プログラミングでは、複数のファイルに分けてコードを書くことが一般的です。そのとき、あるファイルから別のファイルを読み込む必要がありますが、コンピュータはどこにそのファイルがあるのかを探さなければなりません。
例えば、図書館で本を探すとき、書名や著者名から本棚の場所を特定しますよね。それと同じように、TypeScriptとNode.jsは特定のルールに従って、必要なファイルやパッケージがどこにあるのかを探し出します。この探索のプロセスをモジュール解決(Module Resolution)と呼びます。
2. TypeScriptのモジュール解決の基本
TypeScriptでは、主に2つのモジュール解決方式があります。それがClassicとNodeです。現在のTypeScript開発では、ほとんどの場合Node方式が使われています。これはNode.jsの動作に合わせた解決方法で、より実用的だからです。
モジュール解決方式の指定
TypeScriptの設定ファイルであるtsconfig.jsonで、どの解決方式を使うかを指定できます。
{
"compilerOptions": {
"moduleResolution": "node"
}
}
この設定で、Node.js互換のモジュール解決を使用することを宣言しています。moduleResolutionという項目が、モジュールをどのように探すかを決める設定です。
3. 相対パスと非相対パスの違い
モジュールを読み込むとき、パスの書き方によって探し方が変わります。大きく分けて相対パスと非相対パスの2種類があります。
相対パスによるインポート
相対パスとは、現在のファイルから見た相対的な位置でファイルを指定する方法です。./や../で始まるパスが相対パスです。
import { User } from './models/user';
import { Config } from '../config/settings';
./は現在のフォルダ、../は1つ上のフォルダを意味します。この方法は、自分で作成したファイルを読み込むときによく使います。
非相対パスによるインポート
非相対パスは、./や../で始まらないパスです。これは主に外部パッケージを読み込むときに使います。
import express from 'express';
import { readFile } from 'fs';
このような書き方をすると、TypeScriptとNode.jsはnode_modulesフォルダの中からパッケージを探します。
4. Node.jsスタイルのモジュール解決の仕組み
Node.jsスタイルの解決方法では、非相対パスのモジュールを探すとき、以下の順番で探索が行われます。
探索の順序
- 現在のフォルダの
node_modulesを確認 - 見つからなければ、親フォルダの
node_modulesを確認 - さらに上の階層の
node_modulesを順に確認 - ルートディレクトリまで到達しても見つからなければエラー
例えば、/project/src/app.tsというファイルでimport lodash from 'lodash';と書いた場合、以下の順番で探します。
-
/project/src/node_modules/lodash -
/project/node_modules/lodash -
/node_modules/lodash
通常、プロジェクトのルートにあるnode_modulesにパッケージがインストールされているため、そこで見つかります。
5. ファイル拡張子の解決
TypeScriptは、インポート時にファイルの拡張子を省略できます。拡張子が省略された場合、TypeScriptは自動的に複数の拡張子を試して、該当するファイルを探します。
import { helper } from './utils/helper';
この場合、TypeScriptは以下の順番でファイルを探します。
helper.tshelper.tsxhelper.d.ts
.d.tsは型定義ファイルと呼ばれ、JavaScriptライブラリにTypeScriptの型情報を追加するためのファイルです。
6. index.tsによるディレクトリのインポート
フォルダを指定してインポートすると、そのフォルダ内のindex.tsというファイルが自動的に読み込まれます。これはNode.jsの規則をTypeScriptも引き継いでいます。
import { functions } from './utils';
この場合、実際には./utils/index.tsが読み込まれます。これにより、複数のファイルをまとめて公開するときに便利です。
具体例:utilsフォルダの構成
以下のようなフォルダ構成があるとします。
utils/
├── index.ts
├── math.ts
└── string.ts
index.tsで他のファイルをまとめて公開すれば、外部からはutilsだけを指定して使えます。
// utils/index.ts
export * from './math';
export * from './string';
7. package.jsonのmainフィールド
パッケージをインポートするとき、そのパッケージのルートにあるpackage.jsonのmainフィールドが確認されます。これはパッケージのエントリーポイント(入口)を示します。
{
"name": "my-library",
"version": "1.0.0",
"main": "dist/index.js"
}
このパッケージをimport myLibrary from 'my-library';でインポートすると、実際にはdist/index.jsが読み込まれます。TypeScriptの場合、typesフィールドで型定義ファイルの場所も指定できます。
{
"name": "my-library",
"version": "1.0.0",
"main": "dist/index.js",
"types": "dist/index.d.ts"
}
8. baseUrlとpathsの活用
プロジェクトが大きくなると、相対パスが長くなって管理が大変になります。TypeScriptではbaseUrlとpathsを使って、独自のパス解決ルールを設定できます。
{
"compilerOptions": {
"baseUrl": "./src",
"paths": {
"@models/*": ["models/*"],
"@utils/*": ["utils/*"]
}
}
}
この設定により、以下のように短く書けるようになります。
// 設定前
import { User } from '../../../models/user';
// 設定後
import { User } from '@models/user';
@記号を使ったパスは、エイリアスと呼ばれ、コードの可読性を高めます。
9. 実践例:モジュール解決の動作確認
実際にモジュール解決がどのように動作するか、簡単な例で確認してみましょう。以下のようなプロジェクト構成を考えます。
project/
├── src/
│ ├── app.ts
│ └── services/
│ └── user.ts
├── node_modules/
│ └── express/
└── tsconfig.json
app.tsで様々なインポートを試してみます。
// 相対パスでの自作モジュール
import { UserService } from './services/user';
// 非相対パスでの外部パッケージ
import express from 'express';
// TypeScriptが探索する順序
// './services/user' → src/services/user.ts を直接参照
// 'express' → node_modules/express を探索
TypeScriptコンパイラは、それぞれのインポート文に対して適切な解決方法を自動的に選択し、ファイルを見つけ出します。もし見つからなければ、コンパイル時にエラーメッセージが表示されます。
10. トラブルシューティング:モジュールが見つからないとき
開発中によく遭遇する「モジュールが見つかりません」というエラーの対処法を紹介します。
Cannot find module './services/user' or its corresponding type declarations.
チェックポイント
- ファイルパスの確認:スペルミスや大文字小文字の間違いがないか
- 拡張子の確認:ファイルが実際に存在するか
- node_modulesの確認:パッケージがインストールされているか
- tsconfig.jsonの確認:moduleResolutionの設定は正しいか
特に外部パッケージの場合、npm installやyarn installでパッケージをインストールし忘れていることがよくあります。エラーメッセージをよく読んで、何が見つからないのかを確認しましょう。