Post

Module bundler

본 글은 <What is module bundler and how does it work?>을 바탕으로 작성되었다.

모듈 번들러란

모듈 번들러는 자바스크립트 모듈들을 브라우저에서 실행 가능한 하나의 자바스크립트 파일로 묶는 도구이다.

왜 필요한가

  • 브라우저는 모듈 시스템을 지원하지 않는다.
  • 모듈의 의존성을 대신 관리해준다.
  • 이미지나 CSS와 같은 자원들을 의존성 순서에 맞게 불러와준다.
1
2
3
4
5
6
7
<html>
  <script src="/src/foo.js"></script>
  <script src="/src/bar.js"></script>
  <script src="/src/baz.js"></script>
  <script src="/src/qux.js"></script>
  <script src="/src/quux.js"></script>
</html>

위와 같은 코드를 실행시키기 위해선 5번의 HTTP 요청을 보내야 한다. 따라서 5개의 파일을 아래와 같이 하나의 파일로 모으는 것이 낫다.

1
2
3
<html>
  <script src="/dist/bundle.js"></script>
</html>

이떄 다음과 같은 문제가 생긴다.

  • 5개의 파일 순서를 어떻게 유지하는가? 어떻게 5개의 파일 간 의존성을 해결하는가?
  • 이름이 중복되는 오류를 어떻게 해결하는가?
  • 아직 번들에 포함되지 않은 파일을 어떻게 구분하는가?

만약 파일간 관계를 알고 있다면 위 문제는 해결될 수 있다.

Webpack은 어떻게 작동하는가

다음과 같은 세 파일이 있다고 가정한다.

1
2
3
4
5
6
// circle.js

const PI = 3.141;
export default function area(radius) {
  return PI * radius * radius;
}
1
2
3
4
5
// square.js

export default function area(side) {
  return side * side;
}
1
2
3
4
5
6
// app.js

import squareArea from "./square";
import circleArea from "./circle";
console.log("Area of square: ", squareArea(5));
console.log("Area of circle", circleArea(5));

webpack은 세 파일을 다음과 같이 묶는다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// webpack-bundle.js

const modules = {
  "circle.js": function (exports, require) {
    const PI = 3.141;
    exports.default = function area(radius) {
      return PI * radius * radius;
    };
  },
  "square.js": function (exports, require) {
    exports.default = function area(side) {
      return side * side;
    };
  },
  "app.js": function (exports, require) {
    const squareArea = require("square.js").default;
    const circleArea = require("circle.js").default;
    console.log("Area of square: ", squareArea(5));
    console.log("Area of circle", circleArea(5));
  },
};

webpackStart({
  modules,
  entry: "app.js",
});

특이점은 다음과 같다.

  • module map의 사용
    • module map: 모듈의 이름과 모듈 함수를 연결짓는 딕셔너리
  • 각 모듈은 함수로 묶여있다.
    • 이 함수를 module factory function이라 한다.
    • 각 함수는 각 모듈만의 scope를 만든다.
    • 함수는 exports, require 매개변수를 갖는다.
  • 애플리케이션은 webpackStart를 통해 시작된다.
    • webpackStart 함수는 module map을 이용해 모든 것을 하나로 묶는 함수이다.
    • webpackStart 함수는 런타임이라 불리기도 한다.

webpackStart 함수

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function webpackStart({ modules, entry }) {
  const moduleCache = {};
  const require = (moduleName) => {
    // 캐시에 존재한다면 캐시 버전을 반환한다.
    if (moduleCache[moduleName]) {
      return moduleCache[moduleName];
    }
    const exports = {};
    // 상호 참조(circular dependencies)로 인해 발생하는 무한 루프를 막는다.
    moduleCache[moduleName] = exports;

    // 모듈을 require 한다.
    // export되었다면 이는 "exports"로 할당된다.
    modules[moduleName](exports, require);
    return moduleCache[moduleName];
  };

  // 프로그램을 시작한다.
  require(entry);
}

webpackStart는 두 가지를 정의한다.

  • require 함수
    • Common JS의 require와는 다르다.
    • 모듈 이름을 받아 모듈의 exported interface를 반환한다.
      • circle.js의 경우 반환 값은 { default: function area(radius){ ... } }가 될 것이다.
    • expoted interface는 모듈 캐시에 캐싱된다.
      • 같은 모듈에 대하여 require 함수를 여러번 실행하더라도 module factory function은 한 번만 실행된다.
  • module cache

요약

  • 모듈 번들러는 자바스크립트 모듈들을 브라우저에서 실행 가능한 하나의 자바스크립트 파일로 묶는 도구이다.
  • 모듈 번들러는 모듈간 의존성 문제를 대신 해결해준다.
  • webpack은 module map을 사용해 모듈들을 이름에 따른 각 함수로 묶고 webpackStart 함수를 사용해 이를 합친다.

참고 자료

This post is licensed under CC BY 4.0 by the author.