[ES6] Module

module

javascript module이란? 주요 기능이나 새로운 기능 단위로 모아놓은 것을 말한다. javascript를 사용할 때, 내부적으로 사용하는 자바스크립트 모듈 시스템을 사용하였을 것이다. 그것은 Node.js에 있는 CommonJS가 될 수도 있고, AMD나, 아니면 그 외의 것들이 될 수 있다. 이런 모든 모듈 시스템의 공통점은 다른 것들을 import하거나, export할 수 있다는 것이다.

Javascript는 이제 그런 것들에 대한 표준된 방안을 가지고 있다. 모듈은, 특정 키워드로 내보내서 외부적으로 사용할 수 있게하는 것이다. const나, function, 그리고 많은 다른 변수 등으로 바인딩하거나 선언해서 export를 할 수 있다.

// lib.mjs
export const speak = (name) => `${name} is speaking`;
export function shout(name) {
    return `${name.toUpperCase()}!;`
}

// another export declaration
export default function(string) {
    return `this is export default ${string} function`;
}
// this default exports can be imported  by any name

위의 예시처럼, 변수 등으로 export하여 사용할 수 있다. 외부적으로 내보낼 수 있다는 것은, 다른 말로 하면 다른 모듈에서 import할 수도 있다는 뜻이다.

import defaultExport, {speak, shout} from 'lib.mjs';
speak('leesoo');    // leesoo is speaking
shout('leesoo is coming');  // LEESOO IS COMING!
defaultExport('function');  // this is export default function function 

클래식과 모듈의 차이점

  • 모듈은 strict mode가 default로 적용된다.
  • html-style의 문법은 모듈에서는 지원되지 않는다. 일반적인 script에서는 동작하던 것도 마찬가지.
  • 모듈은 독립적인 스코프를 가지고 있다. 모듈을 쓸 때, 전역 스코프를 가지고 생성되지않는다는 것이다. 즉, 모듈 내에서는 var로 변수 선언을 한다해도 이는 전역변수가 될 수 없다. 그래서 모듈 내에서 선언한 변수는 모듈 외부에서 참조할 수 없다.
  • 클래식 스크립트 내에서는 import, export 문법을 사용할 수 없다.

브라우저에서 JS 모듈을 사용할 땐, script 태그에 type="module"을 추가하여 사용하면 된다.

브라우저에서 모듈 & 클래식의 차이점

  • 브라우저에서 모듈을 사용할 때, 똑같은 script 모듈이 두 번 입력되어 있어도 단 한번만 실행지만, 클래식은 DOM에 추가된 만큼 실행된다.
  • 클래식은 CORS 헤더와 관계없이 항상 로드된다. 모듈은 CORS에 맞는 헤더가 필요하다. -> 정확히 어떤 뜻일까
  • 클래식에서는 async attribute가 붙은 스크립트의 다운로드를 막을 수 없지만, 모듈은 가능하다.(defer attribute)

여기서 defer과 async 태그를 몰라서 mdn에서 가져왓다

Screen Shot 2020-01-19 at 7 14 43 PM

Screen Shot 2020-01-19 at 7 15 08 PM

파일 확장자

모듈 자바스크립트를 사용하려면 .mjs 확장자를 사용하는데, 웹에서는 이 확장자를 구분하지 않는다. .js로 제공된다.

  • 브라우저는 type attribute를 이용하여 모듈인지 구분한다.
  • 구글에서는 .mjs를 사용하길 권장한다.
    • 개발할 때, 모듈 파일인지 여부를 알 수 있다.
    • nodejs의 모듈 지원은 .mjs 파일에서만 동작한다. (?)

클래식은 HTML 파서의 속도를 늦춘다. 반면 모듈은 defer 어트리뷰트를 추가함으로써 스크립트 다운로드 및 파싱을 병행하도록 할 수 있다. 이는 모듈간의 의존관계로 인한 문제가 생기지 않음을 의미한다.

Dynamic import (동적 import)

static import는 모든 모듈이 다운로드된 후에 코드가 실행된다. (아 그래서 import문에 특정 모듈이 존재하지않으면 바로 에러가 떠버렸구나) 특정 모듈만 실행하고 싶은 컴포넌트가 있다면, 해당 컴포넌트가 실행될 때 모듈을 받도록 하는 것이 전체 로드 시간을 줄일 수 있을 것이다.

<script type="module">
    (async () => {
        const moduleJS = '.lib.mjs';
        const {speak, shout} = await import(moduleJS);
        speak('leesoo');    // leesoo is speaking
    })
</script>

import.meta

import.meta라는 것은, 현재 모듈에 대한 메타데이터를 제공하는 기능이다. ECMAScript에서 정의되지 않은 호스트 환경에 종속적인 기능을 사용하는 데에 정보를 제공해주는 기능이라고 한다……..잘모르겠다.

bundling

  • 모듈로 나눠지더라도, 수많은 모듈 환경들을 테스트할 때, 병목현상이 발생한 경우가 있다. 그래서 번들링을 권장한다.
  • 모듈을 배포하기전에 번들러를 돌려서 모듈과 코드 수를 줄이는 것이 성능이 좋다.
  • 번들링하는 것과, 하지 않는 것은 trade-off 관계이므로 잘 생각해서 결정하는 것이 사실 제일 좋음
  • 모듈을 세분화해서 사용하는 것
  • 모듈 preload하기 -> 모듈 전달 최적화를 위해, 의존성을 미리 로드해서 컴파일할 수 있도록 : 모듈의 종속성이 크면, 브라우저는 이를 확인하려고 많은 http 요청을 보내게된다. 이를 preload하여 한번에 처리할 수 있다.
  • http/2 사용 -> 이쪽 부분은 공부가 더 필요할듯……..

주의할 점은 여기서 소개한 module은 es6에서 새로 나온 기능이기 때문에, 모든 브라우저가 지원하지 못한다. 그래서 꼭 babel, webpack 같은 번들링을 이용하여 브라우저에서 호환할 수 있는 작업이 먼저 진행되어야 사용할 수 있을 것이다.

참고