[Revisit Project] Vite과 코드 분할로 성능 최적화

2023. 10. 2. 19:58Project Tours/Tour on Plantopia

1. 고민의 시작

 나는 이 프로젝트를 시작 전 성능 최적화가 목표였다. 이 전까지는 그냥 리액트 문법에 익숙해지자는 목표가 있었다면 한 단계 더 나아가 리액트를 잘 사용해보자라는 의미였다. 잘 사용하는 데에는 많은 방법이 존재하겠지만 그 중 성능 최적화가 눈에 들어와 이 것을 목표로 하였다. 이 전 프로젝트에서 Vite을 알게된 점이 가장 큰 이유였다. 프로젝트 시작 전에 Vite을 사용을 할 것을 제안했고 vite이 왜 cra보다 빠른지 팀원들에게 소개했다. 단순 vite 사용만으로도 성능 개선이 이루어지지만 이 외 방법들도 많기에 어떤 방법들이 존재하는지 알아보고 적용하고 싶었다.

2. 고민 해결의 과정

2-1 먼저 성능 최적화란

> 일단 웹 성능을 결정하는 요소로로는 크게 로딩 성능과 렌더링 성능으로 나눌 수 있다.

 

- 로딩 성능은 서버에 있는 웹 페이지와 웹 페이지에 필요한 기타 리소스를 다운로드할 때의 성능이다. 

 

- 렌러링 성능은 다운로드한 리소를 가지고 화면을 그릴 때의 성능이다. 사실 코드에 따라서 많이 달라지는데 이 부분은 갑자기 효율을 높이기 어려운 부분이라고 생각했다.

 

팀원들에게 Vite 빠르다는 것을 이해시키기는 쉬웠지만 그 때 잘 설명하지 못해 먼저 정리 부터 하고가는게 좋겠다.

 

2-2 팀원들에게 설명했던 CRA에 비해 Vite이 가지는 장점을 정리

 

1)  ES Build를 통한 프리번들링

 

> CRA의 번들링을 웹팩은 JS를 사용하고 Vite은 프리번들링으로 매우 빠른 ES Build는 Go를 사용한다. Go는 로우 레벨 언어로 자바스크립트보다 빠르다. 일단 이것은 라이브러리 자체의 차이고 Vite에서는 이를 이용해 미리 필요한 것들을 미리 bundle로 만들어놓는다. 이렇다보니 새로 번들링을 해야하는 CRA보다 빠를 수 밖에 없다. 그리고 Vite의 실제 번들링은 RollUp을 이용한다. 그 이유는 ESBuild가 Code splitting, CSS처리 관련 기능이 부족하기 RollUp을 채택하였다.

 

게이지가 다 찰 때까지 기다리기 힘들정도로 속도 차이를 느낄 수 있다.

2) 변경 사항 처리 방식의 차이

 

> 어떤 모듈이 수정되면  Vite 는 그저 수정된 모듈과 관련된 부분만을 교체할 뿐이고, 브라우저에서 해당 모듈을 요청하면 교체된 모듈을 전달한다. 전 과정에서 완벽하게 ESM을 이용하기에, 앱 사이즈가 커져도 HMR을 포함한 갱신 시간에는 영향을 끼치지 않는다. 이같은 경우가 가능해진 것은 브라우저가 ES6 문법을 받아드렸기에 가능했다. 정리하자면, Vite은 변경 사항만 다시 번들링한다. Vite은 dependencies와 source code를 구분해 개발 시 바뀌지 않은 디팬던시 코드를 ES Build로 사전 번들링을 하고 계속 수정해야하는 소스 코드는 브라우저의 ESM을 이용해서 반영할 수 있게 처리했다.  

 

 

2-3 코드 스플리팅 적용

1)  코드 스플리팅

 

> Vite의 장점을 살펴보면서 코드 스플리팅을 알게 되었다. ES Build에서 이 것을 지원안해줘서 RollUp을 사용한다는 이야기를 보았기에 더 궁금해졌다.

 

> 알아보니 코드 스플리팅은 말 그대로 코드를 쪼개는 것이다. 이유는 즉슨 SPA 특성상 모든 코드를 한번에 번들링하는데 이것을 지금 현재 필요한 부분만 번들링하는 것이다. 이를 위해서 React.Lazy, Suspense는 리액트 16 버전에 추가되었다. React.Lazy는 렌더링하는 시점에 비동기적으로 로딩할 수 있게 해주는 유틸 함수이고 Suspense는 코드 스플리팅 된 컴포넌트를 로딩하고 로딩이 끝나지 않았을 때 UI를 설정할 수 있다. 우리는 Progress라는 로딩 페이지를 만들어줘서 넣어줬다.

 

> 이 방법은 라우팅을 통해서 스플리팅하는 방법인데 직접 웹팩에서 설정할 수 있다. Vite의 경우 RollUp을 통해 설정해야한다. 

 

import { useEffect, Suspense } from 'react';
import { setBodyHeight } from './utils/setBodyHeight';
import Progress from './components/progress/Progress';
import Toast from './components/notification/ToastContainer';
import AppRoutes from './routes/AppRoutes';
import 'react-toastify/dist/ReactToastify.css';
import '@/styles/customToastStyles.scss';
import 'react-confirm-alert/src/react-confirm-alert.css';
import '@/styles/alertStyle.scss';

const App = () => {
  useEffect(() => {
    setBodyHeight();
  }, []);

  return (
    <>
      <Toast />
      <Suspense fallback={<Progress />}>
        <AppRoutes />
      </Suspense>
    </>
  );
};

export default App;

> 라우팅을 할 때는 따로 라우터 페이지를 따로 두는 것을 좋아해서 따로 두었다.

import { Route, Routes } from 'react-router-dom';
import rootRoutes from './rootRoutes';
import diaryRoutes from './diaryRoutes';
import dictRoutes from './dictRoutes';
import myPageRoutes from './myPageRoutes';
import myPlantRoutes from './myPlantRoutes';

const routes = [
  rootRoutes,
  diaryRoutes,
  dictRoutes,
  myPageRoutes,
  myPlantRoutes,
];

const AppRoutes = () => (
  <Routes>
    {routes.map(route =>
      route.map(({ path, element }) => <Route path={path} element={element} />),
    )}
  </Routes>
);

export default AppRoutes;

> 마지막으로 각 URL 진입 전 Lazy 함수를 처리해주었다.

import { lazy } from 'react';

const MyPlantMainPage = lazy(
  () => import('@/pages/myPlantPage/myPlantMainPage/MyPlantMainPage'),
);
const MyPlantDetailPage = lazy(
  () => import('@/pages/myPlantPage/myPlantDetailPage/MyPlantDetailPage'),
);
const MyPlantRegisterPage = lazy(
  () => import('@/pages/myPlantPage/myPlantRegisterPage/MyPlantRegisterPage'),
);
const MyPlantEditPage = lazy(
  () => import('@/pages/myPlantPage/myPlantEditPage/MyPlantEditPage'),
);

const myPlantRoutes = [
  { path: '/myplant', element: <MyPlantMainPage /> },
  { path: '/myplant/:docId', element: <MyPlantDetailPage /> },
  { path: '/myplant/:docId/edit', element: <MyPlantEditPage /> },
  { path: '/myplant/register', element: <MyPlantRegisterPage /> },
];

export default myPlantRoutes;

 

3. 결론

 

> 라이트 하우스를 확인해보면 55점에서 73점으로 오른 모습을 볼 수 있었다. 사실 아직 만족스럽지 않은 점수인데 그래도 놀라운 점수이다. 그리고 아직 성능이 낮은 이유를 살펴보았는데 이미지가 문제였다. 추가로 이미지 작업을 해보록 하겠다.