React - Code Splitting

박정호·2023년 2월 5일
0

React.js

목록 보기
6/6
post-thumbnail

🚀 Start

웹팩은 어플리케이션의 모든 파일들을 묶고 압축하여 하나의 결과물을 만들어주는 번들러이다.

💡 번들러 : 각각의 모듈들에 대해 의존성 관계를 파악하여 하나 또는 여러개의 그룹으로 묶어주는 웹 개발 도구

그리고 이를 통해 HTTP 요청 수를 감소하여 웹사이트 성능을 향상시킬 수 있다.


하지만, 번들링한 파일 자체도 너무 크다면?

번들링을 해야할 전체 파일의 크기가 너무 크기 때문에 번들링을 마친 파일 또한 규모가 클 것이다.

따라서, 번들이 거대해지는 것을 방지하기 위한 방법이 번들을 나누어주자.

결과적으로 코드스필리팅을 사용하여 코드를 다양한 번들로 분할하고, 요청에 따라 로드하거나 병렬로 로드할 수 있게 된다.



⭐️ Code Splitting

코드 분할의 원리는 다음과 같다.

한개의 파일에서 처음부터 모든걸 불러오는 것이 아니라, 내가 설정한대로, 라이브러리나 컴포넌트가 실제로 필요해졌을 때, 나중에 불러오게 하는 것이다.


코드 분할은 웹팩의 가장 매력적인 기능 중 하나로 일반적으로 세가지 방식으로 접근

  • Entry Points(= 진입점) : entry 설정을 사용하여 코드를 수동으로 분할

  • Prevent Duplication(= 중복 방지): Entry dependencies 또는 SplitChunksPlugin을 사용하여 중복 청크를 제거하고 청크를 분할

  • Dynamic Imports(= 동적 가져오기): 모듈 내에서 인라인 함수 호출을 통해 코드를 분할



👉 Entry Points

Entry Points는 웹팩이 앱에서 번들링하려는 모듈의 진입 파일로, 코드 분할을 하는 가장 쉽고 직관적인 방법이다.

만약 다음과 같이 entry를 분리하게 되면, 웹팩에서 자동으로 각각 다른 chunk(큰 덩어리, 묶음)로 관리해주어 따로 로딩하게 해준다.

// webpack.config.js
const path = require('path');

module.exports = {
  mode: 'development',
  entry: {
    index: './src/index.js',
    another: './src/another-module.js',
  },
  output: {
    filename: '[name].bundle.js',
    path: path.resolve(__dirname, 'dist'),
  },
};

💡 chunk: 웹팩에서 애플리케이션 코드를 각각 다른 파일로 나눈것



👉 Prevent Duplication

단, 문제는 이 둘의 dependcies(의존성)도 분리해서 관리하기 때문에, 만약 같은 의존성을 가진 여러 entry point에서 가지고 있다면, 중복된 로딩이 많아져 성능 저하를 일으킬 수 있다.


dependOn 옵션을 사용하여 chunk간 모듈을 공유

  • loash를 각각 가지고 있는 모습(중복)
// webpack.config.js
const path = require('path');

module.exports = {
  mode: 'development',
  index: {
      import: './src/index.js',
      dependOn: 'shared',
    },
    another: {
      import: './src/another-module.js',
      dependOn: 'shared',
    },
    shared: 'lodash',
  },
  output: {
     filename: '[name].bundle.js',
     path: path.resolve(__dirname, 'dist'),
   },
}

기존 entry나 새로운 chunk의 공통 의존성 추출

  • 아래와 같이 SplitChunk 플러그인을 사용하면 하나의 빌드 결과물을 가져온다. 즉, 공통 의존성을 따로 split하여 중복 제거하는 것.
module.exports = {
  // ...
  optimization: {
    splitChunks: {
      chunks: 'all',
    },
  },
}


👉 Dynamic Imports

동적 코드 분할에 대해서 권장되는 방법으로는 ECMAScript 제안을 준수하는 import()구문을 사용하는 방식이다.


동적 코드 분할은 원래 방식대로 그냥 import를 사용하지 않고 원하는 함수형태로 사용하게 된다.

따라서, 함수형태로 사용하여 필요할 때만 import를 실행한뒤, 돌아오는 promise에서 모듈을 실행할 수 있도록 만드는 것이다.

만약 product라는 컴포넌트가 로딩되는데 오랜시간이 걸리면, 유저가 사용하기 전까지는 import를 안해주는게 나을 것이다. 따라서, 다음과 같이 코드를 작성할 수 있다.

if(videoComponent) {
  import('./component/products')
    .then(prducts => {
        products.loadElements()
    })
    .catch(e => console.log(e))
}


⭐️ React.lazy

React에서는 React.lazy()라는 동적 코드 분할을 위한 함수가 내장되어 있다.

따라서, 위에서 봤던 함수형태를, 간단하게 lazy()를 사용하여 동작시킬 수 있다.

import React, { lazy } from 'react';

const Product = lazy(() => import('./component/products'));

const MainComponent = () => (
  <>
    <Prdouct />
  </>
)


👉 Suspense

단, lazy 컴포넌트는 Suspense 컴포넌트 하위에서 렌더링되어야한다.

👍 Suspense는 lazy 컴포넌트가 로드되길 기다리는 동안 로딩 화면과 같은 예비 콘텐츠를 보여줄 수 있게 된다.

Loading UI 출력을 통한 사용자 경험 개선

  • fallback propdms 컴포넌트가 로드될 때까지 기다리는 동안 렌더링하려는 React 앨리먼트를 받아들인다.
import React, { lazy } from 'react';

const Product = lazy(() => import('./component/products'));

const MainComponent = () => (
  <>
    <Suspense fallback={<div>Loading...</div>}>
		<Prdouct />
    </Suspense>
  </>
)

💡 Route-based code splitting

앱에 코드 분할을 도입하기 좋은 장소는 라우트이다. 웹페이지를 불러오는 시간은 페이지 전환에 어느 정도 발생하여 대부분 페이지를 한번에 렌더링하기 때문에 사용자가 페이지를 렌더링하는 동안 다른 요소와 상호작용하지 않는다.



👉 Error boundaries

만약 네트워크 장애와 같은 이유로 로드에 실패할 경우 에러를 발생시킬 수 있다. 이때 Error Boundaries를 이용하여 사용자의 경험과 복구 관리를 처리할 수 있다.

Error Boundary를 만들고 lazy 컴포넌트를 감싸면 네트워크 장애가 발생했을 때 에러를 표시할 수 있다.

import React, { lazy } from 'react';
import MyErrorBoundary from './MyErrorBoundary';


const Product = lazy(() => import('./component/products'));

const MainComponent = () => (
  <>
    <MyErrorBoundary>
     <Suspense fallback={<div>Loading...</div>}>
		 <Prdouct />
     </Suspense>
    </MyErrorBoundary>
  </>
)

💡 정리하면, 위와 같은 코드를 통해 product 컴포넌트는 유저 사용시에 불러지는 동적인 import가 가능해지며, Suspense의 기능으로 불러오는동안 사용자에게 보여줄 로딩창을 출력시켜 사용자 경험을 개선시킬 수 있다. 또한, 에러 발생에 대한 UI도 출력시켜 사용자 경험과 복구 관리를 처리 가능하다.



✚ Loadable Components

React.lazy의 대안은 아니지만, 비슷한 역할을 하는 React 코드분할 라이브러리가 있다.

바로, Loadable Components이며, 다음과 같은 특징을 가진다.

  • 라이브러리 분할
  • 미리 가져오기
  • 서버 사이드 렌더링
  • 전체 동적 가져오기
import loadable from '@loadable/component'

const Product = loadable(() => import('./component/products'));

const MainComponent = () => (
  <>
    <Prdouct />
  </>
)

✔️ React.lazy와 달리 SSR까지 커버가 가능하다는 것만 알고가면 될 것 같다.



💡 Reference
👉 Webpack을 이용한 코드 스플리팅
👉 코드 분할 - React 공식문서
👉 Code Splitting
👉 Loadable Components - 공식문서

profile
기록하여 기억하고, 계획하여 실천하자. will be a FE developer (HOME버튼을 클릭하여 Notion으로 놀러오세요!)

0개의 댓글