본문 바로가기
카테고리 없음

코드스플리팅(Code splitting)

by jyee 2024. 12. 22.
728x90
반응형

 

https://crystallize.com/comics/no-code-splitting-vs-code-splitting

 

Code splitting은 왜 하는걸까?

웹 애플리케이션은 점점 더 복잡해지고 규모가 커지고 있다. 이런 변화 속에서 애플리케이션의 코드 역시 방대해지고, 모든 코드를 하나의 번들 파일로 묶는 방식은 초기 로딩 속도와 사용자 경험에 있어 한계를 보이게 된다. 이런 문제를 해결하기 위한 기술이

코드스플리팅이다. 

자바스크립트 프로젝트 만들 때  모든 코드를 하나의 번들로 묶어서 만들게 되는데 앱이 비교적 간단한 편이면 괜찮지만 프로젝트가 커지면서 전달해야 하는 파일도 커지고, 유저의 브라우저가 파싱(parsing) 해야하는 정보도 많아지기 때문에 퍼포먼스 문제들이 생길 수 밖에 없다. 코드 스플리팅은 애플리케이션의 코드를 더 작은 단위로 나눠 필요한 코드만 로드하고, 나머지 코드는 필요한 순간에 로드하도록 만드는 방식이다. 이 방법을 통해 초기 로딩 시간을 줄이고, 네트워크 리소스를 효율적으로 사용하며, 브라우저 캐싱을 적극 활용해 성능을 최적화할 수 있다.

 

코드 스플리팅의 작동 원리 

1. 엔트리 포인트 분할: 애플리케이션의 다양한 진입점에 따라 코드를 나눔

2. 동적 임포트: import() 함수를 사용하여 필요한 시점에 모듈을 로드

3. 벤더 분할: 서드파티 라이브러리를 별도의 청크로 분리

4. 공통 청크 추출: 여러 페이지에서 공통으로 사용되는 코드를 별도의 청크로 추출

 

코드 스플리팅의 장점

1. 초기 로딩 시간 감소: 필요한 코드만 먼저 로드하므로 초기 페이지 로딩 속도가 빨라짐

2. 리소스 효율성: 사용자가 실제로 필요로 하는 코드만 다운로드하므로 네트워크 리소스를 절약

3. 캐싱 최적화: 변경되지 않은 코드 청크는 브라우저 캐시를 효과적으로 활용

4. 병렬 로딩: 여러 작은 청크를 병렬로 로드할 수 있어 전체적인 로딩 시간을 줄임

 

React에서의 Code splitting

 

1. React.lazySuspense

React 애플리케이션에서 코드 스플리팅을 검색했을때 가장 많이 나오는 것이 React.lazySuspense 였다.

이거는  컴포넌트 단위의 코드 스플리팅에 초점을 둔다

import React, { Suspense, lazy } from 'react';
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';

const Home = lazy(() => import('./routes/Home'));
const About = lazy(() => import('./routes/About'));
const Contact = lazy(() => import('./routes/Contact'));

function App() {
  return (
    <Router>
      <Suspense fallback={<div>Loading...</div>}>
        <Switch>
          <Route exact path="/" component={Home} />
          <Route path="/about" component={About} />
          <Route path="/contact" component={Contact} />
        </Switch>
      </Suspense>
    </Router>
  );
}

export default App;

 

동작 원리

  • React의 lazy는 동적 import()를 통해 필요한 컴포넌트를 동적으로 로드하며 해당 컴포넌트가 렌더링될 필요가 있을 때 비로소 관련 청크를 요청.
  • Suspense는 컴포넌트가 로드될 때까지 로딩 상태를 표시하거나 대체 콘텐츠를 렌더링할 수 있게 해줌.

 

주의할 점?

1. 항상 필요한 컴포넌트는 lazy로 나누지 말 것 
-네비게이션, 헤더, 푸터처럼 모든 페이지에서 항상 필요한 컴포넌트는 lazy로 로드하지 않아야 한다.

 

2. 초기 로드 속도를 고려할 것 

-너무 많은 컴포넌트를 lazy로 나누면 페이지가 로드될 때마다 지연이 발생할 수 있습니다. 특히 자주 방문하는 페이지는 lazy를 너무 많이 적용하면 안됨. 핵심적인 header는 번들에 포함하고 무거운 herosection과 footer만 lazy로 처리함 

 

3. fallback 디자인

사용자에게 로딩 중임을 알리는 fallback UI는 사용자 경험에 중요한 역할을 함. 너무 간단하거나 너무 복잡한 fallback은 사용자에게 혼란을 줄 수 있음 따라서 fallback 디자인을 신경 써야 함 

 

4. 네트워크 상태 고려

느린 네트워크 환경에서는 컴포넌트 로딩에 시간이 더 걸릴 수 있음. 이를 고려하여 Fallback UI의 표시 시간을 조절하거나, 사용자에게 네트워크 상태에 따른 추가적인 안내를 제공해야함 

 

5. SuspenseList 사용

여러 lazy 컴포넌트의 로딩 순서를 제어하려면 SuspenseList를 활용가능. 이를 통해 여러 컴포넌트의 로딩 순서를 정의하거나, 동시에 로드되도록 설정할 수 있음 

 

React의 lazy는 런타임에서 필요한 순간에 컴포넌트를 요청한다 즉, 사용자가 특정 페이지에 접근했을 때 서버로부터 새로 분리된 청크를 요청하게 된다. 네트워크 요청으로 인해 청크를 불러오는 시간이 소요되므로 초기 렌더링이 느려질 수 있다. 만약 요청된 청크 파일이 작지 않다면 오히려 사용자 경험이 저하될 수 있다. 


2. vite의 manualChunks 설정

동작원리

  • vite에서 manualChunks는 Rollup의 코드 스플리팅 기능을 활용하여 번들링 단계에서 미리 파일을 분리 
  • 주로 의존성 라이브러리(node_modules)를 기준으로 코드를 나눠 네트워크 요청과 로드 성능을 최적화한다.
  • 설정된 규칙에 따라 정적 분석을 통해 번들 파일을 나누고, 각 파일은 entrypoint에 따라 로드된다.

manualChunks 설정은 번들링 최적화에 유용하지만, 설정을 과도하게 세분화할 경우 네트워크 요청이 지나치게 많아져 오히려 성능 저하를 초래할 수 있다는 단점이 있다.

rollupOptions: {
  output: {
    manualChunks: (id) => {
      if (id.includes("node_modules")) {
        const module = id.split("node_modules/").pop()?.split("/")[0];
        return `vendor/${module}`;
      }
    },
  },
},

 

 

주요 차이점

구분 manualChunks React.lazy + Suspense
스플리팅 대상 번들 파일 전체 (라이브러리, 모듈 포함) React 컴포넌트 단위
작동 시점 빌드 시점 (정적 분석) 런타임 시점
사용 목적 번들 크기 최적화, 중복된 의존성 처리 특정 컴포넌트를 동적으로 로드하여 초기 로딩 속도 개선
의존성 처리 방식 모듈/라이브러리 단위 컴포넌트 단위
주요 장점 의존성 관리가 용이, 전체 번들 크기 감소 페이지/컴포넌트 로딩 속도 최적화 가능
주요 단점 설정이 복잡할 수 있음, 세분화가 과하면 성능 저하 가능 모든 컴포넌트 로드에 적합하지 않음

 

서로 다른 레벨에서 작동하기 때문에 함께 사용할 수 있다.

  • manualChunks: 의존성 라이브러리를 기준으로 청크를 분리하여 번들 크기를 최적화.
  • React.lazy: 컴포넌트를 동적으로 로드하여 초기 화면 렌더링 성능을 최적화.
// vite.config.js
rollupOptions: {
  output: {
    manualChunks: (id) => {
      if (id.includes("node_modules")) {
        const module = id.split("node_modules/").pop()?.split("/")[0];
        return `vendor/${module}`;
      }
    },
  },
}
// App.tsx
const LazyComponent = lazy(() => import('./MyComponent'));

function App() {
  return (
    <Suspense fallback={<div>Loading...</div>}>
      <LazyComponent />
    </Suspense>
  );
}

결론적으로, manualChunksReact.lazy + Suspense는 서로 다른 레벨에서 동작하는 코드 스플리팅 기술로, 각각 빌드 단계와 런타임 단계에서의 최적화를 담당한다. manualChunks는 애플리케이션 전체의 번들 크기를 효과적으로 관리할 수 있는 도구이고, React.lazy는 특정 컴포넌트 로드를 동적으로 제어함으로써 초기 렌더링 성능을 개선한다.
이 두 기술은 단독으로도 유용하지만, 함께 사용하면 번들 크기와 로드 시간을 모두 최적화할 수 있는 강력한 조합이 된다.

728x90
반응형