트랜스파일러와 번들러

트랜스파일러와 번들러

2024년 6월 23일 by이호연
thumbnail.png

트랜스파일러와 번들러

트랜스파일러와 번들러는 JavaScript 생태계에서 중요한 역할을 하는 도구들이다.
이 글에서는 트랜스파일러와 번들러의 역할과 동작 방식에 대해 알아본다.

트랜스파일러

트랜스파일러는 사실 컴파일러의 일종으로 볼 수 있다.
트랜스파일러는 소스 코드를 다른 버전의 JavaScript로 변환하는 도구이다.
Babel이나 TypeScript와 같은 도구들이 트랜스파일러의 역할을 수행하는데, 각각 알아보자.

Babel

Babel은 ES6+ 코드를 ES5 코드로 변환하여 구형 브라우저에서도 실행할 수 있게 해준다.
예를 들어 옵셔널 체이닝 연산자나 널 병합 연산자와 같은 ES6+ 문법을 사용하여 코드를 작성하면, Babel을 사용하여 ES5 코드로 변환할 수 있다.

es6.js
const value = obj?.key ?? 'default';

위의 코드는 ES6+ 문법을 사용한 코드인데, 이 코드를 Babel을 사용하여 ES5 코드로 변환하면 다음과 같이 변환된다.

es5.js
var value =
  (obj !== null && obj !== undefined ? obj.key : undefined) !== null &&
  (obj !== null && obj !== undefined ? obj.key : undefined) !== undefined
    ? obj !== null && obj !== undefined
      ? obj.key
      : undefined
    : 'default';

이렇게 Babel을 사용하면 ES6+ 코드를 ES5 코드로 변환할 수 있다.

TypeScript

TypeScript는 정적 타입을 지원하는 JavaScript의 슈퍼셋이다.
TypeScript는 JavaScript 코드를 TypeScript 코드로 변환하여 타입을 검사하고, 타입 오류를 사전에 방지할 수 있다.

// TypeScript 코드
const arrowFunction = (): void => {
  console.log('Hello, TypeScript!');
};

위의 코드는 TypeScript 코드인데, 이 코드를 TypeScript 컴파일러를 사용하여 JavaScript 코드로 변환하면 다음과 같이 변환된다.

// JavaScript 코드
const arrowFunction = () => {
  console.log('Hello, TypeScript!');
};

이렇게 TypeScript를 사용하면 정적 타입을 지원하는 JavaScript 코드를 작성할 수 있다.

추상 구문 트리 (Abstract Syntax Tree, AST)

트랜스파일러가 소스 코드를 변환하는 과정은 다음과 같다.

  1. 파싱(Parsing): 소스 코드를 토큰(Token)으로 분해한다.
  2. 추상 구문 트리 생성(Abstract Syntax Tree, AST): 토큰을 분석하여 추상 구문 트리를 생성한다.
  3. 코드 생성 (Code Generation): 추상 구문 트리를 다른 버전의 JavaScript 코드로 변환한다.
💡

파싱(Parsing) 소스 코드를 토큰(Token)으로 분해하는 과정이다. 토큰은 소스 코드의 최소 단위로, 변수, 함수, 연산자 등을 나타낸다.

여기서 추상 구문 트리는 소스 코드의 구조를 표현하는 트리 구조이다.

const arrowFunction = () => {
  console.log('Hello, Babel!');
};

위의 코드를 추상 구문 트리로 변환하면 다음과 같이 표현된다.

{
  "type": "Program",
  "body": [
    {
      "type": "VariableDeclaration",
      "declarations": [
        {
          "type": "VariableDeclarator",
          "id": {
            "type": "Identifier",
            "name": "arrowFunction"
          },
          "init": {
            "type": "ArrowFunctionExpression",
            "id": null,
            "params": [],
            "body": {
              "type": "BlockStatement",
              "body": [
                {
                  "type": "ExpressionStatement",
                  "expression": {
                    "type": "CallExpression",
                    "callee": {
                      "type": "MemberExpression",
                      "object": {
                        "type": "Identifier",
                        "name": "console"
                      },
                      "property": {
                        "type": "Identifier",
                        "name": "log"
                      }
                    },
                    "arguments": [
                      {
                        "type": "Literal",
                        "value": "Hello, Babel!",
                        "raw": "'Hello, Babel!'"
                      }
                    ]
                  }
                }
              ]
            },
            "generator": false,
            "async": false
          }
        }
      ],
      "kind": "const"
    }
  ],
  "sourceType": "module"
}

일반적으로 추상 구문 트리에는 위와 같이 탭이나 공백, 주석을 제외한 코드의 구조만 표현된다.

CSS 코드 역시도 이 트랜스파일러의 대상이 될 수 있다.
Sass나 Less와 같은 CSS 전처리기(Preprocessor)를 사용하면, CSS의 확장 언어로 작성된 코드를 브라우저가 이해할 수 있는 CSS 코드로 변환할 수 있다.

번들러

번들러는 여러 개의 파일과 모듈들을 하나의 파일로 묶어주는 도구이다. 번들러에는 대표적으로 Webpack과 Parcel이 있다.

Webpack

Webpack은 모듈 번들러로, 여러 개의 파일과 모듈들을 하나의 파일로 묶어준다.
다양한 파일들을 하나의 파일로 번들링할 수 있으며, 코드 스플리팅, 트리 쉐이킹, 로더, 플러그인 등 다양한 기능을 제공한다.

💡

코드 스플리팅 번들링된 파일을 여러 개의 작은 파일로 분할하는 기능이다. 이를 통해 초기 로딩 시에는 필요한 파일만 로드하고, 나중에 필요한 파일은 나중에 로드할 수 있다. 이렇게 하면 초기 로딩 속도가 빨라진다.

💡

트리 쉐이킹 사용하지 않는 코드를 제거하는 기능이다. 트리 쉐이킹을 통해 번들링된 파일의 크기를 줄일 수 있다.

Parcel

Parcel은 웹 애플리케이션 번들러로, Webpack과 유사한 기능을 제공한다.
Webpack과 달리 설정이 필요 없으며, 간단하게 사용할 수 있다는 장점이 있다.
빌드 속도가 빠르고, HMR(Hot Module Replacement)을 지원한다.

💡

HMR(Hot Module Replacement) 모듈이 수정되었을 때, 수정된 모듈만 다시 로드하여 새로고침 없이 애플리케이션을 업데이트하는 기능이다.

번들링을 하면 뭐가 좋은데?

번들링을 하면 네트워크 효율성이 높아진다.
여러 개의 파일을 하나의 파일로 묶어주기 때문에, 파일을 로드하는 요청 횟수가 줄어들어 네트워크 성능이 향상된다.

번들링 전후 비교

100개의 JavaScript 파일을 번들링하면 1개의 JavaScript 파일로 묶이기 때문에, 100번의 요청이 아닌 1번의 요청으로 파일을 로드할 수 있다.

게다가, 번들러는 코드 스플리팅, 트리 쉐이킹, 그리고 압축 등의 동작을 통해 번들링된 파일의 크기를 줄여주기 때문에, 네트워크 효율성을 더 높여준다.

번들링 단계

번들러는 다음과 같은 단계를 거쳐 번들링된 파일을 생성한다.

  1. 엔트리(Entry): 번들링의 시작점이 되는 파일을 읽어들인다./.
    엔트리 파일은 번들링의 시작점이 되는 파일로, 번들링된 파일은 이 파일을 기준으로 생성된다. 엔트리 파일로부터 종속성을 추적하여 필요한 모듈을 찾는다.
  2. 리졸브(Resolve): 모듈의 경로를 해석하여 실제 경로를 찾는다.
    모듈의 경로를 해석하여 실제 파일 경로를 찾는다. 예를 들어 import 'lodash'와 같은 코드가 있을 때, node_modules 디렉토리에서 lodash 모듈을 찾아야 한다.
  3. 변환(Transform): 모듈을 번들링할 수 있는 형태로 변환한다.
    트랜스파일러를 활용하여 모듈을 번들링할 수 있는 형태로 변환한다. 예를 들어 ES6+ 코드를 ES5 코드로 변환하거나, Sass 코드를 CSS 코드로 변환하는 작업이 여기에 해당한다.
  4. 종속성 그래프 생성(Dependency Graph): 모듈 간의 종속성을 파악하여 그래프를 생성한다.
    모듈 간의 종속성을 파악하여 그래프를 생성한다. 이 그래프를 통해 모듈 간의 의존 관계를 파악할 수 있다.
  5. 최적화(Optimize): 번들링된 파일을 최적화한다.
    코드 스플리팅, 트리 쉐이킹, 압축 등의 최적화 작업을 수행한다.
  6. 번들링(Bundle): 모듈을 하나의 파일로 묶어준다.
    모듈을 하나의 파일로 묶어준다. 번들링된 파일은 브라우저에서 실행할 수 있는 형태로 생성된다.
  7. 결과 출력(Output): 번들링된 파일을 출력한다.
    번들링된 파일을 출력한다. 이 파일은 브라우저에서 실행할 수 있는 형태로 생성된다.

트랜스파일러와 번들러

일반적으로 트랜스파일러와 번들러를 함께 사용하는 경우가 많다.
트랜스파일러를 사용하여 ES6+ 코드를 ES5 코드로 변환하고, 번들러를 사용하여 여러 개의 파일을 하나의 파일로 묶어주는 것이다.