CJS, ESM 모듈시스템 비교 (node.js 환경과 브라우저 환경)

Jeenie·2025년 3월 4일
0

CS

목록 보기
1/2

📍 참고
CommonJS와 ESM에 모두 대응하는 라이브러리 개발하기: exports field
CJS 와 ESM
[javascript] 모듈 시스템 - CJS, ESM 동작원리 이해하기

모듈 시스템 Module System

모듈은 "필요한 함수들의 집합"

모듈 시스템의 사용은 바퀴를 다시 만들지 않는 것과 같다.

자바스크립트 실행환경

모든 프로그래밍 언어는 실행환경이 필요하다.
자바스크립트 또한 자바스크립트 엔진이 구동한다.

자바스크립트 엔진

자바스크립트 코드를 실행하는 프로그램 또는 인터프리터.
크롬의 경우 V8 엔진, 파이어폭스는 스파이더몽키

node.js와 브라우저는 JS를 실행하기 위한 자바스크립트 엔진을 내장하고 있다.

node 환경

서버 개발 환경을 제공하는 것이 주된 목적.
서버 측에서 자바스크립트 코드를 실행할 수 있게 해주고, 이를 통해 파일 시스템, 네트워크 통신, 데이터베이스와의 연동 등을 가능하게 한다

브라우저 환경

클라이언트 측에서 자바스크립트를 실행하여 동적인 웹 페이지를 구현하는 데 중점

두 환경 모두 자바스크립트 기반이기에 ECMAScript 실행할 수 있지만, 각 환경에서 자신의 특수한 목적 실현시키기 위해 제공되는 API들은 서로 호환이 되지 않는다.

ECMAScript는 ECMA에서 만든 스크립트이다.

자바스크립트 모듈 시스템: CJS와 ESM

📍자바스크립트의 표준 정의 : CommonJS vs ES Modules
에 상세히 설명

자바스크립트는 ES2015 이전까지 모듈을 지원하지 않아 하나의 파일에 모든 코드를 담아야했다.
가독성 저하, 변수 충돌 등의 문제를 해결하기 위해 모듈 시스템이 도입됨.

Node.js에는 CommonJS(CJS), ECMAScript Modules(ESM)라는 두 가지 모듈 시스템이 존재한다.

CJS(commonJS)

Node.js에서 가장 일반적으로 사용되는 모듈 시스템.
동적 로딩을 지원하며, 런타임에 require()를 사용하여 모듈을 가져온다

if (condition) {
  const moduleA = require('./moduleA'); // 런타임에서 동적 로딩 가능
}
  • 모듈 가져오기 require()
  • 모듈 내보내기 module.exports
  • 런타임동적으로 코드를 처리
  • 확장자 .js

동적으로 처리하기 때문에 조건문 내부에서 처리가 가능하고,
런타임에서만 모듈간 의존관계를 파악할 수 있다.

ESM (ES Modules)

최신 JavaScript 버전에서 지원되는 모듈 시스템

import moduleA from './moduleA'; // 정적으로 로드됨 (컴파일 시 분석)
  • 모듈 가져오기 import
  • 모듈 내보내기 export
  • 컴파일 타임정적으로 코드를 처리
  • 확장자 .mjs 사용 권장

ESM module loader는 비동기적으로 작동 (Top-level Await을 지원)

따라서 ESM에서 CJS를 import 할 수는 있지만, CJS에서 ESM을 require 할 수는 없다. CJS는 Top-level Await을 지원하지 않기 때문

코드를 가져오기 전 실제 사용하는 부분과 사용하지 않는 부분을 미리 파악한다.
컴파일 타임시 정적으로 처리하기 때문에 조건문 처리가 불가능하다.
따라서 import는 최상단 스코프에서만 사용이 가능하다.

트리 쉐이킹 tree shaking

트리 쉐이킹이 왜 필요한가?

브라우저 환경에서는 페이지 렌더링을 빠르게 하는 것이 중요한데, 이 때 JavaScript는 로딩되어 실행되는 동안 페이지 렌더링을 중단시키는 리소스들 중 하나 입니다.
따라서 JavaScript 번들의 사이즈를 줄여서 렌더링이 중단되는 시간을 최소화 하는 것이 중요합니다. 이를 위해 필요한 것이 바로 Tree-shaking입니다. Tree-shaking이란 필요하지 않은 코드와 사용되지 않는 코드를 삭제하여 JavaScript 번들의 크기를 가볍게 만드는 것을 말합니다.
https://toss.tech/article/commonjs-esm-exports-field

CJS는 Tree-shaking이 어렵고, ESM은 쉽게 가능하다.

CJS의 tree-shaking

CJS는 동적인 구조require/module.export 한다.

// https://toss.tech/article/commonjs-esm-exports-field 코드 참조

// require
const utilName = /* 동적인 값 */
const util = require(`./utils/${utilName}`);

// module.exports
function foo() {
  if (/* 동적인 조건 */) {
    module.exports = /* ... */;
  }
}
foo();

따라서 빌드 타임에 정적 분석을 적용하기가 어렵고,
런타임에서만 모듈 관계를 파악할 수 있다.

ESM의 tree-shaking

ESM은 정적인 구조로 모듈끼리 의존하도록 강제한다.
import path에 동적인 값을 사용할 수 없고, export는 항상 최상위 스코프에서만 사용할 수 있다.

// https://toss.tech/article/commonjs-esm-exports-field 코드 참조

import util from `./utils/${utilName}.js`; // 불가능
import { add } from "./utils/math.js"; // 가능

function foo() {
  export const value = "foo"; // 불가능
}

export const value = "foo"; // 가능

따라서 ESM은 빌드 단계에서 정적 분석을 통해 모듈 간의 의존 관계를 파악할 수 있다.

문제는 라이브러리의 사용

CJS 환경에서는 ESM 모듈을 사용할 수 없지만,
ESM 환경에서도 CJS를 실행할 수 있다.

브라우저는 ESM 시스템을 사용하니 CJS 모듈은 다 실행할 수 있어야 할텐데, 왜 문제가 될까?

CJS 모듈은 브라우저에서 실행할 수 없다

브라우저의 정적 분석(Static Analysis)

브라우저는 네트워크 환경에서 동작하기 때문에 최적화된 로딩 방식을 필요로 한다. 정적 분석을 통해 필요한 모듈을 미리 파악하면 병렬 다운로드가 가능하다.

Node.js는
CJS(CommonJS)ESM(ECMAScript Modules) 모두 사용 가능하지만,

브라우저는 ESM만 지원하며 정적 분석이 필요한 환경이다.

브라우저에서는 CJS 모듈을 직접 사용할 수 없다

컴파일 타임에 모듈을 정적으로 로드하는 ESM는 호환되지만,
런타임에 모듈을 동적으로 로드하는 CJS는 정적분석이 불가능해서 호환되지 않는다.

브라우저에서 CJS 모듈을 사용하려면 번들링(Webpack, Vite 등)을 통해 변환이 필요하다.

예시

node.js는 서버측에서 동작하는 환경이기 때문에 브라우저에서 사용할 수 없는 특정 API나 모듈을 제공한다.
파일 시스템 관련 모듈 fs, 네트워크 관련 모듈 http, https 과 같은 모듈은
브라우저에서 사용할 수 없다.

어떻게 구분하는가?

package.json의 type field 또는 확장자를 보고 알 수 있다.

.js 파일의 Module System은 package.json의 type field에 따라 결정됩니다.
type field의 기본값은 "commonjs" 이고, 이 때 .js 는 CJS로 해석됩니다.
다른 하나는 "module" 입니다. 이 때 .js 는 ESM으로 해석됩니다.
.cjs 는 항상 CJS로 해석됩니다.
.mjs 는 항상 ESM으로 해석됩니다.
https://toss.tech/article/commonjs-esm-exports-field

모듈 시스템

CJSESM
모듈 사용require()import
모듈 내보내기module.exportexport
모듈 로드 시점런타임컴파일 타임
모듈 로딩동적(조건문 내부 가능)정적(최상위에서만 가능)

요약

node.js브라우저
모듈시스템CJSESM
적합한 환경서버 측 개발클라이언트 측 개발

profile
Web Front-end developer

0개의 댓글