-
Notifications
You must be signed in to change notification settings - Fork 166
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[성능 베이스캠프 미션] 호프(김문희) 미션 제출합니다. #33
Conversation
- css 코드 압축을 위해서 css-minimizer-webpack-plugin 설치 및 웹팩 적용 - css 코드 온디맨드 로딩을 위해서 mini-css-extract-plugin 설치 및 웹팩 적용 (style-loader 대신 적용)
- terser-webpack-plugin 설치 및 적용 - 기존의 uglifyJs 가 webpack v5.0.0 을 지원해주지 않아서 terser 설치 - parallel 옵션을 사용하여 빌드 속도 개선
- 코드 스플릿팅을 적용하여, Home Page 에서 불러오는 스크립트 리소스에 gif 검색을 위한 giphy 모듈이 포함되어 있지 않도록 구현 - Suspense 를 사용하여 해당 페이지 레이지로딩 하는 동안 로딩중이라는 안내메시지 보여주도록 구현
- 이미지 확장자 webp 로 변경하도록 구현 - 이미지 압축 플러그인 설정
- 기존의 top, left를 바꾸어주던 것을 transform 속성으로 대체
- hover 시 top 을 조정하던 것을 transform 으로 대체
- right 속성을 변경하던 것을 transform-translateX 로 대체
- GifItem 컴포넌트 React.memo 로 메모이제이션하여 리렌더링 방지 - HelpPanel 컴포넌트 React.memo로 메모이제이션하여 리렌더링 방지
- trendingAPI 가 SearchPage 에 들어올 때 마다 새로 요청되지 않도록 구현 - 캐시 클래스 구현
- 기존 데이터 덮어쓰도록 수정
- APICache 클래스 제네릭 타입 수정
- 웹팩 설정 수정 (webp로 바꿔주는 rule 제거) - webp, png 이미지 각각 추가
- 검색결과 캐싱 삭제
- devtools sourcemap 생성 false 로 수정 - 이미지 압축 png 파일도 압축하도록 플러그인 설치 및 설정 추가 (pngquant)
- 웹팩에 내장되어있기 때문에, 제거
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
호프! 안녕하세요 🙂
미션 진행하신 거 잘봤습니다. 전반적으로 최적화를 잘 하셨다고 생각했어요! 많이 배웠습니다. 👍
보다가 궁금한 것들 코멘트로 남겨봤습니다!
(빨리 제출하셨는데 늦게 코멘트 달아서 죄송..합니다. 😓)
리소스들의 max-age
를 1년으로 주셨다고 했는데, max-age=604800
이어서 의도하신 값이랑 다른 것 같습니다! 단위가 seconds로 알고있어서 1년이라면 max-age=31536000
이 맞는거 같슴다.
추가로 다들 1년으로 하길래, 1년으로 설정하는 이유가 있는지 궁금해요.
gif 는 mp4 로 굳이 변환하지 않았는데요, 그 이유는...저희 페이지의 유저가 홈페이지에 보여지는 gif 파일을 저장할 수 있는데 그러면 mp4확장자로 저장되어서 좋지 않은 유저경험을 발생시킨다고 생각했기 때문입니다.
너무 좋은 접근이라고 생각합니다. 👍👍 근데 저는 webm으로 변환했는데 다운로드 해보니깐 gif로 되더라고요. 어찌된 영문인지 몰라서 좀 더 찾아볼 예정입니다.
hero 이미지의 경우 처음에는 웹팩 설정을 통해서 png 를 webp 로 변환해줘서 빌드하게 구현했는데요, 그렇게 되면 webp 를 지원하지 않는 브라우저에 이미지를 보여줄 방법이 없더라구요. 그래서 webp 로 변환된 파일 하나와, png 파일 하나를 각각 가지고, picture 태그를 통해서 이미지를 제공해주었습니다. 그런데 하다보니 든 의문이, 이런식으로 한다면 제공해줘야하는 모든 이미지에 대해서 webp 파일과 png/jpg/jpge 파일을 가지고 있어야 하는가?
개인적인 생각으로 모든 이미지를 webp로 변환할 필요는 없다고 생각해요. 이미지 압축과 리사이징으로도 파일 크기가 상당히 줄어드는 것을 확인했고, 이미지를 많이 사용하는 서비스라면 호프가 gif 를 mp4 로 굳이 변환하지 않은 이유도 고려해야 할 사항이라고 생각합니다. 핀터레스트 메인 페이지 같은 경우에는 jpg를 사용하면서 이미지 한 개당 30~100KB를 유지하네요.. 🤔
webpack.config.js
Outdated
new ImageMinimizerPlugin({ | ||
minimizer: { | ||
implementation: ImageMinimizerPlugin.imageminGenerate, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
minimizer의 implementation로 ImageMinimizerPlugin.imageminGenerate
를 사용할 수 있네요?
minimizer와 generator에서 할 때 어떤 차이가 있어요??
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
와 밧드! 먼저, 짚어주셔서 감사해요.
전 그냥 웹팩에 있는대로 따라 했는데..
imageminGenerate 는 png 도 webp 확장자로 바꾸고 압축하는 역할을 하고있었네요.
그래서
implementation: ImageMinimizerPlugin.imageminMinify
로 바꿔서 압축만 해주게 해주어 원래의 의도대로 동작하도록 했어요.
만약에 generator 를 사용하고 싶다면...
밧드가 물어보신 것에 대해 답변을 하자면
minimizer 와 generator의 역할을 제가 제대로 인지 못하고 있었네요.
minimizer 에서는 압축관련 implementation 만 해주는게 올바른 것 같습니다.
위와 같이 imageminGenerate 를 해도 어찌저찌 webp 로 generate 는 하고 압축까지 해주지만, 예상치 못한 사이드이펙트가 일어날 수 있을 것 같아요.
그리고 generator 관련 implementation은 generator 에서 해주는게 맞는 것 같구요!
감사합니다!
minimize: false | ||
minimize: true, | ||
minimizer: [ | ||
'...', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
오.. 👍
@@ -14,8 +14,7 @@ const CustomCursor = ({ text = '' }: CustomCursorProps) => { | |||
|
|||
useEffect(() => { | |||
if (cursorRef.current) { | |||
cursorRef.current.style.top = `${mousePosition.pageY}px`; | |||
cursorRef.current.style.left = `${mousePosition.pageX}px`; | |||
cursorRef.current.style.transform = `translate3d(${mousePosition.pageX}px, ${mousePosition.pageY}px, 0)`; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍 translate3d
를 사용하신 이유가 있는지 궁금합니다!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
제가 알아본 바로는 translate3d 속성이 다른 tranlsate 속성들보다 성능에 유리한데요
그 이유가 tranlsate3d 는 GPU 를 사용하기 때문이라구 하네요.
GPU를 사용하는 속성을 사용하면 브라우저에서 렌더링 할 때 별도의 렌더 단계가 생기구, 그 후 합성 단계에서 이 렌더 단계를 합성해서 출력하는데 이 때 GPU를 사용한다고 해요!
그래서 별도의 리플로우/리페인트 과정을 거치지 않아서 훨씬 애니메이션이 부드럽기도 하고, 성능상 유리하다고 합니다!
const cacheStorage = new Map<string, unknown>(); | ||
|
||
const cache = async <T>(key: string, apiRequestCallback: () => Promise<T>): Promise<T> => { | ||
if (cacheStorage.has(key)) return cacheStorage.get(key) as T; | ||
|
||
const response = await apiRequestCallback(); | ||
cacheStorage.set(key, response); | ||
return response; | ||
}; | ||
|
||
export default cache; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
깔~끔하네요👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
감사합니다 🙃
src/App.tsx
Outdated
<Suspense fallback={<h1>로딩 중 입니다.</h1>}> | ||
<Router> | ||
<NavBar /> | ||
<Routes> | ||
<Route path="/" element={<Home />} /> | ||
<Route path="/search" element={<Search />} /> | ||
</Routes> | ||
<Footer /> | ||
</Router> | ||
</Suspense> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
사람 By 사람이라고 생각하는데 NavBar, Footer는 라우팅에 상관없이 계속 보여줘도 상관없지 않을까 생각해요🙂
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
저도 동의합니다 🙃 짚어주셔서 감사해요! 수정했습니다!
<script> | ||
if (typeof window.__REACT_DEVTOOLS_GLOBAL_HOOK__ === 'object') { | ||
__REACT_DEVTOOLS_GLOBAL_HOOK__.inject = function () { }; | ||
} | ||
</script> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
오.. 찾아보니깐 리액트 devtool 비활성화하는 방법이네여 👍👍 배워갑니다~
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
그런데 무조건 prod mode 일 때, 비활성화는 아니고, React Dev Tools 가 설치되어 있지 않은 경우에만 비활성화하는 방법이긴 합니다. 😢 그래도 Index.html 에서 환경변수에 접근 할 수 없으니 이게 최선이라구 생각은 했어요.
webpack.config.js
Outdated
options: { | ||
plugins: [ | ||
['gifsicle', { interlaced: true, optimizationLevel: 3, colors: 64 }], | ||
['webp', { quality: 50, resize: { width: 1280, height: 0 } }], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
webp 이미지는 파일 사이즈가 작을 거 같은데 여기서 quality를 50으로 설정할 이유가 있을지 궁금합니다.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
return await cache<GifImageModel[]>('trendings', async () => { | ||
const gifs = await fetch(TRENDING_GIF_API).then((res) => res.json()); | ||
return convertResponseToModel(gifs.data); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
@@ -57,4 +57,4 @@ const HelpPanel = () => { | |||
); | |||
}; | |||
|
|||
export default HelpPanel; | |||
export default memo(HelpPanel); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
HelpPanel에 memo를 사용하면 어떤 영향을 주나요?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
다시 보니, HelpPanel 에는 props 가 없어서 불필요하게 리렌더링 될 일이 없었네요! 허허
오히려 memo 연산이 한번 더 들어가서 성능상 더 좋지 못하게 될 것 같구요 ㅎㅎ
짚어주셔서 감사해요!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
안녕하세요 호프! 리뷰 남겼습니다! 확인 부탁드려요~
- implementation에서 imageminGenerate -> imageminMinify 로 변경하여, 이미지 변환이 아니라 압축 작업을 해주도록 수정
- Supsense Loader 가 NavBar 와 Footer 와 함께 렌더링 되도록 수정
- HelpPanel 에는 props 가 존재하지 않으므로 memo 성능상 이점이 없어서, 제거
- 50에서 80으로 수정
@kamwoo 밧드~ 좋은 리뷰 감사합니다 🙂 밧드 덕분에 놓쳤던 부분도 많이 알았고, 몰랐던 것들도 많이 학습했어요.
허헛. 제가 실수했네요. 감사합니다. 이 부분 수정해서 적용했슴다 머쓱 🙄
와우 좋은 이야기 감사합니다! 저의 고민/구현에 많이 공감해주셔서 감사해요 ㅎㅎ 혹시 더 수정할 것 있으면 언제든 편안하게 알려주세요 🙃 오늘 밧드한테 이것저것 많이 배워가네요! 감사합니다 하하 |
webm은 gif로 저장되는게 아니라 적용이 안되고 있었습니다..ㅋㅋ 😅 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
안녕하세요! 호프 🙂
답변 보면서 공부 많이 했습니다! 감사합니다 👍
이만 approve 하겠습니다!
🔥 결과
✅ 개선 작업 목록
1 요청 크기 줄이기
before
after
2 필요한 것만 요청하기
3 같은 건 매번 새로 요청하지 않기
4 최소한의 변경만 일으키기
before
after
공유
새롭게 배운 내용
1. picture tag
2. S3 & cloundfront
개인적으로..왜 정적파일을 s3 에 올려두고 s3에 직접 접근하면 안되는걸까..? 에 대한 의문이 있었습니다.
더 공부해야 할 부분
https://docs.aws.amazon.com/ko_kr/AmazonCloudFront/latest/DeveloperGuide/Expiration.html#ExpirationDownloadDist