반응형

 

 

 

 

 

 

🌈 자바스크립트 모듈화의 역사

 

자바스크립트 모듈화의 방법은 크게 3가지의 변화를 겪으면서 발전하였다.

 

IFFECommonJS와 AMDES6

 

 

 

 

 

0️⃣ Intro

자바스크립트에서 모듈화에 필요한 조건은 아래와 같이 세 가지가 있다.

 

◾ 스코프의 독립성

스코프가 독립적이지 없다면 전역 변수 영역이 엉키는 문제가 발생할 수 있다.

 

예를 들어서, script 태그로 math.js 와 app.js를 import 하게 되면 math.js을 먼저 로딩하고 app.js을 로딩한다.

//main.html...
<script src="./src/math.js"></script>    
<script src="./src/app.js"></script>  
...

 

이때 두 개의 js파일에서 같은 변수 이름을 사용한다면 마지막에 로딩된 스크립트를 기준으로 변수 이름이 적용된다.

/* math.js */
function sum(x, y) {
  return x + y;
}

/* app.js */
sum(1, 2)
console.log(sum(1, 2)); // 3

 

따라서, 아래와 같은 경우에는 전역변수 영역이 엉키게 되어 에러를 유발한다.

/* math.js */
function sum(x, y) {
  return x + y;
}

/* app.js */
sum = 1;

/* main */
sum(3, 4)	//Uncaught TypeError: sum is not a function

 

 

 

함수, 객체단위의 import

함수, 객체 단위의 모듈화를 통해 가독성 높은 코드 작성이 가능해지며, import 된 파일의 내부에 어떤 모듈이 포함되어 있는지 유추할 수 있다.

 

예를 들어서, script 태그로 import하는 방식은 아래와 같이 파일을 통째로 가져온다.

//main.html...
<script src="foo.js"></script>
...

위와 같은 방식은 모듈을 import할 때 해당 파일 내부를 알 수 없게 된다는 단점이 있다.

 

반면, nodeJS 에서 사용하는 것과 같은 방식은 함수 또는 객체를 import 해서 가져올 수 있기 때문에 충분히 의미가 담긴 코드를 작성할 수 있게 된다.

 

//main.js
const function_a = require("foo.js").function_a

 

 

 

의존성 관리의 수월함

모듈 A가 모듈 B를 import하고, 모듈 B가 모듈 C를 import 하는 등... 여러 모듈이 사용되는 경우에 의존성 관리가 제대로 이루어지지 않는다면 확장 가능한 (=scalable) 코드를 작성할 수 없게 된다. 따라서 모듈은 의존성 관리를 수월하게 할 수 있는 특징을 가지고 있어야 한다.

 

 

 

 

 

 

1️⃣ IIFE

즉시 실행 함수 표현(IIFE, Immediately Invoked Function Expression)은 정의되자마자 즉시 실행되는 함수를 말한다.

 

일반적으로 JS에서 함수의 선언과 호출은 다음과 같다.

function foo(){
  statements
};

foo(); //Call

 

초기 웹에서는 선언과 실행을 나눌 필요가 없었기 때문에 즉시실행 함수 표현식을 사용하게 되었다.

(function() {
  statements
})()


/* IIFE 내부에서 정의된 변수는 외부에서 접근할수없다. */
(function() {
  const str = "Hello Module"
})()

str // Uncaught ReferenceError: str is not defined


/* IIFE를 변수에 할당하면 IIFE 자체는 저장되지 않고, 함수가 실행된 결과만 저장된다. */
const result = (function() {
  const str = "Hello Module"
  return str
})()

str // "Hello Module"

 

위와 같이 IIFE는 스코프 문제를 해결하였지만, 즉시 실행된다는 점에서 IIFE의 내부에 접근할 수 없기 때문에 궁극적인 모듈화의 해결 방법은 아니었다. 그래서 제안된 것이 AMD와 CommonJS이다.

 

 

 

 

 

 

2️⃣ CommonJS와 AMD

CommonJS (CJS)는 서버사이드 애플리케이션이나 데스크톱 애플리케이션에서도 자바스크립트를 사용하기 위해 조직된 그룹이며, 대표적으로 서버 사이드 플랫폼인 Node.js에서 이를 사용한다.

 

CJS에서 제시한 방법은 exports 와 require() 를 통한 모듈화 방법이다.

/* export.js */
module.exports.foo = function() {
  statements
}

/* import.js */
const foo = require('export.js').foo

 

이 방법의 장점은 다음과 같다.

  1. 모듈 파일마다 스코프가 설정되며, 전역변수와 지역변수를 분리하여 모듈이 독립적인 영역을 갖는다.
  2. script 태그를 통해 파일을 가져오는 것이 아니기 때문에 필요한 함수나 변수를 가져올 수 있다.
  3. exports와 require를 이용하여 의존성 관리가 유용하다.

 

이렇게 CommonJS는 모듈화의 조건을 충족시키지만 CJS의 blocking 방식은 브라우저에서는 필요한 모듈을 모두 내려받을 때까지 아무것도 할 수 없게 된다는 결정적인 단점이 있다.

 

 

이 시기에 CJS의 내부에서 서버사이드를 지향한 모듈화 기법을 만드는 것이 아닌 non-blocking, async를 적극적으로 활용해서 웹 브라우저의 퍼포먼스를 확보할 모듈화 기법을 만드는 것이 목표라고 주장한 그룹이 있었고, 합의점에 이루지 못하고 독립하여  AMD (Asynchronous Module Definition) 라는 그룹을 만들게 되었다.

 

AMD의 슬로건은 브라우저의 Event Loop가 제공하는 Async 한 특성을 적극적으로 활용하는 것이었다.

define(['./foo.js', './bar.js'], function(foo, bar){
    //foo.js와 bar.js를 활용할 수 있다.
})

 

 

아래는 AMD 모듈화의 특성을 코드를 통한 예시로 볼 수 있다.

 

/* 1) 스코프의 분리 */

//  *******GLOBAL NAMESPACE******
define(['./foo.js', './bar.js'], function(foo, bar){
    //*******LocaL NAMESPACE******
})

/* 2) 세멘틱한 모듈 활용 */

define(['./foo.js', './bar.js'], function(foo, bar){
    const functionA_in_foo = foo.functionA
})

/* 3) 추가로 디펜던시의 관리가 수월하며 Async한 특성도 가지고 있다. */

 

 

 

UDM (Universal Module Definition) AMD와 CommonJS 두 그룹으로 나누어지다 보니 서로 호환되지 않는 문제를 해결하기 위해 등장하였다. UMD는 모듈이라기 보다는 디자인 패턴에 가까우며 AMD와 CommonJS, window에 추가하는 방식까지 모두 가능한 모듈을 작성하는 방식이다.

 

 

 

 

 

 

 

3️⃣ ES6 (ECMAscript 2015)

ES6 (ECMAscript 2015, ECMAScript Module)에서는 export를 이용해 모듈로 만들고 import를 통해 가져온다.

/* math.js */
export function sum(x, y) {
  return x + y
}

/* app.js */
import * as math from "./math.js"
// import {sum} from "./math.js"
// 위와 같이 일부 객체 또는 함수만 가져올 수도 있다.

console.log(math.sum(1, 2)); // 3

 

 

ES6에서는 클라이언트 사이드 자바스크립트에서도 동작하는 모듈 기능을 추가하였다. 

// script 태그에 type="module" 속성을 추가하면 모듈로 사용할 수 있다.
<script type="module" src="./src/app.js"></script>

 

이때는 app.js에서 math를 가져오기 때문에 math.js는 따로 로드하지 않아도 된다.

 

 

 

 

 

📘 요약 

/* CommonJS */
module.exports.foo = function() {}
const foo = require('export.js').foo


/* AMD */
define(['./foo.js'], function(foo, bar){})


/* ES6 */
export function sum(x, y) {}
import * as name from "./export.js"
  • CJS(CommonJS): 동기적인 특징으로 서버 사이드에서 사용하기 용이하다.
  • AMD(Asynchronous Module Definition): 비동기적인 특징으로 클라이언트 사이드에서 사용하기 용이하다.
  • UMD(Universal Module Definition): CJS와 AMD 모두 사용 가능한 모듈을 만들기 위해 사용된다.
  • ESM(ECMAScript Module): 자바스크립트 공식 모듈 시스템이다.

 

 

 

 

 

 

 

반응형
  • 네이버 블러그 공유하기
  • 네이버 밴드에 공유하기
  • 페이스북 공유하기
  • 카카오스토리 공유하기