▶ES Module이란?
ES Module은 ES6부터 도입된 모듈 시스템
export 및 import문을 사용해 분리되어 있는 자바스크립트 파일 간의 접근을 가능하게 만들어줌
▶ ES Module 등장배경
1. 기존의 웹은 자바스크립트 비중이 크지 않았기 때문에 큰 스크립트가 필요하지 않았다.
2. 웹의 발전에 따라 점점 자바스크립트의 중요도가 커지고, 여러 개의 스크립트 파일을 쓰면서 상호작용을 해야 했다.
3. 이를 처리하기 위해 jQuery등으로 해결(각각의 script 파일을 전역 스코프처럼 사용)했지만 여러 문제점이 발생했다.
- script 파일들을 올바른 순서대로 정렬해야 하기 때문에 순서가 뒤틀리면 에러를 발생시킴
- 하위에 있는 script가 상위 script의 상태를 쉽게 변경시키는 '전역 오염'이 발생하기 쉬움
- 모든 script 파일에서 전역 스코프에 있는 변수들에 접근할 수 있기 때문에 하나의 script가 어떤 script를 의존하고 있는지 파악하기 어려움
- 이와 같은 문제로 유지보수가 어려움
4. 이러한 문제를 해결하기 위해 각 파일간의 상호작용을 모듈화함
▶ 모듈화
모듈은 함수와 변수를 모듈 스코프에 넣고, 각 함수는 함수 스코프를 가짐
export문을 사용해 해당 변수와 함수를 다른 모듈에서 import문을 사용해 의존할 수 있도록 해줌
장점
1. export-import의 명시적인 의존성 관계로, 하나의 모듈이 제거되면 어떤 모듈이 손상됐는지 파악하기 쉬움
2. 코드들은 각각 독립적으로 동작할 수 있는 단위로 나누기 용이함(모듈의 재사용성해 다양한 작업 가능)
3. export-import로 관계되어 있지 않는 모듈은 서로 오염을 일으키지 않음
-> 코드를 의미있는 단위로 분리해서 사용해 유지 보수하기 쉽고, 코드의 재사용성을 증가시킴
node.js에는 RequireJS와 같은 CommonJS를 제공하고 있으며, 그 외에도 AMD 기반 모듈 시스템(대표적으로 RequireJS), Webpack(모듈 번들러), Babel(트랜스파일러) 같은 모듈 기반 시스템과 같이 모듈 사용을 가능하게 만들어주는 자바스크립트 라이브러리와 프레임워크가 존재한다.
기존에는 라이브러리에 의존해야 했던 모듈 기능을 ECMAScript 6부터 네이티브 자바스크립에서도 지원하기 시작해 여러 브라우저에서도 모듈 로딩을 최적화할 수 있도록 모듈 기능을 지원한다.
▶ ES Module 동작 방식
- 의존성 간의 연결은 import문이 작성된 코드에서 발생
- import문은 브라우저 또는 node가 어떤 코드를 불러와야 하는지 인식하는데 사용됨
- impor문에서 지정한 파일(일반적으로 url)이 의존성 그래프의 진입점(entry point)가 되고 연결되어 있는 import문을 따라가면서 의존성 그래프가 그려짐
ES Module이 동작하기 위해서는 브라우저가 사용할 수 있도록 모듈 레코드(Module Record)라고 하는 데이터 구조로 변환 작업이 필요한데, 이러한 모듈화 작업 과정은 구성 → 인스턴스화 → 평가의 3단계를 거침
모듈화 작업 3단계
1. 구성 단계 : 모든 파일을 찾아 다운로드하고 모듈 레코드로 구문 분석(파일을 읽어들여 export, import문의 데이터를 구별함)
1. 모듈이 들어있는 파일을 어디서 다운로드 할 것인지 확인
2. URL을 통하거나 파일 시스템을 이용해 파일을 가져옴
3. 파일을 모듈 레코드로 구문 분석
파일을 불러오는 역할을 로더(loader)가 수행하는데, 사용중인 플랫폼에 따라 다른 로더를 가질 수도 있지만 브라우저의 경우 HTML 명세를 따른다.
로더는 스크립트 태그에서 진입점 파일을 찾을 수 있는 단서를 얻고 import문의 모듈 지정자(module specifier)를 통해 다음 모듈의 의존성을 파악한다.
또한 모듈 맵을 이용해 각 모듈의 캐시를 관리하기도 한다.
- 모듈 해석: 브라우저는 <script type="module"> 태그를 통해 모듈 파일을 찾고, 파일 내의 export, import 문을 분석합니다.
- 의존성 그래프 생성: 모듈 간의 의존 관계를 파악하여 의존성 그래프를 생성합니다. 이 그래프는 모듈을 로딩하고 실행하는 순서를 결정하는 데 사용됩니다.
- 모듈 레코드 생성: 분석된 정보를 바탕으로 각 모듈에 대한 모듈 레코드를 생성합니다. 모듈 레코드에는 모듈의 이름, 경로, export된 값, import된 모듈 등의 정보가 포함됩니다.
2. 인스턴스화 : export, import를 모두 배치하기 위한 메모리 공간들을 찾음(실제 값은 아직 채워지지 않고 메모리 공간만 가리키고 있음)
먼저 독립적인 모듈(import가 없는 모듈)부터 시작하여 의존 관계에 따라 순차적으로 모듈들을 연결
▶ 라이브 바인딩
CommonJS의 경우 import한 값은 메모리에 복제된 값이 올라가 수정이 발생해도 서로 변경사항을 알 수 없음
ES 모듈은 라이브 바인딩을 통해 모듈끼리는 같은 메모리 주소로 접근해 변경사항을 알 수 있지만, import하는 모듈에서 export된 값 자체를 수정할 수는 없음(모듈이 객체를 가져오는 경우에는 해당 객체의 프로퍼티의 값은 변경 가능)
*객체의 프로퍼티 수정
- CommonJS에서는 복제된 객체의 프로퍼티를 수정
- ES 모듈에서는 원본 객체의 프로퍼티를 수정
- 메모리 할당: 각 모듈 레코드에 해당하는 메모리 공간을 할당합니다. 이때 export된 값을 저장할 공간도 함께 할당됩니다.
- 연결: 모듈 간의 연결 관계를 설정합니다. 즉, import된 모듈이 가리키는 메모리 공간을 연결합니다.
3. 평가 단계 : 코드를 실행해 변수에 실제 값을 채움
코드를 실행해 메모리 공간에 실제 값을 채움
자바스크립트 엔진은 함수 외부 코드인 최상위 레벨 코드를 실행하여 이를 수행
평가는 수행한 횟수에 따라 다른 결과를 가질 수 있기 떄문에 한 번만 평가하도록 설계됨(모듈 맵을 이용해 깊이 우선 탐색을 통해 처리) →
- 코드 실행: 실제 코드를 실행하여 변수에 값을 할당하고, 함수를 호출합니다.
- 값 채우기: 인스턴스화 단계에서 할당된 메모리 공간에 실제 값을 채웁니다.
▶ ES Module은 순환 의존성 지원
순환 의존성이란 두 개 이상의 모듈이 서로를 참조하는 경우
ES Module에서는 라이브 바인딩을 사용하기 때문에 순환 구조가 발생해도 빈 객체를 반환함
하지만 CommonJS는 라이브 바인딩이 아니고 사본을 메모리에 넣기 때문에 사이클이 생기면 처리할 수 없음
▶ ES Module 사용 방법
- 함수, var, let, const 키워드를 사용한 변수, 클래스를 export하거나 import 할 수 있음
- export문은 최상위 항목이어야 함 (ex. 함수 안에서 export문을 사용할 수 없음)
- 내보내거나 가져올 때는 중괄호({})로 묶을 수 있음
- 스크립트를 모듈로 선언하려면 <script> 요소에 type="module"을 포함시키면 됨
▶ ESM(ECMAScript Modules) VS CJS(CommonJS)
항목 | ESM(ECMAScript Modules) | CJS(CommonJS) |
정의 | ECMAScript (ES6 이상)에서 정의된 모듈 시스템 | Node.js에서 사용하기 위해 만들어진 모듈 시스템 |
로딩 방식 |
정적(Static) import와 export 구문이 소스 코드의 상단에 미리 정의 |
동적(Dynamic) require() 함수를 통해 필요한 시점에 모듈 로드 |
분석 시점 |
컴파일 단계(코드가 번들링 되는 시점) 번들러는 이 정보를 사용하여 모듈 간의 의존성을 파악하고 번들링 단계에서 트리쉐이킹에 활용 |
런타임(코드가 실행되는 시점) |
문법 | import / export | require() / module.exports |
부분 로딩 |
쉬움(특정 모듈에서 파일단위로 default export된 일부 모듈만 가져오거나 하나의 모듈 내에서도 named export로 모듈을 부분로딩 할 수 있음) |
어려움(모듈 전체를 가져와야 함) |
트리 쉐이킹 |
효율적(사용하지 않는 코드 제거 가능) | 비효율적(동적 특성 때문에 어떤것을 사용하지 않는지 파악하기 어려워 제거 불가능) |
ES Module은 번들링 시점에 모듈 간의 의존성을 파악하고 있기 때문에 불필요한 모듈들을 알 수 있음
그러므로 번들링 단계에서 의존성이 없는 불필요한 모듈을 효과적으로 제거함으로서 번들링 크기를 크게 줄일 수 있음(초기 렌더링 속도 단축)
*트리 쉐이킹 : 모듈 번들러가 프로젝트에서 사용되지 않는 코드를 자동으로 제거하여 번들 크기를 최소화하는 프로세스
▶ 모듈의 생명주기
1. 파싱 → 2. 분석 → 3. 번들링 → 4. 컴파일 → 5. 실행(런타임)
Reference
ES-Module(ESM, ECMAScript Modules)
JavaScript modules
'프론트엔드 > JavaScript' 카테고리의 다른 글
디바운스(Debounce), 쓰로틀(Throttle) (0) | 2024.11.17 |
---|---|
[JS] Promise 객체 (0) | 2024.11.15 |
[모던 자바스크립트 Deep Dive] 05장 - 표현식과 문 (3) | 2024.11.09 |
[모던 자바스크립트 Deep Dive] 04장 - 변수 (1) | 2024.11.05 |
CSP(Content Security Policy) (0) | 2024.11.04 |