자바스크립트 개발을 하다 보면 가끔씩 require문을 사용하는 코드들을 볼 수 있습니다. 프론트엔드가 아닌 백엔드를 개발하시는 분이라면 어쩌면 import보다 require에 익숙하실 수도 있습니다. 웹개발을 시작한지 오래되지 않은 프론트엔드 개발자로서, ESMdoules이 다른 파일의 코드를 가져오는 당연한 방식이라고 생각할 때도 있었는데요. ES Module은 어떻게 등장하게 되었는지, 왜 옛 코드에는 아직도 require가 남아있고, 왜 아직도 ES Module이 아닌 CommonJS를 쓰려 하는지에 대해 포스팅하였습니다.
먼저 ES Modules이란 무엇인가에 대해 설명드려야 할 것 같습니다. ES Modules은 2015년에 ECMAScript6에서 자바스크립트 모듈 시스템으로 정식으로 추가된 기능인데요. ES Modules가 등장하기 전, CommonJS, AMD 등 브라우저 환경에서 혼재되어 사용되던 모듈시스템을 통합하기 위해 표준으로 지정되었습니다. ES Modules은 이제는 너무나 익숙한 방식이지만 사실 타입스크립트는 4.7버전, NextJS는 12버전부터 ES Module을 지원하기 시작했을만큼 얼마되지 않은 모듈 시스템입니다.
그런데 왜 지금도 많이 사용되는 CommonJS를 표준으로 지정하지 않고 새로운 모듈 시스템을 만든걸까요?
초창기 자바스크립트의 모듈시스템 문제를 해결하기 위해 등장한 CommonJS는 사실 ServerJS라는 이름의 프로젝트를 시작했을 만큼 서버를 위한 모듈시스템이었습니다. (서버 사이드 자바스크립트에 필요한 것)
ES Module과 CommonJS의 가장 큰 차이점은 비동기적으로 동작하는 ES Module과는 달리, CommonJS는 동기적으로 동작한다는 점인데요. 서버에서는 파일시스템으로 빠르게 모듈을 불러올 수 있었기 때문에 안정성을 위해 실행 전에 모든 의존성을 불러오고 있었습니다. 때문에 동기적으로 동작하는 CommonJS는 서버에서는 문제가 없었지만 브라우저에서 사용할 때 문제점이 발생하게 됩니다. 로컬 디스크에 모든 디펜더시를 가지고 파일시스템 I/O를 통해 빠르게 필요한 모듈을 호출하는 서버와는 달리, 브라우저는 네트워크를 통해 필요한 모듈을 다운로드 하고 나서야 사용할 수 있었습니다. 그리고 이때 발생하는 동기적인 로딩이 성능과 사용자의 경험에 치명적인 영향을 끼치게 됩니다.
이 문제를 해결하기 위해 브라우저 환경을 위한 AMD(Asynchronous Module Definition) 라는 모듈 시스템이 등장했는데요. 비동기적 모듈 선언이란 뜻처럼, 비동기 방식을 사용하는 모듈 시스템입니다. 모듈과 의존성을 정의하고 콜백함수를 통해 필요한 모듈을 병렬적으로 불러오는 방식이었습니다. 덕분에 비동기 문제를 해결하고 더 쉬운 디버깅 환경을 만드는 등의 장점이 있었습니다. 하지만 AMD는 CommonJS와 완벽히 호환되지 않았고, 이런 환경 속에서 두 모듈을 호환할 수 있는 UMD 패턴이 등장하기도 하였습니다.
그리고 이런 여러가지 모듈 시스템이 혼재되어 있는 상황을 해결하기 위해, ESCMAScript 6에서 ES Module이 표준 모듈 시스템으로 명세되었습니다.
ES Module은 import/export라는 직관적인 구문과 정적구조를 통해 사용성과 성능 향상을 이끌어냈는데요.
모듈이 런타임 시점에서 동적으로 할당되는 CommonJS와 달리, ES Module은 정적 분석을 통해 런타임 이전에 의존성 그래프를 만들어주었습니다.
덕분에 별도로 모듈의 의존성이나 불러오는 순서를 정의하지 않아도 되었을 뿐만 아니라, 이 시점에서 사용되지 않는 코드를 정리해주는 트리쉐이킹이나 코드스플리팅을 진행할 수 있다는 장점이 있었습니다.
기존에 사용되던 대부분의 라이브러리가 CommonJS를 기반으로 만들어졌습니다. 프로젝트에서 중요한 역할을 하는 모듈을 포함하여 npm에 게시된 수백만 개의 모듈이 이미 CommonJS를 사용하고 있습니다. ES Module을 지원하는 라이브러리가 많아지고 있지만, 이 많은 패키지가 모두 그렇게 될 수는 없습니다.
ES Module은 브라우저를 위해 비동기로 구현된 반면, 앞서 이야기했던 것 처럼 CommonJS는 서버 환경을 중점적으로 설계되었습니다. ES Module은 정적 분석 과정을 거치며 import를 export에 바인딩되는데요. 브라우저 환경에서는 트리쉐이킹 등으로 인해 네트워크로 불러와야 할 용량이 줄어들어 높은 성능 향상을 이끌어내지만, 파일 입출력으로 코드를 불러오는 서버사이드 환경에서는 상대적으로 성능 향상이 크지 않습니다. 오히려 이 과정으로 인해 설계상 CommonJS보다 느릴 수 밖에 없기 때문에, 대규모 애플리케이션 환경에서는 더 느린 성능을 보여주게 됩니다.
출처:
자바스크립트의 표준 정의 : CommonJS vs ES Module
JavaScript 번들러로 본 조선시대 붕당의 이해
코어자바스크립트 모듈
commonjs vs esm
JavaScript Module System(2) - UMD와 ESM
TOAST UI 의존성 관리
[번역] CommonJS는 사라지지 않습니다
[번역] CommonJS가 자바스크립트를 해치고 있습니다
JavaScript 표준을 위한 움직임: CommonJS와 AMD
번들러 파헤치기 1 - 모듈 시스템의 발전과 역사 (commonJS, AMD, UMD, ESM-ES Module)
CommonJS와 ESM에 모두 대응하는 라이브러리 개발하기: exports field
Why AMD?
why CommonJS (and its async incarnation, AMD) were not adopted by browsers?
CommonJS에서 ESM으로 전환하기