Search

자바스크립트 범용화와 모듈 시스템

subtitle
ESM, CommonJS
Tags
module
모던 자바스크립트
Created
2021/03/06 07:35

모듈의 필요성

모듈은 기능에 따라 파일 별로 분리한 코드 조각을 말한다. 한 파일 내에 모든 코드를 관리한다면 변수나 함수가 중복되거나 다른 기능의 코드끼리 영향을 줄 수 있다. 따라서 기능 별로 코드를 분리해서 여러 파일로 나눈 뒤, 최종적으로 실행할 main 함수가 있는 파일에서 코드를 불러와 사용한다.
이때 분리된 파일끼리도 서로 영향이 없어야 한다. 예를 들어 A와 B 파일에서 전역변수 x를 선언했을 때 x가 덮어씌워지거나 하면 안된다. 따라서 각 모듈은 자신만의 모듈 *스코프를 가져야 한다.
*스코프: 코드가 영향을 끼치는 범위

자바스크립트의 모듈 시스템

자바스크립트는 원래 웹페이지 내 보조 작업을 처리하기 위한 언어였다. 때문에 다른 프로그래밍 언어와 달리 모듈 시스템이 없다. 브라우저 내에서 자바스크립트는 여러 파일로 분리해도 하나의 파일 안에 있는 것처럼 전역(window)을 공유한다. 아래 코드를 실행하면 변수 x가 덮어쓰여진다. 다른 파일의 변수를 쓰기 위해 아래와 같은 방법을 사용한다해도, 어느 파일을 먼저 로드할지의 순서가 중요해진다.
// a.js var x = 1; console.log(window.x); // b.js var x = 2; console.log(window.x);
JavaScript
복사
<!DOCTYPE html> <html> <head> </head> <body> <script src="a.js"></script> <script src="b.js"></script> </body> </html>
HTML
복사

자바스크립트 범용화

브라우저 외에 자바스크립트를 범용적인 환경에서 사용하기 위해서는 이러한 모듈 시스템 문제가 먼저 해결되어야 했다. 이에 자바스크립트 모듈화를 위해 CommonJS, AMD가 등장했다. (AMD는 여기서 따로 설명하지 않는다)

CommonJS

not just for browsers any more! CommonJS의 홈페이지에 있는 슬로건이다.
The intention is that an application developer will be able to write an application using the CommonJS APIs and then run that application across different JavaScript interpreters and host environments. — commonjs.org
commonjs는 표준 API를 사용해 개발자가 어플리케이션을 작성하고, (브라우저 외에) 다른 자바스크립트 인터프리터나 호스트 환경에서 해당 어플리케이션을 실행할 수 있도록 하기 위해 만들어졌다.
CommonJS는 Mozila 엔지니어인 Kevin Dangoor에 의해 처음 시작되었다. commonjs 이전에도 helma나 jaxer같은 서버 사이드 자바스크립트를 위한 프로젝트가 있었지만 kevin은 서버사이드 자바스크립트를 위한 표준적이고 편리한 생태계의 필요성을 느꼈다. 그가 쓴 'What Server Side JavaScript needs' 글을 보면 그런 생태계를 구축하기 위해 필요한 것들이 나열되어 있다.
cross-interpreter 표준 라이브러리: 정규식이나 날짜 등 표준 라이브러리가 있긴 하지만 파일, 폴더에 관해서는 없다. JS core나 V8 등 어디서나 같은 API가 동작하면 좋을 것이다.
표준 인터페이스의 필요성: Python의 *DBAPI처럼 데이터베이스에 연결하는 모듈에 대한 표준 인터페이스가 필요하다.
모듈을 로드하는 표준적인 방법 필요: 네임스페이스를 사용하는 방법이 있지만, 프로그래머틱하게 모듈을 로드하는 방법은 없다.
배포를 위한 코드 패키징, 패키지 설치 방법 필요: 개발 환경을 설정, 배포하고 다른 사람들이 사용할 수 있도록 작성한 코드를 패키징하는 편리한 방법이 필요하다.
패키지 저장소: 사람들이 아무리 많은 라이브러리를 설치해도 의존성이 문제되지 않고, 편리하게 패키지 설치를 할 수 있어야 한다.
*DBAPI: 데이터베이스에 접근하는 모듈들 간의 유사성을 제공하는 인터페이스
어디서나 실행될 수 있는 라이브러리는 더 많은 개발자를 모으고, 라이브러리와 함께 언어도 성장할 수 있게 된다. 하지만 이를 위해 모듈 시스템이 먼저 정립되어야 했고, server js(commonjs) 그룹은 이러한 배경으로 등장하게 됐다. 지금은 NodeJS나 대부분의 npm 패키지들은 commonJS의 스펙을 따르고 있다.

CommonJS의 동작

commonjs는 require로 모듈이 이미 로드 되어있는지 확인하고, 인터페이스를 반환한다. 모듈 로더가 모듈 코드를 함수로 감싸기 때문에 모듈은 자동으로 자신만의 스코프를 갖는다. 디펜던시에 접근할 때는 require를 쓰면 되고, 코드를 재사용할 때는 인터페이스를 export 오브젝트에 넣으면 된다.

CommonJS의 문제점

CommonJS가 사실상 표준(De Facto)이긴 하지만, 모듈 시스템은 *덕 테이프 해킹으로 남아있다. require로 필요한 디펜던시를 불러오기 전까지는 아무것도 실행할 수 없다. 서버사이드 같은 경우는 이미 로드된 모듈의 경우 바로 사용할 수 있기 때문에 문제가 없지만, 브라우저 같은 경우는 이 작업으로 인해 지연이 발생하게 된다.
*덕테이프 해킹: 덕테이프는 부러지고 찢어진 것을 대부분 고칠 수 있다. 하지만 뒷모습이 깔끔하지 않고 불완전하다.

ESM (ES6 Module)

이러한 이유로 2015년에는 표준 자바스크립트에서도 ES Modules라는 독자적인 모듈 시스템을 추가했다. (여기서 ES는 ECMAScript의 ES를 의미한다.) 디펜던시에 접근하기 위해 함수를 호출하지 않고 import 키워드를 사용한다.
ESM은 IE와 같은 구 브라우저를 제외하면 대부분의 브라우저에서 지원하고 있다. 브라우저에서는 script 태그에 type="module" attribute를 추가하면 된다. 일반적인 자바스크립트 파일과 구분하기 위해 ESM 파일은 .mjs 확장자를 사용할 것을 권장한다.

ESM 동작 원리

ESM 시스템은 구성, 인스턴스화, 평가 세 단계로 이루어진다.
구성
모듈은 종속성에 따라 그래프로 그려진다. 각 파일이 어떻게 연결되어있는지 그래프로 그려본다고 생각하면 된다. 이때 종속성을 연결하는 것은 import 키워드이다. import로 연결된 파일 자체는 브라우저가 사용할 수 없으므로 *모듈 레코드라는 데이터 구조로 변환한다. 구성 단계에서는 import하는 모든 파일을 찾아 로드하고 모듈 레코드로 변환하기 위해 구문을 분석한다.
*모듈 레코드: export, import에 대한 정보가 담긴 데이터 구조
인스턴스화
import할 모든 값을 할당할 메모리 공간을 찾는다. export, import 모두 해당 메모리를 가리키도록 한다. 이것을 연결이라고 한다. 각 모듈 레코드는 디펜던시가 걸려 있는 다른 모듈 레코드를 가리킨다.
평가
코드를 실행하여 변수의 실제 값으로 메모리를 채운다.
구성, 인스턴스화, 평가는 개별적으로 수행할 수 있다. commonjs와는 다르게 비동기적으로 수행할 수 있다. 하지만 반드시 비동기적으로 수행되는 것은 아니다. ESM 스펙의 경우 구문 분석, 인스턴스화, 평가 방법에 대해서는 나와있지만 파일을 로드하는 방법은 나와있지 않다. 실행하는 환경에 따라 파일 로더가 다르며 로더의 방식에 따라 동기적으로 수행될 수도 있다. 브라우저에서 실행한다면 로더는 html spec에 따라 모듈을 로드한다.
더 자세한 원리를 알고 싶으면 여기를 참고할 것. 나도 다 읽어보지는 못했다. 이에 대해 공부하고 글로 정리한다면 댓글로 알려주시길.. 구경하러 갈테니. ㅎㅎ

모듈 스코프

ESM은 파일 내에서 독자적인 모듈 스코프를 가질 수 있도록 한다. 모듈 내의 var, let, const 키워드로 선언한 변수는 더 이상 전역 변수도 아니고, windows 객체의 프로퍼티도 아니다.
유럽의 전역변수는 아니다! 지구의 windows 객체 프로퍼티는 아니다! | source: youtube

import

다른 모듈이 export한 코드를 로드하려면 import 키워드를 사용한다. 처음 어플리케이션의 진입점이 되는 파일은 script 태그로 로드해야한다. 하지만 엔트리 파일에서 import한 모듈은 script 태그로 로드하지 않아도 된다.

References

모던 자바스크립트 deep dive(책)