TypeScript

TypeScriptのモジュール入門:import・exportとNode.jsの設定を理解する

この記事でわかること

TypeScriptのモジュールをファイル別のコードで解説。名前付きとデフォルトのexport、import type、相対パス、NodeNextとJavaScript出力の対応を学べます。

モジュールを使うと、関数や型をファイルごとに分け、必要なものを exportimport で共有できます。ファイルの役割と依存関係が明確になり、同じ名前の変数が別のファイルへ意図せず影響する問題を避けやすくなります。

ここでは、Node.jsで動く小さなプログラムを三つのファイルに分けます。TypeScript 4.9.4の strict 設定で確認した例です。準備はTypeScriptのインストールを参照してください。

モジュールと名前空間の違い

ファイルのトップレベルで importexport を使うと、そのファイルはモジュールとして扱われます。公開していない宣言は、そのモジュールの内部で使用します。公式のモジュール解説

古い資料で「内部モジュール」と呼ばれていた仕組みは、現在の namespace に対応します。新しいNode.jsアプリやフロントエンドのコード分割では、通常はファイル単位のモジュールを使います。namespace で包み直す必要はありません。公式の名前空間とモジュールの説明

モジュールに分割しただけで、必ずビルド時間が短くなったり、配信するJavaScriptが小さくなったりするわけではありません。これらはビルドツールや依存関係、最適化の設定にもよります。

実行環境とコンパイラ設定を合わせる

学習用プロジェクトの package.json"type": "commonjs" とします。既存の package.json へ他の設定を残したまま指定するには、ルートで npm pkg set type=commonjs を実行します。

tsconfig.json は次の設定にします。既存アプリの設定とは分けて試してください。

{
  "compilerOptions": {
    "target": "ES2020",
    "module": "NodeNext",
    "moduleResolution": "NodeNext",
    "rootDir": "src",
    "outDir": "dist",
    "strict": true,
    "noEmitOnError": true
  },
  "include": ["src/**/*.ts"]
}

この組み合わせでは、.ts に書いた importexport が、CommonJS形式のJavaScriptへ変換されます。Node.jsが生成した .js をどう解釈するかは、package.jsontype などで決まります。ソースの記法と、出力形式と、実行環境をそろえることが大切です。Node.js公式のパッケージと拡張子の説明

ファイルを分割して読み込む

名前付きエクスポートを使う

src/article.ts を作成します。

export interface Article {
  title: string;
  views: number;
}

export function formatArticle(article: Article): string {
  return `${article.title}: ${article.views}回`;
}

Article は型の情報、formatArticle は実行時に存在する関数です。名前付きエクスポートは、一つのファイルから複数公開できます。

デフォルトエクスポートを使う

src/create-heading.ts を作成します。

export default function createHeading(label: string): string {
  return `【${label}】`;
}

デフォルトエクスポートは、一つのモジュールにつき一つです。名前付きとデフォルトのどちらを使うかは、プロジェクト内で読みやすい方針に統一すると管理しやすくなります。

関数と型をインポートする

src/main.ts を作成します。

import { formatArticle } from "./article.js";
import type { Article } from "./article.js";
import createHeading from "./create-heading.js";

const article: Article = {
  title: "モジュール入門",
  views: 42,
};

console.log(createHeading("TypeScript")); // 【TypeScript】
console.log(formatArticle(article)); // モジュール入門: 42回

名前付きの読み込みには { formatArticle }、デフォルトの読み込みには createHeading を使います。import type は型だけを読み込むことを示し、生成するJavaScriptには残りません。

相対パスでは、実行時の出力ファイルに合わせて .js を指定しています。この設定では、TypeScriptが対応する .ts ファイルを見つけて型チェックできます。Node.jsのES Modulesでも相対インポートの拡張子が必要になるため、出力先との対応を意識しておくと移行時にも理解しやすくなります。

ビルドして実行する

npx tsc --project tsconfig.json
node dist/main.js

dist に三つの .js ファイルが生成されます。実行時にも依存するファイルが必要なので、main.js だけを別の場所へ移すと読み込めなくなることがあります。

npmパッケージを利用するとき

外部ライブラリもモジュールとして読み込めます。例えば lodash を使う場合は、実行用パッケージと、必要に応じて型定義をプロジェクトへ追加します。

npm install lodash
npm install --save-dev @types/lodash

型定義がパッケージに同梱されている場合は、別の @types パッケージは不要です。ライブラリごとに、対応するTypeScriptのバージョン、名前付きまたはデフォルトのエクスポート、CommonJSとES Modulesへの対応を公式資料で確認します。上の三ファイルのサンプルには外部ライブラリは不要です。

型定義を入れても、実行用のライブラリ自体がインストールされるわけではありません。また、型定義は外部から受け取る値を自動で検証する仕組みではありません。

モジュールが見つからないときの切り分け

型が解決することと実行できることを分ける

エディターで参照先へ移動できても、出力後のJavaScriptで読み込めるとは限りません。相対パスの拡張子、ファイル名の大文字小文字、package.jsontype、出力先の位置を確認します。

依存関係を一方向に保つ

画面、通信、共通の計算を分けると、計算だけをテストしやすくなります。循環参照で初期化順が問題になる場合は、共通の型や関数を下位のモジュールへ移し、互いの読み込みを減らします。

関連記事

TypeScriptとは・学習順・目的別の記事一覧へ戻る

y.
WRITTEN BY

y_ymo10

SEの部屋で、JavaScript・TypeScript・React.js・Next.jsの開発ノートを公開しています。

ほかの開発ノートを読む →