Node.jsでの開発において、スクリプトが実行されているディレクトリの絶対パスを取得する __dirname は、長らく必須のツールとして活用されてきました。
しかし、近年のNode.js開発における標準となりつつある ECMAScript Modules (ESM) 環境では、この便利な変数がデフォルトで利用できません。
本記事では、ESMで __dirname が使えない根本的な原因を解明し、最新のNode.js環境における推奨される解決策と代替コードを詳しく解説します。
モダンな開発環境への移行をスムーズに進めるための知識を、ここでしっかりと整理しておきましょう。
なぜNode.jsで__dirnameが使えないのか
Node.jsには、従来から利用されている CommonJS (CJS) と、JavaScriptの標準仕様である ECMAScript Modules (ESM) の2つのモジュールシステムが存在します。
これまで多くの開発者が慣れ親しんできた __dirname や __filename は、実はNode.jsが各モジュールを実行する際に自動的に注入する特別な変数です。
CommonJS形式のファイル (.js または .cjs) では、コード全体が暗黙的に関数でラップされ、その引数として __dirname が渡されます。
しかし、ESM環境ではこの「モジュールラッパー」という仕組み自体が存在しません。
ESMはブラウザなど他の環境との互換性を重視して設計されており、特定のプラットフォームに依存するグローバル変数の提供を避ける傾向にあります。
その結果、ESMで __dirname を呼び出そうとすると、定義されていない変数へのアクセスとして ReferenceError が発生してしまいます。
この仕様変更は、プロジェクトの package.json で "type": "module" を設定した場合や、拡張子が .mjs のファイルを使用した場合に顕著となります。
現代のNode.js開発では、この差異を理解し、適切な代替手段を選択することが不可欠です。
ECMAScript Modules (ESM)環境でのエラー原因
ESM環境でエラーが発生する直接的な原因は、モジュールのスコープ管理の違いにあります。
CommonJSにおいて __dirname は、実行中のファイルの絶対パスを保持する文字列として提供されます。
一方、ESMではファイルの位置情報を「パス」ではなく「URL」として管理しています。
具体的には、import.meta.url というオブジェクトを通じて、実行中のファイルのURLを取得する仕組みが採用されています。
この import.meta.url は file:///C:/project/app.js のような形式で情報を保持しています。
したがって、従来のOS依存のファイルパス文字列である __dirname とは、データの型も構造も根本的に異なっています。
Node.js公式は、ESMにおいて環境に依存するグローバル変数を排除することで、より純粋なJavaScriptの実行環境を目指しました。
この設計思想により、開発者は明示的にパス変換処理を行う必要が生じたのです。
ESMで現在のディレクトリを取得する最新の解決策
Node.jsの進化に伴い、ESM環境で __dirname の代わりとなるパスを取得する方法はいくつか提案されてきました。
ここでは、最も推奨される最新の手法から、互換性を重視した手法まで順に紹介します。
Node.js 20.11.0以降で利用可能な「import.meta.dirname」
Node.js v20.11.0 および v21.2.0 以降、開発者の利便性を向上させるために待望の新機能が追加されました。
それが、import.meta.dirname と import.meta.filename です。
これにより、以前のように複雑な変換処理を記述することなく、CommonJSと同じ感覚でパスを取得できるようになりました。
2026年現在のモダンなプロジェクトであれば、このプロパティを使用するのが最も効率的で簡潔な解決策です。
// Node.js v20.11.0+ の推奨コード
const currentDir = import.meta.dirname;
const currentFile = import.meta.filename;
console.log("現在のディレクトリ:", currentDir);
console.log("現在のファイル名:", currentFile);
現在のディレクトリ: /Users/username/project/src
現在のファイル名: /Users/username/project/src/index.js
この方法であれば、追加のモジュールをインポートする必要もなく、コードの可読性も非常に高く保てます。
「import.meta.url」と「fileURLToPath」を組み合わせる汎用的な方法
もし、古いバージョンのNode.js環境をサポートする必要がある場合は、 url モジュールを使用する方法が一般的です。
import.meta.url で取得したファイルURLを、 fileURLToPath 関数を用いてOS標準のパス形式に変換します。
さらに path.dirname を組み合わせることで、従来の __dirname と同等の値を得ることができます。
import { fileURLToPath } from 'url';
import { dirname } from 'path';
// 現在のファイルのURLをパス文字列に変換
const __filename = fileURLToPath(import.meta.url);
// ファイルパスからディレクトリ名を取得
const __dirname = dirname(__filename);
console.log("互換性のあるディレクトリパス:", __dirname);
互換性のあるディレクトリパス: /home/user/app
この手法は、ESMが導入された初期から使われている「定番」の回避策であり、多くのライブラリやドキュメントで見かける形式です。
具体的な実装例とコード解説
実際の開発では、取得したディレクトリパスを元に、他のディレクトリにある設定ファイルや静的ファイルを読み込むケースが多くあります。
ここでは、path.join を利用して、安全にパスを結合する実践的なコード例を紹介します。
import path from 'node:path';
import fs from 'node:fs/promises';
// import.meta.dirname を使用した最新のパス構築
const configPath = path.join(import.meta.dirname, '..', 'config', 'settings.json');
async function loadConfig() {
try {
const data = await fs.readFile(configPath, 'utf8');
console.log("設定ファイルの読み込みに成功しました");
return JSON.parse(data);
} catch (err) {
console.error("エラーが発生しました:", err.message);
}
}
loadConfig();
上記の例では、 import.meta.dirname を起点として、一つ上の階層にある config フォルダ内のファイルを指定しています。
node:path モジュールを使用することで、Windows環境(バックスラッシュ \)とPOSIX環境(スラッシュ /)の違いを自動的に吸収してくれます。
パスを扱う際は、文字列の足し算ではなく必ず path.join や path.resolve を使用するようにしましょう。
CommonJSとESMにおけるパス指定の違い
開発を混乱させないために、それぞれのモジュールシステムにおけるパス関連変数の違いを比較表で確認しておきましょう。
| 機能 | CommonJS (CJS) | ECMAScript Modules (ESM) |
|---|---|---|
| ディレクトリパス | __dirname | import.meta.dirname (v20.11+) |
| ファイルパス | __filename | import.meta.filename (v20.11+) |
| パスの形式 | 絶対パス文字列 | URLオブジェクト (import.meta.url) |
| 利用準備 | 不要 (自動注入) | 不要 (最新版) または 手動変換 |
このように、ESMは一度書き方を覚えてしまえば、CommonJSよりも標準化された強力な機能を提供していることがわかります。
パス操作における注意点とベストプラクティス
Node.jsでパスを操作する際には、実行環境の違いに起因する予期せぬトラブルを防ぐためのポイントがいくつかあります。
まず第一に、OSによるパス区切り文字の違いを常に意識することです。
開発をMacで行い、デプロイ先がWindows ServerやLinuxコンテナである場合、ハードコーディングされたパス区切り文字はバグの温床となります。
前述の path モジュールを常に利用することで、この問題は解決可能です。
また、import.meta.url を直接操作して文字列置換でパスを作ろうとするのは推奨されません。
URLにはエンコードされたスペース(%20)などが含まれる可能性があり、単純な文字列操作では正しいファイルパスにならないためです。
必ず fileURLToPath を通すか、最新の import.meta.dirname を利用するように徹底してください。
さらに、相対パスを指定する際の基準点にも注意が必要です。
fs.readFile('./data.txt') のように記述した場合、基準となるのは「スクリプトがある場所」ではなく「コマンドを実行したカレントディレクトリ」になります。
意図したファイルを確実に読み込むためには、 import.meta.dirname を起点とした絶対パスを生成するのが最も安全な方法です。
まとめ
Node.jsのプロジェクトをESMへ移行する際、 __dirname が使えないことは大きな障壁に感じられるかもしれません。
しかし、Node.js v20.11.0以降では import.meta.dirname という非常にシンプルな代替手段が用意されています。
それ以前のバージョンであっても、 url モジュールの fileURLToPath を活用すれば、安全にパスを取得することが可能です。
「なぜ使えないのか」という背景にあるモジュールシステムの設計思想を理解することで、よりトラブルに強いコードを書くことができるようになります。
最新のNode.jsの機能を最大限に活用し、クリーンでメンテナンス性の高いディレクトリ管理を実現していきましょう。
