여기에서는 RenderingNG 구성요소가 설정되는 방식과 렌더링 파이프라인이 이를 통해 흐르는 방식을 확인할 수 있습니다.
가장 높은 수준에서 시작하여 렌더링 작업은 다음과 같습니다.
- 화면의 픽셀로 콘텐츠를 렌더링합니다.
- 콘텐츠의 한 상태에서 다른 상태로 시각 효과를 애니메이션 처리합니다.
- 입력에 응답하여 스크롤합니다.
- 개발자 스크립트 및 기타 하위 시스템이 응답할 수 있도록 적절한 위치에 효율적으로 입력을 라우팅합니다.
렌더링할 콘텐츠는 각 브라우저 탭의 프레임 트리와 브라우저 인터페이스입니다. 터치 스크린, 마우스, 키보드, 기타 하드웨어 기기의 원시 입력 이벤트 스트림도 있습니다.
각 프레임에는 다음이 포함됩니다.
- DOM 상태
- CSS
- 캔버스
- 이미지, 동영상, 글꼴, SVG와 같은 외부 리소스
프레임은 HTML 문서에 URL이 추가된 것입니다. 브라우저 탭에 로드된 웹페이지에는 최상위 프레임, 최상위 문서에 포함된 각 iframe의 하위 프레임, 재귀적인 iframe 자손이 있습니다.
시각 효과는 스크롤, 변환, 클립, 필터, 불투명도, 혼합 등 비트맵에 적용되는 그래픽 작업입니다.
아키텍처 구성요소
RenderingNG에서 이러한 작업은 여러 단계와 코드 구성요소에 걸쳐 논리적으로 분할됩니다. 구성요소는 다양한 CPU 프로세스, 스레드, 스레드 내 하위 구성요소로 끝납니다. 각각은 모든 웹 콘텐츠의 안정성, 확장 가능한 성능, 확장성을 달성하는 데 중요한 역할을 합니다.
렌더링 파이프라인 구조
렌더링은 파이프라인에서 진행되며 그 과정에서 여러 단계와 아티팩트가 생성됩니다. 각 단계는 렌더링 내에서 잘 정의된 작업 하나를 실행하는 코드를 나타냅니다. 아티팩트는 스테이지의 입력 또는 출력인 데이터 구조입니다.
단계는 다음과 같습니다.
- 애니메이션: 선언적 타임라인에 따라 계산된 스타일을 변경하고 시간 경과에 따라 속성 트리를 변경합니다.
- 스타일: DOM에 CSS를 적용하고 계산된 스타일을 만듭니다.
- 레이아웃: 화면에서 DOM 요소의 크기와 위치를 결정하고 변경 불가능한 프래그먼트 트리를 만듭니다.
- 사전 페인트: 속성 트리를 계산하고 적절한 경우 기존 디스플레이 목록 및 GPU 텍스처 타일을 invalidate합니다.
- 스크롤: 속성 트리를 변경하여 문서 및 스크롤 가능한 DOM 요소의 스크롤 오프셋을 업데이트합니다.
- 페인트: DOM에서 GPU 텍스처 타일을 래스터하는 방법을 설명하는 표시 목록을 계산합니다.
- 커밋: 속성 트리와 표시 목록을 컴포지터 스레드에 복사합니다.
- 계층화: 독립적인 래스터화 및 애니메이션을 위해 표시 목록을 합성된 레이어 목록으로 분할합니다.
- 래스터, 디코딩, 페인트 워크렛: 표시 목록, 인코딩된 이미지, 페인트 Worklet 코드를 각각 GPU 텍스처 타일로 변환합니다.
- 활성화: GPU 타일을 화면에 그리고 배치하는 방법을 시각 효과와 함께 나타내는 컴포지터 프레임을 만듭니다.
- 집계: 모든 표시된 컴포지터 프레임의 컴포지터 프레임을 단일 글로벌 컴포지터 프레임으로 결합합니다.
- 그리기: GPU에서 집계된 컴포저블 프레임을 실행하여 화면에 픽셀을 만듭니다.
렌더링 파이프라인의 단계는 필요하지 않으면 건너뛸 수 있습니다. 예를 들어 시각 효과와 스크롤의 애니메이션은 레이아웃, 사전 페인트, 페인트를 건너뛸 수 있습니다. 이것이 다이어그램에서 애니메이션과 스크롤이 노란색과 녹색 점으로 표시되는 이유입니다. 시각 효과를 위해 레이아웃, 사전 페인트, 페인트를 건너뛸 수 있는 경우 컴포지터 스레드에서 완전히 실행하고 기본 스레드를 건너뛸 수 있습니다.
브라우저 UI 렌더링은 여기에 직접 표시되지 않지만 이 동일한 파이프라인의 단순화된 버전으로 생각할 수 있습니다(실제로 구현은 코드의 대부분을 공유함). 동영상 (직접 묘사되지 않음)은 일반적으로 프레임을 GPU 텍스처 타일로 디코딩한 후 컴포저이터 프레임 및 그리기 단계에 연결하는 독립적인 코드로 렌더링됩니다.
프로세스 및 스레드 구조
CPU 프로세스
여러 CPU 프로세스를 사용하면 사이트 간에, 브라우저 상태와 간에 성능 및 보안 격리를 달성하고 GPU 하드웨어와 간에 안정성 및 보안 격리를 달성할 수 있습니다.
- 렌더링 프로세스는 단일 사이트 및 탭 조합의 입력을 렌더링, 애니메이션 처리, 스크롤, 라우팅합니다. 렌더링 프로세스는 여러 가지가 있습니다.
- 브라우저 프로세스는 브라우저 UI(주소 표시줄, 탭 제목, 아이콘 포함)의 입력을 렌더링, 애니메이션 처리, 라우팅하고 나머지 모든 입력을 적절한 렌더링 프로세스로 라우팅합니다. 브라우저 프로세스가 하나 있습니다.
- Viz 프로세스는 여러 렌더링 프로세스와 브라우저 프로세스의 합성 결과를 집계합니다. GPU를 사용하여 래스터링하고 그립니다. Viz 프로세스는 하나입니다.
서로 다른 사이트는 항상 서로 다른 렌더링 프로세스로 연결됩니다.
동일한 사이트의 여러 브라우저 탭 또는 창은 일반적으로 서로 관련이 있는 탭(예: 하나의 탭이 다른 탭을 열 때)이 아니라면 서로 다른 렌더링 프로세스로 이동합니다. 데스크톱 Chromium의 메모리 압력이 심한 경우 관련이 없더라도 동일한 사이트의 여러 탭을 동일한 렌더링 프로세스에 배치할 수 있습니다.
단일 브라우저 탭 내에서 서로 다른 사이트의 프레임은 항상 서로 다른 렌더링 프로세스에 있지만 동일한 사이트의 프레임은 항상 동일한 렌더링 프로세스에 있습니다. 렌더링 관점에서 여러 렌더링 프로세스의 중요한 이점은 교차 사이트 iframe과 탭이 서로 성능 격리를 달성한다는 것입니다. 또한 출처는 더 많은 격리를 선택할 수 있습니다.
일반적으로 그리기에 사용할 GPU와 화면은 하나뿐이므로 모든 Chromium에는 정확히 하나의 Viz 프로세스가 있습니다.
Viz를 자체 프로세스로 분리하면 GPU 드라이버 또는 하드웨어의 버그 발생 시 안정성을 유지하는 데 도움이 됩니다. Vulkan과 같은 GPU API 및 일반 보안에 중요한 보안 격리에도 좋습니다.
브라우저에는 여러 탭과 창이 있을 수 있으며, 모두 그릴 브라우저 UI 픽셀이 있으므로 브라우저 프로세스가 정확히 하나인 이유가 궁금할 수 있습니다. 한 번에 하나의 탭에만 포커스가 설정되기 때문입니다. 실제로 표시되지 않는 브라우저 탭은 대부분 비활성화되고 모든 GPU 메모리가 삭제됩니다. 그러나 복잡한 브라우저 UI 렌더링 기능 (WebUI라고 함)이 렌더링 프로세스에서 점점 더 많이 구현되고 있습니다. 이는 성능 격리 때문이 아니라 Chromium의 웹 렌더링 엔진의 사용 편의성을 활용하기 위한 것입니다.
이전 Android 기기에서는 WebView에서 사용할 때 렌더링 및 브라우저 프로세스가 공유됩니다. 이는 일반적으로 Android의 Chromium에는 적용되지 않으며 WebView에만 적용됩니다. WebView에서는 브라우저 프로세스도 삽입 앱과 공유되며 WebView에는 렌더링 프로세스가 하나만 있습니다.
보호된 동영상 콘텐츠를 디코딩하기 위한 유틸리티 프로세스도 있습니다. 이 프로세스는 이전 다이어그램에 표시되지 않습니다.
스레드
스레드는 느린 작업, 파이프라인 병렬화, 다중 버퍼링에도 불구하고 성능 격리와 응답성을 달성하는 데 도움이 됩니다.
- 기본 스레드는 스크립트, 렌더링 이벤트 루프, 문서 수명 주기, 히트 테스트, 스크립트 이벤트 전달, HTML, CSS 및 기타 데이터 형식 파싱을 실행합니다.
- 기본 스레드 도우미는 인코딩 또는 디코딩이 필요한 이미지 비트맵 및 blob 만들기와 같은 작업을 실행합니다.
- 웹 워커는 스크립트를 실행하고 OffscreenCanvas의 렌더링 이벤트 루프를 실행합니다.
- 합성기 스레드는 입력 이벤트를 처리하고, 웹 콘텐츠의 스크롤 및 애니메이션을 실행하고, 웹 콘텐츠의 최적 레이어화를 계산하고, 이미지 디코딩, 페인트 워크렛, 래스터 작업을 조정합니다.
- 합성기 스레드 도우미는 Viz 래스터 태스크를 조정하고 이미지 디코딩 태스크, 페인트 워크렛, 대체 래스터를 실행합니다.
- 미디어, 디뮤셔 또는 오디오 출력 스레드는 동영상 및 오디오 스트림을 디코딩, 처리, 동기화합니다. 동영상은 기본 렌더링 파이프라인과 동시에 실행됩니다.
기본 스레드 작업에서 애니메이션과 스크롤을 성능 격리하는 데는 기본 스레드와 컴포저 스레드를 분리하는 것이 매우 중요합니다.
동일한 사이트의 여러 탭 또는 프레임이 동일한 프로세스로 끝날 수 있지만 렌더링 프로세스당 기본 스레드는 하나만 있습니다. 그러나 다양한 브라우저 API에서 실행되는 작업과는 성능 격리가 있습니다. 예를 들어 Canvas API의 이미지 비트맵 및 블롭 생성은 기본 스레드 도우미 스레드에서 실행됩니다.
마찬가지로 렌더링 프로세스당 컴포지터 스레드는 하나만 있습니다. 일반적으로 하나만 있는 것은 문제가 되지 않습니다. 컴포저이터 스레드의 모든 비용이 많이 드는 작업은 컴포저이터 작업자 스레드 또는 Viz 프로세스에 위임되며 이 작업은 입력 라우팅, 스크롤 또는 애니메이션과 동시에 실행할 수 있기 때문입니다. 컴포저 작업자 스레드는 Viz 프로세스에서 실행되는 작업을 조정하지만 모든 곳의 GPU 가속은 드라이버 버그와 같이 Chromium에서 제어할 수 없는 이유로 실패할 수 있습니다. 이 경우 작업자 스레드는 CPU에서 대체 모드로 작업을 실행합니다.
컴포저 작업자 스레드 수는 기기의 기능에 따라 다릅니다. 예를 들어 데스크톱은 CPU 코어가 더 많고 휴대기기보다 배터리 제약이 적으므로 일반적으로 더 많은 스레드를 사용합니다. 다음은 확장 및 축소의 예입니다.
렌더링 프로세스 스레딩 아키텍처는 세 가지 최적화 패턴을 적용한 것입니다.
- 도우미 스레드: 장기 실행되는 하위 태스크를 추가 스레드로 전송하여 상위 스레드가 다른 동시 요청에 계속 응답하도록 합니다. 기본 스레드 도우미와 컴포지터 도우미 스레드가 이 기법의 좋은 예입니다.
- 다중 버퍼링: 새 콘텐츠를 렌더링하는 동안 이전에 렌더링된 콘텐츠를 표시하여 렌더링 지연 시간을 숨깁니다. 컴포저 스레드에서 이 기법을 사용합니다.
- 파이프라인 동시 로드: 렌더링 파이프라인을 여러 위치에서 동시에 실행합니다. 이렇게 하면 스크롤과 애니메이션이 빠르게 실행될 수 있습니다. 기본 스레드 렌더링 업데이트가 진행 중일 때도 스크롤과 애니메이션을 동시에 실행할 수 있습니다.
브라우저 프로세스
- 렌더링 및 컴포지션 스레드는 브라우저 UI의 입력에 응답하고 다른 입력을 올바른 렌더링 프로세스로 라우팅합니다. 브라우저 UI를 배치하고 페인트합니다.
- 렌더링 및 합성 스레드 도우미는 이미지 디코딩 작업을 실행하고 대체 래스터 또는 디코딩을 실행합니다.
브라우저 프로세스 렌더링 및 컴포지션 스레드는 렌더링 프로세스의 코드 및 기능과 유사하지만 기본 스레드와 컴포저 스레드가 하나로 결합된다는 점이 다릅니다. 이 경우에는 스레드가 하나만 필요합니다. 설계상 긴 기본 스레드 작업에서 성능을 격리할 필요가 없기 때문입니다.
시각화 프로세스
- GPU 기본 스레드 래스터는 목록과 동영상 프레임을 GPU 텍스처 타일로 표시하고 화면에 컴포지터 프레임을 그립니다.
- 디스플레이 컴포지터 스레드는 각 렌더링 프로세스와 브라우저 프로세스의 컴포지션을 집계하고 최적화하여 화면에 표시하기 위한 단일 컴포지터 프레임으로 만듭니다.
래스터링과 그리기는 일반적으로 동일한 스레드에서 실행됩니다. 둘 다 GPU 리소스를 사용하며 GPU를 안정적으로 멀티스레드로 사용하기 어렵기 때문입니다(GPU에 더 쉽게 멀티스레드로 액세스할 수 있다는 점은 새로운 Vulkan 표준을 개발한 동기 중 하나입니다). Android WebView에서는 WebView가 네이티브 앱에 삽입되는 방식으로 인해 그리기를 위한 별도의 OS 수준 렌더링 스레드가 있습니다. 향후 다른 플랫폼에도 이러한 스레드가 있을 수 있습니다.
디스플레이 컴포저이터는 항상 반응해야 하며 GPU 기본 스레드에서 속도가 느려질 수 있는 소스를 차단해서는 안 되므로 다른 스레드에 있습니다. GPU 기본 스레드에서 속도 저하의 원인 중 하나는 공급업체별 GPU 드라이버와 같이 Chromium이 아닌 코드를 호출하는 것입니다. 이 코드는 예측하기 어려운 방식으로 느려질 수 있습니다.
구성요소 구조
각 렌더링 프로세스 기본 스레드 또는 컴포저 스레드 내에는 구조화된 방식으로 서로 상호작용하는 논리적 소프트웨어 구성요소가 있습니다.
렌더링 프로세스 기본 스레드 구성요소
Blink 렌더기:
- 로컬 프레임 트리 프래그먼트는 로컬 프레임의 트리와 프레임 내 DOM을 나타냅니다.
- DOM 및 Canvas API 구성요소에는 이러한 모든 API의 구현이 포함되어 있습니다.
- 문서 수명 주기 실행기는 커밋 단계까지의 렌더링 파이프라인 단계를 실행합니다.
- 입력 이벤트 히트 테스트 및 전달 구성요소는 히트 테스트를 실행하여 이벤트의 타겟이 되는 DOM 요소를 찾고 입력 이벤트 전달 알고리즘과 기본 동작을 실행합니다.
렌더링 이벤트 루프 스케줄러 및 러너는 이벤트 루프에서 실행할 작업과 시점을 결정합니다. 기기 디스플레이와 일치하는 주기로 렌더링이 실행되도록 예약합니다.
로컬 프레임 트리 프래그먼트는 약간 복잡합니다. 프레임 트리는 기본 페이지와 하위 iframe을 재귀적으로 나타낸다는 점에 유의하세요. 프레임이 렌더링 프로세스에서 렌더링되는 경우 렌더링 프로세스에 로컬이고, 그렇지 않으면 원격입니다.
렌더링 프로세스에 따라 프레임 색상을 지정한다고 생각해 보세요. 위 이미지에서 녹색 원은 하나의 렌더링 프로세스의 모든 프레임이고 주황색 원은 두 번째 프로세스의 프레임이며 파란색 원은 세 번째 프로세스의 프레임입니다.
로컬 프레임 트리 프래그먼트는 프레임 트리에 있는 동일한 색상의 연결된 구성요소입니다. 이미지에는 사이트 A용 2개, 사이트 B용 1개, 사이트 C용 1개 등 4개의 로컬 프레임 트리가 있습니다. 각 로컬 프레임 트리는 자체 Blink 렌더기 구성요소를 가져옵니다. 로컬 프레임 트리의 블링크 렌더기는 다른 로컬 프레임 트리와 동일한 렌더링 프로세스에 있을 수도 있고 그렇지 않을 수도 있습니다. 앞서 설명한 대로 렌더링 프로세스가 선택되는 방식에 따라 결정됩니다.
렌더링 프로세스 컴포저러 스레드 구조
렌더링 프로세스 컴포지터 구성요소에는 다음이 포함됩니다.
- 합성된 레이어 목록, 표시 목록 및 속성 트리를 유지관리하는 데이터 핸들러입니다.
- 애니메이션, 스크롤, 합성, 래스터링, 렌더링 파이프라인의 단계를 디코딩하고 활성화하는 수명 주기 실행자입니다. 애니메이션과 스크롤은 기본 스레드와 컴포저이터 모두에서 발생할 수 있습니다.
- 입력 및 히트 테스트 핸들러는 합성된 레이어의 해상도에서 입력 처리 및 히트 테스트를 실행하여 컴포지터 스레드에서 스크롤 동작을 실행할 수 있는지와 어떤 렌더링 프로세스 조회 테스트가 타겟팅해야 하는지 확인합니다.
실제 아키텍처 예시
이 예에는 세 개의 탭이 있습니다.
탭 1: foo.com
<html>
<iframe id=one src="foo.com/other-url"></iframe>
<iframe id=two src="bar.com"></iframe>
</html>
탭 2: bar.com
<html>
…
</html>
탭 3: baz.com
html
<html>
…
</html>
이러한 탭의 프로세스, 스레드, 구성요소 구조는 다음과 같습니다.
렌더링의 네 가지 주요 작업에 관한 예시를 하나씩 살펴보겠습니다. 참고:
- 콘텐츠를 화면의 픽셀로 렌더링합니다.
- 콘텐츠의 시각 효과를 한 상태에서 다른 상태로 Animate합니다.
- 입력에 응답하여 스크롤합니다.
- 개발자 스크립트 및 기타 하위 시스템이 응답할 수 있도록 입력을 적절한 위치로 효율적으로 라우트합니다.
탭 1의 변경된 DOM을 렌더링하려면 다음을 실행합니다.
- 개발자 스크립트가 foo.com의 렌더링 프로세스에서 DOM을 변경합니다.
- Blink 렌더러는 컴포지터에 렌더링이 필요하다고 알립니다.
- 컴포저이터는 렌더링이 필요하다고 Viz에 알립니다.
- Viz는 렌더링 시작을 컴포지터에 다시 알립니다.
- 컴포지터는 시작 신호를 Blink 렌더러로 전달합니다.
- 기본 스레드 이벤트 루프 러너는 문서 수명 주기를 실행합니다.
- 기본 스레드가 결과를 컴포지터 스레드로 전송합니다.
- 컴포저 이벤트 루프 러너는 컴포지션 수명 주기를 실행합니다.
- 모든 래스터 작업은 래스터용 Viz로 전송됩니다 (이러한 작업은 2개 이상인 경우가 많음).
- 시각화는 GPU에서 콘텐츠를 래스터링합니다.
- 시각화가 래스터 작업의 완료를 확인합니다. 참고: Chromium은 래스터가 완료될 때까지 기다리지 않는 경우가 많으며 대신 15단계가 실행되기 전에 래스터 태스크에서 확인해야 하는 동기화 토큰을 사용합니다.
- 컴포지터 프레임이 Viz로 전송됩니다.
- Viz는 foo.com 렌더링 프로세스, bar.com iframe 렌더링 프로세스, 브라우저 UI의 컴포저이터 프레임을 집계합니다.
- 시각화가 무승부를 예약합니다.
- Viz는 집계된 컴포지터 프레임을 화면에 그립니다.
두 번째 탭에서 CSS 변환 전환을 애니메이션 처리하려면 다음 단계를 따르세요.
- bar.com 렌더링 프로세스의 컴포저러 스레드는 기존 속성 트리를 변경하여 컴포저러 이벤트 루프에서 애니메이션을 틱합니다. 그러면 컴포지터 수명 주기가 다시 실행됩니다. 래스터링 및 디코딩 작업이 발생할 수 있지만 여기에는 표시되지 않습니다.
- 컴포지터 프레임이 Viz로 전송됩니다.
- Viz는 foo.com 렌더링 프로세스, bar.com 렌더링 프로세스, 브라우저 UI의 컴포저이터 프레임을 집계합니다.
- Viz가 무승부를 예약합니다.
- Viz는 집계된 컴포지터 프레임을 화면에 그립니다.
3번 탭에서 웹페이지를 스크롤하려면 다음 단계를 따르세요.
- 일련의
input
이벤트 (마우스, 터치 또는 키보드)가 브라우저 프로세스에 들어옵니다. - 각 이벤트는 baz.com의 렌더링 프로세스 컴포저 스레드로 라우팅됩니다.
- 컴포저이터는 기본 스레드가 이벤트에 관해 알아야 하는지 결정합니다.
- 필요한 경우 이벤트가 기본 스레드로 전송됩니다.
- 기본 스레드는
input
이벤트 리스너(pointerdown
,touchstar
,pointermove
,touchmove
또는wheel
)를 실행하여 리스너가 이벤트에서preventDefault
를 호출할지 확인합니다. - 기본 스레드는
preventDefault
가 컴포저에 호출되었는지 여부를 반환합니다. - 그렇지 않으면 입력 이벤트가 브라우저 프로세스로 다시 전송됩니다.
- 브라우저 프로세스는 이 이벤트를 다른 최근 이벤트와 결합하여 스크롤 동작으로 변환합니다.
- 스크롤 동작이 baz.com의 렌더링 프로세스 컴포저 스레드로 다시 전송됩니다.
- 여기에서 스크롤이 적용되고 bar.com 렌더링 프로세스의 컴포저러 스레드가 컴포저러 이벤트 루프에서 애니메이션을 틱합니다.
그러면 속성 트리의 스크롤 오프셋이 변경되고 컴포저 수명 주기가 다시 실행됩니다.
또한 기본 스레드에
scroll
이벤트 (여기에 표시되지 않음)를 실행하도록 지시합니다. - 컴포지터 프레임이 Viz로 전송됩니다.
- Viz는 foo.com 렌더링 프로세스, bar.com 렌더링 프로세스, 브라우저 UI의 컴포지터 프레임을 집계합니다.
- 시각화가 무승부를 예약합니다.
- Viz는 집계된 컴포지터 프레임을 화면에 그립니다.
탭 1의 iframe 2의 하이퍼링크에서 click
이벤트를 라우트하려면 다음 단계를 따르세요.
input
이벤트 (마우스, 터치 또는 키보드)가 브라우저 프로세스로 전달됩니다. 근사 히트 테스트를 실행하여 bar.com iframe 렌더링 프로세스에서 클릭을 수신해야 하는지 확인하고 그곳으로 전송합니다.- bar.com의 컴포지터 스레드는
click
이벤트를 bar.com의 기본 스레드로 라우팅하고 렌더링 이벤트 루프 작업을 예약하여 처리합니다. - bar.com의 기본 스레드 입력 이벤트 프로세서는 테스트를 실행하여 iframe에서 클릭된 DOM 요소를 확인하고 스크립트가 관찰할
click
이벤트를 실행합니다.preventDefault
이 들리지 않으면 하이퍼링크로 이동합니다. - 하이퍼링크의 대상 페이지가 로드되면 이전 예의 '변경된 DOM 렌더링'과 유사한 단계를 통해 새 상태가 렌더링됩니다. 후속 변경사항은 여기에 표시되지 않습니다.
요약
렌더링의 작동 방식을 기억하고 내면화하는 데는 많은 시간이 걸릴 수 있습니다.
가장 중요한 점은 렌더링 파이프라인이 세심한 모듈화와 세부사항에 대한 주의를 기울여 여러 개의 독립형 구성요소로 분할되었다는 것입니다. 그런 다음 이러한 구성요소를 병렬 프로세스와 스레드로 분할하여 확장 가능한 성능 및 확장성 기회를 극대화했습니다.
각 구성요소는 최신 웹 앱의 성능과 기능을 지원하는 데 중요한 역할을 합니다.
계속해서 RenderNG에 코드 구성요소만큼 중요한 주요 데이터 구조를 읽어보세요.
삽화: 우나 크라베츠