Code Split & Lazy Loading
dynamic import
나 react-lazy
를 사용해서 chunk를 여러 개로 나눠 불필요한 코드, 중복 코드 없이 적절한 크기의 코드가 적절한 타이밍에 로드될 수 있도록 하는 것이다.
하나의 큰 JS파일을 다운로드 하는것 보다 여러개의 작은 chunk
(해당 페이지에서 필요한 chunk들만)를 다운로드하는 것이 더 빠르기 때문에 코드 스플릿을 통해 초기 로딩 속도를 개선할 수 있다.
크게 2가지 패턴이 있다.
- 페이지 별로 (라우팅 단위)
- 모듈 별로 (컴포넌트 단위)
페이지별로 코드 스플릿
번들 파일 분석하기
코드스플릿을 하기에 앞서 webpack-bundle-analyzer
를 활용해서 번들된 파일들의 크기를 확인한다.
보면 refractor
라는 모듈이 chunk의 절반을 사용하고 있다.
package.lock
을 통해 해당 모듈이 어디서 사용되는지 확인해본다.
react-syntax-highlighter
라는 모듈에서 사용하고 있음을 알 수 있다.
해당 모듈은 md
의 코드 블럭에서 코드들을 강조처리 하는 역할을 한다.
이 모듈은 상세조회 페이지에서만 사용되기 때문에 목록화면에서는 필요가 없다.
코드 스플릿 하기
페이지 별로 (라우트 기반) 코드 스플릿을 한다.
Suspense, lazy
를 사용한다.
Suspense
는 해당 페이지에 필요한 파일을 다운로드할 동안 fallback
에 제공된 컴포넌트를 보여주고, 다 다운로드하면 원래 보여줘야할 페이지를 보여준다.
lazy
는 런타임 중에 필요한 컴포넌트를 레이지 로딩하게 해준다.
import React, {Suspense, lazy} from 'react'
import { Switch, Route } from 'react-router-dom'
import './App.css'
// import ListPage from './pages/ListPage/index'
// import ViewPage from './pages/ViewPage/index'
const ListPage = lazy(() => import('./pages/ListPage/index'))
const ViewPage = lazy(() => import('./pages/ViewPage/index'))
function App() {
return (
<div className="App">
<Suspense fallback={<div>Loading...</div>}>
<Switch>
<Route path="/" component={ListPage} exact />
<Route path="/view/:id" component={ViewPage} exact />
</Switch>
</Suspense>
</div>
)
}
export default App
결과
- 코드 스플릿 전
- 코드 스플릿 후
퍼포먼스 탭을 통해 검사해보면, 목록 페이지 로딩이 184.40ms -> 108.27ms로 기존보다 훨씬 빨라진 것을 확인할 수 있다.
번들된 파일을 검사해보면, chunk 가 여러개로 나뉘어져 있는 것을 확인할 수 있다.
각 chunk의 이름이 해시값이라 뭔지 알아보기가 힘들다.
chunk파일 이름 정해주기
웹팩 config
에서 chunk파일의 이름을 정해줄 수 있다.
output: {
filename: 'static/js/[name].[contenthash:8].js',
path: path.resolve(__dirname, '../../dist'),
publicPath: './',
chunkFilename: 'static/js/[name].[contenthash:8].chunk.js',
},
chunkFilename
에 규칙을 정해줌으로써 각 청크가 무슨 페이지인지 알 수 있다.
컴포넌트 코드 스플릿
번들 파일 분석하기
이미지 모달에서만 사용되는 이미지 갤러리 라이브러리와 이미지모달이 각각 메인 chunk들에 포함되어 있다.
이미지 모달을 눌렀을 때만 사용되므로 해당 컴포넌트를 레이지 로딩하면 chunk의 크기를 줄일 수 있다.
최적화하기
// import ImageModal from './components/ImageModal'
const ImageModal = lazy(() => import('./components/ImageModal'))
결과
이미지 모달을 클릭했을 때 관련 청크들을 불러온다.
번들에서도 코드가 분리된 것을 확인할 수 있다.
이미지 Lazy loading
사이트 분석
이미지를 먼저 다운로드하고 그 후에 배너 비디오를 다운로드한다.
문제점: 배너비디오가 사용자에게 먼저 보여지는데, 다운로드가 늦어서 사용자가 배너 비디오를 늦게 보게 된다.
해결방안:
1) 이미지를 빠르게 다운로드 - 근본적 해결책은 아님
2) 이미지를 나중에 다운로드 (이미지 Lazy loading)
이미지 로드 시점
이미지를 나중에 다운로드한다면 어느 시점에 다운로드해야 될까?
해당 이미지 요소가 보여질 때
IntersectionObserver
를 활용해서 구현할 수 있다.
또는 react-lazyload
활용 가능하다.
구현
이미지 주소를 dataset-src
에 저장해놨다가 IntersectionObserver
를 활용해 해당 이미지가 화면에 보여질 때 src
로 dataset-src
를 준다. 이러면 화면에 보여지는 시점에 이미지를 불러온다.
이 때 문제점이 생길 수 있다. width, height
을 지정해주지 않으면 src가 없는 img
태그는 0x0이라서 한꺼번에 많은 img
태그가 한 화면에 있고 동시에 매우 많은 이미지를 다운로드 요청해서 부하가 걸릴 수 있다.
따라서 width,height
을 주고 src
가 없을 때는 알록달록한 색깔을 주는 식으로 해서 Layout shift
도 줄이고 좋은 사용자 경험을 줄 수 있다. ex) Pinterest
/*
* Image with Lazy Loading
*/
export default function Image(props) {
const {src, width, height, ...restProps} = props
const imgRef = useRef()
useEffect(() => {
const callback = (entries, observer) => {
entries.forEach(entry => {
if(entry.isIntersecting) {
console.log(entry,'이미지 불러오기!')
entry.target.src = entry.target.dataset.src
observer.unobserve(entry.target)
}
})
}
const observer = new IntersectionObserver(callback, {})
observer.observe(imgRef.current)
return () => {
observer.unobserve(imgRef.current)
}
}, [])
return <img width={width} height={height} data-src={src} {...restProps} ref={imgRef}/>
}
적용 및 결과
img
태그를 Image
컴포넌트로 변경해준다.
적용시 banner-video
로드를 훨씬 빠르게 한다.
'프론트엔드 > 성능 최적화' 카테고리의 다른 글
로딩 성능 최적화 - 캐시 (0) | 2023.04.28 |
---|---|
로딩 성능 최적화 - 폰트 (0) | 2023.04.28 |
로딩 성능 최적화 - 프리 로딩 (0) | 2023.04.28 |
로딩 성능 최적화 - 이미지, 동영상 (0) | 2023.04.25 |
웹 성능 최적화 - 기초, 분석 툴 (0) | 2023.04.25 |