Front-End/Web & 표준 & ETC

[Web] 프론트엔드 성능 최적화

Voyage_dev 2022. 8. 23. 16:42

웹은 무수히 많은 프로토콜과 많은 실행 환경 속에서 동작이 되고 있기 때문에 웹 성능 최적화를 위한 방법들을 단순히 정의하는게 쉽지는 않다.

 

성능 최적화는 단순히 각종 비용을 줄이는 측면에서 볼 수 있다. 왜냐? 프로그램은 컴퓨터의 리소스를 소모하면서 동작한다. 그러한 리소스에는 메모리 사용량, 처리 시간 등이 있다. 처리하는 작업이 복잡하고 클수록 리소스를 더 많이 소모하게 되기 때문에 성능 저하로 이어진다.

 

특히 웹 프론트엔드 환경에서는 각종 파일들을 주고 받으며 화면을 띄우고 업데이트하는 과정속에서 비용을 소모하기 때문에 웹 개발자는 최소한의 데이터로 가장 빠른 시간에 사용자가 불편함을 느끼지 않는 최적의 화면을 띄워야 한다.

 

이러한 성능 최적화를 통해 사용자 경험의 개선으로 이어지면서 기업의 실적으로도 이어진다.

브라우저 동작 원리

성능 최적화를 학습하기 앞서 기본 배경이 되는 브라우저가 어떻게 화면에 보여주는지 과정을 알아야 한다.

브라우저 파싱 → 스타일 → 레이아웃 → 페인트 → 합성 및 렌더

브라우저 렌더링 과정 링크

성능 최적화

성능 최적화가 가능한 시점을 기준으로 페이지를 로드할 때와 페이지를 렌더링할 때로 분류할 수 있다. 즉, 프론트엔드 성능 최적화에는 웹 페이지 로드 최적화, 웹 페이지 렌더링 최적화가 있다

웹 페이지 로드 최적화

블록 차단 리소스

HTML을 파싱할 때, CSS나 JS를 만나게 되면, HTML은 파싱을 중단하고 해당 피알을 파싱하거나 다운로드 후 실행하게 되는데 이처럼 HTML 파싱을 차단하는 요소를 블록차단 리소스라 한다.

  • HTML 파서는 스크립트 태그를 만나면 DOM 생성 프로세스를 중지하고 자바스크립트 엔진에 제어 권한을 넘긴다. 자바스크립트 엔진의 실행이 완료된 후 브라우저가 중지했던 시점부터 DOM 생성을 재개한다.
  • 즉, 인라인 스크립트를 실행하면 DOM 생성이 차단되고, 이로 인해 초기 렌더링도 지연되게 된다.

블록 차단 리소스는 곧 렌더링 차단 요소에 속하기 때문에 올바른 실행 위치에서 코드를 작성해야 한다.

  • CSS는 <head> 태그 안에 임포트해야 하며, <script> 태그로 실행되는 JS는 일반적으로 <body> 맨 하단에 위치시킨다.
  • css의 경우 media attribute로 어떤 단말기의 유형인지에 따라 해당 css를 적용할지를 명시하면 불필요한 블로킹을 방지할 수 있다.
//block resource 방지
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>My test page</title>
    <!-- css는 여기에 위치 -->
  </head>
  <body>
    <div>
    </div>

    <!-- js는 body 하단에 위치 -->
    <script>
    //...
    </script>
  </body>
</html>

JS의 경우 비동기로 다운로드하도록 명시한다면, DOM 트리나 Style 트리를 변경하지 않겠다는 의미이기 때문에 defer, async attribute를 활용한다.

 

리소스 용량 줄이기

리소스의 용량을 줄임으로서 리소스 라운로드 시간을 최적화할 수 있다.

 

크롬 개발자 도구의 network 탭 → 필터에서 리소스 유형

XHR은 xml http request로 브라우저단에서 서버단으로 HTTP 비동기 통신 할 때 request가 어떻게 구성되어 서버로 전달되는지와 서버로부터 요청에 따른 response 결과를 확인하는 용도이므로 제외하자!

  • 불필요한 코드는 제거
  • 압축 및 난독화로 용량을 최소화
  • 간결한 셀렉터 사용
  • 모듈번들러 (webpack) css, js 번들링
  • 캐싱할 필요 없는 style은 내부 스타일 시트를 사용

JS의 용량 최적화

 

트리쉐이킹

  • 번들링 대부분 webpack, parcel 과 같은 번들러가 제공하는 기능을 이용해서 파일 사이즈를 줄일 수 있다. 번들러를 이용해서 하나의 번들로 만들게 되면 한 번의 호출로 여러 자바스크립트 파일을 사용할 수 있다. 트리쉐이킹 기능을 이용하면 쓰지 않는 코드는 번들에 추가하지 않는다.
  • 트리쉐이킹은 마치 나무를 흔들면 몇몇 가지가 땅으로 떨어지는 것 처럼 외부 모듈에서 필요한 기능만을 임포트하는 것을 의미한다.
// 모든 배열 유틸리티들을 가져온다.
import arrayUtils from 'array-utils'; // bad

// 유틸의 일부만 가져온다.
import {unique, implode, explode} from 'array-utils'; // good
  • 전자의 경우 모듈 전부를 다 가져오지만 후자의 경우 트리쉐이킹 기능을 사용하면 필요한 메서드들만 번들링 되어서 번들 크기를 많이 줄일 수 있게 된다.

불필요한 코드는 제거

Tab size는 2 spaces 권장

압축(Minify) 및 난독화(Uglify)로 용량을 최소화

 

CSS의 용량 최적화

  • 기본적으로 css는 렌더링 차단 리소스이다.
  • DOM은 CSSOM이 있어야 렌더 트리가 구상되기 때문에 CSS는 항상 HTML 최상단 head 태그에 배치하도록 한다.
<head>
  <link href="style.css" rel="stylesheet" />
</head>
  • 특정 조건에서만 필요한 CSS가 있을 경우 미디어 쿼리를 사용하면 불필요한 블루킹을 방지할 수 있다.
<link href="style.css" rel="stylesheet" />
<link href="print.css" rel="stylesheet" media="print" />
<link href="portrait.css" rel="stylesheet" media="orientation:portrait" />
  • 외부 스타일시트를 가져올 때 사용하는 @import 사용은 피한다
/* foo.css */
@import url('bar.css');

간결한 셀렉터 사용

공통 스타일은 class로 정의하여 사용

 

이미지 용량 최적화

WebP 사용

  • JPEG 대신 WebP를 사용하면 평균 20% ~ 30% 정도 크기 감소를 시킬 수 있다. WebP를 지원 안 하는 구버전 브라우저도 있기 때문에 점진적으로 대응하는게 좋다.

이미지 스프라이트

  • 이미지 스프라이트는 여러 개 이미지를 하나로 만들고, CSS의 background-position 속성을 사용해 부분 이미지를 사용하는 방법이다.

이미지 지연로딩

  • 이미지가 다수 필요한 서비스에서, 사용자 화면에 보이는 이미지만 요청하고, 사용자가 스크롤을 내려 다른 이미지가 보여야 할 때 이미지를 요청하는 지연 로딩을 통해 리소스 요청을 줄일 수 있다.

정리

웹 페이지 렌더링 최적화

페이지 렌더링 최적화의 목표는 레이아웃을 최대한 빠르게, 최대한 적게 발생시키는 것!

웹 페이지를 렌더링 하기 위해서는 DOM, CSS가 필요하다. 그러나 다양한 기능과 효과를 구현하기 위해선 자바스크립트를 많이 사용한다. 자바스크립트는 브라우저에서 단일 스레드로 동작하기 때문에 자바스크립트의 실행 시간은 곧 렌더링 성능과 직결된다. 결국엔 자바스크립트에서 실행되는 코드가 최적화 될 수 있게 구성해야 한다.

 

레이아웃 최적화

렌더링 과정에서 레이아웃은 DOM 요소들이 화면에 어느 위치에서 어떤 크기로 배치될지를 결정하게 되는 계산 과정이다. 레이아웃은 글자의 크기를 일일이 계산하고 요소 간 관계를 모두 파악해야 하는 과정이므로 시간이 오래걸린다.

레이아웃 최적화의 목표는 자바스크립트 실행 과정과 렌더링이 다시 일어나는 과정에서 레이아웃에 걸리는 시간을 단축하고 최대한 발생시키지 않도록 하는 것이다.

 

자바스크립트 실행 최적화

강제 동기식 레이아웃과 레이아웃 스레싱 피하기

  • 강제 동기식 레이아웃이란, 레이아웃 과정 끝나기 전에 JS 파일에서 DOM 요소의 위치나 크기값을 변경 후 바로 가져오려 하면 강제로 레이아웃을 발생시키는데, 이것을 강제 동기식 레이아웃이라고 한다.
//계산된 값을 반환하기 전에 변경된 스타일이 계산 결과에 적용되어 있지 않으면 변경 이전 값을 반환하기 때문에 브라우저는 동기로 레이아웃을 해야만 한다

const tabBtn = document.getElementById("tab_btn");

tabBtn.style.fontSize = "24px";
console.log(testBlock.offsetTop); // offsetTop 호출 직전 브라우저 내부에서는 동기 레이아웃이 발생한다.
tabBtn.style.margin = "10px";
// 레이아웃
  • 레이아웃 스레싱은 이 강제 동기식 레이아웃을 반복문 내에서 연속적으로 사용하는 것을 의미한다.
//한 프레임 내에서 강제 동기 레이아웃이 연속적으로 발생하면 성능이 더욱 저하된다.

function resizeAllParagraphs() {
  const box = document.getElementById("box");
  const paragraphs = document.querySelectorAll(".paragraph");

  for (let i = 0; i < paragraphs.length; i += 1) {
    paragraphs[i].style.width = box.offsetWidth + "px";
  }
}

// 레이아웃 스래싱을 개선한 코드
function resizeAllParagraphs() {
  const box = document.getElementById("box");
  const paragraphs = document.querySelectorAll(".paragraph");
  const width = box.offsetWidth;

  for (let i = 0; i < paragraphs.length; i += 1) {
    paragraphs[i].style.width = width + "px";
  }
}
  • 이러한 강제 동시기 레이아웃과 레이아웃 스레싱은 불필요할 수도 있는 레이아웃을 추가적으로 발생시키기 때문에 지양해야 한다.

정리

HTML, CSS 최적화

화면을 렌더링하기 위해서 필요한 데이터는 HTML과 CSS로, 각각 DOM트리와 CSSOM 트리를 만들고 렌더링할 때 사용된다. DOM트리와 CSSOM 트리를 변경하면 렌더링을 유발하고 트리가 클수록 더 많은 계산이 필요하다. 그러므로 HTML과 CSS를 최적화하여 렌더링 성능을 향상할 수 있다.

  • CSS가 복잡하고 많을수록 스타일 계산과 레이아웃이 오래 걸린다. 복잡한 선택자는 시간이 많이 걸리므로 피한다.
  • DOM이 작고 깊이가 얕을수록 계산이 빠르므로 불필요한 래퍼 엘리먼트는 제거한다.

 

 

 

출처 : 아래의 사이트들을 보면서 큰 공부 하였습니다

https://coffeeandcakeandnewjeong.tistory.com/34

https://www.stevy.dev/frontend-web-performance-guide-1/

https://velog.io/@kmlee95/프론트엔드-성능최적화

https://mingule.tistory.com/66