자바스크립트는 node.js 전까지는 단순히 웹 브라우저에서의 동작만을 담당하여, 파일을 따로 나누더라도 <script src="경로"></script>
와 같은 방식으로 그냥 HTML 파일에 추가하기 마련이었는데 ES2015부터 모듈
이라는 개념이 도입되었다.
모듈
이란 무엇이냐면, 여타 다른 프로그래밍 언어들처럼 내가 코딩할 때, 다른 파일에서 코딩한 부분을 불러와서 이용할 수 있게 만들어주는 것이다. 이를테면 자바에서는 import
라는 구문을 통해 미리 만들어진 모듈
을 불러와 사용할 수 있다.
import java.util.Random;
public class RandomTest {
public static void main(String[] args) {
Random rand = new Random();
}
}
위는 java.util.Random
이라는 패키지에서 Random
클래스를 불러온 경우이다. 위의 Random
클래스는 특별한 마법같은 것이 아니라 결국 누군가가 직접 코딩하여 배포한 하나의 모듈
이다.
모듈이 없었을 때의 자바스크립트를 체험해보며 문제가 무엇인지 생각해보자.
function sum(a, b) {
return a + b;
}
console.log("1+2는?", sum(1, 2));
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>테스트용</title>
</head>
<body>
콘솔을 확인해봅시다.
<script src="src/math.js"></script>
<script src="src/app.js"></script>
</body>
</html>
script
태그를 이용해 js 파일을 두개 추가해주었다.
콘솔에 값이 잘 뜬다. 그러나... 문제가 몇가지 있다 하나씩 살펴보자.
자바스크립트 엔진에서 전역을 담당하는 window
에 .sum
이라는 프로퍼티를 출력해보면, 위와 같이
f sum(a, b) {
return a + b;
}
우리가 작성했던 함수가 등록되어 있다.
sum
이라는 메소드를 정의해버리면 어떻게 될까?sum
메소드가 앞서 작성한 sum
메소드를 덮어써버릴 것이다.결국 애플리케이션이 예측하기 힘든 방식으로 동작할 수 있고, 런타임 에러가 발생할 수 있다. 사실 에러가 발생하면 다행이고, 원인을 알 수 없는 버그가 발생하는 경우가 최악이다.
(function() {
statements
})();
IIFE는 Self-Executing Anonymous Function으로 알려진 디자인 패턴이다. IIFE 모듈 방식 코드의 원리는 자바스크립트 엔진에서 함수를 실행했을 때 생기는 스코프를 유지하며 독립적인 공간으로써 활용하는 것이다.
위 코드는 복잡해보이지만, 그냥 단순히 함수를 만들자마자 바로 실행하는 것이다.
위 그림처럼 독립적인 영역이 생기게 된다. 다수의 IIFE 영역을 만든다면 다음과 같을 것이다.
다수의 IIFE 영역이 생겨도 서로를 침범하지 않는다.
// 기존에 전역 스코프에 math 프로퍼티가 사용되고 있었다면,
// 기존 객체를 참조한다.
const math = window.math || {};
(function () {
function sum(a, b) {
return a + b;
}
math.sum = sum;
})();
이제 math.js
는 window.math
이외에 다른 영역을 침범하지 않는다. 또, 기존에 math
가 사용중이었더라도 const math = math || {}
라는 코드를 통해 기존에 사용중이라면, 기존 사용중인 객체를 참조하게 만들었다.
console.log("1+2는?", math.sum(1, 2));
app.js
는 아무런 변화가 없다.
이제 전역에서는 math
라는 객체 안에서만 sum
이 존재하게 된다. 추가적으로 다른 수학 메소드를 추가해도 모두 math
라는 객체 안에서 존재하기 때문에 다른 영역을 침범할 일이 없다.
IIFE를 이용하여 전역 스코프를 침범하지 않게 코딩할 수 있다는 것까지는 배웠는데, ES2015 이후에는 IIFE보다 더욱 직접적으로 그 역할을 해줄 모듈
이라는 것이 추가되었다.
대표적인 모듈은 AMD
와 CommonJS
가 있다.
CommonJS는 자바스크립트를 사용하는 모든 환경에서 모듈을 제공하는 것이 목표이다.
exports
라는 키워드로 모듈을 내보내고, require()
함수로 모듈을 불러온다.
Node.js에서 사용하는 방법이다.
exports.sum = (a, b) => a + b;
exports.multiple = function (a, b) {
return a * b;
};
const math = require("./math_commonjs");
console.log("1+2", math.sum(1, 2));
console.log("3*5", math.multiple(3, 5));
Node.js 공식 문서에서 관련 내용을 확인할 수 있다.
AMD
: Asynchronous Module Definition의 약자로 비동기로 로딩되는 환경에서 모듈을 사용하는 것을 목표로 한다.UMD
: Universal Module Definition의 약자로 AMD를 기반으로 CommonJS 방식까지 지원하는 통합 형태이다.위와 같이 각 커뮤니티에서 각자의 스펙을 제안하다가 ES2015에서 표준 모델 시스템을 내놓게 되었다. 지금은 바벨과 웹팩을 이용해 모듈 시스템을 사용하는 방식이 일반적이다.
export function sum(a, b) {
return a + b;
}
import * as math from "./math_amd.js";
console.log("1+2 = ", math.sum(1, 2));
위와 같은 방식으로 사용할 수 있는데,
node js 기본 스펙에서는 사용이 안되고 바벨 등을 거쳐야 한다.
항상 웹 표준에서 새로운 것을 지원할 때는 브라우저 호환성이라는 문제가 생긴다. 인터넷 익스플로러를 포함한 몇몇 브라우저에서는 아직 모듈을 지원하지 않는다.
크롬 브라우저는 모듈을 지원하는데, 모듈을 사용하고 싶다면 다음과 같이 script
태그를 입력하면 된다.
<script type="module" src="app.js"></script>
<script>
태그로 로딩할 때, type="text/javascript"
대신에 type="module"
을 사용해야 한다.
node js 기본 스펙에서도
AMD
와UMD
를 지원하지 않았다. 그런데 개발자가 원하는 것은 어떤 환경에서도 모듈 시스템을 사용하는 것이다. 이렇게 환경을 구성하려면 웹팩의 번들링이 필요하다.
이전에 AMD 방식으로 작성한 모듈이 정상적으로 동작하는지 확인해보자.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>테스트용</title>
</head>
<body>
콘솔을 확인해봅시다.
<script type="module" src="src/app_amd.js"></script>
</body>
</html>
위와 같이 script
태그를 이용해서 app_amd.js
만 넣어주고 반드시 type=module
로 해야 한다.
app_amd.js
에서 정상적으로 출력이 되고 있다.