1. 모듈 시스템

JavaScript의 모듈 시스템은 아주 단순하게 구성되어 있다.

만약 여러 개의 JavaScript 파일이 있고, 이를 import하는 HTML 파일이 다음과 같다면, 위에서 아래로 순차적으로 JavaScript 파일을 읽게되며, 브라우저는 이를 한 개의 전역 스코프로 실행시키게 된다.

<html>
  <body>
    <script src="index1.js"></script>
    <script src="index2.js"></script>
  </body>
</html>

전역 스코프로 실행될 때의 발생하는 문제점은 아래와 같다.

만약 index1.js, index2.js 파일이 서로 중복되는 변수명을 가지고 있다고 가정해보자.
이런 상황에서 위와 같이 JavaScript 파일을 불러올 경우, 변수가 덮어써지거나(var), 오류가 날 수 있다(let, const).
왜냐하면 각각의 JavaScript 파일을 실행시킬 때, 브라우저 상에서는 한 개의 전역 스코프로 동작하기 때문이다.
위와 같은 문제점을 해결하기 위해, 변수명이 서로 겹치지 않도록 코드를 작성하는 방법도 있겠으나, 근본적인 해결을 위해 module system이 등장하게 되었다.

2. 발전

시간이 지나며, JavaScript 프로젝트는 더욱 거대해졌으며, V8 엔진의 등장으로 JavaScript는 더이상 브라우저에서만 돌아가는 언어가 아니게 되었다.

이에 따라 더욱 발전된 모듈 시스템에 대한 요구가 증가했고, 크게 CommonJS 방식과 AMD 방식으로 갈라지게 되었다.
두 진영의 차이는 간단히 구분하자면, 모듈을 호출할 때 동기식으로 호출할 것인지, 비동기적으로 호출할 것인지에 대한 것에서 비롯되었다.
(참고로 Node.js는 Common JS의 방식을 채택하였으나, package.json 설정을 통해 방식을 변경할 수 있다)

이후 ES2015가 등장하며, 제대로 된 모듈 시스템이 도입되게 되었다.
이 모듈 시스템의 이름은 ES Module(=esm) 이다.

esm은 JavaScript의 공식적인 모듈이며 동기 / 비동기 호출이 모두 가능하다.
하지만 ES2015를 제대로 지원하지 않은 브라우저는 ESM을 사용할 수 없기 때문에 transpile을 거쳐야 한다.

3. commonjs

commonjs 방식은 다음과 같다.

package.json에서 typecommonjs로 설정한다.
(사실 default값이 commonjs이므로 따로 지정하지 않아도 무방함)

{
  "type": "commonjs",
}

module을 import하는 법은 다음과 같다.

const a = require("./foo");

module을 export하는 법은 다음과 같다.
여러 가지 방법으로 export할 수 있다.

const a = 10;
 
module.exports = a;  // 10
module.exports = { a };  // { a: 10 }
exports.a = a;  // { a: 10 }

commonjsnode.js 환경에서 채택한 module 방식이며, 브라우저와 호환되지 않는다.
따라서 node.js 환경이 아닌 브라우저 환경에서 module system이 적용된 코드를 실행시킬 목적이라면 commonjs가 아닌 아래의 esm 방식을 사용해야 한다.

4. esm

esm을 사용하는 방법은 다음과 같다.

package.json에서 typemodule로 설정한다.

{
  "type": "module",
}

module을 import하는 법은 다음과 같다.

import a from './foo';

module을 export하는 법은 다음과 같다.
여러 가지 방법으로 export할 수 있다.

export const a = 10;
const a = 10;
export default a;

만약 esm 방식으로 작성된 모듈을 브라우저에서 실행시키기 위해 html 파일에 import할 경우
script 태그에 type="module"을 선언해야 한다.

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

5. TypeScript

5.1. 기본 동작 방식

TypeScript 에서는 별도의 파일에 작성된 코드라도, 기본적으로 global scope로 간주된다.
따라서 서로 다른 파일에 같은 이름의 변수를 선언하면 TypeScript 컴파일러는 이를 중복 선언으로 인식하고 오류를 발생시킨다.

// a.ts
const a = 10;  // 중복선언 오류가 발생한다. 
 
// b.ts
const a = 20;  // 중복선언 오류가 발생한다.
// a.ts
const a = 10;
 
// b.ts
console.log(a);  // cannot find 오류가 뜨지 않는다.  

이 문제를 해결하기 위해서는 namespace를 이용하거나(비권장), module system을 사용하거나, tsconfig를 통해 옵션을 조정해야 한다.

module system

TypeScript는 esm이 적용되어있다.
앞서 언급한 문제를 해결하기 위해서는 중복 선언 문제가 발생한 변수 및 함수에 export, 혹은 export default를 적용하면 된다. 더이상 global scope로 간주되지 않고, 모듈화 되기 때문이다.

moduleDetection

tsconfig의 한 옵션.
해당 옵션의 값이 force로 설정되어있을 경우, 각각의 TypeScript 파일은 각각의 module로 인식된다.