웹팩 (Webpack)

5 분 소요


RECOMMEND POSTS BEFORE THIS

0. 들어가면서

자바(Java)를 주력으로 사용하는 백엔드 개발자이지만, 최근 프론트엔드에 대한 이해도도 높아야 한다고 느끼고 있다. 여태껏 프론트엔드 관련 기술에 큰 관심이 없었지만, 프로젝트에서 프론트엔드 관련 개발을 많이 하게 되면서 관심이 커졌다. 요즘 많이 사용하는 React 라이브러리나 Vue.js 프레임워크를 처음 접하면 웹팩(Webpack), 바벨(Babel)이라는 단어를 듣게 된다. 많은 글을 찾아봤지만 해당 기술에 대한 정확한 개념이 잡히질 않는다. 그래서 글로 정리해 봤다. 이번 웹팩 관련 포스트를 시작으로 앞으로 프론트엔드 기술을 공부한 내용을 공유할 생각이다.

1. JavaScript Module and Webpack

Webpack is a module bundler.

공식 홈페이지에서 웹팩을 모듈 번들러(module bundler)라고 소개하고 있다. 번들러(bundler)는 무엇인가를 한데 모아주는 것이라는 의미인데, 모듈(module)에 대한 감이 잘 잡히지 않는다. 우선 모듈에 대한 개념부터 정리해 보겠다.

Module from dictionary
any of a number of distinct but interrelated units from which a program may be built up or into which a complex activity may be analyzed.

보통 개발 분야에서 모듈이란 특정 기능을 수행할 수 있는 코드를 집약해 놓은 것을 의미한다. 간단한 기능을 수행하는 모듈을 모아서 더 복잡한 기능을 수행하는 모듈을 만들 수도 있다. 모듈은 자신만의 고유한 영역(scope)을 가지고 있는 것이 특징인데, 예전 자바스크립트(JavaScript) 세상에는 모듈이라는 개념이 없었다고 한다. 간단한 예를 살펴보자. 모듈을 사용하지 않은 코드는 다음과 같다. hello.js 파일에 아래와 같은 코드를 작성한다.

  • message 변수를 선언하고 ‘Hello’라는 문자열을 할당한다.
var message = 'Hello';

world.js 파일에 아래와 같은 코드를 작성한다.

  • message 변수를 선언하고 ‘World’라는 문자열을 할당한다.
var message = 'World';

index.html 파일에서 hello.js, world.js 두 파일을 script 태그를 통해 추가한다. 다음 본문 script 블록에서 root ID를 가진 div 태그의 내부 HTML을 message 변수에 지정된 값으로 변경하는 코드를 작성한다.

<!DOCTYPE html>
<html>
<head>
    <script src="./hello.js"></script>
    <script src="./world.js"></script>
</head>
<body>
    <div id="root"></div>
    <script>
        document.getElementById("root").innerHTML = message;
    </script>
</body>
</html>

브라우저에서 위 index.html 코드를 살펴보면 “World”가 출력된다. 이유는 다음과 같다.

  • 동일한 message 변수명이 hello.js, world.js 파일 모두에 존재한다.
  • 나중에 선언된 world.js 파일의 message 변수가 최종적으로 사용된다.


위와 같은 현상을 전역 스코프(scope)가 오염되었다고 표현한다. 많게는 수십 개의 자바스크립트 파일이 필요한 HTML 문서를 작성할 때 여러 명의 개발자가 다수의 자바스크립트 파일을 나누어 작업했다면 변수 충돌 문제는 더 심각할 수 있다. 변수명 충돌로 의도치 않은 버그가 발생할 가능성이 커진다. 이번에는 모듈을 사용한 코드를 살펴보자. 마찬가지로 hello.js 파일에 다음과 같은 코드가 작성되어 있다.

  • message 변수를 선언한 후 객체에 담아서 이를 외부에 노출(export)한다. 이는 hello.js 파일을 모듈(module)로 만들어 외부에 제공한다는 것을 의미한다.
var message = 'Hello';

export default {
    message: message
};

다음 world.js 파일에 다음과 같은 코드가 작성되어 있다.

  • message 변수를 선언한 후 객체에 담아서 이를 외부에 노출한다. world.js 파일을 모듈로 만들어 외부에 제공한다는 것을 의미한다.
var message = 'World';

export default {
    message: message
};

index.html 파일에서 두 모듈을 사용해 보자.

  1. script 태그 타입을 module로 지정한다.
  2. hello.js 파일에서 제공하는 모듈을 hello라는 이름으로 입력(import)받는다.
  3. world.js 파일에서 제공하는 모듈을 world라는 이름으로 입력받는다.
  4. 스크립트 태그 안에 root ID를 가진 div 태그의 내부 HTML을 hello.message + world.message로 지정하는 코드를 작성한다.
<!DOCTYPE html>
<html>
<head></head>
<body>
    <div id="root"></div>
    <script type="module">
        import hello from './hello.js';
        import world from './world.js';
        document.getElementById("root").innerHTML = hello.message + world.message;
    </script>
</body>
</html>

index.html 파일을 브라우저에서 실행해 보자. 동일한 message 변수명이 hello.js, world.js 파일 모두에 존재하지만, 모듈 단위로 구분하여 사용할 수 있다. export 키워드를 통해 각 자바스크립트 파일을 모듈화하여 외부로 제공하였다. import 키워드를 통해 필요한 자바스크립트 파일을 적절한 위치에 선언하여 사용하였다.

  • “HelloWorld”라는 문자열이 출력된다.


type="module" 키워드 사용 시 유의점이 있다. type="module" 키워드는 모든 브라우저에서 사용할 수 없다. 관련된 정보는 이 링크에서 확인할 수 있다.

https://caniuse.com/?search=module

2. Use Webpack

이번 글은 웹팩에 대한 개념을 공부하기 위한 글이기 때문에 매우 불편한 방법으로 웹팩을 사용했다. 설정 파일을 이용하여 더 쉽게 웹팩을 사용하는 방법은 다음 글로 정리할 예정이다. 이제부터 작성한 모듈들을 웹팩을 사용하여 한데 모으는 작업을 진행한다. 이런 행위를 번들링(bundling)이라고 한다. 우선 웹팩을 설치한다. npm 명령어를 통해 설치할 수 있다. npm이 없다면 노드(Node.js)를 우선 설치하길 바란다. 전역에서 사용할 수 있도록 -g 옵션을 두어 설치한다.

$ npm install -g webpack webpack-cli

다음 엔트리 포인트(entry point)를 만든다. 엔트리 포인트에 대한 정의는 다음과 같다.

An entry point indicates which module webpack should use to begin building out its internal dependency graph.

웹팩으로 한데 모은 모듈들을 사용하기 위한 시작점이 필요하다. 자바(Java)의 클래스를 보면 프로그램의 시작점이라고 할 수 있는 public static void main(String[] args) 메서드와 비슷한 느낌이다. index.js 파일을 엔트리 포인트로 지정한다.

  • hello.js, world.js 파일을 추가한다.
  • root ID를 가진 div 태그의 내부 HTML을 hello.message + world.message로 지정하는 코드를 작성한다.
import hello from './hello.js';
import world from './world.js';
document.getElementById("root").innerHTML = hello.message + world.message;

지금부터 설치한 웹팩으로 코드들을 번들링해 보자. 우선 번들링할 파일들이 위치한 디렉터리로 이동한다.

$ cd <directory>

번들링 수행 전에 폴더를 보면 다음과 같은 파일들이 존재한다.

$ ls -al
total 4
drwxr-xr-x 1 kang3 197609   0 Sep 15 06:25 ./
drwxr-xr-x 1 kang3 197609   0 Sep 15 05:56 ../
-rw-r--r-- 1 kang3 197609  68 Sep 15 05:19 hello.js
-rw-r--r-- 1 kang3 197609 130 Sep 15 06:02 index.html
-rw-r--r-- 1 kang3 197609 140 Sep 15 05:57 index.js
-rw-r--r-- 1 kang3 197609  68 Sep 15 05:19 world.js

다음과 같은 명령어를 수행한다.

  • --entry 옵션 - 엔트리 포인트는 index.js 파일로 지정한다.
  • --output-path 옵션 - 번들링한 파일은 현재 디렉터리에 만든다.
$ webpack --entry ./index.js --output-path ./

asset main.js 78 bytes [emitted] [minimized] (name: main)
orphan modules 136 bytes [orphan] 2 modules
./index.js + 2 modules 276 bytes [built] [code generated]

WARNING in configuration
The 'mode' option has not been set, webpack will fallback to 'production' for this value.
Set 'mode' option to 'development' or 'production' to enable defaults for each environment.
You can also set it to 'none' to disable any default behavior. Learn more: https://webpack.js.org/configuration/mode/

webpack 5.52.1 compiled with 1 warning in 221 ms

번들링 수행 후 폴더를 보면 아래와 같이 main.js 파일이 만들어진 것을 확인할 수 있다.

$ ls -al

total 5
drwxr-xr-x 1 kang3 197609   0 Sep 15 06:28 ./
drwxr-xr-x 1 kang3 197609   0 Sep 15 05:56 ../
-rw-r--r-- 1 kang3 197609  68 Sep 15 05:19 hello.js
-rw-r--r-- 1 kang3 197609 130 Sep 15 06:02 index.html
-rw-r--r-- 1 kang3 197609 140 Sep 15 05:57 index.js
-rw-r--r-- 1 kang3 197609  78 Sep 15 06:28 main.js
-rw-r--r-- 1 kang3 197609  68 Sep 15 05:19 world.js

이제 번들링이 완료된 파일을 사용해 보자. index.html 파일에 번들링 결과물인 main.js 파일을 선언한다.

<!DOCTYPE html>
<html>
<head></head>
<body>
    <div id="root"></div>
    <script src="./main.js"></script>
</body>
</html>

브라우저에서 실행하면 위 예제와 동일한 문장을 확인할 수 있지만, 필요한 파일은 main.js 파일 하나다. 번들링을 사용하지 않은 위 예제의 결과를 보면 다운로드되는 파일이 hello.js, world.js 두 개이다.


웹팩을 사용했을 때 얻는 이점은 다음과 같이 정리할 수 있을 것 같다.

  • 변수 충돌 문제 해결
    • 전역 스코프(scope)의 오염을 막을 수 있다.
  • 호환성 문제 해결
    • type=module처럼 특정 브라우저에서 호환되지 않는 기능을 사용할 필요가 없다.
  • 자원 사용의 효율 개선
    • 번들링을 통해 생성된 자원만 사용하면 되므로 자원에 대한 접근이 대폭 감소한다.

TEST CODE REPOSITORY

REFERENCE

댓글남기기