모듈 시스템의 역사와 ESM

황준승·2023년 10월 31일
2
post-thumbnail

목표
javascript의 모듈 시스템의 역사와 필요성에 대해 이해하고 이를 최대한 잘 활용할 수 있게 한다.

1. 모듈 시스템이 없었을 때

1-1 서버 사이드에 대한 수요

  • javascript는 브라우저를 위해 만들어진 간편한 언어였지만, 이것을 서버 측면에서도 사용하고자 하는 수요는 지속적으로 있어 왔습니다.

  • 특히, Kevin Dangoor는 JS의 다음과 같은 아쉬움을 지적해왔습니다.

1. 파일이나 디렉터리를 읽을 수 없다. 
2. 웹 서버, 데이터베이스 등에 연결하거나 쿼리하기 위한 표준 인터페이스 존재하지 않는다. 
3. package manager가 없다. (패키지를 배포, 관리, 설치하는 기능이 존재 X)
4. module system이 없다. (다양한 모듈들을 쉽게 load할 수 없고, 네임스페이스를 구분 X)
5. 전역 변수 관리 문제 

1-2 전역 변수 관리 문제

앞서 Kevin Dangoor가 언급한 전역 변수 관리 문제에 대해서 더 자세히 알아보도록 하겠다.

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="utf-8">
        <title>useTodo.js Todos</title>
        <link rel="stylesheet" href="todos.css"/>
    </head>

    <body>
        <script src="../../test/vendor/json2.js"></script>
        <script src="../../test/vendor/jquery.js"></script>
        <script src="../../test/vendor/underscore.js"></script>
        <script src="../../useTodo.js"></script>
        <script src="../useTodo.sessionStorage.js"></script>
        <script src="todos.js"></script>
    </body>

    <!-- (...) -->

</html>
  • 기존에는 자바스크립트 파일들을 script 태그를 이용하여 불러오는 방법을 사용하였다. script태그를 통한 방법 외에는 모듈화 시킬 방법이 없었기 때문이다.

이때, script 태그를 통한 방식에는 두 가지의 문제점이 발생할 수 있다.

  1. 각각의 태그로 불러온 js파일들의 전역변수가 공통으로 생성된다. 전역 변수의 값이 바뀌어 에러가 발생할 수 있다.
var alertNum = 10; // index.js
var alertNum = 20; // page.js
<script type="text/javascript" src="./index.js"></script>
<script type="text/javascript" src="./page.js"></script>
  • 전역 변수 alertNum을 index.js와 page.js에서 각각 선언하여 값을 할당한 후, 해당 파일들을 script태그를 통해서 가져오면 값의 변화가 생기게 된다.
  • page.js가 index.js 이후에 불러와지기 때문에 전역 변수 alertNum은 20으로 값이 변하게 된다. alertNum은 변수 값이 20으로 통일된다. (의도하지 않은 코드)
  1. js파일들을 불러오는 순서가 올바르지 않을 경우 에러가 발생한다.
// index.js
$(document).ready(function() {
    console.log('READY');
});
<script type="text/javascript" src="./index.js"></script>
<script type="text/javascript" src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<!-- HTML File - script js files -->

index.js에 jquery 문법을 활용하여 페이지가 로드된 후, 콘솔에 READY라는 로그를 남기는 코드를 추가하였다. 이를 실행할 경우 에러가 발생한다.

해당 소스 코드에는 jquery 관련 cdn 파일이 먼저 불러와져야 하는데 script 태그의 순서가 잘못되어 있다. jquery cdn없이 index.js를 불러왔을 경우, jquery 문법이 정의되어져 있지 않기 때문에 오류가 발생한다.

2. CommonJS

  • 위에 제시한 5가지 문제점을 해결하고 브라우저 뿐만 아니라, 서버, 데스크탑 등에서도 이용 가능한 javascript 모듈 시스템입니다.

  • CommonJS 모듈화는 아래와 같이 세 부분으로 이루어집니다.

    스코프: 모든 모듈은 자신만의 독립적인 실행 영역이 있어야 한다.
    정의: 모듈의 정의는 exports 객체를 이용한다.
    사용: 모듈 사용은 require 함수를 이용한다.

문제점

  1. commonjs는 ECMA standards의 지원없이 독립적으로 개발되었다는 점입니다. 즉, 언어 자체로 commonjs를 포함하지 않는다는 것이었습니다.

  2. 정적으로 모듈을 분석하고 트리쉐이킹 할 방법이 없습니다. -> 번들 사이즈 문제

  3. commonjs는 모듈을 동기적으로 로드하기 때문에 브라우저 환경에서 js코드가 화면에 렌더링되기까지 상당히 많은 시간이 소요됩니다.

3. AMD

  • commonjs는 모듈을 로드할 때 동기적으로 로드한다고 했습니다. AMD의 Asynchronouse에서도 알 수 있듯이 AMD에선 비동기적으로 모듈을 다루는 것에 대한 표준안을 다룹니다.

  • commonjs에서 분리되어 나온 그룹이기 때문에, 기본적으로 공통점을 지닌 점이 있습니다. require나 exports 같은 문법을 그대로 사용할 수 있습니다. AMD만의 특징이라면 define함수가 있습니다.

  • AMD의 define()함수(클로저를 이용한 모듈 패턴)을 이용하여 모듈을 구현하므로 전역변수 문제가 없다.

4. ESM

  • 이렇게 표준을 찾아 헤메던 중 ECMAScript에서 드디어 모듈 시스템에 대한 표준을 발표했습니다. 이는 현재 모든 브라우저에서 지원하고 있습니다.

ESM 사용방법

  1. script 태그에 type=module 추가하기
  2. node.js 환경이라면
  • package.json에 type:module 추가하기
  • 바벨 같은 트랜스파일러를 사용하여 import문을 require로 바꾸기

defer 키워드

  • esm에서는 defer 키워드가 default로 적용됩니다. nomodule이라고 명시해준 경우에 대해서만 선택적으로 defer 키워드가 적용됩니다.

ESM은 static하다

  • commonjs를 사용하거나, webpack을 활용하여 파일 확장자를 생략하는 경우도 있다.
  • 하지만 esm 자체는 파일 확장자를 강제하고 있다. (파일의 최상단에서만 선언이 가능하다.)
if(user.length > 0){
	const math = require('./math.js') 
    //... Do something.. 
}

// 기존의 require는 함수였기에 if문 중간에 넣어도 상관없지만
// import는 모듈 시스템이라 최상단에서만 선언을 강제하고 있다. 

스크립트 실행횟수

  • class script는 매번 다시 평가하지만, module은 단 한번만 평가하기 때문에 단 한번만 실행하게 됩니다.
<script src="classic.js"></script>
<script src="classic.js"></script>
<!-- 2번 실행 -->

<script type="module" src="module.mjs"></script>
<script type="module" src="module.mjs"></script>
<script type="module">import './module.mjs';</script>
<!-- 1번만 실행 -->

dynamic import

  • 종종 초기 렌더링 시 성능을 높이기 위해 특정 스크립트를 처음에 같이 로드하지 않고 필요한 순간에만 로드하고 싶을 수 있다. 그럴 때 dynamic import를 사용하면 된다.
button.addEventListener('click', event => {
    import('./dialogBox.js')
    .then(dialogBox => {
        dialogBox.open();
    })
    .catch(error => {
        /* Error handling */
    })
});

참고자료

https://velog.io/@yesbb/모듈-시스템의-역사-그리고-ESM

https://d2.naver.com/helloworld/12864

https://phsun102.tistory.com/52

profile
다른 사람들이 이해하기 쉽게 기록하고 공유하자!!

0개의 댓글