Node.js 21에는 --experimental-default-type=module
플래그로, JavaScript 파일 해석을 기본적으로 CJS(CommonJS)에서 ESM(ECMAScript Modules)로 변경할 수 있습니다.
이는, Node.js에서 JavaScript 파일(.js
)의 기본값을 ESM으로 변경하기 위한 첫 걸음입니다.
이번 Deep Dive에서는, Node.js에서 기본값 ESM로 인한 Issue나 구현에 대해 소개합니다.
Node.js에서 기본값 ESM
Discussion: New “ESM by default” mode · Issue #49432 · nodejs/node
이 Issue는, Node.js에서 ambiguous file 해석을 CJS(CommonJS)에서 ESM(ECMAScript Modules) 바꾸지는 Discussion Issue입니다.
Ambiguous file(애매한 파일)이란, 이하와 같은 정의입니다.
.js
파일 또는package.json
에서type
정의가 안되어있는 것.mjs
는 ESM에서도 동작하므로 ambiguous file가 아님
node --eval
같이 문자열 입력으로 되어있으면서,--input-type
가 지정되지 않음
지금 Node.js는, 애매한 파일을 CommonJS으로 해석하고 실행합니다.
Issue에서는, 애매한 파일을 ESM으로 해석하고 실행하는 방법에 대해 논의가 있었습니다.
node
바이너리를 나눈다- 바이너리 관리 비용 발생한다
- package.json 생성할 때
type=module
넣어둔다- package.json 사용하지 않는 "스크립트" 문제, 튜토리얼을 다루는 글에서 설명 비용 발생한다
- 기본 해석을 변경하는 플래그 추가한다
등 옵션이 언급되었으며, 실험적인 기능으로 기본값 해석을 변경하는 --experimental-default-type
가 Node.js 21에 구현되었습니다.
esm: --experimental-default-type
flag to flip module defaults by GeoffreyBooth · Pull Request #49869 · nodejs/node
이 PR에서는, node --experimental-default-type=module
에서 실행하는 경우, ambiguous file(애매한 파일)을 ESM으로 해석하고 실행하는 플래그가 구현되었습니다.
📝 이 플래그는 Node.js 21.0.0에 포함되었습니다.
When to make --default-type=module
the Node.js default · Issue #1445 · nodejs/TSC
이 Issue는 --experimental-default-type=module
가 Node.js 21에 구현됨에 따라, Node.js 21를 출시할 때, 이를 언제 기본값으로 하는가 앞으로의 방향성에 대해 관한 Issue입니다.
Issue에서는, --experimental-default-type=module
플래그만으로는, 엄청나게 파괴적인 변경으로 인한 혼란을 야기한다 지적하고 있습니다.
가령, node_modules/
이하 패키지 기본 해석을 ESM으로 바꾸면 동작하지 않은 패키지가 많으며, 이미 유지보수 관리되지 않은 패키지도 있다는 문제가 지적됩니다.(애플리케이션과 패키지 제작자가 달라 패치할 수 없어지는 문제)
이에, 기본 해석을 바꿀 뿐인 --experimental-default-type=module
플래그만으로는, 이행 과정이 부족한 문제가 있습니다. 이 0 아니면 1 문제는 다음 Issue에서 다뤄집니다.
Proposal: Set --experimental-default-type
mode by detecting ESM syntax in entry point · Issue #50043 · nodejs/node
이 Issue에서는, node_modules/
이하 파일이 CJS인가 ESM인가 판단하는 --experimental-detect-module
같은 플래그가 제안되었습니다.
esm: detect ESM syntax in ambiguous JavaScript by GeoffreyBooth · Pull Request #50096 · nodejs/node
이 PR에는, --experimental-detect-module
가 구현되었습니다.
--experimental-detect-module
구현은 node_modules/
이하 애매한 파일 안에 ESM 구문이 포함되어 있으면 ESM으로 다루고, 그렇지 않으면 CJS으로 다룹니다.
ESM 구문은 import
/ export
/ import.meta
등을 정적으로 해석할 수 있으면 V8으로 판정합니다.( import()
는 CommonJS에서도 이용되기에, ESM 구문으로 치지 않습니다)
지금은 기본값이 CJS이므로, ambiguous file에 ESM 구문이 포함되어 있지 않으면 실행할 때 에러가 됩니다(acorn 사용해 ESM 구문이 있는가 없는가 판정합니다)
Node.js 20에서 ESM 구문을 포함하는 CJS을 싱핼할 때 에러는 이렇게 됩니다.
import lodash from "lodash"
^^^^^^
SyntaxError: Cannot use import statement outside a module
이에, ESM 구문을 포함하는 CJS가 없다는 전제가 가능해, --experimental-detect-module
는 파괴적 변경없이 도입할 수 있지 않겠냐는 이야기입니다.(기존에 실행되던 것이 실행되지 않는 일이 없기에)
이 접근의 단점은 파일 안을 보고서 ESM인가 판단하기에 성능이 나빠지는 점입니다.
맺는말
여전히 Node.js에서 .js
파일을 ESM로 다룰 수 있는가 어떤가에 대한 방법은 확정되지 않았습니다.
개발 버전인 Node.js 21에서 --experimental-*
으로 플래그 구현하면서, 호환성 문제가 없으면서 성능 문제도 없도록 진행되고 있다 생각됩니다.
관련 글
tc39/proposal-UnambiguousJavaScriptGrammar
2016년부터 2017년까지, Node.js가 TC39(ECMAScript 사양 정하는 그룹)에서, 파일 안을 보고서 Script인가 Module인가 판단하는 Proposal가 있었습니다.
여기서 Script는 CommonJS, Module은 ESM입니다.
이 애매한 파일 판단하는 방법이 import
혹은 export
가 파일에 포함되어있는가 였습니다(또한 "use module"
같이 Directive스러운 이야기도 있었습니다)
이 제안은, ECMAScript 사양이 아니라 플랫폼(브라우저나 Node.js)에서 해야할 일이었으므로, TC39로 합의되지 않고 논의 단계에서 종료되었습니다.
- https://github.com/tc39/notes/blob/main/meetings/2016-11/nov-30.md#12iia-proposal-to-reform-the-spec-to-solve-nodejs-ecosystem-compatibility-breaks-w-es-modules
- https://github.com/tc39/notes/blob/afc1eacf01c5374a1a55cdd6ae00f82fa291d4b8/meetings/2017-01/jan-25.md#13iia-proposed-grammar-change-to-es-modules
2016-2017년 단계에서, Node.js는 ESM 지원하는 방침으로 다음 3가지를 갖고 있었습니다
.js
안을 보고서 CJS인가 ESM인가 판단package.json
의type
같이 특정 필드로 판단.mjs
같은 확장자로 판단
TC39 제안에서 1 관련한 이야기로, 당시(2016-2017년)는 진전되지 않았습니다.
Node.js 20 시점에서는 2와 3 구현이 되었으며, 이번 --experimental-default-type=module
은 2016-2017년 제안했던 1의 내용에 근접하다 생각됩니다.