JavaScript - CommonJS, ESM

dobby·2024년 12월 3일
0
post-thumbnail

CommonJS, ES Modules

Node14에는 옛날 스타일의 CommonJS와 새로운 스타일의 ESM Script 두개가 공존하고 있다.

JS 모듈을 내보내거나 가져올 때 2가지 방식을 사용한다.

  • module.exports로 모듈을 내보내고 require()로 접근하는 CJS(CommonJS)
  • export로 모듈을 내보내고 import로 접근하는 ESM(ES Modules)이 있다.
// CJS 방법
module.exports = { ... };

const utils = require('utils');
// ESM 방법
export default = () => { ... };
import utils from 'utils';

CJS, MJS의 차이점

📌 CJS

  • NodeJS에서 지원하는 모듈 방식으로, 초기 Node 버전부터 사용되었다.
  • 별도의 설정이 없다면 CJS가 기본값이다.

외부 모듈에 접근할 때는 require()을 사용한다.

const utils = require('utils');

module.exports로 모듈을 내보낸다.
모듈을 내보내는 방식은 named exports, default exports 두 가지가 있다.

module.exports.utils = { ... }; // named exports
module.exports = { ... }; // default exports
  • named exports의 경우 export 대상을 명명하여 내보내는 방식이다.
  • 한 파일에서 여러 개의 변수, 클래스, 함수를 내보낼 때 주로 사용한다.
// calculator.js
module.exports.add = (a, b) => a + b;
module.exports.sub = (a, b) => a - b;
  • named exports 모듈은 2가지 방식으로 접근할 수 있다.
const calculator = require('./calculator.js');
const { add } = require('./calculator.js');

console.log(calculator.add(2, 2)); // 4
console.log(add(2, 2)); // 4
  • default exports 모듈을 접근할 때는 원하는 이름으로 설정하여 사용할 수 있다.
const add = require('./calculator2.js'); // 모듈을 add로 명명
console.log(add(2,2)); // 4

📌 CJS의 특징

top-level await
async function 밖에서 await를 사용하게 해주는 것

트리 셰이킹
사용되지 않는 코드를 제거하여 번들 크기를 줄이는 기술

  • require()는 즉시 스크립트를 실행하는 구조이다.

  • top-level await가 불가능하므로, 동기적으로 작동한다.
    모듈이 필요한 시점에 즉시 로드되고 해당 모듈의 코드가 실행될 때까지 다음 진행이 차단된다.
    브라우저 환경에서 차단은 성능 혹은 동작에 문제가 발생할 수 있다.

  • 동기로 작동하므로 promise를 리턴하지 않고, module.exports에 설정된 값만을 리턴한다.

  • import 순서에 따라 스크립트를 실행한다.

  • 서버 사이드 혹은 런타임에서 사용한다.

  • 캐싱
    같은 모듈이 여러번 로드되어도 한 번만 실행된다. 이는 무한 루프를 방지하고 성능을 향상시킨다.

  • 모듈이 동기적으로 로드되므로, 비동기 로드가 필수적인 브라우저에서 사용하기 어렵다.
    서버 사이드 렌더링과 같은 환경에서 유리하다.
    왜냐하면, 서버 사이드에서는 모든 모듈이 로드된 후에야 코드가 실행되기 때문이다.

  • 동적 로드를 지원하기 때문에, 트리 셰이킹이 어렵다.


📌 ESM 방식

  • ES Modules는 ECMAScript에서 지원하는 방식이다.
  • 최신 자바스크립트 표준이다.
  • Node14에선 CJS, MJS 모두 공존하는데, 두 개를 동시에 사용하기 위해 별도의 처리가 필요하다.
  • 모듈 시스템을 CJS(기본값)에서 ESM으로 변경할 시, JS 일부 동작이 변경된다. (호환성 문제)

ESM 방식을 사용하기 위해선 package.json"type": "module"을 설정해야 한다.

// package.json
{
  "type": "module",
}
  • 모듈에 접근하기 위해서는 import를 사용한다.
import utils from 'utils';
import { add } from 'utils';
import { add as add_func } from 'utils';
  • export로 내보낸 모듈은 import로 접근할 수 있다.
  • named exportsdefault exports 두 가지를 지원한다.

아래는 named exports의 예시이다.

// calculator.js
export const sum = (x, y) => x + y;
  • named exports는 명명된 이름으로만 모듈을 불러올 수 있다.
  • as를 사용해 명명된 이름을 다른 별칭으로 수정할 수 있다.
import { sum } from './calculator.js';
import { sum as sum_func } from './calculator.js';

console.log(sum(2, 4)); // 6
console.log(sum_func(2, 4)); // 6
  • 만약 default 방식으로 접근할 시 SyntaxError가 발생한다.
// SyntaxError does not provide an export named 'default'
import calculator from "./calculator.js";

console.log(calculator.sum(2, 4));  // error

다음은 default export의 예시이다.
CJS와 마찬가지로, 별도의 이름을 지정하지 않아도 된다.

// calculator2.js
export default (x, y) => x + y;
export default function sum(a, b) {
  return a + b;
}
  • 명명된 이름이 없으므로 원하는 이름으로 import 하면 된다.
import add from './calculator2.js';
console.log(add(2, 4)); // 6

📌 ESM의 특징

top-level await
async function 밖에서 await를 사용하게 해주는 것

트리 셰이킹
사용되지 않는 코드를 제거하여 번들 크기를 줄이는 기술

  • top-level await를 지원하므로, module loader가 비동기 환경에서 실행된다.

  • 그러므로 CJS처럼 스크립트를 바로 실행하지 않고 import, export 구문을 찾아 스크립트를 파싱한다.

  • 파싱 단계에서 import, export 에러를 감지할 수 있다.

  • 모듈을 병렬(비동기)로 다운로드하지만, 실행은 순차적으로 한다.

  • import와 export를 지원하지 않는 브라우저가 있기에, ESM 사용을 위해 번들러가 필요하다.

  • 모듈이 비동기로 로드되므로, 비동기 로드가 필수적인 브라우저 환경에서도 사용될 수 있다.

  • ESM은 정적으로 의존성을 분석할 수 있어서 트리 셰이킹이 용이하다.

ESM이 CommonJS의 다양한 문제점들을 해결하게 되면서 언어 표준으로 지정되었다.
따라서 ESM은 자바스크립트 언어의 일부가 되었고, Node.js 지원 환경에서만 사용 가능했던 CommonJS와는 달리 브라우저, Deno 등 다양한 런타임에서도 쉽게 사용할 수 있다.


특징CommonJSESM
로드 방식동기적비동기적
트리 셰이킹어려움용이
사용 환경서버 사이드브라우저
키워드require, exportsimport, export

Config 파일에서 CJS를 사용하는 이유

config 파일들에서는 대부분 cjs 방식을 사용한다.
이는 아래의 결과이다.

  • Node.js 환경 호환성
    프론트엔드의 프레임워크 혹은 라이브러리의 config 파일은 보통 Node.js 환경에서 실행된다.
    Node.js는 CJS를 모듈 시스템 기본값으로 활용하기 때문에, 별다른 설정을 해줄 필요가 없어 용이한 점이 있다.

  • CJS와 ESM 동작의 차이
    require는 동기로 동작이 이루어지고 ESM은 import와 export 구문을 바로 실행하지 않고 비동기 환경에서 동작하도록 한다.
    이는 정확한 동작 순서 예측과 어떤 곳에서 문제가 발생하는지 찾기 어렵게 만든다.

.cjs는 CommonJS, .mjs는 ESM로 동작한다.

profile
성장통을 겪고 있습니다.

0개의 댓글