나는 렌더링을 두 개의 관점에서 접근한다.
렌더링이 뭐냐? 질문을 받는다면, 두 개의 관점에서 말하고 싶다. 첫 번째는 우리가 url에 검색을 하고 화면에 뛰우는 작업
, 두 번째는 thread가 움직이는 작업
이다. 그리고 나는 지금까지 첫 번째 과정에서의 시각으로만 접근했다. 그래서 렌더링과 관련된 개념공부를 하면 항상 변수가 많았다.
관점1
에서는 그냥, 입력 이후 우리가 잘 아는 dom트리 파싱, cssom 구성, 렌더트리, 페인트 레이아웃~~
등의 순서가 마치 하나의 고정된 개념이고, 동기적 순서으로 느껴지지만, 관점2에서는 이들이 사실 구체화를 한다면, 저 순서가 어쩔 때는 비동기적으로도 동작하며 브라우저의 종류에 따라서도 서로 차이가 있다는 것을 알게 되었다. 즉, 일반화 시킬수 없는 개념이었다.
UI 스레드의 유효성 검사
브라우저 구성요소에서 사용자 인터페이스 부분에 있는 주소창의 입력은 enter 발생 전에, 유효성 검사를 한다. 내가 입력하는 문자가 단순 검색인지, 아니면 url인지 판단을 한다. 이는 Chrome 기준으로, chromium 이라고 웹 브라우저 오픈소스인데, gurl.cc 파일에서 찾아볼 수 있었다.
사실 다른 파일에도, 유효성을 검증하는 방식은 여러 가지가 있었지만, 모두 확인하는 것은 비효율적이라 판단했다. 대신, 정규표현식을 사용해 검증 결과를 is_valid에 true 또는 false로 반환하는 방법이 가장 직관적이라고 생각했다.
이렇게 된 결과를 통해, 검색어
인지 url
인지 구분하고, url의 경우 네트워크 스레드에게 전달해서 우리가 아는 DNS, TLS, CONTENT-TYPE에 따른 문서 전달
하고, 그 과정에서 이전의 UI스레드는 네트워크 스레드로 전송한 동시 병렬적으로 찾아놓은 렌더러 스레드를 통해, 이후 받은 response를 바로 render 한다.
잘못된 진실이라 생각하는 부분들
최근에 나를 많이 헷갈리게 한 부분이 있었는데, SSR이 서버에서 렌더된 HTML을 브라우저로 주면 사용자는 바로 본다
라는 부분이었다. 이 말이 사실 넷상에 일반화 되어있다고 생각한다. 이 말대로라면, SSR에서 브라우저는 렌더링 하지 않나? 그래서 바로 본다는 건가? 등의 착각을 불러왔는데, 아니다. 결국 브라우저에서 렌더링을 해야하고, 바로 볼 수 있다는 말은 다른 렌더링방식들과 비교해서 상대적으로 빠르게 FCP에 도달
한다. 라는 말이 함축된 의미라 생각한다.
Next.js로 구현한 지금 이 블로그를 보면, SSR이라도, HTML파싱과 layout, paint 그리고 이후의 GPU 동작이 존재하고, 그것이 단지 다른 렌더링과 비교하면 빠르게 FCP가 측정이 될 뿐이라 생각한다.
그리고 DCL이후에 FCP가 발생하는게 정석이라 생각을 했지만 그렇지 않았다.
FCP가 DCL이후에 발생을 하고 있는데, 사실 DOM트리가 전부 완료 되기 전부터 DOM노드는 다음 단계로 각각 병렬적으로(DOM트리 완성을 위한 과정, 생성중인 DOM트리의 DOM NODE 각각이 다음 단계 진행
) 실행이 되서 그런 것이다.
다만 CSS같은 경우는 화면에 찍는 위치, 크기 정보가 다 있기 때문에 CSSOM트리가 모두 생성된 이후 다음단계(RENDER 트리 구성)로 진행 된다. 그래도, CSS의 로딩(파싱이 아님)은 script와 달리 메인스레드(DOM트리 생성)을 막지 않는다.
다시 본론으로 들어가면, DOM의 일부 완성이 이미 layout, paint를 거치고 초기 첫 번째의 사이클로 일부분이 화면에 그려지는 것이다. 그래서 본다면 나는 새로고침을 한 번 눌렀는데, layout, paint, html 파싱이 계속 일어난다.
paint 과정을 보통 화면에 그리는 단계
라고 표현을 하지만, 사실 정확한 의미는 화면에 그릴 내용을 표현하는 그래픽 명령을 생성
하는 단계이다. 이는 화면에 실제로 렌더링을 하거나 픽셀을 채우는 작업이 아니다. 그래서 어떻게 그릴 것인지를 나타내는 명령어들을 준비하는 과정
이라고 정의하고 싶다. 실제로 paint가 그리는 단계라면, 아래 사진에서 페인트 바로위에 FP, FCP가 있어야 하는데 없기 때문이다.
왜냐하면, 잘보면 이후 commit이 나오는데, 이는 main thread에서의 결과를 composite thread로 옮기고, composite thread는 이를 gpu process와 병렬적으로 처리하면서, 화면에 그리기 때문이다. 이런 이유는 main thread의 결과물을 다른 thread로 옮기기 때문에 main thread는 다른 작업을 composite와 병렬적으로 수행할 수 있기 때문이다.
저번 포스팅에서 html에 script를 삽입한 이유 추가 설명
추가로 이 병렬성이라는게, 서로 순서가 중요하지 않은 작업에서는(main T, compositer T는 이미 렌더로 완성된 정보를 gpu에 던져주고 다른 추가 렌더링을 해야하니 순서는 필요없다.
) 상관은 없지만 js로딩같은 네트워크를 타야하는 작업에서는 렌더링 엔진의 main thread가 block되게 된다. js가 dom 조작을 할 수 있으니, 이후 일어난 렌더링 과정에서 비효율을 없애기 위해서 이다. 즉 올바른 화면을 렌더링 하겠다는건데, 이런 경우 때문에 직전 블로그 포스팅에서는 velog와 현재 이 블로그에서 script태그에 외부 링크 대신 직접 코드를 삽입하여, 바로 태그를 파싱할때 js가 컴파일되도록하여 main thread의 block을 방지했다.
다크모드 적용이 FC, FCP 이전에 해야하는 상황(깜빡임 방지)가 조건인 환경에서는, 외부 스크립트를 가져오는 기간동안 main thread를 차단하는 것보다, inline script를 줘서 바로 main thread가 이 작업을 처리하도록해서 평균적인, FP, FCP시점을 앞당겨올 수 있다.
모든 내용을 해당 글에 적기 보다는 앞으로 적어나갈 렌더링 과정에 계속 추가할 예정이다. 브라우저에 대한 이해가 직접적으로 개발에 영향을 미치는 경우는 많지 않을 것이다. 하지만 프론트엔드 최적화 과정이 렌더링과 깊은 연관이 있다는 점을 생각해보면, 렌더링이 이루어지는 장소가 바로 브라우저이다. 그리고 공부를 하며 끊임없이 의문을 품다 보면, 결국 브라우저라는 주제에 도달하게 된다. 이 때 만약 브라우저에 대해 전혀 모른다면, 이해하는 공부보다는 단순히 외우는 공부로 전락할 가능성이 클 것이다. 그래서 지금 브라우저에 대해 조금이라도 공부해 두는 것이 중요하다고 생각을 했고, 이렇게 하면 나중에 예상치 못한 문제에 직면했을 때, 그 원인을 더 쉽게 파악할 수 있으리라는 기대를 가지고 있다면 그나마 효율적이지 않을까? 생각을 해봤다.