Module은 무엇인가요?

모듈은 코드의 집합입니다.

코드의 대부분은 변수를 변경하는 것이므로 이러한 변수를 어떻게 구성하느냐에 따라 코드 유지 관리에 큰 영향을 미칩니다. 모듈을 사용함으로써 각각 격리된 환경을 제공하고(충돌 방지), 재사용성과 유지 보수성을 높입니다.

JS에는 대표적으로 ES Modules, CommonJS 모듈 시스템이 있습니다.

CommonJS 란?

CommonJS 모듈은 Node.js용 JavaScript 코드를 패키징하는 방식입니다. 모듈은 requiremodule.exports를 사용하여 정의됩니다.

ES Modules 이란?

ECMAScript modules은 재사용을 위해 자바스크립트 코드를 패키징하는 공식 표준 형식입니다. 모듈은 importexport 문을 사용하여 정의됩니다.

CommonJS과 ES Modules은 무엇이 다른가요?

1. require는 동기적이고 import는 비동기적입니다.

require는 해당 모듈을 호출시에 동기적으로 불러오고 즉시 실행합니다.

왜 이렇게 동작할까요?

그를 알기 위해서는 CommonJS가 왜 만들어졌는 지에 대해서 알아야합니다.

CommonJS는 웹 브라우저 외부의 JavaScript용 모듈 생태계를 표준화하기 위해서 만들어졌습니다.

웹 브라우저의 외부는 일반적으로 모듈을 네트워크로 요청해서 불러오는 것이 아닌 로컬 파일 시스템에서 여러 모듈이 저장된 파일을 불러왔기에, 속도가 빨랐고 시작될 때 단 한번만 불러오면 됐기에 성능상의 문제가 없었습니다.

문제는 브라우저에서 동기적으로 모듈을 가져올 때(require)입니다.

브라우저 환경의 성능은 알 수 없으며, 메인 스레드가 각 파일이 다운로드될 때까지 기다린다면 대기열에 다른 많은 작업이 쌓이게 됩니다. 브라우저에서 작업할 때는 다운로드하는 데 시간이 오래 걸리기 때문입니다.

ES Modules은 어떨까요?

ES Modules은 비동기적으로 로드됩니다. ES Modules은 런타임에 동적으로 모듈을 가져오는 CommonJS와 달리 컴파일 시에 어떤 모듈을 가져오고 내보낼지 결정할 수 있습니다.(static module structure)

이로 인해 어떤 이점이 있을까요?

아래의 그림에 나타난 <script type="modules">를 보면 HTML 파싱과 동시에 모듈의 다운로드가 이뤄집니다.

alt text 출처: https://v8.dev/features/modules

또한 번들링을 통해 필요하지 않은 코드를 제거할 수 있습니다. 이는 번들 사이즈를 줄여주게 됩니다.

2. ES Modules는 Top-level await을 지원합니다.

ES Modules에서의 실행 순서는 아래와 같습니다.

JavaScript 엔진의 모듈 실행 순서는 후위 순회(Post-order traversal)방식으로 탐색하여, 모듈의 코드가 실행헌 뒤에 export 합니다.

  1. 현재 모듈의 실행은 대기 중인 promise가 resolved될 때까지 지연됩니다.
  2. 부모 모듈(특정 모듈을 import한 모듈)의 실행은 await을 호출한 자식 모듈과 그 모든 형제 모듈이 export 될 때까지 지연됩니다.
  3. 그래프에 cycle이나 awaited promises가 없다는 가정 하에, 부모 모듈의 형제 모듈은 동일한 동기 순서로 계속 실행할 수 있습니다.
  4. await을 호출한 모듈은 awaited promise resolves된 후 실행을 재개합니다.
  5. 부모 모듈과 후속 트리는 대기 중인 다른 프로미스가 없는 한 동기식 순서로 계속 실행됩니다.
// ESM

// a.mjs
export const five = await Promise.resolve(5);

// b.mjs
import { five } from "./a.mjs";

console.log(five); // Logs `5`

3. Value Copy vs Reference

CommonJS와 ES Modules은 모듈에서 값을 내보내는 방식이 다릅니다. CommonJS에서는 모듈의 값을 복사하여 내보내고, ES Modules은 값을 참조합니다.

// CommonJS

// 파일: a.js
let value = 1;
function increment() {
  value += 1;
}
module.exports = { value, increment };

// 파일: b.js
const a = require("./a");
console.log(a.value); // 1
a.increment();
console.log(a.value); // 1
// 원본 모듈의 변경 사항이 반영되지 않습니다.
// 이는 a.js에서 내보낸 value의 복사본을 참조하기 때문입니다.
// 파일: a.mjs
export let value = 1;
export function increment() {
  value += 1;
}

// 파일: b.mjs
import { value, increment } from "./a.mjs";
console.log(value); // 1
increment();
console.log(value); // 2
// 원본 모듈의 변경 사항이 반영됩니다.
// value는 ESM 모듈의 스코프에서 직접적으로 참조되는 변수입니다.

4. Import read-only feature of esm

CommonJS에서는 내보낸 객체의 속성을 수정할 수 있지만, ES Modules에서는 내보낸 값이 읽기 전용으로 취급되어 직접 변경할 수 없습니다.

// CommonJS

// a.js
let count = 0;
function increment() {
  count++;
  return count;
}
module.exports = { increment };

// b.js
const a = require("./a");
console.log(a.increment()); // 1
a.increment = function () {
  // 실행 중에 모듈의 함수를 변경
  return "Modified!";
};
console.log(a.increment()); // "Modified!"

// c.js
const a = require("./a");
console.log(a.increment()); // "Modified!"
// ESM

// a.mjs
let count = 0;
export function increment() {
  count++;
  return count;
}

// b.mjs
import { increment } from "./a.mjs";
console.log(increment()); // 1

// SyntaxError: Identifier 'increment' has already been declared
export const increment = function () {
  return "Modified!";
};

console.log(increment());

참고 링크

CommonJS와 ES Modules는 왜 함께할 수 없을까?

commonjs란 무엇인가?

왜 esmodule 이어야 하는가?

CommonJS와 ESM에 모두 대응하는 라이브러리 개발하기: exports field

CommonJS에서 ESM으로 전환하기

ES modules: A cartoon deep-dive

https://nodejs.org/api/esm.html##resolution-and-loading-algorithm

https://nodejs.org/api/modules.html

https://f-lab.kr/insight/understanding-modern-javascript-module-system

The difference between commonjs and esm

ES6 In Depth: Modules

Previewing ES6 Modules and more from ES2015, ES2016 and beyond

Axel Rauschmayer’s book: “Exploring JS: Modules”

Tree Shaking

What Server Side JavaScript needs

CommonJS WIKIPEDIA

CommonJS is hurting JavaScript

모듈 소개

MDN JavaScript modules

JavaScript modules

Top-level await