Quick Reference
현재 ESM 모듈 옆 파일은 new URL("./file", import.meta.url)로 가리킵니다. 대부분의 node:fs API와 Worker는 file: URL 객체를 직접 받을 수 있고, path API처럼 문자열 경로가 필요한 곳에서만 fileURLToPath()로 변환합니다.
import { readFile } from "node:fs/promises";
import { fileURLToPath } from "node:url";
import { dirname, join } from "node:path";
const configUrl = new URL("./config/default.json", import.meta.url);
const text = await readFile(configUrl, "utf8"); // fs는 file: URL을 받음
const currentFile = fileURLToPath(import.meta.url); // path API가 필요할 때만 변환
const configPath = join(dirname(currentFile), "config", "default.json");
console.log(text, configPath);파일 기준점
먼저 머릿속에 잡아 둘 기본형은 아래입니다.
- 현재 모듈 위치:
import.meta.url - URL -> path:
fileURLToPath(import.meta.url) - 현재 디렉터리 계산:
dirname(...) - 최신 단축형:
import.meta.dirname,import.meta.filename - 모듈 해석 결과:
import.meta.resolve(...)
ESM의 기준 위치는 process.cwd()가 아니라 import.meta.url이다
실행 위치가 어디든 현재 모듈 옆 파일을 읽고 싶다면 process.cwd()로 가면 안 됩니다. 이때는 현재 파일 기준 경로를 잡습니다.
import.meta.url은 파일 경로가 아니라 URL이다
file:///... 형태라 path.join()에는 바로 넣을 수 없습니다. 다만 대부분의 node:fs API는 file: URL 객체를 직접 받으므로, new URL("./data.json", import.meta.url)를 그대로 전달해도 됩니다. 문자열 path가 필요한 API에서만 fileURLToPath()로 변환합니다.
Node 20.11+면 더 짧아질 수 있다
최신 Node에서는 import.meta.dirname, import.meta.filename가 있어 예전 변환 패턴을 짧게 줄일 수 있습니다. 이 값들은 20.11+에서 도입됐고 22.16+/24.0+부터는 더 이상 experimental이 아니므로, 팀 런타임 버전을 같이 보는 편이 정확합니다.
모듈 해석이 필요하면 import.meta.resolve()를 쓴다
현재 파일 옆 경로 계산과, 패키지 해석 결과를 얻는 일은 다릅니다. 후자는 import.meta.resolve()가 더 정확합니다.
현재 파일 기준 경로와 패키지 해석을 섞지 않는다
./data.json처럼 모듈 옆 파일을 찾는 일과, "some-package/subpath"를 해석하는 일은 문제 종류가 다릅니다.
전자는 import.meta.url, 후자는 import.meta.resolve()로 나눠 읽는 편이 덜 헷갈립니다.
언제 URL을 쓰나
체크포인트
- 현재 모듈 위치:
import.meta.url - 파일 경로 변환:
fileURLToPath(import.meta.url) - 디렉터리 계산:
dirname(...) - 최신 Node 단축형:
import.meta.dirname - 패키지 해석 결과:
import.meta.resolve(...) - 현재 파일 기준 경로와 패키지 해석을 분리해서 본다
주의할 점
import.meta.url은 문자열처럼 보여도 파일 경로가 아니라 URL이다. path API에는 넣지 말고, fs에는 URL 객체를 직접 넘길 수 있는지 먼저 확인한 뒤 문자열 경로가 필요할 때만 변환한다.
참고 링크
2 sources