이번에 바닐라 자바스크립트 패키지를 개발하며 모듈에 대한 지식이 부족하다 생각되어 CommonJS 와 ES Modules 에 대해 조사해보았습니다.
commonJS 모듈은 ECMAScript module 가 나오기 이전에 자바스크립트 모듈을 불러올 때 사용하는 방식이였습니다.
우선 시작하기 앞서 "모듈"은 무엇이고 이러한 "모듈 시스템"이 탄생하게 된 배경에 대해 알아보도록 하겠습니다.
Node 환경에서 이야기하는 "모듈"이란 코드가 작성된 각 파일을 별도의 "모듈" 이라고 정의합니다.
"모듈 시스템"이 발생하기 이전
Kevin Dangoor 이라는 개발자는 JavaScript 에 필요한 내용들을 네 가지 언급했습니다.
1. cross-interpreter standart library
2. standard interfaces
3. standard way to include other modules
4. package repository and its dependencies
자바스크립트에는 HTML 의 script 태그를 활용해 파일을 불러와 평가하고 실행할 수 있었습니다.
하지만 불러온 자바스크립트 파일들은 전역 영역을 공유하는 문제가 존재했습니다.
그리고 JS 파일들을 불러오는 순서가 코드의 실행에 영향을 끼치는 문제가 존재했습니다.
위와 같은 스크립트를 포함시켰을 때
각 파일에서 선언한 A 라는 변수는 전역 공간을 공유하는 문제가 발생했고
b.js 파일이 가장 나중에 include 됐기 때문에 20 이라는 값이 출력되게 됩니다.
위와 같은 문제점들을 자바스크립트는 가지고 있었고 웹 생태계가 급격하게 발전하고 자바스크립트가 서버 환경에서도 동작할 수 있어야 한다는 요구가 생기며(클라이언트와 서버 간의 코드를 쉽게 공유할 수 있다는 이점을 가짐)
What Server Side JavaScript needs · (blueskyonmars.com)
What Server Side JavaScript needs ·
Server side JavaScript technology has been around for a long time. Netscape offered server side JavaScript in their server software back in 1996, and Helma has existed for a number of years as well. But, server side development has changed a lot over the p
www.blueskyonmars.com
이러한 문제점을 해결하기 위해 CommonJS 가 출범하게 됐습니다.
CommonJS 는 require 문법을 통해 다른 모듈을 불러올 수 있고
exports 문법을 통해 모듈의 내용을 다른 곳에서 import 할 수 있게 내보낼 수 있습니다.
또한 CommonJS 의 경우 모듈의 코드가 실행되기 전 Node 는 아래와 같이 랩핑 하는 과정을 거친다고 합니다.
이러한 방법을 통해 최상위 변수의 범위를 글로벌 개체가 아닌 모듈로 유지할 수 있고
모듈 안에서만 적용되는 글로벌한 변수를 제공하는데 도움이 된다고 합니다.
위와 같은 방식으로 모듈을 exports 하고 require 한 후 console.js 를 실행시키면
위와 같은 결과를 확인할 수 있습니다.
CommonJS 는 몇 가지 문제를 가지고 있었습니다.
첫 번째는 CommonJS 는 ECMA 와 독립적으로 개발이 되어 자바스크립트 언어 자체로 CommonJS 를 지원하지 않는다는 것이였습니다.
브라우저 환경은 ECMAScript 표준을 따르기에 CommonJS 방법을 사용할 수 없고 Webpack 을 통해 번들링을 해야 사용할 수 있었습니다.
두 번째는 불러오는 모듈을 트리쉐이킹 할 수 있는 방법이 없었습니다.
트리쉐이킹을 지원하는 번들링 도구인 Webpack 의 내용을 참조하면
16. Modules
16. Modules 16.1. Overview 16.1.1. Multiple named exports 16.1.2. Single default export 16.1.3. Browsers: scripts versus modules 16.2. Modules in JavaScript 16.2.1. ECMAScript 5 module systems 16.2.2. ECMAScript 6 modules 16.3. The basics of ES6 modules 16
exploringjs.com
해당 글에서 CommonJS 가 어째서 트리 세이킹이 힘든지에 대해 설명해주고 있는데요.
CommonJS 의 경우 동적 구조의 모듈 시스템 입니다. 즉, 런타임의 값에 따라 불러오는 모듈을 다르게 할 수 있습니다.
이로 인해 번들링을 할 때 어떤 파일을 포함시켜야 할지 번들러 입장에서는 알기가 어렵기 때문에 모든 파일을 번들에 포함하게 됩니다.
세 번째는 CommonJS 의 경우 모듈을 동기적으로 로드하기 때문에 브라우저 환경에서 사용하게 되면 화면의 렌더링이 지연되는 문제가 발생하게 됩니다.
브라우저 환경은 HTML 을 파싱하는 과정을 거치며 script 태그를 만나게 되면 잠시 파싱을 멈추고 스크립트를 평가 및 실행하게 됩니다. 해당 작업을 파싱을 멈추고 하는 이유는 돔의 요소(HTML 내부 태그)를 수정하는 코드가 발생할 수 있기 때문입니다.
이러한 단점들을 해결하기 위해 ECMAScript 는 ESModules(이하 ESM) 을 만들게 됩니다.
우선 첫 번째 단점인 자바스크립트 언어 자체에서 ESM 를 지원하게 됐습니다. 이로 인해 브라우저에서도 ESM 을 사용해 모듈 파일을 불러올 수 있게 됐습니다.
JavaScript modules - JavaScript | MDN (mozilla.org)
JavaScript modules - JavaScript | MDN
이 가이드는 JavaScript 모듈 구문을 시작하는데 필요한 모든 것을 제공합니다.
developer.mozilla.org
ECMAScript® 2025 Language Specification (tc39.es)
ECMAScript® 2025 Language Specification
Introduction This Ecma Standard defines the ECMAScript 2025 Language. It is the sixteenth edition of the ECMAScript Language Specification. Since publication of the first edition in 1997, ECMAScript has grown to be one of the world's most widely used gener
tc39.es
HTML 에서는 위와 같은 type="module" 을 선언해주는 방식을 통해 ESM 으로 작성된 모듈을 불러올 수 있다고 합니다.
위와 같이 ESM 형식의 파일을 작성하고 type="module" 을 선언해 스크립트를 넣었을 때 정상적으로 브라우저에서 작동하는 것을 확인할 수 있었습니다.
두 번째는 트리 세이킹 입니다.
ESM 은 하나의 특징을 가지고 있는데요.
모듈을 import 하거나 export 할 때 top-level 에서만 할 수 있다고 합니다.
이런 특성으로 인해 정적인 모듈 구조의 특성을 가지게 되고
이러한 두 가지 장점을 가지게 되어 트리 세이킹에 용의하다고 합니다.
세 번째는 ESM 형식의 모듈은 비동기적으로 로딩을 하기 때문에 주요 스레드를 블로킹 하지 않습니다.
모든 import 가 정적인 구조로 되어 있다면 평가 하기 전에 모듈을 비동기적으로 로드 할 수 있다고 합니다.
실제로 ECMAScript 문서를 확인해보면 모듈을 로드하고 promise 를 리턴한다고 합니다.
또한 Script 태그의 속성 중 async 와 defer 속성을 통해 HTML Parsing 과 스크립트의 로드를 비동기적으로 할 수 있고 스크립트의 평가 및 실행의 타이밍을 조절할 수 있습니다.
'웹 공부' 카테고리의 다른 글
React Fiber 구경하기 (0) | 2025.02.27 |
---|---|
React - Controlling an input with a state variable (2) | 2024.12.26 |
브라우저와 Node 환경에서의 Date 객체는 어째서 다를까 ? (1) | 2024.12.19 |
제어 컴포넌트와 비제어 컴포넌트 (0) | 2024.10.07 |
useState 객체는 왜 useEffect 를 유발하지 않을까 ? (2) | 2024.09.10 |