Fork me on GitHub

Deep Dive: Node.js에서 기본값 ESM으로 가는 길

Translator: rewrite0w0 Edit on GitHub See Revisions

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으로 해석하고 실행하는 방법에 대해 논의가 있었습니다.

  1. node 바이너리를 나눈다
    • 바이너리 관리 비용 발생한다
  2. package.json 생성할 때 type=module 넣어둔다
    • package.json 사용하지 않는 "스크립트" 문제, 튜토리얼을 다루는 글에서 설명 비용 발생한다
  3. 기본 해석을 변경하는 플래그 추가한다

등 옵션이 언급되었으며, 실험적인 기능으로 기본값 해석을 변경하는 --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로 합의되지 않고 논의 단계에서 종료되었습니다.

2016-2017년 단계에서, Node.js는 ESM 지원하는 방침으로 다음 3가지를 갖고 있었습니다

  1. .js 안을 보고서 CJS인가 ESM인가 판단
  2. package.jsontype 같이 특정 필드로 판단
  3. .mjs 같은 확장자로 판단

TC39 제안에서 1 관련한 이야기로, 당시(2016-2017년)는 진전되지 않았습니다.
Node.js 20 시점에서는 2와 3 구현이 되었으며, 이번 --experimental-default-type=module 은 2016-2017년 제안했던 1의 내용에 근접하다 생각됩니다.

Pull Request to this article
JSer.info Slackに参加する