자바스크립트에서 모듈 시스템은 코드의 재사용성을 높이고, 의존성 관리, 성능 최적화를 위해 필수적인 개념이다. 과거에는 HTML에 <script> 태그를 사용해 모든 자바스크립트 파일을 로드했지만, 이는 여러 문제를 발생시켰다. 이후 ESM(ECMAScript Modules)과 CJS(CommonJS)라는 모듈 시스템이 등장하면서 이전의 문제들이 해결되기도 하였다.
01. 과거 모듈 시스템: <script> 태그를 사용한 스크립트 로드
과거에는 HTML 파일 내에서 <script> 태그를 통해 자바스크립트 파일을 포함하는 방식이 사용되었다.
하지만 이런 방식은 다음과 같은 문제를 발생시켰다.
성능 문제 : 모든 스크립트가 페이지 로드 시 한꺼번에 로드되고 실행되면서, 페이지 로딩 속도에 악영향. 스크립트가 동기적으로 로드되면 브라우저가 페이지 렌더링을 중단하는 문제 발생 -> <script> 로드 시 async 또는 defer를 사용하여 해결 가능하며 스크립트 로드 전략은 바로 아래에서 더 자세히 다룬다.
의존성 관리의 어려움 : 여러 스크립트 파일의 순서나 의존성 문제를 관리하기 어려움
전역 스코프 문제 : 모든 스크립트가 전역 스코프에서 실행되어 변수나 함수 이름 충돌이 빈번하게 발생
코드 재사용성 부족 : 모든 페이지에서 동일한 스크립트를 재사용하기 어려움
이런 문제들을 해결하기 위해 모듈 시스템의 필요성이 커졌으며, 이후 ESM(ECMAScript Modules)과 CJS(CommonJS) 두 가지 모듈 시스템이 등장하였다.
02. 스크립트 로드 전략
앞서 HTML 파일 내에서 <script> 태그를 통해 자바스크립트 파일을 포함하는 방식을 사용할 때, 모든 스크립트가 페이지 로드 시 한꺼번에 로드되며 실행되기 때문에 페이지 로딩 속도에 악영향을 미치는 문제가 있었다. 이를 해결하기 위해 <script> 로드 시 async 또는 defer를 사용하여 개선가능하다. 이외에도 <script> 태그는 스크립트의 로드와 실행 방식에 따라 다양한 속성들을 지원하며, 각각의 속성은 스크립트 로드 및 실행 순서, 성능에 영향을 미쳐 상황에 따라 알맞게 선택하는 것도 중요하다.
◼︎ <script></script>
<script> 태그에 아무 속성 없이 기본적으로 사용하는 경우, 스크립트의 로드 및 실행 방식은 동기적으로 이루어진다.
HTML 문서를 위에서 아래로 읽다가 <script> 태그를 만나면, HTML 파싱을 중단하고 스크립트를 다운로드하고 실행한 후, 다시 HTML 파싱을 계속한다.
장점 : HTML 파싱이 스크립트 실행 후에 계속되므로, 스크립트는 DOM 요소가 생성되기 전에도 접근 가능
단점 : 스크립트가 클수록 페이지 로딩이 느려지며, HTML 파싱이 중단되기 때문에 사용자 경험이 저하
◼︎ <script defer></script>
defer 속성은 스크립트를 비동기적으로 로드하지만, HTML 파싱이 끝난 후 실행한다.
스크립트는 HTML 파싱과 동시에 다운로드되지만, 파싱이 끝난 후에 실행되는 것이다.
HTML 문서가 전부 파싱된 후 DOM 트리가 완성된 상태에서 스크립트가 실행되므로 DOM에 안전하게 접근할 수 있다.
장점 : HTML 파싱과 스크립트 로드가 병렬로 진행되어 로딩 속도가 향상. DOM 트리가 완성된 상태에서 스크립트가 실행되므로 안정적
단점 : 모든 스크립트가 파싱 순서대로 실행
◼︎ <script async></script>
async 속성은 스크립트를 비동기적으로 로드하고, 로드가 완료되는 즉시 실행한다.
HTML 파싱을 중단하지 않고, 스크립트 로드와 파싱이 병렬로 진행되지만, 스크립트가 로드되는 순간 바로 실행한다.
즉, 스크립트는 비동기적으로 로그되며, 로드가 완료되면 HTML 파싱을 잠시 중단하고 스크립트를 실행한다.
스크립트는 로드 순서에 상관없이 먼저 로드된 스크립트가 먼저 실행된다.
장점 : HTML 파싱과 스크립트 로드가 동시에 이루어져 로딩 속도가 빠르며, 즉시 실행이 필요한 외부 스크립트에 적합
단점 : 스크립트 간 실행 순서가 보장되지 않으므로 의존성이 있는 스크립트에서는 문제 발생
◼︎ <script type="module"></script>
<script type="module">은 ESM을 사용하여 모듈 스코프에서 동작하는 스크립트를 로드한다. 모듈 스크립트는 자동으로 defer 속성을 포함하므로, 비동기적으로 로드되고 HTML 파싱이 완료된 후 실행된다.
import와 export 구문을 사용하여 다른 모듈을 가져오고 내보낼 수 있다.
장점 : 모듈 스코프가 제공되며, 전역 스코프 오염을 방지. 모듈 간 의존성이 명확하게 정의되므로 복잡한 프로젝트에서 코드 구조를 명확하게 유지 가능
단점 : 모든 최신 브라우저에서만 지원되며, 구형 브라우저에서는 추가적인 설정이 필요
03. ESM(ECMAScript Modules) : import / export
ESM은 ECMAScript6(ES6) 이후 표준화된 자바스크립트 모듈 시스템이다.
모듈 스코프를 제공하여 필요한 코드만 모듈 단위로 가져와 사용할 수 있게 한다.
💡 ESM 특징
비동기적 동작 : 파일을 비동기적으로 로드하여 성능을 개선
정적 의존성 분석 : import와 export 구문은 정적으로 분석되어 빌드시 최적화를 지원. 이를 통해 Tree-shaking과 같은 성능 최적화가 가능
Top-level Await 지원 : 모듈 레벨에서 비동기적으로 동작하므로 await 사용 가능
💡 ESM 구문
◼︎ Named Export 예시 : 고정된 명칭
여러 개의 변수를 export 할 수 있으며, 모듈 외부에서 명칭을 사용하여 가져온다.
// sum.js
export const sum = (x, y) => x + y;
export const minus = (x, y) => x - y;
// main.js
import { sum, minus } from './sum.js';
console.log(sum(2, 4)); // 6
◼︎ Default Export 예시 : 명칭 변경 가능
모듈에서 하나의 기본 값을 export 할 수 있으며, 가져올 때 이름을 임의로 정할 수 있다.
// sum.js
export default (x, y) => x + y;
// main.js
import add from './sum.js';
console.log(add(2, 4)); // 6
04. CJS (CommonJS) : require / module.exports
CJS는 Node.js 환경에서 널리 사용되는 모듈 시스템으로, 서버 측 자바스크립트에서 자주 사용된다.
ESM과 달리 CJS는 동기적으로 작동하며, 모듈을 런타임에 로드하고 실행한다.
💡 CJS 특징
동기적 동작 : 모듈을 로드할 때 동기적으로 동작
Top-level Await 미지원 : await 구문을 지원하지 않음
💡 CJS 구문
// sum.js
const sum = (x, y) => x + y;
module.exports = sum;
// main.js
const sum = require('./sum.js');
console.log(sum(2, 4)); // 6
05. 모듈 시스템 선택 기준
프론트엔드에서는 주로 ESM을 사용하며, 백엔드(Node.js)에서는 CJS가 사용된다.
◼︎ ESM
브라우저에서 표준으로 사용되며, 비동기적 모듈 로딩 및 성능 최적화를 지원한다.
Tree-shaking을 지원하여 브라우저 성능을 높이고, 빌드 시 모듈 관계를 정적으로 분석하는 것이 가능하다.
[JavaScript] 모듈시스템
자바스크립트에서 모듈 시스템은 코드의 재사용성을 높이고, 의존성 관리, 성능 최적화를 위해 필수적인 개념이다. 과거에는 HTML에 <script> 태그를 사용해 모든 자바스크립트 파일을 로드했지만, 이는 여러 문제를 발생시켰다. 이후 ESM(ECMAScript Modules)과 CJS(CommonJS)라는 모듈 시스템이 등장하면서 이전의 문제들이 해결되기도 하였다.
01. 과거 모듈 시스템: <script> 태그를 사용한 스크립트 로드
과거에는 HTML 파일 내에서 <script> 태그를 통해 자바스크립트 파일을 포함하는 방식이 사용되었다.
하지만 이런 방식은 다음과 같은 문제를 발생시켰다.
이런 문제들을 해결하기 위해 모듈 시스템의 필요성이 커졌으며, 이후 ESM(ECMAScript Modules)과 CJS(CommonJS) 두 가지 모듈 시스템이 등장하였다.
02. 스크립트 로드 전략
앞서 HTML 파일 내에서 <script> 태그를 통해 자바스크립트 파일을 포함하는 방식을 사용할 때, 모든 스크립트가 페이지 로드 시 한꺼번에 로드되며 실행되기 때문에 페이지 로딩 속도에 악영향을 미치는 문제가 있었다. 이를 해결하기 위해 <script> 로드 시 async 또는 defer를 사용하여 개선가능하다. 이외에도 <script> 태그는 스크립트의 로드와 실행 방식에 따라 다양한 속성들을 지원하며, 각각의 속성은 스크립트 로드 및 실행 순서, 성능에 영향을 미쳐 상황에 따라 알맞게 선택하는 것도 중요하다.
◼︎ <script></script>
<script> 태그에 아무 속성 없이 기본적으로 사용하는 경우, 스크립트의 로드 및 실행 방식은 동기적으로 이루어진다.
HTML 문서를 위에서 아래로 읽다가 <script> 태그를 만나면, HTML 파싱을 중단하고 스크립트를 다운로드하고 실행한 후, 다시 HTML 파싱을 계속한다.
◼︎ <script defer></script>
defer 속성은 스크립트를 비동기적으로 로드하지만, HTML 파싱이 끝난 후 실행한다.
스크립트는 HTML 파싱과 동시에 다운로드되지만, 파싱이 끝난 후에 실행되는 것이다.
HTML 문서가 전부 파싱된 후 DOM 트리가 완성된 상태에서 스크립트가 실행되므로 DOM에 안전하게 접근할 수 있다.
◼︎ <script async></script>
async 속성은 스크립트를 비동기적으로 로드하고, 로드가 완료되는 즉시 실행한다.
HTML 파싱을 중단하지 않고, 스크립트 로드와 파싱이 병렬로 진행되지만, 스크립트가 로드되는 순간 바로 실행한다.
즉, 스크립트는 비동기적으로 로그되며, 로드가 완료되면 HTML 파싱을 잠시 중단하고 스크립트를 실행한다.
스크립트는 로드 순서에 상관없이 먼저 로드된 스크립트가 먼저 실행된다.
◼︎ <script type="module"></script>
<script type="module">은 ESM을 사용하여 모듈 스코프에서 동작하는 스크립트를 로드한다. 모듈 스크립트는 자동으로 defer 속성을 포함하므로, 비동기적으로 로드되고 HTML 파싱이 완료된 후 실행된다.
import와 export 구문을 사용하여 다른 모듈을 가져오고 내보낼 수 있다.
03. ESM(ECMAScript Modules) : import / export
ESM은 ECMAScript6(ES6) 이후 표준화된 자바스크립트 모듈 시스템이다.
모듈 스코프를 제공하여 필요한 코드만 모듈 단위로 가져와 사용할 수 있게 한다.
💡 ESM 특징
💡 ESM 구문
◼︎ Named Export 예시 : 고정된 명칭
여러 개의 변수를 export 할 수 있으며, 모듈 외부에서 명칭을 사용하여 가져온다.
◼︎ Default Export 예시 : 명칭 변경 가능
모듈에서 하나의 기본 값을 export 할 수 있으며, 가져올 때 이름을 임의로 정할 수 있다.
04. CJS (CommonJS) : require / module.exports
CJS는 Node.js 환경에서 널리 사용되는 모듈 시스템으로, 서버 측 자바스크립트에서 자주 사용된다.
ESM과 달리 CJS는 동기적으로 작동하며, 모듈을 런타임에 로드하고 실행한다.
💡 CJS 특징
💡 CJS 구문
05. 모듈 시스템 선택 기준
프론트엔드에서는 주로 ESM을 사용하며, 백엔드(Node.js)에서는 CJS가 사용된다.
◼︎ ESM
◼︎ CJS
'ASAC > Front-End' 카테고리의 다른 글