Rendering on the Web: Performance Implications of Application Architecture
역시 웹 관련이라 들어갔는데 또 한 번 신기한 경험을 했다. 바로 Preact의 Creator인 Jason Miller가 강연을 했다는 것. 평소에 트위터에서 팔로우하고 있던 사람이기도 하고 요즘 트렌드를 쫓는 웹 개발자라면 한 번 정도는 들어봤을 이름이라서 직접 봤다는 게 신기했다. Preact의 창시자이니 Preact를 다룰 것 같았지만 Preact의 P자도 안 나왔다.
이 세션에서는 요즘 웹의 Single Page Application으로의 트렌드 변화와 더불어 여러가지 렌더링 기법(Client Side Rendering & Server Side Rendering 등)에 대한 고찰을 하는 세션이었다. 그리고 각각의 렌더링 기법이 어떤 장점과 단점을 갖는지, 그리고 선택할 만한 옵션이 무엇인지에 대해서 다루었다.
Client Side Rendering VS Server Side Rendering
SPA에서 일반적으로 말하는 렌더링 기법이 두 가지 있다. 하나는 Client Side Rendering(CSR) 그리고 다른 하나는 Server Side Rendering(SSR)이다. 발표에서는 SPA에서 가장 쉽게 접근할 수 있는 렌더링 기법인 CSR의 약점에 대해 시작부터 짚고 넘어간다. CSR은 느린 인터넷 커넥션에서는 특히 문제가 될 수 있는데, 그건 큰 크기의 JavaScript 번들을 모두 다운로드 받고 파싱하고 실행할 때까지는 유저가 제대로된 화면을 만날 수 없다는 것이다. 처음 유저가 다운로드 받게되는 HTML은 완전히 빈 페이지일테니 말이다.
유저 경험에 영향을 미칠 수 있는 몇 가지 중요한 렌더링 퍼포먼스 지표들을 기준으로 CSR이 유저 경험에 얼마나 치명적일 수 있는지 설명했다. CSR에서는 FCP(First Contentful Paint)가 상당히 긴 시간 뒤에 일어날 수 있다. 물론 TTI(Time To Interact)는 그보다도 뒤에 일어날 것이다. 따라서 큰 자바스크립트 번들 사이즈는 항상 느린 퍼포먼스를 야기한다.
이와 반대로 Server Side Rendering(SSR)은 FCP가 CSR에 비해서 빠르다. 이 점은 분명 SSR이 CSR에 비해 갖는 장점이다. 하지만 유려한 페이지 전환과 인터랙션 역시 필요하다. 이 점은 오히려 CSR에서 가지는 장점이다. 요즘의 웹 앱은 빠른 FCP와 유려한 페이지 전환 및 인터랙션 모두 필요하다. 모두를 충족시키기 위해 SSR에도 Hydration이라는 기법을 적용할 수 있다.
SSR with Hydration
Hydration 아키텍처에서는 첫 웹 페이지 렌더는 SSR로 이루어지지만 그 이후부터 전환될 모든 페이지는 CSR로 이루어진다. 특히 이후에 이루어지는 이벤트 리스너 등록작업등을 Hydrate라고 부른다. 이런 작업은 원래 개발자가 직접해야하는 것이지만 프로그래머가 신경 쓸 필요가 없도록 구현되어있는 프레임워크를 사용할 수도 있다. 바로 Next.js 와 Nuxt.js 등의 메타 프레임워크이다. Hydration을 이용하면 모든게 좋아질 것 같지만 그럼에도 TTI는 전혀 좋아지지를 않는다.
Pre-rendering
이에 대한 대안으로 Pre-rendering이 있다. Pre-rendering에서는 빌드 타임에 모든 HTML을 렌더링한다. 이미 렌더링 되어있으므로 SSR하는데 걸리는 시간이 필요하지 않아 FCP는 더욱 빨라질 것이다. 쉽게 말해 정적으로 페이지를 렌더링하는 것이고 유명한 툴로는 React 생태계의 Gatsby가 있다. 물론, Pre-rendering 접근 방식도 단점이 있는데 빌드 타임에 모든 페이지 생성을 끝내야 하기 때문에 항상 정적인 컨텐츠에만 활용이 가능하다는 것이다.
Streaming
또 다른 대안으로 SSR Streaming이 소개되었다. Streaming은 브라우저가 페이지 렌더링을 서버 응답이 끝나기 전에 시작할 수 있도록 렌더링에 필요한 데이터를 청크로 쪼개 내려주는 것이다. 이 방법을 적용하면 Time To First Byte (TTFB)를 향상시킬 수 있다. React나 Vue에서는 이미 관련 API가 있기 때문에 바로 적용이 가능한 방식이다. 실제로 Spectrum이라는 웹 서비스에서 Streaming 기법을 적용한 사례가 소개되었다.
Progressive Hydration
Progressive Hydration은 마찬가지로 Hydration을 수행하지만 한 번에 하지 않고 필요한 부분만 점진적으로 하는 것이다. 페이지가 로드되면 나머지 어플리케이션을 모두 로드하는 Full Hydration과는 달리 Progressive Hydration에서는 상황 혹은 유저의 인터랙션에 따라 필요한 부분을 로드한다. React에서는 아직 지원하지 않고있는 기능이지만 별도의 라이브러리를 사용하면 비슷하게 구현이 가능하고, 차후에 React Suspense를 통해 지원할 계획이다. Airbnb는 이미 Progressive Hydration을 적용해 TTI를 극적으로 향상시켰다고 한다. React Progressive 예제는 이 레포지토리에서 확인할 수 있다.
SEO
렌더링 퍼포먼스 외적인 측면도 다루었다. 흔히 많이들 하는 오해가 CSR은 SEO가 잘 되지 않는다라는 것인데, 많은 크롤러들이 JavaScript를 지원하지 않기 때문에 발생한 오해다. Google Bot(크롤러)은 JavaScript를 지원하기 때문에 CSR 사이트도 SEO가 잘 된다. 특히, 최신 버전의 Google Bot은 ES2015 이상의 최신 JavaScript도 지원한다. 또한 Full SSR 없이도 메타 태그들을 잘 활용하면 SEO를 잘 지원할 수 있다.
Data Fetching
SSR은 단순히 HTML을 서버에서 렌더링한다는 것 이상의 의미를 갖는다. 서버는 특정 클라이언트의 요청에서 해당 클라이언트가 필요한 정보(스크립트, 데이터)가 무엇인지를 모두 판단할 수 있기 때문에 HTML이 클라이언트에서 렌더링 되기전부터 정보를 선택적으로 통합해서 내려줄 수 있다.
전반적으로 발표가 다소 딱딱하기는 했지만 연사자 분들이 열심히 준비한 느낌을 받았다. 또 내가 몰랐던 새로운 개념을 알아가게 되어 좋았던 발표다. 특히 신기했던 건 Google I/O 였음에도 불구하고 각 사례별로 React / Vue에서 어떤 방식으로 접근하는지에 대해서도 다루었으며, 특히 React를 중심으로 설명했다는 점이 인상깊었다.
보다 자세한 정보는 이 링크에서 확인해 볼 수 있다.
Modern Web Testing and Automation with Puppeteer
Puppeteer는 Chrome 혹은 Chromium 을 조작할 수 있는 API를 제공하는, Google Chrome 팀에서 만든 라이브러리다. Pre-rendering을 할 때 사용할 수도 있고(react-snap) 이 세션에서 소개된 것처럼 직접 만든 웹 앱을 테스트하는데에도 사용할 수 있다. 이 세션에서는 Puppeteer의 다양한 새로운 기능들이 소개되었다.
Puppeteer for Firefox
기존 Puppeteer를 통한 웹 테스팅의 한계는 해당 테스트가 동작한다고 해서 모든 브라우저에서 코드가 동작할 거라는 보장이 없다는 것이었다. 지금까지는 Pupeeteer가 Chrome만 지원했기 때문이었다. 이 세션에서는 Puppeteer for Firefox가 소개되었는데 브라우저를 조작할 수 있는 것은 마찬가지이지만 해당 브라우저가 Firefox인 점이 다르다. 동일한 API를 가지고 있고 아직은 Experimental 프로젝트이다. 소개에 따르면 90%의 Puppeteer API를 지원한다고 한다.
Browser Context
Pupeeteer를 이용한 테스트는 보통 느렸는데, 이유는 테스트 케이스 하나를 실행할 때마다 고립된 환경을 만들기 위해 브라우저를 끄고 켜기를 반복했기 때문이다. 이를 보완하기 위해 Browser Context라는 개념이 소개되었다. Browser Context를 이용하면 브라우저를 켜고 끌 필요 없이 고립된 환경을 만들 수 있다.
waitFor methods
비동기적으로 렌더링되는 HTML Element의 경우 페이지가 열린 시점에 DOM API를 통해 접근할 수 있을거라는 보장이 없다. 이 때문에 해당 Element에 의존하는 테스트는 항상 성공할 거라고 보장할 수 없다. 이를 해결할 수 있는 waitFor..
메소드들이 소개되었다. 예를 들어 waitForSelector
의 경우 실제 그 Element를 DOM API로 선택할 수 있을 때까지 기다리는 메소드다.
Device Emulation
요즘의 웹 환경에서 모바일을 빼놓기란 어렵다. Puppeteer는 모바일 페이지 역시 테스트 가능하게끔 하기 위해 Device Emulation을 지원한다. 물론 완벽하게 해당 디바이스 환경을 제공하는 것은 아니지만 최대한 비슷한 환경을 지원하고 있는 것 같다. 정확히는 User Agent, Device Pixel Ratio, Viewport Size, Touch Support 등을 제공한다. 이것만으로도 어느 정도는 (최소한 Viewport에 따른 레이아웃 변화 정도는) 테스트가 가능할 것 같다.
Offline Support
이미 PWA등을 이용해서 웹페이지에서 Offline 기능을 지원할 수 있다. Puppeteer는 이것 역시 테스트할 수 있도록 Offline 모드를 지원한다. 이 기능은 간단히 setOfflineMode(true)
로 사용할 수 있다. 또한 ServiceWorker에 접근할 수 있는 API도 열려있다.
Geolocation
어떤 웹페이지에서는 웹의 Geolocation API를 이용해서 유저의 위치에 따라 다른 동작을 하도록 만들 수도 있다. 마찬가지로, Puppeteer에서는 이를 테스트할 수 있도록 Geolocation을 바꿀 수 있는 API를 제공한다.
Network Monitoring
Puppetter에서는 Chrome Devtool의 Network 탭과 같은 네트워크 모니터링을 코드로도 할 수 있다. 또한 모니터링하는데에 그치지 않고, Request를 가로채서 실제 서버에 전송하지 않고 Puppeteer 단에서 적절한 Response로 바꿔치기 할 수도 있다. 이를 이용하면 서버에서 내려주는 Response가 실패했을 때를 가정하여 테스트를 할 수 있다.
Keyboard & Mouse
특정 사용자 인터랙션에 따른 테스트를 하고자 할 때, JavaScript에서도 dispatchEvent
라는 메소드를 사용하면 Event를 Programmatically 발생시킬 수 있지만, 이는 오직 JavaScript 단에서만 Event를 발생시키는 것이지 실제로 브라우저 내에서 일어나는 다양한 동작 (focus, hover)등은 일어나지 않는다. Puppeteer를 이용하면 이런 사용자 동작을 흉내낼 수 있다.
Performance Testing / Tracing / Code Coverage
Puppeteer는 웹페이지의 성능을 측정할 수 있는 API를 제공한다. 이를 통해 특정 수치가 너무 높거나 낮은 경우 테스트가 실패하도록 테스트를 짤 수도 있다.
Tracing은 특정 상황에서 퍼포먼스를 측정할 수 있는 도구다. 예를 들어 사용자의 인터랙션에 따른 CSS 애니메이션이 있는 경우 해당 상황에서의 퍼포먼스를 측정할 수 있다.
Code Coverage 역시 특정상황에서 코드 커버리지를 측정한다. 동적으로 측정을 시작하고 중단할 수 있다.
Accessibility
Puppeteer를 이용하면 접근성 관련 속성에도 쉽게 접근할 수 있다. page.accessibility.snapshot()
이라는 메소드를 이용해서 특정 시점의 접근성 트리를 얻을 수 있다.
개인적으로 전혀 Puppeteer를 사용하지 않고 있는데 소개된 기능들이 상당히 흥미롭고 사용하기 어렵지 않아보여서 한 번 쯤 시도해 볼 만 하겠다는 생각이 들었다. 보여준 데모만 놓고 봤을 때는 실무적으로도 사용할 때 번거롭지 않을 것 같았다. 코드도 읽기 쉬웠고.. 일단 테스트를 짜야
Anatomy of a Web Media Experience
내가 Google I/O에서 들은 마지막 세션이었다. 시간이 시간인지라 사람들도 지쳤는지 발표장에 사람들이 많지 않았다. 하지만 발표 내용은 알찼다. 세션 내용은 제목 그대로, Web에서의 Media(Video, Audio 등) 경험을 어떻게 개선하는지에 관한 것이었다. 즉, 웹 관련 기술 세션이면서도 UX에 관한 내용이었다.
Chrome에서 Youtube로 음악을 들어봤던 사람이라면 음악을 끄기 위해서 수 많은 탭 속에서 한 번 쯤 헤맸던 경험이 있을 것이다. 이 문제를 Sportify가 Chrome에서 구현된 MediaSessionAPI로 해결했다는 내용이었다. MediaSessionAPI는 웹 환경에서 노트북등에 흔히 있는 재생, 일시정지, 다음, 이전 키가 눌리는 이벤트를 접근할 수 있는 API다.
웹 상에서 스포츠 경기를 본다고 할 때 대부분은 중요하지 않은 장면에서는 다른 일을 하면서 보다가 중요한 장면이 나올 때만 스포츠 경기에 집중하는 경우가 많다. Picture in Picture(PiP) API는 이런 경우를 지원할 수 있도록 만들어진 API다. v.qq.com 이라는 중국의 영상 사이트는 PiP API를 적용해 16%의 영상시청시간 증가와 16%의 시청완료 비율 증가를 얻었다고 한다. Sportify는 PiP API를 이용해 떠다니는 음악 플레이어를 만들었다.
그리고 Chrome의 Video Player 인터페이스를 어떻게 개선했는지에 대한 내용이 다뤄졌다. 기본 Video Player에서는 하나의 Bar에 모든 컨트롤 들이 들어있어 조작하기 어려웠다. 이 부분을 개선하기 위해 Shaka Player를 만들게 되었다는 내용이었다. Shaka Player에서는 각 버튼을 적절한 위치로 떼어놓고 재생위치를 표현하는 Bar를 최대폭으로 늘려 사용하기 편하도록 개선했다. 하지만, Shaka Player는 데스크탑에서 동작할 때 문제가 생겼다. 모바일에서는 적절한 사이즈였던 컨트롤들이 데스크탑에서는 너무 작고 컨트롤 간의 거리가 너무 멀다는 것이었다. 이 부분도 데스크탑에 맞게 인터페이스를 개선해 해결할 수 있었다. 이후, Shaka Player 기능적인 설명이 이어졌다. Shaka Player는 Chrome 뿐만 아니라 다양한 플랫폼을 지원하고 있다.
전반적으로 기술에 대한 내용도 다루어졌지만, 결론적으로 사용자의 경험에 포커스를 맞춘 세션이었고 인지과학적인 내용도 있어서 흥미롭게 들을 수 있었다. Shaka Player에 대한 내용은 거의 듣지 못했지만 키워드를 얻었다는 것만으로도 의미는 있었다.
Google I/O 후기 포스팅
- Google I/O 2019: Day 1
- Google I/O 2019: Day 2
- Google I/O 2019: Day 3 👈