TL;DR 概要
- Node.jsのCommonJSモジュールでよく使われるグローバル変数である__dirnameと__filenameは、ESモジュール(ESM)では利用できません。これは、CommonJSからESMに移行する開発者にとって一般的なエラーの原因です。
- 正しいESMの代替は、import.meta.urlをurlモジュールのfileURLToPathとpath.dirname関数と組み合わせて、同等のディレクトリパスを再構築することです。
- Node.js用のSonarQubeルールは、ESMコンテキストでの__dirnameの使用を検出し、正しい最新の代替を提案し、CommonJSからESMへの移行中のランタイムエラーのクラスを防ぎます。
- ESMがNode.jsおよびフロントエンドJavaScriptの標準モジュールシステムになるにつれて、これらの同等物を理解することは、現代のJavaScriptアプリケーションを構築する開発者にとって重要な知識です。
ECMAScriptモジュール(またはESモジュール)は、JavaScriptコードを再利用するための新しい標準フォーマットです。Node.jsの世界では、CommonJSからESモジュールへの大きな移行が進行中ですが、その過程で摩擦がありました。
その摩擦の一部が最近取り除かれました。現在のモジュールのディレクトリにアクセスすることが再び簡単になりました!
TL;DR
ESモジュールでは、__dirnameや__filenameの代わりに、次のように使用できます:
クリップボードにコピー
import.meta.dirname // 現在のモジュールのディレクトリ名 (__dirname)
import.meta.filename // 現在のモジュールのファイル名 (__filename)
興味があるなら、この話にはもっと続きがありますので、読み進めてください。
現在のディレクトリを取得する
現在のモジュールのディレクトリパスにアクセスすることで、コードが配置されている場所に相対的にファイルシステムを移動し、プロジェクト内のファイルを読み書きしたり、コードを動的にインポートしたりできます。この情報へのアクセス方法は、CommonJSの実装からESモジュールへの最新の更新まで、年々変化してきました。どのように進化してきたかを見てみましょう。
古いCommonJSの方法
Node.jsは最初にCommonJSモジュールシステムを使用しました。CommonJSは、現在のモジュールのディレクトリ名とファイル名を返す2つの変数を提供しました。それらの変数は__dirnameと__filenameでした。
クリップボードにコピー
__dirname // 現在のモジュールのディレクトリ名
__filename // 現在のモジュールのファイル名
古いESモジュールの方法
__dirnameと__filenameはESモジュールでは利用できません。代わりに、それらを再現するために次のコードが必要でした:
クリップボードにコピー
import * as url from 'url';const __dirname = url.fileURLToPath(new URL('.', import.meta.url));
const __filename = url.fileURLToPath(import.meta.url);
このボイラープレートコードを覚えることができず、いつも__dirnameを取り戻す方法についてSam Thorogoodの説明を探していました。もっと簡単な方法があるはずです。
新しいESモジュールの方法
ついに、多くの議論の末、より良い方法が登場しました。Node.jsバージョン20.11.0、Denoバージョン1.40.0、およびBunバージョン1.0.23以降、import.metaオブジェクトのdirnameおよびfilenameプロパティを呼び出すことができます。
クリップボードにコピー
import.meta.dirname // 現在のモジュールのディレクトリ名
import.meta.filename // 現在のモジュールのファイル名
どのようにここにたどり着いたのか?
この記事の冒頭で書いたように、ESモジュールはJavaScriptの標準です。しかし、JavaScriptはもともとウェブブラウザで実行される言語として生まれました。Node.jsはサーバーでJavaScriptを実行することを普及させましたが、多くの慣習を使用または発明する必要がありました。Node.jsプロジェクトが初期に行った選択の一つは、CommonJSモジュールシステムとそれに付随するすべてを採用することでした。
ESモジュールは、ブラウザとサーバーの両方の環境を考慮して設計されました。ブラウザは通常、ファイルシステムへのアクセスを持たないため、現在のディレクトリやファイル名へのアクセスを提供することは意味がありません。しかし、ブラウザはURLを扱い、ファイルパスはfile://スキームを使用してURL形式で提供できます。したがって、ESモジュールにはモジュールのURLへの参照があります。すでに上記で見たように、import.meta.urlとして存在します。Node.jsでURLを使用して何ができるかを見てみましょう。
どこでもURL
次のコードを持つmodule.jsというESモジュールを考えてみましょう:
クリップボードにコピー
console.log(import.meta.url);
このファイルをNode.jsを使用してサーバーで実行すると、次の結果が得られます:
クリップボードにコピー
$ node module.js
file:///path/to/module.js
このmodule.jsをウェブブラウザで読み込むと、次のように表示されます:
クリップボードにコピー
https://example.com/module.js
どちらの結果もURLですが、コンテキストに基づいて異なるスキームを持っています。
少し混乱を招くかもしれませんが、import.meta.urlはURLを表す文字列であり、実際にはURLオブジェクトではありません。この文字列をURLコンストラクタに渡すことで、実際のURLオブジェクトに変換できます:
クリップボードにコピー
const fileUrl = new URL(import.meta.url);
console.log(url.protocol);// Node.js: "file:"
// Browser: "https:"
そして、ここからNode.jsでの__dirnameと__filenameの元の代替が生まれました。URLオブジェクトを使用すると、Node.jsのURLモジュールを使用してモジュールのURLをファイルパスに変換し、__filenameを再現できます。
クリップボードにコピー
import * as url from "url";
const fileUrl = new URL(import.meta.url);
const filePath = url.fileURLToPath(fileUrl);
console.log(filePath);// /path/to/module.js
また、URLを操作してディレクトリ名を取得し、__dirnameを再現することもできます。
クリップボードにコピー
import * as url from "url";
const directoryUrl = new URL(".", import.meta.url);
const directoryPath = url.fileURLToPath(directoryUrl);
console.log(directoryPath);// /path/to
文字列の代わりにURLを使用できます
Node.js内で一般的なファイル操作を行うためにパス文字列を使用する必要があると思うかもしれません。実際には、多くのNode.js APIは文字列パスで動作するだけでなく、URLオブジェクトでも動作します。
__dirnameの最も一般的な使用法は、データファイルを読み込むためにディレクトリを移動することです。たとえば、module.jsファイルがdata.jsonというファイルと同じディレクトリにあり、スクリプトにデータを読み込みたい場合、以前は__dirnameを次のように使用していました:
クリップボードにコピー
const { join } = require

