CommonJS(CJS)
module.exports = { ... } // 모듈 내보낼 때
const utils = require('utils'); // 모듈 가져올 때
NodeJS에서 지원하는 모듈 방식으로 초기 Node 버전부터 사용되었다.
별도의 설정이 없다면 CJS가 기본값이다.
require()는 즉시 스크립트를 실행하는 구조이고, 동기적으로 작동하기 때문에 import 순서에 따라 스크립트를 실행하고 module.exports에 설정된 값만을 리턴한다.
ES Modules
export.default = () => { ... } // 모듈 내보낼 때
import utils from 'utils'; // 모듈 가져올 때
ECMAScript에서 지원하는 방식이다.
package.json에 "type": "module"을 설정해야 사용할 수 있다.
비동기 환경에서 실행되기 때문에 스크립트를 바로 실행하지 않고, import/export 구문을 찾아 스크립트를 파싱한다. 참고로 파싱 단계에서는 에러를 감지할 수 있다.
모듈을 병렬로 다운로드하지만, 실행은 순차적으로 한다.
import/export를 지원하지 않는 브라우저가 있기 때문에 번들러가 필요하다.
모듈 (import, expot)
모듈은 함수와 변수를 모듈 스코프에 넣어 관련있는 값들을 한 곳에 모을 수 있다.
그리고 모듈 스코프를 통해 모듈의 함수 사이에서 변수를 공유할 수 있다.
1. 코드를 독립적으로 작동할 수 있는 단위로 나눌 수 있고,
2. 코드 조각들을 조합해 같은 모듈들의 모음으로 다양한 애플리케이션을 만들 수 있다.
현재(2023) 활발하게 사용되는 모듈 시스템은 CommonJS(CJS)와 Node.js, EcmaScript modules(ESM)이다.
ES 모듈 동작 방식
코드들이 작성된 파일 자체는 브라우저가 사용할 수 없다.
1. 분석 후 모듈 레코드라고 하는 데이터 구조로 변환이 필요하다.
2. 그런 후 모듈 레코드를 모듈 인스턴스로 변환해야 한다.
인스턴스는 코드와 상태, 2가지를 결합한다.
여기서 코드는 명령어의 집합, 상태는 그 시점의 실제 변수값이라고 생각하면 된다.
각 모듈은 모듈 인스턴스가 필요하고, 전체 파일이 모듈 인스턴스의 전체 그래프를 그리는 것을 시작으로 모듈을 불러오게 된다.
ES 모듈은 3가지 단계로 나뉘어 진행된다.
1. 구성 - 모든 파일을 찾아 다운로드(로더의 역할)하고 모듈 레코드로 구문분석한다.
2. 인스턴스화 - export된 값을 모두 배치하기 위해 메모리에 있는 공간을 찾는다.(아직 실제 값을 채우지는 않음) 그 다음 export와 import들이 메모리 공간들을 가리키도록 한다.(linking)
3. 평가 - 코드를 실행하여 상자의 값을 변수의 실제 값으로 채운다.
좀 더 구체적으로 살펴보자.
1. 구성
구성 단계에서는 각 모듈에 대해 3가지 일이 일어난다.
1. 모듈이 들어있는 파일을 어디서 다운로드할지 확인하고(HTML에서는 스크립트 태그를 사용해서 로더에게 어디에서 진입점 파일을 찾을 수 있는지 알려준다.)
<script src="main.js" type="module">
2. 파일을 가져오고(url을 통해 다운로드하거나 파일 시스템에서 불러오기)
참고로 여러 모듈이 특정 모듈에 의존하고 있어도 모듈 파일은 1번만 불러 들여진다.(캐시) 로더는 모듈맵을 이용해서 캐시를 관리하고, 각 전역은 별도의 모듈맵에서 모듈을 관리하기 때문이다.
3. 파일을 모듈 레코드로 구문분석한다.
모듈 레코드가 만들어지고 나면, 모듈맵에 추가된다. 그럼 필요할 때마다 로더가 모듈맵에서 모듈 레코드를 가져올 수 있다.
모든 모듈은 코드 상단에 "use strict"가 있는 것처럼 구문분석된다.
브라우저에서는 스크립트 태그 내부에 type="module"이라고 적어두면 해당 파일은 모듈로 구문분석해야한다고 인식한다.
2. 인스턴스화
인스턴스는 코드와 상태를 결합한다. 상태는 메모리에 있기 때문에 모든 것을 메모리에 연결한다고도 할 수 있다. 인스턴스화 단계에서는 export/import 한 변수에 대한 모든 인스턴스와 메모리 위치가 연결된다.
1. JS 엔진은 모듈 환경 레코드를 생성하고 이를 통해 모듈 레코드의 변수를 관리한다.
2. 모든 export에 대해 메모리에 있는 상자를 찾는다.
3. 모듈 환경 레코드는 각 export와 연관된 메모리의 상자를 추적한다.
참고로 export 된 함수 선언은 인스턴스화 단계에서 초기화가 된다.
export들을 먼저 연결해서 import들이 모두 각각의 export들에 연결되는 것을 보장한다. (반면, CommonJS는 전체 export 객체가 내보낼 때 복사된다. 그래서 나중에 export하는 모듈이 해당 값을 변경하면, 그 모듈을 import하는 모듈은 해당 변경 사항을 알 수 없다.) 하지만 ES 모듈에서는 라이브 바인딩이라는 것을 사용해 두 모듈 모두 메모리의 같은 위치를 가리키게 된다. 라이브 바인딩을 사용하는 이유는 코드를 실행하지 않고 모든 모듈을 연결할 수 있기 때문이다.
3. 평가
메모리 상자들을 채우는 것이다. JS 엔진은 함수 외부 코드인 최상위 레벨 코드를 실행하여 이를 수행한다.
혹시나 생길 수 있는 부작용 가능성(평가 도중에 모듈이 서버에 무언가를 요청하는 등..)때문에 모듈은 한 번만 평가하도록 한다. 이것이 모듈 맵을 사용하는 다른 이유이기도 하다. 모듈 맵은 표준 URL로 모듈을 캐시하기 때문에 각 모듈에 대해 하나의 모듈 레코드만 있어 각 모듈이 한 번만 실행된다.
참고자료
'JavaScript > 그 외' 카테고리의 다른 글
Google OAuth 2.0 로그인 구현하기 (0) | 2024.01.15 |
---|
댓글