코드 스플리팅

코드스플리팅

코드 스플리팅 이란?

우리가 자바스크립트로 애플리케이션을 개발하게 되면, 기본적으로는 하나의 파일에 모든 로직들이 들어가게 됩니다.
(예를 들어 리액트의 경우 모든 컴포넌트에 대한 코드가 한 파일에 저장되어 버립니다.)
따라서, 프로젝트의 규모가 커질수록 지금 당장 필요하지 않은 자바스크립트 파일 용량도 커지겠죠?
용량이 커지면, 페이지 로딩속도 느려지고 트래픽도 많이 잡아먹게 됩니다.

코드 스플리팅을 하게 되면, 지금 당장 필요한 코드가 아니라면 따로 분리시켜서, 나중에 필요할때 불러와서 사용 할 수 있습니다.
이런 방법을 코드 비동기 로딩이라고 합니다. 코드 비동기 로딩 방식을 통하여, 페이지의 로딩 속도를 개선 할 수 있죠.


자바스크립트 함수 비동기 로딩

자바스크립트 함수가 빌드 되는 과정과 스플리팅하는 방식을 살펴보기 위해 예시 코드를 작성해줍니다.

notify.js
1
2
3
export default function notify() {
alert('안녕하세요!');
}

notify 자바스크립트 함수를 만들고 onClick시 이 함수가 실행되도록 App 컴포넌트를 작성합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import React from 'react';
import logo from './logo.svg';
import './App.css';
import notify from './notify';

function App() {
const onClick = () => {
notify();
};
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<p onClick={onClick}>Hello React!</p>
</header>
</div>
);
}

export default App;

위는 기본적인 빌드과정을 보기 위한 예시입니다.
일반적인 자바스크립트 로직이 모두 한 파일에 저장되는 방식입니다.
(실제로 notify 함수는 실행되지 않아도 불러와져 있습니다.)

사실상 notify 함수는 “Hello React!” 문구를 클릭하기 전까지 필요없는 파일입니다.
notify 함수를 비동기적으로 로딩하기위한 방법으로 아래와 같이 작성할 수 있습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import React from 'react';
import logo from './logo.svg';
import './App.css';

function App() {
const onClick = () => {
import('./notify').then((result) => result.default());
};
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<p onClick={onClick}>Hello React!</p>
</header>
</div>
);
}

export default App;
  • import 구문을 상단에 작성하지 않고 import() 함수 형태로 메서드 안에서 사용하면 파일을 따로 분리시켜 저장할 수 있습니다. 함수가 필요한 시점에 파일을 불러와 사용하게 됩니다.

import 를 함수로 사용하면, Promise 를 반환합니다.
import() 함수는 아직 표준은 아니지만 stage-3 단계에 있는 dynamic import 라는 문법입니다.
현재는, webpack 에서 지원해주고 있는 함수이기에 프로젝트에서 별도의 설정 없이 바로 사용 할 수 있습니다. 이 함수는 모듈을 비동기적으로 CommonJS 형태로 불러오니, 따로 default 를 명시해주어야 합니다.
위에서 사용한 코드에서는 “default 를 notify 를 부르겠다” 라고 설정을 해준 것입니다.


React.lazy, Suspense를 통한 컴포넌트 코드 스플리팅

리액트 16.6 버전부터는 코드 스플리팅을 위해 React.lazy 유틸 함수와 Suspense 컴포넌트가 리액트에 내장되었습니다. (이전 버전에서는 import 함수를 통해 불러온 다음, 컴포넌트 자체를 state에 넣는 방식으로 구현했어야 합니다.)

React.lazySuspense를 사용하면 코드 스플리팅을 위해 state를 따로 선언하지 않아도 간편하게 컴포넌트 코드 스플리팅을 할 수 있습니다.

React.lazy

React.lazy는 컴포넌트를 렌더링하는 시점에 비동기적으로 로딩할 수 있게 해 주는 유틸 함수입니다.

사용방법은 다음과 같습니다.

예시 코드
1
const Split = React.lazy(() => import('./Split'));

Suspense

Suspense는 리액트 내장 컴포넌트로서 코드 스플리팅된 컴포넌트를 로딩하도록 할 수 있고, 로딩이 끝나지 않았을 때 보여줄 UI를 설정할 수 있습니다.

사용방법은 다음과 같습니다.

예시코드
1
2
3
4
5
6
import React, { Suspense } from 'react';

(...)
<Suspense fallback={<div>loading...</div>}>
<Split />
</Suspense>

Suspense에서는 fallback props를 통해 로딩 중에 보여줄 JSX를 지정할 수 있습니다.

  • 프로젝트에 적용
Split.js
1
2
3
4
5
6
7
import React from 'react';

const Split = () => {
return <div>this is Split</div>;
};

export default Split;
App.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import React, { useState, Suspense } from 'react';
import logo from './logo.svg';
import './App.css';

const Split = React.lazy(() => import('./Split'));

function App() {
const [visible, setVisible] = useState(false);
const onClick = () => {
setVisible(true);
};
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<p onClick={onClick}>Hello React!</p>
<Suspense fallback={<div>loading...</div>}>
{visible && <Split />}
</Suspense>
</header>
</div>
);
}

export default App;

주의
React.lazy와 Suspense는 아직 서버 사이드 렌더링을 할 수 없습니다.
서버에서 렌더링 된 앱에서 코드 분할을 하기 원한다면 Loadable Components를 추천합니다

Loadable Components를 통한 코드 스플리팅

Loadable Components는 코드 스플리팅을 편하게 하도록 도와주는 서드파티 라이브러리입니다.
이 라이브러리의 이점은 서버 사이드 렌더링을 지원한다는 것입니다.

서버 사이드 렌더링이란 웹 서비스의 초기 로딩 속도 개선, 캐싱 및 검색 엔진 최적화를 가능하게 해 주는 기술입니다.
서버 사이드 렌더링을 사용하면 웹 서비스의 초기 렌더링을 사용자 브라우저가 아닌 서버 쪽에서 처리합니다.
사용자는 서버에서 렌더링한 html 결과물을 받아 와서 그대로 사용하기 때문에 초기 로딩 속도가 개선되고, 검색 엔진에서 크롤링할 때도 문제없습니다.

서버 사이드 렌더링의 대해서는 다음에 더 자세히 다루고 지금은 Loadable Components의 기본적인 사용법을 알아봅니다.

npm i @loadable/component

사용법은 React.lazy와 비슷하나 Suspense를 사용할 필요는 없습니다.

App.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import React, { useState } from 'react';
import logo from './logo.svg';
import './App.css';
import loadable from '@loadable/component';
const SplitMe = loadable(() => import('./SplitMe'));

function App() {
const [visible, setVisible] = useState(false);
const onClick = () => {
setVisible(true);
};
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<p onClick={onClick}>Hello React!</p>
{visible && <SplitMe />}
</header>
</div>
);
}

export default App;

로딩 중에 보여주고 싶은 UI가 있다면 loadable을 사용하는 부분을 다음과 같이 수정해 주면됩니다.

App.js
1
2
3
const SplitMe = loadable(() => import('./SplitMe'), {
fallback: <div>loading...</div>
});

현재는 “Hello React!” 문구를 클릭하면 onClick 으로 setVisible(true)가 실행되어 SplitMe컴포넌트를 불러오고 렌더링됩니다.

특정 상황에 SplitMe 컴포넌트를 미리 불러오고 싶으면 어떻게 할까요?
preload()를 사용하면 특정 상황에, 예를 들어 : 마우스 오버시 컴포넌트를 미리불러오게 코드를 수정할 수 있습니다.

App.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import React, { useState } from 'react';
import logo from './logo.svg';
import './App.css';
import loadable from '@loadable/component';
const SplitMe = loadable(() => import('./SplitMe'), {
fallback: <div>loading...</div>
});

function App() {
const [visible, setVisible] = useState(false);
const onClick = () => {
setVisible(true);
};
const onMouseOver = () => {
SplitMe.preload();
}
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<p onClick={onClick} onMouseOver={onMouseOver}>Hello React!</p>
{visible && <SplitMe />}
</header>
</div>
);
}

export default App;

마우스 오버시 SplitMe.preload()을 걸어주면 “Hello React!” 위에 마우스 커서를 올리는 순간 SplitMe컴포넌트 로딩이 시작됩니다. 그리고 클릭했을 때 렌더링됩니다.

Loadable Components는 미리 불러오는 기능 외에도 타임아웃, 로딩 UI 딜레이, 서버 사이드 렌더링 호환 등 다양한 기능을 제공합니다.

공식 문서 : https://www.smooth-code.com/open-source/loadable-components/docs/delay/