◆ EsModules だと __dirname などモジュール実行時に自動で定義される変数が存在しない
◆ import.meta.url で URL として自身のファイルのパスが取得できる
◆ URL なので new URL(url).pathname でパスを取得してから dirname 関数を通す

Node.js 10 で Es Modules が実験的にですが使えるようになったらしいので使ってみました

EsModules


[test.mjs]
import http from "http"
import fs from "fs"
import path from "path"

$ node --experimental-modules test.mjs

実行時にフラグが必須なのと拡張子を mjs にしないといけないところに注意です
js 拡張子だと http のところで Unexpected identifier とエラーになりました
ブラウザでいう type="module" 忘れと同じで非モジュール扱いみたいですね

__dirname

ここさえ気をつければブラウザ側と同じ感じで EsModules のエクスポートインポートが使えると思ったのですが 意外なところで問題が起きました

console.log(__dirname)

これが __dirname が未定義というエラーになりました
Node.js では __dirname で今実行中のファイルが存在するフォルダのパスが取得できます
それが使えないです

--experimental-modules フラグを外して実行すると取得できていたので モジュールが原因みたいです
ロードの方法が変わったことで Node.js 固有の用意されていた変数が使えなくなったのでしょうか

require

これまでの require だと

(function (exports, require, module, __filename, __dirname) { });

こういう関数が作られて この関数の中身に require するコードがそのまま貼り付けられています
この関数が実行されるので モジュール内から __dirname などが使えていました

読み込み方法がこの方法とは違うのなら未定義なのもわかります
これまでは CommonJS から来ていた Node.js 独自のものでしたが EcmaScript の仕様にあわせるならこういう余計な変数を勝手に追加することはできないでしょうし

対処

でも今のファイルの場所がわからないのは不便です
調べてみると StackOverflow で import.meta を使えば良いと書いてる人がいました

console.log(import.meta)
{ url: 'file:///home/user/js/test.js' }

こういう情報が入っていました
なので dirname を作るには

const dirname = path.dirname(new URL(import.meta.url).pathname)
// /home/user/js

でできます

Web の機能なので url というプロパティ通り URL 形式でしか取得できないです
なのでローカルのものにするため URL の pathname を取得します
あとはフォルダ名にするために dirname 関数を通します

import はオブジェクトではなくインポートのためのキーワードなので meta プロパティにアクセスすればオブジェクトが取得できますが import 自体をオブジェクトのように扱ってもエラーになります
console.log もできません
super みたいなものです

JavaScript はクラスもなく全部オブジェクトという単純なものでよかったので こういう特殊なキーワードはやめてほしかったのですけどねー

import.meta を使うにせよ 毎回こういうのを書くのは結構面倒です

関数にしたい

import.meta のオブジェクトを渡せば dirname を返す関数を作るのは簡単です
でも import.meta というワードを書かずに済ませたいです
ですが ライブラリに dirname 関数を作るとすると そのライブラリの中で import.meta を参照してもライブラリのファイルのパスに対するものになります
その関数を呼び出したファイルのパスではありません

呼び出し元のパスというとエラー時の stack です
とりあえずこんなものを作ってみました

[test.mjs]
import {dirname} from "./dirname"

console.log(dirname())

[dirname.mjs]
import path from "path"

export function dirname() {
const stack = new Error().stack
const lines = stack.split("\n")
const idx = lines.findIndex(e => e.trim().startsWith("at dirname ")) + 1
if (idx) {
const matched = lines[idx].match(/(file:.+):\d+:\d+$/)
if (matched) return path.dirname(new URL(matched[1]).pathname)
}
return false
}

user@localhost ~> n/bin/node --experimental-modules js/test.mjs 
(node:7359) ExperimentalWarning: The ESM module loader is experimental.
/home/user/js

一応ちゃんと動きます……がちゃんとしたところでは使いたくないものです
まだ実験的なサポート段階ですし 簡単に扱える機能ができることを期待しましょう