はじめに (対象読者・この記事でわかること)
この記事は、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) はimportとexport構文を使用します。
TypeScriptは、最終的にJavaScriptにコンパイルされる言語です。TypeScriptで書かれたimport/export構文は、コンパイル時にどのようなJavaScriptのモジュール形式(CommonJS形式かES Modules形式かなど)に変換されるかをtsconfig.jsonのcompilerOptions.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:
Typescriptexport const greet = (name: string) => `Hello, ${name}!`;
src/index.ts:
Typescriptimport { 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にすることをお勧めします。
設定変更後、再度コンパイルして実行します。
Bashnpx 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"などで問題ありません。
設定変更後、再度コンパイルして実行します。
Bashnpx 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.jsをdist/index.mjsにリネームして実行する、またはTypeScriptのコンパイル時に.mjs拡張子で出力するように設定します。(後者は少々複雑な設定が必要になる場合があります)
この方法はファイル単位でES ModulesとCommonJSを混在させたい場合に有用ですが、プロジェクト全体をES Modulesに移行するならアプローチAの方が一般的で簡単です。
ハマった点やエラー解決
- Node.jsのバージョン: Node.jsの古いバージョン(特に12未満)では、ES Modulesのサポートが不完全または実験的でした。最新の安定版Node.js LTSバージョンを使用しているか確認しましょう。
tsconfig.jsonとpackage.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ファイルを実行するようにしてください。
解決策のまとめ
最も一般的で推奨される解決策は以下のいずれかです。
-
Node.jsのプロジェクトをCommonJSとして運用する:
tsconfig.jsonのcompilerOptions.moduleを"commonjs"に設定。package.jsonには"type": "module"を追加しない。- コンパイル後、
node dist/your_entry_file.jsで実行。
-
Node.jsのプロジェクトをES Modulesとして運用する:
package.jsonに"type": "module"を追加。tsconfig.jsonのcompilerOptions.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プロジェクトでのモジュール解決パターンについても掘り下げていく予定です。
参考資料
- TypeScript Handbook - Modules
- Node.js - Modules: CommonJS modules
- Node.js - Modules: ES modules
- TypeScript Deep Dive -
moduleoption