icon
7장 : 자바스크립트 심화 II

모듈 시스템

우리는 지금까지 자바스크립트의 기본 문법부터 시작하여 비동기 처리, 객체 지향, 함수형 프로그래밍 등 다양한 심화 개념들을 학습했습니다. 이제 여러분은 복잡한 기능을 구현할 수 있는 충분한 지식을 갖추게 되었습니다. 하지만 실제 대규모 웹 애플리케이션을 개발할 때는 코드가 수십, 수백 개의 파일로 나뉘어지게 됩니다. 이렇게 방대한 코드를 어떻게 효율적으로 관리하고 재사용하며, 서로 간의 의존성을 깔끔하게 처리할 수 있을까요?

이 질문에 대한 답이 바로 모듈 시스템(Module System) 입니다. 모듈은 특정 기능 단위로 코드를 분리하고 캡슐화하여, 필요한 부분만 가져다 쓸 수 있도록 하는 재사용 가능한 코드 블록입니다. 모듈 시스템을 사용하면 코드의 응집도를 높이고, 결합도를 낮추며, 전역 스코프 오염을 방지하여 유지보수성과 확장성이 뛰어난 애플리케이션을 구축할 수 있습니다.

이번 장에서는 자바스크립트가 발전하면서 등장한 다양한 모듈 시스템의 역사와 현재 표준인 ES 모듈(ES Modules) 에 대해 깊이 있게 탐구해 보겠습니다. 모듈 시스템의 이해는 현대 자바스크립트 개발의 필수적인 부분입니다.


모듈 시스템의 필요성

모듈 시스템이 없던 초기의 자바스크립트는 다음과 같은 문제점들을 안고 있었습니다.

  1. 전역 스코프 오염: 모든 스크립트 파일이 전역 스코프를 공유하기 때문에, 변수나 함수 이름이 충돌(name collision)할 위험이 높았습니다. 이는 코드의 안정성을 해치고 디버깅을 어렵게 만들었습니다.
  2. 의존성 관리의 어려움: 스크립트 간의 실행 순서나 의존 관계를 수동으로 <script> 태그의 순서로 관리해야 했고, 누락되거나 순서가 바뀌면 오류가 발생했습니다.
  3. 코드 재사용의 어려움: 코드를 특정 기능 단위로 분리하기 어려워 재사용이 힘들었고, 불필요한 코드가 포함될 가능성이 높았습니다.
  4. 정보 은닉의 부족: 모든 것이 전역 스코프에 노출되어 있어, 캡슐화나 정보 은닉이 어려웠습니다.

이러한 문제들을 해결하기 위해 다양한 모듈 시스템이 등장하게 됩니다.


자바스크립트의 모듈 시스템 역사

공식적인 모듈 시스템이 없던 시절, 개발자들은 여러 편법과 패턴을 사용하여 모듈과 유사한 기능을 구현했습니다.

IIFE 를 이용한 모듈 패턴 (과거)

가장 흔하게 사용되던 패턴 중 하나로, 함수 스코프를 이용하여 전역 스코프 오염을 방지하고 변수를 은닉했습니다.

// (function() { ... })();
// 또는 (function() { ... }());
// (즉시 실행 함수 표현: Immediately Invoked Function Expression)

(function() {
    let privateVariable = "나는 비밀!"; // 함수 스코프 안에 캡슐화
    function privateMethod() {
        console.log(privateVariable);
    }

    // 전역 스코프에 노출할 유일한 객체
    window.myModule = {
        publicMethod: function(message) {
            console.log("공개 메서드: " + message);
            privateMethod(); // privateMethod는 내부에서만 접근 가능
        }
    };
})();

myModule.publicMethod("안녕하세요"); // 결과: 공개 메서드: 안녕하세요, 나는 비밀!
// console.log(myModule.privateVariable); // undefined (접근 불가)

이 방식은 전역 오염을 줄여주지만, 여전히 의존성 관리가 수동적이고 코드 복잡성이 증가하는 단점이 있었습니다.

CommonJS (Node.js의 표준 모듈 시스템)

서버 사이드 자바스크립트 런타임인 Node.js에서 표준으로 채택된 모듈 시스템입니다. 동기적으로 모듈을 로드하며, 파일 시스템에서 직접 모듈을 가져오는 방식에 적합합니다.

  • require(): 다른 모듈을 가져올 때 사용합니다.
  • module.exports 또는 exports: 모듈 외부로 내보낼 대상을 정의합니다.
// 📁 math.js
function add(a, b) {
    return a + b;
}

function subtract(a, b) {
    return a - b;
}

module.exports = { // 외부로 내보낼 객체 정의
    add: add,
    subtract: subtract
};

// 또는 간단하게
// exports.add = add;
// exports.subtract = subtract;
// 📁 app.js
const math = require('./math'); // math.js 모듈을 가져옴

console.log(math.add(5, 3));      // 결과: 8
console.log(math.subtract(10, 4)); // 결과: 6

CommonJS는 Node.js 생태계를 성장시키는 데 큰 기여를 했지만, 브라우저 환경에서 바로 사용하기는 어렵다는 단점이 있었습니다 (변환 도구 필요).

AMD

CommonJS가 동기적인 로드를 지원하는 반면, 브라우저 환경에서는 네트워크를 통해 파일을 가져와야 하므로 비동기 로딩이 필요했습니다. AMD는 이러한 요구사항을 충족하기 위해 등장한 비동기 모듈 시스템입니다. require.js 라이브러리가 대표적입니다.

// 📁 myModule.js (AMD 형식)
define(['dependency1', 'dependency2'], function(dep1, dep2) {
    // 모듈 코드
    const myValue = dep1.someFunction() + dep2.anotherValue;
    return {
        myFunction: function() {
            console.log(myValue);
        }
    };
});

AMD는 비동기 로드를 지원했지만, 문법이 다소 복잡하다는 단점이 있었습니다.


ES Modules (ESM)

ES2015(ES6)부터 자바스크립트 언어 자체에 내장된 공식적인 모듈 시스템인 ES 모듈(ECMAScript Modules, ESM) 이 등장했습니다. 이제 대부분의 모던 브라우저와 Node.js(최신 버전)에서 별도의 도구 없이 ES 모듈을 직접 지원합니다.

ES 모듈은 CommonJS나 AMD의 장점을 취합하고 단점을 개선하여, 더 간결하고 강력한 모듈 기능을 제공합니다.

export: 모듈 내보내기

export 키워드를 사용하여 모듈에서 내보낼 변수, 함수, 클래스 등을 지정합니다.

  1. 이름 기반 내보내기 (Named Exports): 여러 항목을 이름으로 내보낼 수 있습니다.

    // 📁 math.js (ESM)
    export const PI = 3.14159;
    
    export function add(a, b) {
        return a + b;
    }
    
    export function subtract(a, b) {
        return a - b;
    }
    
    // 클래스도 내보낼 수 있습니다.
    export class Calculator {
        multiply(a, b) {
            return a * b;
        }
    }
  2. 기본 내보내기 (Default Export): 모듈당 오직 하나의 기본 내보내기만 가능합니다. 주로 모듈의 핵심 기능이나 단일 클래스를 내보낼 때 사용합니다. export default는 이름 없이 내보내지므로 가져올 때 원하는 이름으로 지정할 수 있습니다.

    // 📁 greeter.js (ESM)
    const defaultGreeting = "Hello, World!";
    
    export default function greet(name = "Guest") {
        return `${defaultGreeting} ${name}!`;
    }
    
    // 클래스도 기본 내보내기 가능
    // export default class User { ... }

import: 모듈 가져오기

import 키워드를 사용하여 다른 모듈에서 내보낸 항목들을 가져옵니다.

  1. 이름 기반 가져오기 (Named Imports): {} 안에 내보낸 이름과 동일하게 지정하여 가져옵니다. 여러 항목을 가져올 수 있습니다.

    // 📁 app.js
    import { PI, add, subtract } from './math.js'; // .js 확장자 필수!
    
    console.log(PI);          // 결과: 3.14159
    console.log(add(10, 5));  // 결과: 15
    
    // 내보낸 이름과 다른 이름으로 사용하고 싶을 때 (별칭)
    import { subtract as minus } from './math.js';
    console.log(minus(20, 7)); // 결과: 13
    
    // 모든 내보낸 항목을 하나의 객체로 가져오기
    import * as MathUtils from './math.js';
    console.log(MathUtils.add(1, 2)); // 결과: 3
  2. 기본 가져오기 (Default Import): 기본 내보내기는 {} 없이 원하는 이름으로 가져올 수 있습니다.

    // 📁 app.js
    import myGreetFunction from './greeter.js'; // greeter.js의 기본 내보내기를 myGreetFunction으로 가져옴
    
    console.log(myGreetFunction("Alice")); // 결과: Hello, World! Alice!
    
    // 이름 기반 내보내기와 기본 내보내기를 함께 가져올 수도 있습니다.
    // import myGreetFunction, { PI, add } from './greeter.js'; // (단, greeter.js에 PI와 add가 export 되어 있어야 함)

브라우저에서 ES 모듈 사용하기

브라우저에서 ES 모듈을 사용하려면 <script> 태그에 type="module" 속성을 추가해야 합니다.

<!DOCTYPE html>
<html lang="ko">
<head>
    <meta charset="UTF-8">
    <title>ES Modules 예제</title>
</head>
<body>
    <h1>ES Modules 테스트</h1>
    <script type="module" src="app.js"></script>
    </body>
</html>
  • type="module" 스크립트는 defer 속성을 가진 것처럼 동작하여 HTML 파싱을 블로킹하지 않습니다.
  • 모듈 내부는 기본적으로 엄격 모드('use strict')로 동작합니다.
  • 모듈 스크립트는 CORS(Cross-Origin Resource Sharing) 정책의 영향을 받습니다.

Node.js에서 ES 모듈 사용하기

Node.js에서 ES 모듈을 사용하려면 몇 가지 방법이 있습니다.

  1. package.json"type": "module" 추가: 프로젝트의 package.json 파일에 "type": "module"을 추가하면, .js 확장자를 가진 파일들이 기본적으로 ES 모듈로 처리됩니다.
    {
      "name": "my-node-app",
      "version": "1.0.0",
      "type": "module", // 이 설정을 추가
      "main": "app.js",
      "scripts": {
        "start": "node app.js"
      }
    }
  2. .mjs 확장자 사용: 파일 확장자를 .mjs로 사용하면 Node.js는 해당 파일을 ES 모듈로 인식합니다. (CommonJS는 .cjs 확장자 사용)

Node.js에서도 브라우저와 동일하게 import/export 문법을 사용하여 모듈을 가져오고 내보낼 수 있습니다.


마무리하며

이번 장에서는 자바스크립트의 모듈 시스템이 왜 필요하며, 어떻게 발전해왔는지, 그리고 현대 자바스크립트의 표준인 ES 모듈을 어떻게 사용하는지 상세히 학습했습니다.

여러분은 모듈 시스템이 전역 스코프 오염 방지, 의존성 관리, 코드 재사용성 및 정보 은닉 측면에서 얼마나 중요한 역할을 하는지 이해했습니다. 과거의 IIFE 패턴, CommonJS, AMD를 통해 모듈화의 필요성과 다양한 시도들을 살펴보았고, 최종적으로 exportimport 키워드를 사용하는 간결하고 강력한 ES 모듈 문법을 익혔습니다.

ES 모듈은 클라이언트(브라우저)와 서버(Node.js) 양쪽에서 모두 사용될 수 있는 통일된 모듈 시스템을 제공하며, 현대 자바스크립트 개발의 핵심적인 부분입니다. 이제 여러분은 대규모 애플리케이션의 코드를 체계적으로 분할하고 관리하며, 협업을 용이하게 할 수 있는 강력한 도구를 갖게 되었습니다. 실제 프로젝트에서 다양한 모듈을 만들고 가져와 사용해보면서 모듈 시스템에 대한 이해를 더욱 깊게 다지시길 바랍니다.