Search

리액트 번들 사이즈 최적화

subtitle
default import + bundle analyzer + library 대체
Tags
front-end
react
refactor
Created
2021/02/23
2 more properties

Bundle Size 최적화

default import 사용

default import와 member style import(global import) 했을 때 가져오는 파일 사이즈가 다르다.
(2021.07 업데이트 - 추가 설명: lodash는 CJS 모듈을 사용하고 default exports를 한다. ESM에서 named import하면 다른 export에 대해 tree shaking이 안되기 때문에 default import와 named import의 파일 사이즈가 다르게 된다.)
// member style imports(global import): import { Row, Grid as MyGrid } from 'react-bootstrap'; import { merge } from 'lodash'; // default style imports: import Row from 'react-bootstrap/lib/Row'; import MyGrid from 'react-bootstrap/lib/Grid'; import merge from 'lodash/merge';
TypeScript
복사
sample code from babel-plugin-transform-imports
lodash를 global import 했을 때 | source: dev.to mbernardeau
vscode 확장프로그램의 import cost 를 사용하면 둘의 차이를 알 수 있다.
vscode import cost
install하면 import 코드 오른쪽에 번들 사이즈가 나온다.
import cost로 비교해본 bundle size 크기이다. default import를 하면 훨씬 더 크기가 줄어든다.
import { time } from 'lodash' // 69.4kb (gzipped: 24.5kb) import times from 'lodash/times' // 2.8kb (gzipped: 1.2kb)
TypeScript
복사
이렇게 일일이 default import 해도 되지만 babel plugin을 써서 member style import를 default import로 변환할 수 있다.
둘 다 member import → default import로 바꿔주지만, 컴파일 결과나 path 표현에 차이가 있다.
babel.config.js
// babel-plugin-import [ 'babel-plugin-import', { libraryName: '@material-ui/core', libraryDirectory: '', camel2DashComponentName: false, }, 'core', ], [ 'babel-plugin-import', { libraryName: 'lodash', libraryDirectory: '', camel2DashComponentName: false, }, 'lodash', ], // transport-imports [ 'transform-imports', { lodash: { // eslint-disable-next-line no-template-curly-in-string transform: 'lodash/${member}', preventFullImport: true, }, '@material-ui/?(((\\w*)?/?)*)': { // eslint-disable-next-line no-template-curly-in-string transform: '@material-ui/${1}/${member}', preventFullImport: true, }, }, ],
TypeScript
복사

Code Split

바로 가져올 필요가 없는 코드는 dynamic import로 변경한다. chunk loading을 비동기적으로 실행하여 필요할 때만 불러와 로드할 때의 bundle size를 줄일 수 있다(전체적인 bundle size는 줄어들지 않는다).
아래처럼 code split을 할 수 있다.
next.configs.js
module.exports = withPlugins( [ { webpack: (config, options) => { return Object.assign({}, config, { optimization: { splitChunks: { chunks: 'all', }, }, }) }, ] )
Shell
복사
그러나 스플릿 되는 코드가 많아질 수록 파싱, 다운로드에 시간이 오래 걸린다. UX에 있어 페이지 로드가 느린 것처럼 느껴질 수 있으므로 남용하지 않는 게 좋다. 우리 팀에서는 아래처럼 next/dynamic을 써서 필요한 부분에만 적용하고 있다.
import dynamic from 'next/dynamic' const PostGridComponent = dynamic(() => import('./PostGrid'), { ssr: false }) export { default as usePostGridPageSize } from './usePostGridPageSize' export default PostGridComponent
TypeScript
복사

크기가 큰 library 대체

@next/bundle-analyzer 를 사용해서 번들 사이즈를 분석했다. devDependencies로 설치하고, 실행 시 ANALYZE=true npm run build 를 실행하면 build하면서 번들 사이즈를 분석한다.
기존 번들 사이즈 (번들 사이즈에 대한 설명)
stat: 14.55 mb | 최적화되기 전 코드의 번들 사이즈.
parsed: 3.76mb | minimize된 파일 사이즈다. 브라우저에서 파싱된 자바스크립트 코드의 사이즈.
gzip: 1.13mb | minimize와 gzip을 거친 후의 사이즈. 네트워크에서 로드될 때의 사이즈.
기존 컴포넌트 파일
기존 node_modules 파일
한 녀석이 유독 컸다. bwip-js라고 바코드 생성할 때 쓰는 library인데 parsed size가 664.96kb로 꽤 컸다. 필요한 코드만 따로 빼서 내부에 Util 파일을 만들려고 했는데 생각보다 너무 커서 안될 것 같았다.
그래서 bwip-js를 jsbarcode 라이브러리로 대체했다. bwip-js unpacked size가 7.56MB인데 반해 jsbarcode는 1.02MB였고 weekly downloads 수도 bwip-js의 2배였다.
대체 후 번들 사이즈
parsed: 3.17MB
gzip: 996.25KB
코드도 간단하게.
// 변경 전 bwipjs.toCanvas(canvas, { bcid: 'code128', // Barcode type text: data, // Pin number scale: 3, // 3x scaling factor height: 10, // Bar height, in millimeters textxalign: 'center', // Always good to set this backgroundcolor: 'FFFFFF', padding: 5, }) // 변경 후 JsBarcode(canvas, data, { height: 50, displayValue: false })
TypeScript
복사