FrontEnd

[React🍇]Webpack build시 최적화하기

머털박사 2022. 10. 10. 13:30

해당 포스팅은 React Webpack 환경에서 최적화하는 방법에 대해 다룹니다. Tree Shaking, 난독화, production 빌드에 대한 이야기가 나옵니다.

트리 쉐이킹이란?

웹 최적화를 위해 사용하지 않는 코드(Dead code)를 제거하는 방법입니다.

webpack과 같은 모듈 번들러에서 해당 기능을 제공합니다. 이 과정을 통해 파일을 최소 크기로 번들링하여 웹 어플리케이션 최적화할 수 있습다. 영어의 뜻을 그대로 해석하자면 나무 흔들기. 죽은 나뭇잎을 떨어뜨리기 위해 나무를 흔드는 것처럼 자바스크립트에서 죽은 코드를 제거하는 과정입니다. 웹팩 v4 이상에서는 production 모드만 선택하면 알아서 트리쉐이킹해줍니다.

애초에 필요한 파일만 가져오기

애초에 죽은 코드가 없다면 죽은 코드 제거할 일도 없습니다. 예를 들어 react-icon 라이브러리 에서 아이콘을 불러온다고 가정해봅시다.

import { AiFillAccountBook } from "react-icons/ai";

const Test = () => {
  return (
    <div>
      <AiFillAccountBook />
    </div>
  );
};

export default Test;

공식문서에 적힌 그대로 문법으로 가져왔습니다.

이걸 webpack-bundle-analyzer를 통해 모듈의 크기를 확인하면

600kb의 크기입니다. 아이콘 하나를 가져온 것 치고 크기가 큽니다. 아마 필요없는 파일까지 포함된 걸로 예상됩니다.

import { AiFillAccountBook} from '@react-icons/all-files/ai/AiFillAccountBook';

const Test = () => {
  return (
    <div>
      <AiFillAccountBook />
    </div>
  );
};

export default Test;

위는 사용하는 일부 파일만 가져오는 방법입니다. 공식문서가 아닌 react-icon라이브러리 이슈에서 발견했습니다. 다들 번들링된 파일보고 크기보고 똑같은 고민을 한 것 같습니다. 해당 문서에 제시된 방법에 따라 @react-icons 를 다시 설치해서 특정 파일만 불러오도록 했습니다.

그리고 다시 확인해보면 8kb까지 줄어있습니다.

“sideEffects” 옵션 활용하기

위에서 말했듯이 webpack은 자동으로 tree shaking해줍니다. 이때 다 해주는 건 아니고 사이드 이펙트가 없는 파일만 처리해줍니다. 사이드 이펙트가 있는지 없는지 긴가민가한 파일은 tree shaking 안 해준다는 뜻입니다.

이때 사이드 이펙트가 없다고 webpack에게 명시적으로 알려주면 webpack이 마음놓고 알아서 tree shaking 처리해줍니다.

//webpack.config.js

"sideEffects": false

위와 같이 webpack.config.js에 sideeffect 설정에 false을 설정하면 해당 프로젝트의 모든 파일은 사이드 이펙트가 없다는 걸 나타냅니다.

//webpack.config.js

"sideEffects": ["*.css"]

만약 오류가 난 경우 배열에 예외 파일을 추가하면 됩니다. 참고로 해당 옵션을 넣어도 번들링된 파일 크기가 줄어들지 않을 수도 있습니다. 모든 파일이 자동으로 tree shaking 된 경우입니다.

"production" 모드 사용하기

//package.json
"scripts": {
    "build": "webpack --mode=production --node-env=production"
}

//webpack.config.js
module.exports = {
  entry: './src/index.tsx',
  mode: 'production',
    ...
}

일단 기본적으로 npm run build 시 webpack 옵션은 production이라 따로 설정할 필요없다. 그러나 webpack에서는 명시적으로 모드 선언할 것을 추천합니다. --mode=”production”이나 =-mode=”development” 이런식으로.

production 모드와 devlopment 모드의 차이점은?

그럼 prod모드는 dev모드랑 뭐가 다르길래 이 옵션을 사용하라는 걸까요?

모드에 따른 결과물차이

일단 결과물의 크기가 다릅니다. dev모드는 1.59mb, prd모드는 471.48kb입니다. 이러한 차이는 어디서 오는 걸까요? 결론부터 말하자면, production 모드 실행시 자동으로 실행되는 플러그인 덕분입니다.

webpack 문서를 참고해본 결과 prod 모드는 위와같은 플러그인을 사용합니다.프러그인 각각의 기능을 설명하기 전에 단어 뜻부터 정리하겠습니다.

청크 번들 프로세스를 관리하기 위해 내부적으로 사용된다. 번들은 여러가지 유형이 있는 청크로 구성된다. 일반적으로 청크는 출력 번들과 직접 일치해야하지만 일대일 관계를 형성하지 않는 구성도 있다.
번들 여러 개별 모듈에서 생성된 번들은 이미 로드 및 컴파일 프로세스를 거친 소스 파일의 최종 버전을 포함합니다.
망글 이름 변수나 함수 이름을 다른 이름으로 바꾸는 것, c언어에서 쓰는 개념으로 맹글링이라합니다. 411.bundle.js처럼 랜덤화된 이름을 의미합니다.

청크가 번들보다 큰 개념이고 번들을 위해서 청크가 존재합니다. import 형식으로 사용한 파일은 분명 그대로 js파일로 사용할 수 없습니다. 변환 단계를 거치는데 이때 변환되고 나온 산물을 번들이라고 부릅니다. 그리고 번들 내부에 청크가 있습니다.

producition 모드에서 사용되는 플러그인은?

TerserPlugin JS 최소화
FlagIncludedChunksPlugin 청크에 포함된 청크의 청크 ID를 추가합니다. 불필요한 청크 로드가 제거됩니다.
ModuleConcatenationPlugin 여러 모듈을 하나로 합쳐준다.
NoEmitOnErrorsPlugin 컴파일 중 오류가 발생했을 때 build 폴더에 asset을 내보내지 않는다.

production 모드에서 별도의 설치 없이 해당 플러그인이 실행되며 빌드 파일을 최적화해줍니다.

 

CSS 최적화하기

MiniCssExtractPlugin 사용하기

MiniCssExtractPlugin 적용 전후

아무런 설정하지 않고 build하면 css 파일이 존재하지 않는 걸 확인할 수 있습니다. 그럼 css 파일은 어디로 갔을가요? js 파일 내부에 인라인으로 적용되어있습니다.

이 방법은 개발 중에는 편리하지만 배포 할 때는 적절하지 않습니다. CSS 캐싱을 허용하지 않기 때문에 FOUC(Flash of Unstyled Content) 문제가 생기기 때문입니다. 간단히 말하면 사용자가 웹사이트를 접속했는데 로딩때문에 css는 적용되지 않아 html 뼈대만 보는 경우입니다.

이 문제를 해결하려면 CSS 파일을 따로 분리해주면 됩니다. 그리고 "MiniCssExtractPlugin" 이 해당 기능을 수행합니다.

npm install --save-dev mini-css-extract-plugin

MiniCssExtractPlugin 설정하기 위해서 일단 해당 플러그인을 다운로드 받습니다.

  plugins: [
    new MiniCssExtractPlugin({
      filename: '[name].css',
      chunkFilename: '[id].css'
    })
  ],
  module: {
      {
        test: /\.css$/i,
        use: [MiniCssExtractPlugin.loader, 'css-loader']
      },
 }

webpack.config.js에서 설정을 수정합니다. plugin에 추가하고 module css 파일은 MiniCssExtactPlugin을 사용한다고 명시합니다. 이후에 build하면 해당 옵션이 적용된 걸 확인할 수 있습니다.

CssMinimizerPlugin 사용하기

CssMinimizerPlugin 적용 전 후

CssMinimizerPlugin는 Css를 최적화하고 축소합니다. 위는 해당 플러그인을 사용하기 전과 후의 같은 css 파일 사진입니다. 여러줄의 css 코드가 한 줄로 압축되었습니다.

npm install css-minimizer-webpack-plugin --save-dev

해당 플러그인을 사용하기 위해서 일단 설치해야합니다.

module.export= {    
	// ...
    optimization: {
    	minimizer: [`...`, new CssMinimizerPlugin()]
    }
}

설치 후 webpack.cofing.js 파일에 minimizer 값을 추가해줍니다 이때 배열 첫 번째 값은 `...`문법을 사용해야 합니다. '...'는 기존에 사용중인 'terser-webpack-plugin'이라는 js를 축소하는 플러그인을 확장하는 문법입니다. 이것까지 마치고 빌드하면 css에 해당 플러그인이 적용된 모습을 확인할 수 있습니다.

source-map 사용하지 않기

devtool:false 사용 전후

devtool 옵션에 'source-map' 설정이 되어있는 경우 제거해야합니다. source-map은 .js.map 행식의 파일입니다. 난독화와 압축된 코드는 디버깅하기 어렵습니다. 디버깅을 위해 해당 코드를 다시 원래대로 되돌릴 때 사용되는 것이 source map입니다. source map의 장점은 다음과 같습니다.

  •  프로덕션 디버깅하기 쉬워집니다.

그러나 단점 두 가지가 존재합니다.

  • 소스맵에 컴파일 시간이 필요합니다.
  • 보안성이 떨어집니다. 다른 사람이 코드를 확인할 수 있게 합니다.

이러한 이유로 source map은 정말 필요한 경우가 아니면 빌드 파일에 제거할 것을 권장합니다.

 

(프로젝트 크기가 작은 경우) 추가 최적화 단계 방지

추가 최적화 옵션 피하기 적용 전후

해당 옵션은 프로젝트 크기가 작은 경우 유용합니다. 불필요한 최적화 단계를 방지해서 추가적인 알고리즘 작업을 하지 않도록 합니다.

module.exports = {
  // ...
  optimization: {
    removeAvailableModules: false,
    removeEmptyChunks: false,
    splitChunks: false,
  },
};

추가 최적화 단계를 방지하는 옵션은 위와 같습니다.

마치며

이상으로 React 프로젝트를 빌드할 씨 최적화를 위해 신경 써야 할 점에 대해 알아보았습니다. 'production' 모드로 빌드하는 것 만으로 대부분 최적화가 되어 크게 신경 쓸 필요는 없습니다. 한 번 정리해두면 이후에 그대로 해당 설정을 따라 하면 될 것 같아 시간내서 정리했습니다.


참조

https://webpack.js.org/guides/build-performance/#avoid-extra-optimization-steps

https://survivejs.com/webpack/styling/separating-css/

https://webpack.kr/plugins/mini-css-extract-plugin/#minimizing-for-production

https://medium.com/naver-fe-platform/webpack%EC%97%90%EC%84%9C-rollup%EC%A0%84%ED%99%98%EA%B8%B0-137dc45cbc38

https://also.github.io/webpack/internal-plugins/#FlagIncludedChunksPlugin

https://webpack.js.org/glossary/#c

https://www.daleseo.com/js-npm-run-script/

https://yceffort.kr/2021/08/javascript-tree-shaking

https://webpack.kr/guides/tree-shaking/