출처: https://crystallize.com/comics/no-code-splitting-vs-code-splitting
안녕하세요, 휴먼스케이프 개발자 oliver입니다.
Code splitting, why? — 코드 스플리팅, 왜 하는것인가?
개발은 나름 좋은 환경(i.e. 좋은 프로세서 및 빠른 인터넷 속도)에서 주로 하게 되서, 인터넷 속도가 느리거나 메모리가 낮은 기기를 사용할 때 생기는 불편한 점들을 잘 인식 못할 수 있습니다. 이런 이유 때문에 자바스크립트 프로젝트를 만들 때, 모든 코드를 하나의 번들로 묶어서 만들게 됩니다. 앱이 비교적 간단한 편이라면 이것이 문제 가 되지 않지만, 당연히 프로젝트가 커지면서 전달해야 하는 파일도 커지고, 유저의 브라우저가 파싱(parsing)해야하는 정보도 많아지기 때문에 퍼포먼스 문제들이 생길 수 밖에 없습니다.
개발자로서의 목표는, 유저가 당장 필요한 정보에 우선순위를 두어 순서대로 로딩하는 것입니다. 앱에서 아직 정보를 로딩중이더라도, 유저한테 티가 안나기만 한다면 결국 어느정도로 좋은 UX(user experience)를 전달할 수 있겠죠.
Webpack
아마 Webpack은 자바스크립트에서 코드 스플리팅을 할때 가장 흔하게 쓰이지 않을까 싶습니다. Webpack으로 코드 스플리팅을 할 수 있는 방법은 여러가지이지만, 그 중 하나인 dynamic import의 간단한 예시를 보겠습니다.
간단하게 설명하자면, 원래 방식대로 import를 하지 않고 원하는 함수 형태로 써서 필요할 때에만 import를 실행한 뒤, 돌아오는 promise에서 모듈을 실행할 수 있도록 만듭니다. 위 예시에서 만약 video라는 컴포넌트가 로딩하는데에 오랜 시간이 걸린다면, 유저가 사용하기 전 까지는 import를 안해주는게 낫겠죠.
이러한 dynamic import를 React에서 활용할 수 있는 방법을 알아보겠습니다.
Code splitting: dynamic import
React.lazy()
리엑트 라이브러리에 컴포넌트 레벨 dynamic import를 위한 함수가 내장되어 있습니다. 쓰는 방법은 간단하죠.
이렇게 import()를 lazy라는 함수 안에 넣어서 쓰고, Suspense라는 컴포넌트로 관리를 해줄 수 있습니다.
Suspense
Dynamic import를 할 때마다 새로운 데이터가 로딩되는 도중에 유저에게는 앱에서 코드가 비어있는 컴포넌트가 보입니다. 이 때문에 로딩이 되는 중에 다른 컴포넌트를 보여주면 좋겠죠. 이것을 React.lazy와 Suspense 로 쉽게 관리를 할 수 있습니다.
Suspense는 fallback이라는 컴포넌트를 받아서 import를 하는 도중에 자동으로 띄어줍니다. 여러 lazy 함수들을 한번에 받을수도 있죠.
이렇게 dynamic loading으로 split하는 경우는 주로 3가지 입니다.
1. Route level: 각 react-router마다 dynamic loading을 설정해놓기.
Splitting을 할 곳을 가장 찾기 쉬운 방법입니다. 각 라우트가 다른 컴포넌트로 관리를 하고있을 경우, 각 라우트를 import함수를 통해 분리된 빌드 파일로 관리를 할 수 있습니다. 유저가 다른 페이지로 넘어가 때에만 그 페이지를 asynchronous하게 로딩할 수 있죠.
2. Component level: 유저의 input으로 인해 나타나는 컴포넌트
페이지가 처음으로 로딩이 되었을 때, 그 페이지 안에 있지만 보이지 않는 컴포넌트가 존재할 수 있습니다. 예를 들어, 유저가 이메일 페이지에서 새로운 메일을 작성하고자 할 때, 작성하기 버튼을 눌러 모달이 뜨게 된다면 그 모달을 import()로 스플리팅 해서 관리를 할 수 있습니다.
3. 하나의 페이지를 스플리팅하기
페이지 하나가 되게 긴 경우, 그 페이지에 들어갈 때 당장 보이는 부분을 나머지와 분리하고, 그 뒷부분을 다른 컴포넌트로 만들어 스플리팅할 수 있습니다.
Code splitting: entry points
Entry point는 webpack이 앱에서 번들링하려는 모듈의 진입 파일입니다. 하나의 React 앱에 여러 entry points를 설정하고 싶은 경우, 각 entry point별로 code splitting이 가능합니다.
위 예시에는 온라인 쇼핑몰 페이지와 쇼핑몰을 관리하는 페이지를 entry point차원에서 관리하는 앱입니다. 이렇게 분리를 하게 되면 webpack에서 자동으로 이 둘을 다른 chunk로 관리를 해주어서 따로 로딩을 하게 됩니다. 그럼에도 문제가 한가지 있는데, webpack이 이 둘의 dependencies도 분리해서 관리를 하기 때문에 만약 같은 dependency가 여러 entry point가 설치되어 있다면, 그만큼 중복되게 로딩이 되는 코드가 많아질수 밖에 없습니다. 중복되는 dependencies들을 다른 chunk로 관리를 해주어야 합니다.
module.exports = { // ... optimization: { splitChunks: { cacheGroups: { // vendor 코드 (dependencies)들을 다른 chunk에 분리하기 vendors: { test: /[\/]node_modules[\/]/i, chunks: 'all' } } }, // 런타임 chunk runtimeChunk: { name: 'runtime' } }, // ... };
Tip: 브라우저에 쓰이지 않는 코드를 보기
Chrome Dev Tools에서 Cmd+shift+P (혹은 ctrl+shift+P)를 눌러 Coverage를 검색하고 reload를 누르면 아래와 같이 로딩 된 스크립트들을 볼 수가 있죠. 현재 페이지에 쓰이지 않아도, 파싱이 된 코드의 비율도 볼수가 있습니다.
감사합니다.
Get to know us better! Join our official channels below.
Telegram(EN) : t.me/Humanscape KakaoTalk(KR) : open.kakao.com/o/gqbUQEM Website : humanscape.io Medium : medium.com/humanscape-ico Facebook : www.facebook.com/humanscape Twitter : twitter.com/Humanscape_io Reddit : https://www.reddit.com/r/Humanscape_official Bitcointalk announcement : https://bit.ly/2rVsP4T Email : support@humanscape.io