I code, therefore I exist.

웹 프론트 엔드 개발을 공부하고 있는 Ocean이라고 합니다. 만나서 반갑습니다.

WEB/JAVASCRIPT

자바스크립트의 모듈 시스템 (CommonJS, ES Module)

Ocean 2025. 1. 12. 18:36

들어가며..

안녕하세요, Ocean입니다.

React로 개발을 진행하다 보면 파일들을 분리하고 이를 import 또는 export 하는 일이 빈번합니다. 이는 코드의 재사용성을 높이고 유지보수를 용이하게 하기 위함인데, 이러한 코드 분리 원칙을 모듈 시스템이라고 합니다.

이번 글에서는 자바스크립트 모듈 시스템의 필요성, 발전 과정과 종류, 간단한 문법을 정리하며 이해하는 시간을 가져보겠습니다.

참고 글

1. 자바스크립트의 표준 정의 : CommonJS vs ES Modules

2. MDN - Javascript Modules


0. 모듈 시스템이란? 

 


모듈 시스템은 코드의 재사용과 관리 용이성을 위해 파일 단위로 코드를 나누고, 이를 서로 연결할 수 있는 방법과 규칙을 제공하는 것을 말합니다. 모듈 시스템은 복잡한 프로젝트에서도 각 기능을 독립적으로 개발하고 유지보수할 수 있게 해주는 핵심적인 원칙을 말합니다.


1. 자바스크립트 모듈 시스템의 필요성

초기 자바스크립트에는 브라우저 위에서 독립적이고 간단한 상호 작용을 위해 사용됐습니다. 이 때는, 작은 규모의 스크립트만으로도 충분했습니다. 하지만 자바스크립트는 지속적으로 발전하여, Node.js와 같은 외부 환경에서도 실행될 수 있는 언어로 성장했습니다.

Node.js는 서버 사이드 환경에서 자바스크립트를 사용할 수 있게 했습니다. 보통 서버 사이드 코드는 규모가 크기 때문에 먼저 모듈 시스템의 필요성이 대두되었고, 이를 해결하기 위해 서버 사이드 환경에 최적화된 CommonJS가 탄생했습니다. 

자바스크립트는 계속 발전하였고, 서버 환경이 아닌 브라우저 위에서도 복잡한 애플리케이션을 실행할 수 있게 되었습니다. 이로 인해 브라우저에 최적화된 모듈 시스템 또한 필요하게 되었습니다. 그러나 CommonJS는 서버 환경에 최적화된 모듈 시스템이기 때문에, 브라우저에는 적합하지 않았고, 이를 위해 AMD, UMD와 같은 다양한 브라우저 친화적인 모듈 시스템이 등장했습니다.

하지만 자바스크립트라는 똑같은 언어를 사용하지만 환경에 따라 다른 모듈 시스템을 사용한다는 것은 비효율적이였습니다. 그래서 이를 통합하기 위한 표준이 필요했고, 결국 ES6 모듈 시스템, ESM이 등장하게 됐습니다.


2. CommonJS와 ESM의 차이


먼저 둘의 문법 차이를 간단하게 알아보겠습니다.

CommonJS의 문법

// math.js
const add = (a, b) => a + b;
const subtract = (a, b) => a - b;

// module.exports를 사용하여 내보냄
module.exports = {
  add,
  subtract
};

// app.js
const math = require('./math');  // math.js 모듈을 가져옴

console.log(math.add(2, 3));      // 5
console.log(math.subtract(5, 3)); // 2

 

ES Module의 문법

// math.js
export const add = (a, b) => a + b;
export const subtract = (a, b) => a - b;

// math.js
const add = (a, b) => a + b;
const subtract = (a, b) => a - b;

export default {
  add,
  subtract
};

// app.js
import { add, subtract } from './math.js';  // named export로 가져옴

console.log(add(2, 3));      // 5
console.log(subtract(5, 3)); // 2

// app.js
import math from './math.js';  // default export로 가져옴

console.log(math.add(2, 3));      // 5
console.log(math.subtract(5, 3)); // 2

 

앞서 말했듯이 CommonJS는 서버 환경에서 최적화 되어있고, 브라우저 환경에는 적합하지 않다고 했습니다. 그 이유는 CommonJS는 모듈을 동기적으로 로드하기 때문입니다.

모듈 로드 방식의 차이

CommonJS는 모듈을 동기적으로 로딩합니다. require()를 통해서 외부 모듈을 호출하면, 해당 모듈이 완전히 로드되고 실행 될 때 까지 다른 코드의 실행을 멈추고 기다립니다. 이 방식은 서버 환경에서는 효율적*일 수 있습니다.

*작업의 선형적인 흐름을 보장해서 안정적이며, 서버 하드웨어는 빠르기 때문에 동기적 로드도 크게 느리지 않다. 오히려 효율적!


하지만 브라우저 환경에서는 다양한 리소스 로딩과 UI 렌더링이 동시에 이루어집니다. 어떤 모듈을 로딩할 때, 해당 모듈을 동기적으로 로드하면 모듈 로딩이 끝날 때 까지 브라우저는 다른 작업을 할 수 없기 때문에 UI 렌더링에 차단이 일어나기 때문에 사용자 경험에 치명적일 수 있습니다. ESM은 모듈을 비동기적으로 병렬 로드합니다. 이는 브라우저에서 여러 리소스를 불러오며, UI 렌더링을 동시에 처리할 수 있게 도와줍니다. 이렇게 CommonJS와 ESM은 근본적으로 모듈을 로드하는 방식이 차이 나기 때문에 기능적인 차이가 존재합니다.

Top Level Await

Top Level Await는 ESM에서만 지원하는 기능으로, 모듈의 최상위 영역에서 직접 await를 사용할 수 있는 기능을 의미합니다. 이전에 await는 오직 비동기 함수(async 함수) 안에서만 사용될 수 있다는 제약을 없애고, 모듈의 최상위에서 직접 비동기 작업을 처리할 수 있게 해줍니다. 이 기능을 통해 모듈 레벨에서도 비동기 작업을 간편하게 처리할 수 있게 되었습니다.

// ESM에서의 Top Level Await 예시
const data = await fetch('https://api.example.com/data');
console.log(data);


반면, CommonJS는 동기적인 모듈 시스템이기 때문에 require() 함수로 해당 모듈의 코드가 완전히 실행되고 나서야 다음 코드가 실행됩니다. 허나 await는 비동기 로드 코드입니다. JS 코드 내부에서 async 함수 안에서 사용하는 것은 가능하지만 Top Level 즉, 모듈의 최상위에서 비동기 로드를 하는 것은 논리적으로 불가능합니다. 따라서 CommonJS에서는 Top Level Await가 불가능합니다.

정적 분석 (Static Analysis)

정적 분석은 코드 실행 전에 코드의 구조와 의존성을 분석하는 과정입니다. 이 과정은 성능 최적화, 코드 품질 향상, 디버깅 등을 돕는 중요한 역할을 합니다. 특히 모듈 시스템에서는 의존성 분석에 필요합니다. CommonJS는 런타임에서 require()함수를 호출하는 시점에서 모듈을 로드하기 때문에 의존성이 코드 실행 중에 동적으로 결정됩니다. 하지만 ESM은 코드의 상단에 import, export 키워드로 코드 작성 시점에 모듈의 의존성을 명시하기 때문에 정적으로 분석될 수 있습니다. 따라서 ESM은 트리 쉐이킹이 원할해집니다.

트리 쉐이킹 (Tree Shaking)

트리 쉐이킹이란 사용되지 않는 코드를 제거하는 최적화 기법입니다. CommonJS는 런타임에서 모듈을 로드하는 동적 로딩 방식이기 때문에 모듈이 실제로 실행되는 시점까지 어떤 함수나 객체가 필요한지 알기 어렵습니다. 따라서 트리 쉐이킹이 어렵습니다. 하지만 ES Module은 정적으로 분석이 가능하기 때문에 트리 쉐이킹이 매우 유리합니다. 따라서 ES Module은 CommonJS 보다 코드 최적화가 유리합니다.

이렇게 CommonJS는 동기적 로드를 통해서 서버 환경에서 적합하지만, 정적 분석과 트리 쉐이킹을 지원하지 않습니다. 하지만 그렇다고 해서 CommonJS가 더 덜떨어진 모듈 로드 방식은 아니고, ESM과 다른 특징으로 각각의 장단점이 있다고 생각하면 좋을 것 같습니다.


마무리하며..

현재 React를 비롯한 현대적인 프론트엔드 개발에서는 ESM을 많이 사용하고 있지만, 여전히 기존의 CommonJS 모듈 시스템을 사용하는 레거시 코드나 라이브러리들이 존재합니다. 이러한 코드들을 마주할 때, 모듈 시스템의 차이를 이해하고 있으면 문제를 보다 쉽게 해결할 수 있습니다.

각각의 모듈 시스템은 특정 환경에 맞게 최적화되어 있고, 그에 따른 장단점이 명확합니다. CommonJS는 서버 환경에서 동기적 로딩을 통해 효율적으로 작동하는 반면, ESM은 브라우저 환경에서 비동기적으로 로딩하며, 정적 분석 및 트리 쉐이킹과 같은 최적화 기법을 지원합니다.

따라서 개발을 진행할 때, 어떤 모듈 시스템을 사용하고 있는지에 대한 이해는 코드의 효율성과 유지보수성에 큰 영향을 미칩니다. 이러한 기본적인 지식은 코드 작성 뿐만 아니라, 프로젝트를 더 효율적이고 안정적으로 관리하는 데 중요한 역할을 합니다.

ESM과 CommonJS의 차이점과 특징을 잘 이해한 후, 두 시스템을 적절히 활용하면, 어떤 환경에서도 안정적이고 최적화된 코드를 작성할 수 있을 것입니다. :)

'WEB > JAVASCRIPT' 카테고리의 다른 글

이터러블(Iterable)과 이터레이터(Iterator)  (0) 2025.05.02
이벤트 캡쳐링, 이벤트 버블링  (0) 2024.02.13
Promise, 프로미스  (0) 2024.02.03
Rest 파라미터, 스프레드 문법  (0) 2023.04.10
클로저  (0) 2023.02.15