thumbnail

Rollup VS Esbuild의 비교 및 CRA 번들러 마이그레이션 해보기

생성일2024. 12. 4.
태그
작성자지한솔

notion image
들어가기 전에, 프론트엔드 개발에 도움이 될 보일러플레이트를 만들어볼 계획입니다. 이를 위해 보일러플레이트를 제작하기 전 설정해야 할 주요 항목들을 정리했습니다.
  1. TypeScript 설정 (TSConfig)
  1. 번들러 설정 (ESBuild, Rollup, Webpack, Vite 등)
  1. ESLint 및 Prettier 설정
이 세 가지 주제를 각각 학습하고, 포스팅을 진행하며 이를 바탕으로 하나의 완성된 보일러플레이트를 만들어볼 예정입니다.
이번 포스팅은 번들러 설정 (ESBuild, Rollup, Webpack, Vite 등) 그리고 딥하게 들어가서 CRA를 Rollup으로 마이그레이션 해보는 작업까지 진행해보도록 하겠습니다.

번들러 Trend 확인하기

번들러 npm trends
번들러 npm trends
최근 들어 esbuild를 사용하는 개발자가 점점 증가하고 있습니다. 반면, webpack은 CRA(Create React App)와 CNA(Create Next App)와 같은 툴에서 기본 빌드 도구로 제공되기 때문에 여전히 꾸준한 사용자층을 유지하고 있습니다.
Rollup은 상대적으로 사용 비율이 낮지만, 특히 라이브러리 번들링에 강점을 가지고 있어 저도 주로 라이브러리를 구현할 때 Rollup을 사용하는 편입니다.

webpack

webpack은 웹 애플리케이션 개발에서 가장 널리 사용되는 빌드 도구로, 다양한 기능과 플러그인을 통해 확장성이 뛰어나며 대규모 프로젝트에 적합합니다.
  • 장점:
    • 다양한 플러그인과 설정 옵션으로 유연한 확장성을 제공.
    • CRA, CNA와 같은 툴에서 기본 빌더로 사용되며 방대한 커뮤니티 지원.
    • 코드 분할(Code Splitting)과 캐싱 최적화 등 복잡한 프로젝트에서도 강력한 기능 제공.
  • 단점:
    • 설정 파일이 복잡하고 관리가 어려울 수 있음.
    • 빌드 속도가 느려, 최신 대안과 비교했을 때 상대적으로 부족함.

esbuild

esbuild는 초고속 빌드 속도를 자랑하는 최신 빌드 도구로, 빠른 개발 피드백이 중요한 프로젝트에 적합합니다.
  • 장점:
    • JavaScript와 TypeScript 트랜스파일링을 초고속으로 지원.
    • 간단한 설정만으로 사용할 수 있어 진입 장벽이 낮음.
    • 대규모 프로젝트에서도 빠른 빌드 시간 제공.
  • 단점:
    • 플러그인 생태계가 아직 제한적.
    • 복잡하거나 고도로 커스터마이징이 필요한 프로젝트에서는 제약이 있을 수 있음.

Rollup

Rollup은 라이브러리 번들링에 최적화된 도구로, 효율적인 모듈 트리쉐이킹(tree-shaking)과 최적화된 출력물을 생성합니다.
  • 장점:
    • ES Module 기반으로 설계되어 불필요한 코드를 제거하는 트리쉐이킹에 강력함.
    • 플러그인 생태계가 잘 구축되어 있으며, 특히 라이브러리 번들링에 최적화.
    • 결과물 크기를 줄이고 최적화된 번들을 제공.
  • 단점:
    • 초기 설정이 다소 복잡할 수 있음.
    • 웹 애플리케이션보다는 라이브러리 번들링에 더 적합.

비교 포인트

  • 속도: esbuild > Rollup > webpack
  • 확장성: webpack > Rollup > esbuild
  • 라이브러리 번들링: Rollup > esbuild > webpack
  • 커뮤니티와 지원: webpack > esbuild > Rollup

정리

각 빌드 도구는 고유한 강점과 약점을 가지고 있으며, 프로젝트의 요구 사항에 따라 선택이 달라질 수 있습니다.
webpack은 안정성과 커뮤니티 지원으로 여전히 강력한 선택지이며, esbuild는 속도가 중요한 프로젝트에서 매력적인 대안입니다.
Rollup은 라이브러리 번들링에 특화되어 있어, 효율성과 최적화를 중시하는 경우 적합합니다.

Rollup, Esbuild Tree-Shaking 확인

// src/utils.ts export function usedFunction() { console.log("This function is used."); } let name; export function unusedFunction() { console.log("This function is not used."); console.log(name); performSideEffect(); } function performSideEffect() { console.log("Side effect performed!"); }
// src/index.ts import { usedFunction } from './utils'; console.log(usedFunction());
간단한 예제코드를 작성해보았습니다. performSideEffectunusedFunction 에서 사용하고 있습니다. index.ts에서는 usedFunction만을 사용하고 있구요.
실질적으로 사용하고 있는 함수는 unusedFunction 하나 뿐입니다. 그렇기에 Tree-Shaking에 의해 사용하지 않는 두 함수는 번들링에 포함되지 않을 겁니다.

Rollup 설정

pnpm add -D rollup @rollup/plugin-typescript
필요한 라이브러리를 설치하고 rollup.config.mjs를 생성합니다.
import typescript from '@rollup/plugin-typescript'; export default { input: 'src/index.ts', output: [ { file: 'dist/rollup-bundle.js', format: 'esm', sourcemap: true, }, ], plugins: [ typescript(), ], };
pnpm rollup -c
를 통해서 롤업 빌드를 진행하면 우리가 만든 ts코드가 js로 변환되고, 번들링될 것입니다.
// dist/rollup-bundle.js function usedFunction() { console.log("This function is used."); } console.log(usedFunction()); //# sourceMappingURL=rollup-bundle.js.map

Esbuild 설정

pnpm add -D esbuild
필요한 라이브러리를 설치하고 설정파일을 생성합니다.
// build.mjs import { build } from 'esbuild'; build({ entryPoints: ['src/index.ts'], bundle: true, outdir: 'dist', format: 'esm', sourcemap: true, treeShaking: true, }).then(() => console.log('esbuild build finished.'));
node build.mjs
를 통해 esbuild를 진행할 수 있습니다.
// dist/index.js // src/utils.ts function usedFunction() { console.log("This function is used."); } // src/index.ts console.log(usedFunction()); //# sourceMappingURL=index.js.map
그럼 여기서 한번 밴치마킹을 통해 둘의 속도를 비교해보도록 하겠습니다.
# 밴치마크 CLI hyperfine \ --export-csv result.csv \ --prepare "rm -rf dist" \ "pnpm rollup -c" \ "node build.mjs"
Benchmark 1: pnpm rollup -c Time (mean ± σ): 776.1 ms ± 54.7 ms [User: 1243.3 ms, System: 71.0 ms] Range (min … max): 726.2 ms … 894.9 ms 10 runs Benchmark 2: node build.mjs Time (mean ± σ): 42.4 ms ± 4.5 ms [User: 39.6 ms, System: 7.4 ms] Range (min … max): 39.5 ms … 60.4 ms 45 runs Summary node build.mjs ran 18.32 ± 2.33 times faster than pnpm rollup -c

벤치마크 요약

  • pnpm rollup -c:
    • 평균 실행 시간: 776.1ms (± 54.7ms)
    • 실행 범위: 726.2ms ~ 894.9ms
    • 총 실행 횟수: 10회
  • node build.mjs:
    • 평균 실행 시간: 42.4ms (± 4.5ms)
    • 실행 범위: 39.5ms ~ 60.4ms
    • 총 실행 횟수: 45회
  • 비교:
    • node build.mjs18.32배 더 빠르게 실행됨.
속도적인 측면에서 esbuild가 어마어마하지만, 제가 rollup을 선택한 주관적인 이유는
  1. 최적화된 트리쉐이킹
    1. Rollup은 ES Module 기반으로 설계되어, 트리쉐이킹에 있어 esbuild보다 더 정교한 결과를 제공합니다. esbuild는 빌드 속도에 초점을 맞추면서도 트리쉐이킹을 지원하지만, 복잡한 의존성 그래프나 라이브러리 번들링의 경우 Rollup이 더 나은 최적화를 제공한다고 느꼈습니다.
  1. 라이브러리 배포에 특화된 생태계
    1. Rollup은 라이브러리 번들링에 필요한 다양한 출력 포맷(ESM, CJS, UMD 등)을 쉽게 지원하며, 플러그인 생태계가 이 작업에 매우 최적화되어 있습니다. 반면, esbuild는 웹 애플리케이션과 같은 빠른 개발 환경에 더 적합하며, 복잡한 라이브러리 배포를 처리하기에는 아직 한계가 있다고 판단했습니다.

CRA Webpack을 Rollup으로 마이그레이션 시켜보기

npx create-react-app rollup-migration --template typescript
먼저 CRA(Create React App)를 사용하여 TypeScript 템플릿 기반의 React 프로젝트를 생성합니다.

기본 설치 패키지

생성된 프로젝트의 package.json에는 다음과 같은 의존성이 포함됩니다.
"dependencies": { "@testing-library/jest-dom": "^5.17.0", "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.5.0", "@types/jest": "^27.5.2", "@types/node": "^16.18.121", "@types/react": "^18.3.12", "@types/react-dom": "^18.3.1", "react": "^18.3.1", "react-dom": "^18.3.1", "react-scripts": "5.0.1", "typescript": "^4.9.5", "web-vitals": "^2.1.4" },
이 중 핵심은 react-scripts로, Webpack 기반의 번들링 설정을 포함하고 있습니다. 우리는 기존 Webpack 설정은 그대로 두고 Rollup을 추가하여 비교할 예정입니다.

Rollup 설치

React 프로젝트를 Rollup으로 번들링하기 위해 필요한 라이브러리를 설치합니다.
npm install -D @rollup/plugin-commonjs @rollup/plugin-html @rollup/plugin-json @rollup/plugin-node-resolve @rollup/plugin-replace @rollup/plugin-typescript rollup rollup-plugin-postcss rollup-plugin-terser

설치 패키지 요약

패키지
역할
React 마이그레이션에서 사용 위치
rollup
번들링 도구의 핵심.
프로젝트 전체를 번들링.
@rollup/plugin-commonjs
CommonJS 모듈을 ES Module로 변환.
React 및 기타 라이브러리 번들링.
@rollup/plugin-html
HTML 파일 생성 및 번들 연결.
index.html 파일 생성.
@rollup/plugin-json
JSON 파일을 ES Module로 가져오기.
JSON 데이터를 사용하는 경우 처리.
@rollup/plugin-node-resolve
외부 패키지(React, ReactDOM 등) 번들링.
React와 ReactDOM 의존성을 처리.
@rollup/plugin-replace
환경 변수 치환(process.env.NODE_ENV).
React의 개발/프로덕션 환경 구분 처리.
@rollup/plugin-typescript
TypeScript 코드를 JavaScript로 트랜스파일링.
TypeScript 기반 React 프로젝트 번들링.
rollup-plugin-postcss
CSS 파일 처리 및 외부 파일로 추출.
React 프로젝트의 스타일 번들링.
rollup-plugin-terser
JavaScript 압축 및 최적화.
프로덕션 환경 번들 최적화.

Rollup 설정

위에서 설치한 라이브러리를 활용하여 rollup.config.mjs 파일을 생성합니다.

설정 코드

import resolve from '@rollup/plugin-node-resolve'; import commonjs from '@rollup/plugin-commonjs'; import typescript from '@rollup/plugin-typescript'; import json from '@rollup/plugin-json'; import { terser } from 'rollup-plugin-terser'; import postcss from 'rollup-plugin-postcss'; import html from '@rollup/plugin-html'; import replace from '@rollup/plugin-replace'; export default { input: 'src/index.tsx', output: { file: 'dist/bundle.js', format: 'iife', name: 'MyApp', sourcemap: true, inlineDynamicImports: true, }, plugins: [ replace({ preventAssignment: true, 'process.env.NODE_ENV': JSON.stringify('production'), }), resolve(), commonjs(), typescript(), json(), postcss({ extract: true, minimize: true, }), html({ title: 'Rollup Migration', template: ({ attributes, files, meta, publicPath, title }) => `<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>${title}</title> <link rel="stylesheet" href="${files.css[0].fileName}"> </head> <body> <div id="root"></div> <script src="${files.js[0].fileName}"></script> </body> </html>`, }), terser(), ], };

Rollup 빌드 실행

rollup.config.mjs를 작성한 후 아래 명령어로 Rollup 빌드를 실행합니다.
npm run build
실행하면 dist 디렉토리에 bundle.jsbundle.css 파일이 생성됩니다.

속도 비교

Rollup과 CRA(Webpack)의 빌드 속도를 비교합니다. 아래 명령어로 두 빌드 시스템을 테스트합니다.
hyperfine \ --export-csv results.csv \ --prepare "rm -rf dist build" \ "npm run build:rollup" \ "npm run build:webpack"

Benchmark 결과 분석

1. 요약

  • npm run build:rollup:
    • 평균 빌드 시간: 2.342초 ± 0.274초
    • 실행 범위: 2.156초 ~ 2.965초
    • 총 실행 횟수: 10회
  • npm run build:webpack:
    • 평균 빌드 시간: 2.934초 ± 0.431초
    • 실행 범위: 2.632초 ~ 3.933초
    • 총 실행 횟수: 10회
  • 결과 비교:
    • Rollup 빌드가 Webpack 빌드보다 1.25배 ± 0.24배 더 빠르게 완료되었습니다.

추가 개선

  1. 파일 크기 비교:
      • Rollup과 Webpack으로 번들된 파일의 크기를 비교하면, Rollup이 더 작은 결과물을 생성합니다. (CRA기준 webpack:563KB, rollup:509KB)
  1. 확장성:
      • Rollup은 라이브러리 번들링에 적합하지만, Webpack은 애플리케이션 개발에 더 유리합니다.
      • Rollup으로 React를 번들링하려니 고려해야할, 설치해야할 라이브러리가 너무 많아 복잡합니다.

보일러플레이트를 위한 번들러

CRA(Create React App)나 CNA(Create Next App)처럼 특정 라이브러리와 프레임워크를 쉽게 다룰 수 있는 보일러플레이트를 구현하고자 합니다. 또한, 간단한 라이브러리를 위한 보일러플레이트를 설계하려는 목표도 가지고 있습니다.
현재는 tsconfig 설정과 다양한 번들러들을 비교하며 연구 중인데요. Webpack의 익숙함과 간결함은 여전히 강점이지만, Rollup을 효과적으로 활용할 줄 안다면 더 유연하고 빠른 개발이 가능하리라 생각합니다.
앞으로는 Rollup을 활용한 보일러플레이트를 제작할 예정입니다. 먼저 간단한 라이브러리용 보일러플레이트를 시작으로, 이를 React, Next.js 등 다양한 환경으로 확장해 나갈 계획입니다. 이를 통해 Rollup의 가능성을 좀 더 깊이 탐구하고 활용 방안을 발전시켜 보겠습니다.
긴 글 읽어주셔서 감사합니다! 😊
피드백은 언제나 환영입니다.