라떼군 이야기
Node.js 22에서 JSON import 시 'Unexpected identifier assert' 에러 원인과 해결 방법
Problem
Node.js 버전을 v21에서 v22로 업그레이드한 후, 기존에 잘 실행되던 애플리케이션이 갑자기 크래시되는 현상을 겪을 수 있습니다. 특히 ESM(ECMAScript Modules) 환경에서 JSON 설정 파일을 불러올 때 SyntaxError: Unexpected identifier 'assert'라는 에러가 발생합니다. 이는 기존 코드에 작성된 import ... assert { type: "json" } 구문에서 assert 키워드를 Node.js v22가 더 이상 인식하지 못하기 때문에 발생하는 문제입니다.
Background
이 에러의 근본적인 원인은 ECMAScript 명세(Spec)의 변경에 있습니다. 기존에는 JSON 등의 모듈을 불러올 때 그 타입을 명시하기 위해 ‘Import Assertions(가져오기 단언)‘이라는 기능과 함께 assert 키워드를 사용했습니다. 하지만 명세가 발전하면서, 단순히 타입을 검사(assert)하는 것을 넘어 모듈의 동작 방식을 변경할 수 있는 ‘Import Attributes(가져오기 속성)’ 개념으로 확장되었습니다. 이에 따라 키워드 역시 assert에서 with로 변경되었습니다. Node.js는 이러한 표준 스펙 변화를 반영하여 v22부터 기존의 assert 키워드 지원을 완전히 제거했습니다.
Solution
이 문제를 해결하기 위해 프로젝트의 요구사항(지원해야 하는 Node.js 버전 등)에 따라 세 가지 방법을 선택할 수 있습니다.
1. 최신 문법 적용 (Node.js 18.20 이상 권장)
가장 간단하고 권장되는 해결책은 assert 키워드를 with로 변경하는 것입니다.
// 기존 코드 (Node.js 22에서 에러 발생)
// import config from "./some-config-file.json" assert { type: "json" };
// 수정된 코드: assert를 with로 변경
import config from "./some-config-file.json" with { type: "json" };
이 방법은 코드가 간결하며 모듈 캐싱의 이점을 그대로 누릴 수 있습니다. 단, with 키워드는 Node.js 18.20 버전부터 추가되었으므로, Node.js 16이나 17 버전을 지원해야 하는 환경에서는 사용할 수 없습니다.
2. 하위 호환성을 위한 createRequire 사용 (캐싱 지원)
구버전 Node.js(v12.2.0 이상)와의 호환성을 유지하면서도 모듈 캐싱 기능을 활용하고 싶다면, node:module의 createRequire를 사용하는 것이 좋습니다.
import { createRequire } from 'node:module';
// 현재 모듈의 URL(import.meta.url)을 기반으로 require 함수 생성
const require = createRequire(import.meta.url);
// require를 사용하여 JSON 파일 로드 (자동으로 파싱 및 캐싱됨)
const config = require('./some-config-file.json');
이 방식은 import와 마찬가지로 한 번 불러온 JSON 파일을 메모리에 캐싱하므로 성능상 유리하며, 폭넓은 Node.js 버전을 지원합니다.
3. 파일 시스템(fs)을 이용한 직접 파싱
import나 require를 사용하지 않고 파일 시스템 모듈을 통해 직접 읽어오는 방법도 있습니다.
import fs from 'node:fs';
import { dirname, join } from 'node:path';
import { fileURLToPath } from 'node:url';
// ESM 환경에서 __dirname(현재 디렉토리 경로) 구현하기
const __dirname = dirname(fileURLToPath(import.meta.url));
const configPath = join(__dirname, './some-config-file.json');
// 동기적으로 파일을 읽고 JSON으로 파싱
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
이 방법은 호환성이 매우 뛰어나지만, 코드가 길어지고 한 번 읽은 파일이 캐싱되지 않아 매번 파일을 새로 읽는다는 단점이 있습니다.
Deep Dive
JSON 파일을 불러올 때 고려해야 할 중요한 요소 중 하나는 ‘모듈 캐싱(Module Caching)‘입니다. import 구문이나 require 함수를 사용하여 JSON을 불러오면, Node.js는 해당 파일을 한 번만 읽고 파싱한 뒤 메모리에 캐싱합니다. 따라서 여러 파일에서 동일한 JSON을 불러와도 성능 저하가 발생하지 않습니다. 반면 fs.readFileSync를 사용하면 호출할 때마다 디스크 I/O와 파싱 비용이 발생합니다. 만약 하위 호환성 때문에 fs 모듈을 사용해야 한다면, JSON을 읽고 파싱하는 로직을 별도의 싱글톤(Singleton) 모듈로 분리하여 애플리케이션 생명주기 동안 단 한 번만 실행되도록 최적화하는 것이 프로덕션 환경에서의 베스트 프랙티스입니다.
Conclusion
Node.js v22에서 발생하는 Unexpected identifier 'assert' 에러는 ECMAScript 표준의 변화에 따른 자연스러운 전환 과정입니다. 현재 개발 중인 애플리케이션이 최신 Node.js 환경에서만 동작한다면 with 키워드를 사용하는 것이 가장 깔끔하며, 다양한 Node.js 버전을 지원해야 하는 오픈소스 라이브러리나 레거시 시스템을 다루고 있다면 createRequire 방식을 채택하는 것을 권장합니다.