오늘은 2. 필요한 것만 요청하기
에 대한 이야기를 해보겠습니다.
Chrome DevTools > Network
탭에서, 현재 페이지를 불러오기 위해 네트워크에 요청하고 있는 것들을 볼 수 있어요. 그런데, 현재 페이지에서 필요없는 것까지 요청하고 있다면...?! 🤭
필요없는 것을 요청하는 것은 불필요한 성능 저하를 일으킬 수 있습니다.
그래서 이번에는 필요한 것만 요청하는 방법인 code splitting
과, tree shaking
에 대해서 알아봅시다!
코드분할이란 무엇일까요? 말그대로 커다란 코드 덩어리를 여러개의 작은 코드 조각들로 분리하는 것을 뜻합니다.
그렇다면, 코드분할은 왜 해야할까요?
현재 webpack
을 사용하고 있는 프로젝트를 기준으로 설명해보겠습니다.
우리는 현재 webpack을 사용해서 여러파일을 하나로 병합합니다. .js 파일들 역시 하나의 파일 (bundle.js
) 에 하나로 통합되고 있어요.
그렇기 때문에 A, B, C 페이지의 js 코드들이 하나의 bundle.js 파일에 묶여있고, 이 파일을 A 페이지에서 불러옵니다. 그런데 이 bundle.js 파일에는 A 페이지에서는 필요없는 B, C 페이지의 코드가 포함되어있기 때문에 실제 필요하지 코드도 포함되어있겠죠? 🥲
지금은 프로젝트 규모가 작아서 괜찮지만, 앱이 커지게 되면 번들 파일(bundle.js
)도 커지게 됩니다. 이렇게 되면 필요없는 코드를 불러오는데 자원을 낭비하게 되기 때문에 번들을 나누는 ‘코드 분할' 작업이 필요합니다.
코드 분할 작업을 하게 되면, 사용자가 현재 페이지에서 필요한 코드만 불러오도록 하게 함으로써(lazy-loading
), 앱의 초기화 로딩에 필요한 비용을 줄여주게됩니다. 필요한 코드만을 불러와서 로딩도 빨라지고, 사용성도 높아지겠죠?
리액트에서는 React.lazy
를 사용해서, 동적 import(필요할 때 import)를 사용해서 필요한 컴포넌트만을 동적으로 불러올 수 있습니다.
프로젝트 코드에 적용해볼까요?
//...
const Home = lazy(() => import('./pages/Home/Home'));
const Search = lazy(() => import('./pages/Search/Search'));
// ...
const App = () => {
return (
<Router>
<Suspense fallback={<div>Loading...</div>}>
<NavBar />
<Routes>
<Route path="/" element={<Home />} />
<Route path="/search" element={<Search />} />
</Routes>
<Footer />
</Suspense>
</Router>
);
};
현재 프로젝트에는 두개의 페이지가 있기 때문에, 저는 이 두 페이지를 기준으로 라우터에 React.lazy
를 적용해봤습니다. (’/’ 경로에서는 Home 컴포넌트를, ‘/search’에 접근했을 때는 Search 컴포넌트를 가져오도록)
이렇게 해주니까, 하나의 bundle.js파일이 여러개로 분리되었습니다. home 페이지에 들어갔을 때는 search 페이지와 관련된 js, css 코드는 불러오지 않습니다. (크롬 개발자도구-네트워크창을 참고해서 비교해보세요!)
tree shaking이란 무엇일까요? 나무를 흔들면 잎이 떨어지듯이, 코드를 털어서 쓰지 않아 필요없는 코드를 제거하는 것이라고 생각하면 될 것 같습니다. 🍃
(예를들어, export는 되어있지만, import 되지 않는 코드를 삭제하는 작업이 tree shaking이라고 할 수 있겠네요.)
webpack에서 tree shaking이 가능하게 해줄 수 있습니다.
기본적으로는 webpack 4버전 이후에는 production 모드에서 자동으로 tree shaking이 적용되지만, 옵션값을 추가적으로 넣으려면 따로 명시해주어야해요.
tree shaking의 이점을 살리기 위해 webpack 문서에서 추천하는 webpack 가이드에 있는 네가지 체크리스트를 보면서 옵션을 추가해봅시다.
1. ES2015 모듈 구문을 사용해야 하는 것을 배웠습니다. (예: import
와 export
)
현재 프로젝트에서 require
를 쓰고있는 코드는 없기 때문에 이 단계는 스무스하게 패스합니다!
2. 컴파일러가 ES2015 모듈 구문을 CommonJS 모듈로 변환하지 않도록 해야 합니다. (이것은 인기 있는 Babel preset @babel/preset-env의 기본 동작입니다. 자세한 내용은 documentation를 참고하세요.)
여기서 commonJS 모듈이란, require 구문입니다.
위에서 ES2015(ES6) 모듈 구문인 import, export를 사용했으니 여기서 이 구문이 commonJS로 변경되지 않도록 해주어야합니다.
// package.json
"babel": {
"presets": [
"@babel/preset-env",
"@babel/preset-react",
{
"modules": false // 추가!
}
]
}
3.package.json
파일에 "sideEffects"
속성을 추가하세요.
tree shaking으로 인해 코드가 사라져서 side effect (부수효과) 가 발생할 가능성이 있으면, webpack은 사용하지 않는 코드이더라도 tree shaking을 적용하지 않습니다.
webpack은 이 코드가 side effect를 일으킬 가능성이 있는지 잘 판단하지 못하기 때문에, 함부로 tree shaking을 하지 않습니다. 대신 개발자에게 이 책임을 넘깁니다.
개발자의 판단하에 side effect의 가능성이 없는 코드라면, package.json 파일에 sideEffects: false
속성 설정을 해주면 됩니다. 이 설정을 해주면, webpack에게 ‘side effect가 일어날 일이 절대 없으니까, 모든 파일을 tree shaking 적용 해줘!’ 라고 알리는 것이예요.
이 설정을 추가해주었더니, 아래와 같이 main.css 크기가 유의미하게 줄었습니다.
그런데 페이지를 보니, css가 매우 깨졌네요…😱 main.css
에서 side effect가 발생했다는 의미가 되겠죠!
그럼 이 main.css
는 side effects가 발생할 수 있음을 알려야합니다. 저는 main.css
뿐 아니라 모든 css 파일에 side effect가 일어날 수 있다고 생각하여, 아래와 같이 설정해주었어요. 이렇게 설정하면 css 파일 제외하고 tree-shaking이 발생하기 때문에 css가 깨질 일이 없겠죠!
// package.json
"sideEffects": [
"*.css"
],
4. 최소화와 tree shaking을 포함한 다양한 최적화를 사용하려면 production mode 설정 옵션을 사용하세요.
tree shaking 적용 전, 후를 비교해봅시다.
tree shaking은 webpack production 모드에서 적용되기 때문에, production 모드로 빌드해본 후 비교해볼게요.
bundle.js
를 비교했을 때, tree shaking을 적용하니 약 50% 이상이 줄어들었네요! 😄
webpack의 production 모드에서는 minimize: true
가 기본값으로 설정되어있기 때문에 기본적인 최적화가 됩니다.
하지만 dev 모드에서는 그렇지 않아요. 만약 dev 모드에서도 최적화를 해주고싶다면 minimize: true
를 넣어주어야합니다.
webpack 4버전 이후, production 모드에서 기본적으로 적용되는 최적화 작업은 아래와 같습니다.
- 사용하지 않는 코드 삭제 (tree shaking)
- js 코드를 난독화 및 디버거 구문을 제거 (terser)
2단계, '필요한 것만 요청하기'에 대한 정리를 마쳤습니다.
2단계 최적화까지 마친 페이지는 85점에서 91점이 되었습니다.
3단계 글에서 보아요 👋
참고
최적화 진행중인데, 정리가 잘 되어 있어 도움이 되었습니다. 감사합니다.