본문으로 건너뛰기
개발 머꼬
개발 노트Node.js
hohyeon.dev22

type: module로 바꿨더니 __dirname is not defined가 난 이유

  • #Common Pitfall
  • #Engineering Note
  • #Node.js

문제 발생

package.json"type": "module"을 넣자 파일을 읽던 코드가 전부 죽었습니다.

ReferenceError: __dirname is not defined in ES module scope
const template = fs.readFileSync(path.join(__dirname, "template.html"), "utf8");

require로 JSON을 읽던 코드도 함께 깨졌습니다.

원인 분석

ES 모듈에는 그 변수들이 없습니다. Node.js 문서가 그대로 적습니다 — 이 CommonJS 변수들은 ES 모듈에서 사용할 수 없습니다. __filename__dirname의 용례는 import.meta.filenameimport.meta.dirname으로 대체할 수 있습니다.

이유는 설계 차이입니다. CommonJS 모듈은 함수로 감싸져 실행되면서 __dirname 같은 지역 변수를 주입받지만, ES 모듈은 그런 래퍼가 없습니다. 대신 표준이 정한 import.meta가 모듈 자신에 대한 정보를 제공합니다.

JSON 쪽도 규칙이 다릅니다. 문서가 명시합니다 — import 로 JSON을 참조할 때 with { type: 'json' } 문법은 필수입니다.

해결 방안

  1. 경로는 import.meta.dirname을 씁니다.
import path from "node:path";
const template = fs.readFileSync(path.join(import.meta.dirname, "template.html"), "utf8");
  1. URL 기반으로 읽는 방식이 더 낫습니다. 문서의 예제 형태이고, 경로 결합 실수를 줄여줍니다.
import { readFileSync } from "node:fs";
const buffer = readFileSync(new URL("./data.json", import.meta.url));
  1. JSON import에는 속성을 붙입니다.
import pkg from "./package.json" with { type: "json" };
  1. require가 필요하면 만들어 씁니다. 문서가 안내하는 방법입니다 — 필요하다면 module.createRequire()로 ES 모듈 안에서 require 함수를 구성할 수 있습니다.
import { createRequire } from "node:module";
const require = createRequire(import.meta.url);
  1. 반대 방향의 제약도 알아둡니다. 문서 설명대로 CommonJS의 require()top-level await를 쓰지 않는 동기 ES 모듈만 불러올 수 있습니다. 라이브러리가 top-level await를 쓰기 시작하면 CJS 소비자가 깨집니다.

  2. 전환은 패키지 단위로 합니다. 한 패키지 안에서 두 방식을 섞으면 확장자(.mjs/.cjs)와 조건부 export가 얽혀 디버깅이 어려워집니다.

공식 문서

마지막 수정

좋아요북마크

댓글0

아직 댓글이 없어요. 첫 의견을 편하게 남겨 보세요.