시스템아 미안해

Chapter 10. Modules 본문

책/Eloquent Javascript

Chapter 10. Modules

if else 2025. 10. 23. 10:53

좋은 프로그램은 명확한 구조를 가진다.
각 부분이 어떤 역할을 하는지 바로 이해할 수 있고,
서로가 불필요하게 얽혀 있지 않다.

 

하지만 현실에서 프로그램은 그렇게 자라지 않는다.
처음엔 단순했던 코드가 기능이 늘어날수록 점점 복잡해지고,
언제부턴가 모든 코드가 서로 연결되어 있는 거대한 덩어리가 된다.
이런 코드는 수정하기도, 재사용하기도 어렵다.

 

그래서 개발자들은 오래전부터 코드를 독립적인 조각으로 나누려 했다.
이 조각이 바로 모듈(module) 이다.


모듈은 프로그램의 한 부분으로,
어떤 기능을 외부에 제공하고 어떤 다른 모듈에 의존하는지를 명확히 정의한다.
이 두 가지 — 인터페이스와 의존성 — 이 모듈의 본질이다.


초기의 모듈 — 함수로 스코프를 만드는 시대

자바스크립트 초창기에는 언어 차원에서 모듈 시스템이 없었다.
그래서 개발자들은 즉시 실행 함수(IIFE)를 사용해
자체적인 스코프를 만들고 변수를 지역화했다.

 
const weekDay = function() {
  const names = ["Sunday", "Monday", "Tuesday"];
  return {
    name(n) { return names[n]; },
    number(name) { return names.indexOf(name); }
  };
}();

이 방식은 전역 변수 오염을 막는 데는 도움이 되었지만,
각 모듈이 어떤 코드에 의존하는지 명시할 방법이 없었다.
결국 프로젝트가 커질수록 의존 관계는 여전히 복잡해졌다.


CommonJS — 서버 사이드에서 시작된 표준화

Node.js의 등장과 함께 자바스크립트는 처음으로
공식적인 모듈 시스템을 갖게 되었다.
이게 바로 CommonJS이다.

CommonJS는 모듈을 파일 단위로 관리한다.
각 파일은 자신만의 스코프를 가지며,
필요한 코드는 require()로 불러오고,
외부에 제공할 코드는 exports로 내보낸다.

// format-date.js
const { days, months } = require("date-names");

exports.formatDate = function(date, format) {
  return format
    .replace(/YYYY|M|D|dddd/g, tag => {
      if (tag === "YYYY") return date.getFullYear();
      if (tag === "M") return date.getMonth() + 1;
      if (tag === "D") return date.getDate();
      if (tag === "dddd") return days[date.getDay()];
    });
};
// app.js
const { formatDate } = require("./format-date");
console.log(formatDate(new Date(), "dddd, M/D/YYYY"));

이 방식은 서버 환경(Node.js)에서는 안정적으로 작동했지만,
런타임에 동기적으로 모듈을 불러온다는 점에서 브라우저 환경에는 맞지 않았다.
브라우저는 네트워크 요청이 느리기 때문에,
정적 분석이 가능한 모듈 시스템이 필요했다.


ES Modules — 언어에 내장된 표준

2015년 ECMAScript 6(ES6)은
자바스크립트에 표준 모듈 시스템을 도입했다.
이제 더 이상 임시방편적인 require나 IIFE가 필요 없다.
모듈은 언어의 일부이며, 파일 단위로 자동 스코프를 가진다.

// format-date.js
import { days, months } from "date-names";

export function formatDate(date, format) {
  return format.replace(/YYYY|M|D|dddd/g, tag => {
    if (tag === "YYYY") return date.getFullYear();
    if (tag === "M") return date.getMonth() + 1;
    if (tag === "D") return date.getDate();
    if (tag === "dddd") return days[date.getDay()];
  });
}
// app.js
import { formatDate } from "./format-date.js";
console.log(formatDate(new Date(), "dddd, M/D/YYYY"));

import와 export는 모두 정적 문법이기 때문에,
번들러나 실행기(Node, 브라우저)가 코드를 실행하기 전에
의존 관계를 미리 파악할 수 있다.
이 덕분에 트리 쉐이킹(Tree Shaking) 과 같은 최적화가 가능해졌다.


NPM — 코드의 생태계

모듈이 파일 단위의 개념이라면,
패키지는 배포 단위의 개념이다.
여러 모듈을 묶어 하나의 패키지로 만들면,
그 자체가 독립된 재사용 단위가 된다.

이 패키지를 저장하고 관리하는 시스템이 NPM(Node Package Manager) 이다.
지금은 전 세계 개발자들이 만든
수백만 개의 패키지가 NPM을 통해 공유된다.
React, Express, Lodash 같은 프레임워크도 모두 NPM 패키지다.

과거엔 “다른 사람이 쓴 코드를 찾아다니는 일”이었지만,
지금은 “필요한 코드를 NPM에서 불러오는 일”로 바뀌었다.


번들링 — 모듈을 하나로 묶는 과정

모듈이 많아질수록 파일 개수가 늘어나고,
브라우저는 각 파일을 별도로 요청해야 한다.
이를 해결하기 위해 등장한 것이 번들러(bundler) 다.

Webpack, Rollup, Vite 같은 도구가 대표적이다.
이들은 import/export 구문을 해석해
하나의 최적화된 파일로 묶는다.
필요한 코드만 포함시키는 트리 쉐이킹이나
페이지 단위로 코드를 나누는 코드 스플리팅도 자동으로 처리한다.

지금은 이 모든 과정이 빌드 파이프라인에 포함되어 있어,
개발자는 모듈화를 신경 쓰기보단 의도를 코드 구조로 표현하는 데 집중할 수 있다.


모듈 설계 — 단순함, 일관성, 조합성

좋은 모듈은 “작고, 예측 가능하며, 조합 가능한” 모듈이다.
입력과 출력이 명확하고, 내부 상태를 최소화해야 한다.
이 원칙은 예나 지금이나 변하지 않았다.

  • 단순함: 한 모듈은 한 가지 일만 한다.
  • 일관성: 다른 개발자가 봐도 익숙한 API 형태를 사용한다.
  • 조합성: 표준 자료구조(JSON, Array, Map 등)를 사용해
    다른 모듈과 쉽게 결합할 수 있도록 만든다.

ini 모듈의 예처럼 parse() / stringify() 같이
명료하고 작동이 예측 가능한 인터페이스가 가장 이상적이다.


정리

모듈은 단순히 코드를 나누는 기술이 아니라,
프로그램의 사고방식을 구조로 표현하는 언어다.
과거엔 함수로 스코프를 흉내냈고,
이후 CommonJS가 표준화를 시도했으며,
지금은 ES Modules이 그것을 언어에 통합했다.

NPM은 모듈을 연결하고,
Bundler는 그것을 최적화하며,
개발자는 이제 코드의 의도를 구조로 설계한다.

결국 좋은 모듈이란

“작동하는 코드”가 아니라 “잘 협력하는 코드”다.