안녕하세요. J4J입니다.
이번 포스팅은 코드 스플릿팅으로 최적화하는 방법에 대해 적어보는 시간을 가져보려고 합니다.
코드 스플릿팅이란?
코드 스플릿팅이라고 하는 것은 webpack, rollup, browserify와 같은 모듈 번들러를 이용하여 만들어진 하나의 번들 파일을 여러 개의 번들 파일로 나누는 것을 의미합니다.
그리고 하나의 번들 파일을 여러 개의 번들 파일로 나누는 이유는 더 빠른 속도로 화면을 로드하기 위해서입니다.
모듈 번들러를 사용하게 된 이유부터 간단히 언급을 해보자면 브라우저에서 호출하는 파일의 개수를 줄여 부하가 발생되는 것을 방지하기 위해 번들러를 사용하고 있습니다.
하지만 프로젝트의 규모가 커짐과 동시에 번들링 되는 파일의 크기도 점점 커지게 되고 이것은 결국 url을 입력하여 처음 접근을 하게 될 때 크기가 커진 번들 파일을 로드하는 시간이 길어지는 결과를 만듭니다.
곧 사용자가 느끼는 서비스의 만족도에 영향을 미칠 수 있게 됩니다.
코드 스플릿팅의 역할은 이런 결과를 방지하는 것에 있습니다.
하나의 번들 파일을 여러 개의 번들 파일로 나눈 뒤 실제 로드될 화면에 필요한 번들 파일만 불러오고 나머지 번들 파일은 호출하지 않고 지연시킴으로 써 작업량을 줄여 더 빠른 속도로 화면이 보일 수 있게 도와줍니다.
공식 문서를 참고해보면 코드 스플릿팅을 적용할 최적의 위치로 라우트를 지칭하고 있습니다.
그 이유로는 "웹 페이지를 불러오는 시간은 페이지 전환에 어느 정도 발생하며 대부분 페이지를 한 번에 렌더링 하기 때문에 사용자가 페이지를 렌더링 하는 동안 다른 요소와 상호작용하지 않습니다."라고 말하고 있습니다.
그래서 아래에서 코드 스플릿팅을 적용해볼 때도 라우트에 적용을 하려고 합니다.
추가적으로 라우트 말고도 어떤 컴포넌트를 불러오더라도 코드 스플릿팅을 모두 적용해낼 수 있습니다.
하지만 개인적인 생각으론 무분별한 코드 스플릿팅은 번들링을 하는 이유를 사라지게 만드는 것이라고 생각됩니다.
코드 스플릿팅 적용 방법
검색을 해봤을 때 코드 스플릿팅을 적용하는 방법은 정말 여러 가지가 있었습니다.
그중 제 눈에 들어온 것은 공식 문서에서도 확인할 수 있는 React.lazy를 이용한 dynamic import입니다.
React.lazy를 이용하여 코드 스플릿팅을 적용해보겠습니다.
예를 들어 App.jsx에서 다음과 같이 라우트 설정이 되어있다고 가정해보겠습니다.
※ 프로젝트 초기 설정이 필요하신 분은 이곳을 참조
※ 라우트 설정이 필요하신 분은 이곳을 참조
import * as React from 'react';
import { Redirect, Route, Switch } from 'react-router-dom';
import HelloWorld from './helloWorld';
import HelloReact from './helloReact';
const App = () => {
return (
<>
<Switch>
<Route exact path='/' component={() => <Redirect to='/helloWorld' />} />
<Route exact path='/helloWorld' component={HelloWorld} />
<Route exact path='/helloReact' component={HelloReact} />
</Switch>
</>
)
}
export default App;
그리고 webpack 설정은 다음과 같이 되어 있습니다.
const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
module.exports = {
// 개발모드, development or production
mode: "development",
// entry를 기준으로 연관된 모든 파일들을 번들링
entry: "./src/index",
// 번들링 될 파일 확장자 등록
resolve: {
extensions: [".js", ".jsx"]
},
// 바벨과 같은 로더 등록
module: {
rules: [
{
test: /\.jsx?$/,
loader: "babel-loader",
exclude: ['/node_modules/']
}
]
},
// 빌드 설정
output: {
path: path.resolve(__dirname, "deploy"), // 빌드되는 파일들이 만들어지는 위치, __dirname: 현재 디렉토리
filename: "bundle.js", // 번들파일 이름
publicPath: "/", // output 위치 지정
},
// webpack 서버 설정
devServer: {
contentBase: path.join(__dirname, "deploy"), // 이 경로에 있는 파일이 변경될 때 다시 컴파일
port: 8088 // 서버 포트 지정
},
plugins: [
new HtmlWebpackPlugin({
// index.html에 output에서 만들어진 bundle.js를 적용하여, deploy에 새로운 html 파일 생성
template: `./public/index.html`
})
]
};
여기서 빌드를 하게 될 경우 만들어지는 파일들은 다음과 같습니다.
번들 파일이 하나만 만들어지는 것을 확인할 수 있습니다.
[ 1. 번들 파일 분리시키기 ]
그럼 여기서 라우터에 코드 스플릿팅을 적용하여 번들 파일을 분리시켜 보겠습니다.
App.jsx를 다음과 같이 수정해보겠습니다.
import * as React from 'react';
import { Redirect, Route, Switch } from 'react-router-dom';
const HelloWorld = React.lazy(() => import('./helloWorld'));
const HelloReact = React.lazy(() => import('./helloReact'));
const App = () => {
return (
<>
<Switch>
<Route exact path='/' component={() => <Redirect to='/helloWorld' />} />
<Route exact path='/helloWorld' component={HelloWorld} />
<Route exact path='/helloReact' component={HelloReact} />
</Switch>
</>
)
}
export default App;
그리고 다시 빌드를 하게 될 경우 만들어지는 파일들은 다음과 같이 변경됩니다.
기존의 bundle.js파일 말고도 이상한(?) 이름의 번들 파일들이 추가적으로 생성된 것을 확인할 수 있습니다.
사실 이 모습으로도 사용하는 것에는 지장이 없지만 어떤 파일들로 이루어진 번들 파일인지 구분하기 위해 추가적인 설정을 해보겠습니다.
[ 2. 번들 파일에 이름 지정하기 ]
우선 App.jsx를 다음과 같이 수정해줍니다.
import * as React from 'react';
import { Redirect, Route, Switch } from 'react-router-dom';
const HelloWorld = React.lazy(() => import(/*webpackChunkName: "helloWorld"*/ './helloWorld'));
const HelloReact = React.lazy(() => import(/*webpackChunkName: "helloReact"*/ './helloReact'));
const App = () => {
return (
<>
<Switch>
<Route exact path='/' component={() => <Redirect to='/helloWorld' />} />
<Route exact path='/helloWorld' component={HelloWorld} />
<Route exact path='/helloReact' component={HelloReact} />
</Switch>
</>
)
}
export default App;
그리고 webpack 설정 파일에서 output부분만 다음과 같이 수정해줍니다.
// 빌드 설정
output: {
path: path.resolve(__dirname, "deploy"), // 빌드되는 파일들이 만들어지는 위치, __dirname: 현재 디렉토리
filename: "[name].bundle.js", // 번들파일 이름
},
위와 같이 설정을 한 뒤 다시 빌드를 하면 다음과 같이 어떤 목적을 위한 번들 파일인지 명확히 구분 가능해집니다.
[ 3. 번들 파일 로드 전 실행되는 컴포넌트 등록하기 ]
마지막으로 나눠진 번들 파일들이 실행될 때마다 파일이 모두 로드되기 전까지 대신 화면에 보여지는 공통 컴포넌트를 등록해줄 수도 있습니다.
예를 들어 위의 파일들에서 helloReact와 관련된 번들 파일만 실행되고 helloWorld와 관련된 번들 파일은 실행되지 않고 지연되고 있다고 가정할 때 helloReact에서 helloWorld 번들 파일과 관련된 경로로 이동을 하게 될 때 해당 번들 파일이 로드되는 동안 화면에 보여지는 컴포넌트라고 생각하시면 됩니다.
설정해주는 방법은 다음과 같이 App.jsx파일을 수정하여 React.lazy로 만들어 둔 컴포넌트 상위에 Suspense를 등록해주면 됩니다.
import * as React from 'react';
import { Redirect, Route, Switch } from 'react-router-dom';
const HelloWorld = React.lazy(() => import(/*webpackChunkName: "helloWorld"*/ './helloWorld'));
const HelloReact = React.lazy(() => import(/*webpackChunkName: "helloReact"*/ './helloReact'));
const App = () => {
return (
<>
<React.Suspense fallback={<div>Loading...</div>}>
<Switch>
<Route exact path='/' component={() => <Redirect to='/helloWorld' />} />
<Route exact path='/helloWorld' component={HelloWorld} />
<Route exact path='/helloReact' component={HelloReact} />
</Switch>
</React.Suspense>
</>
)
}
export default App;
그리고 화면을 실행시켜 보면 다음과 같이 화면이 넘어갈 때 잠시 동안 fallback으로 등록해둔 Loading...이 보여지고 있는 것을 확인할 수 있습니다.
이렇게 까지만 해주면 코드 스플릿팅은 모두 적용이 된 것이고 정말 별거 없는(?) 설정이지만 이런 사소한 것 하나하나가 서비스의 속도를 개선시키는 요소로 작동될 수 있습니다.
번외
위의 설정을 하고도 코드 스플릿팅이 안 되는 상황이 발생될 수도 있습니다.
저 같은 경우는 타입 스크립트 설정이 이루어진 프로젝트에서 코드 스플릿팅이 적용이 안되었던 사례가 있었는데 다른 설정을 변경하여 해결한 적이 있습니다.
변경했던 설정은 tsconfig파일의 module이었습니다.
module이 commonjs로 되어있을 경우 코드 스플릿팅이 불가했지만 ESNext로 변경했더니 정상적으로 동작되는 것을 확인할 수 있었습니다.
혹시나 적용이 안 되는 분들은 다른 설정들에 의해 잘못된 것일 수도 있으니 적용하실 때 참고 부탁드립니다.
이상으로 코드 스플릿팅으로 최적화하는 방법에 대해 간단하게 알아보는 시간이었습니다.
읽어주셔서 감사합니다.
'SPA > React' 카테고리의 다른 글
[React] 정적 이미지 파일 사용하기 (0) | 2021.08.24 |
---|---|
[React] 지연 로딩(Lazy Loading)으로 최적화하기 (1) | 2021.08.18 |
[React] Router를 타입스크립트와 같이 사용하는 방법 (0) | 2021.08.11 |
[React] 함수형 컴포넌트 + 타입스크립트 환경에서 Redux 사용하기 (0) | 2021.08.09 |
[React] CRA 없이 타입스크립트 개발환경 구축하기 (webpack 구 버전 사용) (0) | 2021.08.09 |
댓글