본문 바로가기

개발공부/Javascript

[JS] ESM, CJS 모듈 시스템

모듈성, 재사용성, 코드 관리를 효율적으로 하도록 도와준다. 초기 환경에서는 모든 코드가 하나의 js파일에 포함되거나 <script> 태그를 사용해서 여러 파일로 분할한다고 하더라도 각 파일 간 명확한 의존성 관리 방법이 없었다. 이로 인해 모든 코드가 단일 파일에 포함되어, 파일의 길이가 너무 길어 코드를 이해하고 관리하기가 힘들었다. 하나의 js파일에 1000줄이 넘는 코드가 있는 경우가 많았다. 프로젝트 가독성이 낮아지고, 코드를 이해하기가 어려웠다. 변수명이 충돌하는 경우가 많아 버그와 오류가 많았다. 코드 베이스가 커져서 유지 보수가 어려워졌고, 버그 수정 시에 해당 코드를 찾고, 변경이 다른 부분에 미치는 영향도를 파악하기가 어려웠다. 이를 극복하고자 ESM, CJS JS모듈 시스템이 개발되었다.

설계철학, 사용법, 장단점, 호환성 살펴볼 예정

 

 

CJS, ESM 이해의 중요성

두 모듈에 대한 이해를 해야 JS코드를 조직화하고 관리하는 방법의 근본적 차이를 이해할 수 있고, 효율적으로 사용하고 상호 운용할 수 있게 된다.

모듈화: JS 앱이 복잡성이 증가함에 따라 코드를 모듈 단위로 나누어 관리하는 것이 중요하다. 모듈화를 통해 개발자는 코드 독립적인 재사용 가능한 단위로 분할하여 프로젝트의 유지 관리성과 확장성을 크게 향상시킬 수 있다.

환경 간 호환성: CJS 같은 경우는 Node.js 및 서버사이드 개발에 사용이 되는 반면, ESM ㅁ듈은 최신 브라우저에서 지원이 된다. 모던 환경 개발에서는 환경 간 호환성을 이해하고 최적의 모듈 시스템을 선택하는 것이 중요하다.

최신 자바스크립트의 특성 통합: ESM 같은 경우에는 ES6에서 도입된 JS 공식 모듈 시스템이다. 따라서, ESM을 사용하면 import와 export 구문을 포함한 최신 JS 특성을 활용할 수 있고, 이는 모던 JS의 핵심 요소이다.

상호 운용성: CJS와 ESM을 혼합하여 사용중이다. Node.js환경에서는 CJS가 널리 사용 되고 있다. 그렇지만 점차 ESM으로 전환을 권장하고 있다. 이러한 환경에서 두 모듈 시스템 간의 상호 운용성을 이해하고 필요에 따라 모듈을 변환하거나 브릿지하는 전략을 개발하는 것이 중요하다.

 

 

CommonJS(CJS) 모듈 시스템

JS가 웹페이지를 넘어서서 서버 사이드 개발, 데스크탑 앱 등 다른 환경에서 사용될 수 있음을 인식하면서 시작되었다. 1995년에 JS는 원래 웹페이지를 동적으로 만들기 위해 개발되었다. 초기에는 클라이언트 사이드에서 작은 스크립트를 실행하는 데 사용이 되었다. 따라서 모듈화나 재사용성에 대한 고려가 필요하지 않았다. 하지만, JS가 많이 사용되고 웹과 기술이 발전하며, 앱이 점점 복잡해지고 코드의 구조화와 관리가 중요한 과제로 부상하였다. 약 2009년 경에 Node.js 의 등장과 함께 서버 사이드에서 JS를 사용하게 되었다. JS를 브라우저 환경 이외의 다른 런타임 환경에서 사용 가능하게 되었기 때문에, 이런 환경에서 새로운 모듈 시스템 환경이 필요해졌다. 당시 JS에서는 현재의 ESM과 같은 모듈화가 공식적으로 지원하지 않아 표준이 없었다. 이런 공백을 메우기 위해 CJS가 등장하였다. CJS는 서버 사이드 개발에 초점을 맞춘 모듈 시스템이다. 간단하고 동기적인 모듈 로딩 매커니즘을 제공한다. require함수를 사용하여 모듈을 불러오고, module.exports를 통해 모듈을 내보낼 수 있다. 이러한 시스템은 서버 환경에서의 동기적 파일 시스템 작업과 잘 어울렸고 , 개발자들에게 익숙한 프로그래밍 패턴을 제공했다. CJS이 영향으로 인해 JS 사용 범위를 크게 확장 시켰다. 이러한 모듈 시스템이 있었기에 개발자들은 크고 복잡한 서버 사이드 앱을 구축할 수 있게 되었다. 코드의 재사용성과 유지 보수성이 크게 향상되었다. NPM과 같은 패키지 매니저 발전에도 크게 기여하였다. CJS는 JS가 웹브라우저를 너머 다양한 환경에서 사용될 수 있는 범용 언어로 성장하는 데에 큰 역할을 하였다.

 

 

ECMAScript Modules (ESM) 모듈 시스템

ESM 모듈 시스템은 ES6 표준의 일부로 도입이 되었다. JS에서 공식적인 모듈화 메커니즘을 제공한다. ESM은 코드를 모듈 단위로 나누고, 각 모듈 간의 의존성을 명확히 정의할 수 있도록 해준다. 이 모듈 시스템은 코드의 재사용성, 유지 보수성을 향상시키며 대규모 앱의 개발을 용이하게 한다. ESM의 주요 특징은 정적구조이다. 모듈 간의 의존성을 정적으로 분석할 수 있게 한다. import와 export 구문을 통해 최상위 레벨에서만 사용할 수 있고, 조건부로 불러내거나 내보낼 수는 없다. 이러한 정적 구조 때문에 ‘트리쉐이킹’과 같은 최적화 기법을 가능하게 한다. ESM의 사용 방법은 모듈을 내보낼 때에는 export 키워드를 사용하고, 모듈을 가져올 때는 import 키워드로 불러온다. exoprt default로 기본 내보내기를 할 수 있다. ESM의 장점은 코드를 모듈 단위로 분할하여 관리할 수 있고, 프로젝트의 구조화와 유지보수성이 향상된다. 정적인 구조 때문에, 빌드 도구는 사용되지 않는 코드를 제거하거나 필요한 코드만 번들링하는 최적화 작업을 수행할 수 있다. 모든 모듈을 순회하여 각 단계를 동기적으로 수행하는 CJS와는 달리 ESM은 비동기적으로 각 단계를 수행하기 때문에, 모듈 하나가 async함수와 같이 모듈 최상위 레벨에서 await 구문을 사용할 수 있는 탑레벨 await가 사용 가능해지고 결과적으로 전체 파일 로드에 대한 성능을 향상 시킬 수 있다. ESM은 JS의 공식 모듈 시스템이므로 다양한 환경과 플랫폼에서 일관된 모듈 관리 방식을 제공한다.

 

ESM vs CJS

새로운 프로젝트나 최신 Node.js 환경에서는 ESM을 사용하는 것이 권장된다. ESM과 CJS 중 선택을 결정할 때, 고려해야 하는 요소가 있다. ESM은 정적인 구조 때문에 빌드 도구가 사용되지 않는 코드를 분석하고 제거 하는 트리 쉐이킹을 가능하게 한다. 이는 최종 번들의 크기를 줄이고 성능을 개선할 수 있다. 동적 임포트를 통해 모듈을 동적으로 불러올 수 있기 때문에, 코드 분할과 지연 로딩을 구현해서 앱의 초기 로딩 성능을 향상 시킬 수 있다. 표준화가 지원되기 때문에, 브라우저나 최신 JS 환경에서 널리 사용된다. 모듈 스코핑을 통해 전역 네임스페이스 오염을 방지할 수 있다.

ESM을 선택해야 하는 이유에도 CJS를 선택해야 하는 이유가 있다. Node.js에서는 오랫동안 CJS가 표준 모듈 시스템으로 사용되었기 때문에 여전히 많은 라이브러리나 프레임워크가 CJS를 기반으로 한다. 이러한 경우 Node.js 기반 레거시 프로젝트 경우에 ESM으로 전환에 비용이 많이 든다면, 현실적으로 CJS를 계속 사용하는 것이 낫다.

 

cjs.js

function sum(a, b) {
    return a + b;
}

module.exports = sum;

 

 

cjs-use.js

const sumFunction = require('./cjs.js');
const result = sumFunction(1, 2);
console.log(result);

 

 

esm.js

export default function sum(a, b) {
    return a + b;
}

 

 

esm-use.js

import sum from './esm.js';
const result = sum(1, 2);
console.log(result)

 

 

ESM의 내보내기 방식 2가지

  1. 기본 내보내기: 앞서 실습한 export default 로 내보내는 방식이고, 모듈 당 하나의 기본 내보내기만 가능하다. 가져올 때는 중괄호를 사용하지 않고 이름으로만 가져올 수 있다. 원하는 이름을 자유롭게 지정할 수 있다. 주로 모듈 전체가 하나의 클래스, 함수, 객체일 때 유용하다.
  2. 기명 내보내기: 앞서 살펴본 하나의 모듈에서 여러 개를 내보낼 수 있다. export + 특정 이름을 지정하여 사용한다. 가져올 때는 중괄호를 사용하여 정확한 이름을 명시해야 한다. 모듈에 여러 개의 함수, 변수가 있을 때 사용한다.

특정 회사에서는 서로 호환되기 어려운 두 모듈 시스템을 지원하는 라이브러리를 직접 개발한다. 그 이유는 SSR을 활발하게 사용하는 경우에는 Node.js에 CJS를 지원하는 것이 아주 중요하다. SSR이 서버와 클라이언트 간에 원활한 상호 작용을 하고 앱의 성능을 향상 시키는데 결정적인 역할을 한다. ESM의 지원은 브라우저의 환경의 성능과 아주 밀접한 관련이 있다. 브라우저의 렌더링 속도를 빠르게 하는 것이 UX에서 중요하여, 이러한 문제를 최소화하기 위해서 JS의 번들 크기를 축소하는 것이 중요하다. 그 핵심기술은 트리 쉐이킹이다. 트리 쉐이킹을 적용하려면, CJS로는 적용하기 어려워 ESM을 사용해야 한다. 따라서, SSR을 사용하는 환경에서는 CJS의 형식이 피룡하고 트리 쉐이킹 같은 최적화 기법을 적용하기 위해서는 ESM 형식도 필요하다. 따라서 모든 요구 사항을 충족시키기 위해 CJS와 ESM을 모두 지원하는 라이브러리를 개발하는 회사도 있다.

 

[참고]

패스트캠퍼스 강의 (내 생에 마지막 JavaScript : 기초 문법부터 실무 웹 개발까지 한 번에 끝내기 초격차 패키지 Online) 를 기반으로 작성하였고, 추가 서치 내용을 정리하였습니다.

'개발공부 > Javascript' 카테고리의 다른 글

[JS] this  (0) 2024.05.12
[JS]프로토타입  (1) 2024.04.28
[JS] 이벤트 루프  (1) 2024.04.18
[JS] 비동기  (0) 2024.04.10
[JS] 이벤트 버블링과 캡처링  (0) 2024.04.04