JavaScript는 초기 개발 기간이 짧았던 만큼 완성도가 낮고 문제점이 많은 언어였습니다. 시간이 지나면서 많이 성숙해지긴 했으나, 브라우저의 특수성 때문에 하위호환성을 갖춰야 할 필요가 있고, 이에 과거 문제가 많았던 기능은 그대로 지원되며 개선된 기능은 새로운 문법으로 추가되는 방식으로 발전해 왔죠. 그렇기에 현재 권장되는 최신 문법 이외에도 문제가 많아 권장되지는 않지만, 과거에 쓰였던 문법 또한 이해할 필요는 있습니다. 그중 모듈에 대해서 알아보겠습니다.

코딩은 한 파일에서만 이뤄지지 않습니다. 협업, 가독성, 재사용성 등을 고려하여 여러 소스 파일로 분산해서 작업합니다. 모듈 시스템은 용도에 따라 파일을 분리하여 독립적으로 기능을 구현하고, 이 파일들이 서로 참조될 수 있도록 하며, 대부분의 프로그래밍 언어에 존재하는 개념입니다. JavaScript 초기에는 모듈 시스템이 존재하지 않았습니다. 모듈의 부재로 인한 문제점을 체감하던 여러 개발 커뮤니티는 이를 해소하기 위한 다양한 시도가 있었으며, 그 결과 현재 모듈 단위로 개발하는 것이 주류로 자리 잡고 있습니다.

모듈 도입 전

JavaScript 파일은 <script> 태그를 통해 로드되며, 여러 파일이 존재하는 경우 파일 전부 다 추가해 줘야 합니다.

<script src="file1.js"></script>
<script src="file2.js"></script>

JavaScript에 모듈 개념이 존재하지 않던 때에는 파일이 여럿 존재하더라도 전부 같은 global scope를 공유했습니다. 그렇기에 각 파일의 최상단에 선언한 변수는 모든 파일에서 공유되며, 이는 많은 문제점을 보여줍니다. 한 파일에서 사용하는 변수가 다른 파일에서도 사용되는 상황이라면 이를 추적하는 것이 직관적이지 않고, 변수명에 신경 쓰지 않는다면 의도치 않게 override를 해버리는 상황도 생길 수 있습니다. 여러 문제점 때문에 scope를 내부적으로 분리하기 위해 Immediately Invoked Function Expressions (IIFE) 같은 방법을 사용하기도 했습니다.

// IIEF
(function () {
  // Module code here
})();

// IIEF with return
var module = (function () {
  // Module code here
})();

2009: CommonJS (CJS)

JavaScript를 브라우저 밖에서 다용도로 사용하고자 하는 여러 시도가 있었고 대표적으로 Node.js가 있습니다. CommonJS는 이 생태계에서 Module 시스템에 대한 표준을 설립하기 위한 프로젝트입니다. 브라우저 기반 JavaScript와 다르게 모든 파일이 하나의 global scope를 공유하는 것이 아닌 각 파일이 독립적인 scope를 갖습니다. 또한 exportsrequire 문으로 의존성을 관리합니다.

// import
const mod = require("./modulePath");

// export
const value = "A value to be exported";
exports.value = value;

2009: Asynchronous Module Definition (AMD)

지금은 거의 보이지 않는 규격으로 CommonJS와 비슷한 시기에 탄생했으며, CommonJS와는 다르게 브라우저 기반 JavaScript에서 모듈 시스템을 구현하기 위한 규격입니다. 대표적으로 RequireJS가 있으며, define 함수를 통해 모듈을 정의합니다. RequireJS는 외부 라이브러리이기 때문에 해당 라이브러리 스크립트 파일을 따로 추가해 줘야 합니다.

<script src="pathToRequireJS" data-main="entry.js"></script>
define(["module1", "module2"], function (mod1, mod2) {
  // Module code goes here
});

2011: Module Bundler

Module Bundler는 사용자의 코드와 종속성을 하나의 JavaScript 파일로 합쳐주는 도구로 대표적으로 Webpack, Rollup 등이 있습니다.

image1
출처: https://webpack.js.org/

Module Bundler는 Node.js 환경을 기반으로 하며, 빌더와 같은 역할을 합니다. Module Bundler를 통해 번들링을 하게 되면 Node.js의 내장 모듈 시스템으로 엮여있는 여러 파일들을 하나의 파일로 합쳐주게 되며, 그 결과물은 단일 파일이기에 사실상 별도의 모듈 시스템을 필요로 하지 않습니다. 따라서 모듈이 지원되지 않는 환경과 호환되어 마치 모듈을 사용하고 있는 듯한 느낌을 줄 수 있습니다.

Module Bundler는 모듈 시스템에 대해서 새로운 규격을 정의하지는 않지만, 자동화, 최적화, 확장성의 측면에서 여러 가지 기여를 합니다. 특히 코드를 파일 시스템에서 로드하는 Node.js에 비해 HTTP 요청으로 로드하는 브라우저 환경에서 더더욱 그 최적화를 체감할 수 있습니다. 그렇기에 현재 많은 프론트엔드 프레임워크 및 개발 환경 구축에 있어서 필수로 사용되는 툴로 자리 잡고 있습니다.

2015: ES Module (ESM)

ECMAScript 6에서 드디어 브라우저 기반 Javascript에 네이티브로 모듈을 지원합니다. type="module" 속성을 추가하여 entry 파일을 로드합니다.

<script type="module" src="entry.js"></script>

Node.js에서도 12버전부터 ES Module을 지원하기 시작했습니다. package.jsontype속성을 추가하여 사용할 수 있습니다.

package.json:

{
  // 생략
  "type": "module"
  // 생략
}

아래와 같이 exportimport 문을 사용합니다.

// import
import mod from "./modulePath";

// export
export const value = "A value to be exported";

import문의 경로를 따라 브라우저의 경우 URL로, Node.js의 경우 파일 시스템의 경로로 해당 모듈을 로드합니다.

업데이트:

댓글남기기