앞의 세 편에서 주소를 해석하고, 이름을 숫자로 바꾸고, 안전한 연결을 열어 마침내 서버로부터 문서를 받아오는 데까지 이르렀다. 그런데 서버가 돌려준 것은 사실 한 덩어리의 텍스트일 뿐이다. 브라우저는 이 텍스트를 그대로 화면에 뿌리지 않고, 먼저 자신이 다루기 좋은 구조로 바꾼다. 이번 편은 이 변환, 곧 문서를 읽어 구조를 세우는 파싱 과정을 다룬다.


이 단계는 건축에 비유할 수 있다. 서버가 보내온 텍스트는 자재 목록이 적힌 종이와 같고, 브라우저는 그 종이를 읽어 실제 골조를 세운다. 이 골조가 튼튼하고 정확해야 이후에 벽을 칠하고 가구를 놓는 모든 작업이 매끄럽게 이어진다. 골조를 세우는 과정이 어떻게 이뤄지고 무엇이 이 과정을 방해하는지를 알면 첫 화면이 뜨는 속도를 크게 개선할 수 있다.

텍스트가 구조로 바뀌는 과정

브라우저가 받은 것은 결국 이어진 글자들의 나열이다. 이 글자들을 의미 있는 구조로 바꾸기 위해 브라우저는 여러 단계를 거친다. 먼저 받아온 원시 데이터를 사람이 읽는 글자로 해독하고, 그 글자들을 다시 의미 단위로 잘라 낸다. 이 잘라 낸 조각들이 구조를 세우는 벽돌이 된다.


이 의미 단위를 만드는 과정에서 브라우저는 어디서부터 어디까지가 하나의 요소인지, 그 요소에 어떤 속성이 붙어 있는지를 알아낸다. 여는 표시와 닫는 표시를 짝지어 요소의 범위를 정하고, 그 안에 담긴 내용과 부가 정보를 함께 묶는다. 이렇게 정리된 조각들이 차곡차곡 쌓인다.


브라우저는 이 조각들을 단순히 나열하는 것이 아니라 서로의 관계를 따져 엮는다. 어떤 요소가 다른 요소 안에 들어 있으면 부모와 자식 관계로 연결하고, 나란히 있으면 형제 관계로 놓는다. 이렇게 관계를 엮으면 자연스럽게 나무 모양의 구조가 만들어진다. 가장 바깥의 요소가 뿌리가 되고, 그 안의 요소들이 가지로 뻗으며, 실제 글자나 이미지 같은 내용이 잎처럼 매달린다.


이 과정에서 브라우저는 문서를 다 받을 때까지 기다리지 않고 조금씩 진행한다. 텍스트가 도착하는 대로 앞부분부터 읽어 구조를 세워 나가기 때문에, 문서가 아직 다 오지 않았어도 앞쪽 내용을 먼저 화면에 보여줄 수 있다. 이 점진적인 처리가 사용자에게 빠른 첫인상을 준다. 또한 브라우저는 문서에 사소한 실수가 있어도 웬만하면 스스로 바로잡으려 애쓴다. 닫는 표시를 빠뜨리거나 순서가 어긋난 경우에도 나름의 규칙으로 구조를 복구하는데, 이 관대함은 편리하지만 의도한 것과 다른 구조가 만들어질 여지가 있다.

DOM이라는 이름의 나무

이렇게 만들어진 나무 구조를 문서 객체 모델이라고 부른다. 이름이 다소 딱딱하지만, 요컨대 문서를 프로그램이 다룰 수 있는 객체들의 나무로 표현한 것이다. 화면에 보이는 모든 요소가 이 나무의 한 마디에 대응한다.


이 나무가 중요한 이유는 이후의 모든 작업이 이것을 기준으로 이뤄지기 때문이다. 스타일을 입힐 때도, 스크립트로 내용을 바꿀 때도, 사용자의 클릭에 반응할 때도 브라우저는 이 나무의 마디를 찾아 조작한다. 말하자면 이 나무가 문서를 다루는 공용 좌표계가 되는 셈이다. 스크립트가 화면을 바꾸는 것도 사실은 이 나무를 고치는 일이다. 새 마디를 붙이면 화면에 새 요소가 나타나고, 마디를 떼어내면 화면에서 사라진다.


다만 이 나무를 자주 고치는 일은 생각보다 비싸다. 마디 하나를 바꿀 때마다 브라우저는 그 변화가 화면에 어떤 영향을 주는지 다시 계산해야 하기 때문이다. 그래서 나무를 잘게 여러 번 고치기보다 한꺼번에 모아서 고치는 편이 훨씬 효율적이다. 화면을 여러 번 바꿔야 할 때 변경 사항을 잠시 모아 두었다가 한 번에 반영하는 방법이 부드러운 화면을 지킨다.


이 나무는 또한 문서의 논리적 구조를 그대로 담기 때문에, 이 구조가 명확할수록 여러 이점이 따라온다. 화면을 소리로 읽어 주는 도구가 내용을 올바르게 전달하고, 검색 엔진이 문서의 의미를 잘 파악하며, 나중에 이 문서를 다루는 개발자도 구조를 쉽게 이해한다. 좋은 구조는 눈에 보이지 않는 곳에서 여러 몫을 한다. 문서를 잘 짓는다는 것은 결국 이 나무를 잘 설계하는 일이며, 각 요소가 제 의미에 맞는 자리에 놓이도록 하는 것이다.

파서를 멈춰 세우는 것들

브라우저가 문서를 앞에서부터 읽어 구조를 세워 나간다고 했는데, 이 흐름을 도중에 멈춰 세우는 것이 있다. 바로 문서 중간에 끼어 있는 스크립트다. 스크립트는 문서의 나무를 바꿀 수 있기 때문에 브라우저는 신중하게 대응한다.


문서를 읽던 중 스크립트를 만나면 브라우저는 원칙적으로 문서 읽기를 잠시 멈추고 그 스크립트를 먼저 실행한다. 스크립트가 앞으로 만들어질 나무를 어떻게 바꿀지 알 수 없으니, 일단 실행해서 결과를 반영한 뒤에야 안심하고 문서 읽기를 이어갈 수 있기 때문이다. 문제는 그 스크립트가 바깥에서 따로 내려받아야 하는 것일 때다. 이 경우 브라우저는 스크립트 파일이 도착할 때까지 문서 읽기를 멈춘 채 기다린다.


특히 문서의 앞머리에 무거운 스크립트를 그냥 두면, 정작 보여줄 내용은 뒤에 있는데도 그 스크립트를 받고 실행하느라 첫 화면이 한참 늦어진다. 사용자는 서버가 느린 줄 알지만 사실은 스크립트가 문서 읽기를 붙잡고 있는 것이다. 원인을 모르면 엉뚱한 곳을 고치기 쉽다. 첫 화면이 이유 없이 느릴 때 가장 먼저 의심할 것이 문서 앞머리에 놓인 붙잡는 스크립트다.


스타일 정보 역시 화면을 그리는 데 필수라 어느 정도는 흐름을 붙잡는다. 스타일이 준비되지 않은 채로 그리면 잠깐 엉성한 모습이 보였다가 갑자기 바뀌는 어색함이 생기므로, 브라우저는 스타일이 갖춰질 때까지 그리기를 미룬다. 다만 스타일은 스크립트와 다루는 방식이 조금 다르다. 무엇이 문서 읽기를 붙잡고 무엇이 화면 그리기를 붙잡는지를 구분해 다루는 것이 이 단계 최적화의 핵심이다.

스크립트를 다루는 두 가지 방법

스크립트가 문서 읽기를 붙잡는 문제를 풀기 위해, 브라우저는 스크립트의 실행 시점을 조절하는 방법을 제공한다. 대표적인 것이 두 가지인데, 각각 성격이 달라 상황에 맞게 골라 써야 한다. 이 둘을 구분하는 것만으로도 많은 성능 문제가 풀린다.


첫 번째 방법은 스크립트를 문서 읽기와 나란히 내려받되 실행은 문서를 다 읽은 뒤로 미루는 것이다. 문서 앞에 defer라는 표시를 붙이면, 브라우저는 문서 읽기를 멈추지 않고 스크립트를 배경에서 내려받다가 문서가 완성된 후에 순서대로 실행한다. 여러 스크립트의 실행 순서가 보장되는 것이 장점이라, 서로 순서에 기대는 화면 구성 스크립트에 알맞다.


두 번째 방법은 스크립트를 배경에서 내려받되 준비되는 즉시 실행하는 것이다. async 표시를 붙이면 브라우저는 문서 읽기를 멈추지 않고 스크립트를 받다가, 다 받는 순간 잠깐 문서 읽기를 멈추고 실행한다. 실행 순서가 보장되지 않으므로 서로 의존하지 않는 독립적인 스크립트에 알맞다. 방문 기록을 남기는 도구나 광고처럼 다른 것과 얽히지 않는 스크립트가 여기에 해당한다.


이 두 방법을 잘 쓰는 것만으로도 문서 읽기가 스크립트에 발목 잡히는 일을 대부분 없앨 수 있다. 새 페이지를 점검할 때 문서 앞머리에 아무 표시 없이 놓인 스크립트가 있는지부터 확인하고 성격에 맞게 표시를 붙이는 것이 기본이다. 한편 브라우저는 문서를 읽으면서 앞으로 필요할 자원을 미리 훑어보는 별도의 눈을 가지고 있어, 스크립트에 붙잡혀 있는 동안에도 뒤에 나올 스타일이나 이미지를 미리 내려받기 시작한다.

잘못된 문서를 복구하는 규칙

브라우저가 문서의 실수를 스스로 바로잡는다고 했는데, 이 복구는 즉흥적인 것이 아니라 정해진 규칙을 따른다. 어떤 태그가 닫히지 않은 채 다른 태그가 나타나면, 브라우저는 정해진 순서에 따라 앞의 태그를 자동으로 닫거나 뒤의 태그를 앞으로 끌어올린다. 어긋난 구조를 만났을 때 무엇을 어떻게 고칠지가 표준으로 상세히 규정되어 있다.


이 규칙이 표준으로 정해져 있다는 것은 중요한 의미를 가진다. 같은 잘못된 문서를 여러 브라우저가 똑같은 방식으로 복구한다는 뜻이기 때문이다. 만약 복구 방식이 브라우저마다 제각각이라면, 실수가 있는 문서는 환경에 따라 전혀 다른 구조로 세워져 화면이 엉망으로 갈릴 것이다. 복구 규칙의 표준화가 이런 혼란을 막는다.


그러나 복구가 표준적이라는 것이 실수를 방치해도 좋다는 뜻은 아니다. 복구된 구조는 작성자가 의도한 구조와 미묘하게 다를 수 있고, 그 차이가 스타일이나 동작에서 엉뚱한 결과로 드러난다. 특정 태그가 자동으로 닫히면서 그 뒤의 요소들이 예상과 다른 부모 밑으로 들어가면, 물려받는 스타일이 뒤바뀌거나 스크립트가 엉뚱한 마디를 조작하게 된다.


그래서 복구에 기대기보다 처음부터 구조를 정확히 닫아 두는 편이 낫다. 여는 표시마다 짝이 되는 닫는 표시를 분명히 두고, 요소가 담길 수 있는 자리와 담길 수 없는 자리를 지키면 브라우저가 손댈 일이 없어진다. 브라우저의 관대함은 안전망일 뿐이며, 그 안전망을 매번 밟는 것을 전제로 문서를 쓰면 언젠가 그 미묘한 차이에 발목을 잡힌다.


브라우저가 실제로 세운 나무가 의도한 것과 같은지는 검사 도구로 확인할 수 있다. 개발자 도구는 브라우저가 최종적으로 만든 구조를 그대로 펼쳐 보여 주는데, 여기에 나타난 구조가 작성한 문서와 다르다면 복구가 개입한 것이다. 화면이 이상하게 어긋날 때 원래 문서만 들여다보면 원인이 보이지 않다가, 복구된 나무를 확인하는 순간 자동으로 닫힌 태그나 옮겨진 요소가 드러나는 경우가 많다.


이 확인 습관은 특히 스크립트가 구조를 바꾸는 화면에서 요긴하다. 처음에 받은 문서는 멀쩡했어도, 스크립트가 마디를 붙이고 떼는 과정에서 구조가 예상과 달라질 수 있기 때문이다. 지금 이 순간 브라우저가 쥐고 있는 나무의 모습을 직접 보는 것이, 문서가 어떻게 해석되고 어떻게 변형되었는지를 추적하는 가장 확실한 방법이다.

문서를 가볍고 빠르게 세우려면

지금까지의 이야기를 종합하면, 문서 파싱을 빠르게 하는 길은 크게 두 갈래다. 하나는 문서 자체를 가볍고 명확하게 유지하는 것이고, 다른 하나는 파싱을 붙잡는 요소들을 잘 다스리는 것이다. 이 둘을 함께 챙겨야 첫 화면이 시원하게 뜬다.


문서를 가볍게 한다는 것은 불필요하게 깊고 복잡한 구조를 줄이는 것을 뜻한다. 나무가 지나치게 깊고 마디가 많으면 그것을 세우고 이후에 다루는 모든 작업이 무거워진다. 화면을 만들 때 꼭 필요한 만큼만 구조를 두고, 습관적으로 겹겹이 감싸는 것을 경계해야 한다. 또한 구조를 의미에 맞게 세우면 브라우저뿐 아니라 사람에게도 이롭다.


파싱을 붙잡는 요소를 다스리는 일은 앞서 말한 스크립트 조절이 핵심이다. 여기에 더해, 첫 화면에 꼭 필요한 스타일만 먼저 갖추고 나머지는 나중으로 미루는 방법도 있다. 당장 보이는 부분을 그리는 데 필요한 것만 앞세우면 사용자는 훨씬 빨리 내용을 마주한다. 이런 최적화의 기준은 항상 실제 사용자의 체감이다.


정리하면, 브라우저는 텍스트를 읽어 나무 구조를 세우고 그 나무 위에서 모든 것을 그리며, 이 세우기를 방해하는 스크립트를 어떻게 다루느냐가 속도를 좌우한다. 이 원리를 몸에 익히면 첫 화면이 늦는 문제 앞에서 헤매지 않고 원인을 곧장 좁혀 갈 수 있다. 다음 편에서는 이 나무에 색과 모양을 입히는 스타일 정보가 어떻게 해석되어 또 하나의 구조를 이루는지를 다룬다.