[React] 빌드 용량 최적화하기(feat. Vite)

찐새·2023년 8월 20일
6

React

목록 보기
20/21
post-thumbnail

얼마 전에 본 면접에서 SPA(Single Page Application)의 단점을 어떻게 해결하느냐에 대한 질문을 받았다. "코드 스플리팅을 통해 파일을 여러 개로 분산하여 로드한다"라고 답했으나, 양심의 가책을 느꼈다. 내 프로젝트에서는 코드를 나눴어도 index.js의 용량이 어마어마했기 때문이다.

bip 빌드 후 용량

lazy를 사용하여 컴포넌트 코드는 스플리팅했지만, 중심이 되는 파일의 용량은 매우 컸다. 뭐 이리 뚱뚱한가 봤더니 사용한 라이브러리가 모두 index에 담겨 있었다. 고민했지만 답은 경고 문구에 있었다.

Use build.rollupOptions.output.manualChunks to improve chunking: https://rollupjs.org/configuration-options/#output-manualchunks

vite에서 빌드 도구로 사용하는 rollup의 수동 스플릿 방법을 안내한다. 공통으로 사용하는 모듈을 분리하는 역할이다. 사용법을 익히기 위해 간단한 프로젝트를 만들어봤다.

setting

npm create vite@latest를 실행하고 React-js 환경으로 설정했다. 페이지를 스플리팅하기 위해 react-router-dom도 함께 설치했다.

/src
  ┣─ App.jsx
  ├─ About.jsx
  └─ main.jsx

대충 이렇게 파일을 만들고 라우트를 작성했다.

import React, { Suspense, lazy } from "react";
import ReactDOM from "react-dom/client";
import "./index.css";
import { BrowserRouter, Routes, Route, Link } from "react-router-dom";
import App from "./App";
import About from "./About";

ReactDOM.createRoot(document.getElementById("root")).render(
  <React.StrictMode>
    <BrowserRouter basename="/">
      <nav>
        {/* 대충 네비게이션 */}
      </nav>
      <Routes>
        {/* 대충 라우트 */}
      </Routes>
    </BrowserRouter>
  </React.StrictMode>
);

기본적인 방식으로 컴포넌트를 라우트에 등록했다. 이 상태로 여과 없이 빌드해 보자.

초기 빌드

초기 빌드

빌드 로그를 보면 파일 세 개만 생겼다. 내가 사용한 모듈과 컴포넌트는 모두 index.js에 들어있을 터이다.

초기 빌드 후 로드

빌드한 파일을 실행해 보면 빌드된 단 하나의 index.js만 로드한다. 큰 프로젝트가 이렇게 로드된다고 생각해 보자. SPA의 단점인 초기 로딩 속도 느림이 아주 도드라질 것이다.

lazy 사용 빌드

이번에는 React.lazy를 사용해서 스플리팅 후 빌드해보자.

const App = lazy(() => import("./App"));
const About = lazy(() => import("./About"));

ReactDOM.createRoot(document.getElementById("root")).render(
  <React.StrictMode>
    <BrowserRouter basename="/">
      <nav>
        {/* 대충 네비게이션 */}
      </nav>
      <Routes>
        {/* 대충 라우트 */}
      </Routes>
    </BrowserRouter>
  </React.StrictMode>
);

lazy 빌드

의도한 대로 AppAbout 컴포넌트는 코드가 분리되어 새로운 파일로 생성되었다. 여기까지가 내가 생각한 코드 스플리팅이었는데, 아무리 봐도 저 index.js의 크기가 납득되지 않았다. 겨우 0.11kb 분리하고 스플리팅이라니. 그렇기 때문에 위에서 언급한 rollupOptions를 추가한 것이다.

manualChunks 빌드

rollupOptionsvite.config.js에서 build 속성에 추가하여 설정한다.

import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [react()],
  build: {
    rollupOptions: {
      output: {
        manualChunks: (id) => {
          if (id.includes("node_modules")) {
            return `vendor`;
          }
        },
      },
    },
  },
});

manualChunks에 추입하는 id에 뭐가 들었나 궁금해 콘솔을 찍어봤더니 사용한 모듈의 경로가 쭉 나왔다. 이 상태로 빌드하면 node_modules에서 사용한 코드가 vendor.js 에 모두 모인다. index.js는 가벼워지겠지만, vendor가 무거워진다. 라이브러리가 무거운 거야 어쩔 수 없지마는, 조절할 수 있다면 최대한 줄여보는 게 좋겠다 싶었다.

일단 id에 찍힌 모듈 모양은 이렇다.

D:/work-space/test-build/node_modules/react-dom/index.js?commonjs-module
D:/work-space/test-build/node_modules/react-dom/cjs/react-dom.production.min.js?commonjs-proxy
D:/work-space/test-build/node_modules/react-dom/cjs/react-dom.production.min.js?commonjs-exports
...
D:/work-space/test-build/node_modules/scheduler/cjs/scheduler.production.min.js?commonjs-exports

필요한 부분은 node_modules 뒷부분의 모듈명이다. 이것만 잘라낸다.

const module = id.split("node_modules/").pop().split("/")[0];

/*
...
react
react
react
react-dom
scheduler
scheduler
*/

vendor/${module}을 반환값으로 설정하면 vendor 디렉토리에 모듈별로 파일이 생긴다.

rollupOptions 수정 빌드

빌드 파일로 앱을 실행해 보면 차이점이 확연히 드러난다.

rollupOptions 수정 빌드 후 로드

결과

이런 방법으로 내 프로젝트의 빌드 용량을 최적화하여 690KB에 육박하던 index.js 크기를 약 30KB로 줄였다.

찾은 방법으로 코드를 나누긴 했지만 맞는 방법인지는 모르겠다. 더 나은 방법은 더 찾아봐야겠지만, 소기의 목적은 달성했다. 공부하다 보면 더 나은 방법이 떠오를지도 모르겠다.


참고
rollup - Configuration Options#output-manualChunks

profile
프론트엔드 개발자가 되고 싶다

0개의 댓글