본문 바로가기

Front-End

모듈 번들러를 사용하는 이유

React 같은 최신 라이브러리나 프레임워크를 사용하면, 기본적으로 Webpack 이나 Vite 같은 모듈 번들러를 기본적으로 사용하게 됩니다.
그럴만한게, 보통 처음에 CRA 나 create vite 같은 명령어를 통해 React 프로젝트를 세팅하므로, 내가 모듈 번들러에 대해 이해를 하였든 하지 못하였든 사용하게 되는경우가 많습니다.

그동안 모듈 번들러가 정확히 하는일이 무엇인지, 모듈 번들러가 없을때와 있을때의 차이점은 무엇인지, 어떤 모듈 번들러를 선택해야하는지 어렴풋이 알고 있던것을 이제는 한번 정확히 알아 볼까 합니다.

먼저, 이를 알아보기 위해서는 배경지식이 조금 필요합니다.

자바스크립트 모듈 ( Javascript Module )


개발자가 자바스크립트로 어떠한 어플리케이션이나 기능등을 구현할때, 특정 규칙이나 단위로 파일을 분리하게 됩니다.
이때 분리하는 파일들을 '모듈' 이라고 부릅니다.

자바스크립트 모듈의 시초는, AMD, CommonJS, UMD 모듈 시스템이 있습니다.

이에 대한 자세한 내용은 아래 두 레퍼런스에 잘 나와 있습니다.

 

모듈 소개

 

ko.javascript.info

 

[JS] 모듈에 대한 이해와 사용법 - 배하람의 블로그

모듈(module)이란? 모듈이란 여러 기능들에 관한 코드가 모여있는 하나의 파일 로 다음과 같은 것들을 위해 사용한다. 유지보수성 : 기능들이 모듈화가 잘 되어있다면, 의존성을 그만큼 줄일 수 있

baeharam.netlify.app

요약해보자면, 아래와 같습니다.

- AMD : define()require() 구문을 사용하는 가장 오래된 모듈 시스템 중 하나로, 비동기 모듈 로딩방식으로 구현되어져 있으며 require.js라는 라이브러리를 통해 처음 개발되었습니다.
- CommonJS(cjs) : require 구문과 module.exports 를 사용하는 Node.js 서버를 위해 만들어진 모듈 시스템입니다. 
- UMD : AMD와 CommonJS와 같은 다양한 모듈 시스템을 함께 사용하기 위해 만들어졌습니다.
- ESModule(esm) : ES6 에서 추가된 import, export 구문을 사용하는 모듈 시스템 입니다.

주로 우리가 익숙한 모듈은 아마도 ESModule 과 CommonJS 모듈 일것이라고 생각합니다. node.js 에서는 아직 CommonJS 도 활발히 사용되고 있습니다. 라이브러리나 내가 쪼개어둔 모듈을 불러오는데 두 방식을 주로 활용하여 불러올 것입니다.

 

모듈의 핵심 기능

그렇다면, 모듈로 부르지 않는 '일반 스크립트' 와 '모듈' 은 무슨 차이점이 있는걸까요?

1. strict mode ( use strict ) 로 실행됨.
선언되지 않은 변수에 값을 할당하는 등의 코드는 에러를 발생시킵니다.

2. 모듈 레벨 스코프로 실행됨
모듈은 자신만의 스코프가 있습니다. 따라서 모듈 내부에서 정의한 변수나 함수는 다른 스크립트에서 접근할 없습니다.

3. 단 한번만 평가됨
동일한 모듈이 여러 곳에서 사용되더라도 모듈은 최초 호출 번만 실행됩니다. 실행 결과는 모듈을 가져가려는 모든 모듈에 내보내 집니다.

4. import.meta
import.meta 객체는 현재 모듈에 대한 정보를 제공해줍니다. 호스트 환경에 따라 제공하는 정보의 내용이 달라집니다.

5. this 는 undefined
모듈의 최상위 레벨 this 는 undefined 입니다. 일반 스크립트의 최상위 레벨 this 는 전역 객체 (window, global) 를 가리킵니다.

브라우저에서는 외부 리소스로 Javascript 모듈 파일을 요청할때, 일반 script 파일 요청으로 불러오면 module 로써 사용하지 않습니다.

<script type="module" src="foo.js"></script>

 

브라우저 에서의 모듈 기능

1. 지연 실행
모듈 스크립트는 항상 지연 실행 됩니다. defer 속성을 붙인것처럼 말이죠.

따라서 모듈 스크립트는 아래와 같이 동작합니다.

- 외부 모듈 스크립트를 다운로드할 때, 브라우저의 HTML 파싱이 멈추지 않습니다. 기타 리소스들을 불러오듯, 병렬적으로 불러옵니다.
- 모듈 스크립트는 HTML 문서가 완전히 준비될 때까지 대기하다가, HTML 문서가 완전히 만들어진 이후에 실행됩니다.
- 스크립트의 상대적 순서가 유지 됩니다. 문서상 맨위의 스크립트부터 차례로 실행됩니다.

2. async 어트리뷰트 사용

<script async type="module" src="bar.js"></script>

일반 스크립트는 외부 스크립트를 불러올때만 async 어트리뷰트가 유효합니다.
모듈 스크립트에서는 async 속성을 인라인 스크립트에도 적용할수 있습니다. 따라서 async 속성이 있다면 다른 스크립트나 HTML 파싱을 기다리지 않고 바로 실행됩니다.

3. 외부 스크립트
type="module" 속성이 붙은 외부 모듈 스크립트는 크게 두 가지 특징이 있습니다.

- src 속성값이 동일하면, 외부 스크립트는 한 번만 로드 및 실행됩니다.
- CORS 규칙이 적용됩니다.

4. 경로 없는 모듈 금지
브라우저 환경에서 import 는 반드시 상대 경로나 절대 경로 앞에 와야합니다.

5. 호환을 위한 nomodule 속성 사용

<script type="module" src="temp.js"></script>

<script nomodule>
	/* 위의 모듈을 불러올수 없을때 처리할 javascript */
</script>

구식 브라우저는 type="module" 을 이해하지 못하기 때문에, 이를 무시하고 넘어갑니다.
nomodule 속성을 사용하면 이런상황을 대비할수 있습니다.

 

모듈 번들러 ( Module Bundler )


먼저, 모듈 번들러를 사용하지 않고 위에서 알아본 자바스크립트 모듈만으로 모든 모듈을 사용한다면 어떻게 될까요?

React, Angular, Vue 와 같은 최신 라이브러리나 프레임워크를 사용하게 되면, 상당히 많은 수의 파일과 의존성 패키지들을 가지게 되는 것을 모두 경험 하셨을겁니다. 이러한 모듈들을 모두 처리하기란 정말 까다롭고 어려운 일입니다. 아래와 같은 문제점을 가지게 될수 있습니다.

Vite 의 공식문서에는, 'Vite 를 사용해야하는 이유' 라는 제목으로 모듈 번들러가 필요한 이유를 설명하고 있습니다.

 

Vite

Vite, 차세대 프런트엔드 개발 툴

ko.vitejs.dev

이런 문제점이 있었어요

브라우저에서 ESM(ES Modules) 지원하기 전까지, JavaScript 모듈화를 네이티브 레벨에서 진행할 없었습니다. 그래서 소스 모듈을 브라우저에서 실행할 있는 파일로 크롤링, 처리 연결하는 "번들링(Bundling)"이라는 해결 방법을 사용해야 했습니다.

배포 시 번들링 과정이 필요한 이유

이제 기본적으로 ESM 대부분의 환경에서 지원되지만, 프로덕션에서 번들 되지 않은 ESM 가져오는 것은 중첩된 import 인한 추가 네트워크 통신으로 인해 여전히 비효율적입니다(HTTP/2 사용하더라도). 프로덕션 환경에서 최적의 로딩 성능을 얻으려면 트리 셰이킹, 지연 로딩 청크 파일 분할( 나은 캐싱을 위해) 이용하여 번들링 하는 것이 좋습니다.

정리해보면, 아래와 같은 문제가 있을수 있습니다.

- 모듈간의 종속성을 개발자가 직접 관리해야함
- 모듈간의 실행순서를 모두 개발자가 직접 관리하고 지켜야함
- 모듈을 모두 일일히 등록 및 빠진것이 없는지 개발자가 직접 관리해야함
- 모든 브라우저가 모듈 시스템을 지원하는 것은 아님
- 이런 번거로운 관리를 해냈다고 하더라도, 웹 페이지가 로드될때 외부 모듈 스크립트를 모두 다운로드 받아야 해서, 네트워크 병목 현상이 발생할수 있음

이러한 문제점들을 해결하기 위해 등장한것이, 모듈 번들러 입니다.

 

모듈 번들러가 하는 역할

이름에서 알수 있듯이, 번들러는 모듈을 하나로 묶어 주는 역할을 합니다. 이를 번들링 (bundling) 이라고 합니다.
번들링 이외에도, 최적화를 하기위한 다양한 기능과 모듈 번들러가 하지못하는 일을 플러그인 등을 통해 추가적인 작업또한 할수 있습니다.
또한 번들링이 되지 않은 상태로, 개발자가 개발을 하기 편하게 도와주는 개발서버를 제공하는것도 큰 특징입니다.

1. 모듈 코드를 하나 혹은 여러개의 파일로 묶어냄

require 나 import 문 등으로 이루어진 자바스크립트 모듈 들을 탐색하여, 하나 또는 여러개 의 파일로 묶어 최종적으로는 모듈을 사용하지 않는 하나의 어플리케이션으로 묶어냅니다. 이는 모듈 시스템을 지원하지 않는 브라우저에서 동작하기 위함입니다.

개발 단계에서 개발자는, 코드 작업간 모듈 시스템을 이용하여 개발하고 실제 프로덕션 환경에서는 번들링된 하나의 어플리케이션으로 동작하게 할수 있게 됩니다.

이러한 덕분에, 브라우저는 자바스크립트 어플리케이션을 동작하기위해 한 번에 많은 요청을 하지 않아도 됩니다.

2. 번들된 코드를 압축 및 난독화

개발시에는 가독성을 위해 보통 개발자가 최대한 이해하기 쉽도록 작성됩니다. 하지만 이 코드를 브라우저에서 그대로 불러오게 된다면, 개발자가 작성한 코드가 그대로 클라이언트에게 노출되어 보안 취약점등을 쉽게 파악당할수도 있고, 개발한 코드를 외부에 비공개 하고 싶을수도 있습니다.

또한 코드 작성시에는 코드를 실행하는데에는 불필요한 공백, 띄어쓰기, 들여쓰기 등이 많이 포함되게 되어 자바스크립트 파일의 크기가 커지게 됩니다.

이러한 문제들을 해결하기 위해 대부분의 번들러들은 번들링 과정에서 코드를 정해진 규칙등에 의해 읽기 힘든 코드로 변환 시키고, 압축시킵니다.

3. 트랜스 파일러( transfiler ) 나 polyfill과 연계하여, 최신 자바스크립트 문법을 구형 브라우저에도 지원

자바스크립트는 시간이 지남에 따라, 기존에 없던 문법이 추가되고 계속해서 발전했습니다.

하지만, 이러한 자바스크립트 버전과 별개로 브라우저도 자체 버전이 존재하고, 구형 브라우저의 경우 최신 자바스크립트 문법을 지원하지 않는 브라우저도 존재합니다.

이러한 문제를 해결하기 위해 polyfill 이나 transfiler 플러그인과 연계하여 번들 과정에서 최신 문법의 자바스크립트를 지원하지 않을수 있는 구형 브라우저를 위해, 똑같은 기능을 가진 구 버전 자바스크립트로 transfiling 하거나 아예 없는 기능을 구 버전 자바스크립트로 구현한 polyfill 등을 제공하여 구형 브라우저에서도 실행가능한 어플리케이션으로 만들어 줍니다.

4. 코드 스플리팅 ( code splitting )

하나의 번들 스크립트로 모든 코드를 번들링 하게 되면, 당장에 필요하지 않고 미래에 사용될 스크립트또한 하나의 번들 파일에 포함되게 됩니다. 이런 스크립트를 페이지 로드시 다운로드 및 실행시키게 되면 불필요한 페이지 로딩 시간을 증가시킬수 있게 됩니다.

따라서, 부분적으로 필요한 경우에만 스크립트를 사용할수 있도록 번들러는 여러개의 청크(chunk) 파일로 번들파일을 구성이 가능하도록 해줍니다. 이를 코드 스플리팅 이라고 합니다.

대표적으로 React 에서는 React.lazy 등을 이용해, 초기 페이지 로드시 당장에 필요하지 않은 컴포넌트나 페이지들을 스플리팅 해둘수 있습니다.

5. 트리 쉐이킹 ( tree shaking )

개발간 라이브러리 등을 사용하게 되면, 보통 필요한 기능만을 이용하고 실제로는 사용하지 않는 기능들이 많이 있습니다. 또한 개발 하면서도 애플리케이션이 점점 커질수록 모듈간의 의존성이 커지게 됩니다. 시간이 지남에 따라 사용하지 않는 모듈들이 제거되지 않는 경우도 종종 생길수가 있게 됩니다.

이러한 문제를 해결하기 위한것이 바로 트리 쉐이킹 입니다. 트리 쉐이킹은 ESModule 의 static import 문을 사용해 모듈의 특정 부분만을 가져오는 방법입니다.

주의할점은, 이 트리 쉐이킹은 ES6 의 ESModule 문법으로 작성했을때에만 작동한다는 점입니다. CommonJS 모듈로 작성되거나 변환 되었을 경우, 번들러는 어떤 모듈이 사용중인지 아닌지 판단하여 제거하기가 어렵습니다.

6. HMR ( Hot Module Replacement )

개발중 코드의 변경이 일어날경우, 모듈 번들러 없이 개발하던 시절에는 페이지를 새로고침 해야지만 코드 변경사항을 확인할수 있었습니다. 그럴만한게, 결국 웹서버가 html 을 다시 응답하던 제공되는 js 파일을 새로 응답하던 해야지만 변경사항이 제대로 실행 될테니까요.

모듈 번들러는 자신이 제공하는 개발서버를 개발자가 이용하면, HMR 을 통해 코드 변경을 감지하고 자동으로 브라우저에 최신 코드를 반영한 모듈을 교체시켜 줍니다.

개발자는 새로고침을 하지 않아도 반영된 사항을 빠르게 확인 할수 있고 변경사항이 생긴 모듈만 업데이트 하기에 개발 속도가 빨라지는 좋은 개발자 경험을 제공합니다.

 

참고한 아티클 :

 

[Bundler] JavaScript 번들러 그리고 Webpack , Parcel , Rollup , Vite... (1)

안녕하세요. 정지현 입니다 :) 많은 JavaScript 프로젝트에서 모듈화를 위해 다양한 번들러를 사용하고 있습니다. CRA를 사용하다 보면 굳이 웹팩 설정까지 건들 일이 없었는데, 프로젝트가 고도화

velog.io

 

 

모듈 소개

 

ko.javascript.info

 

[JS] 모듈에 대한 이해와 사용법 - 배하람의 블로그

모듈(module)이란? 모듈이란 여러 기능들에 관한 코드가 모여있는 하나의 파일 로 다음과 같은 것들을 위해 사용한다. 유지보수성 : 기능들이 모듈화가 잘 되어있다면, 의존성을 그만큼 줄일 수 있

baeharam.netlify.app

 

 

Vite

Vite, 차세대 프런트엔드 개발 툴

ko.vitejs.dev

 

 

Vite

Vite, 차세대 프런트엔드 개발 툴

ko.vitejs.dev

 

 

[React with TS] Vite란? ( 사용하면 리액트 10배 빨라짐 )

오늘은 Vite에 대해 알아보겠습니다. [ Vite란? ] Vite(비트)는 프랑스어로 '빠르다'라는 뜻을 가진 단어로, 차세대 프런트엔드 개발 도구입니다. 이름처럼 빌드와 개발 서버 구동 시간이 매우 빠릅니

despiteallthat.tistory.com

 

 

vite란? : 빠른 빌드를 원한다면

vite [비트] 처음 프로젝트에 도입했을 때 팀내에서 이걸 바이트라고 불러야할지 비트라고 불러야할지부터 어색해서 처음에는 다들 바이트라고 부르다가, >프랑스어로 "빠르다(Quick)"를 의미하며,

velog.io

 

 

트리쉐이킹으로 자바스크립트 사이즈 줄이기

Table of Contents Introduction 오늘날의 웹 애플리케이션의 크기는 꽈거에 비해 꽤 커졌다. 특히, 자바스크립트의 비중이 그렇다. http archive의 자료를 보면, 자바스크립트 크기의 중위값을 본다면 데스

yceffort.kr

 

 

폴리필

 

ko.javascript.info