48장 모듈


모듈

모듈은 애플리케이션을 구성하는 개별적 요소로 재사용 가능한 코드 조각을 말하고, 일반적으로 기능을 기준으로 파일 단위로 분리한다.
모듈이 되려면 자신만의 파일 스코프를 가질 수 있어야 한다.

자신 만의 파일 스코프를 갖는 모듈의 모든 자산은 캡슐화 되어 다른 모듈에서 접근할 수 없다.

대신에 모듈은 자산을 선택적으로 공개할 수 있다. 이를 export라고 한다.
모듈이 공개한 자산을 불러와서 재사용하는 것을 import 라고 한다.

자바스크립트와 모듈

자바스크립트는 웹페이지의 단순한 보조 기능을 처리하기 위한 제한적인 목적을 가지고 만들어졌기 때문에 모듈 기능을 필요로 하지 않았다.
하지만 JS를 브라우저 환경에 국한하지 않고 범용적으로 사용하려는 움직임이 있었고, 모듈 기능이 필요해짐에 따라서 CommonJS, AMD가 제안되었다.

가장 유명한 JS 런타임 환경인 Node.js는 모듈 시스템에 CommonJS를 채택했다. 현재는 독자적으로 진화된 CommonJS라고 볼 수 있다.

js에서의 모듈 시스템의 역사는 다음과 같은 글을 통해 알 수 있다.

ES6 모듈(ESM)

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

script 태그에 type="module" 속성을 추가하면 로드된 js파일은 모듈로 동작한다.
구분을 위해 파일 확장자를 mjs로 사용하는 것이 권장된다.

<script type="module" src="app.mjs"></script>

ESM에는 기본적으로 strict mode가 적용된다.

모듈 스코프

ESM과 일반적인 script의 차이는 독자적인 모듈 스코프의 차이이다.

script 태그

script 태그로 불러온 js 파일은 독자적인 모듈 스코프를 갖지 않고, 전역 스코프를 공유한다.
그렇기 떄문에 각 파일에서 var로 전역변수로 선언된다.

// foo.js
// x 변수는 전역 변수다.
var x = 'foo';
console.log(window.x); // foo
// bar.js
// x 변수는 전역 변수다. foo.js에서 선언한 전역 변수 x와 중복된 선언이다.
var x = 'bar';

// foo.js에서 선언한 전역 변수 x의 값이 재할당되었다.
console.log(window.x); // **bar**
<!DOCTYPE html>
<html>
<body>
  <script src="foo.js"></script>
  <script src="bar.js"></script>
</body>
</html>

ESM

ESM은 파일 자체의 독자적인 모듈 스코프를 제공한다.
즉, 모듈 내에서 var 키워드로 선언한 변수는 전역변수로 선언되지 않게 된다.
또한 모듈 내에서 선언된 식별자도 외부에서 참조할 수 없다.

// foo.mjs
// x 변수는 전역 변수가 아니며 window 객체의 프로퍼티도 아니다.
var x = 'foo';
console.log(x); // foo
console.log(window.x); // undefined
// bar.mjs
// x 변수는 전역 변수가 아니며 window 객체의 프로퍼티도 아니다.
// foo.mjs에서 선언한 x 변수와 스코프가 다른 변수다.
var x = 'bar';
console.log(x); // bar
console.log(window.x); // undefined

export 키워드

모듈 내부의 자산을 외부에서 사용할 수 있게 하기 위해서 export 키워드를 사요앟ㄴ다.

선언문 앞에 export 키워드를 붙여서 사용할 수 있다.

// lib.mjs
// 변수의 공개
export const pi = Math.PI;

// 함수의 공개
export function square(x) {
  return x * x;
}

// 클래스의 공개
export class Person {
  constructor(name) {
    this.name = name;
  }
}

혹은 export 할 대상을 객체로 구성하여 한번에 export할 수도 있다.

// lib.mjs
const pi = Math.PI;

function square(x) {
  return x * x;
}

class Person {
  constructor(name) {
    this.name = name;
  }
}

// 변수, 함수 클래스를 하나의 객체로 구성하여 공개
export { pi, square, Person };

import 키워드

다른 모듈에서 공개한 자산을 import 키워드로 가져올 수 있다.
export 했던 식별자 이름으로 import 해야하고, ESM의 경우 파일 확장자를 생략하면 안된다.

// app.mjs
// 같은 폴더 내의 lib.mjs 모듈이 export한 식별자 이름으로 import한다.
// ESM의 경우 파일 확장자를 생략할 수 없다.
import { pi, square, Person } from './lib.mjs';

console.log(pi);         // 3.141592653589793
console.log(square(10)); // 100
console.log(new Person('Lee')); // Person { name: 'Lee' }
<!DOCTYPE html>
<html>
<body>
  <script type="module" src="app.mjs"></script>
</body>
</html>

as 키워드로 import 모듈의 사용이름 변경할 수 있다.

import * as lib from './lib.mjs';
import { pi as PI, square as sq, Person as P } from './lib.mjs';

하나의 값만 Export한다면 default 키워드 사용할 수 있다.
객체가 아닌 임의의 이름으로 impor한다.

// lib.mjs
export default x => x * x;
// app.mjs
import square from './lib.mjs';

console.log(square(3)); // 9

reference