웹팩을 왜 쓸까?

Why Webpack? 웹팩 같은 모듈 번들러는 왜 필요할까?

2022-12-04에 씀

웹팩은?

웹팩은 자바스크립트 모듈 번들러이다. 프로젝트 내에서 사용하는 에셋과 파일을 합쳐서 한 덩어리의 번들로 만들어 준다.

왜 번들링이 필요한가?

어떤 웹 사이트가 사용자와의 상호작용을 지원하려면 자바스크립트 스크립트를 사용하는 것이 일반적이다. 많은 기능을 제공하는 사이트일수록 자바스크립트 코드의 양도 많아질 것이다.

이런 코드를 하나의 파일에서 관리한다는 것은 불가능에 가깝다. 파일의 어느 부분에 어떤 코드가 있는지 확인하기도 어렵고, 다른 개발자와 협업하기도 매우 불편할 것이다.

코드를 적절한 파일 단위로 나누어 작업할 때, 나눠진 파일 각각을 모듈이라고 부른다. 이 모듈은 다른 모듈이 가진 값이나 함수를 가져다 쓰는 식으로 서로 상호작용하며 프로그램을 구성한다.


자바스크립트에는 모듈 관련 표준 문법이 없었다. 즉, 어떤 모듈에서 명시적으로 값을 내보내고 다른 모듈에서 그 값을 가져다 쓰는 문법이 지원되지 않고 있었다.

그래서 모듈 간에 값을 공유하려면 전역 스코프에 값을 넣어서 가져다 써야 했다.

1<!-- Module A -->
2<script>
3 let a = 123;
4 window.callA = () => console.log(a);
5</script>
6<!-- Module B -->
7<script>
8 window.callA(); // 123
9</script>

html에 script 태그로 삽입된 자바스크립트 코드들은 모두 전역 스코프를 공유하기 때문에, 다른 모듈이 전역 스코프에 넣은 값을 가져다 쓸 수 있다.

브라우저가 화면을 렌더링하기 위해 html을 파싱할 때, 파서가 script 태그를 만나면 자바스크립트 엔진에게 해당 태그의 자바스크립트 코드 실행을 요청한다. 코드 실행은 html 상에서 script 태그가 삽입된 순서대로 순차적으로 실행된다.

그 말은, A 모듈이 전역 스코프에 넣어준 값을 B 모듈이 가져다 쓰고 싶다면, A 모듈의 코드는 B 모듈의 코드보다 먼저 실행되어야 한다는 것이다. 그렇지 않으면 B 모듈이 참조하려는 A 모듈의 값은 undefined 상태일 것이다.

1<!-- Module B -->
2<script>
3 window.callA(); // Uncaught TypeError: window.callA is not a function
4</script>
5<!-- Module A -->
6<script>
7 let a = 123;
8 window.callA = () => console.log(a);
9</script>

따라서 html 상에 script 태그가 삽입되는 순서는 의존성에 따라 엄격하게 관리되어야 한다.


그런데 전역 스코프를 사용하는 방법은 어느 파일에서 어떤 값을 내보내고, 어느 파일에서 어떤 값을 가져다 쓰는지 관리하기가 어렵다. 그래서 RequireJS, CommonJSAMD등등의 모듈 시스템이 등장했고, 자바스크립트에서도 ES6부터 ESM이라고 불리는 모듈 시스템을 제공한다.

하지만 자바스크립트에서 모듈 시스템을 표준 문법으로 지원하고 있음에도, 일부 오래된 라이브러리는 CommonJS 방식 등의 다른 모듈 시스템을 사용할 수도 있다. 만약 이런 라이브러리를 사용한다면 ESM 방식을 사용하기는 어려울 것이다.


또 다른 문제로, 최근에 거의 필수로 자리잡은 타입스크립트는 브라우저가 이해할 수 없는 언어이다. 타입스크립트가 브라우저에서 실행되기 위해서는 자바스크립트로 변환되어야 한다. SASS와 같은 스타일 전처리기도 마찬가지로 CSS 형식으로 변환되어야 한다.


정리하면, 자바스크립트 모듈만 사용하기엔 이런 점이 불편하다.

이런 작업을 모듈 간의 의존성에 따라 알아서 관리해 주면 편하지 않을까? 그 고민을 해결해주는 도구가 웹팩이다.

웹팩이 하는 일

의존성을 기반으로 번들을 만들기

의존성 그래프. 출처

웹팩이 해주는 일은 파일 간의 의존성을 파악한 다음, 그 의존성을 기반으로 모든 파일을 하나의 파일로 합치고 압축하는 것이다. 이 과정을 빌드라고 부르고, 그 결과물은 번들이라고 부른다.

의존성을 파악하기 위한 출발점에 해당하는 파일을 entry라고 부른다. 웹팩은 이 파일에서 시작해서 import 문으로 불러오는 파일을 재귀적으로 파악한다. 이렇게 하면 entry 파일을 루트 노드로 하는 의존성 그래프가 생성된다. 의존성 그래프에 포함된 코드를 하나의 파일에 합치면 번들이 만들어진다.

코드 압축하기

트리쉐이킹

웹팩은 의존성 트리를 만들면서 실제로 사용하는 것을 확인한 코드만 취해 번들에 넣을 수 있다. 즉, 사용하지 않는 코드는 번들에 포함되지 않고, 번들의 크기를 줄일 수 있다. 이렇게 사용하지 않는 코드를 제외시키는 것을 나무를 흔들어 죽은 잎을 떨어뜨리는 것 같다고 해서 트리쉐이킹이라고 부른다.

자세한 내용은 웹팩 공식 문서에 설명되어 있다.

uglify & minify (플러그인)

변수와 함수의 이름은 개발자가 알아보기 편하기 위해 짓는 이름이다. 당연히 변수와 함수의 이름이 길수록 파일의 용량은 커진다. 하지만 친절하고 자세한 이름일수록 개발자가 알아보기 편하기 때문에, 변수나 함수 이름이 길어져도 그 역할을 잘 드러내는 이름이 권장된다.

하지만 변수 이름을 a, b, c로 지으면 개발자는 아주 혼란스럽겠지만, 컴퓨터는 개의치 않고 코드를 실행할 수 있다. 용량이 적기 때문에 컴퓨터 입장에선 이런 이름을 선호할지도 모른다.

번들은 개발이 완료된, 컴퓨터에게 실행을 요청하기 위한 코드 덩어리이다. 즉 번들의 코드를 개발자가 꼭 확인 가능해야 할 필요는 없는 것이다.

따라서 번들 용량을 줄이기 위해 함수 이름과 변수 이름을 모두 간단한 이름으로 바꾸고, 공백이나 줄바꿈을 모두 제거할 수 있다. 그래서 빌드의 결과물은 개발자가 읽기 난해한 코드가 되는데, 이를 통해 난독화 효과를 줄 수도 있다. 물론 변수 명만 읽기 힘들어진 것이라서, 코드를 이해하고자 한다면 가능은 하겠지만 그 동작을 파악하기는 아주 힘들 것이다.

압축은 uglifyjs-webpack-plugin 플러그인을 붙여서 적용시킬 수 있다.

부가 작업을 빌드에 포함시키기

하나의 프로젝트에는 자바스크립트 파일만 사용되지 않는다. .png, .jpg, .svg 등의 리소스 파일이 포함되어 있을 수도 있고, .ts 처럼 자바스크립트로 변환되어야 하거나 .scss, .sass 등 css로 변환되어야 하는 전처리기가 있을 수도 있다.

이러한 변환은 바벨 등의 외부 트랜스파일러의 도움을 받아야 한다. 웹팩을 사용하면, 이런 트랜스파일러가 하는 일도 빌드 과정의 일부로 포함시킬 수 있다.

로더와 플러그인

웹팩에는 로더와 플러그인을 사용해 번들링 과정에 필요한 부가 작업을 포함시킬 수 있다.

번들러는 자바스크립트만 처리할 수 있다. 따라서 Sass, TypeScript 처럼 변환이 필요한 파일을 사용하고 있다면 그 파일을 번들러가 처리 가능한 형식으로 변환해줘야 한다. 이 작업을 로더가 수행한다. 로더는 번들링 과정을 수행하기 전에, 로더에 정의된 정규식 조건에 맞는 파일이 있다면 로더에 정의된 방식대로 변환해준다.

웹팩의 번들링 과정 도중에 어떤 작업이 수행되어야 하는 경우가 있다. 이런 작업은 플러그인을 붙여서 수행할 수 있다. 예를 들면, 번들링 진행 전에 환경 변수를 등록해 주거나, CSS 파일을 압축하거나, 빌드가 끝난 후에 html 파일에 자동으로 번들을 삽입해 주는 등등의 작업을 수행해 줄 수 있다.

웹팩 외의 선택지

웹팩은 포함된 개념이 많고 복잡해서 직접 설정해서 사용하기에 어려울 수 있고 학습이 오래 걸릴 수 있다. 그리고 웹팩은 오래된 역사만큼 너무 많은 기능이 추가되었고, 복잡한 라이브러리가 되었다. 이에 좀 더 간단하고 편리한 모듈 번들러가 등장하고 있다.

참고한 글

프로필 사진

조예진

이전 포스트
디스코드 알림봇 추가하기
다음 포스트
글또 8기를 시작하면서