Tree Shaking
포스트
취소

Tree Shaking

해당 포스트는 Tree Shaking에 대한 포스트입니다.
혹시 webpack에 대한 지식이 필요하다면 webpack를 참고해주세요!

🍂 Tree Shaking

사용되지 않는 코드를 제거하기 위해 JavaScript 컨텍스트에서 일반적으로 사용되는 용어라고 webpack 공식 문서에 적혀 있습니다.
즉, import하고 사용하지 않는 코드는 번들링 후에 제거하는 기술을 의미합니다.

🌳 Tree Shaking 예시

아래의 코드를 예시로 설명하겠습니다. ( webpack - tree-shaking 참고 )

index.js에서는 math.ts의 모든 export를 가져옵니다.
하지만 사용은 cube()라는 함수 하나만 사용하고 있습니다.
이런 경우 굳이 square()를 번들링에 포함할 필요가 있을까요?

이렇게 사용하지 않는 코드를 번들링에서 제외하는 기술을 Tree Shaking이라고 합니다.
다음으로는 어떻게 Tree Shaking을 적용하는지에 대해서 알아보겠습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
// math.ts
export function square(x: number) {
  return x * x;
}

export function cube(x: number) {
  return x * x * x;
}

// index.ts
import * as math from "./math";

console.log(math.cube(5));

🪓 Tree Shaking 적용 방법

0️⃣ CommonJS 모듈 사용하지 않기

CommonJS 모듈 시스템이란 requiremodule.exports를 이용해서 파일을 내보내고 가져오는 방식을 의미합니다.
CommonJS를 사용하면 Tree Shaking을 적용할 수 없다고 합니다.

저희는 React를 사용할 때는 항상 ES6 모듈 시스템을 사용합니다.
그렇다면 신경쓰지 않아도 되는 부분이 아니라고 생각할 수 있습니다.

create-react-app같은 경우 내부적으로 어떻게 설정되어 있는지 모르겠지만, webpack을 직접 설정한다면 대부분 @babel/preset-env를 사용할 것입니다.
@babel/preset-env는 기본적으로 CommonJS로 변환시켜주는 기능을 갖고 있습니다.
따라서 명시적으로 변환시키지 말라는 코드를 작성해줘야 합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
// babel.config.js

const presets = [
  [
    "@babel/preset-env",
    {
      modules: false, // CommonJS 모듈 시스템으로 변환하지 않게 하는 속성
    },
  ],
];
const plugins = [];

module.exports = { presets, plugins };

1️⃣ Side Effect 고려하기

SideEffect란 외부의 데이터에 영향을 끼치는 행위를 의미합니다.

아래 예시에서 func()Side Effect가 있는 함수입니다.
외부의 데이터인 person을 변경시켰기 때문입니다.

1
2
3
4
5
6
7
const person = {
  name: "Aatrox",
};

const func = () => {
  person.age = 26;
};

여기서 중요한 것은 func()를 사용하지 않았어도 import한다면 Side Effect가 있는 함수이기 때문에 Tree Shaking이 적용되지 않습니다.

해결 방법은 크게 두 가지가 있습니다.

  1. package.json"sideEffects": false or "sideEffects": [특정 파일 경로, ...] 설정하기
  2. /*#__PURE__*/ 라벨 붙이기

1번은 명시적으로 패키지와 디펜던시들에 Side Effect가 발생하지 않는다고 알려주는 것입니다.
2번은 특정 함수에 사용하고 그 함수는 Side Effect가 발생하지 않는다고 알려주는 것입니다. ( 실제로 사용해본 적은 없음 )

2️⃣ 필요한 것만 import 하기

첫 예시에서 모든 것을 import하는 구문을 사용했는데 그것보다는 사용하는 것만 import해야 합니다.

1
2
3
4
// import * as math from "./math";
import { cube } from "./math";

console.log(cube(5));

🍁 Tree Shaking 전/후 비교

Tree Shaking을 적용하기 전과 후의 코드의 차이는 모르겠지만, 번들링된 index.ts의 크기가 적용 후의 코드가 더 적은 것으로 보아하니 정상적으로 적용이 된 것 같습니다.
( 물론 지금은 코드량이 얼마 없어서 큰 차이를 느낄 수는 없습니다. )

0️⃣ Tree Shaking 전

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// bundle.js

// ... 나머지 생략

eval("/* harmony import */ var _math__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./math */ \"./src/math.ts\");\n\nconsole.log(_math__WEBPACK_IMPORTED_MODULE_0__.cube(5));\n\n//# sourceURL=webpack://tree-shaking/./src/index.ts?");

/**
 * 번들링 결과
 * 
 * asset bundle.js 3.65 KiB [emitted] (name: main)
 * runtime modules 396 bytes 2 modules
 * cacheable modules 151 bytes
 *   ./src/index.ts 58 bytes [built] [code generated]
 *   ./src/math.ts 93 bytes [built] [code generated]
 * webpack 5.75.0 compiled successfully in 1134 ms
 * /

1️⃣ Tree Shaking 후

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// bundle.js

eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _math__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./math */ \"./src/math.ts\");\n\nconsole.log((0,_math__WEBPACK_IMPORTED_MODULE_0__.cube)(5));\n\n//# sourceURL=webpack://tree-shaking/./src/index.ts?");

/**
 * 번들링 결과
 * 
 * asset bundle.js 4.19 KiB [compared for emit] (name: main)
 * runtime modules 670 bytes 3 modules
 * cacheable modules 145 bytes
 *   ./src/index.ts 52 bytes [built] [code generated]
 *   ./src/math.ts 93 bytes [built] [code generated]
 * webpack 5.75.0 compiled successfully in 957 ms
 */

📮 레퍼런스

  1. webpack - tree-shaking
  2. Younkue Choi - Webpack에서 Tree Shaking 적용하기
  3. ToastUI - 트리 쉐이킹으로 자바스크립트 페이로드 줄이기

  4. 1-blue - webpack
  5. 1-blue - CommonJS 모듈 시스템
  6. 1-blue - ES6 모듈 시스템
  7. babel - @babel/preset-env
이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.

리덕스 툴킷 ( Redux-Toolkit + TypeScript + React )

자바스크립트 완벽 가이드 11장 정리 ( Standard Library )