이 글은 frontendmastery의
“The new wave of Javascript web frameworks”라는
글을 참고했습니다.
React의 부상
빅 테크 시대로 빠르게 접어들면서 자바스크립트는 업계에 진입하는데 매우 중요한 도구가 되었고, 이와 함께 별도의 백엔드를 구축하여 API로 지원되는 SPA 방식이 일반적인 트렌드가 됐습니다. 그리고 최근 SPA 개발을 위한 주요 도구로 사용되는 프레임워크인 React는 처음에 페이스북이 다음의 몇 가지 과제를 해결하기 위해 개발한 것이라고 합니다.
- 데이터가 자주 변경될 때 일관성 유지
- 여러 위젯을 서로 동기화하는 것은 여전히 중요한 과제였고, 데이터 흐름의 예측 가능성 부족은 이를 규모 있는 문제로 만들었다.
- 조직적인 확장
- 시장 출시 시간과 속도가 우선순위가 되었는데, 빠르게 속도를 내고, 생산성을 높일 수 있는 새로운 개발자를 투입하는 것이 필수적이었다.
React가 등장하면서 개발자들은 프론트엔드 코드를 선언적으로 작성해야 할 필요성이 대두되었습니다. React 이전에는 아직 MVC 프레임워크가 상대적으로 확산되지 않았었는데, 기존 언어의 템플릿 방식에서 자바스크립트 기반의 JSX로 전환하는 것이 쉽지 않았기 때문입니다.
React와 같은 컴포넌트 모델은 독립적인 컴포넌트들을 병렬로 보다 더 쉽게 작업할 수 있는 별도의 프론트엔드로 분리할 수 있게 만들었는데, 아키텍처 관점에서 컴포넌트 모델은 공유된 기본 요소부터 페이지의 뿌리까지 구성된 유기체로서 컴포넌트의 계층화를 허용했습니다.
React의 단방향 데이터 흐름은 데이터의 흐름을 이해하고, 추적하고, 디버그하기 쉽게 만들었고, 이전에는 찾아보기 힘들었던 예측 가능성을 추가했는데, 가상 DOM은 UI에 대한 설명을 반환하는 함수를 작성하고, React가 어려운 부분을 파악하도록 할 수 있음을 의미했습니다. React는 이를 통해 데이터가 자주 변경될 때의 일관성 문제를 해결했고, 템플릿 구성을 훨씬 더 효율적으로 만들었습니다.
대규모 환경에서의 React와 한계점
React는 전 세계적으로 히트했고 업계의 표준이 되었는데, 심지어는 성능이 필요하지 않은 사이트에서도 사용하기 시작했지만, 규모가 커짐에 따라 다음과 같은 몇 가지 한계점을 보이기 시작했습니다.
CPU에 대한 실행
DOM은 React 모델의 문제 중 하나로 브라우저는 연속적인 렌더링 주기에서 DOM 노드를 지속해서 생성하고 파괴하도록 설계되지 않았기 때문에, 새로운 수준의 간접 참조를 통해 해결할 수 있는 다른 문제들 처럼 React 역시 가상 DOM 추상화를 시도했습니다.
하지만 웹사이트를 매끄럽게 느껴지도록 만들려면 사람들이 100ms 이하의 피드백을 인식해야 하는데, 스크롤과 같은 UI 작업을 수행할 때는 훨씬 더 낮아지기 때문에 이런 방식의 최적화는 단일 스레드 환경과 결합하여 대화형 애플리케이션의 새로운 병목 현상으로 나타났습니다.
가상 DOM과 실제 DOM 간의 조정이 일어나는 동안 대규모의 대화형 앱은 사용자의 입력에 반응하지 않게 되고, 이런 문제가 계속 나타나면서 Long task와 같은 용어가 등장했습니다. 이로 인해 2017년에는 동시 모드를 위한 개선이 포함된 전체적인 React 코드의 재작성이 이뤄졌습니다.
런타임 비용의 증가
더 빠르다는 것은 더 많은 코드를 전송한다는 것을 의미합니다. 코드의 재작성으로 React의 일부 문제는 해결되었지만, 브라우저들이 자바스크립트를 소화하기 위해 발생하는 느린 시작 시간이 또 다른 문제가 됐습니다. 그래서 개발자들은 HTML과 가상 DOM 뿐만 아니라 CSS를 작성하는 방법 등으로 인해 발생하는 모든 암묵적인 런타임 비용에 주목했고, 자바스크립트 컴포넌트 모델을 스타일을 컴포넌트와 함께 위치할 수 있게 하여 삭제 가능성을 향상시켰습니다.
이전에는 CSS 코드를 삭제하는 것이 두려운 일이었던 것과 달리, 새로운 컴포넌트 모델에서는 해당 컴포넌트만 삭제하면 간단하게 해결되었기 때문에 개발자들은 이런 방식을 선호하게 되었습니다.
종속과 그것의 특수성에 대한 문제들은 CSS in JS 라이브러리에 의해 추상화되었는데, 이러한 라이브러리에는 종종 암묵적인 런타임 비용이 수반됐습니다. 즉 컴포넌트가 렌더링 될 때까지 기다렸다가 해당 스타일을 페이지에 삽입하는 형태였기 때문에 자바스크립트 번들의 스타일링 문제가 이슈로 등장했습니다.
대규모 프로젝트에서 느린 성능은 매우 끔찍한 결과를 만들어 낼 수 있는 만큼, 이러한 비용이 발생하는 것을 막기 위해 스타일시트를 추출할 수 있는 더 고도화된 사전 컴파일러를 사용하게 되었고, 런타임 비용을 없애는데 중점을 둔 새로운 CSS in JS 라이브러리가 탄생했습니다.
네트워크 비효율성과 렌더링 차단 컴포넌트
브라우저가 HTML을 렌더링 할 때 CSS, JS 처럼 렌더링을 차단시키는 리소스는 HTML의 나머지 부분을 표시하지 못하게 만드는데, 이런 것들은 종종 부모 컴포넌트 계층의 자식 컴포넌트에 대한 렌더링 차단의 원인이 됩니다.
실제로 많은 컴포넌트가 데이터베이스의 데이터와 코드 스플리팅을 통한 CDN의 코드에 의존하고 있었는데, 이는 종종 순차적인 네트워크 요청 차단의 워터폴로 이어졌습니다. 렌더링 프로세스는 컴포넌트가 렌더링 한 후 데이터를 가져오고 비동기 하위 컴포넌트의 잠금을 해제한 후, 그다음 프로세스를 반복하여 필요한 데이터를 가져오게 되는데, 렌더링이 차단되면 UI가 로드될 때 화면에 나타나는 “무한 로딩” 또는 누적 레이아웃 이동과 같은 현상을 만나게 됩니다.
React는 이후 Suspense를 출시했는데, 데이터를 가져오기 위한 Suspense는 데이터를 가져올 때 렌더링하는 패턴을 허용하여 페이지 로드 단계를 원활하게 진행할 수 있도록 지원하는 것이었지만, 순차적 네트워크 워터폴의 근본적인 문제를 방지하는 것은 아니었습니다.
페이스북의 해결 방법
런타임 비용 최적화
React는 가상 DOM의 런타임 비용을 피할 수 없었는데, 동시 모드는 상호작용 경험에서 반응성을 유지하는 것에 대한 해답이었습니다.
CSS in JS의 영역에서는 Stylex라고 불리는 내부 라이브러리가 사용되었는데, 이것은 수천 개의 컴포넌트가 렌더링 될 때 런타임 비용 없이 효율적인 개발자 경험을 유지할 수 있습니다.
네트워크 최적화
페이스북은 Relay를 통해 순차적인 네트워크 워터폴 문제를 피했는데, 주어진 시작점에 대한 정적 분석은 로드해야 하는 코드와 데이터를 정확하게 결정했습니다. 즉, 최적화된 graphQL 쿼리에서 코드와 데이터를 병렬로 로드함으로써 초기 로딩 및 SPA 전환을 순차적 네트워크 워터폴보다 확연한 차이로 빠르게 표시할 수 있었던 겁니다.
자바스크립트 번들 최적화
번들 최적화의 근본적인 문제는 특정 사용자와 관련이 없는 자바스크립트를 전송하는 것으로, 이것은 언어와 로케일 설정을 포함하여 A/B 테스트, 기능 플래그 경험 및 특정 유형의 사용자와 집단을 위한 코드가 있는 경우에 발생합니다.
코드의 분기가 많은 경우 정적 의존성 그래프는 특정 사용자 집단에 대해 함께 사용되는 모듈을 확인하기 어려운데, 페이스북은 AI로 구동되는 동적 번들링 시스템으로 긴밀한 클라이언트-서버 통합을 활용하여 런타임 요청을 기반으로 최적의 종속성 그래프를 계산했고, 이는 우선순위에 따라 단계적으로 번들을 로드하는 프레임워크와 결합했습니다.
또 다른 생태계
페이스북은 수년간 구축된 복잡한 인프라와 자체 라이브러리를 갖추고 있었는데, 엄청난 양의 돈과 자원을 소비할 수 있었기 때문에 이러한 장단점들을 대규모로 최적화할 수 있었겠지만, 모든 개발자들이 페이스북과 같은 규모의 애플리케이션을 만들 수 있는 것은 아니었습니다.
여전히 많은 조직에서 성능은 중요한 주제이며, 가능한 상위 컴포넌트에서 데이터 가져오기, 네트워크 병렬화, 인라인 요구사항등과 같은 패턴들을 통해 해답을 찾을 수 있었습니다. 몇몇 빅 테크 기업들은 종종 자체적으로 개발한 애플리케이션 프레임워크를 내부적으로 사용하고 있었고, 많은 솔루션들이 다양한 사용자 공간과 라이브러리에 흩어져 있었기 때문에 결국 많은 사람들이 자바스크립트 생태계에 대한 피로감을 느끼게 하는 원인이 되었고, 프레임워크 번아웃으로 이어지는 상황이 됐습니다.
frontendmastery, “The new wave of Javascript web frameworks”