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로 변환할 수 있다.
•
•
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
복사