본문 바로가기

개발 이야기/front-end

리액트 번들 사이즈 최적화

Bundle Size 최적화

참고: https://dev.to/mbernardeau/6-tips-to-optimize-bundle-size-50n9

 

default import 사용

default import와 member style import(global 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';

 

 

lodash를 global import 하면 아래처럼 필요없는 다른 모듈도 함께 가져온다.

 

lodash를 global import 했을 때 source:  dev.to   mbernardeau

 

 

 

 

vscode 확장프로그램의 import cost 를 사용해서 둘의 차이를 비교해보자.

vscode import cost

 

 

import cost를 적용하면 아래처럼 import 코드 오른쪽에 번들 사이즈가 나온다.

 

 

lodash import를 비교해보자. default import를 하면 훨씬 더 크기가 줄어든다.

import { time } from 'lodash' // 69.4kb (gzipped: 24.5kb)
import times from 'lodash/times' // 2.8kb (gzipped: 1.2kb)

 

이렇게 일일이 default import 해도 되지만 babel plugin을 써서 member style import를 default import로 변환할 수 있다.

 

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,
    },
  },
],

 

 

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',
          },
        },
      })
    },
  ]
)

 

 

그러나 스플릿 되는 코드가 많아질 수록 파싱, 다운로드에 시간이 오래 걸린다. UX에 있어 페이지 로드가 느린 것처럼 느껴질 수 있으므로 남용하지 않는 게 좋다. 우리 팀에서는 아래처럼 next/dynamic을 써서 필요한 부분에만 적용하고 있다.

 

import dynamic from 'next/dynamic'

const PostGridComponent = dynamic(() => import('./PostGrid'), { ssr: false })
export { default as usePostGridPageSize } from './usePostGridPageSize'

export default PostGridComponent

 

 

크기가 큰 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 })