Code Splitting

Taemin Jang·2024년 3월 20일
0

번들링

웹 개발에서 번들링은 빌드라고 할 수 있다.

번들링은 HTML, CSS, JS와 같이 각각의 모듈들을 하나의 묶음으로 만드는 작업을 의미한다.

즉, 모듈 간의 의존성 관계를 파악해 그룹화시켜주는 작업이다.

번들링을 하는 이유 중 하나는 파일 크기 문제를 해결할 수 있고, 번들링된 파일은 원본 파일보다 크기가 작아 파일 실행 속도와 로딩 속도가 빨라진다.

Code Splitting

애플리케이션의 규모가 커지면 번들링을 하더라도 번들 사이즈는 커지게 된다. 특히 큰 규모의 서드 파티 라이브러리를 추가하고 앱이 커져 번들된 파일도 커지게 되므로, 번들링만으로는 한계가 있다. 따라서 이를 해결할 수 있는 방안이 코드 스플리팅이다.

코드 스플리팅을 하면 초기에 필요하지 않은 컴포넌트 정보들을 불러오지 않고 필요한 시점에서 불러와 로딩 속도를 향상시킬 수 있다.

코드 스플리팅을 적용하는 다양한 방법에 대해 알아보자.

Dynamic import()

코드 스플리팅을 적용시키는 방법으로 dynamic import() 문법을 사용하는 것이다.

dynamic import()를 사용하면 애플리케이션에 필요한 모듈을 필요한 시점에 로드할 수 있어 초기 로딩 속도를 향상시키고, 필요하지 않은 모듈의 로드를 지연시킬 수 있다.

dynamic import()는 모듈을 읽고 해당 모듈이 export하는 것들을 객체로 담아 fulfilled한 Promise를 반환한다.

  • then() 메소드를 사용해 로드된 모듈을 수행할 작업에 대한 정의가 가능하다.
  • catch() 메소드로 모듈 로드 중에 발생하는 오류를 처리할 수 있다.

사용법

// math.js
export function add(num1, num2) {
  return num1 + num2;
}

// static import()
import { add } from './math';

console.log(add(16, 26));

// dynamic import()
import("./math").then(math => {
  console.log(math.add(16, 26));
});

만약 가져오는 파일에 export가 여러개면 다음과 같이 가져올 수 있다.

// math.js
export function add(num1, num2) {
  return num1 + num2;
}

export function minus(num1, num2) {
  return num1 - num2;
}

export default (num) {
  return num;
}

// static import()
import math, { add, minus } from './math';

console.log(math(123), add(16, 26), minus(26, 16));

// dynamic import()
import("./math").then(({default: math, add, minus}) => {
  console.log(math(123), add(16, 26), minus(26, 16));
});

객체 구조 분해 할당으로 받아와 math는 export default 키워드로 내보냈기 때문에 default로 사용해야한다.

export default와 export 차이
export default는 한 파일(모듈)에 하나만 있다는 것을 명확하게 나타내서 원하는 이름으로 import할 수 있다. (이름이 없어도 가능)
export는 내보내는 모듈의 이름이 꼭 있어야하며, export한 이름으로 import를 해야한다.

// static import()
import { setupAdminUser } from './admin.js';

if (user.admin) {
  setupAdminUser();
}

// dynamic import()
if (user.admin) {
  import('./admin.js').then({ setupAdminUser }) => {
    setupAdminUser();
  }
}

static import() 코드는 실제 관리자만 해당 코드를 실행하지만, 일반 사용자들도 setupAdminUser() 함수를 다운로드하게 된다.

만약 admin.js 파일이 엄청 거대한 파일일 경우, 일반 사용자들은 필요없음에도 다운로드해 로드 속도를 저하시킬 수 있다.

따라서 dynamic import()를 사용하게 되면 관리자 기능에 필요한 파일과 코드는 관리자에게만 제공되고, 일반 사용자들은 불필요한 리소스를 다운로드 하지 않기 때문에 초기 로드 속도를 향상시킬 수 있다.

변수에도 dynamic import()를 적용할 수 있다.

// static import
import englishTranslations from "./en-translations";
import spanishTranslations from "./sp-translations";
import frenchTranslations from "./fr-translations"; // 사용하지 않는 대용량의 파일을 import한다.

const user = { locale: "sp" };

let translations;

switch (user.locale) {
  case "sp":
    translations = spanishTranslations;
    break;
  case "fr":
    translations = frenchTranslations;
    break;
  default:
    translations = englishTranslations; 
}
console.log(translations.HI); // hola

// dynamic import
const user = { locale: "ko" };

import(`./${user.locale}-translations.js`)
  .catch(() => import('./en-translations.js')) // 위의 import문에서 에러가 날 경우, catch 구문이 실행되며 영어 번역을 불러온다.
  .then(({ default: translations }) => {
    console.log(translations.HI); // catch문의 import가 정상적으로 이루어지면 이어서 then이 실행되며 hi가 출력된다.
  });

또한 async / await 구문과 함께 사용할 수도 있다.

export default async function Logo({ name }: { name: string }) {
	const reName = name[0].toUpperCase() + name.slice(1) + 'Logo'
	const res = await import(`@/data/svgs/${reName}.svg`)
	const Logo = res.default
	return <Logo />
}

React.lazy

React.lazy 함수를 사용하면 동적 import를 사용해 컴포넌트가 사용되는 시점에 가져오도록 구현할 수 있다.

즉, 컴포넌트를 렌더링하는 시점에서 비동기적으로 로딩할 수 있게 해준다.

React.lazy로 불러오는 컴포넌트는 export default로 내보내야만 한다.

사용법

const Component = React.lazy(() => import('./Component'));
const Wrapper = () => {
  return <div><Component/></div>
}

이렇게 사용하게 되면 아무것도 없는 페이지가 노출되었다가, 해당 컴포넌트가 보이게 된다.

Suspense

React.lazy로 불러온 컴포넌트는 단독으로 쓰일 수 없고, Suspense 컴포넌트 하위에서 렌더링되어야 한다.

Suspense는 로딩을 완료할 때까지 fallback UI를 보여주다가 로딩이 완료되면 코드 스플링된 자식 컴포넌트를 보여준다.

사용법

const Component = React.lazy(() => import('./Component'));
const Wrapper = () => {
  const [visible, setVisible] = React.useState(false);
  const onClick = () => setVisible(true);
  return (
    <div>
      <button onClick={onClick}>클릭</button>
      <React.Suspense fallback={<div>Loading.....</div>}>
        {visible && <Component/>}
      </React.Suspense>
    </div>
  )
}

버튼을 클릭하면 visible이 true로 상태가 바뀌고 Component가 렌더링되는 중에 fallback UI가 보이다가 완료되면 해당 컴포넌트가 렌더링된다.

SPA로 페이지를 개발하고 있다면, 코드 스플리팅을 이용해 페이지 별로 파일을 나눌 수 있다.

import React, { Suspense, lazy } from 'react';
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
const Home  = lazy(() => import('./page/Home '));
const About = lazy(() => import('./page/About'));
const Page = () => (
  <Router>
    <Suspense fallback={<div>Loading...</div>}>
      <Routes>
        <Route path="/" component={<Home/>}/>
        <Route path="/about" component={<About/>}/>
      </Routes>
    </Suspense>
  </Router>
);

참고

profile
하루하루 공부한 내용 기록하기

0개의 댓글