성능 최적화 1

정호진·2023년 9월 6일
0

목록 보기
6/7
post-thumbnail

‘성능 최적화’란 무엇일까요? GPT에게 물어본 답은 다음과 같습니다.

웹 개발과 소프트웨어 개발의 과정에서 사용자 경험을 향상시키고, 시스템의 효율성을 높이며, 자원 사용을 최소화하기 위한 다양한 기술과 전략을 적용하는 프로세스입니다 - GPT 3.5

성능 최적화를 하는 이유에 대해 추상적으로 알아보면 ‘사용자 경험을 향상시키기 때문’ 이라는 대답이 나옵니다. 하지만, 단순히 사용자 경험을 향상시킨다 뿐만 아니라 또 다른 이유가 존재합니다.

  • 비용 절감
  • 성능 향상 및 품질 증가
  • 효율성 증가
  • 확장성

서비스 제공자 입장에서는 비용도 줄고, 성능도 오르고, 효율성, 확장성 또한 증가하게 되는데 성능 최적화를 하지 않을 이유를 찾기가 더 어려울 것 같습니다.

이처럼 장점만을 가져다 주는 성능 최적화를 할 수 있는 방법에는 여러가지가 존재합니다. 애초에 요청을 많이 보내지 않아도 되는 방법이 있고, 현재 존재하는 파일의 크기나 리소스의 크기 자체를 압축하는 방법도 있습니다. 앞에서 언급한 방법들 말고도 여러가지 방법이 존재하는데 이번에는 이미지 최적화 및 파일 크기 줄이기에 초점을 맞춰서 글을 작성해 보고자 합니다.

우선적으로 성능 최적화 하는 패키지 스팩은 다음과 같습니다.

  • "webpack": "^5.88.2"

이미지 최적화

클라이언트 폴더에서 정적으로 제공하는 이미지 파일이 존재할 것입니다. 보통 이미지 하면 png 혹은 jpg를 생각하실 겁니다. 하지만 이러한 확장자들 보다 좀 더 좋은 확장자가 있는데 바로 webp입니다. **webp는 기존 확장자들 보다 좀 더 고품질의 이미지를 제공하며, 용량은 더 작다는 단점이 있습니다.** 좀 더 자세한 설명은 여기에 있습니다.

확장자 변경

따라서 기존에 있는 이미지 및 gif 확장자를 모두 webp로 변환시켜주면 됩니다. 그리고 기존 확장자들을 변환시켜서 build하는 webpack plugin이 있는데 바로 ImageMinimizerPlugin 입니다.

npm install image-minimizer-webpack-plugin sharp --save-dev

webpack에는 이미지 최적화를 하기 위한 여러가지 방법이 있지만, sharp를 통한 이미지 최적화가 가장 빠르다는 어느 글을 보고 sharp를 선택했습니다. (물론 이것 말고도 webpack-cli 버전 설정 등의 이유도 있습니다)

webp 설정을 하지 않고 일반적인 png와 gif의 용량을 봤을때 상당히 막막한 용량들입니다. 이제 webp를 통해 용량을 줄여 보겠습니다.

optimization: {
    minimizer: [
      '...',
      new ImageMinimizerPlugin({
        minimizer: {
          implementation: ImageMinimizerPlugin.sharpMinify
        },
        generator: [
          {
            // You can apply generator using `?as=webp`, you can use any name and provide more options
            preset: 'webp',
            implementation: ImageMinimizerPlugin.sharpGenerate,
            options: {
              resize: {
                height: 700
              },
              encodeOptions: {
                webp: {
                  quality: 85
                }
              }
            }
          },
          {
            preset: 'gif-webp',
            implementation: ImageMinimizerPlugin.sharpGenerate,
            options: {
              encodeOptions: {
                webp: {
                  quality: 50
                }
              }
            }
          }
        ]
      })
    ],
  ...
  }

webp에서 설정하는 옵션을 각각 다르게 설정했는데, webpquality에 따라 용량이 달라지기 때문입니다. gif의 quality를 85로 설정했었다가 오히려 용량이 커지는 모습을 목격했기 때문에 확장자에 따라 옵션을 다르게 설정했습니다.

import heroImage from '../../assets/images/hero.png';
import heroWebp from '../../assets/images/hero.png?as=webp';

import trendingWebp from '../../assets/images/trending.gif?as=gif-webp';
import findWebp from '../../assets/images/find.gif?as=gif-webp';
import freeWebp from '../../assets/images/free.gif?as=gif-webp';

선언하고자 하는 확장자 뒤에 webpack에서 설정한 ?as=${preset}형식으로 설정하면 됩니다. 실제 코드를 작성할때는 일반적으로 img 태그에 할당하는 것이 아니라 [picture](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/picture) 태그에 할당하여 사용했습니다. picture 태그는 기기와 상황에 맞게 제공된 source와 img 중에서 가장 적합한 이미지를 선택해서 제공합니다. webp가 상당히 많은 브라우저에서 제공하고 있지만, 제공하지 않는 경우도 있기 때문에 최적의 상태를 보여주기 위한 하나의 장치라고 볼 수 있습니다.

<picture>
  <source srcSet={heroWebp} type="image/webp" />
  <img className={styles.heroImage} src={heroImage} alt="hero" />
</picture>

wepb로 확장자를 변경했을 때 용량 면에서 확연한 차이를 볼 수 있었습니다. 이미지 최적화만 해도 Lighthouse 점수가 상당히 높아집니다.

파일 크기 줄이기

서버에 올라가 있는 정적 파일을 요청을 통해서 받을 수 있습니다. 이때 받는 정적 파일의 크기가 크다면 어떻게 될까요? 첫 페이지 로딩 시간이 매우 오래걸립니다. 실제로 프로젝트를 할 때 react-icons 때문에 초기 로딩이 4초가 넘게 걸린 적이 있습니다. 또한, 배포를 위한 압축을 할 때 번들 크기가 244kib를 넘어간다면 파일 용량이 너무 크다고 경고를 보냅니다.

파일 크기 알아보기

그렇다면 파일 크기를 줄이기에 앞서 파일 크기를 줄이기 전에 어느 파일의 용량이 큰지 어떻게 확인할 수 있을까요? 바로 BundleAnalyzerPlugin을 통해서 번들의 크기를 측정할 수 있습니다.

자세한 설치 방법은 다음 글에 나와있습니다.

파일 크기를 줄이는 방법

번들에 올라가는 대표적인 파일은 js파일과 css파일이 있습니다. 그리고 파일 크기를 줄이는 방법에는 파일 하나를 여러개로 분리하는 방법과, 분리된 파일들의 불필요하게 긴 주석 혹은 변수명들을 변환시켜주는 작업을 합니다. 이를 압축, 난독화 라고 합니다.

  • JS최적화 webpack v4 이후부터는 배포 환경 --mode=produciton이라면 자동으로 압축과 난독화를 진행해 줍니다.
  • CSS 최적화 css 확장자 별로 파일을 분리해 주는MiniCssExtractPlugin과 css 파일의 크기를 압축해주는 CssMinimizerPlugin을 통해서 css 파일 최적화를 할 수 있습니다.

최적화에 사용되는 플러그인은 webpack에 있는 optimizer 옵션을 설정해주면 됩니다. 만약에 새로운 플러그인을 추가한다면 다음과 ...을 꼭 추가해 줘야 합니다. 그렇지 않는다면 새롭게 추가하는 플러그인만 옵션에 추가되버리는 불상사가 발생할 수 있습니다.

코드 스플리팅

코드 스플리팅은 하나의 번들 파일을 각각의 관심사(?)에 맞게 분리하는 것을 의미합니다. 코드 스플리팅을 통해 불필요한 파일 호출을 방지할 수 있습니다. webpack에서 설정하는 방법은 다음과 같습니다.

output: {
  filename: '[name].[contenthash].bundle.js',
  chunkFilename: '[name].[chunkhash].chunk.bundle.js',
  path: path.join(__dirname, '/dist'),
  clean: true
},

optimization: {
  splitChunks: {
		chunks:'all'
	}
}

단순히 all 을 설정하는 것만으로 코드 스플리팅이 되지만, 호출하는 패키지의 이름을 명확하게 알기 위해서는 추가적인 옵션이 필요합니다. 가운데에 [contenthash]를 붙이는 이유는 파일이 빌드될 때마다 새로운 중간 이름이 생겨서 이전 버전과 이름을 다르게 하기 위함입니다.

splitChunks: {
  cacheGroups: {
    react: {
      test: /[\\/]node_modules[\\/](react|react-dom)[\\/]/,
      name: 'react',
      chunks: 'all'
    },
    giphy: {
      test: /[\\/]node_modules[\\/](@giphy)[\\/]/,
      name: 'giphy',
      chunks: 'all'
    },
    reactIcons: {
      test: /[\\/]node_modules[\\/](react-icons)[\\/]/,
      name: 'react-icons',
      chunks: 'all'
    }
  }
}

위와 같이 작성하게 된다면 외부 패키지가 분리된 파일들에 이름을 적용할 수 있습니다.

React 는 하나의 SPA이기 때문에 하나의 큰 파일로 빌드가 되는데, lazy라는 메서드를 통해 컴포넌트별 코드를 분리할 수 있습니다. 리액트에서 코드를 분할하는 기준은 페이지를 기준으로 분할합니다. 따라서 Router에서 페이지 컴포넌트를 호출할 때 적용할 수 있습니다.

const Search = lazy(() => import(/* webpackChunkName: "Search" */ './pages/Search/Search'));

const App = () => {
  return (
    <Suspense fallback={<Loading />}>
      <Router>
        <NavBar />
        <Routes>
          <Route path="/" element={<Home />} />
          <Route path="/search" element={<Search />} />
        </Routes>
        <Footer />
      </Router>
    </Suspense>
  );
};

위와 같이 스플리팅을 적용할 컴포넌트 호출을 lazy를 통해 하게 된다면 아래와 같이 파일이 사용되는 경우에만 호출하게 됩니다.

webpackChunkName을 설정한 이유는 위에서 설정한 Search의 이름을 명시하기 위함입니다. 만약 이게 되지 않는다면 아무 숫자나 나타나게 됩니다.

단, 타입 스크립트를 사용하고 있다면 동적으로 설정한 파일 이름을 설정할 때 주석에 대해 주의를 해야 합니다. 왜냐하면 타입 스크립트가 컴파일 되는 과정에서 위 주석이 제거되기 때문에 tsconfig에서 주석 제거를 삭제해야 합니다. (어차피 웹팩 압축 과정에서 주석이 제거됩니다.)

"compilerOptions":{
  "removeComments": false,
}

Tree Shaking

트리쉐이킹이란 사용하지 않는 코드를 제거해서 번들 크기를 줄이는 방법입니다. 웹팩에서는 트리쉐이킹을 자동으로 지원해주기 때문에 모종의 이유로 사용을 원치 않는다면 다음과 같이 설정하면 됩니다.

// package.json

"sideEffects": true

성능 최적화의 효과와 방법

0개의 댓글