はじめに (対象読者・この記事でわかること)

この記事は、Node.jsとTypeScriptを使ってアプリケーション開発を行っている開発者、特にTypeScriptで書かれたコードをコンパイル・実行する際に「exports is not defined」というエラーに遭遇し、その原因と解決策を探している方を主な対象としています。

この記事を読むことで、Node.jsにおけるモジュールシステム(CommonJSとES Modules)の基本的な違いを理解し、TypeScriptのコンパイル設定がこのエラーにどう影響するかを把握できます。また、具体的なtsconfig.jsonの設定変更やNode.jsの実行方法を学ぶことで、「exports is not defined」エラーを解消し、スムーズにTypeScriptプロジェクトを動かせるようになるでしょう。

前提知識

この記事を読み進める上で、以下の知識があるとスムーズです。 * TypeScriptの基本的な構文とimport/exportの概念 * Node.jsの基本的な実行方法とプロジェクトの初期化(npm initなど) * ターミナルでの基本的なコマンド操作

TypeScriptとNode.jsのモジュールシステム:exports is not definedエラーの背景

Node.jsは、もともと独自のモジュールシステムであるCommonJSを採用していました。これはrequire()でモジュールを読み込み、module.exportsまたはexportsで公開する形式です。一方で、JavaScriptの標準仕様であるES Modules (ESM) はimportexport構文を使用します。

TypeScriptは、最終的にJavaScriptにコンパイルされる言語です。TypeScriptで書かれたimport/export構文は、コンパイル時にどのようなJavaScriptのモジュール形式(CommonJS形式かES Modules形式かなど)に変換されるかをtsconfig.jsoncompilerOptions.module設定で指定できます。

exports is not defined」エラーは、主にNode.jsがCommonJSモジュールとして動作している環境で、ES Modules形式で出力されたJavaScriptファイルを実行しようとした場合に発生します。これは、Node.jsの旧バージョンや、package.json"type": "module"が設定されていないプロジェクトでよく見られます。Node.jsがimport/export構文をCommonJSのrequire/module.exportsとは異なるものとして認識できないため、exportsというグローバルなオブジェクトが存在しない、というエラーを吐いてしまうのです。

exports is not definedエラーの具体的な解決策

このセクションでは、「exports is not defined」エラーに遭遇した際の具体的な確認手順と解決方法を解説します。

ステップ1: エラーの再現と確認

まず、エラーが発生している状況を最小限のコードで再現してみましょう。

例えば、以下のようなTypeScriptファイルを作成します。

src/sub.ts:

Typescript
export const greet = (name: string) => `Hello, ${name}!`;

src/index.ts:

Typescript
import { greet } from './sub'; console.log(greet('World'));

次に、これらのファイルをコンパイルし、Node.jsで実行します。

Bash
# TypeScriptのインストール (まだの場合) npm install -g typescript # プロジェクトの初期化とtsconfig.jsonの作成 mkdir my-ts-app cd my-ts-app npm init -y npx tsc --init # tsconfig.jsonが作成されます # コンパイル npx tsc # コンパイルされたJavaScriptファイルを実行 node dist/index.js

この実行で「exports is not defined」エラーが発生した場合、それはまさに本記事で扱う問題です。

ステップ2: tsconfig.json の設定見直し

エラーの原因は、TypeScriptのコンパイル設定、特にcompilerOptions.moduleがNode.jsの期待するモジュール形式と合っていないことにあります。

tsconfig.jsonを開き、compilerOptions内のmoduleオプションを確認してください。

解決策1: CommonJS形式でコンパイルする

最も簡単な解決策の一つは、Node.jsのデフォルトであるCommonJS形式でTypeScriptをコンパイルすることです。

tsconfig.json

Json
{ "compilerOptions": { // ... その他の設定 "target": "es2016", // またはそれ以上 (Node.jsでサポートされているESバージョン) "module": "commonjs", // ここを "commonjs" に設定 "outDir": "./dist", "esModuleInterop": true, // 必要に応じて追加 "forceConsistentCasingInFileNames": true, "strict": true, "skipLibCheck": true }, "include": ["src/**/*.ts"], "exclude": ["node_modules"] }
  • "module": "commonjs": これにより、TypeScriptのimport/export構文がNode.jsが理解できるrequire/module.exports形式に変換されます。
  • "esModuleInterop": true: これは、CommonJS形式で出力されるモジュールが、ES Modulesのデフォルトインポート(import React from 'react'のような形式)と互換性を持つようにするための設定です。多くの場合、module: "commonjs"と合わせてtrueにすることをお勧めします。

設定変更後、再度コンパイルして実行します。

Bash
npx tsc node dist/index.js

これでエラーが解消されるはずです。

解決策2: Node.jsをES Modulesモードで実行する

Node.jsのバージョン12以降では、ES Modulesをネイティブでサポートしています。プロジェクト全体をES Modulesとして扱うことで、TypeScriptもES Modules形式でコンパイルし、Node.jsでそのまま実行することが可能です。

この解決策には、主に2つのアプローチがあります。

アプローチA: package.json"type": "module" を追加する

プロジェクトのルートにあるpackage.jsonファイルを開き、トップレベルに"type": "module"を追加します。

package.json

Json
{ "name": "my-ts-app", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "keywords": [], "author": "", "license": "ISC", "type": "module" // ★ここを追加★ }

次に、tsconfig.jsonでTypeScriptをES Modules形式で出力するように設定します。

tsconfig.json

Json
{ "compilerOptions": { // ... その他の設定 "target": "es2020", // Node.jsのESM対応バージョンに合わせる (Node.js 14+ならes2020など) "module": "ESNext", // または "ES2020", "Node16" など "outDir": "./dist", "esModuleInterop": true, "forceConsistentCasingInFileNames": true, "strict": true, "skipLibCheck": true }, "include": ["src/**/*.ts"], "exclude": ["node_modules"] }
  • "type": "module": これをpackage.jsonに追加すると、Node.jsはそのディレクトリ内の.jsファイルをデフォルトでES Modulesとして解釈するようになります。
  • "module": "ESNext": TypeScriptを最新のES Modules形式でコンパイルするように指示します。"Node16""NodeNext"もより新しいNode.jsのESMサポートに合わせたオプションです。
  • "target": Node.jsがサポートするESバージョンに合わせる必要があります。新しいNode.jsバージョンを使用している場合は"es2020""esnext"などで問題ありません。

設定変更後、再度コンパイルして実行します。

Bash
npx tsc node dist/index.js

この設定により、Node.jsはES Modulesとして生成されたJavaScriptコードを正しく解釈し、exports is not definedエラーは発生しなくなります。

アプローチB: .mjs 拡張子を使用する (より限定的)

package.json"type": "module"を追加する代わりに、ES Modulesとして扱いたいJavaScriptファイルの拡張子を.mjsにする方法もあります。

例えば、dist/index.jsdist/index.mjsにリネームして実行する、またはTypeScriptのコンパイル時に.mjs拡張子で出力するように設定します。(後者は少々複雑な設定が必要になる場合があります)

この方法はファイル単位でES ModulesとCommonJSを混在させたい場合に有用ですが、プロジェクト全体をES Modulesに移行するならアプローチAの方が一般的で簡単です。

ハマった点やエラー解決

  • Node.jsのバージョン: Node.jsの古いバージョン(特に12未満)では、ES Modulesのサポートが不完全または実験的でした。最新の安定版Node.js LTSバージョンを使用しているか確認しましょう。
  • tsconfig.jsonpackage.jsonの不整合: tsconfig.jsonでES Modulesを出力するように設定しているにもかかわらず、package.json"type": "module"が設定されていない、またはその逆のパターンがよく見られます。両者の設定が一致しているか確認が重要です。
  • esModuleInteropの有無: CommonJSのmodule.exports = ...形式でエクスポートされたライブラリをES Modulesのimport構文でインポートする際に、"esModuleInterop": trueがないとエラーになることがあります。これはTypeScriptコンパイラが互換性レイヤーを提供するためです。
  • 実行方法の誤り: TypeScriptファイルを直接Node.jsで実行しようとしている場合(例: node src/index.ts)、Node.jsはTypeScriptを直接解釈できないため、ts-nodeのようなツールを使用しない限り実行できません。常にコンパイル後のJavaScriptファイルを実行するようにしてください。

解決策のまとめ

最も一般的で推奨される解決策は以下のいずれかです。

  1. Node.jsのプロジェクトをCommonJSとして運用する:

    • tsconfig.jsoncompilerOptions.module"commonjs"に設定。
    • package.jsonには"type": "module"を追加しない。
    • コンパイル後、node dist/your_entry_file.jsで実行。
  2. Node.jsのプロジェクトをES Modulesとして運用する:

    • package.json"type": "module"を追加。
    • tsconfig.jsoncompilerOptions.module"ESNext""Node16"、または"NodeNext"など、Node.jsのESM対応に合わせた適切なオプションに設定。
    • compilerOptions.targetもNode.jsがサポートするバージョンに合わせる("es2020"など)。
    • コンパイル後、node dist/your_entry_file.jsで実行。

どちらの解決策を選ぶかは、プロジェクトの要件やチームの慣習によって異なりますが、新しいプロジェクトであればES Modulesへの移行を検討する価値は十分にあります。

まとめ

本記事では、Node.js環境でTypeScriptをコンパイル・実行する際に発生する「exports is not defined」エラーの原因と、その具体的な解決策について解説しました。

  • モジュールシステム: Node.jsのCommonJSとJavaScript標準のES Modulesの間の不整合がエラーの根本原因であることを理解しました。
  • tsconfig.jsonの設定: TypeScriptのコンパイルオプション、特にcompilerOptions.moduleが、出力されるJavaScriptのモジュール形式を決定し、このエラーに直接影響することを確認しました。
  • Node.jsのESM対応: package.json"type": "module"を追加することで、Node.jsがES Modulesをネイティブで扱えるようになることを学びました。

この記事を通して、Node.jsにおけるTypeScriptのモジュール解決に関する理解を深め、「exports is not defined」エラーに自信を持って対処できるようになるでしょう。今後は、Webバンドラー(WebpackやRollupなど)との連携や、より複雑なTypeScriptプロジェクトでのモジュール解決パターンについても掘り下げていく予定です。

参考資料