Webpack 번들링의 효과: 로딩 속도 비교

kichan.kim·2023년 5월 29일
1
post-thumbnail

대부분의 언어들은 모듈 시스템을 지원하고 있는데요.

c에서는 헤더 파일을 통해, java에선 패키지 시스템, python은 모듈과 패키지를 사용하는 등 언어 자체적으로 모듈 시스템을 지원하여 코드 재사용성을 높이고 있습니다.

그렇다면 자바스크립트에서는 어떨까요?

const module = require("some-package")
import module from 'some-package'


위와 같은 표현을 많이 만나 보셨을 텐데요. 그렇습니다, 자바스크립트에서도 모듈 시스템을 지원하고 있습니다.

하지만 자바스크립트의 모듈 시스템은 언어가 처음 개발되었을 때에는 존재하지 않았는데요.
자바스크립트는 처음에는 웹 브라우저의 동작을 제어하는 간단한 스크립팅 언어로 개발되었기 때문입니다.
자바스크립트의 공식적인 모듈 시스템은 ES6 (ECMAScript 2015)에 추가되어 import와 export 문법을 이용하여 모듈을 정의하고 가져올 수 있게 되었습니다.


ESM이 발표되기 전에는 어떤 방법으로 모듈 시스템 부재를 극복할 수 있었을까요?

다양한 방법이 존재하지만 그 중 하나로 모든 자바스크립트 파일을 하나로 묶어서 하나의 자바스크립트 파일로 만드는 방법이 있습니다. 이러한 번들링을 도와 주는 도구 중 가장 널리 사용되는 것이 Webpack 입니다.

Webpack을 사용하여 많은 자바스크립트 파일을 하나로 번들링 하여 브라우저 로딩 속도가 얼마나 감소되는지 파악해 보는 것이 이번 포스팅의 주제입니다.

간단한 html과 Live Server를 이용하여 테스트를 진행해 보겠습니다.

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <script type="module" src="./index.js"></script>
  </head>
  <body>
    <button type="button" id="buttonA">buttonA</button>
    <button type="button" id="buttonB">buttonB</button>
    <button type="button" id="buttonC">buttonC</button>
    <div id="display"></div>
  </body>
</html>

버튼을 누르면 div 태그에 값이 보여지는 간단한 웹 페이지 입니다. script 태그의 type을 module로 선언하여 esm을 사용할 수 있도록 설정해 주었습니다. 그럼 index.js를 살펴볼까요?

import a from './a.js';
import b from './b.js';
import c from './c.js';

const buttonA = document.querySelector('#buttonA');
const buttonB = document.querySelector('#buttonB');
const buttonC = document.querySelector('#buttonC');
const display = document.querySelector('#display');

buttonA.addEventListener('click', () => {
  display.textContent = a;
});

buttonB.addEventListener('click', () => {
  display.textContent = b;
});

buttonC.addEventListener('click', () => {
  display.textContent = c;
});

버튼의 동작을 정의하고, display에 보여줄 변수를 각각의 자바스크립트 파일에서 불러오고 있습니다. 그럼 a.js를 살펴볼까요?

import a1 from './a-1.js';

export default `this is ${a1}`;

a-1.js에서 내보내기한 값을 가져와서 또다시 export 하고 있습니다. 마지막으로 a-1.js를 살펴볼게요.

export default 'module a-1';

네, module a-1이라는 문자열을 내보내기 하고 있네요, 그렇다면 a.js에서는 this is module a-1이라는 문자열을 내보낼 것입니다. 이것은 b.js와 c.js에도 동일하게 적용되어 있습니다.

이제 브라우저에서 로딩 속도를 확인해 볼게요.
모든 자바스크립트 파일이 로드 되기까지 292ms가 소요되었습니다. 그렇다면 이제 웹팩을 이용하여 자바스크립트 파일을 하나로 번들링 해 볼까요?

우선 devDependencies에 webpack과 webpack-cli를 추가해 주세요

{
  "name": "webpack",
  "version": "1.0.0",
  "main": "index.js",
  "license": "MIT",
  "devDependencies": {
    "webpack": "^5.84.1",
    "webpack-cli": "^5.1.1"
  }
}

그리고 다음 커맨드를 통해 번들링을 실행합니다.

npx webpack --entry ./index.js --output-path ./dist --mode production

Webpack으로 번들링 할 진입점(entry)파일을 index.js로 지정하고, 번들링 된 파일의 출력 경로를 지정합니다. 이를 통해 minified된 하나의 자바스크립트 파일을 얻을 수 있습니다.

(()=>{"use strict";const t=document.querySelector("#buttonA"),e=document.querySelector("#buttonB"),n=document.querySelector("#buttonC"),o=document.querySelector("#display");t.addEventListener("click",(()=>{o.textContent="this is module a-1"})),e.addEventListener("click",(()=>{o.textContent="this is module b-1"})),n.addEventListener("click",(()=>{o.textContent="this is module c-1"}))})();


파일을 살펴보면 버튼에 대한 event listner가 정의 되어 있네요. 신기한 점은 a-1.js에서 가져온 값을 따로 변수에 할당하지 않고 다음과 같이 값이 사용되는 곳에 바로 적용된다는 것입니다.

()=>{o.textContent="this is module a-1"}


그럼 번들링 된 파일을 script 태그에서 불러와 브라우저에서 로드되기 까지의 시간을 알아볼까요?

main.js 파일 하나만 가져오고, 소요 시간은 31ms로 기존에 번들링 하기 전보다 10배 가량 단축 되었습니다.

Webpack이 import 구문을 사용하는 파일을 처리하는 내부적인 과정을 더 구체적으로 알아볼까요?

1. Dependency Resolution

  • 먼저, Webpack은 모든 import 문을 통해 모듈간의 의존성을 파악합니다.
    이는 Webpack이 코드 내에서 어떤 모듈이 다른 모듈을 참조하고 있는지 확인하는 과정입니다.

2. Transformation

  • 그 다음으로 Webpack은 코드 변환 작업을 수행하는데, 이 과정에서 각 import 문을 webpack_require 함수 호출로 대체합니다.
    이 함수는 Webpack이 생성한 번들 내에서 작동하는 특수한 함수로, 번들에 포함된 각 모듈을 로드하는 데 사용됩니다.
    import 문은 대략 다음과 같이 변환됩니다
// Before transformation
import { myFunction } from './myModule';

// After transformation
var _myModule = __webpack_require__("./myModule");
var myFunction = _myModule.myFunction;
  • 여기서 webpack_require 함수는 번들된 코드 내에서 정의되며, 번들에 포함된 모듈을 로드하는 역할을 합니다.

3. Bundling

  • 마지막으로, Webpack은 이런 방식으로 변환된 모든 모듈을 하나의 파일(또는 여러 파일)로 묶습니다.
    이렇게 하면 웹 브라우저에서 쉽게 로드하고 실행할 수 있는 코드가 생성됩니다.


하지만 이러한 번들링이 무조건적으로 웹 사이트의 초기 로딩을 줄이는데 도움이 된다고 말할수 있을까요?

정답은 X 입니다. 번들링의 결과로 생성된 파일의 크기가 너무 크면, 단일 HTTP 요청의 지연시간이 오히려 더 커질 수 있는데요.

이러한 이유로 Webpack에는 code splitting 기능이 내장되어 있습니다. 이 기능을 사용하면 큰 번들을 여러개의 작은 번들로 나눌 수 있기 때문에, 사용자가 실제로 필요한 코드만 초기에 로드할 수 있어, 로딩 시간을 줄일 수 있습니다.

결론적으로, Webpack을 사용하는 것이 웹사이트의 성능을 향상시키는 데 도움이 될 수 있지만,
단순히 모든 코드를 하나의 큰 번들로 만드는 것만으로는 충분하지 않습니다.
코드 분할, 지연 로딩, 미니화 등의 전략을 사용해야 효과적으로 웹사이트의 로딩 시간을 줄일 수 있습니다.


이상으로 Webpack 번들링의 효과: 로딩 속도 비교 포스팅을 마치겠습니다. 감사합니다.

profile
keep going

0개의 댓글