728x90

이제 성능에 앞서서 웹페이지가 로딩되는 순서를 알아보도록 하자.

왜 이게 중요한지에 대해서는 바로 최적화 문제이다.

 

출처 - https://developers.google.com/web/fundamentals/performance/critical-rendering-path/

 

위의 예시를 보자. 모든 페이지가 로딩되는데 걸리는 시간은 위나 아래나 동일하다.

하지만 사용자 입장에서는 페이지가 오랫동안 정적이다가 한번에 뜨는 것에는 불만을 크게 가지지만,

페이지가 계속해서 진행되면서 뜨는 것에 대해서는 관대하다.

그래서 보통 페이지의 최적화의 경우에는 성능개선도 크지만

맨처음 페이지가 뜰 때 페이지가 진행하면서 뜨고 있다는 느낌을 주는것 역시 아주 중요하다.

이에 관련된 용어도 있지만 이건 나중에 설명하도록 하겠다.

 

이제 페이지의 로딩이 왜 중요한지는 알 것이다.

페이지의 로딩이 최적화 되야만 사용자 경험적인 측면에서 유리하게 가져갈 수 있다.

그럼 정확하게 페이지의 로딩은 어디서 부터 어디까지일까?

우리가 특정 페이지를 url에 입력을 하는 순간 부터 우리가 원하는 내용일 뜰때 까지로 생각할 수 있다.

이를 그림으로 표현하면 아래와 같다.

 

출처 - https://www.w3.org/TR/navigation-timing-2/

 

위 그림에서 노란색 부분은 프론트엔드에서는 핸들링을 할 수 없는 부분이다.

백엔드의 성능을 어렴풋이 프론트에서도 측정을 할 수는 있는 것이다(물론 상세히는 알 수 없다.)

 

 

performance.timing

 

해당 정보는 performance.timing으로 변수로 입력하면 나온다.

이는 브라우저에서 제공해주는 기본스팩이다.

어디까지 지원하는지는 여기를 참조하자.

 

우리가 직접 핸들링 할 수 있는 부분은 뒷부분의 퍼런 영역이다.

이 중에서 특히 중요한건 domContnetLoadedEventStart와 loadEventStart부분이다.

 

document.addEventListener("DOMContentLoaded", function(){
	//syntax;
});

위의 코드로 DOMContentLoaded타이밍에 콜백을 추가하여 핸들링 할 수 있다.

DOMContentLoaded는 아주 중요한 타이밍인데 여러분이 받은 HTML의 파싱이 끝났고(파싱이 끝났다는건 로딩은 당연히 됬다는 뜻)

그리고 DOM트리가 완성되서 그걸 화면에 그리기 까지 했다는 의미이다.

 

 

여러분이 여기서 옆에 보이는 얇은 파란선(큰 파란선은 마우스를 오버했을 때 나오는 것)이

DomContentLoaded가 발생한 시점이다. 영어를 보면 알겠지만 이미 돔의 로딩이 끝난 상황이다.

즉 dom tree는 이미 여기서 만들어진 것이다.

 

window.onload = () => {
	//syntax;
}

그 다음 중요한건 loaded이다. 중요도로 그 다음 이란건 아니다.

위의 코드로 Loaded타이밍에 콜백을 추가하여 핸들링 할 수 있다.

이는 페이지에 모든 화면이 로딩됬을 때 발생한다.

 

 

화면상에 로딩이 완료됬다는 것의 의미는 dom트리가 구성되고 나서 그 위에 스타일(css, style)이 다 입혀지는게 완료된걸 의미한다.

보통 빨간선으로 표시되며 위의 예제에서 180ms 뒤에 있는 빨간선이 바로 loaded의 종료시점이다.

 

자 그럼 이 때까지 순서에대해서 이야기했는데 아직 중요한게 빠져있다.

브라우저가 어떻게 HTML을 읽고 분석하고 이벤트들이 발생하느냐이다.

이를 순서대로 표시하는것이 좋을 거 같다.

 

출처 - https://developers.google.com/web/fundamentals/performance/critical-rendering-path/analyzing-crp

 

일단 우리가 특정 url을 요청을 하면 상대는 html을 던져준다.

여기서 html에는 안에 여러가지 태그가 있다. 그 중에서 몇몇 태그들은 특별하게도 추가로 요청을 하게 된다.

 

https://github.com/justkukaro/WEB-PERFORMANCE-TEST/tree/master/Ch02

예시는 이 프로젝트로 해보자

 

<!DOCTYPE html>
<html lang="kr">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <link rel="icon" href="favicon.png">
    <link rel="stylesheet" type="text/css" href="index.css"> 
    <link rel="stylesheet" type="text/css" href="style.css"> 
</head>
<body>
<div id="div1">
    <span style="font-family: Roboto;">span</span>
</div>
<img src="vue.png" width="100" height="100">
<script src="index.js"></script>

<script src="index2.js"></script>

<img src="react.png" width="100" height="100">
<div id="div3">
    <div id="div2">

    </div>
</div>
<b>bold</b>
<script src="main.js"></script>
<img src="angular.png" width="100" height="100">
</body>
</html>

 

위를 예제로 보도록하자.

html을 요청후에 보면 html자체에서 요청하는 것을 나열하면 아래와 같다.

 

index.html

<link rel="icon" href="favicon.png">
<link rel="stylesheet" type="text/css" href="index.css">
<link rel="stylesheet" type="text/css" href="style.css">
<img src="vue.png" width="100" height="100">
<script src="index.js"></script>
<script src="index2.js"></script>
<img src="react.png" width="100" height="100">
<script src="main.js"></script>
<img src="angular.png" width="100" height="100">

총 9개가 호출된다.

 

style.css

@font-face{
    font-family: Roboto; src: url(./Roboto-Regular.ttf)
}

또한 style.css에서 1개가 호출된다.

 

위에서 여러분이 궁금한건 DOMContentLoaded가 언제일어나며 또한 Loaded는 어떻게 일어나느냐이다.

결과를 보면서 설명하도록 하겠다.

 

 

페이지를 요청하고 나면 html파일이 넘어온다.

html파일의 마지막 바이트까지 읽는게 끝나면 이제 html 파싱을 시작한다.

html의 파싱을 하는 과정에서 나오는 모든 외부 콜은 일단 큐에 적재되고 시간이 지나면 요청을 한다.

여기서 단 큐에 적재되는 녀석은 html에 직접 있는 녀석이다.

가령 위의 예제에서 style이 font를 불러오지면 html에 직접있지 않으므로 요청이 되지 않는다.

그래서 일단 요청은 9개가 적재된다.

파싱이 끝이 나면 바로 적재된 요청을 바로 시작한다.

요청은 요청이고 그 동안 파싱한 데이터를 기반으로 DOM트리를 만든다.

DOM트리를 만드는 동안 요청한 데이터가 넘어오면 즉각 반영한다.

DOM트리 구축이 끝나면 DOMContentLoaded가 일어난다.

 

여기서 html을 파싱하는동안 request를 요청한다고 하였다.

위의 예제에서 9개를 요청했는데 요청은 비동기로 이루어진다.

즉 html을 파싱하는동안 응답을 받는 경우가 생긴다.

이 경우 html을 파싱하는 것을 중지하고 해당 응답을 파싱하거나 실행하게 된다.

가령 css라면 파싱을 할 것이며 js라면 실행을 한다.

 

이 때 html파싱을 중지하고 요청을 받는 작업을 할경우

쭉 html을 파싱하는것보다 유휴시간이 발생해서 성능에는 조금 나쁘다.

그러나 유휴시간은 무시할 수는 있는 정도인지라 크게 신경쓰지는 않는다.

 

css의 경우 css파싱이 끝나게되면 cssom tree를 갱신하게 된다.

이 과정에서 html도 현재 파싱이 마친데까지 dom tree를 만들고 이를 반영해서 바로 render tree를 만든다.

아직 cssom tree, dom tree, render tree에 대한 언급을 하진 않았지만 결론 부터 말하자면

css가 선두에 선언되면 html을 읽는 도중에 css의 파싱이 이루어지고 html파싱은 멈추게 되며

현재까지 읽은 정보 + css로 화면에 반영하게 된다. 이 말은 당연히 쭉 html을 파싱하고 css를 적용하는것 보다 성능저하 발생,

하지만 그렇다고 css를 뒤에 두게 되면 css자체를 서버에서 받아오는데 유휴시간이 걸리게된다.

장단점을 정리하면 아래와 같다.

 

css를 선두에 선언
장점 - 파싱이 빠르게 이루어지므로 변환되는 dom트리에 즉각적으로 적용됨
단점 - 모든 일은 한꺼번에 처리(배치처리)를 하는게 이득이나 도중에 흐름을 끊고 유휴시간이 발생, 시간 낭비

css를 말미에 선언
장점 - 모든 dom이 완성된 후에 cssom트리를 만듦으로 유휴시간이 발생하지 않음
단점 - 근데 요청자체를 늦게하기 때문에 페이지를 모두 로딩하고 css로딩 자체를 기다려야함. 선두에서 요청하면 어짜피 비동기 요청이라 요청하고 할거 하다가 응답오면 처리하면되는데 말미에 요청하면 응답올때까지 기다리고 처리할때까지 기다림

 

둘다 장단점이 있는거 같지만 정말 특별한일이 있지 않는한.. 사실 정말 특별해도 CSS는 선두에 선언하는게 시간이 절약된다.

따라서 일반적으로 css는 선두에 선언한다.

 

let sum = 0;
for (let i = 0; i < 100000000; i++) {
    sum += i;
}
console.log(sum)

 

다만 조심해야할게 js이다.

js파일이 어느타이밍에 도착할지는 정말 아무도 모른다.

그리고 js로직이 어떻게 될지도 모른다.

가령 js로직이 위와 같다고 가정해보자(실제 예제이기도하다)

그럴 경우 우리는 저 녀석을 기다릴 수 밖에 없고 파싱은 더뎌진다.

그리고 또다른 문제는 js가 실행되는 시점에서 사용하는 변수는 그 시점의 변수밖에 모른다는 것이다.

가령 js가 전역변수 a를 사용하는데 전역변수 a를 선언한 js가 나중에 호출될경우 이는 에러가 난다는 것이다.

물론 그런 식으로 짜면 뒤통수 한대 맞아야겠지만 dom을조작하거나 css선택자를 js로 쓰거나 이러면 문제가 불거진다.

그래서 js는 특별한일이 있지 않는한 맨 아래에 선언해주는 것이 좋다.

css도 가능하면 순서대로 배치해주는게 좋긴한데 성능에 크리티컬하진 않으므로 넘어가도록 하자.

 

위의 예제를 하면 css의 파싱이 끝나고 나서 css에서 font가 필요한걸 깨닫고 font를 요청한다.

우리가 생각하기에는 font의 요청이 끝난 후 DOMContentLoaded가 요청될것 같지만,

font의 요청과 상관없이 DOMContentLoaded가 완료된다.

그 이유는 위에서 설명했듯이 html파싱 끝나고 DOM트리 제작이 끝나는 순간이 DOMContentLoaded이기 때문이다.

 

단 Loaded는 상황이 좀 다르다.

위의 경우 예제는 아니고 필자가 개발하는 프로그램의 예인데 초록색의 경우 ajax콜이다.

html 내부콜이나 html과 직접적 연결되어있는 연쇄 콜들 뿐만아니라 js로 실행되는 ajax까지 실행됬다.

loaded가 굉장히 뒤에 발생한걸 알 수 있다.

loaded는 현재 큐에 있는 콜들을 다 처리하고 나서 일어난다.

경우에 따라서는 아주 늦게 일어날 수도 있다.

 

이번 포스팅에서는 어떻게 콜이 날라오고 이러한 지표를 어떻게 보는지에 대해서 언급하였다.

다음 포스팅에서는 이제 response가 날라오고 나서 어떻게 웹이 구성되는지에 대한 과정을 보도록 하자.

+ Recent posts