본문 바로가기
기타

웹 페이지 최적화하기 - 1

by 유세지 2022. 9. 4.

프로그래머들의 바이블이라고 불리는 개발 서적들에서 공통적으로 볼 수 있는 재밌는 점이 하나 있습니다. 바로 성능에 대한 견해인데요. 많은 사람들이 성능은 굉장히 중요한 요소라고 생각하고, 실제로도 아니라고 할 순 없지만 성능 개선이라고 하면 이렇게 말하는 것을 들은 적 있으실 것 같습니다.

 

최적화를 할 때 두 가지 규칙을 따르라.
첫 번째, 하지 마라.
두 번째, (전문가 한정) 아직 하지 마라.

 

물론 이 말은 최적화가 나쁘다는게 아니라 쓸데없는 코드 레벨의 최적화를 지양하라는 의미입니다. '성능이 좋은' 코드를 짜려고 가독성과 재사용성과 같은 다른 중요한 요소들을 포기하지 말고, '좋은' 코드를 짜는데 집중하라는 뜻입니다.

 

그러나 하지 말라면 더 하고 싶은법입니다. 이제부터 이 규율을 어기고, 함께 최적화를 해봅시다.

 

최적화를 왜 해야할까?

사실 웹 사이트에서 말하는 최적화는 위의 프로그래밍 격언과는 성격이 다릅니다. 네트워크 연결을 통해 이미지나 동영상과 같은 무거운 자원들을 내려받고, 보여주며 사용자들과 상호작용하는 지금의 웹에선 최적화는 반드시 고려해야 할 요소 중 하나입니다.

 

느리고, 버벅이고 끊기는 동작들은 사용성을 저해하는 요소입니다. 이러한 요소들은 우리의 서비스에도 큰 영향을 미치는데, 실제로 페이지가 로딩되는 시간이 늘어날수록 사용자들의 이탈율은 급격하게 늘어납니다.

 

결국 위의 필자가 말하고자 했던 건 성능 개선은 '필요할 때' 해야 한다는 것이었는데, 이 정도면 성능 개선이 필요한 이유로는 충분해보입니다.

 

 

바로 시작해봅시다.

 

 

자바스크립트 파일 크기 줄이기

일반적으로 우리가 CRA과 같은 환경에서 개발하고, 빌드한 자바스크립트 파일은 이미 어느정도 최적화가 진행되어 있습니다. 바로 웹팩이라는 모듈 번들러 덕분입니다. 웹팩이 무엇인지, 모듈 번들러는 또 무엇인지에 대해서는 따로 포스팅으로 정리하였으니 참고해주시면 감사하겠습니다. 오늘은 최적화 방법에 대해서 집중적으로 알아보겠습니다.

 

webpack

 

필요없는 부분 없애기 : 공백, 변수 이름

자바스크립트에 있는 공백과 변수, 함수 이름들은 개발하는 사람에게는 꼭 필요하지만 컴퓨터에게는 필요하지 않습니다. 이러한 불필요한 공백을 지우고, 변수와 함수의 이름들을 a, b, c... 등으로 변경하는 것을 난독화(uglify) 라고 합니다. 이러한 난독화를 진행하게 되면 기존 코드의 용량 대비 많게는 절반 이상까지 감축시키는 효과를 볼 수 있습니다.

 

 

아래는 난독화가 적용된 코드입니다.

언뜻 깔끔하게 보이지만...

 

개발자 도구의 pretty print 기능을 끄면 도저히 읽기 어려울 정도입니다.

 

 

원래는 terser-webpack-plugin 이라는 플러그인을 설치해서 적용해주어야 했지만, 웹팩 5버젼부터는 내장 플러그인이 되어 webpack.config 파일에서 optimization 속성을 설정해주는 것으로 적용할 수 있습니다.

 

webpack.config.js

optimization: {
  minimize: true,
}

 

사실 minimize 속성은 기본값이 true이기 때문에, 따로 건드리지 않았다면 이미 적용되셨을겁니다.

 

 

필요없는 부분 없애기 : 사용하지 않는 모듈

웹팩은 프로젝트 내에서 사용되지 않는 코드들을 제거하기 위해 코드를 탐색하고, 임의로 수정합니다. 이를 트리 쉐이킹(tree shaking)이라고 합니다. 나무를 흔들어서 잎과 가지들을 떨어뜨리는 상황을 연상하시면 될 것 같습니다.

 

tree shaking

 

트리 쉐이킹 또한 자동으로 적용되지만, 일부 적용되지 않는 경우가 있습니다. 웹팩이 코드를 제대로 분석하지 못하면 종종 일어나는 경우인데요, CommonJS 방식은 웹팩이 트리 쉐이킹을 적용하기 어렵게 합니다. 요즘은 대부분 import를 사용하는 ES6 방식을 사용하지만, 예전의 코드 구조나 오래된 라이브러리를 살펴보면 이러한 CommonJS 방식을 사용하는 것을아직 확인할 수 있습니다.

 

웹팩과 함께 많이 사용하는 바벨이 호환성을 위해 CommonJS 방식으로 변환하는 경우가 있는데, 설정을 통해 막을 수 있습니다.

 

 

.babelrc.js

"presets": [
    ["env", {
      "modules": false
    }]
  ]

 

필요없는 부분 없애기 : CSS

css 파일 또한 번들 파일에 함께 있을 필요가 없습니다. 번들 파일에서 분리하겠습니다.

mini-css-extract-plugin을 설치하여 웹팩 설정에 적용해주어야 합니다.

 

const MiniCssExtractPlugin = require('mini-css-extract-plugin');

...
module.exports = {
  ...
  plugins: [
    new MiniCssExtractPlugin(),
  ],
  module: {
    rules: [
      {
        test: /.css$/,
        use: [MiniCssExtractPlugin.loader, 'css-loader']
      },
    ]
  }
}

 

이렇게 분리한 css 파일도 압축을 적용해주면 좋습니다. css-minimizer-webpack-plugin을 설치하여 함께 적용해줍니다.

 

const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');

...
optimization: {
  minimize: true,
  minimizer: [
    '...',
    new CssMinimizerPlugin(),
  ]
}

 

기껏 웹팩 설정을 하며 모듈을 하나로 합쳤는데, 최적화를 한답시고 다시 나눠주는 작업을 하니 기분이 썩 유쾌하진 않습니다. 한 파일을 두 개로 나누기만 한다면 어차피 합한 용량은 변하지 않을텐데 처음과 다를게 없어보이기도 합니다.

 

실제로 브라우저가 한 번에 처리할 수 있는 요청 수는 무한하지 않습니다. 우리가 많이 사용하는 크롬 브라우저의 경우 동시에 최대 10개, 한 호스트에서 6개로 제한되어 있습니다. 우리가 파일을 7개로 분리했다고 해도 앞의 파일의 전송이 완료되지 않는 한 나머지 1개의 파일은 전송 대기 상태를 유지하고 있습니다.

 

또한 브라우저가 받아와야 하는 파일은 비단 js와 css파일 뿐만이 아닙니다. 이미지, 동영상, 폰트 ... 경우에 따라 다르겠지만 한 페이지를 구성하기 위해서는 생각보다 다양한 파일들이 필요합니다. 덮어놓고 파일들을 많이 나누다보면 그만큼 많은 요청을 보내야하고, 또 다른 성능 저하로 이어질 수 있기 때문에 주의해야 하는 이유입니다.

 

혼란스럽습니다. 지금까지 계속 나누는걸 설명했는데, 파일을 나누는게 나쁠수도 있다니 충격적입니다.

그럼 파일을 나누면 안되는걸까요? 언제 나눠야하고, 언제는 나누지 말아야할까요. 함께 생각해봅시다.

 

 

분할과 병합, 균형을 잡아보자

구글 개발자들의 기술 블로그인 web.dev에 올라온 글에서도 좋은 사용자 경험을 위해서 낮은 용량과 적은 청크(분할된 파일)를 유지하라고 조언하고 있습니다. 파일이 적은데 용량도 작아야한다니... 모순된 말처럼 들리지만 제가 생각하는 결론은 이렇습니다.

 

나눠야합니다. 대신, 잘 나눠야합니다.

 

무턱대고 파일을 나누는게 안좋다면, 잘 생각해서 나누면 좋다는 의미가 될 수도 있습니다. 우리가 파일을 분할하는 이유는 한 파일에 많은 시간을 쓰지 않으려는 의도도 있지만, 필요한 파일만 받아오기 위한 목적이 가장 큽니다. 소제목으로 필요없는 부분 없애기를 사용한 이유이기도 합니다.

 

우리는 페이지를 처음 방문할 때 필요한 리소스를 받아오고, 그것들을 로컬에 저장해둡니다. 두 번째 방문부터는 변경사항이 없다면 새로 리소스를 받아 올 필요가 없습니다. 저장한 리소스들을 그대로 사용하여 구성해주면 화면을 훨씬 빠르게 보여줄 수 있습니다. 이를 캐시(cache)라고 합니다.

 

정책적인 부분은 여기서 다루기엔 내용이 많으니 다음에 한 번에 설명하도록 하고, 일단은 '저장된 데이터를 이용한다' 정도로 생각해주시면 충분할 것 같습니다.

 

기능 단위로, 또는 사용되는 범위를 기준으로 파일이 잘 나누어진 상태라면 이후에 어떤 부분이 수정되더라도 모든 파일을 다시 받아올 필요가 없습니다. 수정된 부분이 위치한 작은 단위의 파일만 가져오면 그 파일만 다시 받아오는 것으로 화면을 구성할 수 있습니다. 이렇게 하면 나머지 파일은 캐싱된 데이터를 사용하면 되니 화면이 구성되는 속도도 훨씬 빠릅니다. 핵심은 "네트워크 페이로드를 최소한으로 줄이기" 입니다.

 

어느 쪽이 빠를지는...

 

이렇게 진행중인 프로젝트에 따라 적절히 청크를 분리해주시면, 훨씬 빠른 속도로 화면을 구성하는 효과를 얻을 수 있습니다.

 

필요할때만 불러오기

우리가 첫 페이지를 접속했을때, 당장 필요없는 코드까지 함께 불러온다면 낭비가 될 수 있습니다. 이러한 코드는 지연 로딩(lazy loading)을 통해 분리해 줄 수 있습니다. 로드하는 코드의 양이 줄어드니, 더 빠르게 화면을 표시할 수 있습니다.

 

리액트 라우팅을 사용하는 상황이라고 하면, 이런식으로 다른 페이지의 코드를 분리시켜 줄 수 있습니다.

 

// 지연 로딩 적용 전
<Route path="/" element={<Home />} />
<Route path="/search" element={<Search />}



// 지연 로딩 적용 후

// lazy()를 사용해 원하는 컴포넌트를 지연 로딩하고
const Search = lazy(() => import('./pages/Search/Search'));

<Route path="/" element={<Home />} />
<Route
  path="/search"
  element={
  // suspense를 사용해 적용
    <Suspense fallback={<Loading />}>
      <Search />
    </Suspense>
  }
/>

 

 

마무리

간단한 방법들이지만, 효과적으로 코드의 크기를 줄여 네트워크의 페이로드를 감축시킬 수 있는 방법들입니다. 또한 최신 버젼의 웹팩을 사용하신다면 상당한 이점이 있으니, 꼭 상위 버젼을 사용하시는걸 추천드립니다.

 

 

참고 문서

Webpack 공식 문서

트리 쉐이킹으로 자바스크립트 페이로드 줄이기

Keep request counts low and transfer sizes small

반응형

'기타' 카테고리의 다른 글

웹 페이지 최적화하기 - 3  (0) 2022.09.18
웹 페이지 최적화하기 - 2  (0) 2022.09.07
npm, yarn, 그리고 yarn berry  (0) 2022.08.28
Submodule로 환경 변수 관리하기  (0) 2022.08.17
Cypress로 E2E 테스트 시작하기  (0) 2022.02.21

댓글