ES6 Modular programming

Chan-uk Son
8 min readDec 22, 2015

--

현재 지원되는 브라우저(Chrome, Safari, Firefox)가 없다.

Module 이란 무엇인가?

JavasScript가 브라우저 언어를 넘어 범용적으로 사용하기 위해 필요한 기술이 바로 “모듈화”였고, 이를 논의 하기 위해 자발적으로 만들어진 그룹이 CommonJS 워킹 그룹이다.

Module의 조건

  • 스코프(Scope): 모든 모듈은 자신만의 독립적인 실행 영역이 있어야 한다.
  • 정의(Definition): 모듈을 정의하는 방법이 필요하다.
  • 사용(Usage): 다른 모듈을 삽입하여 사용하는 방법이 필요하다.

ES6 이전 JavaScript modules

Module 관점에서 JavaScript 언어의 문제점.

  • JavaScript는 전역 영역을 마음대로 침범할 수 있다.
  • 다른 모듈을 삽입하는 표준적인 방법이 없다.
  • 코드를 패키징해서 배포하고 설치하는 방법이 필요하다.
  • 의존성 문제까지 해결하는 공통 패키지 모듈 저장소가 필요하다.

IIFE (The Immediately-Invoked Function Expression)

전역(window)에 모듈을 배포하는 방법이다. 각 모듈의 의존성을 개발자가 관리해야 한다.

IIFE 내부에서 윈도우에 import
IIFE 에서 반환. 외부에서 윈도우에 import

AMD (Asynchronous Module Definition)

AMD에서는 모듈은 js로 분리해야한다. AMD 명세는 define() 함수(클로저를 이용한 모듈 패턴)를 이용해 모듈을 구현하므로 전역변수 문제가 없다. 또한 해당 모듈을 필요한 시점에 로드하는 Lazy-Load 기법을 응용할 수도 있다. 대표적으로 requirejs 가 있다.

AMD 명세에서 정의하는 전역변수는 definerequire 객체가 있다. 그리고 전역 모듈을 명시적으로 가리킬 때 사용하는 define.amd 프로퍼티도 사용할 수 있다.

define으로 모듈을 정의(의존성도 명시)하고, require를 이용(의존성도 명시)하여 모듈을 비동기적(asynchronous)으로 사용한다.

AMD 모듈 정의 (define)
AMD 모듈 사용 (require)

필요한 파일을 네트워크를 통해 내려받아야 하는 브라우저와 같은 환경에서는 AMD가 CommonJS보다 더 유연한 방법을 제공한다.

CommonJS

AMD와 마찬가지로, 모듈은 js로 분리해야한다. CommonJS 명세에서 정의하는 전역변수는 module.exports와 require 객체가 있다.

exports로 모듈을 정의하고, require를 이용하여 모듈을 동기적(synchronous)으로 사용한다.

CommonJS 모듈 정의 (exports)
CommonJS 모듈 사용 (require)

필요한 파일이 모두 로컬 디스크에 있어 바로 불러 쓸 수 있는 상황, 즉 서버사이드에서는 CommonJS 명세가 AMD 방식보다 간결하다.

UMD (Universal Module Definition)

IIFE + AMD + CommonJS 를 모두 지원하는 모듈. AMD, CommonJS 순으로 지원 여부를 확인하고, 둘 다 지원하지 않을 경우, IIFE 방식을 사용한다.

UMD 방식의 모듈 정의
opensource 라이브러리 사용 형태

AMD, CommonJS 모두 범용적으로 사용할 수 있다. 단, IIFE를 대비하여, 각 모듈의 의존성은 개발자가 관리해야 한다. (js 파일의 순서에 따라 로딩)

ES6 modules

  1. 모듈은 각각의 .js 파일로 분리되어야한다.
  2. 각 모듈은 export 키워드에 의해 모듈을 정의(노출) 할 수 있다
  3. import 키워드에 의해 모듈을 사용할 수 있다.

Demo — https://github.com/sculove/modular

export

  • 모듈의 내부코드는 기본적으로 “strict” 모드로 동작한다.
  • export는 어느 위치든, 하나든 두개든 자유롭게 쓸수 있다. (단, 조건문에서 사용할 경우 error가 발생한다. why? export는 컴파일 시점에 결정하기 때문이다)
  • export시 중복되면 안된다. 중복될 경우, alias로 중복되지 않게 지정한다.
ES6 정의 (exports)
  • default로 alias를 줄 경우 “default exports”라 하고, 그 외는 “named exports”라고 한다.
  • default exports와 named exports를 함께 사용하는 것은 좋지 않다. default exports는 단지 하나의 변수만 노출할때 사용하고, named exports를 사용할때는 다수의 변수를 노출할때 사용하는게 좋다.

import

  • alias를 지정할 경우, alias만 접근 가능하고, 실제 변수는 접근이 불가능하다.
  • 모듈은 single 인스턴스이다. 중복 import하여도 동일한 하나의 모듈이 반환된다.
ES6 사용 (import)

Notice

  1. export, import는 컴파일 시점에 결정되기 때문에, 조건문에 의해 실행될 경우 에러가 발생한다.
  2. export된 변수는 모듈 내부코드에 의해 변경될 수 있다. (Why? import될때 원래 모듈을 복사하지 않고, 레퍼런스만 참조한다) 하지만, import된 변수는 read-only 이기 때문에 모듈 내부를 변경 할 수 없다.
  3. eval() 내부에서 import나 export를 사용할 수 없다.
  4. script 태그는 import 키워드를 지원하지 않는다. 브라우저는 비동기식으로 import 되지만, script 태그는 동기식으로 동작하기 때문에, script 태그 대신 module 태그를 사용해야한다.

The Module loader

import 키워드는 내부적으로 module loader를 사용한다. module loader는 JavaScript 엔진의 구성품으로, module loader의 구현은 환경에 따라 다르다.

브라우저 인 경우, import 될 때 자원을 서버로 부터 가져오는 반면, Node.js 인 경우, import 될 때 자원을 파일 시스템으로 부터 가져온다.

Module loader의 동작을 바꾸거나, 제어하기 위해서는 Module Loader API를 사용해야 한다.

Module loader에 대한 스펙은 ES6이 아니다. 이는 WHATWG 브라우저 표준 그룹에서 스펙을 정의한다.

http://whatwg.github.io/loader/

ES6 사용을 위한 툴

ES6 문법을 사용하기 위해서는 babel(https://babeljs.io/)과 같은 변환 툴을 이용하여 사용이 가능하다.

Babel 6.0의 구성

  1. babel-cli : babel 커멘드 라인 모듈
  2. babel-core : babel의 NODE API, require Hook이 있는 모듈
  3. transform : 코드를 변경하는 plugin
  4. preset : transform 들을 미리 모아 놓은 묶음
    babel-preset-es2015 : es6으로 변경하는 preset

Set, Symbol, Promise, …과 같은 스펙을 사용하려면 babel-polyfill 을 사용하여 Babel Runtime 환경을 만들어 주어야한다.

브라우저 환경을 위해서는 require와 같은 것을 합칠 번들 툴(Browserify, Webpack, …)이 추가로 필요하다.

ES6 Module에 대한 추가글

참조 문서

--

--

Chan-uk Son

A developer who knows only his wife, son and daughter