ESM? CJS? In NodeJS

Wednesday, Jan 9, 2019

동기

EcmaScrit Module 가 explorer 를 제외한 메이저 브라우저에서 모두 지원이 되는 마당에 코드 재활용성 문제도 있고 해서 ESM 스타일로 일부 코드가 작성되었을 경우 nodejs 프로젝트에서도 사용하고 싶게 되었다.
물론 nodejs 의 고유기능들을 사용하려면 (package.js관련된것이나, 배터리 패키지들) ESM 스타일로 코드를 짠들 브라우저 호환성을 보장 할 수 없지만 전체 코드베이스에서 이런 디펜던시를 가지는 파일들이 몇개나 되는지 생각해보면 적용 할 가치는 충분하다고 생각하고, 시간이 지날수록 더욱 중요해 질것이라고 기대한다.

javascript 호환성 관련 자료
비슷한 주제의 읽을거리

방법

여러가지 방법이 있지만 시도해본 메이져한 방법들 각각의 장점, 한계점에 대해 이야기를 해 보겠다.

1. babel build

아예 프로젝트 전체를 트랜스파일링 해서 ES5 나 ES6 로 빌드해버리는 방법

장점

  • nodejs 뿐만 아니라 브라우저에서의 동작도 보장할 수 있다.

단점

  • 빌드시간이 오래걸린다.
  • 서드파티 개발자가 디버깅할때 번거로울 수 있다.

2. nodejs native

nodejs native ESM 호환 기능을 사용함 –experimental-modules

장점

  • 별도의 빌드 과정을 만들고 관리하지 않아도 됨
  • 있는파일 그대로 가지고 디버깅 할 수 있음

단점

  • 낮은버전의 nodejs 에서 동작을 보장하지 않음.
  • 마찬가지로 브라우저에서도 그대로 사용 할 수 없음.
  • esm 형태로 작성된 파일은 무조건 .mjs 확장자를 사용해야함.
  • mjs 파일 내에서는 require 를 사용 할 수 없음.
    • 내부적으로 캐싱이 따로 되기 때문에 require 캐시를 이용하여 싱글톤 패턴처럼 사용한 경우에는 프로그램이 의도하지 않은 동작을 할 수 있음 참고
  • babel 로 빌드된 모듈을 import 하거나 클래스를 상속받을때 오동작함

3. esm

장점

  • mjs 확장자를 강요당하지 않음
  • 기존에 쓰던 테스트코드를 그대로 사용 할 수 있음
  • zero configuration
  • 별도의 빌드 과정을 만들고 관리하지 않아도 됨
  • (사용하고 싶다면) ESM파일 내에서 require 도 섞어서 쓸 수 있음. (… 근데 왜?)

단점

  • babel 로 빌드된 모듈을 import 하거나 클래스를 상속받을때 오동작함
  • 파일이 nodejs 디펜던시가 있는지 없는지 분간하기 어려움 (개발자가 파일에 명시된 디펜던시를 체크해야함)
    • 즉, 웹브라우저에서 쓸수있는 파일이 어떤것인지 명시적이지 않음.

4. @babel/cli 에 포함된 babel-node

장점

  • esm 과 비슷한 편의성, 빌드관리 하지 않아도 됨
  • 메모리를 더 먹는것 같음
  • babel 로 빌드된 모듈들을 별다른 설정없이도 잘 읽어들임
  • mjs 확장자를 강요당하지 않음

단점

  • 빌드시간이 오래걸린다.
  • 서드파티 개발자가 디버깅할때 번거로울 수 있다.

추가설명

nodejs –experimental-modules, esm 에서 babel로 빌드된 패키지들이 발생시키는 문제

구체적으로 설명하자면 babel로 빌드된 패키지들을 읽어올때 default export 를 제외한 export 를 제대로 인식하지 못한다. 또, 빌드된 class 들의 constructor 안에서 super() 를 콜하더라도 전혀 동작하지 않는것으로 보였다.

결론

TL;DR;

  • 사용하는 라이브러리가 babel로 빌드되어있으면 당분간은 babel 사용이 좋음
    • 빌드 해야하면 babel 설정
    • 그냥 실행만 시키고 싶으면 @babel/cli 설치후 babel-node 로 실행
  • 사용하는/할 라이브러리가 앞으로도 babel이랑 관계가 없을것으로 기대한다면
    • mjs 확장자를 싫어하면 esm,
    • 써도 상관없다면 nodejs 의 –experimental-modules 를 사용

고찰

Broken system

esm 이나 nodejs 자체 옵션을 이용하여 얼마든지 ESM 을 사용 할 수 있음. 그러나 이건 어디까지나 ESM 으로 작성된 파일을 사용하는데 국한된 이야기이고 babel로 빌드된 ESM 모듈을 사용하려면 결국 babel 을 사용하는게 정신건강에 이로워보임
브라우저에서 최신 javascript 스펙들을 지원하기 시작한 이래로 ES6^ 의 이용이 급속도로 전파되고 있는 현상이 commonjs 를 사용하는 Node.js 커뮤니티를 지속적으로, 점점 더 심하게 괴롭힐것으로 예상된다.
babel을 사용하고 있는 골드가 되어버린 레거시들이 너무 많은데다 안정된 자리를 차지하고 있고 TypeScript를 사용자들의 입장도 있기 때문에 새로운 커뮤니티의 합의를 이루는것은 점점 더 어려워졌다고 본다. 앞으로는 불가능하다고 보는게 옳을것이다.

좋을것은 없다.

이런 상황이 Node.js에게 위기가 될것이냐에 대해선 의문이긴 하다. 이미 Node.js 는 충분한 가치를 제공하고 있고 브라우저에서 사용되는 javascript가 commonjs 와 다름에 대해선 선을 그어놓고 있었기 때문에 혼란을 줄 염려도 적다.
하지만 범용으로 쓰이고 싶은 라이브러리들이 ESM을 공격적으로 받아들이고 있는데다 절대 다수의 프로젝트들이 관리상의 이유로 한가지 빌드만 만들어 npm에 배포시키고 있다. bower 가 죽어가는 마당에 이런 괴리는 점점 심해질것이고, 이런 괴리는 Node.js 의 입지에 악영향을 줄것이다.

How to solve

이 문제는 Node.js 가 아니라 npm 으로 풀어야 할 문제인것 같다. 이미 –experimental-modules 나 esm 과 같은 방법으로 유저들은 Node.js 에서 ESM 을 사용하고 싶은 욕망과 그에대한 답변을 들었으며 이를 실무와 유리시키고 있는것이 트랜스파일링 이라고 본다.
잠깐 딴 이야기를 하자면 내가 타입스크립트를 좋아하지 않는 이유중에 하나가 type.d 파일의 존재이기도 하다. 아주 거추장스럽고 모든 서드파티들이 신경써준다는 보장도 없다. 버전별로 관리가 될것이라고 기대하는것은 당연히 오산이다.
이에 나는 npm 이 flavor 별로 배포를 다양화 할 수 있는 방법을 제공해야 한다고 생각한다.
ts, esm, cjs, 지금은 자취를 많이 감춘 coffeescript 등으로 배포를 할 수 있게 만들어야 한다. 일단 그렇게 된다면 앞서 이야기한 babel이 필요없음에도 사용을 강요당한다거나 type.d 파일을 따로 배포한다거나 하는 요상한 일이 일어나지 않을것이고 어떤 패키지가 어떤 환경을 지원하는지 구분하기도 쉬워지고, 때론 이것들이 뒤섞여서 발생하는 오동작이나 어려워지는 테스팅문제가 일부 해결 될 수도 있다.

맺으며

Node.js 를 2014년부터 써왔지만 드디어 다 긁어먹고 바닥이 보인다는 느낌이다.
내 자신감과는 별개로 내자신에 대한 나의 평가는 남들이 생각하는것보다 박한편인데도 불구하고, 이제는 어디가서 expert 라고 이야기를 해도 되지 않을까 하는 생각마저 최근 들고 있다.
그래도 이래저래 편하게 쓰고있는 플랫폼인데 롱런했으면 좋겠다.

comments powered by Disqus