Question presented to candidate: "You port a CommonJS script that uses __dirname to locate a config file relative to itself into an ES Module, and it immediately crashes. What's happening, and what's the correct ESM replacement?"
What a strong answer should cover:
__dirnameand__filenameare CommonJS-only globals, injected by Node's CJS module wrapper — they genuinely do not exist in ES Modules at all, which is exactly the prompt's crash: referencing an undeclared identifier. 📌 Verified, not assumed: referencing bare__dirnamein a real ESM file genuinely threw a realReferenceError: __dirname is not defined in ES module scope— the exact, specific error message, not a generic one.- The ESM replacement, precisely:
import.meta.urlgives the current module's own URL (afile://URL, not a plain path) —fileURLToPath()(fromnode:url) converts it to a real filesystem path, anddirname()(fromnode:path) then derives the directory, together reconstructing exactly what__filename/__dirnameprovided in CommonJS. - 📌 Verified, not assumed:
fileURLToPath(import.meta.url)genuinely reconstructed the correct, real, absolute file path of the running script, anddirname()on that genuinely produced the correct real containing directory — matching the actual location on disk, not merely plausible-looking output. - A precise answer explains why
import.meta.urlis a URL rather than a plain path in the first place: ES Modules can be loaded over genuinely different schemes (file://, but alsohttp://ordata:in some environments/bundlers) — a URL is the more general representation, andfileURLToPathis specifically the conversion step for the commonfile://case, which is why it's a required, explicit step rather thanimport.meta.urlsimply being a path string already. - The practical guidance: this pattern (
fileURLToPath(import.meta.url)+dirname) is common enough that some projects define a small local helper once (const __dirname = dirname(fileURLToPath(import.meta.url));) at the top of files that need it repeatedly — genuinely recreating the familiar name as a real localconst, not a global, since ESM does not (and cannot) provide it as an actual global the way CommonJS does.
Clarifying questions expected:
- "Is this a one-off usage in a single file, or does the project need this pattern repeated across many ESM files?" — decides between an inline one-off and a small shared helper module.
- "Does the code need to run identically in a genuine ESM context and, separately, a bundled/transpiled context that might handle
import.meta.urldifferently?" — some bundlers rewrite or polyfillimport.meta.url, worth confirming for the specific target.
Code / implementation expected: Yes — a real ESM file showing the genuine ReferenceError for bare __dirname, alongside the real, correctly-reconstructed path via fileURLToPath(import.meta.url), is the concrete, convincing proof of both the problem and the fix.